C++11 语言新特性

本文只是罗列 C++11 一些常见的,但不是很复杂的特性,所以就没展开篇幅讲。其他比较复杂的 C++11 特性,具体见 C++11 相关特性汇总

1. nullptr 和 std::nullptr_t

  • C++11 允许你使用 nullptr 取代 0或NULL
  • 这个新特性特别能够帮助你在 “NULL 指针被解释为一个整数值”时避免误解
  • nullptr 是个新关键字,它被 自动转换为各种指针类型,但不会被转换为任何整数类型

例如:

void f(int);
void f(void*);

f(0);       // call f(int)
f(NULL);    // call f(int) if NULL is 0
f(nullptr); // call f(void*) 

2. 一致性初始化与初值列

  • 一致性初始化,面对任何初始化动作你可以使用相同语法,也就是使用大括号,例如:
int values[] {1, 2, 3, 4, 5};
std::vector<int> v {1, 2, 3, 4, 5};
std::vector<std::string> cities {"beijing", "shanghai"};
  • 初值列会强迫造成所谓值初始化,意思是即使某个 local 变量属于某种基础类型(通常会有不明确的初值)也会被初始化为0或 nullptr
int i;    // i 可能是个随机值
int j{};  // j 被初始化成 0
int* p;   // p 可能是个随机值
int* q{}; // q 会被初始化成 nullptr
bool b{}; // b 被初始化成 0
  • 支持“用户自定义类型之初值列”的概念,提供 std::initializer_list<>,用来支持以一系列值进行的初始化,或者想处理一系列的值,例如:
void print(std::initializer_list<int> vals) {
    for (auto v : vals) {
        std::cout << v << std::endl;
    }
}
print({ 1,2,3,4,4,5,6,7 });

3. range-based for 循环

  • 一种崭新的 for 循环形式,可以逐一迭代某个给定的区间、数组、集合内的每一个元素,一般语法如下:
// decl是给定的 coll 集合中的每个元素的声明,针对这些元素,给定的 statement 会被执行。
for ( decl : coll ) {
    statement
}
// 常规遍历
for (int i : {1, 2, 3, 4, 5}) {
    std::cout << i << std::endl;
}
// 修改元素值,vector每个元素乘以3
std::vector<double> vecD;
...
for (auto& elem : vecD) {
    elem *= 3;
}
// 建议的写法,可以避免调用每个元素的拷贝构造函数和析构函数
template <typename T>
void PrintElements(const T& coll) {
    for (const auto& elem : coll) {
        std::cout << elem << std::endl;
    }
}

4. 新式的字符串字面量

  • 原始字符串(raw string),以 R("开头,以")结尾,可以内包含line break,例如:
// 常规,两个反斜杠和一个n
"\\\\n"
// raw string
R("\\n")
  • 原始字符串对于定义正则表达式特别有用
  • 编码前缀,可以给字符串字面量定义一个特殊的字符编码前缀,如下:
// u8 定义一个 UTF-8 编码,字符串类型为 const char[N]
auto u8 = u8"你好";

// u 定义一个 UTF-16 编码,字符串类型为 const char16_t[N]
auto u = u"你好";

// U 定义一个 UTF-32 编码,字符串类型为 const char16_t[N]
auto U = U"你好";

// L 定义一个宽字符串字面量,字符串类型为 const wchar_t[N]
auto L = L"你好";

5. 关键字 noexcept

  • C++11 提供了关键字noexcept,用来指明某个函数 无法 或 不打算 抛出异常,例如:
void foo() noexcept;
  • 可以在 noexcept(...) 中指定一个Boolean条件,若符合条件就不抛出异常,通常指明使用 noexcept 而不带条件,其实就是 noexcept(true) 的一个简洁形式

6. 关键字 constexpr

  • C++11 ,可以使用 constexpr 让表达式的结果确定在代码编译过程中,例如
constexpr int square(int x) {
    return x*x;
}

float a[square(9)]; // a 有81个元素,在代码编译过程中就计算好了

7. 关键字 final

C++ 中增加了 final 关键字来限制某个类不能被继承,或者某个虚函数不能被重写。如果使用 final 修饰函数,只能修饰虚函数,并且要把final关键字放到类或者函数的后面。

class A
{
public:
	A() {};
	~A() {};

	virtual void echo() {
        std::cout << "Hello, i am A!" << std::endl;
	}
};

class B final : public A
{
public:
	B() {};
	~B() {};

	void echo() final {
		std::cout << "Hello, i am B!" << std::endl;
	}
};

// ERROR: 'C': cannot inherit from 'B' as it has been declared as 'final'
class C : public B
{
public:
	C() {};
	~C() {};
	// ERROR: 'B::echo': function declared as 'final' cannot be overridden by
    void echo() {
        std::cout << "Hello, i am C!" << std::endl;
    }
};

8. 关键字 override

override 关键字确保在派生类中声明的重写函数基类的虚函数有相同的签名,同时也明确表明将会重写基类的虚函数,这样就可以保证重写的虚函数的正确性,也提高了代码的可读性,该关键字要写到方法的后面。使用方法如下:

class A
{
public:
	A() {};
	~A() {};

	void sayHi() {
        std::cout << "Hi, i am A!" << std::endl;
	}

	virtual void echo(int a) {
        std::cout << "Hello, i am A!" << std::endl;
	}
};

class B : public A
{
public:
	B() {};
	~B() {};

