C++ 学习笔记 (4) 拷贝构造函数

#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 指针的内容

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在C++中,复制构造函数拷贝构造函数是指同一个概念,用于创建一个对象的副本。它们被定义为类的特殊成员函数,用于将一个对象的值复制到另一个对象中。 复制构造函数/拷贝构造函数的语法如下: ```cpp ClassName(const ClassName& obj) { // 复制obj的成员变量到新对象中 } ``` 其中,`ClassName`是类的名称,`obj`是同类对象的引用,用于初始化新创建的对象。 在使用复制构造函数时,编译器会自动调用它来创建一个对象的副本。例如: ```cpp ClassName obj1; // 创建一个对象obj1 ClassName obj2(obj1); // 使用obj1调用复制构造函数创建obj2,obj2是obj1的副本 ``` 需要注意的是,如果没有显式定义复制构造函数/拷贝构造函数,编译器会为类提供一个默认的复制构造函数,该构造函数会逐个复制类的成员变量。但是如果类中有指针成员变量或资源管理等特殊情况,则需要自定义复制构造函数来确保正确地复制对象。 同时,复制构造函数/拷贝构造函数也可以通过赋值运算符重载来实现对象的复制。例如: ```cpp ClassName obj1; // 创建一个对象obj1 ClassName obj2 = obj1; // 使用赋值运算符重载实现对象的复制 ``` 这里的赋值运算符重载函数会被编译器解析为复制构造函数/拷贝构造函数的调用。 总结:复制构造函数/拷贝构造函数是用于创建对象的副本的特殊构造函数,它们采用同类对象的引用作为参数,并使用该对象的值来初始化新创建的对象。如果未显式定义复制构造函数/拷贝构造函数,编译器会提供一个默认的复制构造函数/拷贝构造函数

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值