Home

cuipf blog

29 Aug 2017

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 };
}

说明:

  1. 如果基类构造函数中存在默认值,继承构造函数不会继承这个默认值, 实际上默认值会导致基类存在多个版本构造函数,这些函数版本会被派生类全部继承;
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()不包含参数的默认构造函数;
  1. 如果基类构造函数被声明为private,或者派生类是从基类中虚继承的,那么就不能够在派生类中声明继承构造函数了;
  2. 一旦使用了继承构造函数, 编译器就不会在为派生类生成默认的构造函数了;

委派构造函数

先看下面代码:

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");  //编译不通过
};

示例代码可以看出,使用{}()完全不一样的,就地初始化只能使用{}

注意:

  1. C++ 11 中非静态成员变量的初始化如上所述存在两种方法:
    • 就地(成员变量声明的时候)初始化;
    • 构造函数时候,在初始化列表中初始化;
  2. 同一个变量既可以就地初始化又可以在初始化列表中初始化,初始化列表的效果总是优先于就地初始化。

  3. 对于存在多个构造函数的类,就地初始化无疑效率要高很多,不需要对每一变量在任何构造函数中初始化。
  4. 对于非常量的静态成员变量,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中被丰富了,全部初始化方式总结如下:

  1. 等号 ‘=’ 加赋值表达式; int a = 3 + 4;
  2. 等号 ‘=’ + ‘{}’ 初始化列表; int a = {3 + 4};
  3. 圆括号表达式列表; int a(3+4);
  4. 花括号初始化列表; int a{3+4};

初始化列表方式如何用于自定义类型??

只需要#include 头文件,并且声明一个initializer_list模板类为参数的构造函数即可

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

scribble