c++构造函数和析构函数
构造函数
💬 什么是构造函数
🔑构造函数就是一个特殊的类成员函数,函数名和类名相同,它在创建类类型对象时会由编译器自动调用
函数名()
{
}
函数名(参数1,参数2)
{
}
💬构造函数有什么作用
🔑构造函数主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中。特别的一个类可以有多个构造函数 ,可根据其参数个数的不同或参数类型的不同来区分它们 (即构造函数的重载)。
🎯构造函数的特点
① 函数名和类名相同
② 没有返回值
③ 如果不写构造函数,任何类中都存在一个默认的构造函数
默认的构造函数是无参的。
当我们自己写了构造函数,默认的构造函数就不存在
④ 构造函数在构造对象的时候调用
⑤ delete可以用来删掉默认的函数
⑥ 指定使用默认的无参构造函数,用default说明
⑦ 允许构造函数调用另一个构造函数,只是要用初始化参数列表的写法
⑧ 初始化参数列表 : 只有构造函数有
构造函数名(参数1,参数2,…):成员1(参数1),成员2(参数2),…{}
⑨避免形参名和数据成员名相同的导致问题
🎯构造函数代码分析
#include<iostream>
#include<string>
using namespace std;
class Student
{
public:
Student()
{
_name = "莫九梦";
_age = 20;
_id = 111;
}
Student(string name, int age, int id)
{
_name = name;
_age = age;
_id = id;
}
void print()
{
cout << _name << " " << _age << " " << _id << endl;
}
protected:
string _name;
int _age;
int _id;
};
int main()
{
Student student;//触发构造,调用无参构造函数
student.print();
Student student1("梦", 21, 2020);//触发构造,调用有参构造函数
student1.print();
return 0;
}
🎡运行结果
🎯默认构造函数分三种:
①我们不写,编译器就自己生成的构造函数
② 无参的构造函数
③ 全缺省的构造函数(效果和无参构造函数一样)
😈注意:
构造函数是特殊的类成员函数,不能用student.Student();的方式调用
Student student;
student.Student();
//错误的调用方法
通过无参构造函数创建对象,对象后面不能跟括号,否则就成了函数声明。
#include<iostream>
#include<string>
using namespace std;
class Student
{
public:
Student() {}
Student(string name)
{
_name = name;
}
void print()
{
cout << _name << endl;
}
protected:
string _name;
};
int main()
{
Student student();
// 这个对象实际上没有被定义出来,这里会报错。
//调用无参构造函数不能带括号,就用不带括号的来定义就行了
//Student student;
return 0;
}
如果你没有定义构造函数(类中未显式定义),C++ 编译器就会自动生成一个默认的无参构造函数。当然,如果你自己定义了,编译器就不会帮你生成。
#include<iostream>
using namespace std;
class Data
{
public:
void print()
{
cout << data << endl;
}
protected:
int data;
};
int main()
{
//没有定义构造函数,对象也可以创建成功
Data data;//这里调用的是默认的无参构造函数
data.print();
return 0;
}
定义了无参构造函数不能同时构造全缺省构造函数
//错误代码
#include<iostream>
#include<string>
using namespace std;
class Student
{
public:
Student()
{
_name="莫九梦";
}
Student(string name="莫九梦")
{
_name = name;
}
void print()
{
cout << _name << endl;
}
protected:
string _name;
};
int main()
{
Student student;
return 0;
}
析构函数
既然知道了怎么初始化对象,那么如何清理对象呢?这里就要用到析构函数了
🔥先来讲讲析构函数的概念
析构函数和构造函数一样,也是特殊的类成员函数,作用吗?和构造函数相反,析构函数主要是用来清理对象的
这里要注意了!!!
构造函数主要任务是初始化对象,而不是创建对象
析构函数主要是清理对象,而不是销毁对象
🎯析构函数的特点
① 析构函数名是在类名前面加个~
② 没有参数也没有返回值(因为没有参数,所以也不会构成重载问题)
③ 释放数据成员new的内存
在对象死亡前自动调用
通常如果数据成员没有做new操作,就可以不写析构函数
④ 不写析构函数,存在一个默认的析构函数
⑤ 一个类的析构函数有且仅有一个(如果不写系统会默认生成一个析构函数)
🎡这里我们写个代码看看析构函数的自动调用
#include<iostream>
#include<string>
using namespace std;
class Student
{
public:
Student(string name = "莫九梦");
~Student();
void print()
{
cout << _name << endl;
}
private:
string _name;
};
Student::Student(string name)
{
_name = name;
}
Student::~Student()
{
cout << "调用了析构函数" << endl;
}
int main()
{
Student student;
Student student1("九梦");
return 0;
}
拷贝构造函数
① 拷贝构造函数也是构造函数
② 拷贝构造函数参数是固定的:对对象的引用
③ 拷贝构造函数不写会存在一个默认的拷贝构造函数
④ 拷贝构造函数作用: 通过一个对象产生另一个对象
关键点:一定是有一个新的对象产生
👀拷贝构造函数的概念
拷贝构造函数,又称复制构造函数,是一种特殊的构造函数,它由编译器调用来完成一些基于同一类的其他对象的构建及初始化。(它是一个特殊的成员函数)
🎨 拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用 const 修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
👀拷贝构造函数的特点
① 拷贝构造函数是构造函数的一个重载形式。函数名和类名相同,没有返回值。
② 拷贝构造函数的参数只有一个,并且必须要使用引用传参
💬代码分析
#include <iostream>
#include <string>
using namespace std;
class Student
{
public:
Student() {}
Student(string name, int age)
{
_name = name;
_age = age;
}
//拷贝构造函数
//通过传入对象属性确定创建对象的属性
Student(Student& object)
{
_name = object._name;
_age = object._age;
cout << "调用自己写的拷贝构造函数" << endl;
}
void print()
{
cout << _name << "\t" << _age << endl;
}
protected:
string _name;
int _age{};
};
//函数传参也可以隐式调用
void print(Student object) //Student object=girl,产生了新的对象object
{
object.print();
}
//C++中传参能用引用就用,效率搞
void printData(Student& object) //引用就是别名,没有产生新的对象
{
object.print();
}
int main()
{
Student student("莫九梦", 20); //产生一个对象
Student beauty = student; //隐式调用拷贝构造函数
Student girl(beauty); //显式调用拷贝构造函数
print(girl); //调用拷贝构造函数
printData(girl);
Student boy; //对象先有了,才赋值,就不调用拷贝
boy = girl; //不调用拷贝构造函数-->默认重载=运算符(后续会讲)
return 0;
}
深浅拷贝问题
⛅浅拷贝
- 没有在拷贝构造函数中给数据成员做new操作
- 默认拷贝构造函数都是浅拷贝
🌞深拷贝
- 在拷贝构造函数中做了new操作
代码分析
#include <iostream>
#include <cstring>
using namespace std;
class MM
{
public:
MM() {}
MM(const char* str)
{
int length = strlen(str) + 1;
name = new char[length];
strcpy_s(name, length, str);
}
~MM()
{
if (name != nullptr)
{
delete[] name;
name = nullptr;
}
}
private:
char* name;
};
int main()
{
{
MM girl("girl");
MM mm = girl; //调用拷贝构造函数
}
return 0;
}
🚗解决方案
通过深拷贝来解决
#include <iostream>
#include <cstring>
using namespace std;
class MM
{
public:
MM() {}
MM(const char* str)
{
int length = strlen(str) + 1;
name = new char[length];
strcpy_s(name, length, str);
}
MM(MM& object)
{
//name = object.name;
int length = strlen(object.name) + 1;
name = new char[length];
strcpy_s(name, length, object.name);
}
~MM()
{
if (name != nullptr)
{
delete[] name;
name = nullptr;
}
}
private:
char* name;
};
int main()
{
{
MM girl("girl");
MM mm = girl; //调用拷贝构造函数
}
return 0;
}
综上: 一旦类中有指针,做了内存申请,并且要对对象做拷贝操作,就必须使用深拷贝
构造和析构顺序问题
- 一般情况构造顺序和析构是相反的
- 静态的和全局的是最后释放的
- delete 立刻调用析构函数
#include <iostream>
#include <string>
using namespace std;
class Test
{
public:
Test(string data = "A")
{
m_data = data;
cout << m_data;
}
~Test()
{
cout << m_data;
}
protected:
string m_data;
};
int main()
{
{
Test t1; //A
static Test t2("B"); //B
Test array[3]; //AAA //数组就是多个无参
Test* p = new Test("C"); //C
delete p; //C
p = nullptr;
}
//先构造函数输出ABAAAC,再析构函数输出CAAAAB
//说明构造函数和析构函数的调用顺序是相反的
return 0;
}