[C++]类和对象(下)
文章目录
1. 构造函数2.0
构造函数,将对象中的成员变量初始化,这种初始化叫构造函数体赋值。
这种初始化时有缺陷的,因为它的初始化只能称为赋初值。对于const的值,它是无法初始化的。
所以,这里引入了初始化列表。
1.1 初始化列表
初始化列表是专门用来初始化成员变量的。
1.1.2 特性
- 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
- 类中包含以下成员,必须放在初始化列表位置进行初始化:
- 引用&成员变量
- const成员变量
- 自定义类型成员(且该类没有默认构造函数时)
-
自定义类型,一定会先使用初始化列表初始化。(可以解决函数调用构造函数,因参数列表出现的问题)
下面代码,我将无参数构造屏蔽,来猜猜编译器会输出什么?class Time { public: //无参构造函数 //Time() //{ // cout << "Time()" << endl; //} //有参构造函数 Time(int a) { cout << " Time(int a)" << endl; } }; class Date { public: Date(int year, int month, int day) : _year(year) , _month(month) , _day(day) { Time t1; } private: int _year; int _month; int _day; }; int main() { Date d1(2023, 5, 23); }
Time没有合适的默认构造函数???
原因是,t1实例化去调用它的默认构造函数,又没有传入参数且有显式构造函数,编译器一时间就无法作出选择,总不能去调用有参的构造函数,参数是随机值吧。
所以一定要在初始化列表初始化自定义对象。
-
成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后 次序无关 。(习惯按照声明的顺序定义成员变量)
再来看看下面代码,乍一看语法是没问题的?
class A
{
public:
A(int a)
:_num1(a)
, _num2(_num1)
{
}
private:
int _num2;
int _num1;
};
int main() {
A aa(1);
}
实际上,_num2被赋值成随机值了。
原因是:编译器是先去声明_num2,然后在初始化列表初始化,但此时 _num1还没初始化,是随机值,给 _num2赋值成随机值了。
所以,平常最好保持声明初始化变量的顺序和初始化列表成员的位置是一致的。
2. explicit关键字
2.1 隐式类型转换
在对对象赋值初始化的时候,会存在隐式类型转换。
如下图,将dd1初始化1。编译器会先将1构造一个临时变量,然后通过这个临时变量对dd1进行拷贝构造,最终完成dd1的初始化。(编译器在这里进行了两部操作,构造和拷贝构造)。
但是实际上,聪明的编译器在这里进行了优化,从输出看到,这里执行了构造和析构,优化掉了拷贝构造。
既然,编译器输出没有拷贝构造,我又如何知道它的本质是构造加上拷贝构造,而不是单纯的编译优化后的构造呢?
来,上代码!
105行,代码是报错了,报错的理由是无法从“int”转化为“Date&”,很明显编译器已经给出答案了。
正因为隐式类型转换才导致了出现这个报错理由。
107行,众所周知常数是具有常性的,而权限是不能放大的,所以给dd3来个const,权限平移,问题就解决了,输出的结果是构造和析构,也间接证明,编译器存在优化的现象。
2.2 禁用隐式类型转换
禁止这种转化也相当简单,在构造函数前加个explicit。
禁用了转化,这里也就无法这样子实例化对象了。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t2BMzxfU-1684844397322)(image-20230523194445250.png)]
3. static静态变量
声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量,用static修饰的函数,称之为静态成员函数
用static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化
3.1 特性
- 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
- 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
- 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
- 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
- 静态成员也是类的成员,受public、protected、private 访问限定符的限制
- 声明不能给缺省值,他不走初始化列表
- 非静态可以访问静态
- 静态不是在对象里面,所以sizeof不计算静态变量
4. 友元
友元提供了一种突破封装的方式,能直接访问私有成员。友元有分为友元函数和友元类。
4.1友元函数
- 友元函数可以直接访问类的私有成员
- 定义在类外部的普通函数,要在类里面声明,声明前加friend
- 友元函数不能用const修饰
- 一个函数可以有多个友元函数
4.2友元类
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员 。
- 友元关系是单向的,不存在交换,也不能继承。
5. 内部类
简单来说,就是类中类。
5.1特性
- 内部类天生是外部类的友元,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。
- 内部类可以在外部类任意的访问限定符内定义
- 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。
- sizeof(外部类)=外部类,和内部类没有任何关系,内部类在外部类看来只是一个声明
- 在访问内部类时,不能直接内部类::内部类对象,要外部类::内部类::内部类对象
6.匿名对象
匿名对象,顾名思义它没有名字。
- 调用的时候,要用类型().函数名()
- 即用即销毁(声明周期到下一行结束)
- 匿名对象无法引用,具有常性
- 加上const修饰时,声明周期会发生变化,会延长到当前函数作用域结束
int main()
{
//定义
Date(2);
//调用函数
Date().Print();
return 0;
}