	// ERROR: 'B::echo': method with override specifier 'override' did not override any base class methods
	// 将会在编译期间检查重写虚函数类型是否正确
	void echo() override {
		std::cout << "Hello, i am B!" << std::endl;
	}

	// ERROR: 'B::sayHi': method with override specifier 'override' did not override any base class methods
	// 只可以覆盖虚函数
	void sayHi() override {
		std::cout << "Hi, i am B!" << std::endl;
	}

	// OK
	void echo(int a) override {
		std::cout << "Hello, i am B!" << std::endl;
	}
};

9. 关键字 using

通常我们使用 using 来引入一个命名空间使用,在 C++11,赋予了它额外的能力,就是定义类型的别名。
之前我们要重定义一个类型的别名,使用的是 typedef,例如:

typedef 旧的类型名 新的类型名;
// 使用举例
typedef std::vector<int> vecNumber;
// 结构体
typdef struct _A {
	int aNumber;
}A, *PA;
// 枚举值
typedef enum _enumB {
	enumNone = 0,
}enumB;

使用 using 定义别名的语法格式是这样的:

using 新的类型名 = 旧的类型名;
// 使用举例
using uint_t = int;
using vecNumber = std::vector<int>;

过去我们想将 typedef 和 模板(template) 结合使用,不是那么特别方便:

template <typename T>
typedef std::vector<T> vecT; // ERROR: a typedef template is illegal

// 不得不使用 class 来包装一下
#include <vector>
template <typename T>
class A
{
public:
	A() {};
	~A() {};
	typedef std::vector<T> vecT;
private:
	vecT m_vecT;
};

现在,使用 using 关键字可以直接做到,例如:

#include <vector>
template <typename T>
using vecT = std::vector<T>;

int main(void)
{
    vecT<std::string> vecStr{ "Hello using keyword!" };
    vecT<std::uint16_t> vecNum{ 1,2,3,4,5 };
    return 0;
}

总结一下:

  • using 语法和 typedef 目的是一样的,只是给某些类型定义了新的别名,但是并不会创建出新的类型。
  • using 相较于 typedef 的优势在于定义函数指针别名时看起来更加直观,并且可以给模板定义别名。

10. 关键字 explicit

explicit 主要用于防止隐式转换,用于修饰构造函数、复制构造函数。
先看个例子:

class A
{
public:
    A() {};
    A(int nValue) :m_nValue(nValue) {};
    A(const A& other) { m_nValue = other.m_nValue; };
    ~A() {};

private:
    int m_nValue{ 0 };
};

int main(void)
{
    A a = 1;   // OK call A(int nValue)
    A b = a;   // OK call A(const A& other)
    return 0;
}

上述的代码,A a = 1 和 A b =a 就是是隐式调用,它是自动完成的。有时候这么写可能看上去很奇怪,直接给一个对象赋值一个数字。如果想禁止这种隐式调用,explicit 关键字就派上了用场,该关键字使用方法,是放在类成员函数的前面,例如:

class A
{
public:
    A() {};
    explicit A(int nValue) :m_nValue(nValue) {};
    explicit A(const A& other) { m_nValue = other.m_nValue; };
    ~A() {};

private:
    int m_nValue{ 0 };
};

int main(void)
{
    A a(1);  // OK
    A a = 1; // ERROR: 'initializing': cannot convert from 'int' to 'A', Constructor for class 'A' is declared 'explicit'
    A b(a);  // OK
    A b = a; // ERROR: 'initializing': cannot convert from 'A' to 'A',  Constructor for class 'A' is declared 'explicit'
    return 0;
}

11. Template 新特性

  • 自从 C++11 起, template 可以拥有 “接受个数不定的 template 实参”,称之为 variadic template。例如:
void print()
{
    std::cout << "the end!!" << std::endl;
}

template <typename T, typename... Types>
void print (const T& firstArg, const Types&... args)
{
    std::cout << firstArg << std::endl; // 打印第一个参数
    print(args...); // 递归调用
}
// 调用
print(1, 1.2, "hello template");

如果传入1个或多个实参,上述的函数就会被调用,它会把第一实参区分开,允许第一个实参被打印,然后递归调用 print() 并传入其余实参,必须提供一个 非模板重载函数 print(),才能使得整个递归动作结束。

具体模板还有很多特性,目前模板用的比较少,用到的时候再补充:

  • 非类型模板参数(Nonetype Template Parameter)
  • 模板参数默认值 (Default Template Parameter)
  • 关键字 typename 的新用法
  • 类成员模板 (Member Template)

12. 带作用域的 Enumeration 类型

在过去,我们要全局定义枚举值,若名字相同便会产生冲突报错:

enum A1
{
    enum_test = 0,
};

enum B1
{
    enum_test = 0, // ERROR:  'enum_test': redefinition; previous definition was 'enumerator'
};

定义的枚举值还会被隐式转换为 int:

enum my_enum
{
    enum0 = 0,
    enum1,
};

int nNumber = enum0;

C++11 提供了一种 带作用域的 Enumeration 类型,其与老的方式的不同是,在 enum 之后指明关键字 class,例如:

enum class Car : char { a, b, c, d }; // a 默认等于 0,b = a+1,和之前一样

在使用的时候,也有变化,须写明作用域,如 Car::a
这个特性有如下好处:

  • 绝对不会隐式转换至 int
  • 可以明确定义底层类型,如上例子中的 char 并因此获得一个保证的大小(如果这里省略掉 “: char”,默认类型是 int)
posted @ 2021-11-25 00:03  Microm  阅读(59)  评论(0编辑  收藏  举报