《深入理解C++11:C++ 11新特性解析与应用》——第2章 保证稳定性和兼容性 2.1 保持与C99兼容...

    xiaoxiao2021-07-15  318

    第 2 章

    保证稳定性和兼容性

    作为C语言的嫡亲,C++有一个众所周知的特性—对C语言的高度兼容。这样的兼容性不仅体现在程序员可以较为容易地将C代码“升级”为C++代码上,也体现在C代码可以被C++的编译器所编译上。新的C++11标准也并不例外。在C++11中,设计者总是保证在不破坏原有设计的情况下,增加新的特性,以充分保证语言的稳定性与兼容性。本章中的新特性基本上都遵循了该设计思想。

    2.1 保持与C99兼容

    类别:部分人

    在C11之前最新的C标准是1999年制定的C99标准。而第一个C++语言标准却出现在1998年(通常被称为C++98),随后的C++03标准也只对C++98进行了小的修正。这样一来,虽然C语言发展中的大多数改进都被引入了C++语言标准中,但还是存在着一些属于C99标准的“漏网之鱼”。所以C++11将对以下C99特性的支持也都纳入了新标准中:

    这些特性并不像语法规则一样常用,并且有的C++编译器实现也都先于标准地将这些特性实现,因此可能大多数程序员没有发现这些不兼容。但将这些C99的特性在C++11中标准化无疑可以更广泛地保证两者的兼容性。我们来分别看一下。

    2.1.1 预定义宏

    除去语法规范等,包括标准库的接口函数定义、相关的类型、宏、常量等也都会被发布在语言标准中。相较于C89标准,C99语言标准增加一些预定义宏。C++11同样增加了对这些宏的支持。我们可以看一下表2-1。

    使用这些宏,我们可以查验机器环境对C标准和C库的支持状况,如代码清单2-1所示。

    在我们的实验机上,__STDC_VERSION__这个宏没有定义(也是符合标准规定的,如表2-1所示),其余的宏都可以打印出一些常量值。

    预定义宏对于多目标平台代码的编写通常具有重大意义。通过以上的宏,程序员通过使用#ifdef/#endif等预处理指令,就可使得平台相关代码只在适合于当前平台的代码上编译,从而在同一套代码中完成对多平台的支持。从这个意义上讲,平台信息相关的宏越丰富,代码的多平台支持越准确。

    不过值得注意的是,与所有预定义宏相同的,如果用户重定义(#define)或#undef了预定义的宏,那么后果是“未定义”的。因此在代码编写中,程序员应该注意避免自定义宏与预定义宏同名的情况。

    2.1.2 __func__预定义标识符

    很多现实的编译器都支持C99标准中的__func__ 预定义标识符功能,其基本功能就是返回所在函数的名字。我们可以看看下面这个例子,如代码清单2-2所示。

    在代码清单2-2中,我们定义了两个函数hello和world。利用__func__预定义标识符,我们返回了函数的名字,并将其打印出来。事实上,按照标准定义,编译器会隐式地在函数的定义之后定义__func__标识符。比如上述例子中的hello函数,其实际的定义等同于如下代码:

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

    __func__预定义标识符对于轻量级的调试代码具有十分重要的作用。而在C++11中,标准甚至允许其使用在类或者结构体中。我们可以看看下面这个例子,如代码清单2-3所示。

    从代码清单2-3可以看到,在结构体的构造函数中,初始化成员列表使用__func__预定义标识符是可行的,其效果跟在函数中使用一样。不过将__fun__标识符作为函数参数的默认值是不允许的,如下例所示:

    void FuncFail( string func_name = __func__) {};// 无法通过编译

    这是由于在参数声明时,__func__还未被定义。

    2.1.3 _Pragma操作符

    在C/C++标准中,#pragma是一条预处理的指令(preprocessor directive)。简单地说,#pragma是用来向编译器传达语言标准以外的一些信息。举个简单的例子,如果我们在代码的头文件中定义了以下语句:

    #pragma once

    那么该指令会指示编译器(如果编译器支持),该头文件应该只被编译一次。这与使用如下代码来定义头文件所达到的效果是一样的。

    #ifndef THIS_HEADER #define THIS_HEADER // 一些头文件的定义 #endif

    在C++11中,标准定义了与预处理指令#pragma功能相同的操作符_Pragma。_Pragma操作符的格式如下所示:

    _Pragma (字符串字面量)

    其使用方法跟sizeof等操作符一样,将字符串字面量作为参数写在括号内即可。那么要达到与上例#pragma类似的效果,则只需要如下代码即可。

    _Pragma("once");

    而相比预处理指令#pragma,由于_Pragma是一个操作符,因此可以用在一些宏中。我们可以看看下面这个例子:

    #define CONCAT(x) PRAGMA(concat on #x) #define PRAGMA(x) _Pragma(#x) CONCAT( ..\concat.dir )

    这里,CONCAT( ..concat.dir )最终会产生_Pragma(concat on "..concat.dir")这样的效果(这里只是显示语法效果,应该没有编译器支持这样的_Pragma语法)。而#pragma则不能在宏中展开,因此从灵活性上来讲,C++11的_Pragma具有更大的灵活性。

    2.1.4 变长参数的宏定义以及 VA_ARGS

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

    #define PR(...) printf(__VA_ARGS__)

    就可以定义一个printf的别名PR。事实上,变长参数宏与printf是一对好搭档。我们可以看如代码清单2-4所示的一个简单的变长参数宏的应用。

    在代码清单2-4中,定义LOG宏用于记录代码位置中一些信息。程序员可以根据stderr产生的日志追溯到代码中产生这些记录的位置。引入这样的特性,对于轻量级调试,简单的错误输出都是具有积极意义的。

    2.1.5 宽窄字符串的连接

    在之前的C++标准中,将窄字符串(char)转换成宽字符串(wchar_t)是未定义的行为。而在C++11标准中,在将窄字符串和宽字符串进行连接时,支持C++11标准的编译器会将窄字符串转换成宽字符串,然后再与宽字符串进行连接。

    事实上,在C++11中,我们还定义了更多种类的字符串类型(主要是为了更好地支持Unicode),更多详细的内容,读者可以参见8.3与8.4节。


    最新回复(0)