Standards are paper. I use paper to wipe my butt every day.
That's how much that paper is worth. —— Linus Torvalds.
储存有程序代码的文本被称为源文件(或预处理文件),一个源文件和他使用#include
包含的所有其他源文件和头文件被统一称作一个 预处理单元
,预处理之后的单元叫做 翻译单元
。
程序的独立翻译单元之间可以使用以下方式进行通信:
各个翻译单元之间可以独立翻译,然后最后链接到一起。
TODO: 检查空字符 \0
和空格字符
是否有理解错误
翻译阶段的顺序如下:
\
,将物理源行拼成逻辑源行。只有物理源行的最后一个 \
才有资格被这样处理。任何非空的源文件应以换行符结尾,否则其前不应紧跟反斜杠字符 \
token
和空白字符序列(如注释),源文件不得以不完整的预处理标记或注释结尾,每一个注释会被一个空格取代(所以反斜杠后面不能接注释),换行字符会被保留。TODO:Whether each nonempty sequence of white-space characters other than new-line is retained or replaced by one space character is implementation-defined_Pragama
一元运算符表达式。如果在token
连接中生成了与通用字符名语法匹配的字符序列,则其行为是未定义的,如6.10.3.3中的##
运算符。使用#include
预处理指令会递归的处理步骤1到4。"ab" "cd"
-> "abcd"
token
都讲被转化为一个token
,生成的tokens
将会被用于语法和语义分析,并作为翻译单元进行翻译运行环境有以下两种:
无论在哪种环境下,C程序都会被环境调用。在C程序启动之前,所有的静态储存类型1都会被初始化2并设定为初始值。这种初始化的方式和时间均未具体规定,程序终止后控制权将返回到运行环境。
引用
1: 静态储存环境
C标准学习笔记 > 6.7.1 储存类型说明符
2: 初始化
6.7.9
程序启动时所被调用的函数为main
,其声明可以是:
int main(void)
或:
int main(int argc, char *argv[])
或上一形式的等效形式:
int main(int argc, char **argv)
或其他类型的由托管环境锁指定的声明。
参数的名字并不重要,但为了方便起见以上述命名进行表述:
当这些参数被声明后,这些参数有如下的限制:
argc
的值应为非负argv[argc]
的值应为空指针NULL
argc
的值不为零,则argv[0]
到argv[argc - 1]
的值都应指向字符串。这些字符串由托管环境所指定,其目的是获取或传递信息。如果主机环境无法提供大写形式的字符串,则main
程序在接收的时候应确保其能接收小写形式的字符串。argc
的值大于0,则argc[0]
指向的字符串应表示程序名。若托管环境无法提供该功能,则argc[0][0]
应为空字符\0
。若argc
的值大于一,则argv[1]
到argv[argc - 1]
的值应表示程序参数。argc
和argv
及其指向的字符串应可由程序修改,并在程序生命周期中一直保存其储存的值。const
、 volatile
、 restrict
char
视为 signed char
是实现定义的。C11标准中的关键词如下:
以上标记在翻译阶段7和8被预留为关键词(区分大小写),不得用作其他行为。关键词 _Imaginary
用作指定为虚数类型。
句法规则
TODO 没看懂什么意思,编译原理相关
语义
_
、小写和大写拉丁字母以及其他字符)和数字,用于指定C标准学习笔记 > 6 2 1 标识符的范围中所述的一个或多个实体。小写字母和大写字母是不同的。标识的最大长度没有具体限制。实现限制
常量类型主要有
限制
每一个常量类型都应具有类型,并且其值应在对应类型的可表示范围之内。
注意本文所述均为整数常量的定义、而非整数变量的运算。
对于整形常量、可以用前缀定义其进制,后缀定义其类型。
整数常量主要有以下几种类型:
十进制常量整数类型直接定义即可
八进制常量整数类型定义时应带前缀0
,例如:
int dec = 10; //等于十进制的10
int oct = 010; //等于十进制的8
十六进制常量整数类型定义时应带前缀0x
,例如:
int hex = 0x10; //等于10进制的16
整数常量类型的修饰后缀:
unsigned后缀可以为:
\ u
或者 U
long后缀可以为:
\ l
或者 L
long-long后缀可以为:
\ ll
或者 LL
对于枚举类型的常量,其标识符的类型为 int
限制
sizeof
运算符不能用于函数类型(参数不可为函数名)或不完备的变量类型,或用括号包裹的变量类型,或含有位域的成员表达式。
_Alignof
运算符不能用于函数类型(参数不可为函数名)或不完备的变量类型。
如:
#include <stdio.h>
typedef struct {
int a : 2;
int b : 2;
} typea_t;
typedef struct {
int a;
int b;
} typeb_t;
int main()
{
typea_t typea;
typeb_t typeb;
printf("testa_t: %lld\r\n", sizeof(typea_t)); //4
//printf("testa_t: %lld\r\n", sizeof((typea_t)));//参数不可为含括号的类型
//printf("testa_t: %lld\r\n", sizeof(typea.a)); //参数不可为含位域的成员
printf("testa_t: %lld\r\n", sizeof(typeb.a)); //4
//printf("testa_t: %lld\r\n", sizeof(main)); //参数不可为函数名
return 0;
}
语义
sizeof
运算符生成其操作数的大小(以字节为单位),操作数可以是表达式或类型的括号名称。大小由操作数的类型决定。结果是一个整数。如果操作数的类型是可变长度数组类型,则计算该操作数(此时不可再简单理解为宏,是运行时求值); 否则,不计算该操作数,结果为整数常量。#include <stdlib.h>
size_t vla_test(int size)
{
int array[size];
return sizeof(array);
}
int main()
{
printf("%lld", vla_test(89)); //356
return 0;
}
限制
%
)的操作数都应当是整数类型。语义
*
的结果是两个操作数的乘积。/
的结果是第一个操作数除以第二个操作数的商; %
运算符的结果是取余数。在这两个操作中,如果第二个操作数的值为零,则行为是未定义的。/
运算符的结果是丢弃所有小数部分的代数商(通常被称为"向零截断")。如果商 a/b
是可表示的,表达式 (a/b) * b + a%b
应等于 a
; 否则,a/b
和 a%b
的行为都是未定义的。#include <stdio.h>
int main() {
printf("%d\r\n", 5/3); // 5/3=1.6667 => 1
printf("%d\r\n", -5/3); // -5/3=-1.667 => -1
printf("%d\r\n", -1/3); // -1/3=-0.333 => 0
}
语义
限制
每一个操作数都应当为整数类型。
语义
E1 << E2
的结果是 E1
左移 E2
位的位置,空位用零填充。如果 E1
是无符号类型,则结果的值为: E1
有符号类型的非负值,并且E1 >> E2
的结果是 E1
右移 E2
位的位置,空位用零填充。如果 E1
是无符号类型,则结果的值为:E1
是一个有符号类型的负值,则结果是由实现来定义的。句法
语义
void
表达式进行计算(即不考虑返回值);然后计算右操作数,并且将右操作数的值作为该表达式的值。int a[] = { 1, 2, 3 }; //此处逗号不生效,仅当分隔符。
printf("This line is: %d\r\n", __LINE__); //此处逗号也不生效。
int a = 5;
func(a, (t=3, t+2), c); //等价于 fuc(5, 5, c);
if(a, a+1 > 5)
{
printf("value of `a, a+1` is 6\r\n");
}
语义
储存类型说明符主要有以下几种:
限制
_Thread_local
可以和 static
、 extern
联合使用以外,一个变量最多使用一个储存类型说明符。 _Thread_local
则他还应包含 static
或 extern
。TODO: If _Thread_local
appears in any declaration of an object, it shall be present in every declaration of that object._Thread_local
不应该出现在函数声明中的说明符中。typedef
被称为储存类型说明符只是为了语法方便,这一特性在章节6.7.8中有所讨论,TODO:且在6.2.2和6.2.4中讨论了各种联系以及储存周期的含义。register
的说明符建议使用在需要尽可能快的访问对象的地方。当然编译器可以不听你的。extern
以外的显示的储存类型说明符。typedef
以外的储存类型说明符来说明struct
和union
对象,则储存说明符所产生的属性也将作用于除链接(TODO:linkage?)以外的成员。语义
变量类型说明符主要有以下几种:
struct
或union
所定义的说明符enum
所定义的说明符typedef
所定义的说明符语义
函数说明符主要有以下两种:
inline
_Noreturn_
限制
main
函数中。inline
修饰符建议编译器将其编译为内联干啥,主要用于尽可能快的调用该函数。当然编译器可以不听你的。inline
修饰一个函数的声明,则其应被定义在同一个翻译单元中(也就是源文件必须include其头文件,且由于该特性,内联函数可以多次被定义???)_Noreturn_
的函数不能返回调用者,若未写明return
而导致函数执行完毕退出的,其结果是未定义行为,如下例中 i <= 0
的情况:_Noreturn_ void func(int i)
{
if(i > 0)
{
abort(); //OK
}
// UB.
}
语句主要有以下几种:
语义
Labeled statements主要包含以下几种:
identifier
: statement (即普通label)constant-expression
: statement (即case+常量表达式)限制
case
和 default
标签应只能在 switch
中出现,更多的约束将在 switch
语句下进行讨论。Label
同一个函数内(而不是块)不能同名,标签作用域是整个函数,即可以如下:#include <stdio.h>
int main(int argc, char** argv)
{
if(5 > 3)
{
main :
printf("jumped to label: \"main\"\r\n");
goto end;
}
goto main;
end :
return 0;
}
复合语句即由花括号及其包含的可选块序列 { block-item-list(opt) }
构成,符合语句必须包含花括号。
语义
一个 Compound statement
就是一个块(block
)。
概要
#include <stdio.h>
int fclose(FILE *stream);
描述
fclose
成功调用时,文件流会被flush并关闭,流中缓冲区的:
stream
都将解除关联,由 setbuf
和 setvbuf
所设置的缓冲区也会和 stream
解除关联(如果是自动分配的缓冲区,则会自动的调用析构函数)。返回值
0
,不成功将返回 EOF
。概要
#include <stdio.h>
int fflush(FILE *stream);
描述
stdout
)或更新流,其中最近的操作没有输入,则 fflush
函数将导致该流的任何未写数据被传送到主机环境,并写入文件;否则,行为是未定义的(例如操作只读流 fflush(stdin)
)。stream
的参数值为 NULL
,则 fflush
会刷新程序中定义的所有流。返回值
fflush
正常执行则会返回 0
,否则则会设置错误标识符并返回 EOF
。概要
#include <stdio.h>
FILE *fopen(const char * restrict filename,
const char * restrict mode);
描述
fopen
函数打开名称(路径)为 filename
的文件,返回一个与该文件绑定的 FILE*
指针。mode
参数应当是以下字符串之一,随后将以对应的模式打开文件。若非以下字符串则行为未定义。"r" | 以字符读取方式打开文件。 |
"w" | 以字符读取方式覆盖(即从0)或创建新文件。 |
"wx" | 以字符读取方式创建新文件并写入 - 若已存在该文件则操作失败,并返回NULL 通常用此避免覆盖掉原来的文件("x"表示强制使用新文件,仅可配合"w"和"w+"使用) |
"a" | 以字符读取方式追加方式打开文件: - 当文件存在时,会从文件尾开始追加(即 EOF 前)- 当文件不存在时,会创建该文件。 |
"r+" | 以字符读取方式读取或写入文件: - 此时指针置于文件头,且不会截断文件(与下方"w+"的第一个区别)。 - 如果文件不存在则操作失败(与下方"w+"的另一区别)。 |
"w+" | 以字符读取方式读取或写入文件: - 如果文件存在则会从文件头截断(即覆盖,文件长度清零)。 - 如果文件不存在则会创建新文件。 |
"w+x" | 以字符读取方式读取或写入文件: - 如果文件存在则会从文件头截断。 - 如果文件不存在则操作失败。 |
"a+" | 以字符读取方式写入文件,此时指针置于文件头。(不可读取) |
"rb" | 以二进制读取方式打开文件。 |
"wb" | 以二进制读取方式覆盖(即从0)或创建新文件 |
"wbx" | 以二进制读取方式创建新文件并写入(若已存在该文件则操作失败,并返回NULL,通常用此避免覆盖掉原来的文件) |
"ab" | 以二进制读取方式追加方式打开文件,当文件存在时,会从 EOF 前开始追加;当文件不存在时,会创建该文件。 |
"rb+" 或 "r+b" | 以二进制读取方式读取或写入文件,此时指针置于文件头。 |
"wb+" 或 "w+b" | |
"wb+x" 或 "w+bx" | |
"a+b" 或 "ab+" |
简单来说,mode主要为以下关键字的组合:
key | 作用和注意事项 |
---|---|
概要
#include <stdio.h>
int fprintf(FILE * restrict stream,
const char * restrict format, ...);
描述
fprintf
函数会将字节流按照 format
所指字符串中所指定的输出格式输出到 stream
所指向的流中。format
所需的参数少,则行为未定义。format
所需的参数多,则多余的表达式也会被计算,然后被忽略。fprintf
处理到 format
所指字符串的末尾字符 \0
后返回。format
应为字符序列,普通的多字节流(除了 %
符)会原封不动的输出,每一个占位符都由 %
符引入,概要:
#include <stdio.h>
int fgetc(FILE *stream);
描述:
stream
没有设置 EOF
指示符,并且存在下一个字符,fgetc
函数将获得该字符作为 unsigned char
转换为 int
(本质还是 char
,只是类型变了),并推进该流的相关文件位置指示符(如果已定义)。返回值:
stream
的 EOF
指示符且 stream
处于 EOF
位置, fgetc
函数将返回 EOF
。否则 fgetc
函数将返回由 stream
指向的输入流中的下一个字符。如果发生读取错误,则设置流的错误指示符,并返回 EOF
。其中,读到的 EOF
是由于文件到达末尾还是读取时发生错误可以使用 feof
或 ferror
函数区分。概要:
#include <stdio.h>
char *fgets(char * restrict s, int n,
FILE * restrict stream);
描述:
fgets
函数从流中读取至多 n - 1
个字符并存入 s
所指字符串中。在新行(可能是 \n
)字符之后或文件结束之后不会读取任何其他字符(其中新行字符会被保留)。并会在字符串末尾立即添加一个 \0
字符(哪怕结尾是换行符)。返回值:
fgets
函数将返回 s
。如果遇到 EOF
并且数组中没有读入任何字符,则数组的内容保持不变并返回空指针。如果在操作期间发生读取错误,则数组内容是不确定的,并返回空指针。概要:
#include <stdio.h>
int getc(FILE *stream);
描述:
getc
函数等价于 fgetc
,是 fgetc
的通过宏的实现。但是如果它被当做宏来调用时,它可能不止一次操作 stream
,因此参数绝不应该是带有副操作的表达式。副操作例如:
#include <stdio.h>
#define MACRO_SQRT(x) x*x
int func_sqrt(int x)
{
return x*x;
}
int main() {
int x = 10,y = 10;
int xx,yy;
xx = func_sqrt(++x);
printf("xx = %d, x = %d\n",xx,x); //xx = 121, x = 11
yy = MACRO_SQRT(++y);
printf("yy = %d, y = %d\n",yy,y); //yy = (++y)*(++y) => UB, y = 12; ++y就是副操作。
return 0;
}
而 getc
和 fgetc
之间的关系就类似于上方程序中 MACRO_SQRT
和 func_sqrt
的关系,当出现类似于如下操作就可能出现问题:
FILE streams[] = { ... };
int index = 0;
while(index < n)
getc(&streams[index++]); //不要这样做,因为执行一次getc可能会触发多次index++
返回值:
getc
函数返回由流指向的输入流中的下一个字符。如果流处于文件结束位置,则设置流的文件结束指示符,并且 getc
返回 EOF
。如果发生读取错误,则设置流的错误指示符,并且 getc
返回 EOF
。概要:
#include <stdio.h>
int getchar(void);
描述:
getchar()
函数等价于 getc(stdin)
。返回值:
getchar
函数返回 stdin
指向的输入流中的下一个字符。如果流处于文件结束位置,则设置流的文件结束指示符,并且 getchar
返回 EOF
。如果发生读取错误,则设置流的错误指示符,并且 getchar
返回 EOF
。概要
#include <stdio.h>
int putc(int c, FILE *stream);
描述
putc
函数等价于 fputc
,是 fputc
的通过宏的实现。因此参数绝不应该是带有副操作的表达式(副操作见getc章节)。返回值
putc
函数返回写入的字符。如果发生写入错误,则设置流的错误指示符,随后返回 EOF
。概要
#include <stdio.h>
int putchar(int c);
描述
putchar
函数等价于 putc
函数的第二个参数为 stdout
的情况。返回值
putc
函数的第二个参数为 stdout
的情况相同。概要
#include <stdio.h>
int puts(const char *s);
描述
puts
函数将 s
指向的字符串写入 stdout
指向的流,并将一个新行字符附加到输出,且不写入终止空字符 \0
。返回值
puts
函数返回 EOF
;否则返回非负值。笔记注:
txt
文件中也不用 \0
分割句子,一般 txt
文件是指可直接阅读的文件,而 \0
通常不可阅读。概要
#include <string.h>
void *memcpy(void * restrict s1,
const void * restrict s2,
size_t n);
描述
memcpy
函数将 s2
指向的内存的 n
个字符复制到 s1
指向的内存中。如果复制发生在重叠的对象之间,则行为是未定义的。返回值
memcpy
函数将返回 s1
指针。概要
#include <string.h>
void *memmove(void *s1, const void *s2, size_t n);
描述
memmove
函数将 s2
指向的内存的 n
个字符复制到 s1
指向的内存中。复制的过程就像是将 s2
指向的对象中的 n
个字符首先复制到一个 n
个字符的临时数组中,这个临时数组不会与 s1
和 s2
指向的内存重叠,然后将临时数组中的 n
个字符复制到 s1
指向的内存中。返回值
memmove
函数将返回 s1
指针。笔记注:
msvcrt
), memcpy
实质上就是 memmove
,虽然微软在MSDN中依旧是按照C语言标准进行描述。memcpy
的内存区域重叠会导致崩溃。memcpy
比 memmove
仅少了一个分支。memcpy
从而导致在一次 glibc
升级后触发了一些bug)概要
#include <string.h>
char *strcpy(char * restrict s1,
const char * restrict s2);
描述
strcpy
函数将 s2
指向的字符串(包括终止空字符)复制到 s1
指向的数组中。如果复制发生在重叠的对象之间,则行为是未定义的。
返回值
strcpy
函数将返回 s1
指针。
笔记注:
strcpy
函数操作的字符串要以 \0
结尾。概要
#include <string.h>
char *strncpy(char * restrict s1,
const char * restrict s2,
size_t n);
描述
strncpy
函数将不超过 n
个字符(空字符后面的字符不复制)从 s2
指向的数组复制到 s1
指向的数组。如果复制发生在重叠的对象之间,则行为是未定义的。s2
指向的数组是一个比 n
个字符短的字符串,则空字符将附加到 s1
指向的数组中的副本,直到所有字符中的 n
个字符都写入(即后尾全为 \0
)。返回值
strncpy
函数将返回 s1
指针。
笔记注:
概要
#include <string.h>
char *strcat(char * restrict s1,
const char * restrict s2);
描述
strcat
函数将 s2
指向的字符串的副本(包括终止空字符)附加到 s1
指向的字符串的末尾。 s2
的初始字符覆盖 s1
末尾的 null
字符。如果复制发生在重叠的对象之间,则行为是未定义的。返回值
strcat
函数将返回 s1
指针。
注:
概要
#include <string.h>
char *strncat(char * restrict s1,
const char * restrict s2,
size_t n);
描述
strncat
函数从 s2
指向的数组到 s1
指向的字符串的末尾追加不超过 n
个字符(空字符和跟随它的字符不被追加)。 s2
的初始字符覆盖 s1
末尾的 null
字符。结果总是附加一个终止空字符(因此,最终可以出现在 s1
指向的数组中的字符数的最大值是 strlen (s1) + n + 1
)。如果复制发生在重叠的对象之间,则行为是未定义的。返回值
strcat
函数将返回 s1
指针。
笔记注:
摘要
#include <string.h>
int memcmp(const void *s1, const void *s2, size_t n);
描述
memcmp
函数将 s1
指向的对象的前 n
个字符与 s2
指向的对象的前 n
个字符进行比较。返回值
memcmp
函数可能会返回大于、等于、或小于0的值,该函数将从 s1
和 s2
的第一个字节进行对比,直到遇见第一个不相等的字节,并返回 s1
的字节 减去 s2
的字节的值。若完全指向内容在前 n
字节完全相等,则返回0。标准注
holes
。n
的内存和 s1
和 s2
重合的内存可能会出现问题。摘要
#include <string.h>
int strcoll(const char *s1, const char *s2);
描述
strcoll
函数会将 s1
指向的字符串与 s2
指向的字符串先根据语言环境(LC_COLLATE
变量)进行重新解释,然后再进行比较。主要用于unicode环境。返回值
strcmp
。笔记注:
strcoll
和 strcmp
的区别可见讨论https://stackoverflow.com/questions/14087062/。strncmp
。摘要
#include <string.h>
void *memchr(const void *s, int c, size_t n);
描述
memchr
函数从 s
指向的内存的前 n
个字符(每个字符都被解释为 unsigned char
,实际上也就是前 n Byte)中定位第一个值 c
的内存地址( c
也会被解释为 unsigned char
)。返回值
c
,则将返回 c
第一次出现的地址,若没有找到指定字符,则返回 NULL
。笔记注:
memchr
会将 int c
解释为 unsigned char
,而 strchr
会将 int c
解释为 char
的讨论可见如下内容:摘要
#include <string.h>
char *strchr(const char *s, int c);
描述
strchr
函数将从 s
所指字符串中找出第一个字符 c
出现的位置。字符串 s
遇到 \0
自动截止,字符 c
会被转换为 char
。返回值
c
,则返回该字符第一次出现的地址,若没有找到指定字符,则返回 NULL
。摘要
#include <string.h>
size_t strcspn(const char *s1, const char *s2);
描述
strcspn
函数将计算从字符串 s1
的第一个字符开始,最大连续不包含字符串 s2
中任意字符的数量(此时 s2
被看做一个字符集合而非字符串)。s1
中查找字符集 s2
中的字符,返回值为第一次出现字符集中字符的字符号-1。可见下方笔记示例。返回值
strcspn
函数将返回从 s1
头部开始最大的不包含字符集合 s2
中任意字符的数量。笔记示例:
int func()
{
const char str1[] = "ABCDEF4960910";
const char str2[] = "013"; // 字符集合 { '0', '1', '3' }
int len = strcspn(str1, str2); // 从str1的开头开始统计有多少不在集合中的字符
printf("len = %d\r\n", len); // len = 9,"ABCDEF496"长度为9
// 另一种理解
if(len == strlen(str1))
{
printf("No characters found in the character set str2 in str1\r\n");
} else {
printf("The first character in the character set str2 in str1 is %c, locate at %d\r\n", str[len], len + 1);
}
return 0;
}
摘要
#include <string.h>
char *strstr(const char *s1, const char *s2);
描述
strstr
函数会从 s1
所指字符串中寻找 s2
字符串所指子串第一次出现的位置。上述字符串均以 \0
为截断。返回值
strstr
函数将返回 s1
中第一次出现子串 s2
位置的指针,如果 s1
中没有子串 s2
,则返回 NULL
。s2
为零长度字符串(即 { '\0' }
),则返回值为 s1
。摘要
#include <string.h>
char *strtok(char * restrict s1,
const char * restrict s2);
描述
strtok
函数通常会被一系列的调用:在首次调用时其会在 s1
字符串中搜索 s2
中的所有字符, s2
字符中的任意一个字符都是单独的分隔符。在后续搜索原 s1
字符串时,参数 s1
需要保持为 NULL
。s2
字符串中的任意一个字符,当有搜索结果时会返回原 s1
字符串内存区域内的指向下一字串的指针,当无更多子串时返回 NULL
。strtok
函数会保存指向后续字符的指针,从而实现后续 s1
参数为 NULL
的搜索。strtok_s
)。返回值
strtok
函数返回 s1
内存区域内的被 s2
字符串中所有字符集合作为分隔符的第一个字串的指针。如果 s1
不包含 s2
字符串中的所有字符集合,则返回 NULL
。#include <string.h>
static char str[] = "?a???b,,,#c";
char *t;
// 1. 在 "?a???b,,,#c" 中搜索字符集合 { '?' },
// 并将 '\0' 插入到第一个被分割字串的结尾。
// 此时 str="\0a\0??b,,,#c", t 所表示的字符串为 "a"
t = strtok(str, "?");
// 2. 在 "??b,,,#c" 中搜索字符集合 { ',' },
// 并将 '\0' 插入到第一个被分割字串的结尾。
// 此时 str="\0a\0??b\0,,#c", t 所表示的字符串为 "??b"
t = strtok(NULL, ",");
// 3. 在 ",,#c" 中搜索字符集合 { '#', ',' },
// 并将 '\0' 插入到第一个被分割字串的结尾。
// 此时 str="\0a\0??b\0\0\0\0c", t 所表示的字符串为 "c"
t = strtok(NULL, "#,");
// 4. 此时 t = NULL
t = strtok(NULL, "?");
笔记注:
\0
插入到后续字符串中第一个被分隔子串的结束位置用于分割字串,具体可见上方示例。摘要
#define __STDC_WANT_LIB_EXT1__ 1
#include <stdio.h>
char *gets_s(char *s, rsize_t n);
运行时限制
s
不应当为空指针描述
gets_s
函数从 stdin
指向的流中读入最多比 n
少一个的字符并且存储到 s
指向的数组中,在新行字符(该新行字符会被丢弃)或文件结束后不会读入任何其他字符。笔记注:
get_s
函数。摘要
#include <stdio.h>
char *gets(char *s);