zoukankan      html  css  js  c++  java
  • C++构造函数和拷贝构造函数详解

    构造函数、析构函数与赋值函数是每个类最基本的函数。它们太普通以致让人容易麻痹大意,其实这些貌似简单的函数就象没有顶盖的下水道那样危险。

    每个类只有一个析构函数和一个赋值函数,但可以有多个构造函数(包含一个拷贝构造函数,其它的称为普通构造函数)。

    对于任意一个类A,如果不想编写上述函数,C++编译器将自动为A 产生四个缺省的函数,例如:

            A(void); // 缺省的无参数构造函数

            A(const A &a); // 缺省的拷贝构造函数

            ~A(void); // 缺省的析构函数

            A & operate =(const A &a); // 缺省的赋值函数

    这不禁让人疑惑,既然能自动生成函数,为什么还要程序员编写?原因如下:

           <1>如果使用“缺省的无参数构造函数”和“缺省的析构函数”,等于放弃了自主“初始化”和“清除”的机会,C++发明人Stroustrup 的好心好意白费了。

           <2>“缺省的拷贝构造函数”和“缺省的赋值函数”均采用“位拷贝”而非“值拷贝”的方式来实现,倘若类中含有指针变量,这两个函数注定将出错。

    C++ 默认构造函数 :

    1、每个类必须有一个构造函数,否则没法创建对象;
    2、若 程序员没有提供任何构造函数,则 C++提供一个默认的构造函数,该默认构造函数是无参构造函数,它仅负责创建对象,不做任何初始化的工作;
    3、只要 programer 定义了一个构造函数(不管是无参还是有参构造),C++就不再提供默认的默认构造函数。即如果为类定义了一个带参的构造函数,还想要无参构造函数,就必须自己定义;

    4、与变量定义类似,在用默认构造函数创建对象时,如果创建的是全局对象或静态对象,则对象的位模式全为 0,否则,对象值是随机的。

    C++默认拷贝构造函数:
    1、默认的拷贝构造函数执行的顺序与其他用户定义的构造函数相同,执行先父类后子类的构造.
    2、拷贝构造函数对类中每一个数据成员执行成员拷贝(memberwise Copy)的动作.
    3、如果数据成员为某一个类的实例,那么调用此类的拷贝构造函数.
    4、如果数据成员是一个数组,对数组的每一个执行按位拷贝.
    5、如果数据成员是一个数量,如int,double,那么调用系统内建的赋值运算符对其进行赋值.

    请看下面代码:

    #include <iostream>
    #include <string>
    using namespace std;
    
    class Student
    {
    public:
    	Student()
    	{
    		cout << "构造函数1" << endl;
    	}
    	Student(int k)
    	{
    		cout << "构造函数2" << endl;
    		i = k;
    	}
    	Student(Student const &m)
    	{
    		cout << "拷贝构造函数" << endl;
    		i = m.i * (-1);
    	}
    
    	void p()
    	{
    		cout << i << endl;
    	}
    	~Student()
    	{
    		cout << "析构函数" << endl;
    	}
    protected:
    	int i;
    };
    
    int main(int argc, char **argv)
    {
    	Student s(9818);
    	// 调用构造函数2
    	s.p();
    
    	Student t(s);
    	// 调用拷贝构造函数
    	t.p();
    
    	Student k = s;
    	// 调用拷贝构造函数
    	k.p();
    
    	Student *p = new Student(s);
    	// 调用拷贝构造函数
    	p->p();
    
    	Student m;
    	// 调用构造函数1
    	m = s;// 赋值运算
    	m.p();
    
    	return 0;
    }
    

      运行结果:

    构造函数2
    9818
    拷贝构造函数
    -9818
    拷贝构造函数
    -9818
    拷贝构造函数
    -9818
    构造函数1
    9818
    析构函数
    析构函数
    析构函数
    析构函数

    下面我们来讨论一下关于浅拷贝和深拷贝的问题。 

            深拷贝和浅拷贝的定义可以简单理解成:如果一个类拥有资源(堆,或者是其它系统资源),当这个类的对象发生复制过程的时候(复制指针所指向的值),这个过程就可以叫做深拷贝,反之对象存在资源但复制过程并未复制资源(只复制了指针所指的地址)的情况视为浅拷贝。
      很多人会问到,既然系统会自动提供一个默认的拷贝构造函数来处理复制,那么我们没有必要去自定义拷贝构造函数呀,对,就普通情况而言这的确是没有必要的,但在某些状况下,类体内的成员是需要开辟动态堆内存的,如果我们不自定义拷贝构造函数而让系统自己处理,那么就会导致堆内存的所属权产生混乱。试想一下,已经开辟的一端堆地址原来是属于对象a的,由于复制过程发生,b对象取得是a已经开辟的堆地址,一旦程序产生析构,释放堆的时候,计算机不清楚这段地址是真正属于谁的,当连续发生两次析构的时候就出现了运行错误。
      为了更详细的说明问题,请看如下的代码。

    #include <iostream>
    #include <string>
    using namespace std;
    
    class aa
    {
    public:
        aa()
        {
            cout << "调用构造函数" << endl;
            f = new char[10];
        }
        ~aa()
        {
            cout << "调用析构函数" << endl;
            delete[] f;
        }
        char * f;
    };
    
    int main(int argc, char **argv)
    {
        aa p;
        printf("p.f=%p
    ",p.f);
        strcpy(p.f, "Computer");
        cout << p.f << endl;
        aa q(p);// 调用默认的拷贝构造函数
        printf("q.f=%p
    ",q.f);
        cout << q.f << endl;
    
        strcpy(p.f, "Software");
        cout << p.f << endl;
        cout << q.f << endl;
    
        strcpy(q.f, "Software");
        cout << p.f << endl;
        cout << q.f << endl;
    
        return 0;
    }

    运行结果:

    调用构造函数
    p.f=003F1048
    Computer
    q.f=003F1048
    Computer
    Software
    Software
    Software
    Software
    调用析构函数
    调用析构函数

    通过上面的例子,我们能清楚的看到,第二个对象调用拷贝构造函数,q.f获得的地址值与p.f相同,即指向了同一片内存区域。程序结束时,会两次调用析构函数,即同一个指针执行两遍delete操作,会发生什么呢?这可能是一场灾难,可能会破坏该堆及自由内存表。

    那我们该如何避免呢?这里我们就需要使用深拷贝。

    #include <iostream>
    #include <string>
    using namespace std;
    
    class aa
    {
    public:
        aa()
        {
            cout << "调用构造函数" << endl;
            f = new char[10];
        }
        aa(aa const & s)
        {
            cout << "调用拷贝构造函数" << endl;
            f = new char[10];
            strcpy(f, s.f);
        }
        ~aa()
        {
            cout << "调用析构函数" << endl;
            delete[] f;
        }
        char * f;
    };
    
    int main(int argc, char **argv)
    {
        aa p;
        printf("p.f=%p
    ", p.f);
        strcpy(p.f, "Computer");
        cout << p.f << endl;
        aa q(p);// 调用用戶的拷贝构造函数
        printf("q.f=%p
    ", q.f);
        cout << q.f << endl;
    
        strcpy(p.f, "Software");
        cout << p.f << endl;
        cout << q.f << endl;
    
        strcpy(q.f, "Software");
        cout << p.f << endl;
        cout << q.f << endl;
    
        return 0;
    }

    运行结果:

    调用构造函数
    p.f=00351048
    Computer
    调用拷贝构造函数
    q.f=00351060
    Computer
    Software
    Computer
    Software
    Software
    调用析构函数
    调用析构函数

    现在我们可以看到,p.f和q.f分别指向不同的内存区域。


    来自:http://blog.csdn.net/sg131971/article/details/7045278

  • 相关阅读:
    WebDriver Api
    Web系统基础
    python自动化测试相关资料
    持续集成Jenkins
    Git和Github库详细使用教程
    TCP/UDP协议
    CentOS连接网络设置
    Web测试基本思路:UI测试、功能测试和兼容性测试
    初识Jenkins
    selenium +python 对table的操作
  • 原文地址:https://www.cnblogs.com/lpxblog/p/4699333.html
Copyright © 2011-2022 走看看