一.初始化列表
1.为什么会有初始化列表
我们在Date中添加了两种成员变量:
分别是引用类型和const类型
为什么编译器会报错呢?
是不是因为编译器默认生成的构造函数不行呢?
那我们自己去实现一下怎么样?
还是不行:它说引用和const类型的对象定义时必须初始化
对啊,因为引用不能改变指向,所以必须在初始化引用的时候就要指定好对象
const类型的变量的值是不能修改的,因此初始化时也必须设好值
那么我们应该怎么办呢?
针对于这个问题C++创始人规定了初始化列表这一语法:
为了解决有些成员变量在定义的时候必须初始化!!!
2.初始化列表的语法形式
Date(int year = 1, int month = 2, int day = 3) :_year(year) , _month(month) , _day(day) , _ref(_year) , _cint(_year) { //函数体内可以继续进行代码的书写 }; 第一个是冒号 后面是逗号
这样我们的程序就可以通过了
其实初始化列表还解决了下面这个问题
3.没有默认构造函数的自定义成员变量
那么问题来了:
如果这个Stack类没有默认构造函数呢?
会发生编译错误
那么怎么办呢?
其实我们仔细想一想:
这个MyQueue类中的Stack类没有了默认构造函数,不就意味着这个Stack类在我们这个MyQueue类定义的时候必须初始化吗?
所以这个时候初始化列表就派上用场了
MyQueue(int capacity1, int capacity2,int size) :_st1(capacity1) ,_st2(capacity2) ,_size(size) { };
这样就可以解决这个问题了
其实就算是你这个Stack类有默认构造函数,但是如果我就是想自己去传参调用你的这个默认构造函数.也是可以这样做的
如果没有初始化列表这一语法,我们在声明的时候去调用Stack类的传参构造函数呢?
会直接报错
4.初始化列表是成员变量定义的地方
那么初始化列表到底是什么身份呢?
这么强大
初始化列表是成员变量定义的地方
我们之前提到过:
也就是说只要你这个类有成员变量,就一定会走构造函数,只要你走构造函数,就一定会走构造函数当中的初始化列表
而成员变量就是在初始化列表中定义的,也就是在初始化列表中分配的空间!!
5.初始化列表可以跟函数体内定义搭配使用
那么既然初始化列表这么强大,可不可以不要函数体了呢?
构造函数只留一个初始化列表不就行了吗?
当然不可以
比如:下面的Stack类
Stack(int capacity = 1) :_a((int*)malloc(sizeof(int)*capacity)) ,_capacity(capacity) ,_top(0) {};
你这不是也可以吗,这是好的情况
万一我申请空间太大,申请失败了呢?
wzs::Stack st(1000000000000000000);
我们在这里直接申请这么大的空间
那么该怎么办呢?
在初始化列表里面去检查吗?
初始化列表是定义成员变量给成员变量开辟空间的地方,
你在里面检查_a是否等于空指针
你这不是大材小用吗
直接让_a在函数体内初始化它不香吗?
也就是这样
Stack(long long capacity = 1) :_capacity(capacity) ,_top(0) { _a = ((int*)malloc(sizeof(int) * capacity)); if (_a == nullptr) { perror("malloc fail"); exit(-1); } };
wzs::Stack st(1000000000000000000); • 1
开辟空间失败:
开辟空间成功:
wzs::Stack st(10);
也就是说初始列表和构造函数的函数体是相辅相成的,
类似的关系:引用和指针
所以日常中:对于成员变量来说
大多数情况下我们都是用初始化列表搞定
剩下那些只能用函数体去初始化的就用函数体去搞定
6.初始化列表执行的顺序
成员变量在类中声明次序就是其在初始化列表中的初始化顺序,
与其在初始化列表中的先后次序无关
大家可以看一下这个题,这是《剑指offer》上的一道题目
答案:
n1:随机值
n2:0
因为成员变量在类中声明次序就是其在初始化列表中的初始化顺序,
所以n1先被初始化,然后n2才被初始化
也就是说
它们在初始化列表中的执行次序如下:
n1 = n2+2; n2 = 0;
而n1和n2在还没有被初始化时都是随机值
因此n1就是随机值,而n2被初始化为了0
7.总结+建议
这个注意当中的第一条和这个初始化列表跟函数体内定义的区别这一条:
也就是说如果有人这么写:
也就是说初始化列表中,成员变量只能初始化一次,因此叫做初始化列表
而函数体中,成员变量可以赋值很多次,所以在函数体中只能被称为赋值初值,而不能被称为初始化!!!
也就是说对于我们的这个构造函数来说
Date(int year = 1, int month = 2, int day = 3) : _ref(_year) , _cint(_year) { _year=year; _month=month; _day=day; }; private: int _year; int _month; int _day=26; int& _ref; const int _cint;
本质上编译器是这样进行的:
Date(int year = 1, int month = 2, int day = 3) :_year(随机值) ,_month(随机值) ,_day(缺省值26) ,_ref(_year) , _cint(_year) { //下面是重新对_year,_month,_day进行再次赋值 _year=year; _month=month; _day=day; }; private: int _year; int _month; int _day=26; int& _ref; const int _cint;
也就是说构造函数中:
初始化列表是成员变量定义的地方,每一个成员变量都需要走初始化列表
如果在初始化列表当中我们没有显式定义该成员变量,就会使用该成员变量声明时给的缺省值,如果没有缺省值,那么就会使用编译器给的随机值
函数体内定义其实本质上是给成员变量进行第二次赋值
也就是像这样
int var = 随机值; //初始化列表 var=1;//函数体内定义
其实翻来覆去就只有一句话:初始化列表是成员变量定义的地方
函数体内是成员变量可以进行二次赋值的地方
二.静态成员变量和静态成员函数
我们出现了一个需求:计算一个类实例化出了多少个对象
1.static成员变量的引出
其实方法很简单:只需要定义一个全局变量count=0,然后在这个类的所有的构造函数当中都让这个count++
最后count的数值就是该类实例化出的对象的个数
这样是可行的,但是并不好
为什么呢?
因为这个变量count是一个全局变量,也就是说这个count可以在程序的任意位置访问并修改
所以有可能会出现这种情况:
这个func函数中它成功地把我count这个全局变量给修改了
导致最后得出的答案少了1
也就是我们无法避免这种极端情况的发生
那怎么办呢?
而且把这个count定义为成员变量也是不行的
因为每一个对象都有自己的count,都是++的自己的count
那么有没有一种成员变量是为我这个类实例化出的所有的对象所共享的呢?
static成员变量就出现了
2.static成员变量的特性
既然我们了解了static成员变量的特性
3.对于解决以上需求: static成员变量的不足
那么我们该怎么解决上面那个需求呢?
但是还是有一个问题
我在这里是把_count这个静态成员变量的访问属性设置为公有了,但是也防不住会出现下面这种情况:
是,你是把_count这个全局变量放到了你A这个类的内部
但是你把它的访问属性设置为了公有,
因此我func想改你这个_count,我只需要加上你A这个类域,我依然能改,你拦不住我
那怎么办呢?
把_count放到私有属性下: