1、  C++98/03初始化

  我们先来总结一下C++98/03的各种不同的初始化情况:

//普通数组
int i_arr[] = {, , }; //POD(plain old data)
struct A
{
int x; struct B
{
int i;
int j;
}b;
}a = {, {, }}; //拷贝初始化
int i = ; class Foo
{
public:
Foo(int){};
}Foo = ; //直接初始化
int j();

  这些不同的初始化方法都有各自的适用范围和方法,但是种类繁多的方法却没有一种可以通用的。所以C++11提出了初始化列表的方法来解决通用问题。

2、  统一初始化方法

  其实C++98/03中已经存在初始化列表的方法,只是范围比较窄,只适用于常规POD类型。

int i_arr[] = {, , };
int i_arr2[] = {, , , }; struct B
{
int i;
int j;
}b = {, };

  而C++11将这种初始化方法适用于所有类型的初始化。我们先来看一组例子:

class Foo
{
public:
Foo(int){};
private:
Foo(const Foo &){};
}; void testFunc(void)
{
Foo val1();
//Foo val2 = 123; // error:Foo::Foo(const Foo &) is private.
Foo val3 = {};
Foo val4{};
int a5 = {};
int a6{};
}

  val3、val4使用了初始化列表来初始化对象,a3虽然使用等号,但是并不影响到私有拷贝,仍然是初始化列表的方式,等统一val1的直接初始化,而val2则调用私有拷贝函数会报错。a5、a6则是一般类型的初始化,val4和a6都是C++11特有的,而C++98/03并不支持。

  新的初始化方法是变量名后面加{}来进行初始化,{}内则是初始化的内容,等号是否存在并不影响。

type val {};

  C++11的新方式同样支持new操作符:

int *a = new int{};
double b = double {12.34};
int *arr = new int[]{,,};

  a指向了new操作符分配的一块内存,通过初始化列表将内存的初始值指定为了5;

  b是对匿名对象进行初始化之后然后进行拷贝初始化;

  arr则是通过new动态申请一个数组,并通过初始化列表进行初始化。

  初始化列表还有一个特殊的地方,就是作为函数的返回值。

struct Foo
{
Foo(int, double){};
}; Foo testFunc(void)
{
return {, 12.3};
}

  在C++11中,初始化列表是非常方便的,不仅统一了对象的初始化方式,还使代码更加简洁清晰。

3、  使用细节

3.1 自定义类型初始化

  当我们在C++11中使用初始化列表时,可能有以下情况:

struct A
{
int x;
int y;
}a = {,}; //a.x = 123, a.y = 123 struct B
{
int x;
int y;
B(int, int) : x(), y() {};
}b = {,}; //b.x = 0, b.y = 0

  这个例子说明什么问题呢,a是以C++98/03的聚合类型来初始化的,用拷贝的方式初始化a中的成员,而b呢,由于自定义了构造函数,所以初始化是以构造函数来初始化的。所以有以下结论:

  当使用初始化列表时,如果是聚合类型,则以拷贝的方式来初始化成员,如果是非聚合类型,则是以构造函数来初始化成员。

3.2 聚合类型

  提了这么多的聚合类型,那么到底什么是聚合类型呢?我们来看聚合类型的定义:

  1)  类型是普通数组(int[10],char[],long[2][3]等)。

  2)  类型是一个类,且:

  • 无用户自定义构造函数;
  • 无私有或者保护的非静态成员;
  • 无基类;
  • 无虚函数;
  • 无{}和=直接初始化的非静态数据成员。

3.2.1 数组

  对于数组而言,就很简单了,只要该类型是一个普通的数组,如果数组的元素并不是聚合类型,那么这个数组也是一个聚合类型:

int [] = {,,};
std::string s_arr[] = {“hello”, “C++”, “”};

3.2.2 存在自定义构造函数

struct A
{
int x;
int y;
int z;
A(int, int){};
}; A a = {, , };

  当一个自定义类拥有自己的构造函数使,无法将该类看作一个聚合类型,必须通过自定义的构造函数才能构造对象。

3.2.3 存在私有或者非静态成员

