=================
1.简介
子类为完成基类初始化,在 C++11 之前,需要在初始化列表调用基类的构造函数,从而完成构造函数的传递。如果基类拥有多个构造函数,那么子类也需要实现多个与基类构造函数对应的构造函数。
class Base {
public:
Base(int v): _value(v), _c(‘0’){}
Base(char c): _value(0), _c(c){}
private:
int _value;
char _c;
};
class Derived: public Base {
public:
// 初始化基类需要透传参数至基类的各个构造函数,非常麻烦
Derived(int v) :Base(v) {}
Derived(char c) :Base(c) {}
// 假设派生类只是添加了一个普通的函数
void display() {
// dosomething
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
书写多个派生类构造函数只为传递参数完成基类初始化,这种方式无疑给开发人员带来麻烦,降低了编码效率。从 C++11 开始,推出了继承构造函数(Inheriting Constructor),使用 using 来声明继承基类的构造函数,我们可以这样书写。
class Base {
public:
Base(int v) :_value(v), _c('0'){}
Base(char c): _value(0), _c(c){}
private:
int _value;
char _c;
};
class Derived: public Base {
public:
// 使用继承构造函数
using Base::Base;
// 假设派生类只是添加了一个普通的函数
void display() {
// do something
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
我们通过 using Base::Base 把基类构造函数继承到派生类中,不再需要书写多个派生类构造函数来完成基类的初始化。
更为巧妙的是,C++11 标准规定,继承构造函数与类的一些默认函数(默认构造、析构、拷贝构造函数等)一样,是隐式声明,如果一个继承构造函数不被相关代码使用,编译器不会为其产生真正的函数代码。这样比通过派生类构造函数“透传构造函数参数”来完成基类初始化的方式,总是需要定义派生类的各种构造函数更加节省目标代码空间。
2.注意事项
(1)继承构造函数无法初始化派生类数据成员。
这个很好理解,因为继承构造函数的功能是初始化基类,对于派生类数据成员的初始化则无能为力。解决的办法主要有两个:
一是使用 C++11 特性就地初始化成员变量,可以通过 =、{} 对非静态成员快速就地初始化,以减少多个构造函数重复初始化变量的工作,注意初始化列表会覆盖就地初始化操作。
class Derived: public Base {
public:
// 使用继承构造函数
using Base::Base;
// 假设派生类只是添加了一个普通的函数
void display() {
// do something
}
private:
// 派生类新增数据成员
double _double{0.0};
};
1
2
3
4
5
6
7
8
9
10
11
12
13
二是新增派生类构造函数,使用构造函数初始化列表。
class Derived :public Base {
public:
// 使用继承构造函数
using Base::Base;
// 新增派生类构造函数
Derived(int a, double b):Base(a), _double(b){}
// 假设派生类只是添加了一个普通的函数
void display() {
// do something
}
private:
// 派生类新增数据成员
double _double{0.0};
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
相比之下,第二种方法需要新增构造函数,明显没有第一种方法简洁,但第二种方法可由用户控制初始化值,更加灵活。各有优劣,两种方法需结合具体场景使用。
(2)构造函数拥有默认值会产生多个构造函数版本,且继承构造函数无法继承基类构造函数的默认参数,所以我们在使用有默认参数构造函数的基类时必须要小心。
class A {
public:
A(int a = 3, double b = 4): _a(a), _b(b){}
void display() {
cout<<_a<<" "<<_b<<endl;
}
private:
int _a;
double _b;
};
class B:public A {
public:
using A::A;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
那么 A 中的构造函数会有下面几个版本:
A()
A(int)
A(int, double)
A(const A&)
1
2
3
4
那么 B 中对应的继承构造函数将会有如下几个版本:
B()
B(int)
B(int, double)
B(const B&)
1
2
3
4
可以看出,参数默认值会导致多个构造函数版本的产生,因此在使用时需格外小心。
(3)多继承的情况下,继承构造函数会出现“冲突”的情况,因为多个基类中的部分构造函数可能导致派生类中的继承构造函数的函数签名(函数名与参数)相同。
class A {
public:
A(int i){}
};
class B {
public:
B(int i){}
};
class C : public A, public B {
public:
using A::A;
using B::B; //编译出错,重复定义C(int)
// 显示定义继承构造函数 C(int)
C(int i):A(i),B(i){}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
为避免继承构造函数冲突,可以通过显示定义来阻止隐式生成的继承构造函数。
此外,使用继承构造函数还需要注意:如果基类构造函数被申明为私有成员函数,或者派生类是从虚基类继承而来 ,那么就不能在派生类中申明继承构造函数
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/K346K346/article/details/81703914
=========
构造函数初始化列表以一个冒号开始,接着是以逗号分隔的数据成员列表,每个数据成员后面跟一个放在括号中的初始化式。例如:
class CExample { public: int a; float b; //构造函数初始化列表 CExample(): a(0),b(8.8) {} //构造函数内部赋值 CExample() { a=0; b=8.8; } };
上面的例子中两个构造函数的结果是一样的。上面的构造函数(使用初始化列表的构造函数)显式的初始化类的成员;而没使用初始化列表的构造函数是对类的成员赋值,并没有进行显式的初始化。
初始化和赋值对内置类型的成员没有什么大的区别,像上面的任一个构造函数都可以。对非内置类型成员变量,为了避免两次构造,推荐使用类构造函数初始化列表。但有的时候必须用带有初始化列表的构造函数:
- 1.成员类型是没有默认构造函数的类。若没有提供显示初始化式,则编译器隐式使用成员类型的默认构造函数,若类没有默认构造函数,则编译器尝试使用默认构造函数将会失败。
- 2.const 成员或引用类型的成员。因为 const 对象或引用类型只能初始化,不能对他们赋值。
初始化数据成员与对数据成员赋值的含义是什么?有什么区别?
首先把数据成员按类型分类并分情况说明:
- 1.内置数据类型,复合类型(指针,引用)- 在成员初始化列表和构造函数体内进行,在性能和结果上都是一样的
- 2.用户定义类型(类类型)- 结果上相同,但是性能上存在很大的差别。因为类类型的数据成员对象在进入函数体前已经构造完成,也就是说在成员初始化列表处进行构造对象的工作,调用构造函数,在进入函数体之后,进行的是对已经构造好的类对象的赋值,又调用个拷贝赋值操作符才能完成(如果并未提供,则使用编译器提供的默认按成员赋值行为)
注意点:
初始化列表的成员初始化顺序:
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 将有一个不可预测的值。有两种方法避免它,一个是总是按照你希望它们被初始化的顺序声明成员,第二个是,如果你决定使用初始化列表,总是按照它们声明的顺序罗列这些成员。这将有助于消除混淆。
原文地址:http://www.cnblogs.com/BlueTzar/articles/1223169.html
===================
在C++中,类的构造函数可以使用以下两种方式进行初始化:
构造函数的初始化列表(Initialization List)
构造函数的初始化列表可以在构造函数的参数列表后面使用冒号分隔符,然后在冒号后面列出每个成员变量及其对应的初始化值。例如:
class MyClass
{
public:
MyClass(int value) : m_value(value) {}
private:
int m_value;
};
1
2
3
4
5
6
7
8
在这个例子中,构造函数使用了初始化列表来初始化m_value成员变量。
在构造函数体内赋值(Assignment in Constructor Body)
构造函数也可以在其函数体内使用赋值语句来初始化成员变量。例如:
class MyClass
{
public:
MyClass(int value)
{
m_value = value;
}
private:
int m_value;
};
1
2
3
4
5
6
7
8
9
10
11
在这个例子中,构造函数使用了赋值语句来初始化m_value成员变量。
需要注意的是,使用初始化列表进行成员变量初始化比在构造函数体内使用赋值语句效率更高,并且可以用于初始化const成员变量或引用类型成员变量
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/Aheaboy/article/details/130513295
=================
参考:
https://blog.csdn.net/Aheaboy/article/details/130513295
标签:初始化,int,成员,c++,Base,public,构造函数 From: https://www.cnblogs.com/rebrobot/p/18220428