构造函数的初始化列表
初始化列表
我们之前说,构造函数等于给整个对象初始化,如果遇到这种情况呢?
#include <iostream> using namespace std; class A { public: A() { _a = 10; _b = 20; _c = 30;//这个成员是无法被改变的 } private: int _a; int _b; const int _c;//const属性的变量必须要初始化 }; int main() { A s; return 0; }
在调用构造函数的时候,就不是初始化成员变量了,而是给成员变量赋值。
C++中成员变量的初始化是在这里。
using namespace std; class A { public: A() :_a(1) , _b(2) , _c(3) { _a = 10; _b = 20; _a = 23; } void print() { cout << _a << endl; cout << _b << endl; cout << _c << endl; } private: int _a; int _b; const int _c; }; int main() { A s; s.print(); return 0; }
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。
构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值。
我们发现,const修饰的成员变量成功初始化了,并且构造函数里面也确实能赋值成员变量。
但是初始化列表不一定能解决所有的问题,比如你要开辟一块空间需要检查指向的位置是否为空指针。所以初始化列表和构造函数内部混着用是最好的。
还要注意的一点就是:如果成员变量有缺省值,但是初始化列表也初始化了这个变量,那么就该成员的值就是初始化列表的值。
我们可以理解为成员变量是声明,初始化列表是定义,就算不写初始化列表也会走初始化列表。
自定义类型与初始化列表
我们在定义一个变量的时候可以不赋值,但是const和引用时必须赋值的,在类中自定义类型也是必须进行初始化的,如果你没写类的初始化,那么它就会调用它的默认构造函数,如果没有默认构造就报错。
#include <iostream> using namespace std; class N { public: N() :_a(0) {} private: int _a; }; class A { public: A(int x) : _b(x) , _c(3) { } private: N _a; int& _b; const int _c; }; int main() { A s(2); return 0; }
如果不是默认构造函数,那就只能在初始化列表中写出自定义类型的初始化了。
#include <iostream> using namespace std; class N { public: N(int x) :_a(x) {} private: int _a; }; class A { public: A(int x) : _b(x) , _c(3) , _a(10) { } private: N _a; int& _b; const int _c; }; int main() { A s(2); return 0; }
尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。
注意:成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关:
#include <iostream> using namespace std; class A { public: A(int x) :_b(x) ,_a(_b)//先初始化_a,这时的成员_b还没有初始化 { } void print() { cout << _a << ' ' << _b << endl; } private: ; int _a; int _b; }; int main() { A s(2); s.print(); return 0; }
explicit关键字
构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值其余均有默认值
的构造函数,还具有类型转换的作用。
#include <iostream> using namespace std; class N { public: N(int x) :_a(x) { } private: int _a; }; int main() { N s1(2022); N s2 = 2022;//2022是一个整形,对象s2是一个自定义类,但是编译通过了 }
这里涉及到了一个隐形转换,就像从int类型转到double类型一样。
int i = 10; double j = i;//这里会产生一个临时变量,然后赋给j,产生的临时变量是常量 const double& x = i;//因为是常量,所以引用的时候属于权限放大,需要加一个const才行
这里支持整形赋值到s2是因为构造函数中有一个单参的整形,也就是说相当于整形2022通过N类的构造函数创建了一个临时对象然后拷贝到了新创建的对象s2中,这里本来的步骤是构造+拷贝构造,但是编译器优化成了直接构造。
如果你不想发生这种隐形的转换,可以用explicit关键字放在构造函数前:
#include <iostream> using namespace std; class N { public: explicit N(int x) :_a(x) { } private: int _a; }; int main() { N s1(2022); N s2 = 2022; }
- 单参构造函数,没有使用explicit修饰,具有类型转换作用explicit修饰构造函数,禁止类型转换—explicit去掉之后,代码可以通过编译。
- 如果是多个参数,要分情况:
N(int x,int y = 10, int z = 20);
N(int x = 4,int y = 10, int z = 20);
- 多参构造函数
#include <iostream> using namespace std; class N { public: N(int x, int y, int z) { _a = x; _b = y; _c = z; } void print() { cout << _a << ' ' << _b << ' ' << _c << endl; } private: int _a; int _b; int _c; }; int main() { N s1(1, 2, 3); N s2 = { 4,5,6 }; s1.print(); s2.print(); return 0; }
原理和前面是一样的,当然这里的构造函数加一个explicit也是编译不能通过的。
static成员
声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化。
静态成员变量
之前说过,全局的静态在哪里都能用,局部的静态只能在局部用,那么类里面的静态只能在类里用,并且是类的对象通用(但是要注意不是在对象里面储存,是在静态区)。
例如,我们要计算某个程序中的构造函数被调用了多少次。
#include <iostream> using namespace std; class N { public: N(int x = 0) { _a = x; ++n; } void print()const { cout << _a << endl; } private: int _a; public: static int n; }; int N::n = 0;//不能再构造函数初始化,不然会被反复初始化 //静态成员可以在类外进行初始化 void www(const N& s) { s.print(); } void yyy(const N& s) { } int main() { N s1; www(10);//这里就等于将10传给函数www中的对象s cout << N::n << endl; N s2 = 10; yyy(s2); cout << N :: n << endl; //还有一些奇怪的访问方式 cout << s1.n << endl;//这里编译器会认为s1是N类,n在s1里面 N* p = nullptr; cout << p->n << endl;//这里虽然指向的是空指针,但是并没有解引用p,因为n在静态区 return 0; }
静态成员函数
上面的代码我们将静态成员变量设置为了共有,其实一般都是私有,那么设置成私有我们就无法再类外访问了,而静态成员函数就很好的解决了这个问题。
这里要注意,静态成员函数没有this指针,只能访问静态成员。
#include <iostream> using namespace std; class N { public: N() :_a(10) { ++n; } static int retu()//静态成员函数 { return n; } private: int _a; static int n; }; int N::n = 0; int main() { N s; cout << N::retu() << endl;//不用通过对象进行访问 return 0; }