struct A
{
int x;
int y;
protected:
int z;
}; A a = {, , }; //error struct B
{
int x;
int y;
protected:
static int z;
}; B b = {, }; //ok

  例子中,A的实例化是失败的,因为z是一个受保护的非静态成员。而b是成功的,因为z是一个受保护的静态数据成员,所以,类成员里面的静态数据成员是不能通过初始化列表来初始化的,静态数据成员的初始化遵循静态成员的初始化方式。

3.2.4 有基类或者虚函数

  有基类或者虚函数同样不适用于使用初始化列表。

struct A
{
int x;
int y;
virtual void fun(){};
}; A a = {, }; //error class B {}; struct C : public B
{
int x;
int y;
}; B b = {, }; //error

3.2.5 {}和=初始化的非静态数据成员

struct A
{
int x;
int y = ;
}; A a = {, }; //error

  在类型A中,y在声明时即被=初始化为2,所以A不再是一个聚合类型。

  这个例子中需要注意的是,C++11中放宽了类型申明的初始化操作,即在非静态数据成员的声明时调用{}或者=来对成员进行初始化,但是造成的影响是该类型不再是聚合类型,所以不能直接使用初始化列表。所以,如果要使用初始化列表就必须自己定义一个构造函数。

3.2.6 聚合类型并非递归

struct A
{
int x;
int y;
private:
int z;
}; A a{, , }; // error A a1{}; //ok struct B
{
A a;
int x;
double y;
}; B b{{}, , 2.5};

  A有一个私有化的非静态成员,所以使用A a{1, 2, 3}是错误的,但是可以调用他的无参构造函数,所以在B中,即使成员a是一个非聚合类型,但是B仍然是一个聚合类型,可以直接使用初始化列表。

3.2.6 小结

  根据这么多例子,我们得到以下结论:

  对于一个聚合类型,使用初始化列表相当于对其中每个元素分别赋值;而对于非集合类型,则需要先定义一个合适的构造函数,此时使用初始化列表将调用它对应的构造函数。

4、  初始化列表

4.1 任意长度的初始化列表

  在c++中,对于stl容器和未显示数组长度的数组可以进行任意长度的初始化,在初始化的时候可以书写任意长度的内容。

int i_arr[] = {,,,};

std::vector<int> veci_t = {,,,};

std::map<std::string, int> mapsi_t = {{"", }, {"", }, {"", }};

  但是对于自定义类型不具备这种能力,但是C++11解决了这个问题,C++11中可以通过轻量级模板std::initalizer_list来解决这个问题。我们只需要添加一个std::initializer_list的构造函数,这个自定义类型即可拥有这种任意长度初始化列表来初始化的能力。

class Foo
{
public:
Foo( std::initializer_list<int> list ) {};
}; Foo foo = {,,,,};

  std::initializer_list负责接收初始化列表,可以通过for循环来读取其中的元素,并将元素做操作。不仅可以作为类型的初始化,同样的,可以作为函数参数传递同类型的数据集合。在任何需要的时候,都可以使用std::initializer_list来一次性传递多个参数。

// code1
class FooVector
{
public:
FooVector(std::initializer_list<int> list)
{
for(auto it = list.begin(); it != list.end(); ++it)
{
mveci_content.push_back(*it);
}
}
private:
std::vector<int> mveci_content;
}; FooVector foo1 = {,,,,}; //code2
using pair_t = std::map<int, int>::value_type;
class FooMap
{
public:
FooMap(std::initializer_list<pair_t> list)
{
for(auto it = list.begin(); it != list.end(); ++it)
{
mmapii_content.insert(*it);
}
}
private:
std::map<int, int> mmapii_content;
}; FooMap foo2 = {{,}, {,}, {,}}; //code3
void vFunc(std::initializer_list<int> list)
{
for(auto it = list.begin(); it != list.end(); ++it)
{
std::cout << *it << std::endl;
}
} void vCallFunc(void)
{
vFunc({});
vFunc({,,,});
}

4.2 std::initializer_list使用细节

  std::initializer_list的特点如下:

  • 它是一个轻量级的容器类型,内部定义了iterator等容器等必须的概念;
  • 可以接收任意长度的初始化列表,但是要求元素必须都是同种类型;
  • 有三个成员接口,size(),begin(),end();
  • 只能被整体初始化或者赋值。

  //获取长度

