编译器默认生成的拷贝构造:默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝称为浅拷贝。
不写拷贝构造,编译器会自动生成默认拷贝构造,拷贝方式为浅拷贝
默认构造函数:对于内置类型成员值拷贝,浅拷贝:按照字节序传值。
默认构造函数:对于自定义类型,去调用自定义类型的拷贝构造
class Stack
{
public:
Stack(int capacity = 4)
{
cout << "Stack(int capacity = 4)" << endl;
_a = (int*)malloc(sizeof(int)*capacity);
assert(_a);
_top = 0;
_capacity = capacity;
}
~Stack()
{
cout << "~Stack()" << endl;
free(_a);
_a = nullptr;
_capacity = _top = 0;
}
private:
int* _a;
int _top;
int _capacity;
};
int main()
{
Stack st1;
Stack st2(st1);
return 0;
}
比如上述程序会出现内存问题。Stack内部没有定义拷贝函数,所以编译器会自动生成拷贝构造函数,拷贝方式为浅拷贝。由于Stack成员变量为指针,按照内存序拷贝拷贝的是指针,因此st1和st2指向的是同一个位置。同一块内存空间析构两次所以会出现内存问题。
浅拷贝:
- 1.指向同一块空间,修改数据会相互影响
- 2.这块空间析构时会释放两次,程序会崩溃。
- 解决方案:实现深拷贝
程序2
class Stack
{
public:
Stack(int capacity = 4)
{
cout << "Stack(int capacity = 4)" << endl;
_top = 0;
_capacity = capacity;
//memset是按照一个字节一个字节进行赋值
//所以初始化最好每个字节初始化为0
memset(_a, 0, sizeof(int) * 10);
}
~Stack()
{
cout << "~Stack()" << endl;
_capacity = _top = 0;
}
private:
int _a[10];
int _top;
int _capacity;
};
int main()
{
Stack st1;
Stack st2(st1);
return 0;
}
如果存放的是数组,可以正常的拷贝。因为st1存储的数组和st2存储的数组指向的不是同一块空间。
自定义类型:去调用自定义类型的拷贝构造
程序一
class Stack
{
public:
Stack(int capacity = 4)
{
cout << "Stack(int capacity = 4)" << endl;
_a = (int*)malloc(sizeof(int)*capacity);
assert(_a);
_top = 0;
_capacity = capacity;
}
Stack(Stack& st)
{
cout<<"调用Stack的拷贝构造"<<endl;
}
~Stack()
{
cout << "~Stack()" << endl;
free(_a);
_a = nullptr;
_capacity = _top = 0;
}
private:
int* _a;
int _top;
int _capacity;
};
class MyQueue{
private:
int _size=0;
Stack _st1;
Stack _st2;
}
int main()
{
MyQueue mq1;
MyQueue mq2(mq1);
return 0;
}
MyQueue既有自定义类型又有内置类型。对于内置类型,编译器会继续浅拷贝,对于自定义类型调用自定义类型的拷贝构造
1.用户不写拷贝构造编译器会默认生成一个拷贝构造
2.自定义类型成员,去调用这个成员的拷贝构造
3.一般类,自己生成的拷贝构造就够用了。只有像Stack类这种需要自己直接管理资源需要实现深拷贝。
4.对于在堆区上开辟了空间的类,就需要使用深拷贝。
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
1.不能通过连接其他符号来创建新的操作符:比如operator@
2.重载操作符必须有一个类类型或者枚举类型的操作数
3.用于内置类型的操作符,其含义不能改变,例如:内置的整型+,不 能改变其含义
4.作为类成员的重载函数时,其形参看起来比操作数数目少1
成员函数的操作符有一个默认的形参this,限定为第一个形参
- *. 、:: 、sizeof 、?: 、. **注意以上5个运算符不能重载。
.* 域作用符,sizeof ,三目操作符 .操作符
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//如果重载定义在类外,为了能够访问成员变量,就需要把成员变量修改为public类型;
//但是这种方法不够好。
//private:
int _year;
int _month;
int _day;
};
//自定义类型,不能直接用各种运算符
//为了自定义类型可以使用各种运算符,制定了运算符重载的规operator
//返回值 operator运算符 参数列表
bool operator==(Date d1,Date d2)
{
return d1._year==d2._year
&&d1._month==d2._month
&&d1._day==d2._day;
}
//操作符重载的使用方式:
bool test(Date d1,Date d2)
{
return d1==d2
}
而Python和Java习惯使用内置返回值函数。还有一种方法是使用有元,但是有元会破坏封装。
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//返回私有变量。
int Getyear()
{
return _year;
}
//........
private:
int _year;
int _month;
int _day;
};
bool operator==(Date d1,Date d2)
{
return d1.Getyear()==d2.Getyear()
&&d1.Getmonth()==d2.Getmonth()
&&d1.Getday()==d2.Getday();
}
bool test(Date d1,Date d2)
{
return d1==d2
}
C++最常用的是把运算符重载定义为成员函数
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
bool operator==(const Date&d)
{
return _year==d._year
&&_month==d._month
&&_day==d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2022,5,17);
Date d2(2022,5,17);
if(d1==d2)
{
cout<<"=="<<endl;
}
return 0;
}
//输出:==
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//赋值运算符的返回值,返回值是赋值运算符的左值,因为赋值运算符要满足连续赋值 k=i=j j先赋值给i,i再赋值给k;
//如果出了作用域对象还存在,可以使用引用返回;
//返回值不可以使用const:比如(k=i)=j;如果返回值是const类型
//那么(k=i)的返回值是const k,j就不能够对k进行下一步赋值。
Date& operator=(const Date&d)
{
if(this!=&d)
//如果使用if(*this!=d),就需要重载!=。得不偿失
{
_year=d._year;
_month=d._month;
_day=d._day;
//对this指针进行解引用z
}
return *this;
}
private:
int _year;
int _month;
int _day;
};
对于该类有意义的运算符才进行运算符重载;比如日期类相加就无意义,所以就不需要操作符重载。