精华内容
下载资源
问答
  • 赋值函数
    千次阅读
    2019-02-20 14:09:46

    这里我们用类String 来介绍这两个函数:

    拷贝构造函数是一种特殊构造函数,具有单个形参,该形参(常用const修饰)是对该类类型的引用。当定义一个新对象并用一个同类型的对象对它进行初始化时,将显式使用拷贝构造函数。为啥形参必须是对该类型的引用呢?试想一下,假如形参是该类的一个实例,由于是传值参数,我们把形参复制到实参会调用拷贝构造函数,如果允许拷贝构造函数传值,就会在拷贝构造函数内调用拷贝构造函数,从而形成无休止的递归调用导致栈溢出。

    string(const string &s);
    //类成员,无返回值

    赋值函数,也是赋值操作符重载,因为赋值必须作为类成员,那么它的第一个操作数隐式绑定到 this 指针,也就是 this 绑定到指向左操作数的指针。因此,赋值操作符接受单个形参,且该形参是同一类类型的对象。右操作数一般作为const 引用传递。

    string& operator=(const string &s);
    //类成员,返回对同一类类型(左操作数)的引用

    拷贝构造函数和赋值函数并非每个对象都会使用,另外如果不主动编写的话,编译器将以“位拷贝”的方式自动生成缺省的函数。在类的设计当中,“位拷贝”是应当防止的。倘若类中含有指针变量,那么这两个缺省的函数就会发生错误。这就涉及到深复制和浅复制的问题了。 
    拷贝有两种:深拷贝,浅拷贝 
    当出现类的等号赋值时,会调用拷贝函数,在未定义显示拷贝构造函数的情况下,系统会调用默认的拷贝函数——即浅拷贝,它能够完成成员的一一复制。当数据成员中没有指针时,浅拷贝是可行的。 
    但当数据成员中有指针时,如果采用简单的浅拷贝,则两类中的两个指针将指向同一个地址,当对象快结束时,会调用两次析构函数,而导致指针悬挂现象。所以,这时,必须采用深拷贝。 
    深拷贝与浅拷贝的区别就在于深拷贝会在堆内存中另外申请空间来储存数据,从而也就解决了指针悬挂的问题。指向不同的内存空间,但内容是一样的 
    简而言之,当数据成员中有指针时,必须要用深拷贝。

    class A{
        char * c;
    }a, b;
    
    //浅复制不会重新分配内存
    //将a 赋给 b,缺省赋值函数的“位拷贝”意味着执行
    a.c = b.c;
    //从这行代码可以看出
    //b.c 原有的内存没有释放
    //a.c 和 b.c 指向同一块内存,任何一方的变动都会影响到另一方
    //对象析构的时候,c 被释放了两次(a.c == b.c 指针一样)
    
    //深复制需要自己处理里面的指针
    class A{
        char *c;
        A& operator =(const A &b)
        {
            //隐含 this 指针
            if (this == &b)
                return *this;
            delete c;//释放原有内存资源
    
            //分配新的内存资源
            int length = strlen(b.c);
            c = new char[length + 1];
            strcpy(c, b.c);
    
            return *this;
        }
    }a, b;
    //这个是深复制,它有自定义的复制函数,赋值时,对指针动态分配了内存

    这里再总结一下深复制和浅复制的具体区别:

    1. 当拷贝对象状态中包含其他对象的引用时,如果需要复制的是引用对象指向的内容,而不是引用内存地址,则是深复制,否则是浅复制。
    2. 浅复制就是成员数据之间的赋值,当值拷贝时,两个对象就有共同的资源。而深拷贝是先将资源复制一份,是对象拥有不同的资源(内存区域),但资源内容(内存里面的数据)是相同的。
    3. 与浅复制不同,深复制在处理引用时,如果改变新对象内容将不会影响到原对象内容
    4. 与深复制不同,浅复制资源后释放资源时可能会产生资源归属不清楚的情况(含指针时,释放一方的资源,其实另一方的资源也随之释放了),从而导致程序运行出错

    深复制和浅复制还有个区别就是执行的时候,浅复制是直接复制内存地址的,而深复制需要重新开辟同样大小的内存区域,然后复制整个资源。

    好,有了前面的铺垫,下面开始讲讲拷贝构造函数和赋值函数,其实前面第一部分也已经介绍了许多

    这里以string 类为例来进行说明

    class String
    {
    public:
        String(const char *str = NULL);
        String(const String &rhs);
        String& operator=(const String &rhs);
        ~String(void){
            delete[] m_data;
        }
    
    private:
        char *m_data;
    };
    
    //构造函数
    String::String(const char* str)
    {
        if (NULL == str)
        {
            m_data = new char[1];
            *m_data = '\0';
        }
        else
        {
            m_data = new char[strlen(str) + 1];
            strcpy(m_data, str);
        }
    }
    
    //拷贝构造函数,无需检验参数的有效性
    String::String(const String &rhs)
    {
        m_data = new char[strlen(rhs.m_data) + 1];
        strcpy(m_data, rhs.m_data);
    }
    
    //赋值函数
    String& String::operator=(const String &rhs)
    {
        if (this == &rhs)
            return *this;
    
        delete[] m_data; m_data = NULL;
        m_data = new char[strlen(rhs.m_data) + 1];
        strcpy(m_data, rhs.m_data);
    
        return *this;
    }

    类String 拷贝构造函数与普通构造函数的区别是:在函数入口处无需与 NULL 进行比较,这是因为“引用”不可能是NULL,而“指针”可以为NULL。(这是引用与指针的一个重要区别)。然后需要注意的就是深复制了。 
    相比而言,对于类String 的赋值函数则要复杂的多:

    1、首先需要执行检查自赋值

    这是防止自复制以及间接复制,如 b = a; c = b; a = c;之类,如果不进行自检的话,那么后面的 delete 将会进行自杀操作,后面随之的拷贝操作也会出错,所以这是关键的一步。还需要注意的是,自检是检查地址,而不是内容,内存地址是唯一的。必须是 if(this == &rhs)

    2、释放原有的内存资源

    必须要用 delete 释放掉原有的内存资源,如果此时不释放,该变量指向的内存地址将不再是原有内存地址,也就无法进行内存释放,造成内存泄露。

    3、分配新的内存资源,并复制资源

    这样变量指向的内存地址变了,但是里面的资源是一样的

    4、返回本对象的引用

    这样的目的是为了实现像 a = b = c; 这样的链式表达,注意返回的是 *this 。

    但仔细一想,上面的程序没有考虑到异常安全性,我们在分配内存之前用delete 释放了原有实例的内存,如果后面new 出现内存不足抛出异常,那么之前delete 的 m_data 将是一个空指针,这样很容易引起程序崩溃,所以我们可以调换下顺序,即先 new 一个实例内存,成功后再用 delete 释放原有内存空间,最后用 m_data 赋值为new后的指针。

    接下来说说拷贝构造函数和赋值函数之间的区别。

    拷贝构造函数和赋值函数非常容易混淆,常导致错写、错用。拷贝构造函数是在对象被创建是调用的,而赋值函数只能在已经存在了的对象调用。看下面代码:

        String a("hello");
        String b("world");
    
        String c = a;//这里c对象被创建调用的是拷贝构造函数
                     //一般是写成 c(a);这里是与后面比较
        c = b;//前面c对象已经创建,所以这里是赋值函数

    上面说明出现“=”的地方未必调用的都是赋值函数(算术符重载函数),也有可能拷贝构造函数,那么什么时候是调用拷贝构造函数,什么时候是调用赋值函数你?判断的标准其实很简单:如果临时变量是第一次出现,那么调用的只能是拷贝构造函数,反之如果变量已经存在,那么调用的就是赋值函数。 
    参考资料:《Effective C++》、《高质量C++&C编程指南》

     

    转自: https://blog.csdn.net/zhoucheng05_13/article/details/80937775

    更多相关内容
  • 详解C++ 编写String 的构造函数、拷贝构造函数、析构函数和赋值函数  编写类String 的构造函数、析构函数和赋值函数,已知类String 的原型为: class String { public: String(const char *str = NULL); // 普通...
  • C++中一般创建对象,拷贝或赋值的方式有构造函数,拷贝构造函数,赋值函数这三种方法。下面就详细比较下三者之间的区别以及它们的具体实现 1.构造函数 构造函数是一种特殊的类成员函数,是当创建一个类的对象时,它...
  • 本文主要介绍了C/C++面试题:编写类String的构造、析构和赋值函数,解析构造函数、析构函数和赋值函数的编写方法
  • C++类中的构造函数和赋值函数

    千次阅读 2020-05-30 17:20:17
    构造函数主要包括:默认构造函数、带参构造函数、拷贝构造函数三种,且构造函数经常与赋值函数混淆,这里放在一起讲,便于区分。 首先初始化一个简单的类作为例子讲解: class A { public: A() { v = 1; printf...

    构造函数主要包括:默认构造函数、普通构造函数、拷贝构造函数三种。
    构造函数经常与赋值函数混淆,这里放在一起讲,便于区分。

    在这里插入图片描述

    首先初始化一个简单的类作为例子讲解:

    class A {
    public:   
        A() {
            v = 1;
            printf("默认构造(无参数)\n");
        }
        
        A(int t = 1) {
            v = 1;
            printf("默认构造(有参数,且参数有默认值)\n");
        }
        
        A(int t) {
            v = t;
            printf("普通构造1\n");
        }
    
        A(int t,int f) {
            v = t;
            m = f;
            printf("普通构造2\n");
        }
        //默认构造函数只能有一个,当有参默认构造函数的参数列表与普通构造函数的参数列表一样时,两者也只能存在一个。
    
        A(const A& a) {
            this->v = a.v;
            printf("拷贝构造\n");
        }
    
        A& operator= ( A& a) {
            this->v = a.v;
            printf("赋值函数\n");
            return *this;
        }
    
        ~A() {
            printf("析构函数\n");
        }
    
        int v;
        int m;
    };
    

    1、 默认构造函数

    ①准确的说,默认构造函数就是在调用时不需要显示地传入实参的构造函数。
    ②一个类只能有一个默认构造函数。

    1.1、默认构造函数

    当类中没有定义任何构造函数时,编译器默认提供一个无参构造函数,并且其函数体为空。
    此外,用户可自定义默认构造函数如下。

    实现形式:

        A() {
            //可以为空,为空则与编译器生成的默认构造函数相同
        }
    
        A(int t = 1) {
            v = t;
        }
    

    上述两种默认构造函数不能同时存在,否则在调用默认构造函数时会引起二义性。

    调用形式:

    int main() {
        A a;//默认构造函数
        return 0;
    }
    

    *C++构造函数重载以及默认参数引起的二义性

    问题解析

    当我们同时定义一个带默认参数的默认构造函数和一个不带参数的默认构造函数之后(一般不这样做,两种构造函数不应被同时定义),在定义一个类对象时,如果不传递参数,会出现二义性错误。因为我们没有传递参数时,系统默认又两种选择,一种是调用无参的默认构造函数,另一种是调用带参默认构造函数并传递默认参数。

      class A  
      {  
      public:  
         A()  
         {  
             v = 0;  
         }  
           
         A(int r = 0)  
         {  
             v = r;   
         }  
         int v;
     };  
     int main()  
     {  
             A c;//error: call of overloaded ‘Complex()’ is ambiguous  
             return 0;  
     } 
    

    会报错:error: call of overloaded ‘Complex()’ is ambiguous

    解决方法

    1.去掉无参默认构造函数。

      class A  
      {  
      public:  
         A(int r = 0)  
         {  
             v = r;   
         }  
         int v;
     }; 
    

    这时,当我们不给构造函数传递参数时,系统会调用有参默认构造函数并传递默认参数对类数据成员进行初始化。

    int main() {
        A a;
        //相当于
        A a(0);//有参默认构造函数
        return 0;
    }
    

    2.去掉带参默认构造函数的默认参数

      class A  
      {  
      public:  
         A()  
         {  
             v = 0;  
         }  
           
         A(int r)  //去掉带参构造函数的默认参数
         {  
             v = r;   
         }  
         int v;
     };  
    

    此时带参默认构造函数就变成了普通构造函数。

    1.2、默认拷贝构造函数

    当类中没有定义任何拷贝构造函数时,编译器默认提供一个默认拷贝构造函数,简单的进行成员变量的值的赋值。这里涉及到深拷贝和浅拷贝的知识,将在拷贝构造函数小节详述。

    2、 普通构造函数

    实现形式:

        //通过参数列表的不同进行重载
        A(int t) {
            v = t;
        }
        
        A(int t,int f) {
            v = t;
            m = f;
        }
        
        A(int t): v(a) {}//普通构造函数1的参数列表形式
        A(int t,int f): v(a), m(f) {}//普通构造函数2的参数列表形式
    

    调用形式:

    int main() {
        A a(1);//普通构造函数1
        A a(1,1);//普通构造函数2
        return 0;
    }
    

    3、 拷贝构造函数(复制构造函数)

    实现形式:

        A(const A& a) {
            this->v = a.v;
            printf("拷贝构造\n");
        }
    

    调用形式(三种情况):

    3.1、对象需要通过另一个对象进行初始化

    int main() {
        A a(1);
        A b = a;//拷贝构造函数形式一
        A c(a);//拷贝构造函数形式二,最好写成这种形式
        return 0;
    }
    

    3.2、对象通过值传递方式进入函数

    void g(A ret) {
        printf("函数作为参数\n");
    }
    int main() {
        A a(1);
        g(a);//作为参数传入函数时调用拷贝构造函数
        return 0;
    }
    

    3.3、对象以值传递方式从函数返回

    A f() {
        A ret(3);
        return ret;
    }
    int main() {
        A a(1);
        A b = f();//以值传递方式从函数返回时调用拷贝构造函数
        return 0;
    }
    

    *关于深拷贝与浅拷贝

    浅拷贝,即在定义一个类A,使用类似A obj; A obj1(obj);或者A obj1 = obj; 时候,由于没有自定义拷贝构造函数,C++编译器自动会产生一个默认的拷贝构造函数。这个默认的拷贝构造函数采用的是“位拷贝”(浅拷贝),而非“值拷贝”(深拷贝)的方式,如果类中含有指针变量,默认的拷贝构造函数必定出错。

    浅拷贝

    浅拷贝:也就是在对象复制时,只是对对象中的数据成员进行简单的赋值,如果对象中存在动态成员,即指针,浅拷贝就会出现问题,下面代码:

    class A
    {
    public:
        A()      // 构造函数,p指向堆中分配的一空间
        {
            m_data = new char(100);
            printf("默认构造函数\n");
        }
        ~A()     // 析构函数,释放动态分配的空间
        {
            if (m_data != NULL)
            {
                delete m_data;
                m_data = NULL;
                printf("析构函数\n");
            }
        }
    private:
        char* m_data;     // 一指针成员
    };
    
    int main()
    {
        A a;
        A b(a);   // 拷贝构造
        return 0;
    }
    

    分析:由于没有拷贝构造函数,走编译器默认的拷贝构造函数,A b(a); 进行对象析构时,会造成释放同一内存空间2次。

    深拷贝

    对于深拷贝,针对成员变量存在指针的情况,不仅仅是简单的指针赋值,而是重新分配内存空间,如下:

    class A
    {
    public:
        A()      // 构造函数,p指向堆中分配的一空间
        {
            m_pdata = new char(100);
            printf("默认构造函数\n");
        }
    
        A(const A& r)
        {
            m_pdata = new char(100);    // 为新对象重新动态分配空间
            memcpy(m_pdata, r.m_pdata, strlen(r.m_pdata));
            printf("深拷贝构造函数\n");
        }
    
        ~A()     // 析构函数,释放动态分配的空间
        {
            if (m_pdata != NULL)
            {
                delete m_pdata;
                printf("析构函数\n");
            }
        }
    
    private:
        char* m_pdata;     // 一指针成员
    };
    
    int main()
    {
        A a;
        A b(a);   // 拷贝构造
        return 0;
    }
    

    4、 赋值函数

    赋值函数不属于构造函数,它是“=”运算符的重载函数。只有两个已经声明的对象之间(两个已经在内存中存在的对象)才能进行赋值。
    实现形式:

        A& operator= ( A& a) {
            if (this != &a)
            {
                this->v = a.v;
            }
            printf("赋值函数\n");
            return *this;
        }
    

    ①返回类型为自身类的引用,使得赋值函数可以联等赋值。

    A t2,t3;
    t2=t3=t1;//假设t1是已被初始化的对象
    

    ②形参为类的引用,可以避免重复调用拷贝构造函数,const可以保护传入对象自身的安全。

    调用形式:

    int main() {
        A a(1);
        A b;
        b = a;//赋值函数
        return 0;
    }
    

    5、string类各种初始化时所调用的构造函数

    //初始化方法1
    string a;//调用无参默认构造函数,初始化后对象a内的字符串成员为空。
    //初始化方法2
    string a = "abc";//先调用普通构造函数生成一个匿名string对象,再调用拷贝构造函数把匿名对象拷贝给a。
    //该语句等同于:
    string a(string("abc"));
    //初始化方法3 
    string a("abc");//调用普通构造函数
    //初始化方法4 
    string a(b);//调用拷贝构造函数
    //初始化方法5 
    string a = b;//调用拷贝构造函数
    //方法6 (a,b已初始化)
    a = b;//调用赋值函数
    
    展开全文
  • 拷贝赋值函数(详解)

    千次阅读 2020-10-19 19:56:58
    拷贝赋值函数 指针拷贝的四大步骤: 检查是否自我赋值; 删掉原数据; 分配新的数据长度; 赋值; inline String& String::operator=(const String& str) { //检测是否是自我赋值, if (this == &str...

    拷贝赋值函数

    C++默认的拷贝构造函数和赋值构造函数都是浅拷贝,所以当遇到类成员含有指针变量时,就得自己实现深拷贝!

    指针拷贝的四大步骤:

    1. 检查是否自我赋值;
    2. 删掉原数据;
    3. 分配新的数据长度;
    4. 赋值;
    inline
    String& String::operator=(const String& str)
    {
    //检测是否是自我赋值,
       if (this == &str)
          return *this;
    //删掉原数据;
       delete[] m_data;
       //分配新的数据长度;
       m_data = new char[ strlen(str.m_data) + 1 ];
       // 赋值
       strcpy(m_data, str.m_data);
       return *this;
    }
    

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    #include <iostream>
    #include <string>
    #include <string.h>
    using namespace std;
     
    class String
    {
    public:
        String(const char *str=NULL);//普通构造函数
        String(const String &str);//拷贝构造函数
        String & operator =(const String &str);//赋值函数
        ~String();//析构函数
     
    private:
        char* m_data;//用于保存字符串
    };
     
    //普通构造函数
    String::String(const char *str)
    {
        if (str==NULL)
        {
            m_data=new char[1]; //对空字符串自动申请存放结束标志'\0'的空间
            if (m_data==NULL)
            {//内存是否申请成功
                std::cout<<"申请内存失败!"<<std::endl;
                exit(1);
            }
            m_data[0]='\0';
        }
        else
        {
            int length=strlen(str);
            m_data=new char[length+1];
            if (m_data==NULL)
            {//内存是否申请成功
                std::cout<<"申请内存失败!"<<std::endl;
                exit(1);
            }
            strcpy(m_data,str);
        }
    }
     
    //拷贝构造函数
    String::String(const String &other)
    { //输入参数为const型
        int length=strlen(other.m_data);
        m_data=new char[length+1];
        if (m_data==NULL)
        {//内存是否申请成功
            std::cout<<"申请内存失败!"<<std::endl;
            exit(1);
        }
        strcpy(m_data,other.m_data);
    }
     
    //赋值函数
    String& String::operator =(const String &other)
    {//输入参数为const型
        if (this == &other) //检查自赋值
        { return *this; }
     
        delete [] m_data;//释放原来的内存资源
     
        int length=strlen(other.m_data);
        m_data= new char[length+1];
        if (m_data==NULL)
        {//内存是否申请成功
            std::cout<<"申请内存失败!"<<std::endl;
            exit(1);
        }
        strcpy(m_data,other.m_data);
     
        return *this;//返回本对象的引用
    }
     
    //析构函数
    String::~String()
    {
        delete [] m_data;
    }
     
    int main()
    {
        String a;
        String b("abc");
        a = b;
        system("pause");
    }
    
    展开全文
  • 文章目录一. 构造函数二. 析构函数三. 拷贝构造函数1.浅拷贝2....) //默认赋值函数。 这四个函数如果我们不自行定义,将由编译器自动生成这四个缺省的函数,下面让我们来看看这四个函数(重点是后


    在C++中,对于一个类,C++的编译器都会为这个类提供四个默认函数,分别是:

    A() //默认构造函数
    ~A() //默认析构函数
    A(const A&) //默认拷贝构造函数
    A& operator = (const A &) //默认赋值函数。

    这四个函数如果我们不自行定义,将由编译器自动生成这四个缺省的函数,下面让我们来看看这四个函数(重点是后两个)。


    一. 构造函数

    构造函数是一种特殊的成员函数,与其他成员函数不同,不需要用户来调用它,而是在建立对象时自动执行。构造函数的功能是由用户定义的,用户根据初始化的要求设计函数体和函数参数,可以是一个,也可以是多个,可以把构造函数理解为重载的一种(函数名相同,不会返回任何类型,也不可以是void类型,参数类型个数可不同)。

    class Animal
    {
    private:
    	string name;
    public:
    	Animal();//默认构造函数
    	Animal(string n);//也可以自定义构造函数
    };
    
    Animal::Animal()
    {
    	//什么都不做
    }
    Animal::Animal(string n)
    {
    	this->name = n;
    }
    
    int main()
    {
    	//第一种实例化对象的方法
    	Animal * a = new Animal(); //将调用默认构造函数
    	Animal * b = new Animal("花狗"); //将调用自定义的构造函数,对name变量初始化。
    	
    	//第二种实例化对象的方法
    	Animal c; //将调用默认构造函数 
    	//注意:对于无参构造函数,不可以使用Animal c(),
    	Animal c("花狗");//将调用自定义构造函数,对name变量初始化。
    	return 0;
    }
    
    

    构造函数的作用就是对当前类对象起到一个初始化的作用,类对象不像我们基本类型那样,在很多时候都需要初始化一些成员变量。

    可以看到构造函数被声明在public里面,那么可以声明在private里面吗?是可以的,只不过不能被外部实例化了,在设计模式中有一种单例模式,就是这样设计的,有兴趣的可以了解一下。


    二. 析构函数

    与构造函数相对立的是析构函数,这个函数在对象销毁之前自动调用,例如在构造函数中,我们为成员变量申请了内存,我们就可以在析构函数中将申请的内存释放,析构函数的写法是在构造函数的基础上加一个~符号,并且只能有一个析构函数。

    class Animal
    {
    private:
    	string name;
    public:
    	Animal();//默认构造函数
    	~Animal(); //默认析构函数
    };
    

    三. 拷贝构造函数

    1.浅拷贝

    class Animal
    {
    private:
    	string name;
    public:
    	Animal()
    	{
    		name = "花狗";
    		cout << "Animal" << endl;
    	}
    	~Animal()
    	{
    		cout << "~Animal:" << (int)&name << endl;
    	}
    };
    
    int main()
    {
    	Animal a;
    	Animal b(a);
    	return 0;
    }
    

    运行结果:
    在这里插入图片描述
    这个例子调用的是默认的拷贝构造函数(注意看控制台显示,调用了一次构造函数和两次析构函数),可以看出两个对象的成员变量地址是不一样的,当成员变量不存在指针类型是,这样做没什么问题,当类中有指针变量,自动生成的拷贝函数注定会出错,往下看。


    2.深拷贝

    我们将成员变量换成指针变量,继续实验。

    class Animal
    {
    private:
    	//string name;
    	string * name;
    public:
    	Animal()
    	{
    		name = new string("花狗");
    		cout << "Animal" << endl;
    	}
    	~Animal()
    	{
    		cout << "~Animal:" << (int)name << endl;
    	}
    };
    
    int main()
    {
    	Animal a;
    	Animal b(a);
    	return 0;
    }
    

    运行结果:
    在这里插入图片描述

    可以看到两个对象的指针成员所指的内存相同(内存里面存着字符串:花狗),还记得析构函数的作用吗,在对象销毁之前自动调用,在构造函数中,我们为成员变量申请了内存,我们就可以在析构函数中将申请的内存释放。

    现在在析构函数中加上对name释放的代码:

    	~Animal()
    	{
    		cout << "~Animal:" << (int)name << endl;
    		delete name;
    		name = NULL;
    	}
    

    再运行发现程序崩溃了,调用一次构造函数,调用两次析构函数,两个对象的指针成员所指内存相同,name指针被分配一次内存,但是程序结束时该内存却被释放了两次,导致程序崩溃
    在这里插入图片描述
    而且发现当重复释放的两个指针分别属于两个类或者说是两个变量的时候,会发生崩溃,如果对一个变量多次释放则不会崩溃。

    例如下面的代码将不会发生奔溃

    	string * a = new string("花狗");
    	delete a;
    	a = NULL;
    	cout << "第一次完成\n";
    	delete a;
    	a = NULL;
    	cout << "第二次完成\n";
    

    现在我们已经知道对于指针进行浅拷贝会出现的奔溃的问题,那么通过自定义拷贝构造函数来解决浅拷贝的问题。

    	Animal(const Animal & a)
    	{
    		//name = s.name;
    		name = new string(*a.name);
    	}
    

    之后运行程序不会崩溃,总结起来就是先开辟出和源对象一样大的内存区域,然后将需要拷贝的数据复制到目标拷贝对象。


    四. 赋值函数

    四个默认函数,当赋值函数最为复杂。

    Animal& operator=(const Animal&obj)
    	{
    		if(this !=&obj)
    		{
    			data=obj.data;
    		 } 
    		 return *this;
    	}
    

    这是它的原型,类似 Animal a(b); Animal a = b; 这样的写法会调用拷贝构造函数。

    而赋值函数是在当年对象已经创建之后,对该对象进行赋值的时候调用的,Animal a; a = b。

    和拷贝构造函数一样,若类中有指针变量,自动生成的赋值函数注定会出错,老样子,先申请内存,再复制值即可完美解决。

    Animal& operator=(const Animal&obj)
    	{
    		if(this !=&obj)
    		{
    			//默认是 name = obj.name;
    			name = new string(*obj.name);
    		 } 
    		 return *this;
    	}
    

    还有一个知识点就是运算符重载这一块,一个自定义类型的对象,如果想要进行预期的加减乘除之类的运算,或者是像内置类型一样,用cout输出一个类对象,这些都是需要我们来用代码告诉机器怎么做,都是需要我们来指定的。

    还是拿这个类举例子,例如运算符+重载

    
    class Animal
    {
    private:
    	string * name;
    	int age;
    	int num;
    public:	
    Animal()
    {
    	name = new string("花狗");
    	age = 5;
    	num = 4;
    }
    
    
    Animal& operator+(const Animal&obj)
    	{
    		if(this !=&obj)
    		{
    			string * s = name;
    			name = new string(*name + *obj.name);
    			delete s;
    			s == NULL;
    			this->age+=obj.age;
    			this->num+=obj.num;
    		} 
    		 return *this;
    	}
    };
    
    int main()
    {
    	Animal a;
    	Animal b;
    	a = a+b;
    	//这样对象a里面的age成员的值是5,num成员的值是8,而*name的值将是"花狗花狗";
    	return 0;
    {
    
    

    cout输出的定义,主要注意的是要用到友元函数。

    class Animal
    {
    	//中间代码略
    	friend ostream& operator << (ostream& os, Animal& a)
    	{
    		os << *a.name << ":" << a.age << ":" << a.num;
    		return os;
    	}
    };
    

    运行结果:
    在这里插入图片描述


    微信官方交流群:
    在这里插入图片描述

    展开全文
  • C++:类中的赋值函数

    千次阅读 2018-12-10 14:58:03
    先来看一个例子: 1 #include&lt;iostream&gt; 2 #include&lt;string&gt; 3 using namespace std; 4 class Student{ ...调用默认构造函数"&lt;&lt;endl; 8 }; 9 ...
  • C++中的赋值函数

    千次阅读 2019-11-16 13:36:10
    1、赋值函数写法分析 Test& operator=(const Test &t) { if(this != &t) { data = t.data; } return *this; } void operator=(Test t) { data = t.data; } void main() { T...
  • 前两者会在创建一个类实例的时候被调用,而最后的拷贝赋值函数是对一个已经创建的实力进行=调用的时候被调用。 拷贝赋值函数的返回值应该为该类的一个引用,否则无法使用classA = classB = classC。 Show me the ...
  • c++中的赋值函数

    千次阅读 2019-03-22 02:01:42
    在c++中,对于任意一个类Class A,如果程序员不显示的声明和定义上述函数,C++编译器将会自动的为A产生4个public inline 的默认函数,这4个函数最常见的形式为: A() //默认构造函数 ...其中只有默认赋值函数...
  • 移动构造、移动赋值函数

    千次阅读 2017-05-02 16:38:07
    //移动赋值函数   private: char *m_data; // 用于保存字符串 }; inline String::String(const char* str) { if (str==NULL) { m_data = NULL; //声明为inline函数,则该函数...
  • 拷贝构造函数赋值运算符的行为比较相似,却产生不同的结果;拷贝构造函数使用已有的对象创建一个新的对象,赋值运算符是将一个对象的值复制给另一个已存在的对象。区分是调用拷贝构造函数还是赋值运算符,主要是否...
  • 拷贝构造函数和赋值函数

    千次阅读 2018-05-31 11:26:37
    本文主要是对拷贝构造函数和赋值函数已经深拷贝、浅拷贝的总结归纳。 拷贝构造函数 如果构造函数第一个参数为自身类类型的引用,且任何额外参数都具有默认值,则此构造函数为拷贝构造函数。 class Foo { public...
  • 本文主要介绍了拷贝构造函数赋值运算符的区别,以及在什么时候调用拷贝构造函数、什么情况下调用赋值运算符。最后,简单的分析了下深拷贝和浅拷贝的问题。有需要的朋友可以看下
  • 这篇来学习字符串的构造函数赋值操作。 1.string的基本概念 string是C++风格的字符串,而string本质上是一个类。string和har * 区别:char *是一个指针,string是一个类,类内部封装了char *,管理这个字符串,是...
  • 第9章类的构造函数、析构函数与赋值函数[文].pdf
  • c++ 拷贝函数和赋值函数的区别

    千次阅读 2018-08-09 15:33:31
    c++ 构造函数,拷贝构造函数,赋值函数,析构函数 1.构造函数 构造函数:: 当创建一个类的对象时,它被调用来对**类的数据成员进行初始化和内存分配** 对于c++的空类,编译器默认加入以下成员函数 1.默认构造...
  • 请你回答一下C++中拷贝赋值函数的形参能否进行值传递? 不能。如果是这种情况下,调用拷贝构造函数的时候,首先要将实参传递给形参,这个传递的时候又要调用拷贝构造函数。。如此循环,无法完成拷贝,栈也会满。 ...
  • 编写strcpy函数和类String的构造函数、析构函数、赋值函数和重载运算符函数[文].pdf
  • 拷贝构造函数: TestClass(const TestClass&); // 形式1 TestClass(TestClass&); // 形式2 TestClass(TestClass); // 形式3 形式1是最常见的构造函数声明,这也是最好的书写方式。 ...
  • 默认拷贝构造函数和默认赋值函数

    千次阅读 2018-09-27 17:19:06
    当一个类中有动态分配内存时,应当自己定义拷贝构造函数和赋值函数 class A { int *p; public: A() { p = new int[10]; } ~A() { delete p; } }; void f(A x) { cout "Yes" << endl; }...
  • 编写类String的构造函数、析构函数和赋值函数,已知类String的函数原型为: class String { public: String(const char *str = NULL); // 普通构造函数 String(const String &amp;other); // 拷贝构造...
  • "调用了拷贝构造函数" << endl; } void operator=(CopyTest& cp) { cout << "调用了拷贝幅值函数" << endl; } }; CopyTest show(CopyTest mmm) { return mmm; } 我们写下以下的测试程序:...
  • 原型如下:class String{public: String(const char *str=NULL);// 普通构造函数 String (const String &amp;other);// 拷贝构造函数 ~String(void);//析构函数 String &...// 赋值函...
  • 数组赋值函数memcpy()的用法

    千次阅读 2019-09-02 17:19:34
    我们知道,数组是不能够进行赋值操作的。 如果你声明了int a[maxn],b[maxn];是不能够把b=a的。 但如果你想从a复制k个元素到b,你可以这样写memcpy(b,a,sizeof(int)*k) 此函数头文件是string.h 此函数第一个元素是你...
  • C++ 之 赋值函数(操作符)

    千次阅读 2019-03-10 12:06:13
    c++允许类对象之间赋值,这是通过自动为类重载赋值操作符实现的。 这种操作符地原型如下: className &amp; className::operator = (const className &amp;); 入参和返回值都是 指向类对象地引用。 当 将...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,460,359
精华内容 584,143
关键字:

赋值函数

友情链接: SpecialEffectsColletion.zip