第 43章 C标准库(The C Standard Library)
目录
43.3 printf() 函数族(The printf() Family)
43.1 引言(Introduction)
C 语言的标准库经过一些细微的修改后,被整合到了 C++ 标准库中。C 标准库提供了许多函数,这些函数在过去多年来已证明在各种场景下都非常有用,尤其是在相对底层的编程领域。
C 标准库函数远不止此处列出的这些;如果你需要了解更多信息,请参阅优秀的 C 语言教材,例如 Kernighan 和 Ritchie 合著的《C 程序设计语言》[Kernighan, 1988] 或 ISO C 标准 [C, 2011]。
43.2 文件(Files)
<stdio> 输入/输出系统基于文件。文件(FILE∗ 类型)可以指向实际文件,也可以指向标准输入输出流:stdin , stdout 和 stderr 。标准流默认可用;其他文件需要打开才能使用。
| 文件之开关 | |
| f=fopen(s,m) | 以模式 m 打开名为 s 的文件,并创建一个文件流。 如果成功,f 是指向已打开文件的 FILE* 指针;否则为 nullptr。 |
| x=fclose(f) | 关闭文件流 f ;如果成功则返回 0 。 |
使用 fopen() 函数打开的文件必须使用 fclose() 函数关闭,否则文件会一直保持打开状态,直到操作系统将其关闭。如果这会造成问题(认为是资源泄漏),则应使用 fstream(§38.2.1)。
模式(mode)是一个C风格的字符串,包含一个或多个字符,用于指定如何打开文件(以及打开后如何使用文件):
| 文件模式 | |
| "r" | 读取(reading) |
| "w" | 写入(writing)(丢弃之前的内容) |
| "a" | 追加(append)(添加到末尾) |
| "r+" | 读取并写入 |
| "w+" | 写入并读取(丢弃之前的内容) |
| "b" | 二进制(binary)模式;需与其他一种或多种模式配合使用。 |
在特定系统中,可能存在(而且通常确实存在)更多选项。例如,x 有时用于表示“在此打开操作之前,该文件必须不存在”。某些选项可以组合使用,例如,fopen("foo","rb") 尝试以二进制读取模式打开名为 foo 的文件。stdio 和 iostream 的 I/O 模式应该相同(§38.2.1)。
43.3 printf() 函数族(The printf() Family)
最常用的C标准库函数是输出函数。但我更喜欢iostream库,因为它类型安全且可扩展。格式化输出函数printf() 被广泛使用(包括在C++程序中),并且在其他编程语言中也得到了广泛的模仿:
| printf() | |
| n=printf(fmt,args) | 将格式字符串 fmt打印到标准输出, 并根据需要插入参数 args作为占位。 |
| n=fprintf(f,fmt,args) | 将格式字符串 fmt 打印到文件 f 中, 并根据需要插入参数 args作为占位。 |
| n=sprintf(s,fmt,args) | 将格式字符串 fmt打印到 C 风格字符串 s 中, 并根据需要插入参数args作为占位。 |
对于每一个函数版本,n 表示写入的字符数;如果输出失败,则 n 为负数。printf() 的返回值通常会忽略。
printf() 函数的声明如下:
int printf(const char∗ format ...);
换句话说,它接受一个 C 风格的字符串(通常是字符串字面量),后面跟着任意数量的任意类型的参数。这些“额外参数”的含义由格式字符串中的转换说明符控制,例如 %c(以字符形式打印)和 %d(以十进制整数形式打印)。例如:
int x = 5;
const char∗ p = "Pedersen";
printf("the value of x is '%d' and the value of s is '%s'\n",x,s);
百分号 (%) 后面的字符控制参数的处理方式。第一个百分号 (%) 应用于第一个“附加参数”(此处,%d 应用于 x ),第二个百分号 (%) 应用于第二个“附加参数”(此处,%s 应用于 s),依此类推。具体来说,printf() 函数的输出是:
the value of x is '5' and the value of s is 'Pedersen'
后接一个换行符。
一般来说,% 转换指令与其所应用的类型之间的对应关系无法进行检查,即使可以检查,通常也不会进行检查。例如:
printf("the value of x is '%s' and the value of s is '%x'\n",x,s); // oops
转换说明符集非常庞大(并且多年来一直在增长),提供了极大的灵活性。各种系统支持的选项超出了 C 标准提供的选项。另请参阅用于 strftime() 格式化的选项集(§43.6)。在 % 符号之后,可能出现以下内容:
− —— 一个可选的负号,用于指定转换后的值在字段中的左对齐方式;
+ —— 一个可选的加号,用于指定有符号类型的值始终以 + 或 − 开头;
0 —— 一个可选的零,用于指定是否使用前导零来填充数值。如果指定了 − 或精度,则此 0 将忽略;
# —— 一个可选的 # 符号,用于指定浮点数值即使其后没有非零数字也会打印小数点,会打印末尾的零,八进制数值会以0开头打印,十六进制数值会以 0x 或 0X 开头打印;
d —— 一个可选的数字字符串,用于指定字段宽度;如果转换后的值字符数少于字段宽度,则会在左侧(如果指定了左对齐指示符,则在右侧)用空格填充,以达到指定的字段宽度;如果字段宽度以零开头,则会用零填充而不是空格填充;
. —— 一个可选的句点,用于将字段宽度与后面的数字字符串分隔开;
d —— 一个可选的数字字符串,用于指定精度,即小数点后显示的位数(用于e- 和f- 格式转换),或者指定从字符串中打印的最大字符数;
∗ —— 字段宽度或精度可以用 ∗ 代替数字字符串。在这种情况下,一个整数参数将提供字段宽度或精度;
h —— h 是一个可选字符,用于指定后面的 d , i , o , u , x 或 X 对应于一个(有符号或无符号)短整型参数;
hh —— 可选的字符对 hh,用于指定后面的 d , i , o , u , x 或 X 参数应视为(有符号或无符号)char 类型的参数;
l —— 可选字符 l(小写字母 l ),用于指定其后的 d , i , o , u , x 或 X 对应于一个(有符号或无符号)长整型参数;
ll —— 可选的一对字符 ll (两个字母 l ),用于指定后面的 d , i , o , u , x 或 X 对应于一个(有符号或无符号)长长整型参数;
L —— 一个可选字符 L,用于指定其后的 a,A,e,E,f,F,g 或 G 对应于一个长双精度浮点数参数;
j —— 指定紧随其后的 d , i , o , u , x 或 X 对应于 intmax_t 或 uintmax_t 类型的参数;
z —— 指定后面的 d , i , o , u , x 或 X 对应于 size_t 类型的参数;
t —— 指定后面的 d , i , o , u , x 或 X 对应于 ptrdiff_t 类型的参数;
% —— 表示要打印字符 % ;不使用任何参数;
c —— 一个字符,用于指示要应用的转换类型。转换字符及其含义如下:
· d —— 整数参数将转换为十进制表示法;
· i —— 整数参数将转换为十进制表示法;
· o —— 整数参数将转换为八进制表示法;
· x —— 整数参数将转换为十六进制表示法;
· X —— 整数参数将转换为十六进制表示法;
· f —— float或double参数会转换为十进制表示法,格式为 [−]ddd.ddd。小数点后的数字位数等于参数的精度。如有必要,数字会进行四舍五入。如果未指定精度,则默认显示六位小数;如果精度明确指定为 0 且未指定 # 标志,则不打印小数点;
· F —— 与 %f 类似,但使用大写字母表示 INF,INFINITY 和 NAN。
· e —— float 或double浮点数参数将转换为科学计数法表示,格式为 [−]d.ddde+dd 或 [−]d.ddde−dd,其中小数点前只有一位数字,小数点后的数字位数等于参数指定的精度。如有必要,数字会进行四舍五入。如果未指定精度,则默认显示六位小数;如果显式指定精度为 0 且未指定 # 标志,则不打印任何数字和小数点;
· E —— 同 e ,但使用大写字母 E 来表示指数;
· g —— float 或double参数会以样式 d, 样式 f 或样式 e 的格式打印,具体选择哪种格式取决于哪种格式能在最小的空间内提供最高的精度;
· G —— 同 g ,但使用大写字母 E 来表示指数;
· a —— 同 g ,double以十六进制格式打印,格式为 [−]0xh.hhhhp+d 或 [−]0xh.hhhhp+d ;
· A —— 与 %a 类似,但使用 X 和 P 代替 x 和 p;
· c —— 字符参数将打印出来。空字符将忽略;
· s —— 该参数被视为字符串(字符指针),程序会打印字符串中的字符,直到遇到空字符或达到精度规范指定的字符数为止;但是,如果精度为 0 或未指定精度,则会打印所有字符,直到遇到空字符为止;
· u —— 无符号整数参数将转换为十进制表示形式;
· n —— printf() ,fprintf() 或 sprintf() 函数调用迄今为止写入的字符数将写入到指向整型变量的指针所指向的整型变量中。
在任何情况下,字段不存在或字段宽度过小都不会导致字段被截断;只有当指定的字段宽度超过实际宽度时才会进行填充。
以下是一个精选的例子:
char∗ line_format = "#line %d \"%s\"\n";
int line = 13;
char∗ file_name = "C++/main.c";
printf("int a;\n");
printf(line_format,line ,file_name);
输出:
int a;
#line 13 "C++/main.c"
使用 printf() 函数是不安全的,因为它不会进行类型检查。例如,以下是一种众所周知的方法,可能会导致输出结果不可预测、发生段错误,甚至更糟:
char x = 'q';
printf("bad input char: %s",x); // %s should have been %c
然而, printf() 函数提供了极大的灵活性,而且其形式对于 C 语言程序员来说非常熟悉。
由于 C 语言不像 C++ 那样拥有用户自定义类型,因此它没有提供为用户自定义类型(例如complex ,vector 或 string )定义输出格式的功能。strftime() 函数(第 43.6 节)的格式就是一个例子,它展示了尝试设计另一套格式说明符会带来多么复杂的麻烦。
C 语言的标准输出 stdout 对应于 C++ 的 cout。C 语言的标准输入 stdin 对应于 C++ 的 cin。C 语言的标准错误输出 stderr 对应于 C++ 的 cerr。C 语言标准 I/O 和 C++ I/O 流之间的这种对应关系非常紧密,以至于 C 风格的 I/O 和 I/O 流可以共享缓冲区。例如,可以混合使用 cout 和 stdout 操作来生成单个输出流(这在 C 和 C++ 混合编程中并不罕见)。这种灵活性是有代价的。为了获得更好的性能,请不要在同一个流上混合使用 stdio 和 iostream 操作。为了确保这一点,请在第一次 I/O 操作之前调用 ios_base::sync_with_stdio(false) (§38.4.4)。
stdio 库提供了一个函数 scanf(),它是一种输入操作,其风格类似于 printf() 函数。例如:
int x;
char s[buf_siz e];
int i = scanf("the value of x is '%d' and the value of s is '%s'\n",&x,s);
在这里,scanf() 函数尝试将一个整数读入变量 x,并将一系列非空白字符读入变量 s。非格式字符指定输入中必须包含该字符。例如:
the value of x is '123' and the value of s is 'string '\n"
它会将 123 读取到 x 中,并将字符串及其后的一个 0 读取到 s 中。如果 scanf() 调用成功,返回值(在上面的示例中为 i)将是成功赋值的参数指针的数量(在示例中应该是 2);否则,返回 EOF。这种指定输入的方式容易出错(例如,如果你忘记了输入行中字符串后面的空格,会发生什么?)。scanf() 的所有参数都必须是指针。我强烈建议不要使用 scanf()。
那么,如果我们必须使用标准库函数,我们可以如何进行输入呢?一个常见的答案是“使用标准库函数 gets()”。
// very dangerous code:
char s[buf_siz e];
char∗ p = gets(s); // read a line into s
函数调用 p=gets(s) 会读取字符到 s 中,直到遇到换行符或文件结束符,并在最后一个写入 s 的字符后添加一个 '\0'。如果遇到文件结束符或发生错误,p 将被设置为 nullptr;否则,p 将被设置为 s。切勿使用 gets(s) 或其类似函数 (scanf("%s",s))!多年来,它们一直是病毒编写者的最爱:通过提供会使输入缓冲区(示例中的 s)溢出的输入,程序可能会破坏,计算机也可能被攻击者控制。sprintf() 函数也可能存在类似的缓冲区溢出问题。C 标准库的 C11 版本提供了一整套替代的 stdio 输入函数,这些函数接受一个额外的参数来防止溢出,例如 gets_s(p,n)。至于 iostream 的非格式化输入,用户需要自行判断具体遇到了哪种终止条件(§38.4.1.2;例如,字符过多、终止符或文件结束符)。
stdio 库还提供了一些简单实用的字符读写函数:
| stdio字符串函数 | |
| x=getc(st) | 从输入流 st 中读取一个字符; x 是该字符的整数值,如果到达文件末尾或发生错误,则 x 的值为 EOF。 |
| x=putc(c,st) | 将字符 c 写入输出流 st; x 是写入字符的整数值,如果发生错误,则为 EOF。 |
| x=getchar() | x=getc(stdin) |
| x=putchar(c) | x=putc(c,stdout) |
| x=ungetc(c,st) | 将字符 c 放回输入流 st 中; x 是字符 c 的整数值,如果发生错误,则 x 为 EOF。 |
这些操作的结果是一个整数(而不是字符,否则无法返回 EOF )。例如,这是一个典型的 C 语言风格的输入循环:
int ch; // note: not ‘‘char ch;’’
while ((ch=g etchar())!=EOF) { /* do something */ }
不要对同一个流连续调用两次 ungetc() 函数。这样做会导致结果不确定且不可移植。
还有很多其他的标准输入输出函数;如果你需要了解更多信息,请参阅一本优秀的 C 语言教材(例如,《C 程序设计语言》(K&R 版))。
43.4 C风格字符串(C-Style Strings)
C 风格字符串是一个以零字符结尾的字符数组。这种字符串概念由 <cstring>(或 <string.h>;注意:不是 <string>)和 <cstdlib> 中定义的一系列函数支持。这些函数通过 char* 指针(对于只读内存使用 const char* 指针,但不是 unsigned char* 指针)操作 C 风格字符串:
| C风格字符串操作 | |
| x=strlen(s) | 计算字符数(不包括终止符 0) |
| p=strcpy(s,s2) | 将 s2 复制到 s 中;[s:s+n) 和 [s2:s2+n) 范围不能重叠; p=s;终止符 0 也将被复制。 |
| p=strcat(s,s2) | 将字符串 s2 复制到字符串 s 的末尾;p 指向 s;终止符 0 也将被复制。 |
| x=strcmp(s, s2) | 按字典顺序比较:如果 s 小于 s2,则 x 为负数; 如果 s 等于 s2,则 x 等于 0;如果 s 大于 s2,则 x 为正数。 |
| p=strncpy(s,s2,n) | strcpy 最多只能复制 n 个字符;可能无法复制终止符 0。 |
| p=strncat(s,s2,n) | strcat 最多处理 n 个字符;可能无法复制终止符 0。 |
| x=strncmp(s,s2,n) | 比较最多 n 个字符的字符串 |
| p=strchr(s,c) | p 指向 s 中的第一个 c。 |
| p=strrchr(s,c) | p 指向 s中的最后一个 c 。 |
| p=strstr(s,s2) | p指向字符串s中第一个与s2相等的子字符串的起始字符。 |
| p=strpbrk(s,s2) | p指向字符串s中也存在于字符串s2中的第一个字符。 |
请注意,在 C++ 中,strchr() 和 strstr() 函数已被重载,以确保类型安全(它们不像 C 语言中的对应函数那样可以将 const char* 转换为 char* )。另请参阅第 36.3.2 节,第 36.3.3 节和第 36.3.7 节。
| C风格字符串数值转换 p指向s中未用于转换的第一个字符; b是数字的基数[2:36],或为0,表示使用C语言源代码风格的数。 | |
| x=atof(s) | x 是一个由s表示的double数 |
| x=atoi(s) | x 是一个由s表示的int数 |
| x=atol(s) | x 是一个由s表示的long数 |
| x=atoll(s) | x 是一个由s表示的long long数 |
| x=strtod(s,p) | x 是一个由s表示的double数 |
| x=strtof(s,p) | x 是一个由s表示的float数 |
| x=strtold(s,p) | x 是一个由s表示的long double数 |
| x=strtol(s,p,b) | x 是一个由s表示的long数 |
| x=strtoll(s,p,b) | x 是一个由s表示的long long数 |
| x=strtoul(s,p,b) | x 是一个由s表示的unsigned long数 |
| x=strtoull(s,p,b) | x 是一个由s表示的unsigned long long数 |
如果转换后的浮点值超出目标类型的表示范围,则会将 errno 设置为 ERANGE(§40.3)。另请参见 §36.3.5。
43.5 内存管理(Memory)
内存操作函数通过 void* 指针(对于只读内存则使用 const void* 指针)对“原始内存”(类型未知)进行操作:
| C风格内存操作 | |
| q=memcpy(p,p2,n) | 将 p2 指向的内存区域中的 n 个字节复制到 p 指向的内存区域(类似于 strcpy); [p:p+n) 和 [p2:p2+n) 这两个内存区域不能重叠;q=p |
| q=memmove(p,p2,n) | 将 p2 中的 n 个字节复制到 p;q=p |
| x=memcmp(p,p2,n) | 比较指针 p2 指向的 n 个字节与指针 p 指向的对应 n 个字节; x<0 表示小于,x==0 表示等于,0<x 表示大于。 |
| q=memchr(p,c,n) | 在区间 [p, p+n) 中查找字符 c(已转换为unsigned char); 如果找到,则 q 指向该元素;如果未找到,则 q=0。 |
| q=memset(p,c,n) | 将字符 c(已转换为unsigned char)复制到 [p:p+n) 范围内的每个位置;q=p |
| p=calloc(n,s) | p 指向在自由存储区分配的 n*s 字节内存,这些内存已初始化为 0; 如果无法分配内存,则 p 为 nullptr。 |
| p=malloc(n) | p 指向堆上 n 个未初始化的字节; 如果无法分配 s 个字节,则 p 为 nullptr。 |
| q=realloc(p,n) | q 指向堆上的 n 个字节; p 必须是由 malloc() 或 calloc() 返回的指针,或者为 nullptr; 如果可能,重用 p 指向的内存空间; 如果不可能,将 p 指向的区域中的所有字节复制到新的内存区域; 如果无法分配 s 个字节,则 q 设置为 nullptr。 |
| free(p) | 释放指针 p 指向的内存;p 必须为 nullptr 或由 malloc() , calloc() 或 realloc() 函数返回的指针。 |
请注意,malloc() 等函数不会调用构造函数,free() 也不会调用析构函数。因此,请勿将这些函数用于具有构造函数或析构函数的类型。此外,对于任何具有构造函数的类型,也绝对不应使用 memset() 函数。
请注意,当需要比从指针 p 开始的可用内存更多时,realloc(p,n) 函数会重新分配(即复制)从 p 开始存储的数据。例如:
int max = 1024;
char∗ p = static_cast<char∗>(malloc(max));
char∗ current_word = nullptr;
bool in_word = false;
int i=0;
while (cin.get(&p[i]) {
if (isletter(p[i])) {
if (!in_word)
current_word = p;
in_word = true;
}
else
in_word = false;
if (++i==max)
p = static_cast<char∗>(realloc(p,max∗=2)); // double allocation
// ...
}
我希望你已经发现了这个严重的错误:如果调用了 realloc() 函数,current_word 指针可能(也可能不会)指向 p 指针当前指向的内存分配区域之外的位置。
在大多数情况下,使用vector(§31.4.1)比使用 realloc() 函数更好。
mem* 系列函数位于 <cstring> 头文件中,而内存分配函数位于 <cstdlib> 头文件中。
43.6 日期和时间(Date and Time)
在 <ctime> 头文件中,你可以找到与日期和时间相关的多种类型和函数:
| 时期和时间类型 | |
| clock_t | 一种用于存储短时间间隔(可能只有几分钟)的算术类型。 |
| time_t | 一种用于存储长时间间隔(可能长达几个世纪)的算术类型。 |
| tm | 一个用于存储日期的时间(自1900年以来)的struct。 |
struct tm 的定义如下:
struct tm {
int tm_sec; // second of minute [0:61]; 60 and 61 represent leap seconds
int tm_min; // minute of hour [0:59]
int tm_hour; // hour of day [0:23]
int tm_mday; // day of month [1:31]
int tm_mon; // month of year [0:11]; 0 means Januar y (note: not [1:12])
int tm_year; // year since 1900; 0 means year 1900, and 115 means 2015
int tm_wday; // days since Sunday [0:6]; 0 means Sunday
int tm_yday; // days since Januar y 1 [0:365]; 0 means Januar y 1
int tm_isdst; // hours of daylight savings time
};
系统时钟由函数 clock() 提供支持,该函数的一些相关函数赋予其返回类型 clock_t 以特定含义:
| 日期和时间函数 | |
| t=clock() | t 是程序启动以来经过的时钟周期数;t 的类型为 clock_t。 |
| t=time(pt) | t 是当前日历时间;pt 是一个 time_t 类型的指针,或者为 nullptr; t 是一个 clock_t 类型的值;如果 pt 不为 nullptr,则 *pt = t。 |
| d=difftime(t2,t1) | d 是一个double,表示 t2−t1 的值,单位为秒。 |
| ptm=localtime(pt) | 如果 pt 为 nullptr,则 ptm 也为 nullptr;否则 ptm 指向 ∗pt 对应的 time_t 类型的本地时间。 |
| ptm=gmtime(pt) | 如果 pt 为 nullptr,则 ptm 也为 nullptr;否则,ptm 指向 ∗pt 对应的格林威治标准时间 (GMT-Greenwich Mean Time) 的 time_t 类型值。 |
| t=mktime(ptm) | 对于 ∗ptm,类型为 time_t,或者 time_t(−1) |
| p=asctime(ptm) | p 是 ∗ptm 的 C 风格字符串表示形式 |
| p=ctime(t) | p=asctime(localtime(t)) |
| n=strftime(p,max,fmt,ptm) | 根据格式字符串 fmt 的控制,将 ∗ptm 的内容复制到 [p:p+n+1) 范围内;超出 [p:p+m) 范围的字符将被丢弃; 如果发生错误,则 n==0;p[n]=0 。 |
调用 asctime() 函数的结果示例是:
"Sun Sep 16 01:03:52 1973\n"
以下是使用 clock() 函数测量函数执行时间的示例:
int main(int argc, char∗ argv[])
{
int n = atoi(argv[1]);
clock_t t1 = clock();
if (t1 == clock_t(−1)) { // clock_t(-1) means "clock() didn’t wor k"
cerr << "sorry, no clock\n";
exit(1);
}
for (int i = 0; i<n; i++)
do_something(); // timing loop
clock_t t2 = clock();
if (t2 == clock_t(−1)) {
cerr << "sorry, clock overflow\n";
exit(2);
}
cout << "do_something() " << n << " times took "
<< double(t2−t1)/CLOCKS_PER_SEC << " seconds"
<< " (measurement granularity: " << CLOCKS_PER_SEC
<< " of a second)\n";
}
在进行除法运算之前,必须进行显式类型转换,将 (t2−t1) 转换为 double 类型,因为 clock_t 类型可能是一个整数。对于 clock() 函数返回的 t1 和 t2 值,double(t2−t1)/CLOCKS_PER_SEC 是系统对两次调用之间时间间隔(以秒为单位)的最佳近似值。
比较 <ctime> 头文件提供的功能与 <chrono> 头文件提供的功能;参见第 35.2 节。
如果处理器不支持 clock() 函数,或者时间间隔太长无法测量,则 clock() 函数会返回 clock_t(-1) 。
strftime() 函数使用 printf() 格式字符串来控制 tm 结构的输出。例如:
void almost_C()
{
const int max = 80;
char str[max];
time_t t = time(nullptr);
tm∗ pt = localtime(&t);
strftime(str,max,"%D, %H:%M (%I:%M%p)\n",pt);
printf(str);
}
输出结果大致如下:
06/28/12, 15:38 (03:38PM)
strftime() 函数的格式化字符几乎构成了一种小型编程语言:
| 日期和时间格式化 | |
| %a | 缩写的星期名称 |
| %A | 星期全称 |
| %b | 月份缩写 |
| %B | 完整的月份名称 |
| %c | 日期和时间表示 |
| %C | 年份除以100,然后截断取整,得到一个十进制整数 [00:99] |
| %d | 月份中的日期,以十进制数字表示 [01:31] |
| %D | 等价于 %m/%d/%y |
| %e | 月份中的日期,以十进制数字表示 [1:31]; 个位数前面有一个空格。 |
| %F | 等价于 %Y−%m−%d; ISO 8601日期格式 |
| %g | 以十进制数字表示的基于周的年份的最后两位数字 [00:99] |
| %G | 以周为单位的年份,用十进制数字表示(例如,2012) |
| %h | 等价于 %b |
| %H | 以十进制数字表示的小时(24小时制)[00:23] |
| %I | 小时(12小时制)以十进制数字表示 [01:12] |
| %j | 一年中的第几天(以十进制数字表示)[001:366] |
| %m | 月份以十进制数字表示 [01:12] |
| %M | 以小数形式表示的分钟数 [00:59] |
| %n | 换行符 |
| %p | 该地区用于表示12小时制时间的上午/下午标记。 |
| %r | 12小时制时间 |
| %R | 等价于 %H:%M |
| %S | 秒数以小数形式表示 [00:60] |
| %t | 水平制表符 |
| %T | 等价于 %H:%M:%S; ISO 8601 时间格式 |
| %u | ISO 8601 日期以十进制数表示 [1:7];星期一为 1。 |
| %U | 一年中的周数(第一个星期日为第一周的第一天),以十进制数字表示 [00:53] |
| %V | ISO 8601 周数,以十进制数字表示 [01:53] |
| %w | 星期几以十进制数表示 [0:6];星期日为 0。 |
| %W | 一年中的周数(第一个星期一为第一周的第一天),以十进制数字表示 [00:53] |
| %x | 本地适用的日期表示格式 |
| %X | 本地的适当时间表示方式 |
| %y | 年份的最后两位数字,以十进制数表示 [00:99] |
| %Y | 年份,以十进制数字表示(例如,2012) |
| %z | ISO 8601 格式的 UTC 时间偏移量为 -0430(比 UTC 时间(格林威治时间)晚 4.5 小时);如果无法确定时区,则不显示任何字符。 |
| %Z | 本地时区的名称或缩写;如果未知时区,则为空。 |
| %% | 字符 % |
此处提到的本地化是指程序的全局本地化。
某些转换说明符可以使用 E 或 O 修饰符进行修改,以指示特定于实现和特定于本地化的替代格式。例如:
| 日期和时间格式修饰符示例 | |
| %Ec | 本地化的替代日期和时间表示法 |
| %EC | 在本地化替代表示形式中,基准年(时期)的名称 |
| %OH | 小时(24小时制),使用本地化的替代数值符号。 |
| %Oy | 年份的最后两位数字,使用本地化的替代数值符号表示。 |
strftime() 函数由 put_time 语言特征(§39.4.4.1)使用。
有关C++风格的时间处理功能,请参见第35.2节。
43.7 其它函数(Etc.)
| <stdlib.h> 中的其它函数 | |
| abort() | 程序“异常”终止。 |
| exit(n) | 程序以值 n 终止; n==0 表示程序成功终止。 |
| system(s) | 将字符串作为命令执行(此操作取决于操作系统)。 |
| qsort(b,n,s,cmp) | 使用比较函数 cmp 对从地址 b 开始、包含 n 个元素且每一个元素大小为 s 的数组进行排序。 |
| bsearch(k,b,n,s,cmp) | 使用比较函数 cmp,在从位置 b 开始、包含 n 个元素且每一个元素大小为 s 的已排序数组中查找元素 k 。 |
| d=rand() | d 是一个伪随机数,范围在 [0:RAND_MAX] 之间。 |
| srand(d) | 使用 d 作为种子生成一系列伪随机数。 |
qsort() 和 bsort() 函数使用的比较函数 (cmp) 必须具有以下类型:
int (∗cmp)(const void∗ p, const void∗ q);
也就是说,排序函数无法获知数组参数的类型信息,它们只是简单地将数组参数视为字节序列。返回的整数是
• 如果 *p 小于 *q,则结果为负数。
• 如果 *p 等于 *q,则结果为零。
• 如果 *p 大于 *q,则结果为正数。
这与 sort() 不同,sort() 使用的是传统的 < 。
请注意,exit() 和 abort() 函数不会调用析构函数。如果你希望已创建对象的析构函数被调用,请抛出异常(§13.5.1)。
类似地,<csetjmp> 头文件中的 longjmp() 函数是一个非局部跳转函数,它会展开栈,直到找到与匹配的 setjmp() 函数对应的结果。它不会调用析构函数。如果在程序中的同一位置抛出异常会调用析构函数,则 longjmp() 的行为是未定义的。切勿在 C++ 程序中使用 setjmp()。
更多关于C标准库函数的信息,请参阅[Kernighan, 1988]或其他权威的C语言参考书籍。
在 <cstdint> 头文件中,我们可以找到 int_fast16_t 和其他标准整数类型别名:
| 整数类型别名, N可以是8,16,32,或64 | |
| int_N_t | N字节的整数类型,例如 int_8_t |
| uint_N_t | N字节无符号整数类型,例如 uint_16_t |
| int_leastN_t | 至少N位宽的整数类型,例如 int_least16_t |
| uint_leastN_t | 至少N位宽的无符号整数类型,例如 uint_least32_t |
| int_fastN_t | 至少N位宽的整数类型,例如 int_fast32_t |
| uint_leastN_t | 至少N位宽的无符号整数类型,例如 uint_fast64_t |
此外,在 <cstdint> 头文件中,我们还可以找到用于表示实现环境中最大有符号整数类型和最大无符号整数类型的类型别名。例如:
typedef long long intmax_t; // largest signed integer type
typedef unsigned long long uintmax_t; // largest unsigned integer type
43.8 建议(Advice)
[1] 如果担心资源泄漏,请使用 fstream 而非 fopen()/fclose();§43.2。
[2] 出于类型安全和可扩展性的考虑,优先使用 <iostream> 而非 <stdlib>;§43.3。
[3] 切勿使用 gets() 或 scanf("%s",s);§43.3。
[4] 出于易用性和资源管理简便性的考虑,优先使用 <string> 而非 <cstring>;
§43.4。
[5] 仅对原始内存使用 C 语言的内存管理例程,例如 memcpy();§43.5。
[6] 优先使用 vector 而非 malloc() 和 realloc();§43.5。
[7] 请注意,C 标准库不了解构造函数和析构函数;§43.5。
[8] 出于计时目的,优先使用 <chrono> 而非 <ctime>;§43.6。
[9] 出于灵活性、易用性和性能方面的考虑,优先使用 sort() 而非 qsort();§43.7。
[10] 不要使用 exit();而是抛出异常;§43.7。
[11] 不要使用 longjmp();而是抛出异常;§43.7。
内容来源:
<<The C++ Programming Language >> 第4版,作者 Bjarne Stroustrup

2万+

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



