[C++11] --- 易用性改进

Posted Overboom

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[C++11] --- 易用性改进相关的知识,希望对你有一定的参考价值。

1 列表初始化

关于 C++ 中的变量,数组,对象等都有不同的初始化方法,在这些繁琐的初始化方法中没有任何一种方式适用于所有的情况。为了统一初始化方式,并且让初始化行为具有确定的效果,在 C++11 中提出了列表初始化的概念。

#include <iostream>
using namespace std;

struct Test

	int a;
	int b;
	char name[50];
;

int main(void)

	struct Test tmp = 1, 2, "mike"; //列表初始化

	int a = 1;
	int b =  1 ; //ok, 列表初始化
	int c 2 ; //ok

	int arr[] =  1, 2, 3 ;
	int arr2[] 1, 2, 3 ;


	system("pause");
	return 0;

Note: 在初始时, 前面的等号是否书写对初始化行为没有任何影响

列表初始化的好处:防止类型收窄
看一段示例代码:

	int a = 1024;
	char b = a; //ok, 数据丢失, 类型收窄
	printf("b = %d\\n", b);

在C语言中可正常编译并运行,只是b的值为0
但是在C++中,如果采用列表初始化的方式对b进行初始化,编译器会报类型收窄的错误

	int a = 1024;
	char b =  a ; //错误	1	error C2397: 从“int”转换到“char”需要收缩转换	

2 基于范围的for循环

C++98/03 中普通的 for 循环,语法格式:

for(表达式 1; 表达式 2; 表达式 3)

    // 循环体

C++11 基于范围的 for 循环,语法格式:

for (declaration : expression)

    // 循环体

在上面的语法格式中 declaration 表示遍历声明,在遍历过程中,当前被遍历到的元素会被存储到声明的变量中。expression 是要遍历的对象,它可以是表达式、容器、数组、初始化列表等

使用基于范围的 for 循环遍历容器,示例代码如下:

#include <iostream>
#include <vector>
using namespace std;

int main(void)

    vector<int> t 1,2,3,4,5,6 ;
    for (auto value : t)
    
        cout << value << " ";
    
    cout << endl;

    return 0;

3 noexcept修饰符

noexcept关键字是c++11之后新增的。该关键字会告诉编译器,被修饰的函数不会发生异常,这有利于编译器对程序做更多的优化。
看一段示例代码:

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;


void func01()

	throw 1;


//这个函数不能抛出任何异常
void func02() throw()




//这个函数不能抛出任何异常
//noexcept vs 2013不支持,换在qt下测试
void func03() noexcept




int main(void)


	system("pause");
	return 0;

从语法上讲,noexcept 修饰符有两种形式:

  • 简单地在函数声明后加上 noexcept 关键字
  • 可以接受一个常量表达式作为参数,如下所示∶
double divisionMethod(int a, int b) noexcept(常量表达式);

常量表达式的结果会被转换成一个 bool 类型的值:

  • 值为 true,表示函数不会抛出异常
  • 值为 false,表示有可能抛出异常这里
  • 不带常量表达式的 noexcept 相当于声明了 noexcept(true),即不会抛出异常。

4 nullptr

C++98/03 标准中,将一个指针初始化为空指针的方式有 2 种:

char *ptr = 0;
char *ptr = NULL;

在底层源码中 NULL 这个宏是这样定义的:

#ifndef NULL
    #ifdef __cplusplus
        #define NULL 0
    #else
        #define NULL ((void *)0)
    #endif
#endif

也就是说如果源码是 C++ 程序 NULL 就是 0
如果是 C 程序 NULL 表示 (void*)0
C++ 中将 NULL 定义为字面常量 0,并不能保证在所有场景下都能很好的工作,比如,函数重载时,NULL 和 0 无法区分:

#include <iostream>
using namespace std;

void func(char *p)

    cout << "void func(char *p)" << endl;


void func(int p)

    cout << "void func(int p)" << endl;


int main()

    func(NULL);   // 想要调用重载函数 void func(char *p)
    func(250);    // 想要调用重载函数 void func(int p)

    return 0;

测试输出为:

void func(int p)
void func(int p)

