类和对象简要小记
一、类的定义
1.1 类定义的格式
class Date
{
public:// 成员函数void print(){cout << _year << "/" << _month << "/" << _day << endl;}private:// 成员变量int _year;int _month;int _day;};
- class为定义类的关键字,Date为类的名字,{}中为类的主体部分,结束和struct一样要加; 。类中的内容称为类的成员:其中变量称为成员变量或属性,函数称为成员函数或方法。
- 为了区分成员变量和一般变量一般在变量的前面加个_之类的特殊表示符。
- C++中struct也可以定义类,C++兼容了struct的用法,同时又把struct升级成了类,明显的区别是struct可以定义函数,一般用class定义类。
- 定义在类里面的成员函数默认是内联函数(声明定义分离的不是,另外展不展开还是编译器决定)
- C++中struct定义的自定义类型,使用时struct名称就可以代表类型。
struct StackNode
{StackNode* a;int capacity;int top;
};
StackNode st1;
1.2 访问限定符
- C++一种实现封装的方式,用类将对象的属性与方法结合在一起,让对象更加完善,通过访问限定符有选择的让使用者调用对应接口。这种更加严格的规范保证了代码正确性。
#include <iostream>
#include <assert.h>using namespace std;class Stack
{
public:void Init(int n = 4){array = (int*)malloc(sizeof(int) * n);if (nullptr == array){perror("malloc申请空间失败");return;}capacity = n;top = 0;}void Push(int x){// ...扩容array[top++] = x;}int Top(){assert(top > 0);return array[top - 1];}void Destroy(){free(array);array = nullptr;top = capacity = 0;}private:int* array;int capacity;int top;
};int main()
{// C语言版本// array[top - 1] 可以调用栈顶元素// 也可以借助函数StTop(&st1) 调用// 第一种方式在栈为空时会出错// C语言中可以随意对top等变量调用使我们可以更灵活访问数组// 但是更灵活的方式可能导致许多错误// C++ 版本Stack st1;st1.Init(100); // init函数会用缺省值使得我们可以指定或默认大小初始化st1.Push(1);st1.Push(2);st1.Push(3);st1.Push(4);cout << st1.Top() << endl;st1.Destroy();return 0;
}
- public 修饰的成员在类外可以直接访问;但是private和protected修饰的成员不可以直接访问(目前认为两者一样)。
- 访问限定符的作用域从该访问限定符出现的位置开始到下一个访问限定符或者}结束。访问限定符可以写多个。
- struct中不写访问限定符默认全是public成员;class中不写访问限定符默认全是private成员。
- 一般成员变量都会被限制为private或者protected,需要给别人使用的成员函数会被设置为public。
1.3 类域
- 局部域和全局域影响查找规则和声明周期,命名空间域和类域影响变量的查找规则。
- 类域内的成员可以被类内的函数直接调用,在类外调用需要:: 操作符指定域查找。
- 下面的Init函数如果不指定类域查找,编译器会把Init当成全局变函数,在类外不可使用成员函数
二、 实例化
2.1 实例化概念
- 用类类型创建变量,编译器为其分配空间,称为类实例化出对象。
- 类是对对象的一种能抽象描述,是一个模型一样的东西,限定了类有哪些成员变量,这些成员变量是声明,并没有分配空间,类实例化出对象才分配空间。
- 打个比方:建筑图规定了房子的架构,里面有什么东西等等,但并没有实体房子可以住,只有用设计图修建房子,才会产生实体可以住人的房屋。类就像建筑设计图,对象就是房子实体,一个类可以创建出对个对象。
2.2 类对象的大小
- 用类实例化出对象后需要占据物理空间,那么占据多大空间呢?
其中成员变量肯定要占用空间,那么成员函数需不需要占据空间呢?函数在编译过程中会被解释成一段指令,对象中没法存储(存在代码段中),要是存也是存指针,其实指针也没必要存,原因是:每个类对象都是调用同一个函数,除内联函数外,编译都是调用[call 地址]指令,这个地址在编译时就已经确定,并不会在运行时查找,要是每个类对象都存一遍的话太浪费空间,只有动态多态是在 运行时查找,这时需要存地址,后面对说到。
- 类对象中成员变量的存储也遵循内存对齐规则:
- 第一个成员在结构体变异量为0的地址处。
- 其他成员要对齐到对齐数整数倍的地方。
- 对齐数 = min(默认对齐数,该成员的大小)
- vs的默认对齐数是8
- 结构体的总大小为:最大对齐数(所有变量类型最大值与默认对齐数的最小值)的整数倍。
- 如果嵌套定义结构体,嵌套定义的结构体对齐到字节最大对齐数的整数倍的位置, 结构体的总大小是所有最大对齐数的整数倍。
#include <iostream>using namespace std;class tmp1
{char _c; // 1int _a; // 4
};struct a
{char c1; // 1char c2; // 1char c3; // 1
};class tmp2
{a _a1; // 3int _a2; // 4a _a3; // 3
};class tmp3
{};int main()
{cout << sizeof tmp1 << endl; // 8cout << sizeof a << endl; // 3cout << sizeof tmp2 << endl; // 12cout << sizeof tmp3 << endl; // 1 这里1是站位用的,用tmp3创建对象,要是0没法确定是否创建这个变量。return 0;
}
2.3 this指针
- Date类中有 Init 与 Print 两个函数都调用了类的成员变量,但是在函数体内没有区分这个成员变量是属于哪个对象的,为了解决这个问题,C++用this指针解决的这个问题。
- 编译器编译后会在成员函数的位置添加一个参数 Date* const this 即类对象的地址。
- 类成员中访问成员变量本质上是通过this指针访问的,如Init函数中给_year赋值,this->——year = year;
- C++规定this指针这个参数只能隐式传,也就是说我们不能在函数形参的位置写上这个变量,调用成员变量是不写this指针,编译器会默认添加。
#include <iostream>using namespace std;class Date
{
public:void Init(int year = 1, int month = 1, int day = 1){//this = nullptr; // 不可修改的左值//this->_year = year;//this->_month = month;//this->_day = day;_year = year; _month = month;_day = day;}void Print(){cout << this->_year << "/" << this->_month << "/" << this->_day << endl;//cout << _year << "/" << _month << "/" << _day << endl;}private:int _year;int _month;int _day;
};int main()
{Date d1;d1.Init(2025, 4, 24);//d1.Print(&d1);d1.Print();Date d2;d2.Init(2025, 4, 26);d2.Print();return 0;
}
三个题:
1.
// 下⾯程序编译运⾏结果是(C)
// A、编译报错 B、运⾏崩溃 C、正常运⾏
#include<iostream>
using namespace std;
class A
{
public:void Print(){cout << "A::Print()" << endl;}
private:int _a;
};int main()
{A* p = nullptr; // 只是用A对象创建出一个类指针,并没有实例化。// call A::Print (地址)p->Print(); // 这里只是通过p找到Print()的地址,然后调用,并没有对空指解引用.return 0;
}
// 下⾯程序编译运⾏结果是(B)
// A、编译报错 B、运⾏崩溃 C、正常运⾏
#include<iostream>
using namespace std;
class A
{
public:void Print(){cout << "A::Print()" << endl;cout << _a << endl; // 对空指针解引用}
private:int _a;
};
int main()
{A* p = nullptr;p->Print();return 0;
}
- this指针存在内存哪个区域的 (A)
A. 栈 B.堆 C.静态区 D.常量区 E.对象里面
this 指针在调用函数的时候&对象地址传给形参,所以this存在栈中,有些编译器在实现的时候认为this会被频繁调用,也可能把它放到寄存器中。
四、类的默认成员函数
默认成员函数是用户不写编译器自动生成的函数。一个类在不写的情况下一般会自动生成6个默认成员函数(C++11是8个),其中最重要的是前四个。
对于默认成员函数
- 我们不写要知道编译器自动生成的默认成员函数的行为。
- 编译器自动生成的不能满足我们的需要,如何自己写。
4.1 构造函数
构造函数是特殊的成员函数,它的行为是在类对象实例化时对其初始化。
构造函数的特点:
- 函数名与类名相同。
- 无返回值。
- 对象实例化的时候自动调用构造函数。
- 构造函数可以重载。
- 如果没有显式的写构造函数,编译器会自动生成一个无参的默认构造,用户显示写后编译器就不再生成。
- 无参构造函数,全缺省构造函数,编译器隐式生成的构造函数都是默认构造函数(就是不用传参数的)。这三个函数只能存在一个,不能同时存在。
- 我们不写,编译器对于内置类型不做处理,对于自定义类型自动调用他们的默认构造。
#include <iostream>using namespace std;class Date
{
public://Date()//{// _year = 1;// _month = 1;// _day = 1;//}Date(int year = 2025, int month = 1, int day = 1){_year = year;_month = month;_day = day;}void print(){cout << _year << " " << _month << " " << _day << endl;}
private:int _year;int _month;int _day;
};typedef int STDataType;
class Stack
{
public:Stack(int n = 4){_a = (STDataType*)malloc(sizeof(STDataType) * n);if (nullptr == _a){perror("malloc申请空间失败");return;}_capacity = n;_top = 0;}
private:STDataType* _a;size_t _capacity;size_t _top;
};class MyQueue
{
public:// 其实就算我们写了但是什么写不干,编译器也会初始化,这时候要用到初始化列表//MyQueue()//{//}// /编译器默认⽣成MyQueue的构造函数调⽤了Stack的构造,完成了两个成员的初始化
private:Stack pushst;Stack popst;
};int main()
{// Date a() 不能这样写,无法和函数声明区分开Date a;a.print();MyQueue q;return 0;
}
4.2 析构函数
析构函数是特殊的成员函数,析构函数的作用不是完成对对象本身的销毁,而是完成对对象调用资源的释放,C++规定对象在销毁是会自动调用它的析构函数。
严格来讲析构函数不是必须的,像日期类这种没有动态开辟内存空间的不需要析构函数。
析构函数的特点:
- 析构函数是在类名前加上~。
- 无返回值。
- 一个类只能有一个析构函数。没有显示写,编译器会自动生成。
- 对象生命周期结束,会自动调用析构函数。
- 一个局部域内的多个对象,C++规定后定义的先析构。
- 跟构造函数类似,默认生成的析构函数对内置类型不做处理,对自定义类型调用他们的析构函数。
- 对于显式写的析构函数,编译器对于自定义类型也会调用他们的析构函数。
- 如果没有动态申请的资源可以不写析构函数,但是有申请资源需要写,避免内存泄漏。
#include <iostream>using namespace std;typedef int STDataType;
class Stack
{
public:Stack(int n = 4){_a = (STDataType*)malloc(sizeof(STDataType) * n);if (nullptr == _a){perror("malloc申请空间失败");return;}_capacity = n;_top = 0;}~Stack(){free(_a);_a = nullptr;_capacity = _top = 0;}
private:STDataType* _a;size_t _capacity;size_t _top;
};class MyQueue
{
public:// 对于自定义类型不写会调用他的析构// 如果写了也会调用自定义类型的析构~MyQueue(){}
private:Stack pushst;Stack popst;
};int main()
{// Date a() 不能这样写,无法和函数声明区分开MyQueue q;return 0;
}
4.3 拷贝构造函数
如果构造函数的第一个参数是自身类型的引用,且任何额外的参数都有缺省值,则此构造函数也叫拷贝构造函数,拷贝构造函数是构造函数的重载。
拷贝构造函数的特点:
- 拷贝构造函数是构造函数的重载。
- 拷贝构造函数的第一个参数必须是自身类型的引用,否则在语法逻辑上会引发无穷递归。构造函数可以有多个参数,但是除第一个参数以外其余参数必须有缺省值。
- C++规定自定义类型的对象进行拷贝行为必须调用拷贝构造,所以自定义类型传值传参和传值返回都会调用拷贝构造完成。
- 若为显示定义拷贝构造,编译器也会自动生成拷贝构造函数。自动生成的拷贝构造对内置类型会逐字节拷贝,对自定义类型成员变量调用它的拷贝构造。
- 如果没有指向什么资源的类,编译器默认生成的拷贝构造就可以完成任务,但是如果动态申请了空间就要深拷贝。总结就是如果显示写了析构释放资源,就要显示写拷贝构造。
- 传值返回会产生一个临时对象调用拷贝构造,传引用返回是返回对象的别名,没有产生拷贝构造。传引用返回可以减少拷贝,但是一定要保证返回的对象在函数结束后还存在,避免野引用。
#include <iostream>using namespace std;class Date
{
public:Date(int year = 2025, int month = 1, int day = 1){_year = year;_month = month;_day = day;}Date(const Date& d) // 拷贝构造函数{_year = d._year;_month = d._month;_day = d._day;}Date(Date* d){_year = d->_year;_month = d->_month;_day = d->_day;}private:int _year;int _month;int _day;};
//Date f(Date& d)
Date f(Date d) // 传值传参调用拷贝构造
{return d; // 传值返回调用拷贝构造
}typedef int STDataType;
class Stack
{
public:Stack(int n = 4){_a = (STDataType*)malloc(sizeof(STDataType) * n);if (nullptr == _a){perror("malloc申请空间失败");return;}_capacity = n;_top = 0;}Stack(const Stack& st){// 需要对_a指向资源创建同样⼤的资源再拷⻉值_a = (STDataType*)malloc(sizeof(STDataType) * st._capacity);if (nullptr == _a){perror("malloc申请空间失败!!!");return;}memcpy(_a, st._a, sizeof(STDataType) * st._top);_top = st._top;_capacity = st._capacity;}~Stack(){cout << "~Stack()" << endl;free(_a);_a = nullptr;_top = _capacity = 0;}
private:STDataType* _a;size_t _capacity;size_t _top;
};class MyQueue
{
public:private:Stack st1;Stack st2;
};
int main()
{Date d1(2025, 5, 9);Date d2(d1); // 拷贝构造// 这⾥可以完成拷⻉,但是不是拷⻉构造,只是⼀个普通的构造Date d3(&d1);// 也可以这样写,这⾥也是拷⻉构造Date d4 = d1;Stack st1;// Stack不显⽰实现拷⻉构造,⽤⾃动⽣成的拷⻉构造完成浅拷⻉// 会导致st1和st2⾥⾯的_a指针指向同⼀块资源,析构时会析构两次,程序崩溃Stack st2 = st1;MyQueue mq1;// MyQueue⾃动⽣成的拷⻉构造,会⾃动调⽤Stack拷⻉构造完成pushst / popst// 的拷⻉,只要Stack拷⻉构造⾃⼰实现了深拷⻉,他就没问题MyQueue mq2(mq1);return 0;
}
五、赋值运算符重载
5.1
- C语言阶段类似于== ,> ,< 等运算符并不适用于类对象,C++提供了对运算符重载的方法,赋予其新含义。C++规定,类对象调用运算符时必须要转化成对应的运算符重载,否则会编译报错。
- 运算符重载具有特殊函数名的函数,由operate和需要重载的运算符构成,和其他函数一样有返回类型、参数列表、函数体。
- 运算符重载要求参数数量和运算的类对象一样多。不要忘记类对象里面默认传了一个this。
- 运算符重载后优先级和结合性不变。
- 运算符只支持重载不支持创建。
- .* :: sizeof ?: . 以上五个运算符不支持重载。
- 运算符重载需要至少一个类类型的参数,也就是说不能对内置类型的运算符重载。
- 运算符重载要看重载后是否有意义。
- 重载运算符++时,前置++,后置++运算符重载的函数名都是operate++,无法很好的区分。C++规定,后置++重载时,增加一个int类型的形参和前置++构成重载,方便区分。
赋值运算符重载
赋值运算符是一个默认成员函数,用于两个已经存在的对象的拷贝赋值,这里要注意跟拷贝构造区分,拷贝构造用于一个对象拷贝初始化给另一个要创建的对象。
- 赋值运算符重载必须重载为成员函数。参数建议写成const 当前类型的引用。
- 有返回值,且建议携程当前类型的引用,引用返回可以提高效率,有返回值是为了支持连续赋值的场景。
- 没有显示写时,编译器会自动生成一个,对内置类型浅拷贝,对自定义类型调用它的赋值运算符重载。
- 一般如果显示写了析构函数就要显示写赋值运算符重载。
#include <iostream>using namespace std;class Date
{
public:Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}~Date(){}void print(){cout << _year << " " << _month << " " << _day << endl;}bool operator==(const Date& d){return _year == d._year&& _month == d._month&& _day == d._day;}int operator-();Date operator+();// 后置++Date operator++(int a);// 前置++Date operator++();// 赋值运算符重载Date& operator+(const Date& d){_year = d._year;_month = d._month;_day = d._day;return *this;}private:int _year;int _month;int _day;
};typedef void(Date::* PF)();//int getyear() const
//{
// return _year;
//}
//int getmonth() const
//{
// return _month;
//}
//int getday() const
//{
// return _day;
//}// 运算符重载成全局在类对象外获取成员函数的方法
// 将成员函数设置为共有
// 调用getXXX()函数
// 友元函数
// 重载成成员函数
//bool operator==(const Date & d1, const Date & d2)
//{
// return d1.getyear() == d2.getyear()
// && d1.getmonth() == d2.getmonth()
// && d1.getday() == d2.getday();
//}int main()
{//Date d1(2025, 5, 13);//Date d2(2025, 5, 14);PF f = &Date::print;Date obj;(obj.*f)();d1.operator==(d2);//if (d1 == d2) //{// cout << "YES" << endl;//}//else //{// cout << "NO" << endl;//}//Date d3, d4, d5(2025, 1, 1);//d3.print();//d4.print();//d5.print();//d3 = d4 = d5;//d3.print();//d4.print();//d5.print();return 0;
}
六、取地址运算符重载
6.1 const成员函数
- 将const修饰的成员函数称之为const成员函数,const修饰成员函数放到参数列表的后面。
- 参数列表后面的const严格来讲修饰的是this指针指向的内容。
- 访问权限可以缩小但是不可以放大,一般不改变对象本身的成员函数都用const修饰。
6.2 取地址运算符重载
取地址运算符重载有两种:普通取地址运算符重载和const取地址运算符重载,其实就是返回this指针的地址,编译器默认生成的就 够我们使用。只有在某些及特殊的情况才需要我们显示写。
七. 初始化列表
初始化列表是成员变量定义的地方。
- 构造函数对成员变量的初始化是在函数体内赋值,另一个初始化的方式是初始化列表。
- 初始化列表中每个成员变量只能出现一次。
- 对于没有默认构造的自定义类型、const成员函数、引用成员变量,必须放到初始化列表初始化。
- C++11支持在成员函数声明位置给缺省值,这个缺省值只有在初始化列表没有显示写的时候使用。
- 尽量使用初始化列表初始化,因为初始化列表是变量定义的地方,每个成员变量都要走一遍,要是再在构造函数里面初始化就重复了。
- 初始化列表初始化的顺序是根据类里面定义的顺序初始化的,跟在初始化列表中写的顺序无关。建议声明顺序和初始化列表顺序一致。
八. 类型转换
- C++支持内置类型隐式转化为类类型对象,需要修相关内置类型为参数的构造函数。
- 构造函数前面加explicit就不再支持隐式类型转换。
- 类类型的对象之间也可以隐式类型转换,需要相应的构造支持。
class A {
public:A(int a){_a = a;}A(int a, int b){_a = a;_b = b;}void print(){cout << _a << " " << _b << endl;}
private:int _a;int _b;
};class B {
public:B(int a = 1, int b = 1){_a = a;_b = b;}B(A a){}void print(){cout << _a << " " << _b << endl;}
private:int _a;int _b;
};int main()
{A a1 = 1; // 相当于用1构造一个临时对象,然后拷贝构造a1,但编译器会优化成直接拷贝构造。A a2 = { 1, 1 }; // C++11也可以支持多参数的构造,用{}括起来// 以上两种都属于隐式类型转换A a2 = 1;B b2 = a2; // 对于类对象只要存在对应的构造都可以隐式类型转换。return 0;
}
九、static成员
前面我们说过,类的实例化时用设计图建出来一个个房屋,static成员就像小区里的公共设施,类域里面所有的对象共享。
- 静态成员变量为类对象所共有,不属于某个具体的对象,不存在对象中,存放在静态区中。
- 用static修饰的成员函数,称之为静态成员函数,静态成员函数没有this指针。
- 用static修饰的成员变量,不能通过构造或者初始化列表的方式初始化,必须要在类外面初始化。
- 非静态的成员函数,可以访问任意的静态成员变量和成员函数。
- 除了把成员函数共有外,可以通过指定类域访问静态成员函数来返回。
- 静态成员变量也是类成员,受访问限定符的限制。
求1+2+3+…+n
本题就可以借助静态成员解决,通过不断创建A类变量累计静态变量从而得到结果。
class A{public:A(){++cnt;sum += cnt;} static int getsum(){return sum;}private:static int sum;static int cnt;
};
int A::sum = 0;
int A::cnt = 0;
class Solution {
public:int Sum_Solution(int n) {A arr[n];return A::getsum();}
};
十、友元
- 友元提供了一种突破类域访问限定封装的方式,友元分为:友元函数和友元类,在函数声明或者类声明前面加friend,并且把友元声明放到同一个类里面。
- 外部友元函数可以访问类的私有和保护成员,友元函数仅仅是一种声明不受访问限定符的限制, 也不是类的成员。
- 一个函数可以有多个友元。
- 友元类中的成员函数都可以是另一个类的友元函数, 都可以访问另一个类中的私有或共有或保护成员。
- 友元不具备传递性和交换性,比如A类是B类的友元, B类是C类的友元,但是A类不是C类的友元, A类是B类的友元,但是B类不是A类的友元。
- 友元支持嵌套声明。
- 友元提供了方便,但是尽量少用它增加了耦合性,破坏了封装。
#include <iostream>using namespace std;class B; // 提前声明,告诉编译器B类是A的友元类使得下面的func可以访问。class A
{// 友元声明friend class B;
public:A(int a = 1, int b = 1){_a = a;_b = b;}void print(){cout << _a << " " << _b << endl;}void func(const A& aa, const B& bb){cout << aa._a << " " << aa._b << endl;cout << bb._a << " " << bb._b << endl;}
private:int _a;int _b;
};class B
{// 友元声明friend class A;
public:void print(const A& aa){cout << aa._a << " " << aa._b << endl;}private:int _a;int _b;
};int main()
{return 0;
}
有了友元函数就可以对如日期类进一步优化:
声明定义分离避免符号表冲突,或者定义成内联也可以解决。
ostream& operator<< (ostream& out, const Date& d)
{out << d._year << "/" << d._month << "/" << d._day << endl;return out;
}istream& operator>> (istream& in, Date& d)
{while (1){in >> d._year >> d._month >> d._day;if (d.checkymd()){break;}else{cout << "输入日期不合法,请重新输入" << endl;}}return in;
}
//inline ostream& operator<< (ostream& out, const Date& d)
//{
// out << d._year << "/" << d._month << "/" << d._day << endl;
// return out;
//}
//
//inline istream& operator>> (istream& in, const Date& d)
//{
// in >> d._year >> d._month >> d._day;
// return in;
//}
十一、内部类
- 一个类定义在另一个类的内部叫做内部类。内部类是一个独立的类,不占外部类的空间,跟定义在全局相比,它之手外部类类域限制和访问限定符的限制。
- 内部类默认是外部类的友元类
- 内部类本质是一种封装,当A类和B类紧密相连时,B类是A类的内部类,A类实例化出来主要是给A类用的,放到私有部分可以说是A类的专属类。
对上一个题的优化:
class Solution {public:int Sum_Solution(int n) {A arr[n];return sum;}
private:
class A
{public:A(){++cnt;sum += cnt;} };static int cnt;static int sum;
};int Solution::sum = 0;int Solution::cnt = 0;
十二、匿名对象
- 用类型定义出来的对象叫做匿名对象,相比于我们之前定义的有名对象,匿名对象定义时不写名字,它的声明周期只有当前这一行
7、对象拷贝时的编译器优化
- 编译器为了提高相率,在不影响正确性的前提下尽可能会减少一些传值返过程中可以省略的拷贝。
- 这种优化并不是标准规定的,全看编译器怎么实现,一些比较激进的编译器会采用隔行优化的方式进行优化。
#include<iostream>
using namespace std;
class A
{
public:A(int a = 0):_a1(a){cout << "A(int a)" << endl;}A(const A& aa):_a1(aa._a1){cout << "A(const A& aa)" << endl;}A& operator=(const A& aa){cout << "A& operator=(const A& aa)" << endl;if (this != &aa){_a1 = aa._a1;}return *this;}~A(){cout << "~A()" << endl;}
private:int _a1 = 1;
};
void f1(A aa)
{
}
A f2()
{A aa;return aa;
}
int main()
{// 无优化,构造 + 拷贝构造A aa1;f1(aa1);cout << endl;// 隐式类型转化 + 拷贝构造 -》直接构造f1(1);// 匿名函数构造 + 拷贝构造 -》直接构造f1(A(2));;cout << endl;cout << "***********************************************" << endl;// 构造aa + 临时变量拷贝构造 -》直接构造临时对象 aa是临时对象的别名f2();cout << endl;// 构造aa + 构造临时对象 + 拷贝构造aa2 -》直接构造aa2(这个在vs2022debug 和 release 以及vs2019的release才有)A aa2 = f2();cout << endl;
```c
6
// 构造aa + 构造临时对象 + 赋值给aa1 这个不优化
aa1 = f2();
cout << endl;
return 0;
}