已经有个构造函数负责初始化,为什么还需要构造函数初始化表呢?
在以下三种情况下需要使用初始化成员列表:
- 需要初始化的数据成员是对象的情况;
- 需要初始化const修饰的类成员;
- 需要初始化引用成员数据;
引用类型的成员变量必须在构造函数的初始化列表中进行初始化。对于类成员是const修饰,或是引用类型的情况,是不允许赋值操作的,(显然嘛,const就是防止被错误赋值的,引用类型必须定义赋值在一起)因此只能用初始化列表对齐进行初始化。
需要初始化的数据成员是对象的情况
#include <iostream>
using namespace std;
class point
{
protected:
int m_x, m_y;
public:
point(int m = 0, int n = 0)
{
m_x = m;
m_y = n;
printf("constructor called!\n");
}
point(point& p)
{
m_x = p.GetX();
m_y = p.GetY();
printf("copy constructor called!\n");
}
int GetX()
{
return m_x;
}
int GetY()
{
return m_y;
}
};
class point3d
{
private:
point m_p;
int m_z;
public:
point3d(point p, int k)
{
m_p = p; //这里是对m_p的赋值
m_z = k;
}
point3d(int i, int j, int k) :m_p(i, j) // 相当于 point m_p(i,j)这样对m_p初始化
{
m_z = k;
}
void Print()
{
printf("%d,%d,%d \n", m_p.GetX(), m_p.GetY(), m_z);
}
};
int main()
{
point p(1, 2); //先定义一个2D坐标
point3d p3d(p, 3);
p3d.Print();
}
我们可以看出来先调用了构造函数构造对象p,如何调用拷贝构造函数赋值,在调用拷贝构造函数构造对象p3d。
//而如果使用成员初始化列表,我们则可以这样:
int main()
{
point p(1, 2);
point3d p3d(1, 2, 3);
p3d.Print();
}
//p3d中的point型成员是通过调用初始化的方式构建的。由于对象赋值比初始化要麻烦的多,因此也带来的性能上的消耗。
//这也是我们在对成员数据是对象成员的采用初始化列表进行初始始化的主要原因。
需要初始化const修饰的类成员
#include <iostream>
using namespace std;
class base
{
public:
const int a;
int& b;
public:
base(int m, int n) :a(m), b(n)
{}
};
int main()
{
base ba(1, 2);
cout << ba.a << endl;
cout << ba.b << endl;
}
需要初始化引用成员数据
#include <iostream>
using namespace std;
class Test
{
private:
int &a;
public:
Test(int &b) : a(b)
{
}
void Modify(int value)
{
a = value;
}
};
int main()
{
int b = 3;
Test test(b);
cout << "b=" << b << endl;
test.Modify(4);
cout << "b=" << b << endl;
return 0;
}
C++必须用带有初始化列表的情况:
(1)成员类型是没有默认构造函数的类。若没有提供显示初始化式,则编译器隐式使用成员类型的默认构造函数,若类没有默认构造函数,则编译器尝试使用默认构造函数将会失败。
(2)const成员或引用类型的成员。因为const对象或引用类型只能初始化,不能对他们赋值。
我们定义一个如下的Person类:
class Person {
public:
Person() { } //default constructor function
Person(string name, string phone, string addr)
{
m_name = name; //想采用赋值初始化数据成员
m_phone = phone;
m_addr = addr;
}
private:
const string m_name;
const string m_phone;
const string m_addr;
};
编译后发现这个类的第二个带参数的构造函数是错误的。我们创建一个Person对象:
Person p(“marcky”, “13233232”, “cqupt”); //调用带参数的构造函数创建一个Person对象 创建对象的过程分为了两步:
一、从内存中分配实际的空间给对象p,其三个字符串对象的数据成员是调用的默认构造函数初始化为空。也就说,此时为止,对象p的三个数据成员都是一个空的字符串。
二、执行调用的构造函数的函数体语句,完成对数据成员的赋值,以此达到我们期望的创建一个指定Person对象,而不是空对象。
从上面的第二步就可以看到,我们在对三个const对象进行赋值操作,这显然是不允许的操作,因此利用这个构造函数创建Person将以失败告终。要想成功的创建一个特定的Person对象,我们需要构造函数初始化列表:
Person(string name, string phone, string addr)
:m_name(name), m_phone(phone), m_addr(addr){ } //冒号开始定义初始化列表
使用初始化列表创建对象的构造函数同样是通过上述的两个步骤来完成的,不同之处在于创建对象的数据成员时使用的不是默认构造函数,而是根据指定参数调用了相应的构造函数,以此创建特定的对象,而不是空对象。这样一来,对象的数据成员的特定值在创建对象的时候就被赋予了相应的成员,而不是在创建对象完成之后再通过赋值语句去修改数据成员,因此利用构造函数初始化列表就可以成功的创建具有const数据成员的对对象了。
除了const的数据成员外,没有默认构造函数的类类型或者是引用类型的成员,都必须在构造函数的初始化列表中进行初始化。
没有默认构造函数的类类型成员,如果不在初始化列表中初始化的话,那么创建该对象的时候,由于没有指定相应的“实参”,编译器就会去调用默认构造函数来创建对象,必然会以失败而告终。
引用类型的成员和const类型成员一样,因为引用必须初始化,初始化后就不能修改,所以后期通过赋值来修改其值是错误的。
ps:数据成员被初始化的顺序与构造函数初始化列表中的次序无关,而是与成员的定义顺序一致。
那么对于下面这个例子,我们就知道是什么情况了。
class Test
{
public:
Test(int data = 100) :mb(data), ma(mb) { }
void show() { cout << "ma:" << ma << "mb:" << mb << endl; }
private:
int ma;
int mb;
};
int main()
{
Test t;
t.show();
return 0;
}