0
点赞
收藏
分享

微信扫一扫

『python爬虫』20. 用协程爬取一本小说(保姆级图文)

后来的六六 2023-05-14 阅读 81

【C++】类与对象(3)

目录

构造函数理解

构造函数不能称作为类对象成员的初始化,构造函数体中的语句只能将其称作为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值。
于是如何对类成员进行初始化?
于是引入初始化列表

初始化列表(是构造函数的一部分)

初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。
例:

class Date
{
public:
	Date(int year,int month,int day)
		:_year(year)
		,_month(month)
		,_day(day)
	{}
private:
	int _year;
	int _month;
	int _day;
};
  • 问题1:既然有构造函数为什么要设计初始化列表?
    有些成员变量必须在初始化列表初始化:
  1. A _aobj;(没有默认构造函数的类)
  2. int& _ref;(引用类型)
  3. const int _n;(const修饰的类型)
    解释:
    以上三种类型都必须在定义的时候初始化,而构造函数的本质并不是初始化而是赋值(函数体内赋值),而初始化列表的执行在构造函数之前,是真正意义上的初始化,所以初始化列表是对象成员定义的位置。
  • 问题2:对成员变量给缺省值和初始化列表冲突吗?
    例:
class Date
{
public:
	Date(int year,int month,int day)
		:x(2)
	{}
private:
	int x=1;
};

解释:
如果在初始化列表中显示地给了x的值,缺省值就失效了,用初始化列表来对x初始化,如果初始化列表中没有对x进行初始化,就会使用缺省值。

  • 问题3如何理解没有默认构造函数的类只能用初始化列表初始化呢?
    例:
class A
{
public:
	A(int a)
	{
		_a = a;
	}
private:
	int _a;
};
class B
{
public:
	B(int b)
		:_b(b)
		, a(2)
	{}
private:
	int _b;
	A a;
};

解释:
这里说的没有默认构造函数(无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数)不是指编译器自动生成的默认构造函数,而是指无参构造函数、全缺省构造函数。这里A类写了构造函数所以编译器不会生成无参的构造函数,如果A没有初始化列表,初始化时没有参数传递,加上没有对应的构造函数(无参或全缺省),编译器会报错。

  • 运用:用初始化列表初始化栈:
    注:由于是举例说明,并没有具体实现Stack类。
class MyQueue
{
public:
	MyQueue()
	{}

	MyQueue(int capacity)
		:_pushst(capacity)
		,_popst(capacity)
	{}
private:
	Stack _pushst;
	Stack _popst;
};
  • 注意1:注意引用的初始化!
    举例:
class A
{
public:
	A(int a,int& ref)//注意ref类型,不能为int类型
		:_ref(ref)
		,_n(1)
	{}
private:
	int& _ref;
	int _n;
};

1. 初始化列表中ref必须是引用类型,如果是局部变量会导致ref销毁,_ref初始化后变为野引用
2. ref不能为常量(数字等const类型数据),常量作为参数会导致权限放大,编译器报错。

这说明引用并不是绝对安全的

  • 注意2:注意初始化列表的初始化顺序!
    初始化列表的初始化顺序是成员变量的定义顺序,与在初始化列表的先后顺序无关!
    以下为错误用例:
    在这里插入图片描述
    由于A类中成员变量的定义顺序为_a2 _a1 ,所以先对_a2进行初始化,再对_a1进行初始化,而不是初始化列表中的顺序(先_a1再_a2)。
    所以初始化列表中变量的顺序最好和成员变量的顺序保持一致,防止出现初始化错误。

  • 注意3:初始化列表并不能代替函数体赋值
    以下功能等初始化列表并不能很好实现:

  1. 用malloc开辟空间(虽然初始化列表括号里能使用malloc,但初始化列表并不能对空指针进行检查)
  2. 对数组进行初始化并检查空指针
  3. 动态开辟二维数组(循环+指针数组+动态开辟空间)

总有工作是初始化列表无法很好完成的,但建议优先考虑使用初始化列表
【总结】

explicit关键字

构造函数不仅可以构造与初始化对象,对于单个参数的构造函数,还具有类型转换的作用。
引入:

  • 在C++中,将整形赋值给对象是可以的。
    如:A aa = 2;
    这里的整形2发生了隐式类型转换(整形转换为自定义类型),既然发生了类型转换就一定会产生临时变量(这里产生了A类型的临时变量,再进行拷贝构造)。
    过程:2构造一个A的临时对象,临时对象再拷贝构造aa。但是构造之后再进行拷贝构造过程较为繁琐,有些编译器会对这个过程进行优化,优化成直接进行构造。(编译器会优化连续的构造函数+拷贝构造)
    以VS2019为例:
    在这里插入图片描述
    两者调用的都是构造函数,说明编译器进行了优化。
    同一行一个表达式(分开写不会优化)中连续的构造+拷贝构造,优化为合二为一(也有合三为一)(《深度探索C++对象模型》)
    【总结】
  • 问题1:既然直接进行构造函数那还会产生临时变量吗?
    在这里插入图片描述
    286行代码正确,而287行代码错误,说明仍然产生了临时变量。
    所以我们可以理解为编译器的优化是直接构造函数,但语法上仍产生了临时变量。
  • 问题2:为什么C++能实现对整形的隐式类型转换成自定义类型?
    举例:在string类中可以将字符串转化为string类,如果要将字符串插入到链表中可以直接将字符串作为string类的参数(隐式类型转换)。
  • 如果我们不想让隐式类型转换发生我们可以用explicit关键字
    将explicit关键字加在只有一个参数的构造函数的开始,来防止发生隐式类型转换。
    在这里插入图片描述

