• C++类中拷贝构造函数详解


    a. C++标准中提到“The default constructor, copy constructor and copy assignment operator, and destructor are special member functions.[Note: The implementation will implicitly declare these member functions for some class types when the program does not explicitly declare them. The implementation will implicitly define them if they are used.]”。即缺省构造函数、拷贝构造函数、拷贝赋值操作符和析构函数是特殊成员函数。

    b. “Constructors do not have names. A special declarator syntax using an optional sequence of function- specifiers(inline, virtual and explicit) followed by the constructor’s class name followed by a parameter list is used to declare or define the constructor.” 构造函数没有名称。

    c. 构造函数不能有返回类型,也不能由virtual, const, static 和 volatile来修饰。但可以由inline来修饰,事实上隐式构造函数就是用inline来修饰的。inline表示编译时展开,通常速度块;virtual表示运行时绑定,通常意味着灵活。

    d. 类中存在虚函数或者有虚基类的情况下需要显式声明构造函数。拷贝构造函数也是如此。

    #include <iostream>
    using namespace std;
    
    class A{
    public:
    	A(){ cout << "A" << endl;}
    	//virtual ~A(){ cout << "~A" << endl;}
    	~A(){ cout << "~A" << endl;}
    };
    class B : public A{
    public:
    	B(){ cout << "B" << endl;}
    	~B(){ cout << "~B" << endl;}
    };
    int main()
    {
    	A *a = new B();
    	delete a;
    	return 0;
    }

    输出:

    A
    B
    ~A
    请按任意键继续. . .

    一般情况下类的析构函数里面都是释放内存资源,而析构函数不被调用的话就会造成内存泄漏。我想所有的C++程序员都知道这样的危险性。当然,如果在析构函数中做了其他工作的话,那你的所有努力也都是白费力气。
    这样做是为了当用一个基类的指针删除一个派生类的对象时,派生类的析构函数会被调用。
    当然,并不是要把所有类的析构函数都写成虚函数。因为当类里面有虚函数的时候,编译器会给类添加一个虚函数表,里面来存放虚函数指针,这样就会增加类的存储空间。所以,只有当一个类被用来作为基类的时候,才把析构函数写成虚函数。

    f. 构造函数是一种特殊函数,而拷贝构造函数是一种特殊的构造函数。类X的构造函数的第一个参数必须为X&,或者const X&;除了第一个参数外,构造函数要么不存在其他参数,如果存在其他参数,其他参数必须有默认值。一个类可以有多个拷贝构造函数。它的形式如下:

    X::X(X& x)
    X::X(const X& x)
    X::X(X& x, int a = 0, int b = 1…)

    g. 什么时候会调用拷贝构造函数?
        以下三种情况出现时,会调用一个类的拷贝构造函数:
        1) 用一个已经实例化了的该类对象,去实例化该类的另外一个对象;
        2) 用该类的对象传值的方式作为一个函数的参数;
        3) 一个函数返回值为该类的一个对象。

    #include <iostream>
    using namespace std;
    
    class CA
    {
    public:
    	int a;
    	int b;
    public:
    	inline CA()
    	{
    		a = 1;
    		b = 1;
    	}
    
    	inline CA(int A, int B)
    	{
    		a = A;
    		b = B;
    	}
    
    	inline CA(CA& x)
    	{
    		a = x.a;
    		b = x.b;
    		cout << "copy constructor is called." << endl;
    	}
    
    	void printInfo()
    	{
    		cout << "a = " << a << ", b = " << b << endl;
    	}
    };
    
    int someFun1(CA x)
    {
    	return x.a + x.b;
    }
    
    CA someFun2(int a, int b)
    {
    	CA ca(a, b);
    	return ca;                                      
    }
    
    int main(void)
    {
    	CA a;
    	// CA b();                                 // 不能用这种方式声明CA的对象b!
    	CA c(10, 10);
    	CA d(c);                                   // 情况1) -> 调用拷贝构造函数
    	int anInt = someFun1(c);           // 情况2) -> 调用拷贝构造函数
    	CA e = someFun2(11, 11);        // 情况3) -> 调用拷贝构造函数
    
    	return 0;
    }

    运行结果:
    copy constructor is called.
    copy constructor is called.
    copy constructor is called.

    运行结果表明,上述结论是正确的。

    h. 什么时候必须要显式声明拷贝构造函数?
        拷贝构造函数的作用就是用一个已经实例化了的该类对象,去实例化该类的另外一个对象。

    1) 下面的代码并没有显式声明一个构造函数,编译器会自动为类CExample1生成一个缺省的隐式拷贝构造函数:

    #include <iostream>
    using namespace std;
    
    class CExample1
    {
    private:
             int a;
    
    public:
             CExample1(int b){a = b;}
             void SetValue(int a){this->a = a;}
             void Show(){cout << a << endl;}
    };
    
    int main(void)
    {
             CExample1 A(100);
             CExample1 B = A;       // 调用了缺省的隐式拷贝构造函数
             CExample1 C(B);         // 调用了缺省的隐式拷贝构造函数
    
             B.Show();                    // 输出应该是100
             B.SetValue(90);
             B.Show();                    // 输出应该是90
             A.Show();                    // 输出应该是100
             C.Show();                    // 输出应该是100
    
             return 0;
    }

    输出为:

    100
    90
    100
    100

    2) 如果有成员变量以指针形式存在,涉及动态内存分配等情况下,一定要显式声明拷贝构造函数。要注意到,如果需要显式定义拷贝构造函数,那么通常都是需要同时定义析构函数(因为通常涉及了动态内存分配),至于是否必须重载操作符“=”,要视情况而定。

    #include <iostream>
    using namespace std;
    
    class CSomething
    {
    public:
             int a;
             int b;
    
    public:
             CSomething(int a, int b)
             {this->a = a;  this->b = b;}
    };
    
    class CA
    {
    private:
             CSomething* sth;              // 以指针形式存在的成员变量
    
    public:
             CA(CSomething* sth){this->sth = new CSomething(sth->a, sth->b);}
             ~CA()
             {
                       cout << "In the destructor of class CA..." << endl;
                       if (NULL != sth) delete sth;
    
             }
             void Show(){cout << "(" << sth->a << ", " << sth->b << ")" << endl;}
             void setValue(int a, int b){sth->a = a; sth->b = b;}
             void getSthAddress()
             {
                       cout << sth << endl;
             }
    };
     
    int main(void)
    {
             CSomething sth(1, 2);
             CA ca(&sth);
             ca.Show();
    
             CA cb(ca);                                      // 调用缺省的隐式拷贝构造函数
             cb.Show();
     
             cb.setValue(2, 3);
             ca.Show();
             cb.Show();
     
             ca.getSthAddress();
             cb.getSthAddress();
    
             return 0;
    }

    上面的程序没有显式声明拷贝构造函数,运行结果如下:
    C++类中的4个特殊函数 - 玄机逸士 - 玄机逸士博客

    可见,ca和cb中的指针成员变量sth指向的是同一个内存地址(Console输出的第5、6行),这就是为什么在cb.setValue(2, 3)后,ca对应的内容也发生了改变(Console输出的第3、4行),而这不是我们所期望的;其次,我们生成了两个对象ca和cb,因此对两次调用析构函数,第一次调用析构函数的时候没有问题,因为此时sth里面有内容,第二次调用析构函数时,sth里面的内容由于在第一次调用析构函数的时候已经被delete了,所以会出现如上的错误提示。

    保持其他代码不变,现在我们增加一个拷贝构造函数如下:

    CA(CA& obj)
    {
             sth = new CSomething((obj.sth)->a, (obj.sth)->b);
    }
    再运行上面的程序,所得到的结果如下:

    C++类中的4个特殊函数 - 玄机逸士 - 玄机逸士博客

    这次,ca和cb中的指针成员变量sth指向的不是同一个内存地址(Console输出的第5、6行)了,这就是为什么在cb.setValue(2, 3)后,ca对应的内容保持不变,而cb的内容该如愿地改为(2, 3)(Console输出的第3、4行);其次,析构函数也不会报告错误了。

    3) 关于拷贝构造函数另外一个完整的例子,其中包含了copy constructor,destructor 和copy assignment operator。

    #include <iostream>
    using namespace std;
    
    class Point
    {
    public:
             int _x;
             int _y;
     
    public:
             Point();
             Point(int, int);
    };
     
    Point::Point()
    {
             _x = 0;
             _y = 0;
    }
     
    Point::Point(int x, int y)
    {
             _x = x;
             _y = y;
    }
     
    class CA
    {
    public:
             Point* _point;
     
    public:
             CA()
             {
                      _point = NULL;
             }
             CA(const Point*);
             void setPointValues(int, int);
             void printCoordinates();
     
             // 需要增加的拷贝构造函数
             CA(const CA&);
             // 需要增加的析构函数
             virtual ~CA();
             // 需要增加的拷贝赋值函数
             CA& operator = (const CA&);
    };
     
    CA::CA(const Point* point)
    {
             _point = new Point();                   // 发生了动态内存分配!因此不能缺少析构函数。
             _point->_x = point->_x;
             _point->_y = point->_y;
    }
     
    // 需要增加的拷贝构造函数的实现
    CA::CA(const CA& ca)
    {
             _point = new Point();
             _point->_x = (ca._point)->_x;
             _point->_y = (ca._point)->_y;
    }
     
    // 需要增加的析构函数的实现
    CA::~CA()
    {
             if(NULL != _point) delete _point;
             _point = NULL;
    }
     
    // 需要增加的拷贝赋值函数的实现
    CA& CA::operator = (const CA& ca)
    {
             _point = new Point();
             _point->_x = (ca._point)->_x;
             _point->_y = (ca._point)->_y;
     
             return *this;
    }
     
    void CA::setPointValues(int x, int y)
    {
             _point->_x = x;
             _point->_y = y;
    }
     
    void CA::printCoordinates()
    {
             cout << "Coordinates = (" << _point->_x << ", " << _point->_y << ")" << endl;
    }
     
    int main(void)
    {
             Point apoint(1, 2);
             CA ca(&apoint);
             ca.printCoordinates();
     
             CA cb(ca);                                               // 调用拷贝构造函数
             cb.printCoordinates();
     
             cb.setPointValues(12, 12);
             cb.printCoordinates();
             ca.printCoordinates();
     
             CA cc;
             cc = cb;                                                 // 调用拷贝赋值函数
             cc.printCoordinates();
             cc.setPointValues(13, 13);
           
             ca.printCoordinates();
             cb.printCoordinates();
             cc.printCoordinates();
     
             return 0;
    }





    Keep it simple!
    作者:N3verL4nd
    知识共享,欢迎转载。
  • 相关阅读:
    复制某文件夹及其子文件夹中的一定大小的文件
    一个简单的查询脚本
    写一个交互的脚本
    nginx+php5.6.12+discuz
    curl 错误
    python 交互界面tab补全
    uwsgi.xml
    supervisorctl
    认识nginx配置文件
    nginx+uwsgi+django 配置3
  • 原文地址:https://www.cnblogs.com/lgh1992314/p/5834898.html
Copyright © 2020-2023  润新知