c++之类和对象详解 拷贝构造,赋值运算符重载
拷贝构造
那在创建对象时,可否创建一个与已存在对象一某一样的新对象呢?
==拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用==
构造——初始化
拷贝构造——拷贝初始化
class Date
{
//....
};
int main()
{
Date d1(2000, 1, 1);
Date d2(d1);//以d1的数据来初始化d2
return 0;
}
拷贝构造特征
拷贝构造函数也是特殊的成员函数,其特征如下:
-
拷贝构造函数是构造函数的一个重载形式。
-
拷贝构造函数的参数只有一个且必须是==类类型对象的引用==,使用传值方式编译器直接报错, 因为会引发无穷递归调用。
-
若未显式定义,编译器会生成==默认的拷贝构造函数==。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做==浅拷贝==,或者值拷贝。
注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定 义类型是调用其拷贝构造函数完成拷贝的。
class Date
{
public:
//Date(Date& d)
//{
// _year = d._day;
// _month = d._month;
// _day = d._day;
//}
Date(int year, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year = 0;
int _month = 0;
int _day = 0;
};
int main()
{
Date d1(2000, 1, 1);
Date d2(d1);
return 0;
}我们可以看到对于日期类这种只含内置类型的,编译器自动生成的默认拷贝构造函数已经够用了!
但是像是更复杂就会出问题
class stack
{
public:
stack(int newcapcacity = 4)
{
int* temp = (int*)malloc(sizeof(int) * newcapcacity);
if (temp == nullptr)
{
perror(malloc fail);
exit(-1);
}
_a = temp;
_top = 0;
_capacity = newcapcacity;
}
~stack()//这就是栈的析构函数!
{
free(_a);
_a = nullptr;
_top = 0;
_capacity = 0;
}
void Push(int x)
{
if (_top == _capacity)
{
int newcapacity = 2 * _capacity;
int* temp = (int*)realloc(_a, sizeof(int) * newcapacity);
if (temp == nullptr)
{
perror(realloc fail);
exit(-1);
}
_a = temp;
_capacity = newcapacity;
}
_a[_top++] = x;
}
private:
int* _a;
int _top;
int _capacity;
};
int main()
{
stack st1;
st1.Push(1);
st1.Push(2);
stack st2(st1);
return 0;
}看上去好像是完成了拷贝!但是有没有发现st1和st2的指针指向了==同一块内存空间==,也就是意味着我 改变st1就会改变st2!
这个程序会导致崩溃!因为当st2的指针被析构函数释放掉之后,st1的析构函数就会导致访问野指针!
所以此时我们需要使用到深拷贝!
class stack
{
public:
stack(int newcapcacity = 4)
{
int* temp = (int*)malloc(sizeof(int) * newcapcacity);
if (temp == nullptr)
{
perror(malloc fail);
exit(-1);
}
_a = temp;
_top = 0;
_capacity = newcapcacity;
}
~stack()
{
free(_a);
_a = nullptr;
_top = 0;
_capacity = 0;
}
stack(stack& st)//深拷贝
{
_a = (int*)malloc(sizeof(int) * st._capacity);
if (_a == nullptr)
{
perror(malloc fail);
exit(-1);
}
memcpy(_a, st._a, st._top * sizeof(int));
_capacity = st._capacity;
_top = st._top;
}
void Push(int x)
{
if (_top == _capacity)
{
int newcapacity = 2 * _capacity;
int* temp = (int*)realloc(_a, sizeof(int) * newcapacity);
if (temp == nullptr)
{
perror(realloc fail);
exit(-1);
}
_a = temp;
_capacity = newcapacity;
}
_a[_top++] = x;
}
private:
int* _a;
int _top;
int _capacity;
};
int main()
{
stack st1;
st1.Push(1);
st1.Push(2);
stack st2(st1);
st2.Push(3);
return 0;
}这下就成功完成了深拷贝
需要写析构函数的类的都要写拷贝构造!
不需要写析构的类都不需要自己写!
-
拷贝构造函数典型调用场景:
- 使用已存在对象创建新对象
- 函数参数类型为类类型对象
- 函数返回值类型为类类型对象
赋值运算符重载
class Date
{
public:
Date(Date& d)
{
_year = d._day;
_month = d._month;
_day = d._day;
}
Date(int year, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year = 0;
int _month = 0;
int _day = 0;
};
int main()
{
Date d1(2000, 1, 1);
Date d2(2000, 2, 28);
d1 > d2;
d1 == d2;
d1 + 100;
d1 - d2;
return 0;
}
像是上面的d1与d2我们如何简洁的进行比较呢?如何像是以前一样使用运算符?这时候就引入了运算符重载!
运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
赋值重载
Date& operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
};//这就是赋值重载
赋值运算符的写法注意
void operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
如果怎么写的话会出现一个问题就是在无法进行链式访问 d1 = d2 = d3;
但是也最好不要使用Date作为返回类型因为这样子会产生一个临时变量!
Date operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
};
因为d在出了作用域后不会被销毁可以使用引用来作为返回值!
使用引用作为传参的类型 也是为了防止产生临时变量!const是为了防止对传参对象进行修改!
以引用作为返回值的时候要注意不可以传参对象作为返回值
Date& operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
return d;
}
这样就会出现经典的权限放大!
解决方法
cosnt Date& operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
return d;
}
// or
Date& operator=(Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
return d;
}
但是这样也要求我们得使用const的类型去接收,我们一般要求变量都是可以修改的!
所以还是使用Date&
如果使用第二种修改方式这样也会导致我们无法对传参对象进行保护!
Date& operator=(Date& d)
{
d._year = _year;//万一写反了!
_month = d._month;
_day = d._day;
return d;
}
赋值重载的默认性
如果我们不写一个赋值重载,类中会自己生成一个赋值重载!
class Date
{
public:
Date(int year = 10,int month = 10,int day = 10)
{
_year = year;
_month = month;
_day = day;
}
//Date& operator=(const Date& d)
//{
// _year = d._year;
// _month = d._month;
// _day = d._day;
// return *this;
//}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(100,10,100);
Date d2;
d2 = d1;
return 0;
}
默认的赋值重载会完成一次直拷贝!按字节一个个的拷贝过去!
==默认赋值重载和默认拷贝构造很相似==
- 对于内置类型都是进行值拷贝!
- 对于自定义类型都是调用自定义类型的默认成员函数!
赋值重载和拷贝赋值的区别在哪里?
class Date
{
public:
Date(int year = 10,int month = 10,int day = 10)
{
_year = year;
_month = month;
_day = day;
}
Date& operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(100,10,100);
Date d2(d1);//拷贝构造 是初始化另一个要马上创建的对象!
d2 = d1;//赋值重载(赋值拷贝!)已经存在的两个对象之间的拷贝!
Date d3 = d1;//这看上去好像是赋值重载!
//但是其实拷贝构造!因为从定义上看它更符合 初始化一个要创建的对象!
return 0;
}