目录
1. 再谈构造函数
1.1构造函数题赋值
1.2初始化列表
初始化列表有什么用呢?
1.3 explicit关键字
2. Static成员
2.1概念
2.2特性
3. 友元
3.1友元函数
3.2友元类
4. 内部类(了解)
5.匿名对象
6.拷贝对象时的一些编译器优化
学习目标
- 1. 再谈构造函数
- 2. Static成员
- 3. 友元
- 4. 内部类
- 5.匿名对象
- 6.拷贝对象时的一些编译器优化
1. 再谈构造函数
1.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 Date { //初始化列表 public: Date(int year, int month, int day):_year(year), _month(month), _day(day) {}private: int _year; int _month; int _day; };
注:
1. 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
2. 类中包含以下成员,必须放在初始化列表位置进行初始化:
- 引用的成员变量
- const成员变量
- 自定义类型成员(且该类没有构造函数)
原因:--引用的成员变量:引用必须在定义的时候初始化
--const成员变量:const必须在定义的时候初始化(只有一次机会)
--自定义类型成员(没有默认构造函数)必须初始化:和下面类似,若Date类里面有一个自定义类型的成员变量 Time _t ,若其没有适合的构造函数,会使得Date的实例化不了,所以其必须初始化
3. 尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化
4.成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
示例:
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 随机值
答案是D,类里先声明了_a2,先初始化_a2,_a2是用_a1初始化(_a1此时是随机值),
然后初始化_a1, _a1用1初始化
所以最后,_a1 = 1, _a2 = 随机值;
初始化列表有什么用呢?
原来的构造函数是函数体内初始化,总会面临一些不好处理的情况(如下),因此引入初始化列表解决问题
--可以用来处理默认构造函数不适配的情况,当一个类(Time)给了一个不适配的构造函数,我们可以用初始化列表处理这个类
问题引入:现给一个Time类,提供一个带参数的构造函数,Date类里面包含Time类,我们该如何初始化Time类?
1.正常情况:Time类没有默认构造函数,编译器要处理自定义类型,会生成一个默认构造函数,但编译器又不会处理内置类型
示例:
class Time { public://不给构造函数 private:int _hour; };class Date { public:private:int _year;Time _t; };int main() {Date d1;//Date和Time类都不给默认构造函数return 0; }
这里看起来编译器什么都没做:
实际上--用Date去实例化d1,它的成员变量_year(不处理),
_t(自定义类型Time)--去调用它的默认构造函数,但Time没给默认构造函数,所以编译器自动生成一个默认构造函数,但其不会处理内置类型,Time的成员变量又是内置类型
所以看起来编译器什么都没做
2.Time类给一个不适配的构造函数:
class Time { public:Time(int hour) //给个不适配的构造函数{_hour = hour;} private:int _hour; };class Date { public:private:int _year;//Time _t; };int main() {Date d1;return 0; }
这里就会发现,我们处理不了Time类,并且实例化不了Date类
然后我们使用初始化列表解决:
class Time { public:Time(int hour) //不适配的构造函数{_hour = hour;} private:int _hour; };class Date { public://要初始化_t只能通过初始化列表Date(int year, int hour):_t(hour)//初始化列表解决自定义类型成员变量没有构造函数的情况{_year = year;}private:int _year;Time _t; };int main() {Date d(2023,1);//给Date构造函数传值return 0; }
这里就解决了,自定义类型的成员变量没有构造函数的情况
总结
1.自定义类型成员,推荐使用初始化列表初始化
2.初始化列表可以认为是成员变量定义的地方
1.3 explicit关键字
功能:explicit是阻止隐式类型转换的
隐式类型转换:
int i = 10; double d = i; //会产生临时变量tmp,把i转换为double类型后再拷贝给d
验证:
int i = 10; double& d = i; //这里引用是引用的临时变量,临时变量具有常性,不能引用
这里加个const也行,因为现在是权限的平移(const 修饰的变量不能修改,常量也不能修改,加上const后再引用也不能修改)
有什么用呢?示例:
现在加上const:
这里做到的优化:可以不用构造一个string对象就能传参
也说明了:传参尽量用引用,用引用尽量加上const
传引用相比于传值调用:不用创建临时变量去拷贝,会更快一些
接下来看下面这段代码:
class Date { public://构造函数Date(int year):_year(year){cout << "Date(int year)" << endl;}//拷贝构造Date(const Date& d) {cout << "Date(const Date& d)" << endl;}private:int _year; };int main() {Date d1(2023);//直接调用构造Date d2 = 2023;//构造 + 拷贝构造 + 优化 ==》 直接调用构造(隐式类型转换)return 0; }
1-- Date d1(2023);//直接调用构造
2--Date d2 = 2023;//构造 + 拷贝构造 + 优化 ==》 直接调用构造(隐式类型转换)2中会先创建一个Date类型的临时变量tmp,然后把2023转换为Date类型拷贝构造给d2
现在在构造函数上为其加上 explicit关键字,就能阻止隐式类型的转换
class Date { public://构造函数explicit Date(int year)//加上explicit:_year(year){cout << "Date(int year)" << endl;}//拷贝构造Date(const Date& d) {cout << "Date(const Date& d)" << endl;}private:int _year; };int main() {Date d1(2023);//直接调用构造Date d2 = 2023;//构造 + 拷贝构造 + 优化 ==》 直接调用构造(隐式类型转换)return 0; }
2. Static成员
2.1概念
声明为static的类成员称为类的静态成员:
用static修饰的成员变量,称之为静态成员变量;
用static修饰的成员函数,称之为静态成员函数。
注:静态成员变量一定要在类外进行初始化
问题1:实现一个类,来计算程序创建了多少个类对象
class A { public:A() { ++_count; }//构造函数A(const A& a) { ++_count; }//拷贝构造函数~A() { --_count; }//析构函数//private:static int _count;//声明int _a; };//类外定义初始化 int A::_count = 0;
2.2特性
- 1. 静态成员为所有类对象所共享,也属于类,不属于某个具体的对象,存放在静态区
- 2. 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
- 3. 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
- 4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
- 5. 静态成员也是类的成员,受public、protected、private 访问限定符的限制
静态成员为所有类对象所共享:
A a1; A a2; A a3;
这里使用A类实例化了3个对象,_count是静态成员变量,这3个类对象共享
,_a是每个对象独立有的
若static修饰的成员变量是私有的,我们该怎么访问呢?
--可以使用static修饰的成员函数来获取:
class A { public:A() { ++_count; }//构造函数A(const A& a) { ++_count; }//拷贝构造函数~A() { --_count; }//析构函数//静态成员函数 --- 没有this指针static int GetCount() {return _count;}private://静态成员变量,属于整个类,在静态区static int _count;//声明int _a; };//类外定义初始化 int A::_count = 0;int main() {A a1;cout << A::GetCount() << endl;return 0; }
问题:
1. 静态成员函数可以调用非静态成员函数吗?--不能,静态成员函数没有隐藏的this指针,不能访问任何非静态成员
2. 非静态成员函数可以调用类的静态成员函数吗?--可以,静态成员函数为所有类对象所共享
2.设计一个只能在栈上定义的对象的类
示例
class StackOnly { public:static StackOnly CreateObj() {StackOnly s;return s;}private:StackOnly()//构造函数:_x(0),_y(0){} private:int _x;int _y; };int main() {//显示定义的构造函数不加private,创建的对象可以在栈,静态区//StackOnly s1; //栈上//static StackOnly s2;//静态区上//显示定义构造函数并加上private,让其只能通过调用成员函数来创建对象//要调用这个函数我们加上static即可StackOnly s = StackOnly::CreateObj();return 0; }
3. 友元
说明:友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用
分类:友元函数和友元类
3.1友元函数
功能:友元函数可以直接访问类的私有成员,
语法:它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。
示例:
class A {friend int sum(const A& a); public:private:int _a;int _b; };int sum(const A& a) {return a._a + a._b; }
补充:
- 友元函数可访问类的私有和保护成员,但不是类的成员函数
- 友元函数不能用const修饰
- 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
- 一个函数可以是多个类的友元函数
- 友元函数的调用与普通函数的调用原理相同
3.2友元类
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
- 友元关系是单向的,不具有交换性。
如:Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接
访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。
- 友元关系不能传递,如果C是B的友元, B是A的友元,则不能说明C时A的友元
4. 内部类(了解)
概念:如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。
注意:内部类就是外部类的友元类,参见友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。
特性:
1. 内部类可以定义在外部类的public、protected、private都是可以的。
2. 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。
3. sizeof(外部类)=外部类,和内部类没有任何关系。示列
class A { private: static int k; int h; public: class B // B天生就是A的友元 { public: void foo(const A& a) { cout << k << endl;//OK cout << a.h << endl;//OK } }; }; int A::k = 1; int main() {A::B b;b.foo(A());return 0; }
5.匿名对象
语法:
Date(2023);//匿名对象
特点:生命周期只有这一行
作用:当我们只想调用类里面的函数的时候,就可以使用匿名对象,而不用实例化对象
//使用匿名对象调用类里的成员函数Date().PrintYear(2023);//实例化对象调用类里的成员函数Date d1(2023);d1.PrintYear(2023);
6.拷贝对象时的一些编译器优化
在传参和传返回值的过程中,一般编译器会做一些优化,减少对象的拷贝
优化:连续一个表达式步骤中,连续的构造一般都会优化:例如构造+拷贝构造 --> 构造
class A { public:A(int a = 0)//构造:_a(a){cout << "A(int a)" << endl;}A(const A& aa)//拷贝构造:_a(aa._a){cout << "A(const A& aa)" << endl;}A& operator=(const A& aa)//运算符重载{cout << "A& operator=(const A& aa)" << endl;if (this != &aa){_a = aa._a;}return *this;}~A()//析构{cout << "~A()" << endl;} private:int _a; };void f1(A aa) {}A f2() {A aa;return aa; } int main() {// 传值传参A aa1;f1(aa1);cout << endl;// 传值返回f2();cout << endl;// 隐式类型,连续构造+拷贝构造->优化为直接构造f1(1);// 一个表达式中,连续构造+拷贝构造->优化为一个构造f1(A(2));cout << endl;// 一个表达式中,连续拷贝构造+拷贝构造->优化一个拷贝构造A aa2 = f2();cout << endl;// 一个表达式中,连续拷贝构造+赋值重载->无法优化aa1 = f2();cout << endl;return 0; }