目录
explicit的应用:防止隐式转换。
#include <iostream>
using namespace std;
class A
{
public:
//阻止构造函数的隐式转换
explicit A(int x=10):val(x){}
void set_val(int x){
this->val = x;
}
void print_val(void){
cout<<"val:"<<val<<endl;
}
//转换函数 实现 A->int
explicit operator int(){
return val;
}
//explicit 用于防止隐式转换
private:
int val;
};
int main(void)
{
A a1(3);//普通构造
a1.print_val();
//强制转换,将 A 类型的 a1 转换成 int 类型
cout<<int(a1)<<endl;
//一般情况下,左右类型不一致,将自动发生隐式转换
//报错:不存在从 "A" 到 "int" 的适当转换函数,这里便是由于explicit的存在
int i = a1;//赋值函数
//报错:没有与这些操作数匹配的 "+" 运算符C/C++(349)
//conversion_func.cpp(31, 15): 操作数类型为: A + int
//因为explicit的存在,阻止了 A 类型的a1转换成 int 类型,所以会抱没有这里对象相+的重载错误
A a2 = a1 + 54;
A a2(a1);//拷贝构造
//报错:不存在从 "int" 转换到 "A" 的适当构造函数
// explicit阻止了 10 转换成 类型A,从而进行拷贝构造的操作
A a2 = 10;
return 0;
}
拷贝构造和赋值函数
拷贝构造:
实质是一种特殊的构造函数,区别于普通构造,其中普通构造是将类中的私有数据进行赋值的操作,而拷贝构造是将定义一个与被拷贝类型相同的对象,如A a1(a2),即定义一个a2的对象,其类型与A类型的a1对象相同
赋值函数:
分为对象赋值,数据赋值,其实质是赋值操作运算符重载,operator=()
//对象赋值 A a1,a2; a2 = a1; //数据赋值 int i = 10;
谈及拷贝构造便会引发深拷贝和浅拷贝问题
浅拷贝:
不开辟空间,直接进行指针复制
#include <iostream> using namespace std; class A{ public: A(int x=10):val(x){ cout<<"构造函数"<<endl; val = x; ptr = new int; *ptr = x; } ~A(){ cout<<"析构函数"<<endl; delete [] ptr; } A(const A &obj){ cout<<"浅拷贝"<<endl; val = obj.val; //两个指针指向同一片 空间,这里会造成double free()问题,也就是指针悬挂现象 ptr = obj.ptr; } void print_data(void){ cout<<"val:"<<val<<endl; cout<<"ptr:"<<ptr<<endl; } private: int val; int *ptr; }; int main(void){ A a1(23); A a2(a1); a1.print_data(); a2.print_data(); } //运行结果 /* 构造函数 浅拷贝 val:23 ptr:0x5581a90b22c0 val:23 ptr:0x5581a90b22c0 析构函数 析构函数 free(): double free detected in tcache 2 已放弃 (核心已转储) */
深拷贝:
开辟空间,并不进行指针的直接复制
#include <iostream> using namespace std; class A{ public: A(int x=10):val(x){ cout<<"构造函数"<<endl; val = x; ptr = new int; *ptr = x; } ~A(){ cout<<"析构函数"<<endl; delete [] ptr; } A(const A &obj){ cout<<"深拷贝"<<endl; //由于成员数据中有指针,因此需要实现深拷贝 //如果成员数据里面没有指针,则不需要实现深拷贝 //浅拷贝实质是两个指针指向同一片空间,释放空间时会导致同一片空间释放两次 //深拷贝实质就是重新开辟一个空间,释放空间时各种释放自己指向的空间 val = obj.val; ptr = new int; *ptr = *obj.ptr; } void print_data(void){ cout<<"val:"<<val<<endl; cout<<"ptr:"<<ptr<<endl; } private: int val; int *ptr; }; int main(void){ A a1(23); A a2(a1); a1.print_data(); a2.print_data(); } /* 构造函数 深拷贝 val:23 ptr:0x561f764b62c0 val:23 ptr:0x561f764b62e0 析构函数 析构函数 */
深拷贝、浅拷贝拓展:实现智能指针中的共享指针;单例问题,如windows的任务管理器
讲完拷贝函数,我们再聊聊赋值函数,赋值函数需要根据实际情况决定是否进行深拷贝
赋值函数:本质是赋值运算符的重载,即operator=()
浅拷贝(数据不独立):
#include <iostream> using namespace std; class A{ public: A(int x=10):val(x){ cout<<"构造函数"<<endl; val = x; ptr = new int; *ptr = x; } ~A(){ cout<<"析构函数"<<endl; delete [] ptr; } A(const A &obj){ cout<<"深拷贝"<<endl; //由于成员数据中有指针,因此需要实现深拷贝 val = obj.val; ptr = new int; *ptr = *obj.ptr; } void operator=(const A &obj){ cout<<"赋值函数"<<endl; //虽然成员数据中有指针,但是是对一个 int 型的指针进行赋值操作,空间也就是4字节,所以不需要进行深拷贝,也就是说不需要考虑越界(非法访问)问题 if(this == &obj){ //自己给自己赋值,不需要进行赋值操作,退出 cout<<"this=&obj"<<endl; return ; } this->val = obj.val; //指针指向的4字节内容被覆盖 *ptr = *obj.ptr; } void print_data(void){ cout<<"val:"<<val<<endl; cout<<"ptr:"<<ptr<<endl; } private: int val; int *ptr; }; int main(void){ A a1(10); A a2(20); a2 = a1; a1.print_data(); a2.print_data(); } /* 构造函数 构造函数 赋值函数 val:10 ptr:0x55ff490d82c0 val:10 ptr:0x55ff490d82e0 析构函数 析构函数 */
那么可能就有人会发出疑问呢?为啥子a1和a2的地址不一样,因为本文代码中a2和a1都是在构造时开辟了空间,两个所占空间不一样,本来a2是20的,但是进行了赋值操作,所以就变成了10,但是两者的所占空间并不一样,注意,我们只是进行的赋值操作,赋值!!!
那么我们升华一点,如果一个占20个字节的空间,该空间中本来有内容,但是进行了赋值操作,注意:这里会分两种情况①是否需要保留原有数据,如果没必要则不用进行深拷贝(重新调用拷贝构造开辟空间),浅拷贝即可②原有数据后续有用,那么必须进行深拷贝。
深拷贝(数据独立):
#include <iostream> #include <cstdlib> #include <cstring> using namespace std; class Array { public: Array(int n=10){ cout<<"构造函数"<<endl; data = new int [n]; size = n; pos = 0; } ~Array(){ cout<<"析构函数"<<endl; delete [] data; } void operator=(const Array &obj){ cout<<"赋值函数深拷贝"<<endl; if(this==&obj){ cout<<"自我赋值"<<endl; return ; } if(size != obj.size){ //大小不一致 重新开辟空间 delete [] data; data = new int [obj.size]; } //内存拷贝函数 size = obj.size; memcpy(data,obj.data,size*sizeof(int)); pos = obj.pos; } //从尾巴插人数据 int append(int val){ if(pos>size-1){ cout<<"full"<<endl; return -1; } data[pos++] = val; return 0; } //修改数组数据 int update(int index,int val){ data[index] = val; return 0; } //show数组 void show_array(){ int i; for(i=0;i<pos;i++){ cout<<data[i]<<" "; } cout<<endl; } //查看数组首地址 void print_data(){ cout<<data<<endl; } private: int *data; int size; int pos; }; int main(void) { Array a(3); a.append(13); a.append(14); a.append(520); Array b; b = a; cout<<"数组a :"; a.show_array(); cout<<endl; cout<<"数组b :"; b.show_array(); cout<<endl; //修改数组b下标为2的内容 //如果是浅拷贝,则代表着公用一片空间,数组a内容也会被修改 //如果数组 a 内容没有被修改则代表着深拷贝 b.update(2,250); cout<<"数组a :"; a.show_array(); cout<<endl; cout<<"数组b :"; b.show_array(); cout<<endl; cout<<"================"<<endl; a.print_data(); b.print_data(); return 0; } /* 构造函数 构造函数 赋值函数深拷贝 数组a :13 14 520 数组b :13 14 520 数组a :13 14 520 数组b :13 14 250 ================ 0x5614e47fc2c0 0x5614e47fc310 析构函数 析构函数 */
拓展:赋值函数的深浅拷贝还需要看等号左右的空间是否一致,如果等号右边的空间大于等号左边的空间,那么就需要进行深拷贝,不然会造成数组越界,非法访问内存。
那么你们是否感觉有点怪怪的,对,就目的而言,赋值函数本来就需要被覆盖,被覆盖的数据都没用,对,赋值函数根本就没有数据独立这一说,所以,赋值函数大多数也都是空间不够,所以需要重新开辟空间,才进行的深拷贝
#include <iostream> #include <cstdlib> #include <cstring> using namespace std; class Array { public: Array(int n=10){ cout<<"构造函数"<<endl; data = new int [n]; size = n; pos = 0; } ~Array(){ cout<<"析构函数"<<endl; delete [] data; } void operator=(const Array &obj){ cout<<"赋值函数浅拷贝"<<endl; if(this==&obj){ cout<<"自我赋值"<<endl; return ; } *data = *obj.data; size = obj.size; pos = obj.pos; } //从尾巴插人数据 int append(int val){ if(pos>size-1){ cout<<"full"<<endl; return -1; } data[pos++] = val; return 0; } //修改数组数据 int update(int index,int val){ data[index] = val; return 0; } //show数组 void show_array(){ int i; for(i=0;i<pos;i++){ cout<<data[i]<<" "; } cout<<endl; } void print_data(){ cout<<data<<endl; } private: int *data; int size; int pos; }; int main(void) { Array a(3); a.append(13); a.append(14); a.append(520); Array b = a; //拷贝构造浅拷贝,第一次出现 cout<<"数组a :"; a.show_array(); cout<<endl; cout<<"数组b :"; b.show_array(); cout<<endl; //修改数组b下标为2的内容 //如果是浅拷贝,则代表着公用一片空间,数组a内容也会被修改 //如果数组 a 内容没有被修改则代表着深拷贝 b.update(2,250); cout<<"数组a :"; a.show_array(); cout<<endl; cout<<"数组b :"; b.show_array(); cout<<endl; cout<<"================"<<endl; a.print_data(); b.print_data(); return 0; } /* 构造函数 数组a :13 14 520 数组b :13 14 520 数组a :13 14 250 数组b :13 14 250 ================ 0x55a2ef01a2c0 0x55a2ef01a2c0 析构函数 析构函数 free(): double free detected in tcache 2 已放弃 (核心已转储) */
区分赋值函数和拷贝构造:
#include <iostream> using namespace std; class A{ public: A(int x=10):val(x){ cout<<"构造函数"<<endl; val = x; ptr = new int; *ptr = x; } ~A(){ cout<<"析构函数"<<endl; delete [] ptr; } A(const A &obj){ cout<<"深拷贝"<<endl; //由于成员数据中有指针,因此需要实现深拷贝 val = obj.val; ptr = new int; *ptr = *obj.ptr; } void operator=(const A &obj){ cout<<"赋值函数"<<endl; //虽然成员数据中有指针,但是是对一个 int 型的指针进行赋值操作,空间也就是4字节,所以不需要进行深拷贝 if(this == &obj){ //自己给自己赋值,不需要进行赋值操作,退出 cout<<"this=&obj"<<endl; return ; } this->val = obj.val; //指针指向的4字节内容被覆盖 *ptr = *obj.ptr; } void print_data(void){ cout<<"val:"<<val<<endl; cout<<"ptr:"<<ptr<<endl; } private: int val; int *ptr; }; int main(void){ A a1 = 10; //a1 第一次出现 a1.print_data(); cout<<"==========="<<endl; A a2(a1); //a2 第一次出现 a2.print_data(); cout<<"==========="<<endl; A a3(20); //a3 第一次出现 a3.print_data(); cout<<"==========="<<endl; a2 = a1; //a2 第二次出现 cout<<"a1:"; a1.print_data(); cout<<endl; cout<<"a2:"; a2.print_data(); cout<<endl; return 0; } /* 构造函数 val:10 ptr:0x562ecc03e2c0 =========== 深拷贝 val:10 ptr:0x562ecc03e2e0 =========== 构造函数 val:20 ptr:0x562ecc03e300 =========== 赋值函数 a1:val:10 ptr:0x562ecc03e2c0 a2:val:10 ptr:0x562ecc03e2e0 析构函数 析构函数 析构函数 */
注意:系统自带的全是拷贝构造,赋值函数全是浅拷贝
=====================================================================
总结:
有指针的情况下:
①拷贝构造进行浅拷贝时是 A a1(23); A a2(a1); 这里a1开辟了空间,a2并没有开辟空间,还是指向之前开辟的空间,析构时会出现double free问题(上文);
②拷贝构造进行深拷贝时是 A a1(23); A a2(a1); 这里两者都开辟了空间(上文);
③普通构造和拷贝构造是对象第一次出现,会调用拷贝构造或者普通构造;
④赋值函数是在对象已经被定义,只是进行赋值操作时才会被调用;
注意:类型不匹配,也会先调用拷贝函数,在调用赋值函数,如:
A a1; a1 = 10; A有两种构造函数,一个无参构造,一个有参构造A(int a){};
⑤调用赋值函数时如果原有数据不重要,则不需要进行深拷贝,浅拷贝即可;
⑥调用赋值函数时如果原有数据很重要,则需要进行深拷贝;
⑦调用赋值函数时如果空间不足,则必须在赋值函数中开辟新空间,进行深拷贝;
⑧调用赋值函数时,由于对象已经被定义,所以地址并不相同;
⑨调用赋值函数浅拷贝时,会间接调用拷贝构造的浅拷贝,所以地址才会显示一样。
无指针深浅拷贝都可