您的位置:首页 > 编程语言 > C语言/C++

重学C++ (一)

2015-08-02 10:16 211 查看

一直以来都把C++当作C+template+STL,然而C++其实远不止此。

有天,大师兄神神秘秘地叫住我:“给你看段C++,猜猜它是在做什么?”显然我并没有看懂。据他说这四行代码牵涉到C++的五个较为罕见的语法点orz

于是就有了写本系列博客的欲望。以C++11标准的Working Draft为根据,以期总结一些以前不太留意的、比较有意思的C/C++特性。

记得有位哲人曾经说过这么一句话,我认为非常切题:

大一就已经学过C++,到了大四却看不懂几行代码。

没错这是我说的。

Trigraph Sequences

Trigraph Sequences是一个古老且讨厌的东西。它出现的原因是某些键盘只支持ISO 646——C/C++语言标准中包含了该字符集以外的一些字符(#{}[]等)。

虽然我感觉这辈子也遇不到一个这样的键盘,但C/C++的预处理程序还是会乖乖地把程序中所有的Trigraph Sequences给替换掉。

C99或是C++0x下的一个有趣的例子:

int i = 0;
//test??/
i++;


打印出
i
后可以发现
i++
根本没有被执行。因为
??/
被替换成了
\
,而这会把下一行与本行拼接起来。

所以在我们写程序时要时刻注意两个问号连用的情况,无论是在注释还是在字符串中(可能会报warning,g++中可以使用-Wno-trigraphs关闭提示)。下面是C++11(也有人管它叫C++0x)标准中规定的所有Trigraph Sequences:

TrigraphReplacementTrigraphReplacementTrigraphReplacement
??=#??([??<{
??/\??)]??>}
??’^??!|??-~
还有一个需要非常留意的地方:在预编译阶段Trigraph的替换是首先会发生的操作,在任何其他的预处理之前。

Alternative Tokens

Trigraph有效地解决了ISO 646的问题,然而这么写程序真的是太丑了:

//#define arraycheck(a,b) a[b] || b[a]
??=define arraycheck(a,b) a??(b??) ??!??! b??(a??)


于是人们发明了第二套方案,使用Alternative Token来替换无法显示的token:

//#define arraycheck(a,b) a[b] || b[a]
%:define arraycheck(a,b) a<:b:> or b<:a:>


好看多了是吧。

在C中同样的东西是以宏的形式定义在
<iso646.h>
中,而在C++中是内建在语法中的。

为什么要使用token这个词,而不像Trigraph一样用Sequence呢?前面提到过,在预编译阶段Trigraph的替换是首先发生的,是在识别token之前的。这意味着不论这个Trigraph的位置在哪里它都会被替换。

而在C++11中提到,Alternative Tokens会以token为单位替换它的原始token。作为比较,可以在g++中使用-std=c++11编译选项,运行一下这段程序:

cout << "??(" << endl; //print [
cout << "<:" << endl;  //print <:


下面是C++11规定的所有Alternative Tokens:

AlternativePrimitiveAlternativePrimitiveAlternativePrimitive
<%{and&&and_eq&=
%>}bitor|or_eq|=
<:[or||xor_eq^=
:>]xor^not!
%:#compl~not_eq!=
%:%:#bitand&
注意到,此处把%:%:当作一个整体而不是两个独立的%:,这正是一个完整的token(区别于C中的宏定义形式)。

Escape Sequence

转义字符其实没什么多说的。

以前忽略的一点是八进制或十六进制数形式的直接转义。前者直接就是
\ooo
,后者
\xhhh
要在前面加一个x。最常用的就是
\0
啦。

这种形式的转义,八进制字符最多有三个,而对十六进制的合法字符来说并没有什么数目的限制,碰到第一个非法字符即认为character literal结束。溢出怎么办,就由编译器决定咯。

Raw String

为了简化包含大量需要转义字符的字符串的书写,python采用了原始字符串的方法,C++11也开始使用这一概念:

prefixoptR"delimiteropt(raw_characteropt∗)delimiteropt"

prefix是可选项,包括u8、u、U和L。前三者是从C++11开始才有的新特性。这四种前缀对应的字符串类型分别为
const char[]
const char_16[]
const char_32[]
const wchar_t[]


字母R是Raw String的标志。

delimiter是可选项,的作用是标记Raw String的开头和结尾,至多16个字符。这16个字符中不能出现转义符\、左右圆括号()和空格等空白字符。注意开头结尾的两个delimiter是一样的。

raw_character也是可选项。它们被包含在
d(
)d
之间,不能包含结束符号组合
)d
。其中
d
代表一个特定的delimiter,可以为空。

Raw String能Raw到什么程度呢?它连源代码中的换行符也不会放过的:

const char *p = R"(a\
b
c)";
assert(std::strcmp(p, "a\\\nb\n\tc") == 0);


更神奇的是这个:

const char *p = R"(??)";
assert(std::strcmp(p, "\?\?") == 0);


如果看过前面关于Trigraph Sequences的介绍,这两句的重要性就不言而喻了。显然,Trigraph替换是在识别任何token之前发生的,但为何这里咋又变回来了呢?

且听下回分解。

转载请注明出处,欢迎关注我的新浪微博@窃听者Oscar
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: