初始化列表
虽然构造函数能帮助我们完成初始化,但其实也就是构造函数赋值而已,根本不算真正的初始化。
所以,祖师爷就提出了一个初始化列表,用来初始化成员变量。
注意
每个成员变量在初始化列表中最多出现一次(初始化只能初始化一次)
你可以再初始化列表里不初始化成员变量,但是不可以初始化两次,最多出现一次
如下图,这是不被允许的
类中包含以下成员,必须放在初始化列表位置进行初始化:
- 引用成员变量
- const成员变量
- 自定义类型成员(且该类没有默认构造函数时)
上面三个成员都有一个特点就是再创建的时候必须被初始化,这是c++规定的。对于引用和const我们清楚,const修饰的变量具有常性,必须再定义的时候初始化,引用也必须初始化,他不可以给空指针。但是自定义类型呢?对于自定义类型是看情况的,如果你的自定义类型没有默认构造函数,那我们就无法自动调用构造函数。那怎么办呢?根据定义又必须要初始化。所以,初始化列表接很好的解决了问题。初始化列表就是专门用来初始化成员变量的地方。
这里注意,初始化列表位置在构造函数上紧紧相连,他是构造函数的一部分,这是语法上的结构绑定。必须先使用初始化列表,才能执行构造函数。所以,对于一般的内置类型你可以直接就在初始化列表解决就行了。
但是这里有一个坑,初始化列表的执行顺序是和成员变量申明的顺序一样的。所以,建议初始化列表里的语句顺序和申明顺序一样。否则可能会出错。
大家看一下这段代码有问题吗?
答案是有问题的,由于执行顺序,_a会被先执行,但是_capacity又没有初始话值是随机的,这时开辟出来的空间你能确定吗?就会有一个内存泄漏的问题。而且是不会报错的,只会运行崩溃,这是很难受的。
那这时候大家可能就会有一个问题,那我们还要构造函数干什么呢?既然都可以靠初始化列表解决。答案是构造函数肯定是有用的。虽然在大部分的时候都会直接用初始化列表,但是我们在有些情况还是会用到构造函数的。
比如,你要动态开辟你一个二维数组,如果没有构造函数,你如何遍历循环呢?你又如何检查开辟是否成功呢?
这里不要非此即彼,在编程中其实没有那么多对立想法?大多时候都是基于实际情况的,要灵活运用。
对于初始化列表和构造函数的关系,大家要记住,一定是先执行初始化列表,再执行构造函数。如果你在初始话列表里没有显式初始化一个内置类型成员变量,那么初始化列表是不会对这个成员变量初始化的。值是随机的。
隐式类型转换
这个词大家因该不陌生,对于c语言来说是个运用很广的词。尤其是在int转换成double的时候。但这是属于内置类型的转换。c++提供了对于自定义类型的转化。
看以下这段代码,第一个是构造函数没问题。那第二个呢?这是什么,其实这里发生了隐式类型转换,int通过临时变量转换成一个自定义类型。如果这个大家看得陌生。那大家看看下面的这段代码
相信对于学过一点c++和java的人来说,肯定很是熟悉。但是知道为什么要这样写吗?string是c++的一个类,既然是类为什么可以直接赋值一个const char*呢?这就是隐式类型转换,但是大家要注意这里从语法看是先调用构造函数再调用构造拷贝函数。但是对于编译器看,连续调用两次编译器,会直接优化,直接调用一次构造函数,基于效率考虑。
explicit
那有什么办法可以取消掉这种隐式类型转换,答案就是在构造函数的前面加上一个explicit关键字,就可以取消掉
Static成员
我们把用static修饰的成员变量叫静态成员变量,static修饰的成员函数叫静态成员函数。
注意:静态成员函数是没有this指针的。静态不可以调用非静态的,只可以调用静态的。非静态的可以调用分静态的和静态的。这里主要是this指针的一个问题。由于,静态成员函数是没有this指针的,那你如果想要在静态函数里调用非静态函数,是需要传参this指针的。没有怎么传。所以,就不能调用非静态。但是对于非静态函数就没有这个烦恼。
这里主要是一个内存的问题。由于静态变量存储在在静态区,他不会随着对象的生命周期,而是在程序结束的时候,销毁。所以,静态变量不是属于对象,而是属于类。你可以把它理解为,静态变量是一个中央空调,谁都可以用。但是它不属于每一个人,而是属于公共的。由于他是属于公共的,所以他也不能在类里面使用构造函数和初始化列表,注意缺省值也是不能给的。因为缺省值是由初始化列表使用的。那真的不能给缺省值吗?其实加一个const修饰就可以了。但这里是为什么呢。大家可以先不用管这个,这个涉及到更复杂的语法。(这个不是祖师爷搞的,是组委会设计的)
那如何给静态变量定义呢,答案就是在全局变量赋值。既然是在全局变量赋值,那为什不用全局变量呢?所以,这里要加上私有权限符,这里还会有疑问?既然都是私有了?还怎么访问呢?答案就是,在全局定义,这个是规定。
- 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
- 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
- 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
- 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
- 静态成员也是类的成员,受public、protected、private 访问限定符的限制
下面是一个经典的static使用案例
只能在把类定义在堆上 或者 栈上
这里就很好的运用了static修饰的成员函数了
友元
友元函数
由于封装的性质,导致我们不能直接使用类里面的成员。但有些时候我们又要直接使用这些成员。所以,友元就诞生了。把一个普通函数,在类里面声明成友元函数就可以直接使用类里的成员,不受访问限定符的限制。
声明格式:在声明函数的前面加上一个friend关键字。注意,友元函数的声明只是一个普通的函数声明,他不是类的成员,不受访问限定符的影响。你可以把它放在任何位置,只要你喜欢。
比较经典的使用案例就是 流提取和流插入的运算符重载
这里是因为这两个运算符的重载,第一个参数必须是istream或者ostream,因此我们不能把他定义在类里面。但是又要使用类里面的私有成员,所以这里用友元函数就可以很好的解决这个问题。但是,由于友元破坏了封装。我们也不是很建议使用友元函数。可以在类里面定义一个get函数,返回私有成员。隔壁java就很喜欢这个用法。
友元类
和友元函数一样,都是定义在全局,在类里面声明成友元。同理,友元类也是不受访问限定符的影响。但是我们也讲过,这种做法破环了封装。所以,友元其实也不是很受欢迎。因此,大家了解一下就行了。
注意
友元是单向的,不是双向的。大家可以理解为,我把你当朋友,但是你可未必把我当成你的朋友。
内部类
- 把一个类定义在一个类的里面,而不是定义在全局,我们管这个类叫内部类。
- 内部类不属于它的外部类,不算外部类的成员,是外部类的友元函数。
- 内部类受外部类访问限定符的影响。要想实例化内部类,必须先通过外部类的类域和限定符的指定
内部类的使用在c++其实很少,c++不太喜欢使用内部类。隔壁的java很喜欢用。
匿名对象
这里和匿名结构体可不一样。大家不要搞混概念。c++允许你在实例化对象的时候,不指定对象命。直接使用域名,注意这里虽然可以不用写对象名。但是需要加一个(),由于这里是匿名对象,它的使用和正常对象是一样的,都是使用.操作符。为了区分,需要加一个括号。要不然你一个类名直接使用.操作符访问成员。这是什么鬼,没有这种用法。
匿名对象的生命周期仅局限在它哪一行代码。但是如果是const引用,可以延长他的生命周期。这里是灵活运用的。匿名对象的产生就是为了即用即销毁。但是如果你是需要多次使用,那我就给你延长生命周期。这里是根据使用需求看的。大家可以想想祖师爷这个设计逻辑有问题吗?其实祖师爷在设计这些东西的时候,是考虑了很多的。我们要长远的看待这些东西。肯定是有他的用法的。
比如这里的隐式转换,如果你刚看觉得好想用处不是很大,但是你现在看看这段代码呢?就是因为隐式转换,我们可以直接使用函数输出对象,根本不需要再创建对象,然后初始化。