精华内容
下载资源
问答
  • 2021-11-22 17:23:50


    构造函数和拷贝函数调用顺序

    Class A;
    Class B;
    void F() 
    {
      A a;
      B b;
    }//我们定义了a,b两个类,那么a,b构造函数的调用顺序和析构函数的顺序是怎样的呢?

    构造顺序是按照语句的顺序进行构造,因此构造函数调用的顺序是先调用a,后调用b。

    析构是程序结束时进行的操作,顺序和构造相反,因此为b,a。

    整体的顺序是ab构造,然后ba析构。

    下面我们单看析构顺序

    C c;
    int main()
    {
    A a;
    B b;
    static D d;
      
    return 0;
    }//其中A,B,C,D都是类

    ①除了知道析构函数顺序和构造函数调用顺序相反,

    ②这里要知道static改变了对象的生存作用域,需要等待程序结束时才会析构释放对象。

    ③而且c为全局变量,因此先于a,b,d构造,因此,c是最后一个析构。

    根据上边三点,析构的顺序是b,a,d,c

    更多相关内容
  • 拷贝构造函数详解

    千次阅读 多人点赞 2020-08-09 15:24:51
    拷贝构造函数详解 拷贝构造函数 int main() { int a; int b = 10; int c(b); return 0 } //结果就是b和c的结果都是10 int main() { int a; int b = 10; int c(b); string s1("hello"); string s2(s1); ...

    拷贝构造函数详解

    拷贝构造函数

    int main()
    {
    	int a;
    	int b = 10;
    	int c(b); 
    	return 0
    }
    //结果就是b和c的结果都是10
    
    int main()
    {
    	int a;
    	int b = 10;
    	int c(b);
    
    	string s1("hello");    
    	string s2(s1);     //用s1去构造s2,然后s1和s2的内容就一模一样了
    	return 0
    }
    //但是通过监视可以看出,s1和s2的地址是一样的,也就是说s1和s2是同一块空间,那么在进行析构的时候就会出现问题,也就是说,同一块空间析构了两次
    

    在现实生活中,可能存在一个与你一样的自己,我们称其为双胞胎.那在创建对象时,可否创建一个与一个对象一某一样的新对象呢?

    概念
    • 拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用
    • 为什么要给const进行修饰,因为我们在进行拷贝构造的时候,只是希望吧d1中的内容给d2,我们并不希望试图去改变d1中的内容,但是或许可能会不小心修改了d1中的内容,所以我们为了方便,给出const进行修饰,这样的话,就算我们不小心操作失误,d1中的内容也不会被改变的
    #include<iostream>
    using namespace std;
    class Date
    {
    public:
    	Date(int year=2020, int month=5, int day=5)
    	{
    		_year = year;
    		_month = month;
    		_day = day;
    		cout << "Date(int,int ,int):" << this << endl;
    		//看构造的是哪一个对象
    	}
    
    	~Date()
    	{
    		//对于日期类来说,这里面没有什么资源是需要去释放的
    		//所以对于日期类来说,给不给析构函数其实都没有什么影响
    		cout << "~Date():" << this << endl;
    		//看析构的是哪一个对象
    	}
    private:
    	int _year;
    	int _month;
    	int _day;
    };
    int main()
    {
    	Date d1(2020, 5, 5);
    	Date d2(d1);
    
    	return 0;
    }
    
    拷贝构造函数的特征
    • 拷贝构造函数是构造函数的一个重载形式。
    • 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用(原因在于,如果是利用值得方式来进行传递的话,就会开辟一段临时的空间,那么这个临时的空间也是需要构造的,那么这个临时的空间就会去调用构造函数,然后移植重复进行下去,就会造成无穷的递归调用)
      在这里插入图片描述
    #include<iostream>
    using namespace std;
    class Date
    {
    public:
    	//构造函数
    	Date(int year = 2020, int month = 5, int day = 5)
    	{
    		_year = year;
    		_month = month;
    		_day = day;
    		cout << "Date(int,int ,int):" << this << endl;
    		//看构造的是哪一个对象
    	}
    
    	//拷贝构造函数
    	Date(const Date& d)
    	{
    		_year = d._year;
    		_month = d._month;
    		_day = d._day;
    		cout << "Date(const Date&d):" << this << endl;
    	}
    
    	//析构函数
    	~Date()
    	{
    		//对于日期类来说,这里面没有什么资源是需要去释放的
    		//所以对于日期类来说,给不给析构函数其实都没有什么影响
    		cout << "~Date():" << this << endl;
    		//看析构的是哪一个对象
    	}
    private:
    	int _year;
    	int _month;
    	int _day;
    };
    void TestDate()
    {
    	Date d1(2020, 5, 5);
    	Date d2(d1);
    }
    int main()
    {
    	TestDate();
    	return 0;
    }
    
    由析构的结果可以看出,先析构的是d2的内容,再去析构d1的内容,也就是说,先构造的后析构,后构造的先析构,原因在于:

    在使用构造函数和析构函数时,需要特别注意对它们的调用时间和调用顺序。在一般情况下,调用析构函数的次序正好与调用构造函数的次序相反:最先被调用的构造函数,其对应的(同一对象中的)析构函数最后被调用,而最后被调用的构造函数,其对应的析构函数最先被调用。可以简记为:先构造的后析构,后构造的先析构,它相当于一个栈,先进后出。下面归纳一下什么时候调用构造函数和析构函数:

    • 在全局范围中定义的对象(即在所有函数之外定义的对象),它的构造函数在文件中的所有函数(包括main函数)执行之前调用。但如果一个程序中有多个文件,而不同的文件中都定义了全局对象,则这些对象的构造函数的执行顺序是不确定的。当main函数执行完毕或调用exit函数时(此时程序终止),调用析构函数。
    • 如果定义的是局部自动对象(例如在函数中定义对象),则在建立对象时调用其构造函数。如果函数被多次调用,则在每次建立对象时都要调用构造函数。在函数调用结束、对象释放时先调用析构函数。
    • 如果在函数中定义静态(static)局部对象,则只在程序第一次调用此函数建立对象时调用构造函数一次,在调用结束时对象并不释放,因此也不调用析构函数,只在main函数结束或调用exit函数结束程序时,才调用析构函数。
    • 局部变量存在栈中,当函数退出时要一个个销毁,先进后出的原理
    若未显示定义,系统生成默认的拷贝构造函数。默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝我们叫做浅拷贝或者值拷贝

    在这里插入图片描述

    #include<iostream>
    using namespace std;
    class Date
    {
    public:
    	//构造函数
    	Date(int year = 2020, int month = 5, int day = 5)
    	{
    		_year = year;
    		_month = month;
    		_day = day;
    		cout << "Date(int,int ,int):" << this << endl;
    		//看构造的是哪一个对象
    	}
    
    	//析构函数
    	~Date()
    	{
    		//对于日期类来说,这里面没有什么资源是需要去释放的
    		//所以对于日期类来说,给不给析构函数其实都没有什么影响
    		cout << "~Date():" << this << endl;
    		//看析构的是哪一个对象
    	}
    private:
    	int _year;
    	int _month;
    	int _day;
    };
    void TestDate()
    {
    	Date d1(2020, 5, 5);
    	Date d2(d1);
    }
    int main()
    {
    	TestDate();
    	return 0;
    }
    
    • 这个代码我们没有显示的声明拷贝构造函数,但是d2对象仍然创建成功了,d2的内容和d1的内容是一摸一样的,同时,代码并没有什么问题,也没有崩溃,但是有些时候,编译器自动声明的拷贝构造函数是有问题的,比如针对string类来说
    那么编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,我们还需要自己实现吗?当然像日期类这样的类是没必要的。那么下面的类呢?验证一下试试?

    在这里插入图片描述

    • 也就是说s1和s2其实指向的是同一块堆内存空间,但是这么看来,是存在着很大的问题的,问题在于:s1和s2是栈上面的两个对象,这两个对象指向的是同一块地址空间,那么当在进行资源释放的时候,会先去释放s2,然后再去释放s1,那么在释放s2的时候,这一块空间肯定就会被释放掉,但是,在s2释放掉资源的时候,s1是不直到这块空间已经被释放掉了,s1仍然会去释放这块空间,那么这个时候,就会引起代码的崩溃
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
    #include<iostream>
    #include<stdlib.h>
    #include<string.h>
    using namespace std;
    class String
    {
    public:
    	String(const char* str = "")
    	{
    		cout << "String(const char* ):" << this << endl;
    		if (nullptr == str)
    			str = "";
    
    		_str = (char*)malloc(strlen(str) + 1);
    		strcpy(_str, str);
    	}
    
    	// 拷贝构造函数必须显式提供
    
    	~String()
    	{
    		cout << "~String():" << this << endl;
    		free(_str);
    		_str = nullptr;
    	}
    
    private:
    	char* _str;
    };
    
    void TestString()
    {
        String s1("hello");
        String s2(s1);
    }
    
    int main()
    {
    	TestString();
    	return 0;
    }
    
    • 上面写的这个代码是有问题的代码,因为用s1去拷贝构造s2的话,通过监视看,s1和s2公用的是用一块内存空间,也就是说两个变量的地址是一样的,又因为先构造的后析构,后构造的析构,所以在释放空间的时候,是需要先去析构s2的,那么当s2析构完成了之后,那么s1就相当于是野指针了,再去析构s1的话,就会出现问题,从而代码崩溃(因为编译器所提供的默认的拷贝构造函数,是把s1中的内容原封不动的拷贝到s2中去的,当然包括s1中指针的地址,所以两个变量公用同一块堆上的内容)
      在这里插入图片描述
    展开全文
  • 可见,拷贝构造函数是一种特殊的构造函数,函数的名称必须和类名称一致,它的唯一的一参数是本类型的一引用变量,该参数是const类型,不可变的。例如:类X的拷贝构造函数的形式为X(X& x)。...

    1.什么是拷贝构造函数
    CA(const CA& C)就是我们自定义的拷贝构造函数。可见,拷贝构造函数是一种特殊的构造函数,函数的名称必须和类名称一致,它的唯一的一个参数是本类型的一个引用变量,该参数是const类型,不可变的。例如:类X的拷贝构造函数的形式为X(X& x)。
    当用一个已初始化过了的自定义类类型对象去初始化另一个新构造的对象的时候,拷贝构造函数就会被自动调用。也就是说,当类的对象需要拷贝时,拷贝构造函数将会被调用。以下情况都会调用拷贝构造函数:
    ① 程序中需要新建立一个对象,并用另一个同类的对象对它初始化,如前面介绍的那样。
    ② 当函数的参数为类的对象时。在调用函数时需要将实参对象完整地传递给形参,也就是需要建立一个实参的拷贝,这就是按实参复制一个形参,系统是通过调用复制构造函数来实现的,这样能保证形参具有和实参完全相同的值。
    ③ 函数的返回值是类的对象。在函数调用完毕将返回值带回函数调用处时。此时需要将函数中的对象复制一个临时对象并传给该函数的调用处。如
    Box f( ) //函数f的类型为Box类类型
    {Box box1(12,15,18);
    return box1; //返回值是Box类的对象
    }
    int main( )
    {Box box2; //定义Box类的对象box2
    box2=f( ); //调用f函数,返回Box类的临时对象,并将它赋值给box2
    }
    如果在类中没有显式地声明一个拷贝构造函数,那么,编译器将会自动生成一个默认的拷贝构造函数,该构造函数完成对象之间的位拷贝。位拷贝又称浅拷贝,后面将进行说明。
      自定义拷贝构造函数是一种良好的编程风格,它可以阻止编译器形成默认的拷贝构造函数,提高源码效率。
    浅拷贝和深拷贝
      在某些状况下,类内成员变量需要动态开辟堆内存,如果实行位拷贝,也就是把对象里的值完全复制给另一个对象,如A=B。这时,如果B中有一个成员变量指针已经申请了内存,那A中的那个成员变量也指向同一块内存。这就出现了问题:当B把内存释放了(如:析构),这时A内的指针就是野指针了,出现运行错误。
      深拷贝和浅拷贝可以简单理解为:如果一个类拥有资源,当这个类的对象发生复制过程的时候,资源重新分配,这个过程就是深拷贝,反之,没有重新分配资源,就是浅拷贝。
    2.C++拷贝构造函数的几个细节

    1) 以下函数哪个是拷贝构造函数,为什么?
    1.X::X( const X&);
    2.X::X(X);
    3.X::X(X&, int a=1);
    4.X::X(X&, int a=1, b=2);
    解答:1) 对于一个类X,如果一个构造函数的第一个参数是下列之一:
    a) X&
    b) const X&
    c) volatile X&
    d) const volatile X&
    且没有其他参数或其他参数都有默认值,那么这个函数是拷贝构造函数.
    1.X::X( const X&); //是拷贝构造函数
    2.X::X(X&, int =1); //是拷贝构造函数

    2) 一个类中可以存在多于一个的拷贝构造函数吗?
    解答:类中可以存在超过一个拷贝构造函数,
    1.class X {
    2.public :
    3. X( const X&);
    4. X(X&); // OK
    5.};
    注意,如果一个类中只存在一个参数为X&的拷贝构造函数,那么就不能使用const X或volatile X的对象实行拷贝初始化.
    1.class X {
    2.public :
    3. X();
    4. X(X&);
    5.};
    6.
    7.const X cx;
    8.X x = cx; // error
    如果一个类中没有定义拷贝构造函数,那么编译器会自动产生一个默认的拷贝构造函数.
    这个默认的参数可能为X::X(const X&)或X::X(X&),由编译器根据上下文决定选择哪一个.
    默认拷贝构造函数的行为如下:
    默认的拷贝构造函数执行的顺序与其他用户定义的构造函数相同,执行先父类后子类的构造.
    拷贝构造函数对类中每一个数据成员执行成员拷贝(memberwise Copy)的动作.
    a)如果数据成员为某一个类的实例,那么调用此类的拷贝构造函数.
    b)如果数据成员是一个数组,对数组的每一个执行按位拷贝.
    c)如果数据成员是一个数量,如int,double,那么调用系统内建的赋值运算符对其进行赋值.
    3) 拷贝构造函数不能由成员函数模版生成.
    struct X {
    template < typename T>
    X( const T& ); // NOT copy ctor, T can't be X
    template < typename T>
    operator=( const T& ); // NOT copy ass't, T can't be X
    };
    原因很简单, 成员函数模版并不改变语言的规则,而语言的规则说,如果程序需要一个拷贝构造函数而你没有声明它,那么编译器会为你自动生成一个.所以成员函数模版并不会阻止编译器生成拷贝构造函数, 赋值运算符重载也遵循同样的规则
    3.拷贝构造函数与赋值函数的异同:
    1) 拷贝构造,是一个的对象来初始化一片内存区域,这片内存区域就是你的新对象的内存区域赋值运算,对于一个已经被初始化的对象来进行operator=操作
    class A;
    A a;
    A b=a; //拷贝构造函数调用
    //或
    A b(a); //拷贝构造函数调用
    ///
    A a;
    A b;
    b =a; //赋值运算符调用
    你只需要记住,在C++语言里,
    String s2(s1);
    String s3 = s1;
    只是语法形式的不同,意义是一样的,都是定义加初始化,都调用拷贝构造函数。
    2) 一般来说是在数据成员包含指针对象的时候,应付两种不同的处理需求的 一种是复制指针对象,一种是引用指针对象 copy大多数情况下是复制,=则是引用对象的
    例子:
    class A
    {
    int nLen;
    char * pData;
    }
    显然
    A a, b;
    a=b的时候,对于pData数据存在两种需求
    第一种copy
    a.pData = new char [nLen];
    memcpy(a.pData, b.pData, nLen);
    另外一种(引用方式):
    a.pData = b.pData

    通过对比就可以看到,他们是不同的
    往往把第一种用copy使用,第二种用=实现
    你只要记住拷贝构造函数是用于类中指针,对象间的COPY
    3) 拷贝构造函数首先是一个构造函数,它调用的时候产生一个对象,是通过参数传进来的那个对象来初始化,产生的对象。
    operator=();是把一个对象赋值给一个原有的对象,所以如果原来的对象中有内存分配要先把内存释放掉,而且还要检查一下两个对象是不是同一个对象,如果是的话就不做任何操作。
    还要注意的是拷贝构造函数是构造函数,不返回值
    而赋值函数需要返回一个对象自身的引用,以便赋值之后的操作
    4) 在形式上
    类名(形参表列); //普通构造函数的声明,如Box(int h,int w,int len);
    类名(类名& 对象名); //复制构造函数的声明,如Box(Box &b);
    5) 在建立对象时,实参类型不同。系统会根据实参的类型决定调用普通构造函数或复制构造函数。如:
    Box box1(12,15,16); //实参为整数,调用普通构造函数
    Box box2(box1); //实参是对象名,调用复制构造函数

    拷贝构造函数,是一种特殊的构造函数,它由编译器调用来完成一些基于同一类的其他对象的构建及初始化。其唯一的参数(对象的引用)是不可变的(const类型)。此函数经常用在函数调用时用户定义类型的值传递及返回。拷贝构造函数要调用基类的拷贝构造函数和成员函数。

    在C++中,下面三种对象需要调用拷贝构造函数:   

    1) 一个对象以值传递的方式传入函数体;   

    2) 一个对象以值传递的方式从函数返回;   

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

    如果在前两种情况不使用拷贝构造函数的时候,就会导致一个指针指向已经被删除的内存空间。对于第三种情况来说,初始化和赋值的不同含义是构造函数调用的原因。事实上,拷贝构造函数是由普通构造函数和赋值操作符共同实现的。描述拷贝构造函数和赋值运算符的异同的参考资料有很多。   

    拷贝构造函数不可以改变它所引用的对象,其原因如下:当一个对象以传递值的方式传一个函数的时候,拷贝构造函数自动的被调用来生成函数中的对象。如果一个对象是被传入自己的拷贝构造函数,它的拷贝构造函数将会被调用来拷贝这个对象这样复制才可以传入它自己的拷贝构造函数,这会导致无限循环直至栈溢出(Stack Overflow)。除了当对象传入函数的时候被隐式调用以外,拷贝构造函数在对象被函数返回的时候也同样的被调用。

    展开全文
  • 类的构造函数 类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。 构造函数的名称与类的名称是完全相同的,并且不会返回任何类型,也不会返回 void。构造函数可用于为某些成员变量设置初始值...

    类的构造函数

    类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。

    构造函数的名称与类的名称是完全相同的,并且不会返回任何类型,也不会返回 void。构造函数可用于为某些成员变量设置初始值

    下面的实例有助于更好地理解构造函数的概念:
    实例

    #include <iostream>
     
    using namespace std;
     
    class Line
    {
       public:
          void setLength( double len );
          double getLength( void );
          Line();  // 这是构造函数
     
       private:
          double length;
    };
     
    // 成员函数定义,包括构造函数
    Line::Line(void)
    {
        cout << "Object is being created" << endl;
    }
     
    void Line::setLength( double len )
    {
        length = len;
    }
     
    double Line::getLength( void )
    {
        return length;
    }
    // 程序的主函数
    int main( )
    {
       Line line;
     
       // 设置长度
       line.setLength(6.0); 
       cout << "Length of line : " << line.getLength() <<endl;
     
       return 0;
    }
    

    当上面的代码被编译和执行时,它会产生下列结果:

    Object is being created
    Length of line : 6
    

    带参数的构造函数

    默认的构造函数没有任何参数,但如果需要,构造函数也可以带有参数。这样在创建对象时就会给对象赋初始值,如下面的例子所示:
    实例

    #include <iostream>
     
    using namespace std;
     
    class Line
    {
       public:
          void setLength( double len );
          double getLength( void );
          Line(double len);  // 这是构造函数
     
       private:
          double length;
    };
     
    // 成员函数定义,包括构造函数
    Line::Line( double len)
    {
        cout << "Object is being created, length = " << len << endl;
        length = len;
    }
     
    void Line::setLength( double len )
    {
        length = len;
    }
     
    double Line::getLength( void )
    {
        return length;
    }
    // 程序的主函数
    int main( )
    {
       Line line(10.0);
     
       // 获取默认设置的长度
       cout << "Length of line : " << line.getLength() <<endl;
       // 再次设置长度
       line.setLength(6.0); 
       cout << "Length of line : " << line.getLength() <<endl;
     
       return 0;
    }
    

    当上面的代码被编译和执行时,它会产生下列结果:

    Object is being created, length = 10
    Length of line : 10
    Length of line : 6
    

    使用初始化列表来初始化字段

    使用初始化列表来初始化字段:

    Line::Line( double len): length(len)
    {
        cout << "Object is being created, length = " << len << endl;
    }
    

    上面的语法等同于如下语法:

    Line::Line( double len)
    {
        length = len;
        cout << "Object is being created, length = " << len << endl;
    }
    

    假设有一个类C,具有多个字段XYZ等需要进行初始化,同理地,您可以使用上面的语法,只需要在不同的字段使用逗号进行分隔,如下所示:

    C::C( double a, double b, double c): X(a), Y(b), Z(c)
    {
      ....
    }
    

    类的析构函数

    类的析构函数是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行。

    析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数。析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。
    下面的实例有助于更好地理解析构函数的概念:
    实例

    #include <iostream>
     
    using namespace std;
     
    class Line
    {
       public:
          void setLength( double len );
          double getLength( void );
          Line();   // 这是构造函数声明
          ~Line();  // 这是析构函数声明
     
       private:
          double length;
    };
     
    // 成员函数定义,包括构造函数
    Line::Line(void)
    {
        cout << "Object is being created" << endl;
    }
    Line::~Line(void)
    {
        cout << "Object is being deleted" << endl;
    }
     
    void Line::setLength( double len )
    {
        length = len;
    }
     
    double Line::getLength( void )
    {
        return length;
    }
    // 程序的主函数
    int main( )
    {
       Line line;
     
       // 设置长度
       line.setLength(6.0); 
       cout << "Length of line : " << line.getLength() <<endl;
     
       return 0;
    }
    

    当上面的代码被编译和执行时,它会产生下列结果:

    Object is being created
    Length of line : 6
    Object is being deleted
    

    构造函数应用实例:

    #include<iostream>
    #include<string>
    using namespace std;
    
    class Student
    {
    public:
        string name;
        string number;
        char X;
        int year;
        Student(string,string,char,int);      //构造函数声明
        void xianshi(void);     //用于输出类成员的值
    };
    
    //成员函数定义,包括构造函数
    Student::Student(string N,string n,char x,int y)    //利用构造函数给类的成员赋值
    {
        name = N;
        number = n;
        X = x;
        year = y;
    }
    
    void Student::xianshi()     //输出成员的值
    {
        cout<<name<<endl;
        cout<<number<<endl;
        cout<<X<<endl;
        cout<<year<<endl;
    }
    
    int main()                             //主函数
    {
        cout<<"输入姓名:";
        string N;
        cin>>N;
        cout<<"输入学号:";
        string n;
        cin>>n;
        cout<<"输入性别(M 或 W):";
        char x;
        cin>>x;
        cout<<"输入年龄:";
        int y;
        cin>>y;
        Student S(N,n,x,y);               //定义对象并对构造函数赋值
        S.xianshi();                           //引用输出函数
        return 0;
    }
    
    初始化列表的成员初始化顺序:

    C++ 初始化类成员时,是按照声明的顺序初始化的,而不是按照出现在初始化列表中的顺序。

    class CMyClass {
        CMyClass(int x, int y);
        int m_x;
        int m_y;
    };
    
    CMyClass::CMyClass(int x, int y) : m_y(y), m_x(m_y)
    {
    };
    

    你可能以为上面的代码将会首先做 m_y=I,然后做 m_x=m_y,最后它们有相同的值。但是编译器先初始化 m_x,然后是m_y,因为它们是按这样的顺序声明的。结果是 m_x 将有一个不可预测的值。有两种方法避免它,一个是总是按照你希望它们被初始化的顺序声明成员,第二个是,如果你决定使用初始化列表,总是按照它们声明的顺序罗列这些成员。这将有助于消除混淆。
    初始化顺序最好要按照变量在类声明的顺序一致,否则会出现下面的特殊情况:

    #include<iostream>
     
    using namespace std;
    
    class Student1 {
        public:
            int a;
            int b;
            void fprint(){
                cout<<" a = "<<a<<" "<<"b = "<<b<<endl;
             }
             
             Student1(int i):b(i),a(b){ }    //异常顺序:发现a的值为0  b的值为2  说明初始化仅仅对b有效果,对a没有起到初始化作用 
    //         Student1(int i):a(i),b(a){ } //正常顺序:发现a = b = 2 说明两个变量都是初始化了的  
    
             Student1()                         // 无参构造函数
            { 
                cout << "默认构造函数Student1" << endl ;
            }
        
            Student1(const Student1& t1) // 拷贝构造函数
            {
                cout << "拷贝构造函数Student1" << endl ;
                this->a = t1.a ;
            }
        
            Student1& operator = (const Student1& t1) // 赋值运算符
            {
                cout << "赋值函数Student1" << endl ;
                this->a = t1.a ;
                return *this;
            }
          
     };
    class Student2
    {
        public:
         
        Student1 test ;
        Student2(Student1 &t1){
            test  = t1 ;
        }
    //     Student2(Student1 &t1):test(t1){}
    };
    int main()
    {
        
        Student1 A(2);        //进入默认构造函数 
        Student2 B(A);        //进入拷贝构造函数 
        A.fprint();            //输出前面初始化的结果 
    }
    

    两种不同的初始化方法结果如下:

    异常初始化顺序:
    在这里插入图片描述
    正常初始化顺序:
    在这里插入图片描述

    由上面的例子可知,初始化列表的顺序要跟你在类声明的顺序要一致。否则像上面的那种特殊情况,有些变量就不会被初始化。经过测试发现,类中变量为下面的情况也是能够正常初始化的:也就是说,只要成员变量的初始化不依赖其他成员变量,即使顺序不同也能正确的初始化。

    int a;
    int b;
    int c;
    Student1(int i):b(i),a(i),c(i){} 
    main:
        Student1 A(2);    
        A.fprint();
    

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

    int a;
    int b;
    int c;
    Student1(int i,int j,int k):b(i),a(j),c(k){}
    main:
        Student1 A(2,3,4);
        A.fprint();
    

    在这里插入图片描述

    C++ 拷贝构造函数

    拷贝构造函数是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。拷贝构造函数通常用于:

    • 通过使用另一个同类型的对象来初始化新创建的对象。
    • 复制对象把它作为参数传递给函数。
    • 复制对象,并从函数返回这个对象。

    如果在类中没有定义拷贝构造函数,编译器会自行定义一个。如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数。拷贝构造函数的最常见形式如下:

    classname (const classname &obj) {
       // 构造函数的主体
    }
    

    在这里,obj是一个对象引用,该对象是用于初始化另一个对象的。
    实例

    #include <iostream>
     
    using namespace std;
     
    class Line
    {
       public:
          int getLength( void );
          Line( int len );             // 简单的构造函数
          Line( const Line &obj);      // 拷贝构造函数
          ~Line();                     // 析构函数
     
       private:
          int *ptr;
    };
     
    // 成员函数定义,包括构造函数
    Line::Line(int len)
    {
        cout << "调用构造函数" << endl;
        // 为指针分配内存
        ptr = new int;
        *ptr = len;
    }
     
    Line::Line(const Line &obj)
    {
        cout << "调用拷贝构造函数并为指针 ptr 分配内存" << endl;
        ptr = new int;
        *ptr = *obj.ptr; // 拷贝值
    }
     
    Line::~Line(void)
    {
        cout << "释放内存" << endl;
        delete ptr;
    }
    int Line::getLength( void )
    {
        return *ptr;
    }
     
    void display(Line obj)
    {
       cout << "line 大小 : " << obj.getLength() <<endl;
    }
     
    // 程序的主函数
    int main( )
    {
       Line line(10);
     
       display(line);
     
       return 0;
    }
    

    当上面的代码被编译和执行时,它会产生下列结果:

    调用构造函数
    调用拷贝构造函数并为指针 ptr 分配内存
    line 大小 : 10
    释放内存
    释放内存
    

    下面的实例对上面的实例稍作修改,通过使用已有的同类型的对象来初始化新创建的对象:
    实例

    #include <iostream>
     
    using namespace std;
     
    class Line
    {
       public:
          int getLength( void );
          Line( int len );             // 简单的构造函数
          Line( const Line &obj);      // 拷贝构造函数
          ~Line();                     // 析构函数
     
       private:
          int *ptr;
    };
     
    // 成员函数定义,包括构造函数
    Line::Line(int len)
    {
        cout << "调用构造函数" << endl;
        // 为指针分配内存
        ptr = new int;
        *ptr = len;
    }
     
    Line::Line(const Line &obj)
    {
        cout << "调用拷贝构造函数并为指针 ptr 分配内存" << endl;
        ptr = new int;
        *ptr = *obj.ptr; // 拷贝值
    }
     
    Line::~Line(void)
    {
        cout << "释放内存" << endl;
        delete ptr;
    }
    int Line::getLength( void )
    {
        return *ptr;
    }
     
    void display(Line obj)
    {
       cout << "line 大小 : " << obj.getLength() <<endl;
    }
     
    // 程序的主函数
    int main( )
    {
       Line line1(10);
     
       Line line2 = line1; // 这里也调用了拷贝构造函数
     
       display(line1);
       display(line2);
     
       return 0;
    }
    

    当上面的代码被编译和执行时,它会产生下列结果:

    调用构造函数
    调用拷贝构造函数并为指针 ptr 分配内存
    调用拷贝构造函数并为指针 ptr 分配内存
    line 大小 : 10
    释放内存
    调用拷贝构造函数并为指针 ptr 分配内存
    line 大小 : 10
    释放内存
    释放内存
    释放内存
    

    拷贝构造函数的调用时机
    在C++中,下面三种对象需要调用拷贝构造函数!

    1. 对象以值传递的方式传入函数参数
    class CExample 
    {
    private:
     int a;
    
    public:
     //构造函数
     CExample(int b)
     { 
      a = b;
      cout<<"creat: "<<a<<endl;
     }
    
     //拷贝构造
     CExample(const CExample& C)
     {
      a = C.a;
      cout<<"copy"<<endl;
     }
     
     //析构函数
     ~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;
    }
    

    调用g_Fun()时,会产生以下几个重要步骤:

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

    拷贝构造函数
    几个原则:

    C++ primer p406 :拷贝构造函数是一种特殊的构造函数,具有单个形参,该形参(常用const修饰)是对该类类型的引用。
    当定义一个新对象并用一个同类型的对象对它进行初始化时,将显式使用拷贝构造函数。
    当该类型的对象传递给函数或从函数返回该类型的对象时,将隐式调用拷贝构造函数。

    C++支持两种初始化形式:

    拷贝初始化 int a = 5; 和直接初始化 int a(5); 对于其他类型没有什么区别,对于类类型直接初始化直接调用实参匹配的构造函数,拷贝初始化总是调用拷贝构造函数,也就是说:

    A x(2);  //直接初始化,调用构造函数
    A y = x;  //拷贝初始化,调用拷贝构造函数
    
    必须定义拷贝构造函数的情况:

    只包含类类型成员内置类型(但不是指针类型)成员的类,无须显式地定义拷贝构造函数也可以拷贝;有的类有一个数据成员是指针,或者是有成员表示在构造函数中分配的其他资源,这两种情况下都必须定义拷贝构造函数。

    什么情况调用拷贝构造函数:

    类的对象需要拷贝时,拷贝构造函数将会被调用。以下情况都会调用拷贝构造函数:
    (1)一个对象以传递的方式传入函数体
    (2)一个对象以传递的方式从函数返回
    (3)一个对象需要通过另外一个对象进行初始化

    展开全文
  • C++拷贝构造函数、构造函数和析构函数

    万次阅读 多人点赞 2018-08-30 22:09:15
    一、拷贝构造函数 转载自:http://www.cnblogs.com/BlueTzar/articles/1223313.html 1、类对象的拷贝  对于普通类型的对象来说,它们之间的复制是很简单的,例如: int a=88; int b=a;   而类对象与普通...
  • 这里我们用类String 来介绍这两函数: ...试想一下,假如形参是该类的一实例,由于是传值参数,我们把形参复制到实参会调用拷贝构造函数,如果允许拷贝构造函数传值,就会在拷贝构造函数内调用拷贝构...
  • 构造函数是一特殊的成员函数,名字与类名相同,编译器创建类类型对象时会自动调用构造函数,保证每数据成员都有 一合适的初始值,并且在对象的生命周期内只调用一次。主要任务是初始化对象,而不是开空间创建...
  • 子类继承父类后,当创建子类对象,也会调用父类的构造函数 问题:父类和子类的构造和析构顺序是谁先谁后? ```cpp #include<iostream> #include<string> using namespace std; class base { public: ...
  • 原文链接:构造函数、析构函数、拷贝构造函数 一、构造函数 概念(作用): 用来在创建对象时,对对象进行初始化 特点 构造函数无返回值,函数名与类名相同,必须存在于public中 构造函数可以重载 不用来初始...
  • 写得很全面,例子也通俗易懂。** 这里我们用类String 来介绍这两函数: 拷贝构造函数是一种特殊构造函数,具有...当定义一新对象并用一同类型的对象对它进行初始化时,将显式使用拷贝构造函数。为啥形参...
  • C++拷贝构造函数,析构函数与内存泄漏的那些坑拷贝构造函数拷贝初始化为什么拷贝构造函数的参数必须是引用类型拷贝赋值元素符析构函数不完整定义拷贝控制成员可能带来的问题阻止拷贝 拷贝构造函数 如果一构造函数...
  • 其实一类在我们不写的情况下,也会生成6默认的成员函数,分别是:构造函数,析构函数,拷贝构造函数,赋值运算符重载,取地址运算符重载,对const对象取地址运算符的重载 构造函数 构造函数是特殊的成员函数,...
  • C++、构造函数与拷贝构造函数

    千次阅读 2022-04-08 17:11:59
    构造函数基本概念。
  • 类中6默认函数,如果在定义类时没有定义这6函数的某... 拷贝构造函数 赋值运算符重载函数 取地址操作符重载函数 const修饰的取地址操作符重载函数   构造函数  1.构造函数与类名相同,并且没有返回值  2...
  • 类的继承、拷贝构造函数

    千次阅读 2016-04-23 22:23:29
    上一节讲到了继承的基本用法,这一节主要讲述继承的基本用法以及拷贝构造函数。 1.继承 class Tutorial{ }; class vedioTutorial:public Tutorial{ }; 注:继承时,私有成员(private)不能被...
  • 文章目录函数的定义函数的的使用方法函数的返回值值传递指针传递引用传递C++引用作为函数返回值函数重载(Function Overloading)运算符重载(Operator Overloading)以成员函数的形式重载构造函数拷贝构造函数内联...
  • 我是柠檬叶子C,本章将继续讲解C++中的面向对象的知识点,本篇主要讲解默认成员函数中的构造函数、析构函数和拷贝构造函数。还是和以前一样,我们将由浅入深地去讲解,以 "初学者" 的角度去探索式地学习。会一步步地...
  • 类的那些函数~~~
  • 拷贝构造函数和移动构造函数定义了 当用同类型的另一对象初始化正在创建的对象时需要做什么; 拷贝赋值运算符和移动赋值运算符定义了 将一对象赋给另一同类型的对象时需要做什么; 析构函数定义了 当类对象...
  • 复制构造函数介绍浅拷贝拷贝 介绍 复制构造函数用于将一对象复制到新对象中。它用于初始化过程中(包括按值传递函数),而不是常规的赋值过程中,类型如下: Class_name(const Class_name &); 它接受一...
  • 拷贝构造函数是C++最基础的概念之一,大家自认为对拷贝构造函数了解么?请大家先回答一下三问题: 1. 以下函数哪个是拷贝构造函数,为什么?... 一类中可以存在多于一拷贝构造函数吗? 3
  • 那么什么时候编译器会为一 class 生成一 default copy constructor 呢? 和 default constructor 一样,C++ Standard 上说,如果 class 没有声明一 copy constructor,就会有隐含的声明(implicitly declared)...
  • 不幸的是,即使是这样朴实无华的声明也隐含了一个微妙的错误,呵,我们来看看:当某个时候需要以一个Sample对象的值来为一个新对象进行初始化时,编译器会在各个重载的构造函数版本(如果有多个的话)搜寻,它找到的这个...
  • 对象是由“底层向上”开始构造的,当建立一对象时,首先调用基类的构造函数,然后调用下一派生类的构造函数,依次类推,直至到达派生类次数最多的派生次数最多的类的构造函数为止。因为,构造函数一开始构造时,...
  • 构造函数 ,是一种特殊的方法 。主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值,总与new...特别的一个类可以有多个构造函数 ,可根据其参数个数的不同或参数类型的不同来区分它们 即构造函数的重载。
  • C++入门之一【类:构造函数(包含拷贝构造函数)】
  • 类的几默认构造函数

    千次阅读 2021-04-18 02:00:49
    1.构造函数什么是构造函数——构造函数即用来给对象初始化的函数。函数名字与类名相同。构造函数的特性——(1)没有返回值类型(2)在创建对象时由编译器自动调用,且在对象的整个生命周期只被调用一次。(3)构造函数...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 92,563
精华内容 37,025
关键字:

多个拷贝构造函数拷贝顺序