第 44 章 兼容性(Compatibility)
目录
44.2 C++11 扩展(C++11 Extensions)
44.2.1 语言特征(Language Features)
44.2.2 标准库组件(Standard_Library Components)
44.2.3 弃用特征(Deprecated Features)
44.2.4 应对较旧的 C++ 实现版本(Coping with Older C++ Implementations)
44.3 C/C++ 兼容性(C/C++ Compatibility)
44.3.1 C 和 C++ 是兄弟语言(C and C++ Are Siblings)
44.3.2 “隐性”差异(‘‘Silent’’ Differences)
44.3.3 不是 C++ 的 C 代码(C Code That Is Not C++)
44.3.3.1 “经典 C语言”问题(“Classic C’’ Problems)
44.3.3.2 C++ 未采用的 C 语言特性(C Features Not Adopted by C++)
44.3.4 不是 C 的C++代码(C++ Code That Is Not C)
44.1 引言(Introduction)
本章讨论标准 C++(由 ISO/IEC 14882-2011 定义)与早期版本(例如 ISO/IEC 14882-1998),标准 C(由 ISO/IEC 9899-2011 定义)与早期版本(例如经典 C)之间的区别。其目的是:
• 简要列出 C++11 中的新增特性
• 记录可能给程序员带来问题的差异
• 指出解决问题的方法
大多数兼容性问题出现在以下情况下:尝试将 C 程序升级到 C++ 程序,尝试将 C++ 程序从旧版本移植到新版本(例如,从 C++98 移植到 C++11),或者尝试使用旧编译器编译包含现代特性的 C++ 代码。本文的目的并非列举所有可能的兼容性问题,而是列出最常见的问题并提供标准的解决方案。
在考虑兼容性问题时,一个关键问题是程序需要在哪些不同的实现环境下运行。对于学习 C++ 而言,使用功能最完善、最有帮助的实现版本是明智之举。而对于发布产品,则可能需要采取更为保守的策略,以最大限度地提高产品在不同系统上的运行兼容性。过去,这曾是(而且更多时候只是一个借口)避免使用一些认为是新颖的 C++ 特性的原因。然而,
各种实现版本正在趋于统一,因此,跨平台可移植性问题不再像以前那样需要我们格外谨慎。
44.2 C++11 扩展(C++11 Extensions)
首先,我列出了为C++11标准添加到C++语言中的语言特性和标准库组件。接下来,我将讨论如何兼容旧版本(特别是C++98)。
44.2.1 语言特征(Language Features)
浏览语言特性列表可能会让人感到相当困惑。请记住,语言特性并非孤立存在的。特别是,C++11 中新增的大多数特性如果脱离旧版本特性提供的框架,就毫无意义。以下列表大致按照这些特性在本书中首次出现的顺序排列:
[1] 使用{}(花括号)列表进行统一且通用的初始化(§2.2.2,§6.3.5)
[2] 从初始化器推导类型:auto (§2.2.2,§6.3.6.1)
[3] 防止窄化转换(§2.2.2,§6.3.5)
[4] 通用且保证的常量表达式:constexpr(§2.2.3,§10.4,§12.1.6)
[5] 基于范围的for循环(§2.2.5,§9.5.1)
[6] 空指针关键字:nullptr(§2.2.5,§7.2.2)
[7] 作用域限定且强类型的枚举:enum class(§2.3.3,§8.4.1)
[8] 编译时断言:static_assert(§2.4.3.3,§24.4)
[9] 花括号列表到std::initializer_list的语言映射(§3.2.1.3,§17.3.4)
[10] 右值引用(启用移动语义;§3.3.2,§7.7.2)
[11] 以 >> 结尾的嵌套模板参数(两个>之间没有空格;§3.4.1)
[12] Lambda表达式(§3.4.3,§11.4)
[13] 可变参数模板(§3.4.4,§28.6)
[14] 类型和模板别名(§3.4.5,§6.5,§23.6)
[15] Unicode字符(§6.2.3.2,§7.3.2.2)
[16] long long整数类型(§6.2.4)
[17] 对齐控制:alignas和alignof(§6.2.9)
[18] 将表达式的类型用作声明中的类型:decltype(§6.3.6.1)
[19] 原始字符串字面量(§7.3.2.1)
[20] 通用POD(§8.2.6)
[21] 推广union (§8.3.1)
[22] 局部类作为模板参数 (§11.4.2, §25.2.1)
[23] 后置返回类型语法 (§12.1.4)
[24] 属性语法和两个标准属性:[[carries_dependency]] (§41.3) 和
[[noreturn]] (§12.1.7)
[25] 防止异常传播:noexcept 说明符 (§13.5.1.1)
[26] 测试表达式中抛出异常的可能性:noexcept 运算符 (§13.5.1.1)
[27] C99 特性:扩展整数类型(即可选更长整数类型的规则;§6.2.4);
窄字符串/宽字符串的连接;__func__ 和 __STDC_HOSTED__ (§12.6.2);
_Pragma(X) (§12.6.3);可变参数宏和空宏参数 (§12.6)
[28] inline命名空间 (§14.4.6)
[29] 委托构造函数 (§17.4.3)
[30] 类内成员初始化器 (§17.4.4)
[31] 默认行为控制:default (§17.6) 和 delete (§17.6.4)
[32] 显式转换运算符 (§18.4.2)
[33] 用户定义字面量 (§19.2.6)
[34] 更显式的template实例化控制:extern template (§26.2.2)
[35] 函数模板的默认模板参数 (§25.2.5.1)
[36] 继承构造函数 (§20.3.5.1)
[37] 重写控制:override 和 final (§20.3.4)
[38] 更简单更通用的 SFINAE 规则 (§23.5.3.2)
[39] 内存模型 (§41.2)
[40] 线程局部存储:thread_local (§42.2.8)
我并没有试图列出 C++11 相对于 C++98 的所有细微变化。有关这些功能的历史背景,请参见第 1.4 节。
44.2.2 标准库组件(Standard_Library Components)
C++11 对标准库的改进主要体现在两个方面:新增组件(例如正则表达式匹配库)以及对 C++98 组件的改进(例如容器的移动构造函数)。
[1] 容器的initializer_list构造函数(§3.2.1.3,§17.3.4,§31.3.2)
[2] 容器的移动语义(§3.3.1,§17.5.2,§31.3.2)
[3] 单向链表:forward_list(§4.4.5,§31.4.2)
[4] 哈希容器:unordered_map,unordered_multimap,unordered_set 和 unordered_multiset(§4.4.5,§31.4.3)
[5] 资源管理指针:unique_ptr,shared_ptr 和 weak_ptr(§5.2.1,§34.3)
[6] 并发支持:thread(§5.3.1,§42.2) ,互斥锁(§5.3.4,§42.3.1) ,锁(§5.3.4,
§42.3.2)和条件变量(§5.3.4.1,§42.3.4)
[7] 更高级的并发支持:packaged_thread,future,promise 和 async()(§5.3.5,
§42.4)
[8] 元组:tuple(§5.4.3,§28.5,§34.2.4.2)
[9] 正则表达式:regex(§5.5,第 37 章)
[10] 随机数:uniform_int_distribution,normal_distribution,random_engine 等
(§5.6.3,§40.7)
[11] 整型类型名称,例如 int16_t,uint32_t 和 int_fast64_t(§6.2.8、§43.7)
[12] 固定大小的连续序列容器:array(§8.2.4,§34.2.1)
[13] 复制和重新抛出异常(§30.4.1.2)
[14] 使用错误代码报告错误:system_error(§30.4.3)
[15] 容器的 emplace() 操作 (§31.3.6)
[16] 广泛使用 constexpr 函数
[17] 系统地使用 noexcept 函数
[18] 改进的函数适配器:function 和 bind() (§33.5)
[19] string到数值的转换 (§36.3.5)
[20] 作用域分配器 (§34.4.4)
[21] 类型特征,例如 is_integral 和 is_base_of (§35.4)
[22] 时间工具:duration 和 time_point (§35.2)
[23] 编译时比率数算术:ratio (§35.3)
[24] 终止进程:quick_exit (§15.4.3)
[25] 更多算法,例如 move(),copy_if() 和 is_sorted()(第 32 章)
[26] 垃圾回收 ABI (§34.5)
[27] 底层并发支持:atomic操作 (§41.3)
有关标准库的更多信息可以在以下章节找到:
• 第 4 章,第 5 章和第四部分
• 实现技术示例:vector (§13.6)、string (§19.3) 和tuple (§28.5)
• 新兴的 C++11 标准库专题文献,例如 [Williams,2012]
• 简要的历史回顾可在 §1.4 中找到。
44.2.3 弃用特征(Deprecated Features)
通过弃用某个功能,标准委员会表达了希望该功能最终被移除的意愿(§iso.D)。然而,委员会无权立即移除一个被广泛使用的功能——无论该功能多么冗余或存在潜在危险。因此,弃用是对用户发出的强烈提示,建议避免使用该功能。该功能将来可能会移除。编译器很可能会对使用已弃用功能的代码发出警告。
• 对于带有析构函数的类,生成复制构造函数和复制赋值运算符的功能已弃用。
• 不再允许将一个字符串字面量赋值给 char* 类型变量 (§7.3.2)。
• C++98 异常规范已弃用:
void f() throw(X,Y); // C++98;现已弃用
与异常规范相关的支持功能,例如 unexcepted_handler,set_unexpected(),
get_unexpected() 和 unexpected(),也同样被弃用。请改用 noexcept (§13.5.1.1)。
• 一些 C++ 标准库函数对象和相关函数已弃用:
unary_function,binary_function,pointer_to_unary_function,pointer_to_binary_function,ptr_fun(),mem_fun_t,mem_fun1_t,mem_fun_ref_t,mem_fun_ref1_t,mem_fun(),const_mem_fun_t,const_mem_fun1_t,const_mem_fun_ref_t,const_mem_fun_ref1_t,binder1st,bind1st(),binder2nd、bind2nd()。请改用 function 和 bind()(§33.5)。
• auto_ptr 已被弃用。请改用 unique_ptr(§5.2.1,§34.3.1)。
此外,委员会还删除了几乎无人使用的导出功能,因为它功能复杂,而且主要供应商也没有提供该功能。
在引入命名类型转换(§11.5.2)时,就应该废弃 C 风格的类型转换。程序员应该认真考虑在自己的程序中禁用 C 风格的类型转换。如果需要显式类型转换,可以使用 static_cast,reinterpret_cast,const_cast 或它们的组合来实现 C 风格类型转换的功能。应该优先使用命名类型转换,因为它们更清晰、更易于理解。
44.2.4 应对较旧的 C++ 实现版本(Coping with Older C++ Implementations)
自 1983 年以来,C++ 一直广泛使用(§1.4)。此后,C++ 定义了多个版本,并涌现出许多独立开发的实现。制定标准的基本目标是确保实现者和用户都能使用统一的 C++ 定义。从 1998 年起,程序员可以依赖 ISO C++98 标准,现在我们有了 ISO C++11 标准。
遗憾的是,人们使用五年前的 C++ 实现来首次认真学习 C++ 并非罕见。典型的原因是这些实现版本随处可见且免费。如果可以选择,任何有自尊心的专业人士都不会使用如此过时的版本。此外,许多现代高质量的实现版本也都是免费提供的。对于初学者来说,使用旧版本会带来严重的隐性成本。由于缺乏语言特性和库支持,初学者必须努力解决在较新版本中已经解决的问题。使用功能匮乏的旧版本,尤其是在使用过时的教程指导下,会扭曲初学者的编程风格,并对 C++ 的本质产生片面的理解。最初学习 C++ 的最佳子集并非是低级功能集(也不是常见的 C 和 C++ 子集;参见 §1.3)。特别是,为了简化学习并对 C++ 编程的可能性有一个良好的初步印象,我建议依赖标准库,并大量使用类、模板和异常。
在某些情况下,由于政治原因或缺乏合适的工具链,人们仍然倾向于使用 C 语言而不是 C++。如果你必须使用 C 语言,请使用 C 和 C++ 的通用子集进行编写。这样,你可以获得一定的类型安全性,提高代码的可移植性,并且在将来可以使用 C++ 功能时也能做好准备。另请参见第 1.3.3 节。
尽可能使用符合标准的实现,并尽量减少对语言中实现定义和未定义特性的依赖。设计时应假设可以使用完整的语言特性,只有在必要时才使用变通方法。这样做可以编写出比针对 C++ 最低通用子集进行设计时更结构化、更易于维护的程序。此外,仅在必要时才使用特定于实现的语言扩展。另请参见第 1.3.2 节。
44.3 C/C++ 兼容性(C/C++ Compatibility)
除少数例外,C++ 是 C 语言的超集(此处指 ISO/IEC 9899:2011(E) 标准定义的 C11)。大多数差异源于 C++ 对类型检查的更高要求。编写良好的 C 程序通常也是有效的 C++ 程序。编译器可以检测出 C++ 和 C 之间的所有差异。C99/C++11 的不兼容之处列于 §iso.C 中。截至本文撰写之时,C11 仍然是一个较新的标准,大多数 C 代码仍然是经典 C 或 C99 标准的代码。
44.3.1 C 和 C++ 是兄弟语言(C and C++ Are Siblings)
经典的 C 语言有两个主要分支:ISO C 和 ISO C++。多年来,这两种语言以不同的速度和方向发展。由此导致的结果之一是,每种语言都以略微不同的方式支持传统的 C 风格编程。由此产生的不兼容性可能会给同时使用 C 和 C++ 的程序员、使用一种语言编写代码但使用另一种语言实现的库的程序员,以及为 C 和 C++ 开发库和工具的开发人员带来诸多麻烦。
我怎么能把 C 和 C++ 称为兄弟语言呢?显然,C++ 是 C 的后代。不过,看看下面这个简化的家族树:

