附录整理:整数、字符、数值计算与语言差异
这份笔记补充日常语法之外值得掌握的知识:如何选择可移植的整数类型、区分字符与编码、理解浮点数的限制,以及避免把 C 与 C++ 的规则混用。
C 部分以 C11 为背景;语言对比明确使用 C11 与 C++11。涉及后续标准时不能直接套用这里的全部结论。
1. 整数类型:用名称表达真正的需求
int、long 的宽度受实现影响。需要表达“恰好多少位”“至少多少位”或“能容纳对象大小”时,应选语义对应的类型。
| 需求 | 类型示例 | 关键条件 |
|---|---|---|
| 恰好 32 位 | int32_t、uint32_t |
可选,必须有满足要求的底层类型 |
| 至少 32 位,优先选择较小表示 | int_least32_t、uint_least32_t |
不保证恰好 32 位 |
| 至少 32 位,由实现选择适合快速运算的类型 | int_fast32_t、uint_fast32_t |
不保证等同 int,也不保证每段程序实测最快 |
| 可容纳任意有符号或无符号整数类型的值 | intmax_t、uintmax_t |
分别针对有符号、无符号范围 |
| void 指针与整数往返转换 | intptr_t、uintptr_t |
可选,不应假设 long 能代替 |
| 对象大小、元素数量 | size_t |
无符号类型 |
| 数组内的指针差 | ptrdiff_t |
有符号类型,结果必须可表示 |
整数别名主要由 <stdint.h> 提供;<inttypes.h> 包含它,并额外提供格式宏和最大宽度整数的转换等接口。
标准要求的 least、fast 家族包括宽度 8、16、32、64。精确宽度家族需要实现有相应的表示;不能无条件认为 int8_t 一定存在。
1.1 固定宽度不能代替外部数据格式
即使使用 uint32_t,把对象的内存原样写入文件,也没有自动解决字节序问题。结构还可能有成员间填充。
网络协议或跨平台文件应明确编码、字节序与字段布局,按约定序列化,而不是仅凭类型名认为内存布局已经标准化。
1.2 输出和输入格式也要匹配
| 类型 | 有符号十进制输出 | 有符号十进制输入 | 上下界宏 |
|---|---|---|---|
int32_t |
PRId32 |
SCNd32 |
INT32_MIN、INT32_MAX |
int_least32_t |
PRIdLEAST32 |
SCNdLEAST32 |
INT_LEAST32_MIN、INT_LEAST32_MAX |
int_fast32_t |
PRIdFAST32 |
SCNdFAST32 |
INT_FAST32_MIN、INT_FAST32_MAX |
intmax_t |
PRIdMAX |
SCNdMAX |
INTMAX_MIN、INTMAX_MAX |
intptr_t |
PRIdPTR |
SCNdPTR |
INTPTR_MIN、INTPTR_MAX |
无符号十进制把格式宏中的 d 换成 u,例如 PRIu32、SCNuMAX;十六进制输出使用 PRIx32 或 PRIX32。
printf("value = %" PRId32 "\n", value);
宏展开得到格式字符串片段,相邻字符串字面量随后拼接。它们不是普通的运行时字符串变量,前面的 % 仍需自己写出。
输入格式宏解决类型匹配,不能自动解决超范围数字输入。需要完整验证时,先读文本,使用 strtoimax() 等函数转换,再检查目标范围。
1.3 整数常量宏不要机械生成
uint_least32_t flag = UINT32_C(1) << 20;
intmax_t limit = INTMAX_C(1000000);
标准宏包括 INT8_C、UINT8_C、INT16_C、UINT16_C、INT32_C、UINT32_C、INT64_C、UINT64_C、INTMAX_C、UINTMAX_C。
它们用于生成适合相应最小宽度类型或最大宽度类型的整数常量表达式,并涉及整数提升。不要把 INT32_C 简单理解成“返回值类型必定是 int32_t 的强制转换”。
不存在按类型名任意拼出来的标准 UINT_LEAST64_C 宏;为 uint_least64_t 准备常量可使用 UINT64_C。
1.4 完整示例
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
int main(void)
{
uint_least32_t flag = UINT32_C(1) << 20;
uintmax_t total = UINTMAX_C(1000000);
printf("flag: %" PRIuLEAST32 "\n", flag);
printf("total: %" PRIuMAX "\n", total);
#ifdef INT32_MAX
int32_t value = -INT32_C(12345);
printf("exact 32: %" PRId32 "\n", value);
#endif
#ifdef UINTPTR_MAX
int object = 7;
void *original = &object;
uintptr_t saved = (uintptr_t)original;
void *restored = (void *)saved;
printf("pointer round trip: %d\n", restored == original);
#endif
return 0;
}
对指针的保证是特定的往返转换,不是说可以随意对整数地址加减,然后认为它指向某个合法对象。对象地址也不能作为持久化文件里稳定的对象编号。
2. 字符处理要区分四个层次
字节:实际存储单元
↓ 按编码解释
编码单元:UTF-8、UTF-16 等编码使用的基本单位
↓ 解码
Unicode 码点:例如 U+4E2D
↓ 根据组合与显示规则
用户看到的字符或字形
这些数量不一定相等。同一个可见字符可能由多个码点构成;一个码点也可能由多个编码单元表示。
2.1 strlen 统计字节
在使用八位字节的 UTF-8 环境里,"中" 的编码占 3 字节,"A中" 占 4 字节。
strlen() 返回结尾空字符之前的 char 单元数,不会替你解码,也不返回可见字符数。按下标删掉一个字节,可能破坏整个多字节编码。
2.2 常见字符串形式
| C11 字面量 | 数组元素类型 | 含义 |
|---|---|---|
"text" |
char |
普通执行字符编码 |
u8"text" |
char |
UTF-8 字符串形式 |
L"text" |
wchar_t |
宽字符串,表示由实现决定 |
u"text" |
char16_t |
与 16 位字符表示相关的字符串 |
U"text" |
char32_t |
与 32 位字符表示相关的字符串 |
C11 中 char16_t、char32_t 是 <uchar.h> 提供的无符号整数别名,不是独立的语言关键字;分别对应 uint_least16_t、uint_least32_t。它们的存储大小不应脱离实现的 CHAR_BIT 与底层类型作假设。
__STDC_UTF_16__、__STDC_UTF_32__ 可用于了解实现对相关表示的承诺。不能仅凭宽字符类型名字推断某个外部文件一定采用同样编码。
2.3 UTF-16 的一个码点可能占两个单元
UTF-8 用变长字节序列表示码点;UTF-16 对基本多文种平面以外的码点使用代理项对;UTF-32 可用一个 32 位编码单元表示一个 Unicode 标量值。
因此,“一个 char16_t 元素就是一个完整字符”并不成立。即使一个码点正好占一个单元,也不等于它必定单独构成一个用户看到的字符。
2.4 通用字符名表达码点
const char *word = u8"\u4E2D\u6587";
\u 后接四位十六进制数字,\U 后接八位,例如 \U0001F600。这种写法让源代码可以用 ASCII 字符表达相应字符,实际存储仍取决于字面量形式和编码规则。
不要把某个 Unicode 码点直接塞进单个普通 char,然后认为已经得到它的 UTF-8 编码。
3. 宽字符、多字节转换与流定向
3.1 wchar_t 不保证统一宽度或 Unicode 编码
wchar_t 的大小和编码由实现决定,不能假设它永远等于某一种 UTF 类型,也不能假设宽字符串在所有平台上都能按相同内存形式交换。
| 普通字符接口 | 对应的宽字符接口示例 |
|---|---|
strlen、strcmp |
wcslen、wcscmp |
fgets、fputs |
fgetws、fputws |
printf、fprintf |
wprintf、fwprintf |
strtod |
wcstod |
isalpha、toupper |
iswalpha、towupper |
宽字符输入结果先用 wint_t 保存,再检查 WEOF。宽字符分类接口的返回值也应按零或非零理解。
3.2 标准转换通常依赖当前本地化
| 方向 | 接口 |
|---|---|
| 多字节序列 → 宽字符 | mbrtowc |
| 宽字符 → 多字节序列 | wcrtomb |
| 多字节字符串 → 宽字符串 | mbsrtowcs |
| 宽字符串 → 多字节字符串 | wcsrtombs |
| 多字节序列 ↔ char16_t | mbrtoc16、c16rtomb |
| 多字节序列 ↔ char32_t | mbrtoc32、c32rtomb |
多字节不等于 UTF-8。若使用 setlocale(LC_CTYPE, ""),得到的是当前环境选择的编码,可能不是所希望的那一种;返回值还可能表示设置失败。
mbstate_t 保存转换状态,使用前可以零初始化。一次读取到的数据可能只有字符编码的前半部分,分块解析时不能每次都丢弃旧状态。
以 mbrtowc() 为例:
- 正数:成功消费相应数量的字节。
- 0:转换到空字符。
(size_t)-1:无效序列。(size_t)-2:给定字节不足以完成转换。
不能在判断这些特殊返回值之前,就把返回值当作普通长度加到指针上。mbrtoc16() 还可能返回 (size_t)-3,表示输出先前多字节序列对应的后续 char16_t 单元,本次没有消费新字节;不能直接照搬所有 mbrtowc 的返回处理。
3.3 一个自包含的转换示例
下面故意使用基本字符集中的 ASCII 文本,使例子不依赖机器是否安装某个 UTF-8 locale。把它用于中文之前,需要先建立明确的编码约定。
#include <locale.h>
#include <stdio.h>
#include <string.h>
#include <wchar.h>
int main(void)
{
if (setlocale(LC_CTYPE, "C") == NULL) {
return 1;
}
const char *p = "ABC";
size_t remaining = strlen(p);
size_t count = 0;
mbstate_t state = {0};
while (remaining != 0) {
wchar_t wc;
size_t used = mbrtowc(&wc, p, remaining, &state);
if (used == (size_t)-1 || used == (size_t)-2 || used == 0) {
fputs("invalid or incomplete sequence\n", stderr);
return 1;
}
++count;
p += used;
remaining -= used;
}
printf("bytes: %zu; wide characters: %zu\n", strlen("ABC"), count);
return 0;
}
输出 bytes: 3; wide characters: 3。计数相同只来自这个输入的性质,不能推导出任意文本都有相同结果。
3.4 宽流与字节流不要随意混用
一个流最初可以没有定向,随后由相应操作确定为字节定向或宽定向。fwide() 可以查询或尝试设置尚未定向的流。
已经用字节输出操作确定方向的 stdout,不应随后直接当作宽流调用 wprintf()。普通 printf 中的 %ls 转换与在宽流上使用 wprintf 也是不同层面的操作。
3.5 遇到旧源码时认识特殊拼写
在 C11 背景下,三字符序列如 ??= 可表示 #,并在较早的翻译阶段处理,甚至可能影响字符串内容。
双字符形式如 <:、:>、<%、%>、%: 分别可表示 [、]、{、}、#,但字符串里面的这些文字不会作为标点记号替换。
<iso646.h> 提供 and、or、not 等运算符的宏拼写。这里主要用于读懂已有代码,新代码使用常规符号通常更直接;不要把这些版本相关的兼容机制当作所有标准版本都相同的规则。
4. 浮点数:范围、精度与间距不同
浮点数可简化理解为:
数值 = 符号 × 有效数 × 基数的指数次幂
指数决定可以表示多大、多小;有效数位数决定可以保留多少细节。扩大指数范围不等于增加有效数字精度。
4.1 常用 float.h 宏
| 宏,以下以 double 为例 | 含义 |
|---|---|
FLT_RADIX |
浮点基数,常见为 2 |
DBL_MANT_DIG |
以该基数计算的有效数位数 |
DBL_DIG |
十进制数转为该浮点类型再转回时,可保证的有效数字能力 |
DBL_DECIMAL_DIG |
该浮点值转成十进制再读回时,需要的足够有效位数 |
DBL_MAX |
最大有限正值 |
DBL_MIN |
最小正规正值,不是最负值 |
DBL_TRUE_MIN |
最小正值,可能是次正规数 |
DBL_EPSILON |
1 与下一个更大可表示数之间的差 |
DBL_HAS_SUBNORM |
对次正规值支持情况的说明 |
FLT_EVAL_METHOD |
浮点表达式中间求值所使用的范围与精度方案 |
DBL_DIG 和 DBL_DECIMAL_DIG 关心相反方向的往返转换,不能混用。保存已有 double 数值的文本形式时,可以使用 printf("%.*g", DBL_DECIMAL_DIG, value)。
在常见的二进制浮点实现中,0.1、0.8 这样的有限十进制小数不能精确表示,存入时需要取邻近值。计算表达式也可能再次舍入。
4.2 epsilon 不是全范围统一误差
浮点数越大,相邻可表示值的绝对间距通常越大。DBL_EPSILON 描述的是 1 附近的间距,不能当作任意数量级下都合适的固定比较误差。
近似比较常同时考虑绝对误差和相对误差:
|a - b| <= max(绝对容差, 相对容差 × max(|a|, |b|))
容差应来自问题本身的尺度和允许误差。处理范围极大的值时,还要注意中间减法或乘法是否溢出;NaN、无穷和有符号零也需要明确规则。
4.3 正规数、次正规数与零
正规数使用完整的有效数精度。接近零且指数已无法继续减小时,支持次正规表示的实现还能用部分有效位表示更小的非零值,从而逐步接近零。
这叫渐进下溢,不代表精度完全不变。次正规区域的相对精度会下降。
DBL_MIN 是最小的正规正数,DBL_TRUE_MIN 才表达最小正数;若没有次正规值,两者可能相同。isnormal(0.0) 为假,因此“不是正规数”并不自动意味着出错。
4.4 分类比猜特殊值更清楚
fpclassify() 的结果可能是 FP_ZERO、FP_NORMAL、FP_SUBNORMAL、FP_INFINITE 或 FP_NAN。也可使用 isfinite、isinf、isnan、isnormal 单独检查。
在支持 NaN 的相应浮点规则下,NaN 不等于自身,许多有序比较为假。检测它应使用 isnan(),不要用 x == NAN。
有符号零的符号可以通过 signbit() 检查。不要假定比较等于零就已经丢失了所有符号信息。
5. 选择更合适的数值接口
5.1 数学上相等,不保证计算结果一样
| 需要计算的量 | 值得优先了解的接口 | 原因 |
|---|---|---|
很小 x 对应的 log(1 + x) |
log1p(x) |
避免 1+x 先舍入到 1 而损失信息 |
很小 x 对应的 exp(x) - 1 |
expm1(x) |
减少接近值相减的精度损失 |
| 平面长度 | hypot(x, y) |
减少直接平方的中间范围问题 |
| 三个数的乘加 | fma(x, y, z) |
按乘加整体只舍入一次的语义计算 |
| 某个方向的相邻可表示数 | nextafter(x, y) |
直接按浮点表示寻找邻值 |
“更合适”依赖具体问题。这些函数不是给任意输入都能获得精确数学实数结果的承诺。
5.2 取整函数有不同规则
| 接口 | 例子或规则 |
|---|---|
floor(x) |
向负无穷,floor(-2.7) == -3.0 |
ceil(x) |
向正无穷,ceil(-2.7) == -2.0 |
trunc(x) |
向零,trunc(-2.7) == -2.0 |
round(x) |
就近;恰好居中时远离零,round(-2.5) == -3.0 |
nearbyint(x) |
使用当前舍入方向,不因这个取整动作引发不精确异常 |
rint(x) |
使用当前舍入方向,可能报告不精确异常 |
lround、llround |
round 的规则,返回相应整数类型 |
浮点结果转成整数前,仍需要确认数值在目标类型的范围内。函数返回“整数值的浮点数”,也不代表已经做了整数类型的范围检查。
5.3 两种余数也不能混用
fmod(x, y) 对应商向零截断的余数;remainder(x, y) 按最接近的整数商取余,居中时选择偶数商。
例如 fmod(7.0, 4.0) 是 3,remainder(7.0, 4.0) 是 -1。它们计算的是不同规则下的量。
5.4 完整示例
#include <float.h>
#include <math.h>
#include <stdio.h>
int main(void)
{
double x = 1e-20;
printf("log1p: %.17g\n", log1p(x));
printf("expm1: %.17g\n", expm1(x));
printf("hypot: %.1f\n", hypot(3.0, 4.0));
printf("floor/ceil/trunc: %.1f %.1f %.1f\n",
floor(-2.7), ceil(-2.7), trunc(-2.7));
printf("round: %.1f %.1f\n", round(2.5), round(-2.5));
printf("remainders: %.1f %.1f\n", fmod(7.0, 4.0), remainder(7.0, 4.0));
printf("next above one: %.*g\n", DBL_DECIMAL_DIG, nextafter(1.0, 2.0));
printf("multiply-add: %.1f\n", fma(2.0, 3.0, 4.0));
return 0;
}
其中 hypot 的结果为 5,round 的两个结果为 3 和 -3,两种余数分别为 3 和 -1。很小值的具体打印末位取决于实现的浮点表示和数学库。
6. 浮点环境:控制模式与状态标志
<fenv.h> 处理两个方面:当前如何舍入,以及计算过程中是否发生某些浮点异常情况。
6.1 状态标志不是 C++ 异常
| 宏 | 表示的情况 |
|---|---|
FE_INVALID |
无效操作 |
FE_DIVBYZERO |
浮点除零相关情况 |
FE_OVERFLOW |
结果超出有限表示范围 |
FE_UNDERFLOW |
下溢相关情况 |
FE_INEXACT |
结果不能精确表示 |
实现只为支持的异常提供相应宏。设置状态标志通常不等于自动跳转到某个错误处理函数,也不意味着所有情况下都会终止程序。
feclearexcept() 清除所选标志,fetestexcept() 检查标志。多个标志可能同时被设置;旧标志也可能一直保留,不能不清楚上下文就把它归因于最近一次运算。
6.2 舍入模式与恢复
若实现支持,可以通过 fegetround()、fesetround() 查询或设置 FE_TONEAREST、FE_TOWARDZERO、FE_UPWARD、FE_DOWNWARD 等方向。
调用设置函数要检查结果。临时更改后应恢复原模式,避免影响调用者的后续运算。fegetenv()、fesetenv() 可保存和恢复更完整的环境。
6.3 编译器也必须遵守相关语义
#pragma STDC FENV_ACCESS ON
这类指示用于告诉支持它的实现:程序会观察或更改浮点环境。具体支持还需要结合编译器能力和选项,不能只修改运行时模式就假设任意优化都不会改变结果。
#pragma STDC FP_CONTRACT 控制表达式运算能否按相应规则收缩,例如把乘加合并。显式调用 fma() 则直接请求它定义的乘加语义。
追求可重现的数值结果时,需要一起考虑:数据类型、求值精度、舍入模式、表达式变换、库函数和编译选项。
7. 复数支持
包含 <complex.h> 后,可以使用 double complex 等形式,I 表示虚数单位。C11 中复数支持是可选能力,需要先确认目标实现提供。
| 接口 | 用途 |
|---|---|
creal(z)、cimag(z) |
实部、虚部 |
cabs(z) |
模长 |
carg(z) |
相位角,单位为弧度 |
conj(z) |
共轭 |
csqrt(z)、cexp(z)、clog(z) |
复数开方、指数、对数 |
csin(z)、ccos(z) |
复数三角函数 |
CMPLX(re, im) |
C11 中构造 double complex 的宏 |
CMPLX() 可以更明确地构造实部与虚部,尤其适用于无穷、NaN 和有符号零等边界。普通有限值的入门示例也可写成 re + im * I。
#include <complex.h>
#include <stdio.h>
int main(void)
{
double complex a = 3.0 + 4.0 * I;
double complex b = 1.0 - 2.0 * I;
double complex product = a * b;
double complex conjugate = conj(a);
printf("modulus: %.1f\n", cabs(a));
printf("product: %.1f %+.1fi\n", creal(product), cimag(product));
printf("conjugate: %.1f %+.1fi\n", creal(conjugate), cimag(conjugate));
return 0;
}
输出:
modulus: 5.0
product: 11.0 -2.0i
conjugate: 3.0 -4.0i
不要因为复数包含两个实数,就直接把复数对象的指针当成任意结构指针解引用。取实部和虚部使用相应接口,也便于表达意图。
8. C11 与 C++11:相近语法,独立规则
C++ 不是 C 的严格超集。合法的 C 程序不一定是合法的 C++ 程序;能通过两种编译器,也不保证所有表达式含义完全一样。
8.1 函数声明
| 写法 | C11 | C++11 |
|---|---|---|
int f(void); |
明确无参数 | 明确无参数 |
int f(); |
不提供参数类型信息的声明 | 明确无参数 |
| 同名函数按参数列表重载 | 不支持这种语言机制 | 支持 |
两种语言都不应该先调用完全没有声明的函数。C11 的空参数列表声明与“可以不声明就调用”是两回事。
8.2 普通字符常量与字符串字面量
普通 'A' 在 C11 中的类型是 int,在 C++11 中是 char。这是类型规则,不表示每次写一个常量,运行时都必须单独分配相应大小的内存。
普通字符串字面量在 C 中是 char 数组形式,在 C++ 中包含 const 限定;无论在哪一种语言里,都不能修改字符串字面量本身。
多字符常量如 'AB' 的值由实现定义,不能把它理解成跨平台固定的字节打包语法,也不能说 C++ 一定拒绝所有多字符常量。
下面这个完整程序可分别用 C11 和 C++11 编译,观察类型差异:
#include <stdio.h>
int main(void)
{
printf("sizeof character constant: %zu\n", sizeof 'A');
printf("sizeof char: %zu\n", sizeof(char));
printf("sizeof int: %zu\n", sizeof(int));
return 0;
}
C11 的第一项与 sizeof(int) 相同;C++11 的第一项与 sizeof(char) 相同。具体 int 大小取决于实现。
8.3 const 对象与常量表达式
文件作用域或命名空间作用域的普通 const 对象,默认链接规则不同:C11 通常为外部链接;C++11 的非 volatile const 对象通常为内部链接。使用 static、extern 或已有声明可以改变相应情形。
因此,把一个带初始化的 C 全局 const 定义直接放进多个源文件包含的头文件,可能造成重复定义。C 中常见的组织方式是头文件 extern 声明,某个实现文件单独定义。
/* 在函数块内: */
const double rate = 0.06;
const double step = 24.5;
const double level = rate * step;
这段代码在 C11 中合法:自动对象可以使用运行时表达式初始化。不能把“文件作用域静态存储对象的初始化要求”误推成“C 中 const 永远不能由另一个 const 初始化”。
C11 的 const int 变量一般不是整数常量表达式。块内拿它作数组长度,可能形成变长数组;文件作用域不能因此得到一个合法的变长数组。需要编译期整数常量时可使用 enum 或合适的宏。
8.4 结构、枚举和 void 指针
| 情形 | C11 | C++11 |
|---|---|---|
struct Point 定义后写 Point p; |
需要额外 typedef 才能这样用 | 可直接使用类型名 |
| int 隐式赋给普通 enum 对象 | 可按 C 的整数转换规则处理 | 通常需要显式转换 |
普通 enum 对象 ++ |
可进行整数类型的相应操作 | 没有内建的这种递增用法 |
对象指针转成 void * |
可以 | 可以 |
void * 隐式转回其他对象指针 |
可以 | 不允许,需要相应转换 |
这解释了为什么常见的 C 分配写法 int *p = malloc(...) 不能原封不动作为 C++11 代码编译。但在 C 中给 malloc 结果增加强制转换,并不会把整个程序自动变成合适的 C++ 程序。
8.5 头文件别名与语言关键字
C11 中 bool、true、false 来自 <stdbool.h> 的宏,char16_t、char32_t 来自 <uchar.h> 的类型别名;C++11 中它们是相应的语言关键字或内建类型名。
C++11 的复数通常使用 <complex> 中的 std::complex<T>。它与 C 的 _Complex、<complex.h> 不是同一套接口。
8.6 inline 不等于内部链接
C11 的外部 inline 定义、外部定义和链接规则,与 C++11 的 inline 规则不同。C++11 普通命名空间作用域 inline 函数并不会因为 inline 自动获得内部链接。
简单的 C 头文件辅助函数常写成 static inline:static 明确内部链接,inline 表达内联相关属性。编译器是否实际展开调用,仍由实现决定。
C++11 可在多个翻译单元中出现符合规则的同一 inline 函数定义,但必须遵守其一致性要求。不要把 C 的实现文件外部定义组织方式直接移用过去。
8.7 特性比较必须注明版本
| 特性 | C11 | C++11 |
|---|---|---|
| 指定初始化器 | 支持 | 不是该版本的标准特性 |
复合字面量 (T){...} |
支持 | 不是该版本的标准语法 |
| restrict | 语言关键字 | 不是该版本的标准关键字 |
| 变长数组 | 可选支持 | 不是该版本的标准特性 |
| 伸缩型数组成员 | 支持 | 不是该版本的标准特性 |
| 带省略号参数的宏 | 支持 | 支持 |
编译器扩展不等于语言标准要求。讨论代码是否可移植,应该同时给出语言、标准版本以及是否启用扩展。
9. 这些补充知识如何使用
整数部分用于表达范围、位宽、对象大小和格式匹配;字符部分用于避免把字节处理误当作文本处理;浮点部分用于确认范围、精度与算法是否满足问题要求;语言差异部分用于读懂跨语言代码和编译错误。
需要写普通业务程序时,先用熟悉的类型和接口把约定表达清楚。遇到明确的跨平台、文本编码、数值误差或互操作需求,再按对应主题深入,不必为了用到更多语言特性而增加实现负担。

