构造与析构函数
构造函数
创建一个对象时,常常需要做某些初始化的工作,例如对数据成员赋初值。
为了解决这个问题,C++提供了构造函数(constructor)来处理对象的初始化。构造函数是一种特殊的成员函数,与其他成员函数不同,不需要用户来调用它,而是在建立对象时自动执行。
特点:
- 构造函数名和类名相同
- 构造函数可以重载
- 构造函数没有返回类型声明
调用:
- 自动调用(隐式) 一般情况下C++编译器会自动调用构造函数(无参构造)
- 手动调用(显示) 在一些情况下则需要手工调用构造函数(有参构造)
析构函数
当对象释放时,我们可能需释放/清理对象里面的某些资源(比如:动态内存释放)。
为了解决这个问题,C++提供了析构函数(destructor)来处理对象的清理工作。析构函数和构造函数类似,不需要用户来调用它,而是在释放对象时自动执行。(对象生命周期结束后自动执行)
特点:
- 析构函数名和类名相同,但是得在前面加一个波浪号 ~
- 析构函数只能有一个(不能重载)
- 构造函数没有返回类型声明
构造/析构函数调用机制
class Test
{
public:
Test()
{
cout << "我是构造函数" << endl;
}
~Test()
{
cout << "我是析构函数" << endl;
}
private:
};
void objshow()
{
Test t1;
Test t2;
}
int main()
{
objshow();
}
结论:
- 先创建的对象先构造,后创建的对象后构造
- 先创建的对象后析构,后创建的对象先析构
构造函数分类
构造函数是可以重载的,根据参数类型和作用可以分为以下几类:
无参构造函数
-
直接创建对象即可自动调用
-
Test te;
注意:不要在对象后面加(),无参构造函数不能显式调用class Int { public: Int()//无参构造函数 :m_data(0)//使用初始化参数列表的方式初始化成员 { //w-data=0// 为赋值 } Int(int number)//有参构造函数 :m_data(number) { } Int(int a, int b) :m_data(a+b) { } ~Int() { } }; int main() { //使用=初始化 Int c = 20; //使用圆括号初始化 Int c(20); //使用花括号初始化 Int c{ 20 }; //多个参数初始化 Int e(3, 3);//直接调用构造函数 //先定义一个临时对象,然后对这个临时对象进行转正 Int d = { 1,2 }; Int f = Int(2, 3);//匿名对象 //Int(2,5)临时对象是一个右值,创建后立马释放 }
有参构造函数
-
有三种调用方法
//1,括号法 Test t1(20,"cc"); t1.print(); //2,赋值符号 Test t2 = {18,"maye"}; t2.print(); //3,匿名对象 Test t3 = Test(90,"plus"); t3.print(); //注意: Test tt; //error:类Test不存在默认构造函数
-
如果没有写有参构造函数,那么C++编译器会自动帮我们生成一个无参构造函数
如果写了有参构造函数,那么就不会帮我们生成了,必须自己写一个无惨构造函数,才能直接定义对象!
-
在C++中,使用初始化参数列表的方式可以在对象创建时直接为成员变量提供初值,而不是在函数体内部进行赋值操作。这种方式在构造函数中初始化成员变量时更为高效和直观。
以下是使用初始化参数列表的语法示例:
cppCopy codeclass MyClass {
public:
MyClass(int x, double y)
: member1(x), member2(y) // 使用初始化参数列表初始化成员变量
{
// 构造函数的函数体
// ...
}
private:
int member1;
double member2;
};
在上述示例中,构造函数MyClass
接受两个参数x
和y
,并通过初始化参数列表将其传递给member1
和member2
成员变量进行初始化。
使用初始化参数列表的优点包括:
- 效率:通过初始化参数列表,成员变量可以直接在对象创建时初始化,避免了默认构造函数和赋值操作的额外开销。
- const 成员变量和引用类型成员变量:某些情况下,成员变量是const类型或引用类型,它们只能在构造函数初始化参数列表中进行初始化,而无法在函数体内赋值。
- 成员变量初始化顺序:初始化参数列表中的成员变量初始化顺序由它们在类中的声明顺序决定,而不是由初始化参数列表中的顺序决定。这确保了成员变量按照正确的顺序初始化。
拷贝构造函数(赋值构造)
-
用一个对象去初始化另一个对象时(函数传参也会拷贝),需要拷贝构造(如果自己没有写,编译器会自动帮我们生成)
Test t(1,"2"); //1,赋值符号 Test t1 =t; //2,参数方法 Test t2(t); t2 = t1; //这个调用的是赋值运算符重载函数
-
注意:定义之后进行赋值不会调用拷贝构造函数,而是调用赋值函数,这是运算符重载
深拷贝和浅拷贝
首先,明确一点深拷贝和浅拷贝是针对类里面有指针的对象的,因为基本数据类型在进行赋值操作时(也就是拷贝)是直接将值赋给了新的变量,也就是该变量是原变量的一个副本,这个时候你修改两者中的任何一个的值都不会影响另一个,而对于对象来说在进行浅拷贝时,只是将对象的指针复制了一份,也就内存地址,即两个不同的对象里面的指针指向了同一个内存地址,那么在改变任一个对象的指针指向的内存的值时,都是该变这个内存地址的所存储的值,所以两个变量的值都会改变。
简而言之,当数据成员中有指针时,必须要用深拷贝。
-
浅拷贝只是增加了一个指针指向已存在的内存地址。
- 使用浅拷贝,释放内存的时候可能会出现重复释放同一块内存空间的错误。
-
深拷贝是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存。
- 使用深拷贝下,释放内存的时候不会因为出现重复释放同一个内存的错误。
- 使用深拷贝下,释放内存的时候不会因为出现重复释放同一个内存的错误。
注意
- C++类中默认提供的拷贝构造函数,是浅拷贝的
- 要想实现深拷贝,必须自己手动实现拷贝构造函数
常量左值引用可以接受左值、右值、常量左值、常量右值,而左值引用只能接受左值
const:普通引用引用不了右值,因此添加const常引用引用右值
移动构造函数
- 移动构造函数数用来实现移动语义,转移对象之间的资源而无需执行深拷贝或复制整个对象的内容。
作用:
- 提高性能:移动构造函数允许将临时对象或即将销毁的对象的资源(如动态分配的内存、文件句柄等)移动到新创建的对象中,而不是进行昂贵的复制操作。这可以避免不必要的数据复制和内存分配,提高程序的性能。
- 转移资源所有权:移动构造函数允许将一个对象的资源所有权转移到另一个对象上,而无需进行深拷贝。这在需要在对象之间传递或转移资源所有权的情况下非常有用,例如容器的扩容、函数返回值的优化等。
- 支持移动语义的类型:移动构造函数是实现移动语义的关键部分,它使得自定义类型可以像标准库类型(例如std::vector、std::string)一样支持高效的移动操作,从而提高整体的代码质量和性能。
移动构造函数通常使用右值引用作为参数,接受将要转移资源的对象。它将接收到的对象的资源转移到新创建的对象中,并使原始对象进入有效但未定义的状态。移动构造函数通常使用std::move函数来标记需要移动的对象。
左值不能移动,而std:move() 能将左值强制转换成右值
class TString
{
public:
TString()
:m_str(nullptr),m_size(0)
{
}
TString(const char* str)
:TString() //(委托构造)委托自己的其他的构造函数,帮忙构造一下
{
m_size = strlen(str);
m_str = new char[m_size + 1];
strcpy(m_str, str);
}
//默认的拷贝构造函数提供的是浅拷贝,但是如果成员变量里面有指针,就会发送问题
//自己实现拷贝构造函数(实现深拷贝)
TString(const TString& other)
{
m_size = other.m_size;
//m_str = other.m_str;
m_str = new char[m_size + 1];
strcpy(m_str, other.m_str);
}
//移动构造函数
TString(TString&& other)
{
m_size = other.m_size;
m_str = other.m_str;
other.m_str = nullptr;
}
~TString()
{
if(m_str)
delete[] m_str;
}
private:
char* m_str;
int m_size;
};
TString CreateTString();
int main2()
{
TString name;
TString hello("hello"); //0x0000023dfea8f140 //0x000002661545f080
TString str = hello; //0x0000023dfea8f140 //0x000002661545ef40
//error C2440: “初始化”: 无法从“TString”转换为“TString”
//message : 由于复制构造函数不明确或没有可用的复制构造函数,因此无法复制构造 class“TString”
TString str1 = TString("maye"); //匿名对象为右值
//
TString love("I Love You!");
TString iLove = std::move(love); //love是左值,std::move就是把左值强转成右值
auto t = CreateTString();
return 0;
}
TString CreateTString()
{
TString t("我是函数");
return t;
}