目录
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.拷贝对象时的一些编译器优化
构造函是编译器调用其时,给对象中各成员变量一个合适的初始值
class Date { public: Date(int year, int month, int day) { _year = year; _month = month; _day = day; } private: int _year; int _month; int _day; };
调用该函数后,对象中的成员变量会得到一个初始值,但是不能叫初始化
原因:初始化只能初始化一次,构造体函数内可以多次赋值(上述操作只能被成为赋值)
语法:以一个冒号(:)开始,用逗号(‘,’)分割成员列表,成员变量的后面跟一个放在括号中的初始值或表达式
示例:
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<
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.初始化列表可以认为是成员变量定义的地方
功能: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; }
声明为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;
- 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; }
说明:友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用
分类:友元函数和友元类
功能:友元函数可以直接访问类的私有成员,
语法:它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加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修饰
- 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
- 一个函数可以是多个类的友元函数
- 友元函数的调用与普通函数的调用原理相同
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
- 友元关系是单向的,不具有交换性。
如:Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接
访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。
- 友元关系不能传递,如果C是B的友元, B是A的友元,则不能说明C时A的友元
概念:如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。
注意:内部类就是外部类的友元类,参见友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。
特性:
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; }
语法:
Date(2023);//匿名对象
特点:生命周期只有这一行
作用:当我们只想调用类里面的函数的时候,就可以使用匿名对象,而不用实例化对象
//使用匿名对象调用类里的成员函数 Date().PrintYear(2023); //实例化对象调用类里的成员函数 Date d1(2023); d1.PrintYear(2023);
在传参和传返回值的过程中,一般编译器会做一些优化,减少对象的拷贝
优化:连续一个表达式步骤中,连续的构造一般都会优化:例如构造+拷贝构造 --> 构造
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; }