深刻理解C++11

深刻理解C++11ios

一、不少 现实 的 编译器 都 支持 C99 标准 中的__ func__ 预约 义 标识符 功能, 其 基本 功能 就是 返回 所在 函数 的 名字。c++

  编译器 会 隐式 地 在 函数 的 定义 以后 定义__ func__ 标识符。程序员

const char* hello() 
{ 
    static const char* __func__ = "hello";
    return __func__; 
}

  __func__ 能够用于构造函数中。ide

#include < iostream> 
using namespace std;

struct TestStruct {
  TestStruct () : name(__ func__) {}
  const char *name;
};

int main()
{
  TestStruct ts;
  cout << ts. name << endl; // TestStruct
}
// 编译 选项: g++ -std= c++ 11 2- 1- 3. cpp

  不过 将__ fun__ 标识符 做为 函数参数的默认值不容许 的,函数

void FuncFail( string func_ name = __func__) {};// 没法 经过 编译

 

 二、在 C99 标准 中, 程序员 能够 使用 变长参数的宏定义。 变长 参数 的 宏 定义 是 指在 宏 定义 中 参数 列表 的 最后 一个 参数 为 省略号, 而 预 定义 宏__ VA_ ARGS__ 则 可 以在 宏 定义 的 实现 部分 替换 省略号 所 表明 的 字符串。优化

#define PR(...) printf(__ VA_ ARGS__)

  一个应用的例子。this

#include < stdio. h> 

#define LOG(...) {\
  fprintf( stderr,"% s: Line %d:\ t", __FILE__, __LINE__);\
  fprintf( stderr, __VA_ ARGS__);\
  fprintf( stderr,"\ n");\
}

int main() {
  int x = 3; // 一些 代码...
  LOG(" x = %d", x); // 2- 1- 5. cpp: Line 12: x = 3
}
// 编译 选项: g++ -std= c++ 11 2- 1- 5. cpp

 

三、long long 是C99标准。C++11中才将其定为正式标准。事实上,在C++11之前,不少编译器也支持了long long。spa

  long long 整型 有 两种: long long 和 unsigned long long。 在 C++ 11 中, 标准 要求 long long 整型 能够 在 不一样 平台 上有 不一样 的 长度, 但 至少 有 64 位。c++11

  咱们 在 写 常数 字面 量 时, 能够 使用 LL 后缀( 或是 ll) 标识 一个 long long 类型 的 字面 量, 而 ULL( 或 ull、 Ull、 uLL) 表示 一个 unsigned long long 类型 的 字面 量。code

long long int lli = -9000000000000000000LL; 
unsigned long long int ulli = -9000000000000000000ULL;

   下面 的 类型 是 等价 的: long long、 signed long long、 long long int、 signed long long int; 而 unsigned long long 和 unsigned long long int 也是 等价 的。

  对于 printf 函数 来讲, 输出 有 符号 的 long long 类型 变量 能够 用 符号% lld, 而无 符号 的 unsigned long long 则 能够 采用% llu。

 

四、__cplusplus

  好比 在 C++ 03 标准 中,__ cplusplus 的 值 被 预约 为 199711L, 而在 C++ 11 标准 中, 宏__ cplusplus 被 预 定义 为 201103L。

  这点 变化 能够 为 代码 所用。 好比 程序员 在想 肯定 代码 是 使用 支持 C++ 11 编译器 进行 编译 时, 那么 能够 按下 面的 方法 进行 检测:

  

#if __cplusplus < 201103L #error "should use C++ 11 implementation" #endif

 

五、assert,运行时检查 

  在 C++ 中, 标准 在< cassert> 或< assert. h> 头 文件 中为 程序员 提供 了 assert 宏, 用于 在 运行时 进行 断言。

  在 C++ 中, 程序员 也能够 定义 宏 NDEBUG 来 禁用 assert 宏。 这对 发布 程序 来讲 仍是 必要 的。

#ifdef NDEBUG 
    # define assert( expr) (static_ cast< void> (0)) 
#else 
    ... 
#endif

  能够 看到, 一旦 定义 了 NDBUG 宏, assert 宏 将被 展开 为 一条 无心义 的 C 语句( 一般 会被 编译器 优化 掉)。

 

六、static_assert,静态检查。

  c++11中引入了static_assert。在过去,须要本身手动实现,或使用boost提供的功能 BOOST_STATIC_ASSERT。一个可能的手动实现以下:

