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 ℞ //声明为引用时则必须在构造函数基/成员初始值设定项列表中初始化
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) {
}