#include <bits/stdc++.h>
using namespace std ;
class Student {
public:
int NO ;
string name ;
explicit Student ( const int _NO , const string _name )
: NO ( _NO ) , name ( _name ) {
cout << endl << "构造函数" << endl ;
}
void display () const {
cout << endl << "NO : " << NO << "\t" << name << endl ;
}
~Student () noexcept {}
} ;
int main () {
Student One ( 1 , string ( "Jack" ) ) ;
One.display () ;
Student Two ( One ) ; // 拷贝一个副本
Two.display () ;
Student Three = One ; // 拷贝一个副本
Three.display () ;
vector < Student > P ; // 放进容器会调用拷贝构造函数
P.push_back ( One ) ;
P[0].display () ;
vector < Student* > Q ;
Q.push_back ( &One ) ; // 这里没有调用 拷贝构造函数.......因为是指针
Q[0]->display () ;
One.name = string ( "Marry" ) ; // 对指针的操作会作用到原来的内存上
Q[0]->display () ;
return 0 ;
}
C++的类有默认的拷贝构造函数,和构造函数一样,如果程序员不声明,编译器自动生成。
容易错的几点是:
1. 容器中放入对象实体,会调用拷贝构造函数
2. 容器中放入对象实体的指针,不会调用拷贝构造函数,因为指针只是地址
3. 容器的一些函数,参数上是 const 类型的,所以,如果自己写拷贝构造函数,参数必须是 const 类型的。
例如, C++ 的 vector :
push_back ( const value_type & )
要求是 const 类型。
C++ 11 还支持右值引用, push_back ( value_type && )
4. 加 const 可以防止拷贝过程中源对象被修改
5. 加 & 是引用,为了减少拷贝构造函数,因为如果不加引用 &, 传入的参数本身就是一个副本,也要调用拷贝构造函数,所以会无限调用下去
6. 对象实体很大的情况,不建议用拷贝构造函数,开销太大。可以选择用指针,只需要保存一个地址,而且是基类的情况下还可以借助指针实现多态,但指针的操作都是在源对象上操作,这点要注意。
7. 拷贝构造函数,默认先找非 const 类型,再找 const 类型
#include <bits/stdc++.h>
using namespace std ;
class Student {
public:
int NO ;
string name ;
explicit Student ( const int _NO , const string _name )
: NO ( _NO ) , name ( _name ) {
cout << endl << "构造函数" << endl ;
}
void display () const {
cout << endl << "NO : " << NO << "\t" << name << endl ;
}
// vector
// push_back ( const value_type & )
Student ( const Student &One ) { // 容器 push_back 参数是 const 类型
this->NO = One.NO ;
this->name = One.name ;
cout << endl << "参数是 const类型 的拷贝构造函数被调用了" << endl ;
}
Student ( Student &One ) { // explicit
this->NO = One.NO ;
this->name = One.name ;
cout << endl << "参数是 普通数据类型 的拷贝构造函数被调用了" << endl ;
}
Student ( Student *One ) {
this->NO = One->NO ;
this->name = One->name ;
cout << endl << "参数是 指针类型 的拷贝构造函数被调用了" << endl ;
}
~Student () noexcept {}
} ;
int main () {
Student One ( 1 , string ( "Jack" ) ) ;
One.display () ;
Student Two ( One ) ; // 拷贝一个副本
Two.display () ;
vector < Student > P ; // 放进容器会调用拷贝构造函数
P.push_back ( One ) ;
P[0].display () ;
vector < Student* > Q ;
Q.push_back ( &One ) ; // 这里没有调用 拷贝构造函数.......因为是指针
Q[0]->display () ;
One.name = string ( "Marry" ) ; // 对指针的操作会作用到原来的内存上
Q[0]->display () ;
return 0 ;
}
2018.4.25 更新
8. 默认的拷贝构造函数是浅拷贝
浅拷贝和深拷贝的本质区别就是——浅拷贝不会为指针重新分配内存,新对象里的指针和源对象指针指向同一片内存;但是,深拷贝需要对指针重新分配内存。
很危险的是,默认的拷贝构造函数是浅拷贝 !
尤其是在类里有指针的时候,类的拷贝构造函数只会浅拷贝,直到析构的时候,其中一个对象被析构了,那另一个对象析构就会在一片已经被释放的内存上操作,程序崩溃。
#include <bits/stdc++.h>
using namespace std ;
#define rep( i , j , n ) for ( int i = int(j) ; i < int(n) ; ++i )
class Gragh {
private:
int *array ;
public:
Gragh () {
array = new int [10] ;
rep ( i , 0 , 10 )
array[i] = i ;
}
~Gragh () {
if ( array != NULL )
delete array ;
array = NULL ;
}
void display () const {
rep ( i , 0 , 10 )
cout << array[i] << " " ;
cout << endl ;
}
} ;
int main () {
Gragh One ;
One.display () ;
Gragh Two ( One ) ;
Two.display () ; // 析构崩溃
return 0 ;
}
有指针存在的情况,不仅要考虑会不会析构崩溃,还要考虑拷贝的那个指针是否会修改源对象的内容。
所以,有指针的情况最好写成深拷贝:
#include <bits/stdc++.h>
using namespace std ;
#define rep( i , j , n ) for ( int i = int(j) ; i < int(n) ; ++i )
class Gragh {
private:
int *array ;
public:
Gragh () {
array = new int [10] ;
rep ( i , 0 , 10 )
array[i] = i ;
}
~Gragh () {
if ( array != NULL )
delete array ;
array = NULL ;
}
Gragh ( const Gragh &One ) { // 深拷贝, 重新为指针分配内存
array = new int [10] ;
rep ( i , 0 , 10 )
array[i] = One.array[i] ;
}
void display () const {
rep ( i , 0 , 10 )
cout << array[i] << " " ;
cout << endl ;
}
} ;
int main () {
Gragh One ;
One.display () ;
Gragh Two ( One ) ;
Two.display () ;
return 0 ;
}
9. 拷贝构造函数中的 const 说明不可以改变对象的成员变量, 但是可以改变成员变量指向的内存的内容
例如指针, const 规定的是不可以改变指针的内容,而指针内容是一片内存的地址,只要不改变地址即可,但是可以改变地址上的内容
#include <bits/stdc++.h>
using namespace std ;
#define rep( i , j , n ) for ( int i = int(j) ; i < int(n) ; ++i )
class Gragh {
private:
int *array ;
public:
Gragh () {
array = new int [10] ;
rep ( i , 0 , 10 )
array[i] = i ;
}
~Gragh () {
cout << endl << "array 的地址是 : " << array << endl ;
if ( array != NULL )
delete array ;
array = NULL ;
}
Gragh ( const Gragh &One ) { // 深拷贝
array = new int [10] ;
rep ( i , 0 , 10 )
array[i] = One.array[i] ;
One.array[0] = 100 ; // 修改 One.array 数组的内容
}
void display () const {
rep ( i , 0 , 10 )
cout << array[i] << " " ;
cout << endl << endl ;
}
} ;
int main () {
Gragh One ;
One.display () ;
Gragh Two ( One ) ; // 深拷贝
One.display () ; // 改成功了......
Two.display () ;
return 0 ;
}
以上的程序不但没有编译错误, 而且还改动成功了
可以看出,深拷贝,对象内部的指针指向 的内存地址是不同的
Gragh ( const Gragh &One ) { // 深拷贝
array = new int [10] ;
rep ( i , 0 , 10 )
array[i] = One.array[i] ;
One.array[0] = 100 ; // 修改 One.array 数组的内容
}
在这里,修改 One.array[0] 的内容,是合法的,因为的确没有修改 One.array 的内容,它只是个指针,修改的是它指向的内容。
更直观一点,可以输出对象的大小 ( sizeof )
可以看出,对象的大小恰好是一个 Int 指针的大小,所以说, 对象内部的指针,分配的内存是不在对象里面的,只是指针可以操作这块内存而已,所以即使对象加上 const , 也可以修改 array 的内容
引发的思考
(1). const 虽好,但是也要提防指针的这种情况,无意中修改了 const 类型对象中指针指向的内容,可能会很危险。
(2). 为什么对象的析构函数要把内部指针分配的内存额外 delete 释放,可能就是这样,为指针分配的内存并不在对象中。
如果把上面那段代码改成
Gragh ( const Gragh &One ) {
array = new int [10] ;
rep ( i , 0 , 10 )
array[i] = One.array[i] ;
// One.array[0] = 100 ; // 修改 One.array 数组的内容
One.array = NULL ;
}
就会编译错误,因为这里确实想要改变 array 指针的内容