#define assert_ static( e) \ 
    do { \ 
    enum { assert_ static__ = 1/( e) }; \ 
} while (0)

  一个使用样例以下:

static_ assert( sizeof( int) == 8, "This 64- bit machine should follow this!"); 
int main() 
{
     return 0; 
} 

   static_ assert 的 断言 表达式 的 结果 必须 是在 编译 时期 能够 计算 的 表达式, 即 必须 是 常量 表达式。 若是 读者 使用 了 变量, 则 会 致使 错误,

int positive( const int n) 
{
    static_ assert( n > 0, "value must > 0"); 
} // 编译 选项: g++ -std= c++ 11 -c 2- 5- 6. cpp

 

七、noexcept

  在 excpt_ func 函数 声明 以后, 咱们 定义 了 一个 动态 异常 声明 throw( int, double), 该 声明 指出 了 excpt_ func 可能 抛出 的 异常 的 类型。 事实上, 该 特性 不多 被 使用, 所以 在 C++ 11 中 被弃 用了( 参见 附录 B), 而 表示 函数 不会 抛出 异常 的 动态 异常 声明 throw() 也 被 新的 noexcept 异常 声明 所 取代。

  如下的语法已被抛弃:

void excpt_ func() throw( int, double) { ... }

  noexcept 的用法:

void excpt_ func() noexcept (常量 表达式);

   下面的第二个 noexcept 就是 一个 noexcept 操做 符。 当 其 参数 是 一个 有可能 抛出 异常 的 表达式 的 时候, 其 返回 值 为 false, 反之 为 true。

template < class T> void fun() noexcept( noexcept( T())) {}

   C++ 11 标准 中 让 类 的 析构函数 默认 也是 noexcept( true) 的。 固然, 若是 程序员 显 式 地 为 析 构 函数 指定 了 noexcept, 或者 类 的 基 类 或 成员 有 noexcept( false) 的 析 构 函数, 析 构 函数 就 不会 再 保持 默认值。

