前面学习了构造与析构函数的概念,此处做一复习。
构造函数: 编译器帮你在对象创建调用函数做初始化动作
析构函数: 编译器在对象要死亡的时候帮你调用函数做资源的反初始化动作
那什么是构造函数?带着这个疑问,我们来一步步拨开它神秘的面纱。
总结:
1.作用: 本质也是一种构造函数,利用一个对象创建另一个对象(利用对象作为参数)
2.调用时机: 用一个对象创建另一个对象的时候调用CStudent stu2 = stu;
3.缺省的拷贝构造作用:
- 完全把对象1拷贝给对象2;
- 内存的拷贝
(你不写,编译器帮你写),若你自己要写CStudent(CStudent& obj) {...},
就会跳入自己写的拷贝构造函数中
4.禁用默认的拷贝构造函数: CStudent(CStudent& obj) = delete;
5.使用默认的拷贝构造函数: CStudent(CStudent& obj) = default;
1. 利用一个对象创建另一个对象
1.1 实现利用一个对象创建另一个对象
我们已经有了一个创建好的对象,如何将其赋值给新的对象:
CStudent stu2 = stu;//将stu对象赋值给stu2 类似于int n=1;int k=n;
整体代码如下:
#include <iostream>
class CStudent {
public:
//无参构造函数
CStudent() {
//为了m_szNam创建一个堆空间(分配动态内存)
m_szName = (char*)malloc(255);//c语言中在堆上创建动态内存
//free(m_szName);//c语言描述释放内存,内存没有得到有效的释放,就是内存泄漏
}
CStudent(char* pName, int nlength) {
//判断缓存要求是否大于255,大于255就返回
if (nlength > 255) {
return;
}
//为了m_szNam创建一个堆空间(分配动态内存)
m_szName = (char*)malloc(255);//c语言中在堆上创建动态内存
//判断堆上创建的内存是否为空,空则返回
if (m_szName == nullptr) {
return;
}
memcpy(m_szName, pName, nlength);
}
//析构函数
~CStudent() {
free(m_szName);//将参数的资源释放
}
void SetName(const char* pszName){}
private:
int m_nStudID;//学号
//指针代表指向的地址,不会限制住m_szName的大小,相对于预定义好的缓冲,避免了溢出的风险
char* m_szName;
};
int main(int argc, char* argv[])
{
//创建对象进行构造,strlen("张三")+1是为了包含结尾
CStudent stu("张三",strlen("张三") + 1);
//利用已经定义的对象定义另一个对象
CStudent stu2 = stu; //将stu对象赋值给stu2 类似于int n=1;int k=n;
return 0;
}
运行结果:可以看到两个对象完全一样。
- 从“const char [5]”转换为“char*的报错解决方法:
此处代码编译后,报错如下:C++ 无法将参数 1 从“const char [5]”转换为“char*
将项目属性 >>C++>> 语言 >> 符合模式调成否
或者将有参构造函数改写为:CStudent(const char* pName, int nlength) {}
再次编译即可正常编译通过。
1.2 两个对象的构造和析构次数
在析构和构造函数中分别加printf函数。
#include <iostream>
class CStudent {
public:
//无参构造函数
CStudent() {
//为了m_szNam创建一个堆空间(分配动态内存)
m_szName = (char*)malloc(255);//c语言中在堆上创建动态内存
printf("CStudent()\r\n");
}
CStudent(char* pName, int nlength) {
printf("CStudent(char* pName, int nlength)\r\n");
//判断缓存要求是否大于255,大于255就返回
if (nlength > 255) {
return;
}
//为了m_szNam创建一个堆空间(分配动态内存)
m_szName = (char*)malloc(255);//c语言中在堆上创建动态内存
//判断堆上创建的内存是否为空,空则返回
if (m_szName == nullptr) {
return;
}
memcpy(m_szName, pName, nlength);
}
//析构函数
~CStudent() {
printf("~CStudent()\r\n");
free(m_szName);//将参数的资源释放
}
void SetName(const char* pszName){}
private:
int m_nStudID;//学号
//指针代表指向的地址,不会限制住m_szName的大小,相对于预定义好的缓冲,避免了溢出的风险
char* m_szName;
};
int main(int argc, char* argv[])
{
//创建对象进行构造
CStudent stu("张三", strlen("张三") + 1);
//利用已经定义的对象定义另一个对象
CStudent stu2 = stu; //将stu对象赋值给stu2 类似于int n=1;int k=n;
return 0;
}
运行结果:
- 一次构造,两次析构
- 整个程序崩溃
为什么会出现构造一次,析构两次的情况呢?
在下面两句设置断点,可以看到:
CStudent stu("张三", strlen("张三") + 1);//跳转到构造函数中
CStudent stu2 = stu; //并未跳转到哪一个构造函数,但是对象已经构造好,但没调用传统函数且和stu成员一样
stu
为什么在没有调用普通构造函数就可以进行构造呢?其实是因为它调用了今天要提到的拷贝构造函数。
2. 拷贝构造函数
- 拷贝构造函数作用: 本质也是一种构造函数
- 调用时机: 用一个对象创建另一个对象的时候调用
2.1 缺省的拷贝构造作用
CStudent stu2 = stu;
的时候就会调用缺省下的拷贝构造函数,对stu2进行初始化。
简单的说就是把一个对象完全拷贝给另一个对象。
从前面的运行可以看到,在拷贝构造时,我们函数内部是没写拷贝构造函数的,但是它还是可以实现拷贝构造的功能,这是因为编译器帮你写了。
2.2 自己写拷贝构造函数
CStudent(CStudent& obj) {printf("CStudent(CStudent& obj)\r\n");} //通过引用的方式,将一个对象obj拷贝给另一个对象,此函数中未写功能,只会实现printf()
。
代码如下:
#include <iostream>
class CStudent {
public:
CStudent() {
printf("CStudent()\r\n");
//为了m_szNam创建一个堆空间(分配动态内存)
m_szName = (char*)malloc(255);
}
//为了初始化方便,使用带有参数的构造
CStudent(const char* pName, int nlength) {
printf("CStudent(char* pName, int nlength)\r\n");
//判断缓存要求是否大于255,大于255就返回
if (nlength > 255) {
return;
}
//为m_szNam创建一个堆空间(分配动态内存)
m_szName = (char*)malloc(255);//c语言中在堆上创建动态内存
//判断堆上创建的内存是否为空,空则返回
if (m_szName == nullptr) {
return;
}
memcpy(m_szName, pName, nlength);
}
//通过引用的方式,将一个对象obj拷贝给另一个对象
CStudent(CStudent& obj) {
printf("CStudent(CStudent& obj)\r\n");
}
~CStudent() {
free(m_szName);
printf("~CStudent()\r\n");
}
void SetName(const char* pszName){}
private:
int m_nStudID;//学号
char* m_szName;
};
int main(int argc, char* argv[])
{
//创建对象进行构造,strlen("张三")+1是为了包含结尾
CStudent stu("张三", strlen("张三") + 1);
//利用已经定义的对象定义另一个对象
CStudent stu2 = stu; //将stu对象赋值给stu2 类似于int n=1;int k=n;
return 0;
}
这个时候再单步运行至:CStudent stu("张三", strlen("张三") + 1);
,跳转至:
CStudent(char* pName, int nlength) {
//判断缓存要求是否大于255,大于255就返回
if (nlength > 255) {
return;
}
//为了m_szNam创建一个堆空间(分配动态内存)
m_szName = (char*)malloc(255);//c语言中在堆上创建动态内存
if (m_szName == nullptr) {
return;
}
memcpy(m_szName, pName, nlength);
}
而CStudent stu2 = stu;
,跳转至:
//将一个对象拷贝给另一个对象,通过引用的方式
CStudent(CStudent& obj) {
printf("CStudent(CStudent& obj)\r\n");
}
当你写了拷贝构造函数,就会跳转至你写的里面,运行结果如下:由于我们自己写的拷贝构造函数中没有执行任何功能,因此stu2中为空
。
从上面可以看出:
在你没有写自己的拷贝构造函数的情况下即缺省情况下的拷贝完全是将对象1拷贝给对象2,memcpy内存复制
2.3 让编译器不采用缺省的拷贝构造函数
写法如下:
CStudent(CStudent& obj) = delete;
整体代码:
#include <iostream>
class CStudent {
public:
CStudent() {
printf("CStudent()\r\n");
//为了m_szNam创建一个堆空间(分配动态内存)
m_szName = (char*)malloc(255);
}
//为了初始化方便,使用带有参数的构造
CStudent(const char* pName, int nlength) {
printf("CStudent(char* pName, int nlength)\r\n");
//判断缓存要求是否大于255,大于255就返回
if (nlength > 255) {
return;
}
//为m_szNam创建一个堆空间(分配动态内存)
m_szName = (char*)malloc(255);//c语言中在堆上创建动态内存
//判断堆上创建的内存是否为空,空则返回
if (m_szName == nullptr) {
return;
}
memcpy(m_szName, pName, nlength);
}
CStudent(CStudent& obj) = delete;
~CStudent() {
free(m_szName);
printf("~CStudent()\r\n");
}
void SetName(const char* pszName){}
private:
int m_nStudID;//学号
char* m_szName;
};
int main(int argc, char* argv[])
{
//创建对象进行构造,strlen("张三")+1是为了包含结尾
CStudent stu("张三", strlen("张三") + 1);
//利用已经定义的对象定义另一个对象
CStudent stu2 = stu; //将stu对象赋值给stu2 类似于int n=1;int k=n;
return 0;
}
编译后:由于禁用了默认的拷贝构造函数,因此报错
2.4 让编译器采用缺省的拷贝构造函数
这个时候就是告诉编译器,我自己没有写拷贝构造函数,显式的告诉编译器使用默认的拷贝构造
,写法如下:CStudent(CStudent& obj) = default;
3.学习视频地址:C++57个入门知识点_23_ 拷贝构造函数