通过打印的结果可以看到,虽然调用 func(NULL); 最终链接到的还是 void func(int p) 和预期是不一样的,其实这个原因前边已经说的很明白了,在 C++ 中 NULL 和 0 是等价的。

出于兼容性的考虑,C++11 标准并没有对 NULL 的宏定义做任何修改,而是另其炉灶,引入了一个新的关键字 nullptr,专用于初始化空类型指针

5 强枚举类型

5.1 传统枚举的缺陷

C/C++ 的 enum 有个很” 奇怪” 的设定,就是具名(有名字)的enum类型的名字,以及 enum 的成员的名字都是全局可见的。这与 C++ 中具名的 namespace、class/struct 及 union 必须通过名字::成员名的方式访问相比是格格不入的,编码过程中一不小心程序员就容易遇到问题。比如∶

enum China Shanghai, Dongjing, Beijing, Nanjing;
enum Japan Dongjing, Daban, Hengbin, Fudao;

上面定义的两个枚举在编译的时候,编译器会报错,具体信息如下:

error C2365: “Dongjing”: 重定义;以前的定义是“枚举数”

错误的原因上面也提到了,在这两个具名的枚举中 Dongjing 是全局可见的,所有编译器就会提示其重定义了。

5.2 强枚举类型

针对枚举的缺陷,C++11 标准引入了一种新的枚举类型,即枚举类,又称强类型枚举(strong-typed enum)。
声明强类型枚举非常简单,只需要在 enum 后加上关键字 class,同时可以指定枚举成员的数据类型。

enum class Colors :char  Red, Green, Blue ;

6 常量表达式 — constptr

6.1 constptr介绍

在 C++11 中添加了一个新的关键字 constexpr,这个关键字是用来修饰常量表达式的。所谓常量表达式,指的就是由多个(≥1)常量(值不会改变)组成并且在编译过程中就得到计算结果的表达式。
C++ 程序从编写完毕到执行分为四个阶段:预处理、 编译、汇编和链接 4 个阶段,得到可执行程序之后就可以运行了。需要额外强调的是,常量表达式和非常量表达式的计算时机不同,非常量表达式只能在程序运行阶段计算出结果,但是常量表达式的计算往往发生在程序的编译阶段,这可以极大提高程序的执行效率,因为表达式只需要在编译阶段计算一次,节省了每次程序运行时都需要计算一次的时间。
C++11 中添加了 constexpr 关键字之后就可以在程序中使用它来修改常量表达式,用来提高程序的执行效率。在使用中建议将 const 和 constexpr 的功能区分开,即凡是表达“只读”语义的场景都使用 const,表达“常量”语义的场景都使用 constexpr。
在定义常量时,const 和 constexpr 是等价的,都可以在程序的编译阶段计算出结果,例如:

const int m = f();  // 不是常量表达式,m的值只有在运行时才会获取。
const int i=520;    // 是一个常量表达式
const int j=i+1;    // 是一个常量表达式

constexpr int i=520;    // 是一个常量表达式
constexpr int j=i+1;    // 是一个常量表达式

对于 C++ 内置类型的数据,可以直接用 constexpr 修饰,但如果是自定义的数据类型(用 struct 或者 class 实现),直接用 constexpr 修饰是不行的。

// 此处的constexpr修饰是无效的
constexpr struct Test

    int id;
    int num;
;

6.2 常量表达式修饰函数

为了提高 C++ 程序的执行效率,我们可以将程序中值不需要发生变化的变量定义为常量,也可以使用 constexpr 修饰函数的返回值,这种函数被称作常量表达式函数,这些函数主要包括以下几种:普通函数/类成员函数、类的构造函数、模板函数。

6.2.1 修饰函数

constexpr 并不能修改任意函数的返回值,时这些函数成为常量表达式函数,必须要满足以下几个条件:
1> 函数必须要有返回值,并且 return 返回的表达式必须是常量表达式。

// error,不是常量表达式函数
constexpr void func1()

    int a = 100;
    cout << "a: " << a << endl;


// error,不是常量表达式函数
constexpr int func1()

    int a = 100;
    return a;

函数 func1() 没有返回值,不满足常量表达式函数要求
函数 func2() 返回值不是常量表达式,不满足常量表达式函数要求