#include < iostream> using namespace std; 
struct A { ~ A() { throw 1; } };
struct B { ~ B() noexcept( false) { throw 2; } };
struct C { B b; };
int funA() { A a; }
int funB() { B b; }
int funC() { C c; }
int main() {
  try { funB(); } catch(...){ cout << "caught funB." << endl; // caught funB. }
  try { funC(); } catch(...){ cout << "caught funC." << endl;
// caught funC. }
  try { funA();
// terminate called after throwing an instance of 'int' } catch(...){ cout << "caught funA." << endl; } } // 编译 选项: g++ -std= c++ 11 2- 6- 2. cpp

  不管是 析 构 函数 声明 为 noexcept( false) 的 类 B, 仍是 包含 了 B 类型 成员 的 类 C, 其 析 构 函数 都是 能够 抛出 异常 的。 只有 什么 都没 有 声明 的 类 A, 其 析 构 函数 被 默认 为 noexcept( true), 从而 阻止 了 异常 的 扩散。

 

八、在 C++ 98 中, 支持 了 在 类 声明 中 使用 等号“=” 加 初始 值 的 方式, 来 初始化 类 中 静态成员常量。 这种 声明 方式 咱们 也称 之为“ 就地” 声明。 就地 声明 在 代码 编写 时 很是 便利, 不过 C++ 98 对 类 中就 地 声明 的 要求 却 很是 高。 若是 静态 成员 不知足 常量 性, 则 不能够 就地 声明, 并且 即便常量 的 静态 成员 也 只能 是 整型 或者 枚举型 才能 就地 初始化。 而非 静态 成员 变量 的 初始化 则 必须 在 构造 函数 中 进行。

class Init{ 
    public: Init(): a( 0){} 
    Init( int d): a( d){} 
private: 
    int a; 
    const static int b = 0;
    int c = 1; // 成员, 没法 经过 编译 
    static int d = 0; // 成员, 没法 经过 编译 
    static const double e = 1. 3; // 非 整型 或者 枚举, 没法 经过 编译     
    static const char * const f = "e"; // 非 整型 或者 枚举, 没法 经过 编译 
}; // 编译 选项: g++ -c 2- 7- 1. cpp

   使用 g++ 的 读者 可能 发现 就地 初始化 double 类型 静态 常量 e 是 能够 经过 编译 的, 不过 这 实际 是 GNU 对 C++ 的 一个 扩展, 并不 听从 C++ 标准)。

  在 C++ 11 中, 标准 还 容许 使用 等号= 或者 花 括号{} 进行 就地 的 非 静态 成员 变量 初始化。

struct init{ int a = 1; double b {1. 2}; };

   就地初始化 和 初始化 列表 并不 冲突。 程序员 能够 为 同一 成员 变量 既 声明 就地 的 列表 初始化, 又在 初始化 列表 中 进行 初始化, 只不过 初始化 列表 老是 看起来“ 后 做用于” 非 静态 成员。 也就是说, 初始化 列表 的 效果 老是 优先于 就地 初始化 的。

  

#include < string> using namespace std; 
class Mem { public: Mem( int i): m( i){} private: int m; };

class Group {
 public:
  Group(){} // 这里 就不 须要 初始化 data、 mem、 name 成员 了
  Group( int a): data( a) {}
// 这里 就不 须要 初始化 mem、 name 成员 了
  Group( Mem m) : mem( m) {}
// 这里 就不 须要 初始化 data、 name 成员 了
  Group( int a, Mem m, string n): data( a), mem( m), name( n){}
 private:
  int data = 1;
  Mem mem{ 0};
  string name{" Group"};
};
// 编译 选项: g++ 2- 7- 4. cpp -std= c++ 11 -c

  对于 非 常量 的 静态 成员 变量, C++ 11 则 与 C++ 98 保持 了 一致。 程序员 仍是 须要 到头 文件 之外 去 定义 它, 这 会 保证 编译 时, 类 静态 成员 的 定义 最后 只 存在 于 一个 目标 文件 中。

 

九、sizeof

  不过 在 C++ 98 标准 中, 对 非静态成员变量 使用 sizeof 是 不能 够 经过 编译 的。

#include < iostream> 
using namespace std;
struct People { 
  public: 
    int hand; 
    static People * all; 
}; 
int main() { 
    People p; 
    cout << sizeof( p. hand) << endl; // C++ 98 中 经过, C++ 11 中 经过 
    cout << sizeof( People:: all) << endl; // C++ 98 中 经过, C++ 11 中 经过 
    cout << sizeof( People:: hand) << endl; // C++ 98 中 错误, C++ 11 中 经过 
} // 编译 选项: g++ 2- 8- 1. cpp

  而在 C++ 98 中, 只有 静态 成员, 或者 对象的实例 才能 对其 成员 进行 sizeof 操做。 所以 若是 读者 只有 一个 支持 C++ 98 标准 的 编译器, 在 没有 定义 类 实例 的 时候, 要 得到 类 成员 的 大小, 咱们 一般 会 采用 如下 的 代码:

sizeof((( People*) 0)-> hand);

 

十、而在 C++ 11 中, 咱们 无需 这样 的 技巧, 由于 sizeof 能够 做用 的 表达式 包括了 类 成员 表达式。

而在 C++ 11 中, 咱们 无需 这样 的 技巧, 由于 sizeof 能够 做用 的 表达式 包括了 类 成员 表达式。

 

十一、friend

  C++98 中没法对 typedef 进行 friend. C++11中能够,而且C++11中省略了 class.

class Poly; 
typedef Poly P; 
class LiLei { 
    friend class Poly; // C++ 98 经过, C++ 11 经过 
}; 
class Jim { 
    friend Poly; // C++ 98 失败, C++ 11 经过 
}; 
class HanMeiMei { 
    friend P; // C++ 98 失败, C++ 11 经过 
}; // 编译 选项: g++ -std= c++ 11 2- 9- 1. cpp

  C++11 中能够为类模板声明友元,这在C++98中是没法作到的。

class P; 
template < typename T>
class People { friend T; };
People< P> PP; // 类型 P 在这里 是 People 类型 的 友 元
People< int> Pi;
// 对于 int 类型 模板 参数, 友 元 声明 被 忽略
// 编译 选项: g++ -std= c++ 11 2- 9- 2. cpp

 

十二、final / override

  C++98 中没有方法阻止 vritual 函数被重载。

  

  C++11 中添加了 final 关键字来实现此功能。

  

  C++11 中,继承类重载时,最好加上 override 关键字,有了override 关键字,编译器能够帮助检测重载是否成功。

struct Base { 
    virtual void Turing() = 0; 
    virtual void Dijkstra() = 0; 
    virtual void VNeumann( int g) = 0; 
    virtual void DKnuth() const; 
    void Print(); 
}; 

struct DerivedMid: public Base { 
    // void VNeumann( double g); // 接口 被 隔离 了, 曾 想 多 一个 版本 的 VNeumann 函数 
}; 

struct DerivedTop : public DerivedMid { 
    void Turing() override; 
    void Dikjstra() override; // 没法 经过 编译, 拼写 错误, 并不是 重载 
    void VNeumann( double g) override; // 没法 经过 编译, 参数 不一致, 并不是 重载 
    void DKnuth() override; // 没法 经过 编译, 常量 性 不一致, 并不是 重载 
    void Print() override; // 没法 经过 编译, 非 虚 函数 重载 
; // 编译 选项: g++ -c -std= c++ 11 2- 10- 3. cpp

 

1三、函数模板的默认参数

  C++98中,只有类模板参数能够有默认值,而函数模板不行。C++11中,这一限制已经解除了。

void DefParm( int m = 3) {} // c++ 98 编译 经过, c++ 11 编译 经过 

template < typename T = int> 
class DefClass {}; // 类模板参数,c++ 98 编译 经过, c++ 11 编译 经过 template 

< typename T = int> 
void DefTempParm() {}; // 函数模板参数,c++ 98 编译 失败, c++ 11 编译 经过

  类模板的参数必须从右到左,而函数模板则无此规定。

template< typename T1, typename T2 = int> 
class DefClass1; 

template< typename T1 = int, typename T2> 
class DefClass2; // 没法 经过 编译 

template< typename T, int i = 0> 
class DefClass3; 

template< int i = 0, typename T> 
class DefClass4; // 没法 经过 编译 

template< typename T1 = int, typename T2> 
void DefFunc1( T1 a, T2 b); 

template< int i = 0, typename T> 
void DefFunc2( T a); 

 

1四、模板的显式实例化、外部模板声明。

  C++98中已经有了模板显式实例化的功能。

// 对于如下模板
template < typename T> void fun( T) {}

// 像下面这样显式实例化
template void fun< int>( int);

  C++11中加入了外部模板声明的功能。外部 模板 的 声明 跟 显 式 的 实例 化 差很少, 只是 多了 一个 关键字 extern。

extern template void fun< int>( int);

   若是 外部模板声明 出现于某个编译单元 中, 那么与之对应的显示实例化必须出现于另外一个编译单元中或者同一个 编译 单元 的 后续 代码 中; 外部 模板 声明 不能用于静态函数( 即 文件域函数), 但能够 用于 类静态成员函数( 这一点 是 显而易见 的, 由于 静态 函数 没有 外部 连接 属性, 不可能 在 本 编译 单元 以外 出现)。

  外部 模板 定义 更应该 算做 一种 针对 编译器 的 编译时间及空间 的优化手段。 不少 时候, 因为 程序员 低估 了 模板 实例 化 展开 的 开销, 所以 大量 的 模板 使用 会在 代码 中产 生 大量 的 冗余。 这种冗余, 有的时候 已经 使得 编译器和连接器 力不从心。

 

1五、局部、匿名类型做模板实参

  局部的类型匿名的类型 在 C++ 98 中 都不 能作 模板类的实参

template < typename T> 
class X {}; 

template < typename T> 
void TempFun( T t){}; 

struct A{} a; 
struct {int i;} b; // b是 匿名类型变量 
typedef struct {int i;} B; // B是 匿名类型 

void Fun() { 
    struct C {} c; // C是 局部类型 
    
    X< A> x1; // C++ 98 ok, C++ 11 经过 
    X< B> x2; // C++ 98 fail, C++ 11 经过 
    X< C> x3; // C++ 98 fail, C++ 11 经过 
    
    TempFun( a); // C++ 98 ok, C++ 11 经过 
    TempFun( b); // C++ 98 fail, C++ 11 经过 
    TempFun( c); // C++ 98 fail, C++ 11 经过 
}

  除了 匿名的结构体(anonymous struct)以外, 匿名的联合体(anonymous union) 以及 枚举类型(union), 在 C++ 98 标准 下 也都是没法作 模板的实参的。 

  现在看来这都是没必要要的限制。 因此 在 C++ 11 中标准容许了以上类型作模板参数的作法。虽然如此,但如下写法是不被接受的。

template < typename T> 
struct MyTemplate { }; 
int main() {
    MyTemplate< struct { int a; }> t; // 没法 编译 经过, 匿名类型的声明不能在模板实参位置 
    return 0; 
} // 编译 选项: g++ -std= c++ 11 2- 13- 2. cpp
相关文章
相关标签/搜索