精华内容
下载资源
问答
  • C++拷贝构造函数(复制构造函数)详解
    2022-02-13 09:12:08

    C++拷贝构造函数(复制构造函数)详解

    拷贝和复制是一个意思,对应的英文单词都是copy。对于计算机来说,拷贝是指用一份原有的、已经存在的数据创建出一份新的数据,最终的结果是多了一份相同的数据。例如,将 Word 文档拷贝到U盘去复印店打印,将 D 盘的图片拷贝到桌面以方便浏览,将重要的文件上传到百度网盘以防止丢失等,都是「创建一份新数据」的意思。

    在 C++ 中,拷贝并没有脱离它本来的含义,只是将这个含义进行了“特化”,是指用已经存在的对象创建出一个新的对象。从本质上讲,对象也是一份数据,因为它会占用内存。

    严格来说,对象的创建包括两个阶段,首先要分配内存空间,然后再进行初始化:

    分配内存很好理解,就是在堆区、栈区或者全局数据区留出足够多的字节。这个时候的内存还比较“原始”,没有被“教化”,它所包含的数据一般是零值或者随机值,没有实际的意义。

    初始化就是首次对内存赋值,让它的数据有意义。注意是首次赋值,再次赋值不叫初始化。初始化的时候还可以为对象分配其他的资源(打开文件、连接网络、动态分配内存等),或者提前进行一些计算(根据价格和数量计算出总价、根据长度和宽度计算出矩形的面积等)等。说白了,初始化就是调用构造函数。

    很明显,这里所说的拷贝是在初始化阶段进行的,也就是用其它对象的数据来初始化新对象的内存。

    那么,如何用拷贝的方式来初始化一个对象呢?其实这样的例子比比皆是,string 类就是一个典型的例子。

    #include <iostream>
    #include <string>
    using namespace std;
    void func(string str){
        cout<<str<<endl;
    }
    int main(){
        string s1 = "http://c.ttt.net";
        string s2(s1);
        string s3 = s1;
        string s4 = s1 + " " + s2;
        func(s1);
        cout<<s1<<endl<<s2<<endl<<s3<<endl<<s4<<endl;
       
        return 0;
    }
    运行结果:
    http://c.ttt.net
    http://c.ttt.net
    http://c.ttt.net
    http://c.ttt.net
    http://c.ttt.net http://c.ttt.net
    

    s1、s2、s3、s4 以及 func() 的形参 str,都是使用拷贝的方式来初始化的。
    对于 s1,表面上看起来是将一个字符串直接赋值给了 s1,实际上在内部进行了类型转换,将 const char * 类型转换为 string 类型后才赋值的。s4 也是类似的道理。

    对于 s1、s2、s3、s4,都是将其它对象的数据拷贝给当前对象,以完成当前对象的初始化。

    对于 func() 的形参 str,其实在定义时就为它分配了内存,但是此时并没有初始化,只有等到调用 func() 时,才会将其它对象的数据拷贝给 str 以完成初始化。

    当以拷贝的方式初始化一个对象时,会调用一个特殊的构造函数,就是拷贝构造函数(Copy Constructor)。

    下面的例子演示了拷贝构造函数的定义和使用:

    #include <iostream>
    #include <string>
    using namespace std;
    class Student{
    public:
        Student(string name = "", int age = 0, float score = 0.0f);  //普通构造函数
        Student(const Student &stu);  //拷贝构造函数(声明)
    public:
        void display();
    private:
        string m_name;
        int m_age;
        float m_score;
    };
    Student::Student(string name, int age, float score): m_name(name), m_age(age), m_score(score){ }
    //拷贝构造函数(定义)
    Student::Student(const Student &stu){
        this->m_name = stu.m_name;
        this->m_age = stu.m_age;
        this->m_score = stu.m_score;
       
        cout<<"Copy constructor was called."<<endl;
    }
    void Student::display(){
        cout<<m_name<<"的年龄是"<<m_age<<",成绩是"<<m_score<<endl;
    }
    int main(){
        Student stu1("小明", 16, 90.5);
        Student stu2 = stu1;  //调用拷贝构造函数
        Student stu3(stu1);  //调用拷贝构造函数
        stu1.display();
        stu2.display();
        stu3.display();
       
        return 0;
    }
    

    运行结果:

    Copy constructor was called.
    Copy constructor was called.
    小明的年龄是16,成绩是90.5
    小明的年龄是16,成绩是90.5
    小明的年龄是16,成绩是90.5
    

    第 8 行是拷贝构造函数的声明,第 20 行是拷贝构造函数的定义。拷贝构造函数只有一个参数,它的类型是当前类的引用,而且一般都是 const 引用。

    1) 为什么必须是当前类的引用呢?

    如果拷贝构造函数的参数不是当前类的引用,而是当前类的对象,那么在调用拷贝构造函数时,会将另外一个对象直接传递给形参,这本身就是一次拷贝,会再次调用拷贝构造函数,然后又将一个对象直接传递给了形参,将继续调用拷贝构造函数……这个过程会一直持续下去,没有尽头,陷入死循环。

    只有当参数是当前类的引用时,才不会导致再次调用拷贝构造函数,这不仅是逻辑上的要求,也是 C++ 语法的要求。

    2) 为什么是 const 引用呢?

    拷贝构造函数的目的是用其它对象的数据来初始化当前对象,并没有期望更改其它对象的数据,添加 const 限制后,这个含义更加明确了。

    另外一个原因是,添加 const 限制后,可以将 const 对象和非 const 对象传递给形参了,因为非 const 类型可以转换为 const 类型。如果没有 const 限制,就不能将 const 对象传递给形参,因为 const 类型不能转换为非 const 类型,这就意味着,不能使用 const 对象来初始化当前对象了。

    以上面的 Student 类为例,将 const 去掉后,拷贝构造函数的原型变为:
    Student::Student(Student &stu);

    此时,下面的代码就会发生错误:

    const Student stu1("小明", 16, 90.5);
    Student stu2 = stu1;
    Student stu3(stu1);
    

    stu1 是 const 类型,在初始化 stu2、stu3 时,编译器希望调用Student::Student(const Student &stu),但是这个函数却不存在,又不能将 const Student 类型转换为 Student 类型去调用Student::Student(Student &stu),所以最终调用失败了。

    当然,你也可以再添加一个参数为 const 引用的拷贝构造函数,这样就不会出错了。换句话说,一个类可以同时存在两个拷贝构造函数,一个函数的参数为 const 引用,另一个函数的参数为非 const 引用。

    默认拷贝构造函数

    在前面的教程中,我们还没有讲解拷贝构造函数,但是却已经在使用拷贝的方式创建对象了,并且也没有引发什么错误。这是因为,如果程序员没有显式地定义拷贝构造函数,那么编译器会自动生成一个默认的拷贝构造函数。这个默认的拷贝构造函数很简单,就是使用“老对象”的成员变量对“新对象”的成员变量进行一一赋值,和上面 Student 类的拷贝构造函数非常类似。

    对于简单的类,默认拷贝构造函数一般是够用的,我们也没有必要再显式地定义一个功能类似的拷贝构造函数。但是当类持有其它资源时,如动态分配的内存、打开的文件、指向其他数据的指针、网络连接等,默认拷贝构造函数就不能拷贝这些资源,我们必须显式地定义拷贝构造函数,以完整地拷贝对象的所有数据。

    更多相关内容
  • 本文主要介绍了拷贝构造函数和赋值运算符的区别,以及在什么时候调用拷贝构造函数、什么情况下调用赋值运算符。最后,简单的分析了下深拷贝和浅拷贝的问题。有需要的朋友可以看下
  • 关键字explicit 修饰构造方法的关键字,加上了,就告诉编译器,不可以隐式初始化对象;不加就可以隐式初始化对象; 下面的代码是可以正常编译执行的,但是加了关键字explicit,编译就会错我,因为...拷贝构造函数如果
  • 1.什么是拷贝构造函数拷贝构造函数嘛,当然就是拷贝和构造了。(其实很多名字,只要静下心来想一想,就真的是顾名思义呀)拷贝又称复制,因此拷贝构造函数又称复制构造函数。百度百科上是这样说的:拷贝构造函数...
  • 拷贝和浅拷贝可以简单理解为:如果一个类拥有资源,当这个类的对象发生复制过程的时候,资源重新分配,这个过程就是深拷贝,反之,没有重新分配资源,就是浅拷贝
  • 详解C++ 拷贝构造函数

    2021-01-19 23:44:42
    拷贝构造函数是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。拷贝构造函数通常用于: 通过使用另一个同类型的对象来初始化新创建的对象。 复制对象把它作为参数传递给...
  • C++中, 构造函数,拷贝构造函数,析构函数和赋值函数(赋值运算符重载)是最基本不过的需要掌握的知识。 但是如果我问你“拷贝构造函数的参数为什么必须使用引用类型?”这个问题, 你会怎么回答? 或许你会回答...
  • 拷贝构造函数C++最基础的概念之一,大家自认为对拷贝构造函数了解么?请大家先回答一下三个问题:1. 以下函数哪个是拷贝构造函数,为什么?X::X(const X&); X::X(X); X::X(X&, int a=1); X::X(X&, int a=1, b=2);...
  • C++中一般创建对象,拷贝或赋值的方式有构造函数,拷贝构造函数,赋值函数这三种方法。下面就详细比较下三者之间的区别以及它们的具体实现 1.构造函数 构造函数是一种特殊的类成员函数,是当创建一个类的对象时,它...
  • C++ 拷贝构造函数详解

    千次阅读 2021-06-14 15:24:36
    C++ 拷贝构造函数详解 下面的讲解将以C++标准库的string类作为讲解对象,string类:class with pointer member(s) 1、拷贝构造函数和拷贝赋值函数 1.1引入 下面是给出的测试函数,也是我们要能在自己设计的myString...

    C++ 拷贝构造函数详解

    下面的讲解将以C++标准库的string类作为讲解对象,string类:class with pointer member(s)

    1、拷贝构造函数和拷贝赋值函数

    1.1引入

    下面是给出的测试函数,也是我们要能在自己设计的myString类中实现的功能:

    int main()
    {
        myString s1();	//无参数构造函数
        myString s2("Hello world!");	//传入字符串的构造函数
        
        myString s3(s1);	//拷贝构造
        cout<<s3<<endl;		//操作符重载,对<<重载
        s3 = s2;		//拷贝赋值
        cout<<s3<<endl;
    }
    

    当我们没有显式写出拷贝构造函数和拷贝赋值函数时,编译器会给我们默认提供拷贝构造和拷贝赋值函数,这两个函数做的都是逐字节地将一个对象的内容拷贝到另一个对象中。

    对于成员没有指针的类,默认的拷贝构造函数一般不需要再重写。但是对于成员中含有指针的类,那么拷贝构造函数必须要进行重写。不能使用默认的拷贝构造函数。

    #ifndef COPYCONSTRUCTOR_MYSTRING_H
    #define COPYCONSTRUCTOR_MYSTRING_H
    
    class myString {
    private:
        char* m_data;//动态分配的方式
    public:
        myString(const char* cstr = 0);
        myString(const myString& str);//拷贝构造函数
        myString& operator=(const myString& str);//拷贝赋值
        ~myString();//析构函数,类死亡的时候调用
        char* get_c_char()const{return m_data;};//inline function
    };
    
    #endif //COPYCONSTRUCTOR_MYSTRING_H
    

    下面我们先对普通的构造函数和析构函数进行创建:

    inline
    myString::myString(const char* cstr) {
        if(cstr){
            m_data = new char[strlen(cstr)+1];//别忘了结束符要占用长度
            strcpy(m_data, cstr);
        }else{//未指定初值
            m_data = new char[1];
            *m_data = '\0';
        }
    }
    
    inline
    myString::~myString() {
        delete[] m_data;
    }
    

    上述的创建分别使用了array new和array delete,即array[]和delete[]的写法。两者一定要搭配使用,不然会造成内存泄漏:

    在这里插入图片描述

    可以进行这样的测试:

    {
    myString s1();
    myString s2("hello");
    
    myString* p = new myString("hello");
    delete p;
    }
    

    使用new的关键字进行动态创建字符串,离开作用域时,必须写出delete p,删除指针对象。

    未使用new关键字创建的字符串会自动调用析构函数。

    1.2 拷贝构造函数

    • class with pointer members 必须有 copy cstr 和 copy op=

    对于内含指针的构造函数,若使用默认的构造函数,则是**“浅拷贝”**memory leak,有可能造成内存泄漏。

    **内存泄漏(Memory Leak)**是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

    如下图,我们希望的是a和b各自有一个指针指向各自的字符串Hello\0,假使我们使用默认的拷贝构造,那么原先的World\0将不会有指针指向:造成了内存泄漏

    alias:别名,两个指针指向同一个字符串也是非常危险的,a修改a的m_data,结果b的m_data也被修改了。这是我们所不希望的。

    • 拷贝构造函数的设置

      下面的写法就避免了内存泄漏,称为深拷贝

    inline
    myString::myString(const myString &str) {
        //创建出足够的空间放蓝本
        m_data = new char[strlen(str.m_data) + 1];//直接取另一个对象的private:兄弟之间互为友元
        strcpy(m_data, str.m_data);
    }
    

    可以看到在拷贝构造函数中,我们使用了另一个object对象的private成员,可以直接调用,这是因为同一个类的不同对象之间互为友元

    可编写测试函数:

    {
    	myString s1("Hello");
    	myString s2(s1);
    	myString s3 = s1;
        //第三行和第四行是不同的操作,
        //一个是利用拷贝构造函数创建出一个新的对象
        //另一个是使用了拷贝赋值函数
    }
    

    1.3拷贝赋值函数

    • 赋值的过程
      • 销毁自己
      • 重分配空间
      • 返回*this
    inline
    myString & myString::operator=(const myString &str) {
        //检测自我赋值
        //self assignment
        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;
    
    }
    

    上述的①②③即分别对应上述赋值过程。特别要注意的是自我赋值,自我赋值的检测不仅关系到效率,还关系到下面程序的正确性。

    2.new 和 delete

    2.1对象的生命——堆空间和栈空间

    对象一定存储在内存中,但可以是存储在栈空间,也可是在堆空间。

    s1,s2,s3的内存空间在栈中,称为stack object,又叫做local object,因为其声明在作用域结束的时候就结束了,又被称为auto object,因为他被自动清理——析构函数被自动调用。

    在这里插入图片描述

    • static变量
    {
        myString s1 = myString();	//无参数构造函数
        static myString s2 = myString("Hello world!");	//传入字符串的构造函数
        return 0;
    }
    

    假如s2对象设定为static,那么这个statck object就会变成static对象,其生存期为程序的生存期,声明在作用域结束之后仍存在。

    要注意的是static变量只会初始化一次。即重复调用函数修改static变量的值也只会修改一次。

    • 全局变量
    class Complex{...};
    ...
    Complex c3(1,2);
    
    int main()
    {
    	……
    }
    

    像c3这样的变量称为全局变量,其作用域和static变量一样。

    2.2 new的正确使用方法

    new 必须搭配delete使用,不然可能造成内存泄漏。

    在这里插入图片描述

    而new运算符会被分解成三个操作:

    • 分配内存:使用operator new函数,内部调用malloc,为对象分配内存
    • 转型:把void转型成为Complex
    • 构造函数:通过转型得到的指针调用其构造函数

    在这里插入图片描述

    即整个new的动作是:先分配内存,再调用构造函数

    2.3 delete的使用

    delete ps;编译器会将其转换为:

    myString::~myString(ps);	//析构函数
    operator delete(ps);	//释放内存
    

    即delete被转化为两个动作:先调用析构函数然后释放内存。

    而调用析构函数需要做什么?这需要我们自己定义:

    myString::~myString() {
        delete[] m_data;
    }
    

    在我们定义的字符串的析构函数中,我们对动态申请的字符串的空间进行了删除,比如删除了字符串"hello word"。而字符串的m_data本身只是一个指针,此时还没有被删除。

    operator delete(ps);则是内部调用free函数的一个函数,将指针删除。

    而调用析构函数需要做什么?这需要我们自己定义:

    myString::~myString() {
        delete[] m_data;
    }
    

    在我们定义的字符串的析构函数中,我们对动态申请的字符串的空间进行了删除,比如删除了字符串"hello word"。而字符串的m_data本身只是一个指针,此时还没有被删除。

    operator delete(ps);则是内部调用free函数的一个函数,将指针删除。

    展开全文
  • C++中,拷贝并没有脱离它本来的含义,只是将这个含义进行了“特化”,是指用已经存在的对象创建出一个新的对象。从本质上讲,对象也是一份数据,因为它会占用内存。 严格来说,对象的创建包括两个阶段..

    拷贝和复制是一个意思,对应的英文单词都是copy。对于计算机来说,拷贝是指用一份原有的、已经存在的数据创建出一份新的数据,最终的结果是多了一份相同的数据。例如,将 Word 文档拷贝到U盘去复印店打印,将 D 盘的图片拷贝到桌面以方便浏览,将重要的文件上传到百度网盘以防止丢失等,都是「创建一份新数据」的意思。

    在 C++ 中,拷贝并没有脱离它本来的含义,只是将这个含义进行了“特化”,是指用已经存在的对象创建出一个新的对象。从本质上讲,对象也是一份数据,因为它会占用内存。

    严格来说,对象的创建包括两个阶段,首先要分配内存空间,然后再进行初始化:

    分配内存很好理解,就是在堆区、栈区或者全局数据区留出足够多的字节。这个时候的内存还比较“原始”,没有被“教化”,它所包含的数据一般是零值或者随机值,没有实际的意义。

    初始化就是首次对内存赋值,让它的数据有意义。注意是首次赋值,再次赋值不叫初始化。初始化的时候还可以为对象分配其他的资源(打开文件、连接网络、动态分配内存等),或者提前进行一些计算(根据价格和数量计算出总价、根据长度和宽度计算出矩形的面积等)等。说白了,初始化就是调用构造函数。

    很明显,这里所说的拷贝是在初始化阶段进行的,也就是用其它对象的数据来初始化新对象的内存。

    那么,如何用拷贝的方式来初始化一个对象呢?其实这样的例子比比皆是,string 类就是一个典型的例子。

     

    运行结果:

    http://c.biancheng.net

    http://c.biancheng.net

    http://c.biancheng.net

    http://c.biancheng.net

    http://c.biancheng.net http://c.biancheng.net

    s1、s2、s3、s4 以及 func() 的形参 str,都是使用拷贝的方式来初始化的。

    对于 s1,表面上看起来是将一个字符串直接赋值给了 s1,实际上在内部进行了类型转换,将 const char * 类型转换为 string 类型后才赋值的。

    对于 s1、s2、s3、s4,都是将其它对象的数据拷贝给当前对象,以完成当前对象的初始化。

    对于 func() 的形参 str,其实在定义时就为它分配了内存,但是此时并没有初始化,只有等到调用 func() 时,才会将其它对象的数据拷贝给 str 以完成初始化。

    当以拷贝的方式初始化一个对象时,会调用一个特殊的构造函数,就是拷贝构造函数(Copy Constructor)。

    下面的例子演示了拷贝构造函数的定义和使用:

     

    运行结果:

    Copy constructor was called.

    Copy constructor was called.

    小明的年龄是16,成绩是90.5

    小明的年龄是16,成绩是90.5

    小明的年龄是16,成绩是90.5

    第 8 行是拷贝构造函数的声明,第 20 行是拷贝构造函数的定义。拷贝构造函数只有一个参数,它的类型是当前类的引用,而且一般都是 const 引用。

    1) 为什么必须是当前类的引用呢?

    如果拷贝构造函数的参数不是当前类的引用,而是当前类的对象,那么在调用拷贝构造函数时,会将另外一个对象直接传递给形参,这本身就是一次拷贝,会再次调用拷贝构造函数,然后又将一个对象直接传递给了形参,将继续调用拷贝构造函数……这个过程会一直持续下去,没有尽头,陷入死循环。

    只有当参数是当前类的引用时,才不会导致再次调用拷贝构造函数,这不仅是逻辑上的要求,也是 C++ 语法的要求。

    2) 为什么是 const 引用呢?

    拷贝构造函数的目的是用其它对象的数据来初始化当前对象,并没有期望更改其它对象的数据,添加 const 限制后,这个含义更加明确了。

    另外一个原因是,添加 const 限制后,可以将 const 对象和非 const 对象传递给形参了,因为非 const 类型可以转换为 const 类型。如果没有 const 限制,就不能将 const 对象传递给形参,因为 const 类型不能转换为非 const 类型,这就意味着,不能使用 const 对象来初始化当前对象了。

    以上面的 Student 类为例,将 const 去掉后,拷贝构造函数的原型变为:

    Student::Student(Student &stu);

    此时,下面的代码就会发生错误:

     

    stu1 是 const 类型,在初始化 stu2、stu3 时,编译器希望调用Student::Student(const Student &stu),但是这个函数却不存在,又不能将 const Student 类型转换为 Student 类型去调用Student::Student(Student &stu),所以最终调用失败了。

    当然,你也可以再添加一个参数为 const 引用的拷贝构造函数,这样就不会出错了。换句话说,一个类可以同时存在两个拷贝构造函数,一个函数的参数为 const 引用,另一个函数的参数为非 const 引用。

    默认拷贝构造函数

    在前面的教程中,我们还没有讲解拷贝构造函数,但是却已经在使用拷贝的方式创建对象了,并且也没有引发什么错误。这是因为,如果程序员没有显式地定义拷贝构造函数,那么编译器会自动生成一个默认的拷贝构造函数。这个默认的拷贝构造函数很简单,就是使用“老对象”的成员变量对“新对象”的成员变量进行一一赋值,和上面 Student 类的拷贝构造函数非常类似。

    编程学习:

     

     

    展开全文
  • c++拷贝构造函数详解

    2020-04-17 16:09:59
    c++拷贝构造函数详解 一、什么是拷贝构造函数 首先对于普通类型的对象来说,它们之间的复制是很简单的,例如: int a=100; int b=a; 而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员变量...

    文章转至:https://www.cnblogs.com/alantu2018/p/8459250.html

    c++拷贝构造函数详解

    一、什么是拷贝构造函数

          首先对于普通类型的对象来说,它们之间的复制是很简单的,例如:

    int a=100;
    int b=a;

     

      而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员变量。
      下面看一个类对象拷贝的简单例子。
       

    复制代码

    复制代码

    #include<iostream>
    using namespace std;
    class CExample
    {
    private:
        int a;
    public:
        //构造函数
        CExample(int b)
        {
            a=b;
            printf("constructor is called\n");
        }
        //拷贝构造函数
        CExample(const CExample & c)
        {
            a=c.a;
            printf("copy constructor is called\n");
        }
        //析构函数
        ~CExample()
        {
            cout<<"destructor is called\n";
        }
        void Show()
        {
            cout<<a<<endl;
        }
    };
    int main()
    {
        CExample A(100);
        CExample B=A;
        B.Show(); 
        return 0;
    }

    复制代码

    复制代码

       运行程序,屏幕输出100。从以上代码的运行结果可以看出,系统为对象 B 分配了内存并完成了与对象 A 的复制过程。就类对象而言,相同类型的类对象是通过拷贝构造函数来完成整个复制过程的。
      

     CExample(const CExample& C) 就是我们自定义的拷贝构造函数。可见,拷贝构造函数是一种特殊的构造函数,函数的名称必须和类名称一致,它必须的一个参数是本类型的一个引用变量

    二、拷贝构造函数的调用时机
        1. 当函数的参数为类的对象时
      

    复制代码

    复制代码

      
    #include<iostream>
    using namespace std;
    class CExample
    {
    private:
        int a;
    public:
        CExample(int b)
        {
            a=b;
            printf("constructor is called\n");
        }
        CExample(const CExample & c)
        {
            a=c.a;
            printf("copy constructor is called\n");
        }
        ~CExample()
        {
         cout<<"destructor is called\n";
        }
        void Show()
        {
         cout<<a<<endl;
        }
    };
    void g_fun(CExample c)
    {
        cout<<"g_func"<<endl;
    }
    int main()
    {
        CExample A(100);
        CExample B=A;
        B.Show(); 
        g_fun(A);
        return 0;
    }

    复制代码

    复制代码

     

     调用g_fun()时,会产生以下几个重要步骤:
    (1).A对象传入形参时,会先会产生一个临时变量,就叫 C 吧。
    (2).然后调用拷贝构造函数把A的值给C。 整个这两个步骤有点像:CExample C(A);
    (3).等g_fun()执行完后, 析构掉 C 对象。  

        


    2. 函数的返回值是类的对象
      

    复制代码

    复制代码

    #include<iostream>
    using namespace std;
    class CExample
    {
    private:
        int a;
    public:
        //构造函数
        CExample(int b)
        {
         a=b;
            printf("constructor is called\n");
        }
        //拷贝构造函数
        CExample(const CExample & c)
        {
         a=c.a;
            printf("copy constructor is called\n");
        }
        //析构函数
        ~CExample()
        {
         cout<<"destructor is called\n";
        }
        void Show()
        {
         cout<<a<<endl;
        }
    };
    CExample g_fun()
    {
        CExample temp(0);
        return temp;
    }
    int main()
    {
        
        g_fun();
        return 0;
    }

    复制代码

    复制代码

     

       当g_Fun()函数执行到return时,会产生以下几个重要步骤:
    (1). 先会产生一个临时变量,就叫XXXX吧。
    (2). 然后调用拷贝构造函数把temp的值给XXXX。整个这两个步骤有点像:CExample XXXX(temp);
    (3). 在函数执行到最后先析构temp局部变量。
    (4). 等g_fun()执行完后再析构掉XXXX对象。  
      

     
    3. 对象需要通过另外一个对象进行初始化

    CExample A(100);
    CExample B=A;

    三、浅拷贝与深拷贝
        1. 默认拷贝构造函数
       很多时候在我们都不知道拷贝构造函数的情况下,传递对象给函数参数或者函数返回对象都能很好的进行,这是因为编译器会给我们自动产生一个拷贝构造函数,这就是“默认拷贝构造函数”,这个构造函数很简单,仅仅使用“老对象”的数据成员的值对“新对象”的数据成员一一进行赋值,它一般具有以下形式:

    Rect::Rect(const Rect& r)
    {
        width=r.width;
        height=r.height;
    }

    当然,以上代码不用我们编写,编译器会为我们自动生成。但是如果认为这样就可以解决对象的复制问题,那就错了,让我们来考虑以下一段代码:
     

    复制代码

    复制代码

     
    #include<iostream>
    using namespace std;
    class Rect
    {
    public:
        Rect()
        {
         count++;
        }
        ~Rect()
        {
         count--;
        }
        static int getCount()
        {
         return count;
        }
    private:
        int width;
        int height;
        static int count;
    };
    int Rect::count=0;
    int main()
    {
        Rect rect1;
        cout<<"The count of Rect:"<<Rect::getCount()<<endl;
        Rect rect2(rect1);
        cout<<"The count of Rect:"<<Rect::getCount()<<endl;
        return 0;
    }
      

    复制代码

    复制代码

    这段代码对前面的类,加入了一个静态成员,目的是进行计数。在主函数中,首先创建对象rect1,输出此时的对象个数,然后使用rect1复制出对象rect2,再输出此时的对象个数,按照理解,此时应该有两个对象存在,但实际程序运行时,输出的都是1,反应出只有1个对象。此外,在销毁对象时,由于会调用销毁两个对象,类的析构函数会调用两次,此时的计数器将变为负数。

    说白了,就是拷贝构造函数没有处理静态数据成员。

    出现这些问题最根本就在于在复制对象时,计数器没有递增,我们重新编写拷贝构造函数,如下:

    复制代码

    复制代码

    #include<iostream>
    using namespace std;
    class Rect
    {
    public:
        Rect()
        {
            count++;
        }
        Rect(const Rect& r)
        {
            width=r.width;
            height=r.height;
            count++;
        }
        ~Rect()
        {
            count--;
        }
        static int getCount()
        {
            return count;
        }
    private:
        int width;
        int height;
        static int count;
    };
    int Rect::count=0;
    int main()
    {
        Rect rect1;
        cout<<"The count of Rect:"<<Rect::getCount()<<endl;
        Rect rect2(rect1);
        cout<<"The count of Rect:"<<Rect::getCount()<<endl;
        return 0;
    }

    复制代码

    复制代码

     

      2. 浅拷贝

        所谓浅拷贝,指的是在对象复制时,只对对象中的数据成员进行简单的赋值,默认拷贝构造函数执行的也是浅拷贝。大多情况下“浅拷贝”已经能很好地工作了,但是一旦对象存在了动态成员,那么浅拷贝就会出问题了,让我们考虑如下一段代码:

     

    复制代码

    复制代码

    #include<iostream>
    #include<assert.h>
    using namespace std;
    class Rect
    {
    public:
        Rect()
        {
         p=new int(100);
        }
       
        ~Rect()
        {
         assert(p!=NULL);
            delete p;
        }
    private:
        int width;
        int height;
        int *p;
    };
    int main()
    {
        Rect rect1;
        Rect rect2(rect1);
        return 0;
    }

    复制代码

    复制代码

     

     在这段代码运行结束之前,会出现一个运行错误。原因就在于在进行对象复制时,对于动态分配的内容没有进行正确的操作。我们来分析一下:

        在运行定义rect1对象后,由于在构造函数中有一个动态分配的语句,因此执行后的内存情况大致如下:

         
      在使用rect1复制rect2时,由于执行的是浅拷贝,只是将成员的值进行赋值,这时 rect1.p = rect2.p,也即这两个指针指向了堆里的同一个空间,如下图所示:
        

     当然,这不是我们所期望的结果,在销毁对象时,两个对象的析构函数将对同一个内存空间释放两次,这就是错误出现的原因。我们需要的不是两个p有相同的值,而是两个p指向的空间有相同的值,解决办法就是使用“深拷贝”。
     
      3. 深拷贝

      在“深拷贝”的情况下,对于对象中动态成员,就不能仅仅简单地赋值了,而应该重新动态分配空间,如上面的例子就应该按照如下的方式进行处理:

     

    复制代码

    复制代码

    #include<iostream>
    #include<assert.h>
    using namespace std;
    class Rect
    {
    public:
        Rect()
        {
         p=new int(100);
        }
        
        Rect(const Rect& r)
        {
         width=r.width;
            height=r.height;
         p=new int(100);
            *p=*(r.p);
        }
         
        ~Rect()
        {
         assert(p!=NULL);
            delete p;
        }
    private:
        int width;
        int height;
        int *p;
    };
    int main()
    {
        Rect rect1;
        Rect rect2(rect1);
        return 0;
    }

    复制代码

    复制代码

     

      此时,在完成对象的复制后,内存的一个大致情况如下:
       
    此时rect1的p和rect2的p各自指向一段内存空间,但它们指向的空间具有相同的内容,这就是所谓的“深拷贝”。

    3. 防止默认拷贝发生

        通过对对象复制的分析,我们发现对象的复制大多在进行“值传递”时发生,这里有一个小技巧可以防止按值传递——声明一个私有拷贝构造函数。甚至不必去定义这个拷贝构造函数,这样因为拷贝构造函数是私有的,如果用户试图按值传递或函数返回该类对象,将得到一个编译错误,从而可以避免按值传递或返回对象。

    复制代码

    复制代码 //防止按值传递 class CExample { private: int a; public: //构造函数 CExample(int b) { a = b; cout<<"creat: "<<a<<endl; } private: //拷贝构造函数,只是声明 CExample(const CExample& C); public: ~CExample() { cout<< "delete: "<<a<<endl; } void Show () { cout<<a<<endl; } }; //???? void g_Fun(CExample C) { cout<<"test"<<endl; } int main() { CExample test(1); //g_Fun(test); //按值传递将出错 return 0; }

    复制代码

    复制代码

    小结:
        拷贝有两种:深拷贝,浅拷贝。

          当出现类的等号赋值时,会调用拷贝函数,在未定义显示拷贝构造函数的情况下,系统会调用默认的拷贝函数——即浅拷贝,它能够完成成员的一一复制。当数据成员中没有指针时,浅拷贝是可行的。但当数据成员中有指针时,如果采用简单的浅拷贝,则两类中的两个指针将指向同一个地址,当对象快结束时,会调用两次析构函数,而导致指针悬挂现象。所以,这时,必须采用深拷贝。

         深拷贝与浅拷贝的区别就在于深拷贝会在堆内存中另外申请空间来储存数据,从而也就解决了指针悬挂的问题。简而言之,当数据成员中有指针时,必须要用深拷贝


    四、拷贝构造函数的几个细节
       1.为什么拷贝构造函数必须是引用传递,不能是值传递?
         简单的回答是为了防止递归引用。
              具体一些可以这么讲:
                  当 一个对象需要以值方式传递时,编译器会生成代码调用它的拷贝构造函数以生成一个复本。如果类A的拷贝构造函数是以值方式传递一个类A对象作为参数的话,当 需要调用类A的拷贝构造函数时,需要以值方式传进一个A的对象作为实参; 而以值方式传递需要调用类A的拷贝构造函数;结果就是调用类A的拷贝构造函数导 致又一次调用类A的拷贝构造函数,这就是一个无限递归。
        
       2. 拷贝构造函数的作用。     
               作用就是用来复制对象的,在使用这个对象的实例来初始化这个对象的一个新的实例。

         3.参数传递过程到底发生了什么?
          将地址传递和值传递统一起来,归根结底还是传递的是"值"(地址也是值,只不过通过它可以找到另一个值)!
          i)值传递:
             对于内置数据类型的传递时,直接赋值拷贝给形参(注意形参是函数内局部变量);
             对于类类型的传递时,需要首先调用该类的拷贝构造函数来初始化形参(局部对象);如void foo(class_type obj_local){}, 如果调用foo(obj);  首先class_type obj_local(obj) ,这样就定义了局部变量obj_local供函数内部使用
         ii)引用传递:
            无论对内置类型还是类类型,传递引用或指针最终都是传递的地址值!而地址总是指针类型(属于简单类型), 显然参数传递时,按简单类型的赋值拷贝,而不会有拷贝构造函数的调用(对于类类型).

       4. 在类中有指针数据成员时,拷贝构造函数的使用?
            如果不显式声明拷贝构造函数的时候,编译器也会生成一个默认的拷贝构造函数,而且在一般的情况下运行的也很好。但是在遇到类有指针数据成员时就出现问题 了:因为默认的拷贝构造函数是按成员拷贝构造,这导致了两个不同的指针(如ptr1=ptr2)指向了相同的内存。当一个实例销毁时,调用析构函数 free(ptr1)释放了这段内存,那么剩下的一个实例的指针ptr2就无效了,在被销毁的时候free(ptr2)就会出现错误了, 这相当于重复释放一块内存两次。这种情况必须显式声明并实现自己的拷贝构造函数,来为新的实例的指针分配新的内存。

    问题1和2回答了为什么拷贝构造函数使用值传递会产生无限递归调用的问题;
    问题3回答了回答了在类中有指针数据成员时,拷贝构造函数使用值传递等于白显式定义了拷贝构造函数,因为默认的拷贝构造函数就是这么干的。
     

      5. 拷贝构造函数里能调用private成员变量吗?
    解答:
    这个问题是在网上见的,当时一下子有点晕。其时从名子我们就知道拷贝构造函数其时就是一个特殊的构造函数,操作的还是自己类的成员变量,所以不受private的限制。

     6. 以下函数哪个是拷贝构造函数,为什么?

     
    X::X(const X&);   //拷贝构造函数
    X::X(X); 
    X::X(X&, int a=1);   //拷贝构造函数
    X::X(X&, int a=1, int b=2);  //拷贝构造函数

       解答:对于一个类X, 如果一个构造函数的第一个参数是下列之一:
       a) X&
       b) const X&
       c) volatile X&
       d) const volatile X&
       且没有其他参数或其他参数都有默认值,那么这个函数是拷贝构造函数.


      7. 一个类中可以存在多于一个的拷贝构造函数吗?
       解答:
    类中可以存在超过一个拷贝构造函数。

      
    class X { 
    public: 
      X(const X&); // const 的拷贝构造 
      X(X&); // 非const的拷贝构造 
    };

     

      注意,如果一个类中只存在一个参数为 X& 的拷贝构造函数,那么就不能使用const X或volatile X的对象实行拷贝初始化.
      如果一个类中没有定义拷贝构造函数,那么编译器会自动产生一个默认的拷贝构造函数。
      这个默认的参数可能为 X::X(const X&)或 X::X(X&),由编译器根据上下文决定选择哪一个。

         
    五、C++构造函数以及析构函数的若干面试问题
     
     Q1:构造函数能否重载,析构函数能否重载,为什么?
     A1:构造函数可以,析构函数不可以。

     

      Q2:析构函数为什么一般情况下要声明为虚函数?

      A2:虚函数是实现多态的基础,当我们通过基类的指针是析构子类对象时候,如果不定义成虚函数,那只调用基类的析构函数,子类的析构函数将不会被调用。如       果定义为虚函数,则子类父类的析构函数都会被调用。

      Q3:什么情况下必须定义拷贝构造函数?

      A3:当类的对象用于函数值传递时(值参数,返回类对象),拷贝构造函数会被调用。如果对象复制并非简单的值拷贝,那就必须定义拷贝构造函数。例如大的堆       栈数据拷贝。如果定义了拷贝构造函数,那也必须重载赋值操作符。


    参考博客:
     http://blog.csdn.net/lwbeyond/article/details/6202256
    http://jaden.blog.51cto.com/1549175/324480
    http://blog.chinaunix.net/uid-28662931-id-3496322.html

    展开全文
  • C++ 拷贝构造函数和赋值构造函数

    万次阅读 多人点赞 2019-04-06 16:43:35
    C++中复制控制是一个比较重要的话题,主要包括复制构造函数、重载赋值操作符、析构函数这三部分,这三个函数是一致的,如果类需要析构函数,则它也需要复制操作符 和 复制构造函数,这个规则被称为 C++的“三法则...
  • 以下几种情况会调用拷贝构造函数 1. 以一个对象初始化另一个对象 2. 函数以某个对象为参数 3. 函数返回某个对象 4. 初始化序列式容器的元素 2. void test2() { Test t1(1,2); Test t2; t2=t1;//调用的不是拷贝构造...
  • C++ 拷贝构造函数 赋值构造函数 解释
  • 1. 何时调用复制构造函数  复制构造函数用于将一个对象复制到新创建的对象中。也就是说,它用于初始化过程中,而不是常规的赋值过程中。类的复制构造函数原型通常如下:  class_name(const class_name&);  它...
  • C++拷贝构造函数(复制构造函数)

    千次阅读 2018-12-22 14:03:33
    一、拷贝构造函数的形式 复制构造函数是构造函数的一种特殊情况。因为类的对象包含各种成员变量,在发生拷贝时不能和普通对象一样来拷贝,所以我们需要使用拷贝构造函数来进行对象拷贝。拷贝构造函数只有一个参数,...
  • 一,拷贝构造函数 拷贝构造函数是一种特殊的构造函数,函数的名称必须和类名称一致,它的唯一的一个参数是本类型的一个引用变量,该参数是const类型,不可变的。例如:类X的拷贝构造函数的形式为X(const X& x)...
  • 拷贝构造函数的目的就是完成对一个对象的拷贝,一般在一下三种场景下会调用该对象的拷贝构造函数: 1.函数返回类型是类的对象时 2.函数的参数是类的对象,调用该函数时会调用该类的拷贝构造函数 3.使用一个对象去...
  • 一段C++代码: //: HowMany_2.cpp #include using namespace std; class HowMany {  static int objectCount;   public:  HowMany() {  ++objectCount;  print("HowMany()");  }  ~HowMany()...
  • c++拷贝构造函数

    千次阅读 多人点赞 2019-06-28 16:22:40
    什么是拷贝构造函数 首先对于普通类型的对象来说,它们之间的复制是很简单的,例如: int a = 100; int b = a; 而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员变量。 下面看一个类对象...
  • C++拷贝构造函数与拷贝赋值运算符

    千次阅读 2019-04-20 21:44:45
    拷贝构造函数 拷贝构造函数定义了当同类型的另一个对象初始化本对象时做什么,如果一个构造函数的第一个参数是自身类类型的引用,且任何额外参数都有默认值,则此构造函数是拷贝构造函数拷贝构造函数的参数类型...
  • A(A p_a)//错误的拷贝构造函数 { m1 = p_a.m1; } A(A&p_a)//正确的拷贝构造函数 { m1 = p_a.m1; } private: int m1; }; int main(void) { A a1; A a2(a1);//调用拷贝构造函数,来创建a2 }
  • 主要介绍了拷贝构造函数防篡改示例,需要的朋友可以参考下
  • C++拷贝构造函数详解

    千次阅读 多人点赞 2017-02-08 09:59:40
    什么是拷贝构造函数 首先对于普通类型的对象来说,它们之间的复制是很简单的,例如: int a = 100; int b = a; 而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员变量。 下面看一个类对象...
  • C++ 拷贝构造函数

    千次阅读 多人点赞 2017-04-26 09:13:49
    1. 定义  (const &);  class A { int x,y; public: A(); A(const A& a) { x = a.x+1;... 其中,const是为了防止在函数中修改实参对象,可以省略。... 拷贝构造函数也可以带有其他参数,但这

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 138,270
精华内容 55,308
关键字:

c++拷贝构造函数

c++ 订阅
友情链接: step-frequency-phase.rar