2> 函数在使用之前,必须有对应的定义语句。

#include <iostream>
using namespace std;

constexpr int func1();
int main()

    constexpr int num = func1();	// error
    return 0;


constexpr int func1()

    constexpr int a = 100;
    return a;

在测试程序 constexpr int num = func1(); 中,还没有定义 func1() 就直接调用了,应该将 func1() 函数的定义放到 main() 函数的上边。

3> 整个函数的函数体中,不能出现非常量表达式之外的语句(using 指令、typedef 语句以及 static_assert 断言、return 语句除外)。

// error
constexpr int func1()

    constexpr int a = 100;
    constexpr int b = 10;
    for (int i = 0; i < b; ++i)
    
        cout << "i: " << i << endl;
    
    return a + b;


// ok
constexpr int func2()

    using mytype = int;
    constexpr mytype a = 100;
    constexpr mytype b = 10;
    constexpr mytype c = a * b;
    return c - (a + b);

因为 func1() 是一个常量表达式函数,在函数体内部是不允许出现非常量表达式以外的操作,因此函数体内部的 for 循环是一个非法操作。

以上三条规则不仅对应普通函数适用,对应类的成员函数也是适用的:

class Test

public:
    constexpr int func()
    
        constexpr int var = 100;
        return 5 * var;
    
;

int main()

    Test t;
    constexpr int num = t.func();
    cout << "num: " << num << endl;

    return 0;

6.2.2 修饰模板函数

C++11 语法中,constexpr 可以修饰函数模板,但由于模板中类型的不确定性,因此函数模板实例化后的模板函数是否符合常量表达式函数的要求也是不确定的。如果 constexpr 修饰的模板函数实例化结果不满足常量表达式函数的要求,则 constexpr 会被自动忽略,即该函数就等同于一个普通函数。

#include <iostream>
using namespace std;

struct Person 
    const char* name;
    int age;
;

// 定义函数模板
template<typename T>
constexpr T dispaly(T t) 
    return t;


int main()

    struct Person p  "luffy", 19 ;
    //普通函数
    struct Person ret = dispaly(p);
    cout << "luffy's name: " << ret.name << ", age: " << ret.age << endl;

    //常量表达式函数
    constexpr int ret1 = dispaly(250);
    cout << ret1 << endl;

    constexpr struct Person p1  "luffy", 19 ;
    constexpr struct Person p2 = dispaly(p1);
    cout << "luffy's name: " << p2.name << ", age: " << p2.age << endl;
    return 0;

在上面示例程序中定义了一个函数模板 display(),但由于其返回值类型未定,因此在实例化之前无法判断其是否符合常量表达式函数的要求:

  • struct Person ret = dispaly§; 由于参数 p 是变量,所以实例化后的函数不是常量表达式函数,此时 constexpr 是无效的
  • constexpr int ret1 = dispaly(250); 参数是常量,符合常量表达式函数的要求,此时 constexpr 是有效的
  • constexpr struct Person p2 = dispaly(p1); 参数是常量,符合常量表达式函数的要求,此时 constexpr 是有效的

6.2.3 修饰构造函数

如果想用直接得到一个常量对象,也可以使用 constexpr 修饰一个构造函数,这样就可以得到一个常量构造函数了。常量构造函数有一个要求:构造函数的函数体必须为空,并且必须采用初始化列表的方式为各个成员赋值。

#include <iostream>
using namespace std;

struct Person 
    constexpr Person(const char* p, int age) :name(p), age(age)
    
    
    const char* name;
    int age;
;

int main()

    constexpr struct Person p1("luffy", 19);
    cout << "luffy's name: " << p1.name << ", age: " << p1.age << endl;
    return 0;

以上是关于[C++11] --- 易用性改进的主要内容,如果未能解决你的问题,请参考以下文章

[C++11] --- 易用性改进

C++11新特性精讲(多线程除外)

C++11新特性精讲(多线程除外)

Arthas发布3.5.5版本,支持macOS ARM64架构,改进易用性

喵呜:C++基础系列:auto关键字(C++11)基于范围的for循环(C++11)指针空值nullptr(C++11)

喵呜:C++基础系列:auto关键字(C++11)基于范围的for循环(C++11)指针空值nullptr(C++11)