std::initializer_list<int> list = {,,};  //初始化
size_t len = list.size(); //len = 3

  std::initializer_list的访问只能通过begin()和end()来进行循环遍历,遍历取得的迭代器是只读的,所以无法修改其中元素的值,但是可以整体赋值来修改其中的元素。

std::initializer_list<int> list;
size_t len = list.size(); //len = 0
list = {,,,,};
len = list.size(); //len = 5
list = {,};
len = list.size(); //len = 2

  在研究了std::initializer_list的用法之后,我们来看std::initializer_list的效率。很多时候,如果容器内部是自定义类型或者数量较大,那么是不是就像vector之类的容器一样,把每个元素都赋值一遍呢?答案是不是!std::initializer_list是非常高效的,它的内部并不保存初始化列表元素中的拷贝,仅仅保存初始化列表中的引用。

  如果我们按照下面的代码来使用std::initializer_list是错误的,虽然可以正常通过编译,但是可能无法得到我们希望的结果,因为a,b在函数结束时生存周期也结束了,返回的是不确定的内容。

std::initializer_list<int> func1(void)
{
int a = , b = ;
return {a, b}; //a,b在返回时并没有拷贝
}

  正确的用法应该是这样,通过真正的容器或者具有转移拷贝语意的物件来替代std::initializer_list返回的结果。

std::vector<int> func2(void)
{
int a = , b = ; return {a, b}; //ok
}

  我们应该将std::initializer_list看作保存对象的引用来使用,在它持有的对象的生命周期结束之前来完成传递。

5、  防止类型收窄

5.1 类型收窄的情况

  我们先来看一段代码:

struct Foo
{
Foo(int i) { std::cout << i << std::endl;}
}; Foo foo(1.2);

  这个例子就是类型收窄的情况,虽然说能够正常通过编译,但是在传递i之后不能完整的保存浮点数的数据。

  我们来看C++中有哪些情况会有类型收窄的情况:

  • 从一个浮点数隐式转换为一个整数,如int I = 2.2;
  • 从高精度浮点数隐式转换为低精度浮点数,如long doule隐式转换为double或者float;
  • 从一个整数隐式转换为一个浮点数,并且超出了浮点数的范围,如float f = (unsigned long long ) – 1;
  • 从一个整型隐式转换为一个长度较短的整型数,并且超出了长度较短的整型数范围,如char x = 65536;

  这些类型收窄的情况,在编译器并不会报错,但是可能存在潜在的错误。

5.2 C++11的改善

  C++11中可以通过初始化列表来检查,防止类型的收窄。我们来看一组例子:

int a = 1.1;            //ok
int b = {1.1}; //error float fa = 1e40; //ok
float fb = {1e40}; //error float fc = (unsigned long long) -; //ok
float fd = { (unsigned long long) - }; //error float fe = (unsigned long long); //ok
float ff = {(unsigned long long)}; //ok const int x = , y = ;
char c = x; //ok
char d = {x}; //error
char e = y; //ok
char f = {y}; //ok

  在C++11中,遇到各种类型收窄的情况,初始化列表是不允许这种转换的,上述例子中,如果x,y去掉const限定符,最后的f也会因为类型收窄而报错。

