“Let’s begin!”
2.1 保持与C99兼容
2.1.1 预定义宏
宏名称 | 功能描述 |
---|---|
__STDC_HOSTED__ | 如果编译器的目标系统环境中包含完整的标准C库,那么这个宏就定义为1,否则宏的值为0 |
__STDC__ | C编译器中通常用这个宏的值来表示编译器的实现是否和C标准一致。 |
__STDC_VERSION__ | C编译器通常用这个宏来表示所支持的C标准的版本 |
如果用户重定义(#define)或(#undef)了预定义的宏,那么后果就是“未定义”的。
2.1.2 __ func __ 预定义标识符
其基本功能就是返回所在函数的名字。
#include <string>
#include <iostream>
using namespace std;
const char *hello() { return __func__; }
const char *world() { return __func__; }
int main()
{
cout << hello() << "," << world() << endl;
}
// 编译选项:g++ -std=c++11 xx.cpp
// 下面的方式是可行的
struct TestStruct {
TestStruct() : name(__func__) {}
const char *name;
}
// 但是这样的形式是无法通过编译的
void FuncFail(string func_name = __func__) {}
// 这是由于在参数声明时,__func__还未被定义
2.1.3 _Pragma 操作符
在C++11中,准定义了与预处理指令#pragma功能相同的操作符_Pragma。
#pragma once等同于_Pragma(“once”)
相比于预处理指令#pragma,由于_Pragma是一个操作符,因此可以用在一些宏中。而#pragma不能在宏中展开,C++11的_Pragma具有更大的灵活性。
#define ON CE("on")
#define CE(x) _Pargma(#x"ce")
ON // 等同于#pragma once
2.1.4 变长参数的宏定义以及__VA_ARGS__
在C99标准中,程序员可以使用变长参数的宏定义。
#define PR(...) printf(__VA_ARGS__)
2.1.5 宽窄字符串的连接
窄字符串(char)转换成宽字符串(wchar_t)在之前的C++标准中是未定义的行为。支持C++11标准的编译器会将窄字符串转换成宽字符串,然后再与宽字符串进行连接。
2.2 long long整型
#include <climits>
#include <cstdio>
using namespace std;
int main()
{
long long ll_min = LLONG_MIN;
long long ll_max = LLONG_MAX;
unsigned long long = ULLONG_MAX;
}
2.3 扩展的整型
C++11一共只定义了以下5种标准的有符号整数:
signed char、 short int、 int、 long int、 long long int
2.4 宏__cplusplus
在C++11中,__cplusplus被预定义为201103L。
#if __cplusplus < 201103L
#error "should use C++11 implementation"
#endif
预处理指令#error,使得非c++11标准的编译器立即报错并终止编译。
2.5 静态断言
2.5.1 断言:运行时与预处理时
在C++中,标准在<cassert>或<assert.h>头文件中为程序员提供了assert宏,用于在运行时进行断言。
在C++中,程序员可以定义宏NDEBUG来禁用assert宏。
// 事实上,assert宏在<cassert>中的实现方式类似于下列形式
#ifdef NDEBUG
#define assert(exptr) (static_cast<void>(0))
#else
// do some things
#endif
事实上,通过预处理指令#if和#error的配合,也可以让程序员在预处理阶段进行断言
2.5.2 静态断言与static_assert
static_assert使用起来非常简单,它接收两个参数,一个是断言表达式,这个表达式通常需要返回一个bool值;一个则是警告信息,它通常是一个字符串。
template <typename t, typename u> int bit_copy(t &a, u &b)
{
static_assert(sizeof(b) == sizeof(a), "the parameters of bit_copy must have same width.");
};
// 这样的错误信息就会在程序的编译时期打印,非常的方便
注意:static_assert的断言表达式的结果必须是在编译时期可以计算的表达式,即必须是常量表达式。
2.6 noexception修饰符与noexcept操作符
异常通常用于路基上可能发生的错误。
noexcept修饰函数不会抛出异常。与throw()不同的是,在C++中如果noexcept修饰的函数抛出了异常,编译器可以选择直接调用std::terminate()函数来终止程序的运行,这样的效率高于异常机制的throw()。
void excpt_func() noexcept;
void excpt_func() noexcept (常量表达式);
// 缺省为true,表示函数不会抛出异常
// 反之,则表示有异常抛出
// noexcept作为一个操作符时,通常可以用于模板
template <calss T>
void fun() noexcept(noexcept(T())) {}
// 这里fun函数是否是一个noexcept的函数,将由T()表达式是否会抛出异常所决定。
// 这里的第二个noexcept就是一个noexcept操作符。
// 当其参数是一个有可能抛出异常的表达式的时候,其返回值为false,反之为true
虽然noexcept修饰的函数通过std::terminate的调用来结束程序的执行的方式可能会带来很多问题(析构函数正常调用,栈无法自动释放)。但是提高了程序退出的速率。往往是更加有效的。
// 在C++98中,new可能会包含一些抛出的std::bad_alloc异常
void *operator new(std::size_t) throw(std::bad_alloc);
// 在C++11中,使用noexcept(false)来进行替代
void *operator new(std::size_t) noexcept(false);
noexcept更大的作用是保证应用程序的安全。一个类析构函数不应抛出异常。因此应在析构函数以及析构函数经常调用的delete函数设置成noexcept,借此提高程序的安全性
C++11中析构函数在缺省的情况下被默认设置为noexcept(true),从而阻止了异常的扩散。
2.7 快速初始化成员变量
在C++98中,支持了在类声明中使用等号“=”来初始化类中的静态成员常量。
struct init { int a = 1; double b{1.2}};
// 这在c++11中是合法的声明
简而言之:在C++11中类成员的初始化形式非常多样。
此外值得注意的是,对于非常亮的静态成员变量,C++11则与C++98保持了一致。程序员还是需要到头文件以外去定义它,这会保证编译时,类静态成员的定义最后只存在于一个目标文件中。
2.8 非静态成员的sizeof
C++98中,费静态成员变量使用sizeof是不能够通过编译的。
#include <iostream>
using namespace std;
struct Prople {
public:
int hand;
static Peiple *all;
};
int main() {
People p;
// C++98, C++11 均通过
cout << sizeof(p.hand) << endl;
cout << sizeof(People::all) << endl;
// C++98不通过,C++11通过
cout << sizeof(People::hand) << endl;
// C++98的技巧
sizeof(((People*)0)->hand);
}
2.9 扩展的friend语法
friend关键字用于声明类的友员,友员可以无视类中成员的属性。无论成员是public、protected或是private的,友元类或友元函数都可以访问。
但是同时也破坏了面向对象编程中封装性的概念。因此friend关键字充满了争议性。
// C++11中,可以为类模板声明友员了。这是C++98中无法做到的
class P;
template <typename T> class People {
friend T;
};
People<P> PP; //类型P在这里是People类型的友员
People<int> Pi; //对于int类型模板参数,友员声明被忽略(因为是基础类型的缘故)
2.10 final/override控制
C++11采用了final关键字来组织函数继续重写。final关键字的作用是使派生类不可覆盖它所修饰的虚函数。
struct Object {
virtual void fun() = 0;
};
struct Base : public Object {
void fun() final;
};
struct Derived : public Base {
void fun(); //无法通过编译
};
基类中的虚函数也是可以使用final关键字的,不过这样该虚函数无法被子类中重写,这样就失去了虚函数的意义。(虚函数生来就是要被子进程重写的)
在C++中有一个特点,对于积累声明为virtual的函数,之后重写版本都不需要声明该函数为virutal。
在C++11中为了帮助程序员写继承结构复杂的类型,引入了虚函数描述符override。声明后函数必须与重载积累中的函数同名,否则无法通过编译。
2.11 模板函数的默认模板参数
C++11中增加了默认参数模板功能
template <class T, class U = double>
void f(T t = 0, U u = 0);
void g() {
f(c, 'c'); // f<int, char>(1, 'c')
f(1); // f<int, double>(1, 0)
f(); // 错误,无法被解析
f<int>(); // f<int, double>(0, 0)
f<int, char>(); // f<int, char>(0, 0)
}
2.12 外部模板
“外部模板”是C++11中一个关于模板性能上的改进。
extern int i;
// 这样做的好处是,生成的多个目标文件中只有i变量的一份定义
对于源代码中出现的每一处模板实例化,编译器都需要去做实例化的工作;而在谅解时,连接器还需要移除重复的实例化代码。
在广泛使用模板的项目中,由于编译器会产生大量冗余代码,会极大增加编译器的编译时间和链接时间。
// C++11中,加入了外部模板的声明。
extern template void fun<int>(int);
实际上,C++11中“模板的显示实例化定义、外部模板声明和使用”好比“全局变量的定义、外部声明和使用”方式的再次使用。外部模板定义应该算作一种对编译器的编译时间及空间的优化手段。再有在项目较大的情况下,才有这样优化的意义。
2.13 局部和匿名类型作模板实参
在C++98中,标准对模板实参的类型还有一些限制。(局部的类型和匿名的类型在C++98中都不能做模板类的实参)
template<typename T> class X {};
template<typename T> void TempFun(T t) {};
struct A{} a;
struct {int i;} b; // 匿名类型变量
typedef struct {int i;} B; // 匿名类型
void Fun()
{
struct C {} c; // 局部类型
X<A> x1;
X<B> x2; // C++98错误,C++11通过
X<C> x3; // C++98错误,C++11通过
TempFun(a);
TempFun(b); // C++98错误,C++11通过
TempFun(c); // C++98错误,C++11通过
// 但是,下面这种写法在C++11中也是不被允许的
X<struct {int a;}> d;
}
近期评论