类和对象 Ⅱ
🔗接上篇【Ⅰ】类和对象的定义、构成、this指针
👉【C++入门】类和对象Ⅰ:类的定义、访问限定符、实例化、对象的大小、this 指针(含面试题)
三、六个默认成员函数
如果我们不写这些函数,编译器会自动生成一个默认的,但如果我们写了,编译器就不会生成了。
在对应位置,会自动调用这些函数。
函数 | 作用 | 备注 |
---|---|---|
✳️构造函数 | 初始化 | |
✳️析构函数 | 清理 | |
*️⃣拷贝构造 | 使用同类对象 初始化 创建对象 | = ,针对初始化,用一个对象初始化另一个 |
*️⃣赋值重载 | 把一个对象 赋值 给另一个对象 | = ,针对都已经实例化了的对象 |
取地址重载 | 普通对象取地址 | |
const 取地址重载 | const 对象取地址 |
默认生成的 构造 / 析构:
- 对 内置类型 ,不做处理
- 对 自定义类型 ,会去调用对应的 构造 / 析构
默认生成的 拷贝构造 / 赋值重载:
- 对 内置类型,完成浅拷贝 / 值拷贝(按 byte 一个一个拷贝)
- 对 自定义类型 ,调用这个成员的 拷贝构造 / 赋值重载
注意:两两一组函数之间💑有很极强的规律性,掌握规律后其实并不复杂。个别细节的问题,在相应函数章节中会详细标记并介绍,目录可查~
3.1 构造函数
构造函数是特殊的成员函数,作用是 初始化对象。
特征如下:
- 函数名与类名相同
- 无返回值
- 对象实例化时,编译器 自动调用 对应的构造函数(选择哪一个进行初始化,完全看用户的传参情况)
- 构造函数可以重载,即,可以有多个初始化方式
- 单参数 的构造函数支持 隐式类型转换
细节 & 实例1
- 对于不传参的对象,在定义时,不能加括号!!!加了会报错,因为这个写法和无参数函数的声明无异,编译器会分不清。
C 语言编写的 Stack 中,忘记 StackInit() 会导致报错
C++ 中,在类里面封装【构造函数】,会大大简化程序,方便程序员关注和实现更有价值的部分
/***************** Stack 类中,构造函数的定义 *******************/
class Stack
{
public:
Stack() ----> 构造函数 1
{
//_a = nullptr;
//_size = _capacity = 0;
}
Stack(int n) ----> 构造函数 2
{
//_a = (int*)malloc(sizeof(int) * n);
//if (_a == NULL)
//{
// perror("malloc fail");
// exit(-1);
//}
//_size = 0;
//_capacity = n;
}
void Push(int n)
{
// ..
//_a[_size++] = n;
}
private:
int* _a;
int _size;
int _capacity;
};
/********************** 构造函数的调用 ************************/
int main()
{
Stack s; // 程序会自动调用第一个构造函数,给对象 s 初始化
//Stack s(); --> err...
Stack st(4); // 传了 int 类型参数,调用第二个构造函数
st.Push(1);
st.Push(2);
st.Push(3);
st.Push(4);
return 0;
}
细节 & 实例2
- 无参的构造函数 和 全缺省的构造函数 都称为 默认构造函数,默认构造函数只能存在一个,因为调用时会存在歧义。
class Date
{ ...
Date()
{
_year = 1;
_month = 1;
_day = 1;
}
// 建议多用全缺省~
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
// 成员变量 略
...
};
【默认】之 - - 构造函数
如果类中没有显式定义构造函数,则 C++ 编译器会自动生成一个无参的默认构造函数。 一旦用户显式定义编译器将不再生成,各种原因导致调用失败(或未初始化),编译器会报错。
具体展开之前,需要了解到 C++ 把类型分为两类:
内置类型 / 基本类型 | 自定义类型 |
---|---|
int、char、double…各种指针… | class、struct、union… |
编译器生成默认的构造函数:
- 对 内置类型 成员,不做处理
- 对 自定义类型 成员,会去调用它的 默认构造函数(即不用传参的构造函数 - - > 全缺省构造函数、无参构造函数、我们没写编译器默认生成的构造函数)
注意:C++11 中针对内置类型成员不初始化的缺陷,打了个补丁,即 内置类型成员变量在类中声明时可以给默认值 / 缺省值,跟成员函数中给出的缺省参数一样。
/* 例1 */
class Date
{
public:
/*
// 如果用户显式定义了构造函数,编译器将不再生成
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
*/
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
// 将Date类中构造函数屏蔽后,代码可以通过编译,因为编译器生成了一个无参的默认构造函数
// 将Date类中构造函数放开,代码编译失败,因为一旦显式定义任何构造函数,编译器将不再生成
// 无参构造函数,放开后报错:error C2512: “Date”: 没有合适的默认构造函数可用
Date d1;
return 0;
}
/* 例2 */
class Date
{
public:
void print()
{
cout << _year << " 年 " << _month << " 月 " << _day << " 日" << endl;
}
private: //------> 成员都是内置类型,不处理
int _year;
int _month;
int _day;
};
未定义构造函数,实例化对象后...
------
输出结果:
-858993460 年 -858993460 月 -858993460 日
/* 例3 -- 题目【用两个栈实现队列】截取 */
class MyQueue
{
public:
void push(int x)
{}
//...
Stack _pushST; // ------> 成员为自定义类型,会调用其构造函数
Stack _popST;
}:
LeetCode 链接:【用两个栈实现队列】
初始化列表(详见3.7)
(初始化列表是构造函数包括拷贝构造的继续完善,在学习体系中,我们选择在学习完六个默认成员函数后,再回过头来学习初始化列表,会更好理解~)
3.2 析构函数
构造函数,简化了初始化对象的步骤。
析构函数,与构造函数功能相反,会 在对象销毁时自动调用,完成对象中 资源的清理工作。
特征如下:
- 析构函数名是在类名前加上字符 ~
- 无参数无返回值类型
- 一个类只能有一个析构函数,不能重载。若未显式定义,系统会自动生成默认的析构函数。
- 对象生命周期结束时,C++ 编译系统 自动调用 析构函数。
class Stack
{
...
// 【析构函数】
~Stack()
{
//cout << "~Stack()" << endl;
free(_a);
_a = nullptr;
_size = _capacity = 0;
}
...
};
注意:对开辟空间的类需要析构处理,普通的类随着栈的销毁而销毁的就不需要析构了,编译器也不会生成相应的析构函数。
LeetCode 链接:【括号匹配】
(本题用 C 语言写需要大量的 StackDestroy() ,C++ 直接在类中定义析构函数即可,可以深刻体会 C++ 语法的便利~~)
【默认】之 - - 析构函数
同编译器默认生成的构造函数类似
编译器生成默认的析构函数:
- 内置类型 成员,不做处理
- 自定义类型 成员,会去调用它的默认析构函数
LeetCode 链接:【用两个栈实现队列】
3.3 拷贝构造函数
拷贝构造函数 是 构造函数的一个重载 形式
可以说,当自己实现了析构函数释放空间,就需要自己实现拷贝构造
实现场景:显示使用拷贝构造(注意写法不要跟赋值搞混了)、传值传参(能用 引用 就用 引用)
- 参数类型一定是要是
引用类型
- 加一个
const
保护引用进来的对象,防止写反 误改
/*********************** 类 ***************************/
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{//...}
// 【拷贝构造】
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
//成员变量 略
};
/******************** 对象的拷贝 *************************/
int main()
{
Date d1;
// 写法一
Date d2(d1);
// 写法二
Date d3 = d1;
return 0;
}
加深
下面笔者将从一个错误写法,引出自定义类型的默认拷贝原理,同时为我们加强 拷贝构造 需引用 这个印象:
// 错!误!写!法!
Date(Date d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
// 报错,识别到了无穷递归...
问题出在:正常的传值传参,传的是内置类型
这里传的参数类型 Date 是自定义类型,
自定义类型的拷贝规则规定,会调用自身的拷贝构造(这里就是调了他自己),造成了无穷递归的现象
我们都知道传值传参时, 形参是实参的临时拷贝,对于两种参数类型,编译器做出了区分:
- 内置类型,编译器知道有多大,可以直接拷贝
- 自定义类型 的拷贝,需要调用其中的 拷贝构造
分析 C++ 设置这一规则的由来:
首先
介绍一个概念所谓 深浅拷贝:
编译器可以自己完成 按字节拷贝 的我们称 浅拷贝 或 值拷贝。
拷贝时 另外开辟一块空间 的称为 深拷贝,编译器无法完成,需要程序员设计。
紧接着
分析自定义类型的拷贝类型:
- 举例 Date 类:其成员变量均为内置类型,设置 浅拷贝 工作就可以完成
- 举例 Stack 类:其中一个成员变量 _a 是一个指针,指向一块空间,如果只是浅拷贝会使两个类的 _a 指针指向同一个空间,就出 bug 啦(a. 插入数据会互相影响,b.析构两次,程序崩溃),所以需要一个 深拷贝 的拷贝构造(后面讲)。
最终呢
区分是否可以通过浅拷贝完成拷贝工作,对编译器来说过于复杂,于是 C++ 祖师爷一锤子定音!!自定义类型的拷贝必须使用拷贝构造!!
【默认】之 - - 拷贝构造
如果类中没有显式定义 拷贝构造函数,则编译器会自动生成拷贝构造函数。 编译器所完成的拷贝都是浅拷贝,即只拷贝了值,开空间的拷贝一律没法完成,所以很多时候需要我们自己完成
编译器生成默认的拷贝构造函数:
- 对 内置类型,完成浅拷贝 / 值拷贝(按 byte 一个一个拷贝)
- 对 自定义类型 ,调用这个成员的拷贝构造函数
// 【两个栈实现队列】
class myQueue
{
public:
// 默认生成构造
// 默认生成析构
// 默认生成拷贝构造
private:
Stack _pushST;
Stack _popST;
int _size = 0;
};
实例:时间计算器(部分实现 GetXDayAfter ) - - 拷贝构造的使用场景
在 Date 类中 实现一个函数:获取 x 天以后的一个日期
首先 分析、理清 题目关键逻辑:
- 需要传入一个 x 的整型值,返回一个 Date 类型赋给对象
- 传入的 x 是天数,天数相加,考虑进制到月份位,其次是进制年份位
- 月份本身天数不一,还需要考虑到闰年的情况。稍微复杂的实现,可以考虑建一个函数,传入年份和月份,输出 当月天数
主要函数 如下:
(该实现完整运行代码见后文)
/**************** 【辅助函数 - 获取当月的天数】 ****************/
int GetMonthDay(int year, int month)
{
assert(month > 0 && month < 13);
int monthArray[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
if (((year%4==0)&&(year%100!=0))||(year%400==0))
{
monthArray[2] = 29;
}
return monthArray[month]; // 返回当月天数
}
/**************** 【实现函数 - 返回 x 天后的日期】 ***************/
// +
Date GetXDayAfter(int x)
{
// this 是调用该函数的 对象的指针,*this 就是这个对象
// 将存有原日期的对象 拷贝一份给 临时对象!!!!!
// Date tmp(*this); --->【拷贝构造函数】两种写法
Date tmp = *this;
tmp._day += x;
// 进位月份
while (tmp._day > GetMonthDay(tmp._year, tmp._month))
{
tmp._day -= GetMonthDay(tmp._year, tmp._month);
++tmp._month;
// 进位年份
if (tmp._month > 12)
{
tmp._month -= 12;
++tmp._year;
}
}
return tmp;
}
到这里该函数便该完成了,尤其在学习【拷贝构造函数】的章节
除了处理题目的关键逻辑,最为重要的一步就是,理解 为什么要在该函数里 给传入对象 做一份临时拷贝
以下是一份不做临时拷贝处理的函数【错版!!!错版!!!错版!!!】:
看似没有太大的不同,但实际上,里面每一个对象成员参数的使用,都是本身,在函数执行的同时,自己的值也被修改了!!
所以这个写法不行,我们需要拷贝一份赋给 tmp,在 tmp 的基础上修改并传返回值,才可以 保留自己
/******************** 【这是一个错版函数!!】 ************************/
// +=
Date GetXDayAfter(int x)
{
_day += x;
while (_day > GetMonthDay(_year, _month))
{
_day -= GetMonthDay(_year, _month);
++_month;
if (_month > 12)
{
_month -= 12;
++_year;
}
}
return *this; // this 是调用该函数的对象的指针,*this 就是这个对象
}
*/
完整、可运行代码 如下:
class Date
{
public:
// 构造函数
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
// 打印日期
void print()
{
cout << _year << " 年 " << _month << " 月 " << _day << " 日" << endl;
}
// 获取当月的天数
int GetMonthDay(int year, int month)
{
assert(month > 0 && month < 13);
int monthArray[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
if (((year%4==0)&&(year%100!=0))||(year%400==0))
{
monthArray[2] = 29;
}
return monthArray[month];
}
// 返回 x 天后的日期
Date GetXDayAfter(int x)
{
Date tmp = *this; // this 是调用该函数的对象的指针,*this 就是这个对象
tmp._day += x;
while (tmp._day > GetMonthDay(tmp._year, tmp._month)) // 进位月份
{
tmp._day -= GetMonthDay(tmp._year, tmp._month);
++tmp._month;
if (tmp._month > 12) // 进位年份
{
tmp._month -= 12;
++tmp._year;
}
}
return tmp;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2023, 3, 16);
// 获取 100 天后的日期
Date d2 = d1.GetXDayAfter(100);
d1.print();
d2.print();
return 0;
}
------
输出结果:
2023 年 3 月 16 日
2023 年 6 月 24 日
3.4 赋值运算符重载
3.4.1 运算符重载
运算符重载:让 自定义类型对象 可以使用运算符,不能对内置类型的操作符重载
重载 这个词看着眼熟不 !!
函数重载:支持函数名相同,参数不同的函数,可以同时使用
运算符重载是 具有特殊函数名 的函数,其返回值类型与参数列表与普通函数类似:
返回值类型 operator操作符(参数列表)
注意:
- 不能创建新的操作符 eg:operate♀
- 参数数量 和 符号操作数数量 一致,且参数左右关系依次定位(第一个参数就是左操作数…以此类推)
- 全局函数写法 和 成员函数 写法和调用都不一样,因为,成员函数有一个隐形参数,需要注意参数数量 -1!!
- 其参数必须有一个 class 类型
- 五个 不能重载 的操作符:
.*
::
sizeof
?:
.
/****************** 重载 == 判断日期是否相等 *****************/
/*-------------------- 全局函数写法 --------------------*/
bool operator==(const Date& d1, const Date& d2)
{
return d1._year == d2._year
&& d1._month == d2._month
&& d1._day == d2._day;
}
/*-------------------- 成员函数写法 --------------------*/
...
bool operator==(const Date& d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
...
/********************* 实例化和函数调用 **********************/
int main()
{
Date d1(2023, 2, 4);
Date d2(2023, 2, 4);
Date d3(8888, 8, 8);
// 隐式调用全局函数 ---> 编译器会找运算符重载函数,并转换成调用 operate==(d1,d2)
d1 == d2;
cout << (d1 == d2) << endl;
// 调用成员函数
d1.operator==(d3);
cout << d1.operator==(d3) << endl;
return 0;
}
------
输出结果:
1
0
- bool 类型的输出,真为 1 假为 0
- << 的优先级较高,在需要的时候打括号
- C++ 设置运算符重载,除了便捷,更是为了提高代码的可读性,一切自定义设置都推荐基于原操作符作用进行设置
小拓展:运算符重载的 通用逻辑 – 复用生成
/************** 【复用】 ****************/
------------------------------------------------------------------------
bool operator==(const Date& d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
bool operator<(const Date& d)
{
return _year < d._year
|| ((_year == d._year) && (_month < d._month))
|| ((_year == d._year) && (_month == d._month) && (_day < d._day));
}
------------------------------------------------------------------------
// step1:实现 <=
bool operator<=(const Date& d)
{
return *this < d || *this == d;
}
// step2:实现 >, >= , !=
bool operator>(const Date& d)
{
return !(*this <= d);
}
bool operator>=(const Date& d)
{
return !(*this < d);
}
bool operator!=(const Date& d)
{
return !(*this == d);
}
3.4.2 赋值运算符重载
赋值运算符是一个使用频率很高的运算符
注意:
- 设置返回值,即 原对象本身 ,是为了支持 连续赋值,保持运算符的特性
- *this 出了作用域还在,直接返回类会产生一次拷贝,所以最好返回 引用值
- 不排除有人写出 d1 = d1 的程序,判断地址,如果 地址相同,直接返回,这样的判断在深拷贝时,对提高运行效率非常奏效
/********************* 赋值运算符 的重载 *********************/
/*------------------- 成员函数的写法 --------------------*/
Date& operator=(const Date& d) // 在拷贝构造没问题的情况下,可以不引用,但从理解的角度来说还是应该加引用
{
if (this != &d) // 判断 地址是否相同
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
// d1 = d2 的返回值应该是 d1
/********************* 实例化和函数调用 **********************/
int main()
{
Date d1(1999, 9, 9);
Date d2(2022, 2, 2);
Date d3(2088, 8, 8);
d1 = d2 = d3; -------------> 都是已经实例化好了的对象,绝对的[赋值重载]
d1.print();
Date d4 = d1; -------------> 用对象初始化另一个,这个便是[拷贝构造]了
// Date d4(1111) = d1; 会编译报错,是不能这样写的
// 所以,放心使用他们吧,很容易区分的!!!!
return 0;
}
------
输出结果:
2088 年 8 月 8 日
【默认】之 - - 赋值运算符重载
- 对 内置类型,完成浅拷贝 / 值拷贝(按 byte 一个一个拷贝)
- 对 自定义类型 ,调用这个成员的 赋值重载函数
还是我们熟悉的 myQueue
// 【两个栈实现队列】
class myQueue
{
public:
// 默认生成构造
// 默认生成析构
// 默认生成拷贝构造
// 默认生成赋值重载
private:
Stack _pushST;
Stack _popST;
int _size = 0;
};
3.5 const 成员函数
先给出 使用建议 和 目的:
对于 内部不改变成员变量的成员函数,建议 加上 const(声明和定义分离时,两边都需要加)
目的是让 普通对象 和 const 对象 都可以对其进行调用
我们将 const 修饰的 成员函数 称之为 const 成员函数,跟 const 修饰变量一样,这意味着该成员函数中 不能对类的任何成员进行修改。
定义 const 成员函数 写法如下:
/*************************** const成员函数的定义 ******************************/
class A
{
public:
void FUNC() // 普通成员函数
{ }
void func() const // 【const 成员函数】const 修饰 *this 对象,至此 this 指针的类型变成了const A*
{ }
private:
int _a = 10;
};
int main()
{
const A aa;
aa.FUNC(); // 报错,因为出现 权限放大问题
aa.func(); // 权限持平,可以正常运行
A bb;
bb.func(); // 权限缩小,也是可以正常运行的~
return 0;
}
报错行分析:
&aa 的类型:【const A*】,意味着 aa 自己指向的对象不能改变
而成员函数的 FUNC() 中 this 指针的类型是:【A*】
很明显的,权限被放大了。
处理分析:
按照权限处理的思路,我们可以将大权现缩小至权限持平
但实际上隐含 this 指针的类型我们是无法直接显式改变的
C++ 给出解决方法:
于是 C++ 语法给成员函数设计了一个加 const 的位置
放在这里,就意味着 *this 这个对象被 const 修饰着
如上,这样的函数便叫作 const 成员函数。
重申:对于 内部不改变成员变量的成员函数,建议 加上 const。为的是让 普通对象 和 const 对象 都可以对其进行调用。
ps:const 修饰 变量 需要在定义的位置初始化!!!!
3.6 【默认】之二 - - 取地址重载函数
到这里,六个默认成员函数我们已经掌握了四个,还有两个默认生成的函数,就是 对普通实例化对象取地址 和 对 const 对象取地址 的 & 取地址重载函数。
针对这两个默认成员函数,我们完全不需要自定义他们,系统会处理的很得当,如果你还是想看看他们,那么他们长如下这样:
class A
{
public:
...
A* operator&() // 对普通对象取地址
{
return this;
}
const A* operator&() const // 对 const 对象取地址
{
return this;
}
private:
...
};
// 实例化 及 使用
A aa;
const A bb;
cout << &aa << endl;
cout << &bb << endl;
------
输出结果: // 至于,若我们没有在 A 类中主动定义两个取地址重载函数,也能输出两个地址~~
00CFFB14
00CFFB08
(ps:不从实际功能考虑,如果你想捣乱,自定义让别人拿不到地址或者一串自定义地址,也不是不可以 😛 …)
3.7 再谈构造函数
之前的构造函数,我们称他做 函数体内的赋值初始化
由问题引入:
在实例化对象的时候,对象被整体定义,那每一个成员变量在何时被定义的呢?
比如 const 修饰的成员变量,只有一次赋值就是定义的时候,这个变量是何时被定义的呢?(在类中值的设定是缺省值。不是定义~)
所以我们必须给每个成员变量找一个定义的位置,构造函数给出了一个新的解决初始化的方式
初始化列表
以一个 冒号开始,接着是一个以 逗号分隔的数据成员列表,每个 成员变量 后都跟一个 放在括号中的初始值 或 表达式`
注意事项:
-
初始化列表中各个变量只能初始化一次,使用时 建议所有成员在初始化列表中初始化。
-
三种 必须要在初始化列表中初始化 的成员变量:
-
const 成员变量
-
引用 成员变量
-
自定义类型成员(且该类 没有默认构造函数 时)
-
案例1:
class B
{
public:
B(int b)
:_b(0)
{}
private:
int _b;
};
class A
{
public:
A()
:_x(1)
,_ref(_a1)
,_a2(1)
,_bb(0)
{
_a1++;
_a2--;
}
private:
int _a1 = 1; // 声明、缺省
int _a2 = 2;
const int _x; --------> 必须要在初始化列表初始化的三个成员
int& _ref; -------->
B _bb; -------->
};
int main()
{
A aa;
return 0;
}
------
初始化结果:
_a1=2 (缺省值++)
_a2=0 (初始化列表给的值--)
_x=1
_ref=2
_bb(_b=0)
案例2:
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{}
private:
const int _x;
...
};
int main()
{
Date d;
return 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();
}
------
输出结果:
1 随机值
- 拷贝构造 也可以使用 初始化列表
补充:隐式类型转换 和 explicit
补充一个对于单 \ 多参数类型的函数,支持隐式类型转换
主动定义 不允许隐式转换,关键字为:explicit
class AA
{
public:
/*explicit*/AA(int a)
:_a1(a)
{
cout << "AA(int a)" << endl;
}
AA(int a1, int a2)
:_a1(a1)
:_a2(a2)
{}
AA(const AA& aa)
:_a1(aa._a1)
{
cout << "AA(const A& aa)" << endl;
}
private:
int _a1;
int _a2;
};
int main() {
// 【单参数类型构造函数】C++98开始支持
AA a1(1); // 构造函数
AA a2 = 1; // 隐式类型转换
//AA& ref = 10; // err.. 引用的时候没有调用构造不能优化
const AA& ref = 10; // 成功运行,因为产生了临时变量,即调用了构造
int i = 1;
double d = i; // 隐式类型转换
// 【多参数类型构造函数】C++11开始支持
AA aa1(1, 2);
AA aa1 = { 1,2 };
return 0;
}
------
原本的隐式转换:i-->double类型临时变量-->d
1-->AA类型的临时变量-->a2 (第一次是构造,第二次是拷贝构造)
编译器优化成,直接构造
🥰如果本文对你有些帮助,请给个赞或收藏,你的支持是对作者大大莫大的鼓励!!(✿◡‿◡) 欢迎评论留言~~
🔗接下篇【Ⅲ】补充篇:Static成员、匿名对象、友元、内部类、拷贝对象时的一些编译器优化、一些 oj 题
👉【C++入门】类和对象Ⅲ:构造函数加强、Static成员、匿名对象、友元、内部类、拷贝对象时的一些编译器优化、再次理解封装(含 oj 题)