实线表示特征的大量继承,虚线表示主要特征的借用,点线表示次要特征的借用。由此可见,ISO C 和 ISO C++ 是 K&R C 的两个主要后代,并且是兄弟关系。它们都继承了经典 C 语言的关键特性,但两者都与经典 C 语言并非完全兼容。我从以前贴在Dennis Ritchie终端上的贴纸上借用了“经典 C”这个术语。它指的是 K&R C 加上枚举和struct赋值功能。
对于程序员来说,不兼容性之所以令人头疼,部分原因在于它会导致各种可能性呈指数级增长。让我们来看一个简单的Venn图:

这些区域并非按比例绘制。C++11 和 C11 都包含大部分 K&R C 的功能。C++11 也包含大部分 C11 的功能。大多数不同的区域都包含一些特有的功能。例如:
| C89 | 调用未声明的函数 |
| C99 | 可变长度数组 (VLA) |
| C++ | 模板 |
| C89 和 C99 | Algol风格的函数定义(ALGOL风格指代ALGOL编程语言(Algorithmic Language)及其衍生影响的编程范式,它在编程语言设计史上具有里程碑意义,其核心特点包括形式化语法、块结构和递归支持,这些创新深刻塑造了现代编程语言的基石。) |
| C89 和 C++ | 将 C99 关键字 restrict 用作标识符 |
| C++ 和 C99 | // 这是注释 |
| C89, C++, 和 C99 | struct |
| C++11 | 移动语义 (使用右值引用; &&) |
| C11 | 使用 _Generic 关键字的类型通用表达式 |
| C++11 和 C11 | 原子操作 |
需要注意的是,C 和 C++ 之间的差异并非完全源于 C++ 对 C 所做的更改。在某些情况下,这些不兼容性源于 C 在很久之后才以不兼容的方式引入了 C++ 中已有的特性。例如,将 T* 类型赋值给 void* 类型以及全局常量链接方式的差异 [Stroustrup, 2002]。有时,某个特性甚至是在它成为 ISO C++ 标准的一部分之后才以不兼容的方式引入到 C 语言中,例如 inline 关键字的含义细节。
44.3.2 “隐性”差异(‘‘Silent’’ Differences)
除少数例外,C++ 和 C 语言编写的程序在两种语言中的含义基本相同。幸运的是,这些例外(通常被称为“隐性差异”)都比较罕见:
• 在 C 语言中,字符常量和枚举类型的大小等于 sizeof(int)。在 C++ 中,sizeof('a') 等于 sizeof(char)。
• 在 C 语言中,枚举成员是 int 类型,而在 C++ 中,实现可以选择最适合枚举类型的任何大小(§8.4.2)。
• 在 C++ 中,struct的名称会引入其声明所在的作用域;而在 C 语言中则不会。因此,在 C++ 中,在内部作用域声明的结构体名称可以隐藏外部作用域中的同名struct。例如:
int x[99];
void f()
{
struct x { int a; };
sizeof(x); /* size of the array in C, size of the struct in C++ */
sizeof(struct x); /* size of the struct */
}
44.3.3 不是 C++ 的 C 代码(C Code That Is Not C++)
导致大多数实际问题的 C/C++ 不兼容性并非难以察觉。大多数问题编译器都能轻松检测到。本节列举了一些不符合 C++ 规范的 C 代码示例。其中大多数在现代 C 语言中被认为是糟糕的编程风格,甚至已经过时。有关不兼容性的完整列表,请参见 §iso.C。
• 在 C 语言中,大多数函数无需事先声明即可调用。例如:
int main() // not C++; poor style in C
{
double sq2 = sqrt(2); /* call undeclared function */
printf("the square root of 2 is %g\n",sq2); /* call undeclared function */
}
在C语言中,通常建议完整且一致地使用函数声明(函数原型)。如果遵循这一明智的建议,尤其是在C编译器提供强制执行此规则的选项时,C代码就能符合C++的规则。如果调用了未声明的函数,你需要非常熟悉这些函数以及C语言的规则,才能判断是否犯了错误或引入了可移植性问题。例如,前面的main()函数作为C程序至少包含两个错误。
• 在 C 语言中,如果函数声明时没有指定任何参数类型,则该函数可以接受任意数量、任意类型的参数。
void f(); /* argument types not mentioned */
void g()
{
f(2); /* poor style in C; not C++ */
}
ISO C 标准认为这种用法已经过时。
• 在 C 语言中,函数可以使用一种语法进行定义,该语法可以选择性地在参数列表之后指定参数类型:
void f(a,p,c) char ∗p; char c; { /* ... */ } /* C; not C++ */
这些定义必须重新编写:
void f(int a, char∗ p, char c) { /* ... */ }
• 在 C 语言中,struct可以在函数返回类型和参数类型声明中定义。例如:
struct S { int x,y; } f(); /* C; not C++ */
void g(struct S { int x,y; } y); /* C; not C++ */
C++ 中定义类型的规则使得此类声明毫无用处,因此不允许使用此类声明。
• 在 C 语言中,可以将整数赋值给枚举类型的变量:
enum Direction { up, down };
enum Direction d = 1; /* error : int assigned to Direction; OK in C */
• C++ 提供的关键字比 C 语言多得多。如果这些关键字之一在 C 程序中被用作标识符,则必须修改该程序才能使其成为 C++ 程序。
不在 C 中的 C++ 的关键字:

