深入理解C++11(1)

本文深入介绍C++11的新特性。包括C99标准中预定义标识符、变长参数宏定义等功能在C++11中的应用,还阐述了long long类型、__cplusplus宏的变化,以及noexcept、就地初始化、sizeof对类成员的支持等新特性,对比了C++98与C++11在多方面的差异。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

深入理解C++11(1)

【深入理解C++11】

1、很多 现实 的 编译器 都 支持 C99 标准 中的__ func__ 预定 义 标识符 功能, 其 基本 功能 就是 返回 所在 函数 的 名字。

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

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

__func__ 可以用于构造函数中。

复制代码

#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__) {};// 无法 通过 编译

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

#define PR(...) printf(__ VA_ ARGS__)
  一个应用的例子。

复制代码

#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

复制代码

3、long long 是C99标准。C++11中才将其定为正式标准。事实上,在C++11以前,很多编译器也支持了long long。

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

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

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。

4、__cplusplus

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

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

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

5、assert,运行时检查

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

在 C++ 中, 程序员 也可以 定义 宏 NDEBUG 来 禁用 assert 宏。 这对 发布 程序 来说 还是 必要 的。

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

可以 看到, 一旦 定义 了 NDBUG 宏, assert 宏 将被 展开 为 一条 无意义 的 C 语句( 通常 会被 编译器 优化 掉)。

6、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

7、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), 从而 阻止 了 异常 的 扩散。

8、在 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 保持 了 一致。 程序员 还是 需要 到头 文件 以外 去 定义 它, 这 会 保证 编译 时, 类 静态 成员 的 定义 最后 只 存在 于 一个 目标 文件 中。

9、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);

10、而在 C++ 11 中, 我们 无需 这样 的 技巧, 因为 sizeof 可以 作用 的 表达式 包括了 类 成员 表达式。

而在 C++ 11 中, 我们 无需 这样 的 技巧, 因为 sizeof 可以 作用 的 表达式 包括了 类 成员 表达式。

11、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

复制代码

12、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

复制代码

13、函数模板的默认参数

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); 

复制代码

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

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

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

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

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

extern template void fun< int>( int);

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

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

15、局部、匿名类型作模板实参

局部的类型 和 匿名的类型 在 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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值