static成员

概念:声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数。静态的成员变量一定要在类外进行初始化

成员变量和静态成员变量区别

成员变量属于每一个类对象,存储在对象里面。
静态成员变量属于类,类的每一个对象共享。生命周期为全局,存储在静态区
如果对象里有静态成员变量那么它必须在类外面定义,因为静态成员变量是共享的,不是属于单独的具体的一个类,如果在类里面定义可能会发生多次初始化从而报错。
定义方式:
在这里插入图片描述
按理来说在类外面不能访问私有变量,这是特例。
如果在类外要访问私有的静态变量,可以用Get成员函数,或者用友元函数,或者改为public

成员函数和静态成员函数区别

成员函数:有this指针,
静态成员函数:无this指针,指定类域和访问限定符(. 和** :: ** 都可以)就可以访问(可以突破类域)
由于静态成员函数没有this指针,所以无法访问非静态的成员变量(无法指定某个类),但可以访问静态成员变量

【总结】

【问题】

  1. 静态成员函数可以调用非静态成员函数吗?(不能)
  2. 非静态成员函数可以调用类的静态成员函数吗?(可以)
C++11 的成员初始化新玩法。

C++11支持非静态成员变量在声明时进行初始化赋值,但是要注意这里不是初始化,这里是给声明的成员变量缺省值。

友元

友元分为:友元函数和友元类
友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。

友元函数

形式friend + 函数声明;
友元函数并不是类成员,因此声明的位置不固定(一般在类开头声明)
友元函数在之前的博客【C++】类与对象(2补充运算符重载,const成员)(博客链接)中流插入流提取中有提到:
我们尝试去重载operator<<,然后发现我们没办法将operator<<重载成成员函数。因为cout的输出流对象和隐含的this指针在抢占第一个参数的位置。this指针默认是第一个参数也就是左操作数了。但是实际使用中cout需要是第一个形参对象,才能正常使用。所以我们要将operator<<重载成全局函数。但是这样的话,又会导致类外没办法访问成员,那么这里就需要友元来解决。operator>>同理

友元函数:可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。
说明:友元函数并不是特别好,因为友元函数一定程度上破坏了类的封装,(像Java这样的语言不太推荐用友元Java常用get成员函数和set成员函数)

【总结】

友元类

形式friend class + 类名;
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。

  • 友元关系是单向的,不具有交换性。
    比如Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time
    类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。
  • 友元关系不能传递
    如果B是A的友元,C是B的友元,则不能说明C时A的友元。
内部类(Java常用)

概念及特性
概念:如果一个类定义在另一个类的内部,这个内部类就叫做内部类。注意此时这个内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去调用内部类。外部类对内部类没有任何优越的访问权限。
注意:==内部类就是外部类的友元类。==注意友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。(也就是内部类可以访问外部类成员,而外部类不能访问内部类成员)
内部类运用示例:
在这里插入图片描述
内部类用于

【总结】:

匿名对象

在这里插入图片描述
如果只需要调用一次类里面的成员函数,可以使用匿名对象来访问成员函数,如果需要多次访问成员函数就不能使用匿名对象,匿名对象调用后就立即销毁(生命周期只在当前行,有名对象生命周期在局部域)。
注意无论是有参调用构造函数还是无参调用构造函数都必须加括号。
在这里插入图片描述

A.print(10);是错误的写法,因为==类型不能调用函数。==匿名对象和普通对象传参一样,只是少了名字。

const延长匿名对象的生命周期

匿名对象是可以被引用的,但需要加const(匿名对象具有常性!!!),const延长了匿名对象的生命周期,使其不会变为野引用。
在这里插入图片描述

  • 301行正确,302行错误(权限放大)。
  • 之后顺序表和链表等会用到const延长匿名对象生命周期这一特性。(push一个匿名对象,作为push函数中的参数:const string& s)
理解封装

C++是基于面向对象的程序,面向对象有三大特性即:封装、继承、多态
C++通过类,将一个对象的属性与行为结合在一起,使其更符合人们对于一件事物的认知,将属于该对象的所有东西打包在一起;通过访问限定符选择性的将其部分功能开放出来与其他对象进行交互,而对于对象内部的一些实现细节,外部用户不需要知道,知道了有些情况下也没用,反而增加了使用或者维护的难度,让整个事情复杂化


【C++】类与对象入门部分完结

举报

相关推荐

0 条评论