此外,关键字 export 保留供将来使用。C99 标准采用了 inline 关键字。
• 在 C 语言中,一些 C++ 关键字是标准头文件中定义的宏:
C++ 中作为 C 宏的关键字:

这意味着在C语言中,可以使用 #ifdef 指令对它们进行测试、重新定义等等。
• 在 C 语言中,全局数据对象可以在同一个翻译单元中声明多次,而无需使用 extern 说明符。只要最多只有一个这样的声明提供初始化值,该对象就被视为只定义了一次。例如:
int i;
int i; /* just another declaration of a single integer ‘‘i’’; not C++ */
在 C++ 中,一个实体必须且只能被定义一次;§15.2.3。
• 在 C 语言中,void* 类型的值可以作为赋值运算符的右操作数,用于赋值或初始化任何指针类型的变量;但在 C++ 中则不允许这样做 (§7.2.1)。例如:
void f(int n)
{
int∗ p = malloc(n∗sizeof(int)); /* not C++; in C++, allocate using ‘‘new’’ */
}
这可能是最难解决的兼容性问题之一。请注意,将 void* 类型隐式转换为其他指针类型通常并非无害:
char ch;
void∗ pv = &ch;
int∗ pi = pv; // not C++
∗pi = 666; // overwrite ch and other bytes near ch
如果你同时使用两种语言,请将 malloc() 的结果强制转换为正确的类型。如果你只使用 C++,请避免使用 malloc()。
• 在 C 语言中,字符串字面量的类型是“字符数组”,但在 C++ 中,它是“常量字符数组”,因此:
char∗ p = "a string literal is not mutable"; // error in C++; OK in C
p[7] = 'd';
• C 语言允许通过跳转到带标签的语句(例如 switch 或 goto 语句;§9.6)来跳过初始化;而 C++ 不允许这样做。例如:
goto foo; // OK in C; not C++
// ...
{
int x = 1;
foo:
if (x!=1) abort();
/* ... */
}
• 在 C 语言中,全局常量默认具有外部链接;而在 C++ 中则不然,全局常量必须进行初始化,除非显式声明为 extern(§7.5)。例如:
const int ci; // OK in C; const not initialized error in C++
• 在 C 语言中,嵌套结构体的名称与包含它们的结构体位于同一作用域。例如:
struct S {
struct T { /* ... */ } t;
// ...
};
struct T x; // OK in C, meaning ‘‘S::T x;’’; not C++
• 在 C++ 中,类的名称会引入其声明所在的作用域;因此,它不能与该作用域中声明的其他类型具有相同的名称。例如:
struct X { /* ... */ };
typedef int X; // OK in C; not C++
• 在 C 语言中,可以使用元素个数多于数组所需元素个数的初始化列表来初始化数组。例如:
char v[5] = "Oscar"; // OK in C, the terminating 0 is not used; not C++
printf("%s",v); // likely disaster
44.3.3.1 “经典 C语言”问题(“Classic C’’ Problems)
如果你需要升级经典的 C 语言程序(“K&R C”)或 C89 标准的程序,还会出现一些其他问题:
• C89 标准不支持 // 注释(尽管大多数 C89 编译器都添加了此功能):
int x; // not C89
• 在 C89 标准中,类型说明符默认为 int (称为“隐式 int”)。例如:
const a = 7; /* in C89, type int assumed; not C++ or C99 */
f() /* f()’s retur n type is int by default; not C++ or C99 */
{
/* .. */
}
44.3.3.2 C++ 未采用的 C 语言特性(C Features Not Adopted by C++)
与 C89 相比,C99 中的一些新增特性被 C++ 有意地排除在外,没有被采纳:
[1] 可变长度数组(VLA);请使用vector或其他形式的动态数组
[2] 指定初始化器;请使用构造函数
C11 的一些特性过于新颖,因此在 C++ 中尚未被考虑采用,但也有例外,例如内存模型和原子操作(§41.3)等特性就是源自 C++。
44.3.4 不是 C 的C++代码(C++ Code That Is Not C)
本节列出了 C++ 提供但 C 语言不提供的功能(或 C 语言在 C++ 引入这些功能多年后才采用的功能,已在文中注明,因此旧版本的 C 编译器可能不支持这些功能)。这些功能按用途分类。然而,存在多种分类方式,而且大多数功能都具有多种用途,因此不应过分拘泥于这种分类。
• 主要用于简化记法的特性:
[1] 注释(§2.2.1,§9.7);已添加到 C99
[2] 支持受限字符集(§iso.2.4);部分已添加到 C99
[3] 支持扩展字符集(§6.2.3);已添加到 C99
[4] static存储对象可以使用非常量初始化器(§15.4.1)
[5] const 关键字可在常量表达式中使用(§2.2.3,§10.4.2)
[6] 声明可作为语句使用(§9.3);已添加到 C99
[7] for 语句的初始化部分可包含声明(§9.5);已添加到 C99
[8] 条件表达式中可包含声明(§9.4.3)
[9] 结构体名称无需以 struct 前缀(§8.2.2)
[10] 匿名union(§8.3.2);已添加到 C11
• 主要用于增强类型系统的功能:
[1] 函数参数类型检查(§12.1);部分功能已添加到 C 语言中(§44.3.3)
[2] 类型安全链接(§15.2,§15.2.3)
[3] 使用 new 和 delete 进行自由存储管理(§11.2)
[4] const(§7.5,§7.5);部分已添加到 C 语言
[5] 布尔类型 bool(§6.2.2);部分已添加到 C99 标准
[6] 命名类型转换(§11.5.2)
• 支持用户自定义类型的功能:
[1] 类(第 16 章)
[2] 成员函数(§16.2.1)和成员类(§16.2.13)
[3] 构造函数和析构函数(§16.2.5,第 17 章)
[4] 派生类(第 20 章,第 21 章)
[5] virtual函数和抽象类(§20.3.2,§20.4)
[6] 公有/保护/私有访问控制(§16.2.3,§20.5)
[7] friend(§19.4)
[8] 指向成员的指针(§20.6)
[9] static成员(§16.2.12)
[10] mutable成员(§16.2.9.3)
[11] 运算符重载(第 18 章)
[12] 引用(§7.7)
• 主要用于程序组织的功能(除了类之外):
[1] 模板(第 23 章)
[2] 内联函数(§12.1.3);已添加到 C99 标准
[3] 默认参数(§12.2.5)
[4] 函数重载(§12.3)
[5] 命名空间(§14.3.1)
[6] 显式作用域限定符(运算符 ::;§6.3.4)
[7] 异常(§2.4.3.1,第 13 章)
[8] 运行时类型识别(第 22 章)
[9] 通用常量表达式(constexpr;§2.2.3,§10.4,§12.1.6)
§44.2 中列出的 C++11 特性在 C 中并不存在。
C++ (§44.3.3) 添加的关键字可用于识别大多数 C++ 特有的功能。但是,某些功能,例如函数重载和const表达式中的常量,无法通过关键字来识别。
C++ 的函数链接是类型安全的,而 C 语言的规则不要求函数链接时保证类型安全。这意味着在某些(大多数?)实现中,C++ 函数必须声明为 extern "C" 才能以 C++ 方式编译,同时符合 C 语言的调用约定(§15.2.5)。
例如:
double sin(double); // may not link to C code
extern "C" double cos(double); // will link to C code
可以使用 __cplusplus 宏(译注:表示在C++环境下,也用其表示C++版本)来判断程序是由 C 编译器还是 C++ 编译器处理的(§15.2.5)。
除了上述功能之外,C++ 标准库(§30.1.1,§30.2)的大部分内容都是 C++ 特有的。C 标准库在 <tgmath.h> 中提供了类型通用宏,并在 <complex.h> 中提供了复数支持,其功能与 <complex> 类似。
C语言也提供了 <stdbool.h> 头文件,其中包含 _Bool 类型及其别名 bool,用于模拟 C++ 中的 bool 类型。
44.4 建议(Advice)
[1] 在生产代码中使用新功能之前,先编写小程序进行测试,以验证你计划使用的实现的标准符合性和性能;§44.1。
[2] 学习 C++ 时,请使用你可以获得的最新、最完整的标准 C++ 实现;§44.2.4。
[3] C 和 C++ 的公共子集并非学习 C++ 的最佳入门子集;§1.2.3、§44.2.4。
[4] 优先使用标准功能而非非标准功能;§36.1,§44.2.4。
[5] 避免使用已弃用的功能,例如 throw 规范;§44.2.3,§13.5.1.3。
[6] 避免使用 C 风格的类型转换;§44.2.3,§11.5。
[7] “隐式 int”已禁用,因此请显式指定每个函数,变量,常量等的类型;§44.3.3。
[8] 将 C 程序转换为 C++ 时,首先确保函数声明(原型)和标准头文件使用一致;§44.3.3。
[9] 将 C 程序转换为 C++ 时,重命名与 C++ 关键字冲突的变量;§44.3.3。
[10] 为了可移植性和类型安全,如果你必须使用 C,请使用 C 和 C++ 的公共子集编写代码;§44.2.4。
[11] 将 C 程序转换为 C++ 时,将 malloc() 的结果强制转换为正确的类型,或将所有 malloc() 的使用更改为 new 的使用;§44.3.3。
[12] 从 malloc() 和 free() 转换为 new 和 delete 时,考虑使用 vector,push_back() 和 reserve() 代替 realloc();§3.4.2,§43.5。
[13] 将 C 程序转换为 C++ 时,请记住没有从 int 到枚举的隐式转换;必要时使用显式类型转换;§44.3.3,§8.4。 [14] 定义在命名空间 std 中的功能组件定义在不带后缀的头文件中(例如,std::cout 声明在 <iostream> 中);§30.2。
[15] 使用 <string> 获取 std::string( <string.h> 包含 C 风格的字符串函数);§15.2.4。
[16] 对于每一个将名称放入全局命名空间的标准 C 头文件 <X.h>,相应的头文件 <cX> 会将名称放入命名空间 std 中;§15.2.2。
[17] (在C++中)声明 C 函数时使用 extern "C" ;§15.2.5。
内容来源:
<<The C++ Programming Language >> 第4版,作者 Bjarne Stroustrup


被折叠的 条评论
为什么被折叠?



