博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C++技术点积累(2)——拷贝构造函数、深拷贝、浅拷贝
阅读量:4124 次
发布时间:2019-05-25

本文共 5375 字,大约阅读时间需要 17 分钟。

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<<"我是构造函数,自动被调用了"<
a.第1种和第2种调用场景:——对对象进行赋值、初始化
#include 
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 
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 
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 
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的内存空间无法释放!具体代码见: ——2、 3)重载=赋值运算符

附录:

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

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

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

你可能感兴趣的文章
JDBC核心技术 - 下篇
查看>>
一篇搞懂Java反射机制
查看>>
【2021-MOOC-浙江大学-陈越、何钦铭-数据结构】树
查看>>
MySQL主从复制不一致的原因以及解决方法
查看>>
RedisTemplate的key默认序列化器问题
查看>>
序列化与自定义序列化
查看>>
ThreadLocal
查看>>
从Executor接口设计看设计模式之最少知识法则
查看>>
OKhttp之Call接口
查看>>
application/x-www-form-urlencoded、multipart/form-data、text/plain
查看>>
关于Content-Length
查看>>
WebRequest post读取源码
查看>>
使用TcpClient可避免HttpWebRequest的常见错误
查看>>
EntityFramework 学习之一 —— 模型概述与环境搭建 .
查看>>
C# 发HTTP请求
查看>>
启动 LocalDB 和连接到 LocalDB
查看>>
Palindrome Number --回文整数
查看>>
Reverse Integer--反转整数
查看>>
Container With Most Water --装最多水的容器(重)
查看>>
Longest Common Prefix -最长公共前缀
查看>>