C++|C++ 【类和对象: 初始化列表,Static成员 --3】

目录

1.自定义类型中构造函数的问题
1.2 初始化列表
1.3 explicit关键字:禁止类型转换
总结:单参数的构造函数支持隐式类型转换
1.4 编译器优化
2.静态成员概念
3.内部类

1.自定义类型中构造函数的问题
由于构造函数的性质是:对内置类型不做处理,对自定义类型调用他的构造函数

当B没有写默认构造函数,提示报错:A”不具备相应的 默认构造函数,原因是A调用不到B的默认构造函数
classB { public: B(int b) { _b = b; } private: int _b; }; class A { private: int _a; B b; };



当我们想用带参构造去A中构造对象B,再使用赋值传递给B,让B初始化时,编译器报错:B不存在默认构造函数
classB { public: B(int b) { _b = b; } private: int _b; }; class A { public: A(int a, int b) { _a = a; //_b1._b = b; 无法访问私有 B b2(b); _b1 = b2; } private: int _a; B _b1; }; int main() { A a(1,1); return 0; }

C++|C++ 【类和对象: 初始化列表,Static成员 --3】
文章图片

以上创建构造函数的方法是:函数体内初始化。但函数体内初始化会面临一些难以解决的问题,相比在函数体内赋值,提供了新方法来初始化:初始化列表

1.2 初始化列表
以一个冒号开始,接着以后是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式
以下成员必须使用初始化列表初始化:
1.引用成员变量和 const成员变量。原因在于:初始化只能初始化一次,他们必须在定义的时候初始化
2.自定义类型成员(且该类没有默认构造函数时)

注意事项:每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
自定义类型成员,必须在初始化列表初始化
要初始化_b1,只能通过初始化列表;或者写了默认构造函数,就可以不写初始化列表
classB { public: B(int b) { _b = b; } private: int _b; }; class A { public: A(int a, int b) :_b1(b)//类型已经声明过:B _b1 ;初始化列表就像调用构造函数一样,只是前面没加B类型而已 { _a = a; _b1._b = b; 无法访问私有 //B b2(b); //_b1 = b2; } private: int _a; B _b1; }; int main() { A a(1,1); return 0; }


尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。
总结:初始化列表可以认为是成员变量定义的地方,每个成员都会在初始化列表初始化,不写初始化列表编译器也默认存在初始化列表。 统一的建议是内置类型也推荐使用初始化列表

传引用和const的写法,ref也是x和y的别名,修改ref也会修改y
classB { public: B(int b) { _b = b; } private: int _b; }; class A { public: A(int a,int b, int& x) :_a(a) ,_b1(b) ,_ref(x) ,_N(10) { _a = a; _ref++; } private: int _a; B _b1; int& _ref; const int _N; }; int main() { int y = 0; A a(2,1,y); return 0; }


C++11中在声明打的补丁的缺省值,给的是初始化列表(初始化时没有显示给值就会用这个缺省值)
private: int _a = 0; B _b1 = 0;



成员变量在类中是按照声明顺序来初始化,与其在初始化列表中的先后顺序无关(谁先声明先初始化)
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(); return 0; }

输出结果是:1,随机值
原因在于先调用a2(a1),a1还没有初始化,赋随机值给a2,再初始化a1

1.3 explicit关键字:禁止类型转换
d1和d2的差别是什么? 虽然都是去调用构造函数初始化,但是过程不一样,原因在于:隐式类型转换
隐式类型转换中间都会产生临时变量,i并不是直接给d,而是有一个临时变量double接收i的值,再把临时变量值赋给d
int i =10; double d = i;

对double 加引用不可以,因为d不是引用i而是引用临时变量,临时变量具有常性,不加const是权限放大,加上const编译成功
int i =10; double& d = i; //错误 const double& d = i;



d1是直接调用构造函数
d2是 构造+拷贝构造+优化 ==直接调用构造函数
class Date { public: Date(int year) :_year(year) { cout << "Date(int year)" << endl; } Date(const Date& d) { cout << "const Date& d" << endl; }private: int _year; }; int main() { Date d1(2022); Date d2 = 2022; return 0; }

隐式类型转化的原因是Date有单参数的构造函数,d2拿2022构造一个Date的临时对象,再用临时对象拷贝构造d2,但是编译器默认会直接优化为构造

为了验证情况,explicit关键字可以修饰构造函数,禁止类型转换
C++|C++ 【类和对象: 初始化列表,Static成员 --3】
文章图片



d3并不是引用了2022,而是引用中间产生的临时变量
class Date { public: Date(int year) :_year(year) { cout << "Date(int year)" << endl; } Date(const Date& d) { cout << "const Date& d" << endl; }private: int _year; }; int main() { Date d1(2022); //Date d2 = 2022; const Date& d3 = 2022; return 0; }

总结:单参数的构造函数支持隐式类型转换

匿名对象
直接写类名,只能定义一次,生命周期只有一行,结束后立刻析构
使用场景:只需要调用对象中函数

1.4 编译器优化
编译器会对以下情况进行优化:
1.单参数的构造函数会进行隐式类型转换

2.f1(w1)传值传参会产生拷贝,是一次构造+一次拷贝构造
class W { public: W(int x = 0) { cout << "W()" << endl; } W(const W& w) { cout << "W(const W& w)" << endl; } W& operator=(const W& w) { cout << "W& operator=(const W& w)" << endl; return *this; } ~W() { cout << "~W()" << endl; } }; void f1(W w) {}int main() { W w1; f1(w1); return 0; }

C++|C++ 【类和对象: 初始化列表,Static成员 --3】
文章图片

所以建议在传参的时候使用传引用和const,就没有拷贝构造
void f2(const W& w) {}




f1(W());
f1函数需要W的对象,传一个匿名对象。本来应该是构造+拷贝构造,编译器优化成直接构造
void f1(W w) {}int main() { W w1; f1(W()); return 0; }

结论:连续的一个表达式步骤中,连续构造一般都会优化(拷贝构造也是构造,合二为一)

f3的调用应该是构造+传值返回拷贝构造
W f3() { W ret; return ret; }


W w1 = f3()是一次构造,两次拷贝构造(ret拷贝做返回值,再拷贝给w1)
W f3() { W ret; return ret; }int main() { f3(); //1构造,1拷贝 cout << endl << endl; W w1 = f3(); return 0; }

C++|C++ 【类和对象: 初始化列表,Static成员 --3】
文章图片

结果却是一次构造,一次拷贝;其中有一个拷贝构造函数被优化,优化发生在ret临时对象拷贝返回值被优化,ret返回值直接给w1(在f3栈帧结束前,ret就把拷贝值给w1)


如果分开写步骤w2,就变成了一构造,一拷贝,一赋值
W f3() { W ret; return ret; }int main() { f3(); //1构造,1拷贝 cout << endl << endl; W w1 = f3(); //1构造,1拷贝W w2; w2 = f3(); //一构造,一拷贝,一赋值 return 0; }

C++|C++ 【类和对象: 初始化列表,Static成员 --3】
文章图片


<<深度探索C++对象模型>>有讲这种情况

以下代码共调用多少次拷贝构造函数
?Widget f(Widget u){Widget v(u); Widget w=v; return w; }main(){Widget x; Widget y=f(f(x)); }


C++|C++ 【类和对象: 初始化列表,Static成员 --3】
文章图片


C++|C++ 【类和对象: 初始化列表,Static成员 --3】
文章图片


如果不发生优化:应该是1次构造,9次拷贝构造
在release版本下:是一次构造,5次拷贝构造(Widget w = v优化,编译器觉得w没有价值,可以直接用u做返回值)


2.静态成员概念
声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;静态成员变量属于类也属于类中所有对象
用static修饰的成员函数,称之为静态成员函数。

静态成员特性
1. 静态成员属于整个类,也属于这个类的所有对象。不属于某个具体的对象,存放在静态区
2.静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
3.类静态成员即可用 类名::静态成员 或者 对象.静态成员(并不在对象中找,.只是帮助突破类域,在类中找) 来访问
4.静态成员函数没有隐藏的this指针,不能访问任何非静态成员,只能访问静态成员
5.静态成员也是类的成员,受public、protected、private 访问限定符的限制

静态本质上是满足全局变量的封装问题;
想访问静态成员变量,不想通过对象去调用,而是指定类域调用,可以使用静态成员函数
特性说明
链接错误:只有声明。
普通成员定义在类定义时也定义出来,普通成员在初始化列表时初始化
class A { public: A() { ++_scount; } A(const A& t) { ++_scount; } private: static int _scount; //声明 }; int main() { A a1; A a2; return 0; }

【C++|C++ 【类和对象: 初始化列表,Static成员 --3】】C++|C++ 【类和对象: 初始化列表,Static成员 --3】
文章图片


静态成员不能在初始化列表初始化,无法初始化静态成员;缺省值也给不了
C++|C++ 【类和对象: 初始化列表,Static成员 --3】
文章图片


静态成员必须在类外定义初始化
C++|C++ 【类和对象: 初始化列表,Static成员 --3】
文章图片



实现一个类,计算程序中创建出了多少个类对象。
有时候需要全局数据,但是使用全局变量十分危险(容易被修改,多文件使用麻烦等情况),这种情况下就需要用到静态成员(等同于创建专门给类使用的全局变量)
每个对象定义,要么构造要么拷贝构造,合计++即可统计

class A { public: A(){ ++_scount; } A(const A& t) { ++_scount; } static int GetCount() { return _scount; }private: static int _scount; }; int A::_scount = 0; int main() { A a1; A a2; cout << A::GetCount() << endl; return 0; }


求1+2+3+...+n_牛客题霸_牛客网
要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
利用构造函数的特性来完成累加
class Sum { public: Sum() { _ret +=_add; _add++; } static int GetRet() { return _ret; } private: static int _ret; static int _add; }; int Sum::_ret = 0; int Sum::_add = 1; class Solution { public: int Sum_Solution(int n) { Sum a[n]; return Sum::GetRet(); } };


设计一个只能在栈上定义的类
如果不使用static,直接调用StackCreate,就产生了歧义
class StackOnly { StackOnly() {}public: static StackOnly StackCreate() { StackOnly s; return s; //使用传值返回,会再拷贝一份 } private: int _a = 1; int _b = 1; }; int main() { StackOnly s1 = StackOnly::StackCreate(); //静态成员函数没有this指针,不需要使用对象去调用 return 0; }


3.内部类
把一个类定义到另一个类的内部。
注意:内部类是外部类的友元类。内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元
内部类受外部类的类域限制,访问限定符

sizeof外部类A的大小是4,不算内部类B。可以想象B就是定义在外部,只是具有以上两个特性
class A { private: int _h; public: class B { public: private: int _b; }; }; int main() { cout << sizeof(A) << endl; // 4 return 0; }

    推荐阅读