C++中默认构造函数和构造函数初始化列表

1、默认构造函数和构造函数

(1)构造函数:C++用于构建类的新对象时需要调用的函数,该函数无返回类型!(注意:是“无”! 不是空!(void))。

(2)默认构造函数默认构造函数是在调用时不需要显示地传入实参的构造函数。

一个类如果自己没有定义构造函数,则会有一个无参且函数体也是空的默认构造函数。只要程序员定义了构造函数,编译器就不会再提供默认构造函数了。

定义默认构造函数有两种方式,一是定义一个无参的构造函数,二是定义所有参数都有默认值的构造函数

class testClass
{
public:
    testClass();                         /* 默认构造函数 */
    testClass(int a, char b);        	 /* 构造函数 */
    testClass(int a=10,char b='c');      /* 默认构造函数 */

private:
    int  m_a;
    char m_b;
};

(3)编译器在什么情况下会生成默认构造函数?

下面几种情况下,编译需要生成默认构造函数:

  • 当该类的类对象数据成员有默认构造函数时。
  • 当该类的基类有默认构造函数时。
  • 当该类的基类为虚基类时。
  • 当该类有虚函数时。

(4)避免“无参数的默认构造函数”和“带缺省参数的默认构造函数”同时存在

无参数的默认构造函数和带缺省参数的默认构造函数同时存在时,编译器会产生二义性,从而生成编译错误

class Sample {
public:
    // 默认构造函数
    Sample() {
        // do something
        printf("Sample()");
    }

    // 默认构造函数
    Sample(int m = 10) {
        // do something
        printf("Sample(int m = 10)");
    }
};


int main()
{
    Sample s; // error C2668: “Sample::Sample”: 对重载函数的调用不明确

    return 0;
}

2、构造函数初始列表

C++类中成员函数一般有两种初始化,一种是在类的构造函数内部初始化,另一种是利用类的构造函数的初始化列表进行初始化。初始化和赋值对内置类型的成员没有什么大的区别。对非内置类型成员变量,为了避免两次构造,推荐使用类构造函数初始化列表。

         初始化数据成员与对数据成员赋值的含义是什么?有什么区别?

首先把数据成员按类型分类并分情况说明:

(1)内置数据类型,如int,float等;复合类型(指针,引用)

        在成员初始化列表和构造函数体内进行,在性能和结果上都是一样的

(2)用户定义类型(类类型)

        结果上相同,但是性能上存在很大的差别。因为类类型的数据成员对象在进入函数体前已经构造完成,也就是说在成员初始化列表处进行构造对象的工作,调用构造函数,在进入函数体之后,进行的是对已经构造好的类对象的赋值,又调用个拷贝赋值操作符才能完成(如果并未提供,则使用编译器提供的默认按成员赋值行为)。因此,能使用初始化列表的时候尽量使用初始化列表。

class Test2
{
public:
    Test1 test1 ;
    Test2(Test1 &t1):test1(t1){}    //调用拷贝构造函数初始化test1,省去了调用默认构造函数的过程
}

3、必须用带有初始化列表的构造函数:

(1)成员类型是没有默认构造函数的类。若没有提供显示初始化式,则编译器隐式使用成员类型的默认构造函数,若类没有默认构造函数,则编译器尝试使用默认构造函数将会失败。

class Test1
{
public:
    Test1(int a):i(a){}//没有默认构造函数
    int i;
};
class Test2
{
public:
    Test1 test1 ;
    Test2(Test1 &t1)
    {test1 = t1 ;}
};

以上代码无法通过编译,因为Test2的构造函数中test1 = t1这一行实际上分成两步执行:

调用Test1的默认构造函数来初始化test1

由于Test1没有默认的构造函数,所以1 无法执行,故而编译错误。正确的代码如下,使用初始化列表代替赋值操作

class Test1
{
public:
    Test1(int a):i(a){}        //没有默认构造函数
    int i;
};
class Test2
{
public:
    Test1 test1 ;
    Test2(int x):test1(x){}    //使用初始化列表
}

(2)const成员引用类型的成员。因为const对象或引用类型只能初始化,不能对他们赋值。

#include <iostream>

using namespace std;

class A
{
public:
#if 0
	//因为成员变量中含有引用和常量则不能如此定义该构造函数
	A(){
		cout<<"aaaaaaaa"<<endl;
	}
#endif
#if 1
	A(int x1 = 0):x(x1),rx(x),pi(3.14) { }//带有初始化列表的默认构造函数
	void print()
	{
		cout<<"x="<<x<<" rx="<<rx<<" pi="<<pi<<endl; 
	}
	~A(){}
private:
	int x;
	int &rx;		 //声明为引用时则必须在构造函数基/成员初始值设定项列表中初始化
	const double pi; //声明为const时则必须在构造函数基/成员初始值设定项列表中初始化
#endif
};

int main()
{
	A a(20);
	a.print();

	A a1;		//因为A有默认构造函数,则可以如此调用
	a1.print();

	system("pause");
	return 0;
}

注:如果一个构造函数为所以参数都提供默认实参,则它实际上也定义了默认构造函数

(3)基类未声明默认构造函数

#include <iostream>

using namespace std;

class Animal
{
public:
	Animal(int weight,int height):        //基类未声明默认构造函数
	m_weight(weight),m_height(height){
		cout<<"Animal "<<"weight="<<weight<<" height="<<height<<endl;
	}

private:
	int m_weight;
	int m_height;
};

class Dog: public Animal
{
public:
	Dog(int weight = 10,int height = 20,int type = 1):
	  Animal(weight,height){//必须使用初始化列表增加对父类的初始化,否则父类Animal无合适构造函数
		cout<<"Dog"<<endl;
	}
private:
	int m_type;
};
int main()
{
	Dog d;

	system("pause");
	return 0;
}

最简单的解决方法是将Animal的构造函数声明默认构造函数:Animal(int weight = 0,int height = 0);

4、初始化列表的成员初始化顺序:

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将有一个不可预测的值。有两种方法避免它,一个是总是按照你希望它们被初始化的顺序声明成员,第二个是,如果你决定使用初始化列表,总是按照它们声明的顺序罗列这些成员。这将有助于消除混淆。

        如果可以的话尽可能避免使用某些成员初始化其他成员。而最好用构造函数的参数作为成员的初始值,这样做的好处是可以比考虑成员的初始化顺序。例如以上可改为:

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

 

    原文作者:king_weng
    原文地址: https://blog.csdn.net/King_weng/article/details/84983925
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