C11简洁之道:初始化改进的更多相关文章

  1. C11简洁之道:模板改进

    1.  右尖括号 我们在C++98/03中使用泛型编程的时候,经常遇到“>>”被当作右移操作符,而不是模板参数的结尾.假如我们有如下代码: template <typename T& ...

  2. C11简洁之道:类型推导

    1.  概述 C++11里面引入了auto和decltype关键字来实现类型推导,通过这两个关键字不仅能方便的获取复杂的类型,还能简化书写,提高编码效率. 2.  auto 2.1 auto关键字的新 ...

  3. C11简洁之道:lambda表达式

    1.  定义 lambda表达式是C++11非常重要也是很常用的特性之一,来源于函数式编程的概念,也是现代编程语言的一个特点.它有如下特点: 声明式编程风格:就地匿名定义目标函数或者函数,不需要额外写 ...

  4. C11简洁之道:tupe元祖

    tuple元组是一个固定大小不同类型的值的集合,是泛化的std::pair.我们也可以把它当作一个通用的结构体来使用,不需要创建结构体有获取结构体特征,在某些情况可以取代结构体,使程序更简洁.直观. ...

  5. C11简洁之道:循环的改善

    1.  for循环的新用法 在C++98/03中,通过for循环对一个容器进行遍历,一般有两种方法,常规的for循环,或者使用<algorithm>中的for_each方法. for循环遍 ...

  6. C11简洁之道:函数绑定

    1.  可调用对象 在C++中,有“可调用对象”这么个概念,那么什么是调用对象呢?有哪些情况?我们来看看: 函数指针: 具有operator()成员函数的类对象(仿函数): 可以被转换为函数指针的类对 ...

  7. 《Clean Code》 代码简洁之道

    作者介绍 原文作者: Robert C. Martin, Object Mentor公司总裁,面向对象设计.模式.UML.敏捷方法学和极限编程领域的资深顾问,是<敏捷软件开发:原则.模式.与实践 ...

  8. JavaScript 代码简洁之道

    摘要: 可以说是<Clean Code>的JS代码示例了,值得参考. 原文:JavaScript 代码简洁之道 作者:缪宇 Fundebug经授权转载,版权归原作者所有. 测试代码质量的唯 ...

  9. JAVA基础之代码简洁之道

    引言 普通的工程师堆砌代码,优秀的工程师优雅代码,卓越的工程师简化代码.如何写出优雅整洁易懂的代码是一门学问,也是软件工程实践里重要的一环.--来自网络 背景 软件质量,不但依赖于架构及项目管理,更与 ...

随机推荐

  1. 4-2:实现cp命令

    #include <stdio.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h& ...

  2. 第一章 Java Web应用开发技术

    Java Web应用开发是基于JavaEE(JavaEnterprise Edition)框架的,而JavaEE是建立在Java平台上的企业级应用解决方案.JavaEES框架提供的Web开发技术主要支 ...

  3. unity像素贪吃蛇

    [ 星 辰 · 别 礼 ] 设计过程: 首先,在之前玩坏控制台做的那个c#贪吃蛇之后,我以为做unity会很简单,但事实比较不如人意...拖了好几天.因为过程中遇到一些问题 蛇身的移动,还是用列表,将 ...

  4. C# Dsoframer.ocx 如何在winform中嵌入Excel,内嵌Excel,word

    如果你还不太清楚Dspframer.ocx怎么放到窗体上就看上一篇文章,里面详细介绍了是如何放到窗体上的. 链接:http://www.cnblogs.com/pingming/p/4182045.h ...

  5. 使用source创建一个新项目(将本地项目文件和github远程库链接)

    1. 本地创建项目文件夹 2. 将本地的项目添加到source中(我使用的source版本为2.4.7.0) 3. github创建远程库  4. 关联本地项目文件和github库 确定添加就可以了. ...

  6. SIM卡是什么意思?你所不知道的SIM卡知识扫盲(详解)【转】

    原文链接:http://www.jb51.net/shouji/359262.html 日常我们使用手机,SIM卡是手机的必须,没有了它就不能接入网络运营商进行通信服务.SIM卡作为网络运营商对于我们 ...

  7. jzoj3865[JSOI2014]士兵部署

    ‘ 数据范围:n,m<=10^5,传送门:https://jzoj.net/senior/#main/show/3865 感觉jzoj好高明啊,就是访问不太稳定. 首先题意中被n个点控制的区域相 ...

  8. Codeforces Gym 101142 C. CodeCoder vs TopForces(思维+图论)

    题意: 每个人有两个积分CC和TF 第i个人能战胜第j个人的条件满足下面两个条件中的一个即可 1.CCi > CCj 或 TFi > TFj 2.i能战胜k,k能战胜j. 题解: 先按CC ...

  9. 进程间通讯-3(Manager)-实现数据的同时修改

    Manager 可以实现列表,字典,变量,锁,信号量,事件等的数据之间的共享.Manager已经默认加锁了.控制数据不会乱. 实现了不同进程之间数据的共享,并且可以同时修改. from multipr ...

  10. BZO4197 & 洛谷2150 & UOJ129:[NOI2015]寿司晚宴——题解

    https://www.lydsy.com/JudgeOnline/problem.php?id=4197 https://www.luogu.org/problemnew/show/P2150 ht ...