C++技术点积累(2)——拷贝构造函数、深拷贝、浅拷贝

C++技术点积累:

1、因为在A类外的B类是无法使用A类的private成的,但是有的时候又有这种需求(A类和B类有一些业务数据联系),一般我们可以A类中编写一些get()函数,getAx(){ return x;//把A类的成员变量甩出去},这样,我们在B类就可以调用A类的getAx()函数来使用A类的成员变量x。


2、拷贝构造函数:

       (1)拷贝初始化 和 直接初始化

               string dots(10,'s');              //直接初始化

               string nines = string (100,'9');            //拷贝初始化

               string s2 = dots;                                //拷贝初始化

       

      (2)拷贝初始化通常使用拷贝构造函数来完成,在下列情况将发生拷贝初始化:

               A. 用 = 定义变量时发生;

               B. 将 一个对象 作为实参 传递给一个 非引用类型的 形参;

               C. 从一个返回类型为非引用类型的函数 返回一个对象;

               D. 用花括号列表初始化一个数组中的元素 或 一个聚合类中的成员;

        

      (3)拷贝构造函数 的第一个参数 必须是 一个引用类型:

class Foo  
{  
public:  
       Foo();             //默认构造函数  
       Foo(const Foo&);   //拷贝构造函数  
}  

        拷贝构造函数被用来 初始化非引用类类型参数。 如果拷贝构造函数自己的参数不是引用类型,则调用永远也无法成功——为了调用拷贝构造函数,我们必须拷贝它的实参,但为了拷贝实参,我们又需要调用拷贝构造函数,如此无限循环。——这也就解释了为什么拷贝构造函数自己的参数必须是引用类型。————引用是别名,避免了循环调用拷贝动作!


     (4)拷贝构造函数的四种调用情景(拷贝构造函数调用时机)
class AA
{
public:
	AA() //无参构造函数 默认构造函数
	{	
		cout<<"我是构造函数,自动被调用了"<<endl;
	}
	AA(int _a) //无参构造函数 默认构造函数
	{	
		a = _a;
	}
 	AA(const AA & obj2)  //拷贝构造函数
 	{
 		cout<<"我也是构造函数,我是通过另外一个对象obj2,来初始化我自己"<<endl;
 		a = obj2.a + 10;
 	}
private:
        int a;
}
a.第1种和第2种调用场景:——对对象进行赋值、初始化
#include <iostream>
using namespace std;

class Test4
{
public:
	Test4()  //无参数构造函数
	{
		m_a = 0;
		m_b = 0;
		cout << "无参数构造函数" << endl;
	}

	Test4(int a)
	{
		m_a = a;
		m_b = 0;
	}

	Test4(int a, int b) //有参数构造函数   //3种方法
	{
		m_a = a;
		m_b = b;
		cout << "有参数构造函数" << endl;
	}

	//赋值构造函数 (拷贝构造函数) 
	Test4(const Test4 & obj)
	{
		cout << "我也是构造函数 " << endl;
		m_b = obj.m_b + 100;
		m_a = obj.m_a + 100;
		//cout << "m_a" << m_a << " m_a" << m_b << endl;//没有上面的赋值,将输出乱码
	}

public:
	void printT()
	{
		cout << "普通成员函数" << endl;
		cout << "m_a:" << m_a << "  m_b:" << m_b << endl;
	}
private:
	int m_a;
	int m_b;
};

//赋值构造函数——用1个对象去初始化另外一个对象,对象之间的赋值
void main()
{
	Test4 t1(1, 2);
	Test4 t0(1, 2);

	//赋值=操作 不会调用构造函数,编译器给我们提供的浅copy(可以单步调试检验)
	//调用的是:operator=()
	t0 = t1; //用t1 给 t0赋值操作 和 初始化是两个不同的概念

	//第1种调用方法
	Test4 t2 = t1; //用t1 来初始化 t2 ,这个时候会调用t2的构造函数,而且是t2的拷贝构造函数
	               //虽然看上去是“=”,但是通过构造函数我们实现了初始时个性化的t2
	t2.printT();

	return;
}

//第二种调用时机
void main()
{
	Test4 t1(1, 2);
	Test4 t0(1, 2);

	//第2种调用方法
	Test4 t2(t1);  //用t1对象 初始化 t2对象 
	t2.printT();
}
b.第3种调用场景:——对象做形参,一个对象以值传递的方式传入函数体 
#include <iostream>
using namespace std;

class Location
{
public:
	Location(int xx = 0, int yy = 0)
	{
		X = xx;  Y = yy;  cout << "Constructor Object.\n";
	}

	//copy构造函数,完成对象的初始化
	Location(const Location & obj)  
	{
		X = obj.X;
		Y = obj.Y;
	}

	~Location()
	{
		cout << X << "," << Y << ":" << " Object destroyed." << endl;
	}
	int GetX() { return X; }		
	int GetY() { return Y; }
private:   
        int  X, Y;
};

//业务函数  形参是一个元素
void f(Location p)
{
	cout << p.GetX() << endl;
}

void playobj()
{
	Location  a(1, 2);

	//调用构造函数
	Location  b = a;//C++编译器会去自动调用copy构造函数   
	cout << "b对象已经初始化完毕" << endl;

	//第三种调用情景
	f(b); //b实参去初始化形参p,会调用copy构造函数
}

void main()
{
	playobj();
	return;
}
c.第4种调用场景:——一个对象以值传递的方式从函数返回,返回一个匿名对象
#include <iostream>
using namespace std;

class Location
{
public:
	Location(int xx = 0, int yy = 0)
	{
		X = xx;  
		Y = yy;  
		cout << "Constructor Object.\n";
	}

	//copy构造函数  完成对象的初始化
	Location(const Location & obj)  
	{
		X = obj.X; 
		Y = obj.Y;
	}
	~Location()
	{
		cout << X << "," << Y << "   Object destroyed." << endl;
	}
	int GetX() { return X; }		
	int GetY() { return Y; }
private:   
	int  X, Y;
};

//g函数 返回一个元素(objplay2()函数)
//结论1 : 函数的返回值是一个元素(复杂类型的), 返回的是一个新的匿名对象(所以会调用匿名对象类的copy构造函数)

//结论2: 有关 匿名对象的去和留
//如果用匿名对象  初始化 另外一个同类型的对象(objplay3()), 匿名对象 转成有名对象
//如果用匿名对象  赋值给 另外一个同类型的对象(objplay4()), 匿名对象 被析构

//第四种调用场景
//返回一个新对象——匿名对象(没有名字),所以会去调用匿名对象类的构造函数
Location g()
{
	Location A(1, 2);
	return A;//返回一个对象,这个时候会去调用拷贝构造函数
	         //完了以后直接先去调用一次析构函数析构A,但是匿名对象还在
}

void objplay2()
{
	g();
}

void objplay3()
{
	Location m = g();//用匿名对象初始化m 此时c++编译器 直接把匿名对转成m,从匿名转成有名字了m(扶正) 
	                 //先去牺牲了空间,但是提高了执行效率
	printf("用匿名对象直接初始化,被扶正,不会析构掉\n");
	cout << m.GetX() << endl;
}

void objplay4()
{
	Location m2(1, 2);
	m2 = g();//用匿名对象 赋值给 m2后, 匿名对象被析构
	printf("因为用匿名对象 =给m2,匿名对象,被析构\n");
	cout << m2.GetX() << endl;
}

void main()
{
	//objplay2();
	//objplay3();
	objplay4();
	cout << "hello..." << endl;
	system("pause");
	return;
}

3、深拷贝和浅拷贝
(1)什么时候会出现浅拷贝?
       在某些状况下,类内成员变量需要动态开辟堆内存,如果实行浅拷贝,也就是把对象里的值完全复制给另一个对象,如A=B。这时,如果B中有一个成员变量指针已经申请了内存,那A中的那个成员变量也指向同一块内存。这就出现了问题:当B把内存释放了(如:析构),这时A内的指针就是野指针了,出现运行错误。
  深拷贝和浅拷贝可以简单理解为:如果一个类拥有资源,当这个类的对象发生复制过程的时候,资源重新分配,这个过程就是深拷贝,反之,没有重新分配资源,而是指向了同一块内存,就是浅拷贝。

#define  _CRT_SECURE_NO_WARNINGS 
#include <iostream>
using namespace std;

class  Name
{
public:
	Name(const char *myp)
	{
		m_len = strlen(myp);
		m_p =(char *) malloc(m_len + 1); //在堆上分配内存
		strcpy(m_p, myp);
	}

	~Name()
	{
		if (m_p != NULL)
		{
			free(m_p);
			m_p = NULL;
			m_len = 0;
		}
	}
private:
	char *m_p;   //类中有指针
	int m_len; 
};

//对象析构的时候,程序当掉
void objplaymain()
{
	Name obj1("abcdefg");
	Name obj2 = obj1; //出错!对象析构的时候程序当掉!
                          //调用C++编译器提供的默认拷贝构造函数———浅拷贝
                          //浅拷贝——仅仅是指针变量进行了赋值,而指针所指的内存空间没有拷贝过去
                          //先析构obj2,再析构obj1,同一块内存空间析构了两次!
}

void main()
{
	objplaymain();
}

        这种情况下,使用C++编译器提供的默认构造函数时,只是将指针变量进行了赋值,并没有对obj2的*p重新开辟内存空间将数据拷贝过去!——浅拷贝
       解决方案: 手工的编写拷贝构造函数, 使用深copy(蓝色线条

Name(const Name& obj1)
{
	m_len = obj1.m_len;
	m_p = (char *)malloc(m_len + 1);
	strcpy(m_p, obj1.m_p);
}

补充:C++编译器提供的“等号操作”,也属 浅拷贝
void objplaymain()
{
	Name obj1("abcdefg");
	Name obj3("obj3");
	obj3 = obj1;  // C++编译器提供的“等号操作”, 也属  浅拷贝,——这个时候需要重载“等号操作”
}

       简单分析:执行“等号操作”时,也是把指针变量赋值过去了,在析构时,还是析构了两次!并且还出现了内存泄露——原来obj3的内存空间无法释放!具体代码见:http://blog.csdn.net/songshimvp1/article/details/48273653 ——2、 3)重载=赋值运算符


附录:

        尽管编译器能为我们合成拷贝、赋值、和销毁的操作,但是对于某些类来说合成的版本无法正常工作。特别是,如上面例子当类需要分配类对象之外的资源时,合成的版本往往会失效。

        不过值得注意的是,如果类包含vector 或者 string成员,则其拷贝、赋值、和销毁的合成版本能够正常工作。当我们对含有vector成员的对象执行拷贝 或者 赋值 操作时,vector会拷贝或者赋值成员中的元素,当对象被销毁时,将销毁vector对象,也就是依次销毁vector中的每一个元素。

       所以,很多需要动态内存的类能(而且应该)使用vector对象或者string对象管理必要的存储空间,使用vector或者string的类能够避免分配和释放内存带来的复杂性。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值