本文主要对浮点数、字符、字符串及 C++ 中的 string 类的一些重要或者容易忽略的要点进行总结。
浮点数
浮点数编码
关于浮点数编码这一块比较复杂,可参考《深入理解计算机系统》“第 2.4 节浮点数”。
浮点数能用 == 或 != 号比较吗?
答案是最好不要用。也就部分可以,如
1 | (float)0.1==(float)0.1; |
是成立的;但我们最好还是不要这样子做,具体原因(总结自他人博客)主要有两个:
多于 99.999…% 的浮点数在计算机不能表示
根据博文代码之谜(五)- 浮点数(谁偷了你的精度?)的说法,在浮点数的表示范围内,有多于 99.999…% 的数在计算机中不能表示(“不能表示”并不意味着“不能精确表示”)。这样,在绝大多数情况下,我们对两个浮点数进行比较(相等或不相等),实际上是对他们的“精确值”进行比较,这必然会有问题。
寄存器与内存表示浮点数精度不同
博文计算机中基本类型 float 值表示和大小比较问题的说法如下:
即使在精度相同的情况下,比较也可能会出问题。因为在运算过程中会将内存(或高速缓存)中的值加载到 CPU 浮点寄存器(80 bit扩展精度)中,然后再进入 CPU 浮点计算单元进行计算,计算结果写回浮点寄存器,然后写回内存(或高速缓存)。
从内存到浮点寄存器,浮点数的精度会扩展,从浮点寄存器到内存,浮点数的精度会降低(精度扩展通常没问题,但如果精度降低了,很可能值会发生变化,出现截断)
,而浮点运算的结果由于下面还要使用所以暂时保存在浮点寄存器中留待下次使用(没有及时写回内存,这是一种优化策略),从而导致数据并不是内存中和内存中的数据比较而是浮点寄存器中的值和内存中的值进行比较,而无论内存中是 float 类型还是 double 类型,其精度和浮点寄存器精度都不相同,从而导致比较结果是不相等。
另外,关于精度扩展,可参考维基百科上的一个条目 Extended precision。
字符及字符串
字符
字符编码可分为两种: ASCII 和 Unicode。Unicode 是 ASCII 的升级版。
ASCII 编码在内存中占一个字节,由 0255 之间的数字组成,可与整数(0255)互相转换。Unicode 编码在内存中占两个字节,表示范围达到 0~65535,包含了 ASCII 编码。
字符串
字符串是由一系列按照一定编码顺序线性排列的字符组成。C/C++ 中使用结束符\0
作为字符串结束标志。ASCII 编码使用一个字节\0
,Unicode 编码使用两个字节\0
。
C++ 之 string 类
string 类的实例是以\0
结束的吗?
这个问题有时还真容易混淆,因为我们可能会将 C++ 语言中的 string 类的实例跟 C/C++ 中的字符串相混淆。C/C++ 中的字符串是以\0
结束的字符数组。那对于 string 类而言,\0
跟其他字符是否有不一样的意义?
实际上,\0 在 string 类之中并不具有特殊意义,它跟其他字符的地位完全相同。
为了证明这个说法,我们来看一个例子:
1 |
|
我们将断点设定在第10行,可以发现有如此结果:
另外,程序输出如下:
从上述结果可以看出,\0
在string类中确跟其他字符的地位一样。
同时,我们也可以知道 string 类的实例并不以 \0 作为结束标志
。不过,我们再看看下边例子,我们可能会动摇这个结论:
1 |
|
程序运行结果如下:
从这个程序来看,string类的实例好像又是以\0
结束的。实际上,是操作符[]
捣的鬼。对于 string 类而言,操作符'[]'并不检查索引是否有效,所以当索引越界时,操作符'[]'的访问行为是不明确的。不过对于string 类而言,用操作符'[]'访问第 length() 个字符时会返回'\0',而对 第 length()+1 以上的访问会使得程序崩溃。
这个可以通过上边程序的 in 变量的原始视图来验证到:
但是这并不能证明 string 类的实例就以\0
结束。如果我们用 string 类提供的会进行类型检查的成员函数 at
来访问实例中的字符,就会发现上一程序会发生越界错误:
1 |
|
程序运行时错误:
综上所述,'\0' 在 string 类之中并不具有特殊意义,它跟其他字符的地位完全相同,而且 string 类的实例也并不以 '\0' 为结束标志。
string 类实例与字符串转换
将 string 类实例转换为字符串是比较常见的,一般利用的是c_str()
函数,如下例:
1 |
|
string::npos
npos
是 string 类的一个公有静态成员变量,定义如下:
1 | static const size_t npos = -1; |
它主要有两个用处:
- 当作为成员函数参数 len 的默认数值时,表示“until the end of the string”。如 str.substr(pos) 表示截取从 pos 到 str 最末尾的字符串。
- 作为成员函数返回值表示没有匹配。如 str.substr(“test”) 若返回 string::npos 则表示在 str 中找不到 test 子串。