C++11特性介绍——初始化与构造
继承构造函数
如果基类存在多个构造函数,我们可以使用using来继承,化繁为简, 以免去自己重复的写那些基类”透传”的每一种构造函数; 对于派生类B中新加的变量, 可以使用C++11的成员变量初始化即可, 如下;
struct A
{
A(int i){}
A(double d, int i) {}
A(float f, int i, const char* c) {}
}
struct B:A
{
using A::A;
int d { 0 };
}
说明:
- 如果基类构造函数中存在默认值,继承构造函数不会继承这个默认值, 实际上默认值会导致基类存在多个版本构造函数,这些函数版本会被派生类全部继承;
struct A
{
A(int a=3, double c = 3.14) {}
}
struct B:A
{
useing A::A;
int d { 0 }; //成员变量初始化C++11
}
B可能从A中继承来的候选构造函数:
- A(int = 3, double = 3.14);
- A(int = 3);
- A(const A&);
- A();
此时B中的构造函数
- B(int, double)这是一个继承构造函数;
- B(int)减少一个参数继承构造函数;
- B(const B& )拷贝构造函数,这不是继承来的;
- B()不包含参数的默认构造函数;
- 如果基类构造函数被声明为private,或者派生类是从基类中虚继承的,那么就不能够在派生类中声明继承构造函数了;
- 一旦使用了继承构造函数, 编译器就不会在为派生类生成默认的构造函数了;
委派构造函数
先看下面代码:
class Info
{
public:
Info()
{
InitInfo();
}
Info(int i):i_(i)
{
InitInfo()
}
Info(int i, double d): i_(i), d_(d)
{
InitInfo();
}
private:
void InitInfo(){};
int i_ = 0;
double d_ = 0.0;
}
问题
如上代码所示, 每一个构造函数, 都需要调用InitInfo
函数, 现实中, 构造函数可能更长, 构造函数个数可能更多?
委派构造函数就是来解决这个问题的, 可以有效的减少程序员写重复代码量;
使用
C++ 11 中所谓的委派构造函数, 就是指委派函数, 将构造任务委派给指定的构造函数, 来完成这样一种类构造的方式;
C++ 委派构造函数,是在构造函数的初始化列表位置进行的构造、委派;
class Info
{
public:
Info()
{
InitInfo();
}
Info(int i):Info()
{
i_ = i;
}
Info(int i, double d):Info()
{
i_ = i;
d_ = d;
}
private:
void InitInfo(){};
int i_ = 0;
double d_ = 0.0;
}
注意:
- C++ 中构造函数不能同时“委派”和使用初始化列表,所以如果委派构造要给变量赋值,初始化代码必须放在函数体中。但是这种情况也可以通过代码设计来避免;
class Info
{
public:
Info():Info(0, 0.0)
{
}
Info(int i):Info(i, 0.0)
{
}
private:
Info(int i, double d): i_(i), d_ = d
{
InitInfo();
}
}
void InitInfo(){};
int i_ = 0;
double d_ = 0.0;
}
快速初始化成员变量
C++98中支持在类的声明中使用=
初始化变量的方式;当然只能用于常量的静态成员static const,且常量的静态成员也只能是整型或者枚举类型;
非静态则必须在构造函数中进行。
C++ 11 改变了这一现状,C++ 11标准中允许非静态成员初始化有多种形式,除了初始化列表之外,还可以在变量声明的时候使用=
或者{}
进行非静态成员变量初始化。
示例代码如下:
/*
* C++11中变量的初始化
*/
struct Init
{
int a = 1;
double b{ 1.2 };
//std::string str1("abc"); //编译不通过
};
示例代码可以看出,使用{}
和()
完全不一样的,就地初始化只能使用{}
。
注意:
- C++ 11 中非静态成员变量的初始化如上所述存在两种方法:
- 就地(成员变量声明的时候)初始化;
- 构造函数时候,在初始化列表中初始化;
-
同一个变量既可以就地初始化又可以在初始化列表中初始化,初始化列表的效果总是优先于就地初始化。
- 对于存在多个构造函数的类,就地初始化无疑效率要高很多,不需要对每一变量在任何构造函数中初始化。
- 对于非常量的静态成员变量,C++ 11 和 C++ 98保持一致,程序还是需要到头文件以外的地方定义,保证编译时候,类静态成员的定义只存在于一个目标文件中。
列表初始化
C++11中引入新的类型初始化方式如下:
//列表初始化
int a_list[] = { 1, 2, 3 };
int b_list[] {1, 2, 4};
vector<int> c_vector{ 1, 5, 6 };
vector<int> d_vector = { 1, 2, 3 };
这样一来,自动变量和全局变量的初始化在C++11中被丰富了,全部初始化方式总结如下:
- 等号 ‘=’ 加赋值表达式; int a = 3 + 4;
- 等号 ‘=’ + ‘{}’ 初始化列表; int a = {3 + 4};
- 圆括号表达式列表; int a(3+4);
- 花括号初始化列表; int a{3+4};
初始化列表方式如何用于自定义类型??
只需要#include
enum Gener
{
boy,
girl
};
class people
{
public:
people(initializer_list<pair<std::string, Gener> > var_list)
{
auto i = var_list.begin();
for (; i != var_list.end(); ++i)
{
data.push_back(*i);
}
}
private:
vector<pair<string, Gener> > data;
};
people name_12 = { { "tom", boy }, { "lily", girl }, { "kuku", boy } };
函数参数列表使用初始化列表
void FunInit(initializer_list<int> iv)
{
}
FunInit({ 1, 2 });
FunInit({}); //空列表
类或者结构体的成员函数使用初始化列表
class InitListData
{
public:
InitListData& operator [] (initializer_list<int> l)
{
for (auto i = l.begin(); i != l.end(); ++i)
{
idx.push_back(*i);
}
return *this;
}
InitListData& operator = (int v)
{
if (idx.empty() != true)
{
for (auto i = idx.begin(); i != idx.end(); ++i)
{
d.resize((*i > d.size()) ? *i : d.size());
d[*i - 1] = v;
}
idx.clear();
}
}
void Print()
{
for (auto i = d.begin(); i != d.end(); ++i)
{
cout << *i << " ";
}
cout << endl;
}
private:
vector<int> idx;
vector<int> d;
};
void TestInitData()
{
InitListData init_d;
init_d[{2, 3, 5}] = 7;
init_d[{1, 4, 6, 8}] = 8;
init_d.Print();
}
初始化列表用于函数返回值
vector<int> GetVector()
{
return { 1, 2 };
}
const vector<int>& GetVectorRef()
{
return { 3, 5 };
}
初始化列表可判断是否发生类型收紧
const int a = 1024;
const int b = 10;
char c = {a}; //类型收紧,编译不过
char d = {b}; //可以编译通过
在C++11中列表初始化是唯一一种可以防止类型收窄的初始化方式;
cuipf