一、初始化列表
1.什么是初始化列表
类的初始化分为 在内部的初始化 和 初始化列表。
对于类,我们要初始化类的成员变量,就需要定义一个对象,这叫做对象实例化,是对象的整体定义。那么要对 对象中的每个成员变量定义初始化的话,就要走初始化列表,并且所有成员变量都要先走初始化列表!
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。
初始化列表在构造函数的函数头 和 实现{}的之间,并且成员变量在初始化列表只能出现一次。
日期类:
class Date { public: Date(int year, int month, int day) : _year(year) , _month(month) , _day(day) {} private: int _year; int _month; int _day; };
1.所有成员变量初始化都要先走初始化列表
对于内置类型,如果没有显示的初始化列表,就会用随机值,有显示的初始化列表,则按照初始化列表进行初始化。
对于自定义类型,如果没有显示初始化列表,那么就会调用它的默认构造函数,这个过程都是发生在初始化列表中!
2.初始化列表和函数体内的初始化可以混着来:(但只有初始化列表不能解决的问题)
栈类:
class Stack { public: /*Stack(int capacity = 4) :_a((int*)malloc(sizeof(int)*capacity)) , _top(0) , _capacity(capacity) { if (_a == nullptr) { perror("malloc fail"); exit(-1); } }*/ // 初始化列表和函数体内初始化可以混着来 Stack(int capacity = 4) : _top(0) , _capacity(capacity) { _a = (int*)malloc(sizeof(int)*capacity); if (_a == nullptr) { perror("malloc fail"); exit(-1); } memset(_a, 0, sizeof(int)*capacity); } ~Stack() { cout << "~Stack()" << endl; free(_a); _a = nullptr; _top = _capacity = 0; } void Push(int x) { // .... // 扩容 _a[_top++] = x; } private: int* _a; // 声明 int _top; int _capacity; };
Stack(int capacity = 4) :_a((int*)malloc(sizeof(int)*capacity)) //初始化列表 , _top(0) , _capacity(capacity) { if (_a == nullptr) { perror("malloc fail"); exit(-1); } } 但如果需要将初始化完成的动态内存空间进行初始化,那就需要混着来: Stack(int capacity = 4) : _top(0) , _capacity(capacity) { _a = (int*)malloc(sizeof(int)*capacity); if (_a == nullptr) { perror("malloc fail"); exit(-1); } memset(_a, 0, sizeof(int)*capacity); }
2.为什么要初始化列表
必须要有初始化列表的三种情况:
1.const修饰的成员变量;2.没有默认构造函数的自定义类型;3.引用类型
1.const修饰的成员变量
class A { public: A() { _n = 1; } private: const int _n; //声明 };
我们知道,const修饰的变量必须初始化,且只能在定义的时候初始化,且只能初始化一次,之后不能修改。
但在这里,对于const修饰的成员变量,没有显示初始化列表,而_n=1;这是在赋值,但n只能在定义的时候初始化,之后不能修改,所以我们必须用到初始化列表!
class A { public: A() :_n(1) {} private: const int _n; //声明 };
2.没有默认构造函数的自定义类型
栈和队列:
class Stack { public: //默认构造 Stack(int capacity = 4) //若Stack(int capacity),则没有默认构造 : _top(0) , _capacity(capacity) { _a = (int*)malloc(sizeof(int)*capacity); if (_a == nullptr) { perror("malloc fail"); exit(-1); } memset(_a, 0, sizeof(int)*capacity); } ...... private: int* _a; // 声明 int _top; int _capacity; }; class MyQueue { public: MyQueue() {} void push(int x) { _pushST.Push(x); } private: Stack _pushST; //自定义类型 Stack _popST; size_t _size = 0; }; int main() { MyQueue q; }
对于自定义类型 :自定义类型若初始化列表中无显示的初始化,自定义类型就会调用默认构造函数,若没有默认构造函数,就需要在初始化列表中显示初始化,否则报错!
没有默认构造情况下: 若Stack(int capacity),则没有默认构造
class MyQueue { public: MyQueue() : _pushST(5) , _popST(5) {} ...... private: Stack _pushST; //自定义类型 Stack _popST; size_t _size = 0; };
3.引用类型
引用类型和const修饰的成员变量是一样的,都是能在定义的时候初始化。
class A { public: A(int a) :_a(a) {} private: int _a=1;//缺省值 }; class B { public: B(int a, int b) :_b(a) ,_c(b) ,_n(10) {} private: A _b; // 没有默认构造函数 int& _c; // 引用 const int _n; // const };
成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后
次序无关:
class A { public: A(int a) :_a1(a) ,_a2(_a1) {} void Print() { cout<<_a1<<" "<<_a2<<endl; } private: int _a2; int _a1; }; int main() { A aa(1); aa.Print(); } A. 输出1 1 B.程序崩溃 C.编译不通过 D.输出1 随机值
试一下,选什么??
声明次序就是其在初始化列表中的初始化顺序
那么先初始化_a2,但因为 ,_a2(_a1),_a1还没有初始化,所以_a1就是随机值。:_a1(a)到_a1初始化时,把a的值传过去,就是1。选D.
总结:
内置类型:在初始化列表中没有写显式初始化,就会用随机值初始化,若有缺省值,则就会用缺省值,若有显示初始化,则不会用缺省值。
自定义类型成员在初始化列表中,没有显式初始化,且没有默认构造函数,则需要在初始化列表中显示!