目录
拷贝构造函数
基本概念:用于创建一个对象,该对象是通过复制现有对象来初始化的
格式:
ClassName(const ClassName &other);//other是另一个ClassName类型的对象的引用,表示要复制的对象
注意事项:
1、拷贝构造函数的传递的参数必须是当前类对象的引用,传值传参的方式会引发无穷递归调用,编译器报错(如果采用传值传参,当你传入一个对象(如d1
)时,C++会试图调用拷贝构造函数来创建一个临时对象进行传递,而此时为了传递就又需要调用拷贝构造将该临时对象交给拷贝构造函数,这导致了无限递归调用,最终出现栈溢出问题)
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//正确写法:Date(Date& d)
//最佳写法:Date(const Date& d)
Date(Date d)//错误写法
{
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2024,3,7);
Date d2(d1);
return 0;
}
补充内容:调用函数时是先传参后调用的
#include <stdio.h> int func(int a) { return a; } int main() { func(5); return 0; }
2、拷贝构造函数的形参记得加const,const可以很好的保护被引用的对象,如果不用const修饰可能出现原本是想借用拷贝构造函数初始化新对象的成员变量,但是赋值两端的内容写反了,新对象的成员变量(随机值)反而把拷贝构造函数的成员变量初始化成随机值了(因为d2(d1)时传递了实际上还有一个包含d2对象地址的this指针 ,下面的代码中我们显示了d2的this指针,并解释了不用const导致的问题)
3、编译器默认的拷贝构造函数,会对内置类型成员变量会进行值/浅拷贝(将成员变量的字节依照原有的顺序一个一个的拷贝)对于指针类型,只拷贝指针地址,不拷贝指针所指向的数据,对于自定义类型会调用这些成员各自的拷贝构造函数,如果自定义类型的成员没有定义自己的拷贝构造函数,编译器将生成一个默认的拷贝构造函数,进行逐成员的浅拷贝(所有的自定义类型最根本还是内置类型,所以到最后可以用编译器生产的默认拷贝构造函数进行浅拷贝)
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
//没有显式定义拷贝构造函数
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2024,3,7);
Date d2(d1);
d1.Print();
d2.Print();
return 0;
}
4、 拷贝构造函数也是构造函数(重载),这意味着如果类中之前没有定义默认构造函数,那么编译器此时就不会再生成了,当然也可以使用C++11中提供的default关键字,用于显式地要求编译器生成指定默认的函数实现
//即使有一个用户定义的构造函数,= default 保证了类还会有一个默认构造函数
class MyClass
{
public:
MyClass() = default; // 显式要求编译器生成默认构造函数
MyClass(int value) : _value(value) {}
private:
int _value;
};
class MyClass
{
public:
MyClass() = default;
MyClass(const MyClass&) = default; // 显式生成默认拷贝构造函数
MyClass& operator=(const MyClass&) = default; // 显式生成默认拷贝赋值运算符
private:
int _value;
};
#include <iostream>
using namespace std;
class Time
{
public:
~Time()
{
cout << "~Time()" << endl;
}
//强制编译器生成默认构造函数: Time() = default;
Time(const Time& t)
{
cout << "Time(const Time& t)" << endl;
_hour = t._hour;
_minute = t._minute;
_mecond = t._mecond;
}
private:
int _hour;
int _minute;
int _mecond;
};
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
//内置类型
int _year;
int _month;
int _day;
//自定义类型
Time _t;//调用Time类的默认构造函数
};
int main()
{
Date d1(2024,3,7);
Date d2(d1);
d1.Print();
d2.Print();
return 0;
}
5、类的默认构造中没有涉及堆资源申请时,该类的拷贝构造函数是否写都可以,一旦涉及堆资源申请,则拷贝构造函数一定要写否则就是浅拷贝
#include <iostream>
using namespace std;
typedef int DataType;
class Stack
{
public:
//默认构造函数(涉及堆资源申请)
Stack(size_t capacity = 10)
{
_array = (DataType*)malloc(capacity * sizeof(DataType));
if (nullptr == _array)
{
perror("malloc申请空间失败");
return;
}
_size = 0;
_capacity = capacity;
}
//插入函数
void Push(const DataType& data)
{
// CheckCapacity();
_array[_size] = data;
_size++;
}
//使用默认拷贝构造函数
//析构函数
~Stack()
{
if (_array)
{
free(_array);
_array = nullptr;
_capacity = 0;
_size = 0;
}
}
private:
DataType* _array;
size_t _size;
size_t _capacity;
};
int main()
{
Stack s1;
s1.Push(1);
s1.Push(2);
s1.Push(3);
s1.Push(4);
Stack s2(s1);
return 0;
}
这是因为,实例化s1对象时在堆上开辟了个空间用于存放元素,接着s2对象要用s1对象的成员变量进行初始化,Stack类没有显示定义拷贝构造函数,编译器会生成一份默认的拷贝构造函数,默认拷贝构造函数是浅拷贝,即将s1中的内容原封不动的拷贝到s2中,对于_size和_capacity没问题,但是_array存放的可是s1在堆上开辟的空间的地址,此时将该地址的值也原封不动的传递给了s2的_array,此时s1和s2对象的_array指向同一片空间,当main函数结束时,s2先销毁,s2销毁时调用析构函数释放掉申请的空间,由于s1不知道,所以会将该空间再次释放,同一块内存空间的多次释放肯定会造成程序崩溃
因此对于申请了空间资源的类我们要进行深拷贝:
class Stack
{
public:
...
//深拷贝
Stack(const Stack& s)
{
DataType* tmp = (DataType*)malloc(s._capacity * (sizeof(DataType)));
if(tmp == nullptr)
{
perror("malloc fail");
exit(-1);
}
memcpy(tmp, s._array, sizeof(DataType) * s._size);
_array = tmp;
_size = s._size;
_capacity = s._capacity;
}
...
};
拷贝对象时的编译器优化
基本概念:在传参和传返回值时,一般编译器会做一些优化,减少对象的拷贝
- 连续构造 + 拷贝构造 = 优化为直接构造
- 连续构造 + 拷贝构造 = 优化为一个构造
- 连续拷贝构造 + 拷贝构造 = 优化为一个拷贝构造
- 连续拷贝构造 + 赋值重载 = 无法优化
~over~