1.对象拷贝时编译器优化
现代编译器为了尽快提高程序的效率,不影响正确性的情况下会尽可能减少一些传参和传参过程中可以省略的拷贝
例子:
先调用f()函数,则应该先触发构造函数初始化a,return a时先拷贝a到应该临时变量上,会触发拷贝构造,然后a再触发析构函数,临时变量在当行结束后就销毁了,但是运行结果只有构造函数和析构函数,所以可以知道编译器进行了优化操作。
在C++中,临时变量(或临时对象)的生命周期有一些关键的规则:
1.函数返回值:
临时对象的生命周期通常会延续到包含它的表达式的结束。特别是当临时对象作为函数的返回值时,它的生命周期会被延续到初始化它的对象的构造过程结束。例如:A getObject() {
return A(); // 临时对象 A() 的生命周期延续到 getObject() 返回值初始化
}
2.赋值和初始化:
在赋值或初始化过程中,临时对象的生命周期通常会延续到赋值或初始化操作完成。例如:A a = A(1); // 临时对象 A(1) 的生命周期延续到 a 的初始化完成
3.常量表达式:
临时对象的生命周期可以延续到其使用的上下文。例如,在常量表达式中,临时对象的生命周期会延续到该表达式的结束。
4.绑定到const引用:
如果临时对象被绑定到const引用,则临时对象的生命周期会被延续到绑定到该引用的对象的生命周期结束。例如:const A& ref = A(); // 临时对象 A() 的生命周期延续到 ref 的生命周期结束
5.无 const 引用绑定:
如果临时对象没有被绑定到const引用,临时对象的生命周期通常不会延续,可能会导致未定义行为。因此,直接使用临时对象创建非const引用是不安全的。总结来说,临时对象的生命周期通常会被延续到当前作用域的结束,尤其是当它被绑定到const引用或用于初始化其他对象时。理解这些规则对于正确管理对象的生命周期和避免未定义行为非常重要。
#include<iostream>
#include<assert.h>
#include <algorithm>
using namespace std;
class A
{
public:
A(int a1=1, int a2=1)
:_a1(a1)
,_a2(a2)
{
cout << "A(int a)" << endl;
}
A(const A& d)
{
cout << "A(const A&d)" << endl;
}
A& operator=(const A& d)
{
cout << "A& operator=(const A& d)" << endl;
return *this;
}
~A()
{
cout << "~A()" << endl;
}
void Print()
{
cout << "A->Print->" << _a1 << endl;
}
A& operator++()
{
_a1 += 100;
return *this;
}
private:
int _a1=0;
int _a2=0;
};
A f()
{
A a(1);
return a;
}
int main()
{
f();
return 0;
}
二:
首先调用函数会构造aa,然后是重载函数,返回是返回临时变量则需要拷贝构造然后析构aa,在拷贝给ret,而运行结果只有构造函数,重载函数和析构函数。
A f()
{
A aa(2);
++aa;
return aa;
}
int main()
{
A ret = f();
return 0;
}
2.C++管理内存方式
C语言内存管理方式在C++中可以继续使用,但也有些麻烦,所以C++有自己的内存管理方式:通过new和delete操作进行动态内存管理。
2.1new/delete操作内置类型
int main()
{
//动态申请一个int类型空间
int* p1 = new int;
//动态申请一个int类型空间并且初始化为10
int* p2 = new int(10);
//动态申请10个int类型空间
int* p3 = new int[10];
delete p1;
delete p2;
delete[] p3;
return 0;
}
new和delete就是C的加强版malloc和free
申请和释放单个元素的空间,使用new和delete,申请和释放连续空间,使用new[]和delete[],匹配起来使用。
2.2new和delete操作自定义类型
int* p3 = (int*)malloc(sizeof(int));
int* p4 = new int;
free(p3);
delete p4;
对于内置来说这俩个几乎是没有区别的
A* p1 = (A*)malloc(sizeof(A));
A* p2 = new A(1,1);
free(p1);
delete p2;
而对于自定义类型new和delete除了会开空间和释放空间还会调用构造函数和析构函数
3.operator new与operator delete函数
new和delete 是用户进行动态申请和释放的操作符,operator new和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间
malloc申请失败会返回NILL,而new申请失败则会抛出异常
4.new和delete实现原理
内置类型:
如果申请的是内置类型空间,new和mallco,delete和free基本相似
自定义类型:
new原理:
1.调用operator new函数申请空间
2.在申请的空间上执行构造函数,完成对象的构造
delete原理:
1.在空间上执行析构函数,完成对象资源的清理
2.调用operator delete函数释放对象空间
new T[N]的原理:
1.调用operator new[]函数,在operator new[]中调用operator new函数完成N个对象的空间申请
2.在申请的空间上执行N次构造函数
delete[]的原理:
1。在释放的对象空间上执行N次析构函数,完成对N个对象中资源的清理
2.调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间
注意:若是不匹配来则会出问题
class A
{
public:
A(int a1=1, int a2=1)
:_a1(a1)
,_a2(a2)
{
cout << "A(int a)" << endl;
}
A(const A& d)
{
cout << "A(const A&d)" << endl;
}
A& operator=(const A& d)
{
cout << "A& operator=(const A& d)" << endl;
return *this;
}
~A()
{
cout << "~A()" << endl;
}
void Print()
{
cout << "A->Print->" << _a2 << endl;
}
A& operator++()
{
_a2 += 100;
cout << "A& operator++()" << endl;
return *this;
}
private:
int _a1=0;
int _a2=0;
};
A f()
{
A aa(2);
++aa;
cout << "hhhh"<< endl;
return aa;
}
class B
{
public:
void Print()
{
cout << "h" << endl;
}
private:
int _a1 = 1;
int _a2 = 1;
};
int main()
{
A* p = new A[10];
B* p1 = new B[10];
delete p;
delete p1;
return 0;
}
因为在开辟10个类型A的空间时(此时有八十个字节),但是还会再开辟一个四个字节的空间去储存有多少个的数量,而delete p就是从四个字节后开始释放,但是在C中我们知道free要从头开始,所以会报错,只有delete[] p才会从头开始释放,而p1没问题是因为类B没有些析构函数,但B有析构函数时也会报错,没有析构则编译器认为B的数据都是内置直接释放就行。