@C Primer Plus 复习总结上(前十二章)
#代码测试
奇怪的代码
#include<stdio.h>
#include<string.h>
#include<ctype.h>
#include<stdlib.h>
///
/* //该行前添加注释符号“//”解除对body内容的注释
//
{
body
}
//*/
/// 1
/* //该行前添加注释符号“//”解除对body内容的注释
//输入输出问题
void display(int ch, int rows, int cols) {
int row, col;
for (row = 0; row < rows; row++) {
for (col = 0; col < cols; col++) {
putchar(ch);
}
putchar('\n');
}
}
int main(void ) {
int ch;
int rows, cols;
while ((ch=getchar()) != '\n') {
if (scanf_s("%d %d", &rows, &cols) != 2)
break;
display(ch, rows, cols);
while (getchar() != '\n')
continue;
}
}
/*
//scanf()函数和缓冲区问题
int i;
char j;
bool input;
while((i = getchar())!-EOF)
putchar(i);
while(scanf_s("%c", &j) == 1){
printf("%c",j);
}
return 0;
//*/
/*
//++和=的结合问题
//前置和后置运算符的执行过程
int i = 1;
if (i-- == 0) {
printf("%d--",i);
}
i = 1;
if (--i == 0) {
printf("--%d", i);
}
int i = 1;
int j;
i = i++;
j = i++;
printf("%d,%d,%d", i, j, i++);
*/
/*
//循环语句,break,contiue,goto的运用
int i=0;
for (printf("来吧,baby \n"); i < 5; i++)
printf("%d \n", i);
//*/
/// 3
//
/* //该行前添加注释符号“//”解除对body内容的注释
//指针综合
int main() {
int a[] = { 1,2,3 };
int* q = a;
printf("一维数组:a[0] = 2%d,*(q+0)=2%d,&a=%p,&q=%p\n\n", a[0], *(q+0), a, q);
int b[][3] = { {1,2,3} ,{4,5,6} };
int (*p)[3] = b;
printf("二维数组:a[0][1] = 2%d,*(*p+1)=2%d,*(*(p+0)+1)=2%d",b[0][1],*(*p+1),*(*(p+0)+1));
printf("\n\n");
int c[] = { 1,2,3 }; //c99复合字面量 (int [2]){1,2}
int c1[] = { 4,5,6 };
int c2[] = { 7,8,9 };
int d = 0;
int d1 = 1;
int d2 = 2;
int e[][3] = { {1,2,3} ,{4,5,6} };
int* m[3];
m[0] = &d;
m[1] = c1;
m[2] = e[1];
printf("指针数组:\n存储变量:d=2%d,*m[0]=2%d\n", d, *m[0]);
printf("存储一维数组:c[1]=2%d,*(m[1]+1)=2%d\n", c[1], *(m[1] + 1));
printf("存储二维数组:e[1][1]=2%d,*(m[2]+1)=2%d\n", e[1][1], *(m[2] + 1));
return 0;
}
//*/
/// 4
/* //该行前添加注释符号“//”解除对body内容的注释
//字符串输入输出
#define F "lose it foreve"
int main() {
char a[] = "You are stupid!"; //static区字符串拷贝给数组
const char* p = "Your love is gone"; //地址拷贝给指针。若不用const,通过指针修改字符串字面量的行为是未知的
puts(F); //传送地址,检验空字符停止。在末尾添加换行符
puts(a);
puts(p);
printf("%c\n", *(++p));
printf(" &a=%p\n &p=%p\n &F=%p\n\n",a,p,F);
const char* q[] = {
"everybody ",
"hurts ",
"somedays "
};
printf(" %p\n %p\n %p\n\n", (*(q))++, (*(q)),(*(q+1)));
printf(" %s\n %c\n\n", (*(q+1))++,*(*(q+1))); //指针和运算符和字符串的综合
//gets(puts)丢弃(添加)末尾的换行符,fgets(fputs)保存(不添加)换行符。
//gets()函数存在的安全问题。c11使用gets_s代替。(读取\n会丢弃)
//overflow问题
char words[12];
puts("input the sentence:");
while (fgets(words, 12, stdin) != NULL && words[0] != '\n') {
int i = 0;
while (words[i] != '\n' && words[i] != '\0') {
words[i++]=toupper(words[i]); //ctype.h含更多字符操作函数
}
if (words[i] == '\n')
words[i] = '\0';
else
while (getchar() != '\n')
continue;
fputs(words,stdout); //puts指针文件的版本,不会在末尾添加换行符
}
fputs("done", stdout);
return 0;
}
//*/
/// 4-1
/* //该行前添加注释符号“//”解除对body内容的注释
//字符串函数(基础,更多参考c99,c11新增的ANSI C库 string.h)
int main() {
char a[50] = "it is impossible that ";
const char * p = "your hreat is borken";
const char * q = "by you";
printf("%d\n", BUFSIZ); //缓冲区大小,一般位512字节
int n = strlen(a) - strlen(p);
if (n < (int)(50 - strlen(a))) {
strcat_s(a, p); // strncat(,,n);
printf("the length of the strcat:%d\n", strlen(a));
}
else
printf("the error length:%d\n", 50 - strlen(a));
printf("%d\n", strcmp("a", "A")); //strncmp(,,n);
char b[20], c[100];
strcpy_s(b, q); //strncpy(,,n)
puts(b);
sprintf_s(c,"%s %s",a,q); //stdio.h输出到字符串
puts(c);
char d[] = "1234";
printf("%d", atoi(d)); //stdlib.h字符串转换位基本数据类型(atof()、atol()、etc)
//strtol还可以将字符串转换位最多三十六进制数
return 0;
}
//*/
/// 5
/* //该行前添加注释符号“//”解除对body内容的注释
// 存储类别,链接和内存管理
int giants = 5; //文件作用域,外部链接,静态存储期
static int dot = 10; //文件作用域,内部链接,静态存储期
void Max(int a, int b); //函数原型作用域
volatile int a; //提醒编译器变量值随时改变,变量从变量地址中读取数据,而不能代码优化后的寄存器读取。
int main() {
//for,do while, while 被视为一个块
int j = 0;
for (int i = 0; i < 1; i++) {
int j = 3; //块作用域,无连接,自动存储器
static int k = 3; //块作用域,无连接,静态存储器
printf("j1 = %d\n", j);
}
printf("j2 = %d\n", j);
return 0;
}
//*/
#书上概念
个人觉得应该注意的偏僻角落
◆ 1.3 C语言的应用范围
面向对象编程是一门哲学,它通过对语言建模来适应问题,而不是对问题建模以适应语言)
◆ 1.8 编程机制
链接器的作用是,把你编写的目标代码、系统的标准启动代码和库代码这3部分合并成一个文件,即可执行文件。
◆ 2.2 示例解释
include文件提供了一种方便的途径共享许多程序共有的信息。
%提醒程序,要在该处打印一个变量,d表明把变量作为十进制整数打印。printf()函数名中的f提醒用户,这是一种格式化打印函数。printf()函数有多种打印变量的格式,包括小数和十六进制整数。
◆ 2.8 关键字和保留标识符
保留标识符包括那些以下划线字符开头的标识符和标准库函数名,如printf()。
◆ 3.2 变量与常量数据
有些数据类型在程序使用之前已经预先设定好了,在整个程序的运行过程中没有变化,这些称为常量(constant)。其他数据类型在程序运行期间可能会改变或被赋值,这些称为变量(variable)。在
◆ 3.4 C语言基本数据类型
char类型用于存储字符(如,字母或标点符号),但是从技术层面看,char是整数类型。因为char类型实际上存储的是整数而不是字符。
标准中的活跃位置(active position)指的是显示设备(屏幕、电传打字机、打印机等)中下一个字符将出现的位置
C99新增了两个头文件stdint.h和inttypes.h,以确保C语言的类型在各系统中的功能相同。
C99和C11定义了一组可使计算达到最快的类型集合。这组类型集合被称为最快最小宽度类型(fastst minimum width type)。例如,int_fast8_t被定义为系统中对8位有符号值而言运算最快的整数类型的别名。
C标准规定,float类型必须至少能表示6位有效数字,且取值范围至少是10-37~10+37。
printf()函数使用%f转换说明打印十进制记数法的float和double类型浮点数,用%e打印指数记数法的浮点数
◆ 3.7 转义序列示例
最初,printf()语句把输出发送到一个叫作缓冲区(buffer)的中间存储区域,然后缓冲区中的内容再不断被发送到屏幕上。C标准明确规定了何时把缓冲区中的内容发送到屏幕:当缓冲区满、遇到换行字符或需要输入的时候(从缓冲区把数据发送到屏幕或文件被称为刷新缓冲区)。
◆ 4.2 字符串简介
数组是同类型数据元素的有序序列。
string.h头文件包含多个与字符串相关的函数原型,包括strlen()。
C99和C11标准专门为sizeof运算符的返回类型添加了%zd转换说明,这对于strlen()同样适用。对于早期的C,还要知道sizeof和strlen()返回的实际类型(通常是unsigned或unsigned long)。
◆ 4.3 常量和C预处理器
这一过程被称为编译时替换(compile-time substitution)。在运行程序时,程序中所有的替换均已完成(见图4.5)。通常,这样定义的常量也称为明示常量(manifest constant)。
C90标准新增了const关键字,用于限定一个变量为只读。
C头文件limits.h和float.h分别提供了与整数类型和浮点类型大小限制相关的详细信息。
◆ 4.4 printf()和scanf()
最初,C把输入/输出的实现留给了编译器的作者,这样可以针对特殊的机器更好地匹配输入/输出。后来,考虑到兼容性的问题,各编译器都提供不同版本的printf()和scanf()。尽管如此,各版本之间偶尔有一些差异。C90和C99标准规定了这些函数的标准版本,本书亦遵循这一标准。
表4.4 printf()的修饰符
[插图]
[插图]
注意 类型可移植性
sizeof运算符以字节为单位返回类型或值的大小。这应该是某种形式的整数,但是标准只规定了该值是无符号整数。在不同的实现中,它可以是unsigned int、unsigned long甚至是unsigned long long。因此,如果要用printf()函数显示sizeof表达式,根据不同系统,可能使用%u、%lu或%llu。这意味着要查找你当前系统的用法,如果把程序移植到不同的系统还要进行修改。鉴于此,C提供了可移植性更好的类型。首先,stddef.h头文件(在包含stdio.h头文件时已包含其中)把size_t定义成系统使用sizeof返回的类型,这被称为底层类型(underlying type)。
转换(conversion)可能会误导读者认为原始值被替换成转换后的值。实际上,转换说明是翻译说明,%d的意思是“把给定的值翻译成十进制整数文本并打印出来”。
printf()函数也有一个返回值,它返回打印字符的个数。如果有输出错误,printf()则返回一个负值(printf()的旧版本会返回不同的值)。
scanf()和printf()类似,也使用格式字符串和参数列表。scanf()中的格式字符串表明字符输入流的目标数据类型。两个函数主要的区别在参数列表中。printf()函数使用变量、常量和表达式,而scanf()函数使用指向变量的指针。
scanf("%s", pet); // 字符数组不使用&
printf("%d $%.2f %s\n", age, assets, pet);
return 0;
}
下面是该程序与用户交互的示例:
Enter your age, assets, and favorite pet.3892360.88 llama
38 $92360.88 llama
scanf()函数使用空白(换行符、制表符和空格)把输入分成多个字段。在依次把转换说明和字段匹配时跳过空白。注意,上面示例的输入项(粗体部分是用户的输入)分成了两行。只要在每个输入项之间输入至少一个换行符、空格或制表符即可,可以在一行或多行输入:
Enter your age, assets, and favorite pet. 42
2121.45
guppy
42 $2121.45 guppy
唯一例外的是%c转换说明。根据%c,scanf()会读取每个字符,包括空白。我们稍后详述这部分。
scanf()函数所用的转换说明与printf()函数几乎相同。主要的区别是,对于float类型和double类型,printf()都使用%f、%e、%E、%g和%G转换说明。而scanf()只把它们用于float类型,对于double类型要使用l修饰符。表4.6列出了C99标准中常用的转换说明。
scanf()函数允许把普通字符放在格式字符串中。除空格字符外的普通字符必须与输入字符串严格匹配。
scanf("%c", &ch)从输入中的第1个字符开始读取,而scanf(" %c", &ch)则从第1个非空白字符开始读取
printf()和scanf()都可以使用*修饰符来修改转换说明的含义。
scanf()中的用法与此不同。把放在%和转换字符之间时,会使得scanf()跳过相应的输入项
◆ 5.2 基本运算符
用于存储值的数据存储区域统称为数据对象(data object)
左值(lvalue)是C语言的术语,用于标识特定数据对象的名称或表达式。因此,对象指的是实际的数据存储,而左值是用于标识或定位存储位置的标签
在C语言中,整数除法结果的小数部分被丢弃,这一过程被称为截断(truncation)。
◆ 5.4 表达式和语句
序列点(sequence point)是程序执行的点,在该点上,所有的副作用都在进入下一步之前发生。
◆ 5.5 类型转换
当类型转换出现在表达式时,无论是unsigned还是signed的char和short都会被自动转换成int,如有必要会被转换成unsigned int(如果short与int的大小相同,unsigned short就比int大。这种情况下,unsigned short会被转换成unsigned int)。在K&R那时的C中,float会被自动转换成double(目前的C不是这样)。由于都是从较小类型转换为较大类型,所以这些转换被称为升级(promotion)。
在赋值表达式语句中,计算的最终结果会被转换成被赋值变量的类型。这个过程可能导致类型升级或降级(demotion)。所谓降级,是指把一种类型转换成更低级别的类型。
◆ 7.2 if else语句
getchar()函数不带任何参数,它从输入队列中返回下一个字符。
C有一系列专门处理字符的函数,ctype.h头文件包含了这些函数的原型。
◆ 7.3 逻辑运算符
C99标准新增了可代替逻辑运算符的拼写,它们被定义在iso646.h头文件中。
◆ 7.8 goto语句
goto语句使程序控制跳转至相应标签语句。冒号用于分隔标签和标签语句。标签名遵循变量命名规则。标签语句可以出现在goto的前面或后面。
形式:
◆ 8.2 缓冲区
缓冲分为两类:完全缓冲I/O和行缓冲I/O。完全缓冲输入指的是当缓冲区被填满时才刷新缓冲区(内容被发送至目的地),通常出现在文件输入中。缓冲区的大小取决于系统,常见的大小是512字节和4096字节。行缓冲I/O指的是在出现换行符时刷新缓冲区。键盘输入通常是行缓冲输入,所以在按下Enter键后才刷新缓冲区。
例如,许多IBM PC兼容机的编译器都为支持无缓冲输入提供一系列特殊的函数,其原型都在conio.h头文件中。
◆ 8.3 结束键盘输入
流(stream)是一个实际输入或输出映射的理想化数据流。
EOF定义在stdio.h文件中:
#define EOF (-1)
为什么是-1?因为getchar()函数的返回值通常都介于0~127,这些值对应标准字符集
◆ 8.4 重定向和文件
重定向输入让程序使用文件而不是键盘来输入,重定向输出让程序输出至文件而不是屏幕。
◆ 9.1 复习函数
因为被调函数使用的值是从主调函数中拷贝而来,所以无论被调函数对拷贝数据进行什么操作,都不会影响主调函数中的原始数据。
调用函数时,创建了声明为形式参数的变量并初始化为实际参数的求值结果。
◆ 9.3 递归
C允许函数调用它自己,这种调用过程称为递归(recursion)。
递归既有优点也有缺点。优点是递归为某些编程问题提供了最简单的解决方案。缺点是一些递归算法会快速消耗计算机的内存资源。另外,递归不方便阅读和维护。
◆ 9.7 指针简介
从根本上看,指针(pointer)是一个值为内存地址的变量(或数据对象)。
间接运算符*(indirection operator)找出存储在bah中的值,该运算符有时也称为解引用运算符(dereferencing operator)。
◆ 10.1 数组
C99增加了一个新特性:指定初始化器(designated initializer)。利用该特性可以初始化指定的数组元素。例如,只初始化数组中的最后一个元素。
C不允许把数组作为一个单元赋给另一个数组,除初始化以外也不允许使用花括号列表的形式赋值。
◆ 10.5 指针操作
创建一个指针时,系统只分配了存储指针本身的内存,并未分配存储数据的内存。
◆ 10.6 保护数组中的数据
指向const的指针不能用于改变值。
C标准规定,使用非const标识符(如,mult_arry()的形参ar)修改const数据(如,locked)导致的结果是未定义的。
◆ 10.8 变长数组(VLA)
变长数组必须是自动存储类别,这意味着无论在函数中声明还是作为函数形参声明,都不能使用static或extern存储类别说明符(第12章介绍)。
变长数组中的“变”不是指可以修改已创建数组的大小。一旦创建了变长数组,它的大小则保持不变。这里的“变”指的是:在创建数组时,可以使用变量指定数组的维度。
◆ 10.9 复合字面量
C99新增了复合字面量(compound literal)。字面量是除符号常量外的常量。例如,5是int类型字面量,81.3是double类型的字面量,'Y’是char类型的字面量,"elephant"是字符串字面量。发布C99标准的委员会认为,如果有代表数组和结构内容的复合字面量,在编程时会更方便。
◆ 11.1 表示字符串和字符串I/O
字符串是以空字符(\0)结尾的char类型数组。
puts()函数也属于stdio.h系列的输入/输出函数。但是,与printf()不同的是,puts()函数只显示字符串,而且自动在显示的字符串末尾加上换行符
从ANSI C标准起,如果字符串字面量之间没有间隔,或者用空白字符分隔,C会将其视为串联起来的字符串字面量。
字符串常量属于静态存储类别(static storage class),
初始化数组把静态存储区的字符串拷贝到数组中,而初始化指针只把字符串的地址拷贝给指针
数组的元素是变量(除非数组被声明为const),但是数组名不是变量。
◆ 11.2 字符串输入
空字符(或’\0’)是用于标记C字符串末尾的字符,其对应字符编码是0。由于其他字符的编码不可能是0,所以不可能是字符串的一部分。
空指针(或NULL)有一个值,该值不会与任何数据的有效地址对应。通常,函数使用它返回一个有效地址表示某些特殊情况发生,例如遇到文件结尾或未能按预期执行。
如果输入行太长会怎样?使用gets()不安全,它会擦写现有数据,存在安全隐患。gets_s()函数很安全,但是,如果并不希望程序中止或退出,就要知道如何编写特殊的“处理函数”。另外,如果打算让程序继续运行,gets_s()会丢弃该输入行的其余字符,无论你是否需要。由此可见,当输入太长,超过数组可容纳的字符数时,fgets()函数最容易使用,而且可以选择不同的处理方式。
◆ 11.11 本章小结
C库中有多个字符串处理函数。在ANSI C中,这些函数都声明在string.h文件中。C库中还有许多字符处理函数,声明在ctype.h文件中。
给main()函数提供两个合适的形式参数,可以让程序访问命令行参数。第1个参数通常是int类型的argc,其值是命令行的单词数量。第2个参数通常是一个指向数组的指针argv,数组内含指向char的指针。
◆ 12.1 存储类别
硬件方面来看,被存储的每个值都占用一定的物理内存,C语言把这样的一块内存称为对象(object)。对象可以存储一个或多个值。一个对象可能并未存储实际的值,但是它在存储适当的值时一定具有相应的大小(面向对象编程中的对象指的是类对象,其定义包括数据和允许对数据进行的操作,C不是面向对象编程语言)。
一般而言,那些指定对象的表达式被称为左值
如果可以使用左值改变对象中的值,该左值就是一个可修改的左值(modifiable lvalue)。
可以用存储期(storage duration)描述对象,所谓存储期是指对象在内存中保留了多长时间。标识符用于访问对象,可以用作用域(scope)和链接(linkage)描述标识符,标识符的作用域和链接表明了程序的哪些部分可以使用它。不同的存储类别具有不同的存储期、作用域和链接。
作用域描述程序中可访问标识符的区域。一个C变量的作用域可以是块作用域、函数作用域、函数原型作用域或文件作用域。
块是用一对花括号括起来的代码区域。
C99把块的概念扩展到包括for循环、while循环、do while循环和if语句所控制的代码,即使这些代码没有用花括号括起来,也算是块的一部分。
函数作用域(function scope)仅用于goto语句的标签。这意味着即使一个标签首次出现在函数的内层块中,它的作用域也延伸至整个函数。如果在两个块中使用相同的标签会很混乱,标签的函数作用域防止了这样的事情发生。
函数原型作用域的范围是从形参定义处到原型声明结束。
变量的定义在函数的外面,具有文件作用域(file scope)。具有文件作用域的变量,从它的定义处到该定义所在文件的末尾均可见
文件作用域变量也称为全局变量(global variable)。
编译器把源代码文件和所有的头文件都看成是一个包含信息的单独文件。这个文件被称为翻译单元(translation unit)。描述一个具有文件作用域的变量时,它的实际可见范围是整个翻译单元。如果程序由多个源代码文件组成,那么该程序也将由多个翻译单元组成。每个翻译单元均对应一个源代码文件和它所包含的文件。
C变量有3种链接属性:外部链接、内部链接或无链接。
具有块作用域、函数作用域或函数原型作用域的变量都是无链接变量。这意味着这些变量属于定义它们的块、函数或原型私有。具有文件作用域的变量可以是外部链接或内部链接。外部链接变量可以在多文件程序中使用,内部链接变量只能在一个翻译单元中使用。
一些程序员把“内部链接的文件作用域”简称为“文件作用域”,把“外部链接的文件作用域”简称为“全局作用域”或“程序作用域”。
存储期描述了通过这些标识符访问的对象的生存期。C对象有4种存储期:静态存储期、线程存储期、自动存储期、动态分配存储期
如果对象具有静态存储期,那么它在程序的执行期间一直存在。文件作用域变量具有静态存储期。注意,对于文件作用域变量,关键字static表明了其链接属性,而非存储期。以static声明的文件作用域变量具有内部链接。但是无论是内部链接还是外部链接,所有的文件作用域变量都具有静态存储期。
线程存储期用于并发程序设计,程序执行可被分为多个线程。具有线程存储期的对象,从被声明时到线程结束一直存在。
块作用域的变量通常都具有自动存储期。当程序进入定义这些变量的块时,为这些变量分配内存;当退出这个块时,释放刚才为变量分配的内存。这种做法相当于把自动变量占用的内存视为一个可重复使用的工作区或暂存区。
关键字auto是存储类别说明符(storage-class specifier)。auto关键字在C++中的用法完全不同,如果编写C/C++兼容的程序,最好不要使用auto作为存储类别说明符。
自动变量不会初始化,除非显式初始化它。
外部变量和自动变量类似,也可以被显式初始化。与自动变量不同的是,如果未初始化外部变量,它们会被自动初始化为0。
C99和C11标准都要求编译器识别局部标识符的前63个字符和外部标识符的前31个字符。
关键字extern表明该声明不是定义,因为它指示编译器去别处查询其定义。
auto说明符表明变量是自动存储期,只能用于块作用域的变量声明中。由于在块中声明的变量本身就具有自动存储期,所以使用auto主要是为了明确表达要使用与外部变量同名的局部变量的意图。
保护性程序设计的黄金法则是:“按需知道”原则。尽量在函数内部解决该函数的任务,只共享那些需要共享的变量。除自动存储类别外,其他存储类别也很有用。不过,在使用某类别之前先要考虑一下是否有必要这样做
◆ 12.4 分配内存:malloc()和free()
所有程序都必须预留足够的内存来存储程序使用的数据。这些内存中有些是自动分配的。
可以在程序运行时分配更多的内存。主要的工具是malloc()函数,该函数接受一个参数:所需的内存字节数。malloc()函数会找到合适的空闲内存块,这样的内存是匿名的。也就是说,malloc()分配内存,但是不会为其赋名。然而,它确实返回动态分配内存块的首字节地址。因此,可以把该地址赋给一个指针变量,并使用指针访问这块内存。
动态分配内存的存储期从调用malloc()分配内存到调用free()释放内存为止。设想malloc()和free()管理着一个内存池。每次调用malloc()分配内存给程序使用,每次调用free()把内存归还内存池中,这样便可重复使用这些内存。
◆ 12.5 ANSI C类型限定符
这种方案必须在头文件中用关键字static声明全局const变量。
智能的(进行优化的)编译器会注意到以上代码使用了两次x,但并未改变它的值。于是编译器把x的值临时存储在寄存器中,然后在val2需要使用x时,才从寄存器中(而不是从原始内存位置上)读取x的值,以节约时间。这个过程被称为高速缓存(caching)。通常,高速缓存是个不错的优化方案,但是如果一些其他代理在以上两条语句之间改变了x的值,就不能这样优化了。如果没有volatile关键字,编译器就不知道这种事情是否会发生。因此,为安全起见,编译器不会进行高速缓存。这是在ANSI之前的情况。现在,如果声明中没有volatile关键字,编译器会假定变量的值在使用过程中不变,然后再尝试优化代码。
restrict关键字允许编译器优化某部分代码以更好地支持计算。它只能用于指针,表明该指针是访问数据对象的唯一且初始的方式。
restrict关键字有两个读者。一个是编译器,该关键字告知编译器可以自由假定一些优化方案。另一个读者是用户,该关键字告知用户要使用满足restrict要求的参数。
C11通过包含可选的头文件stdatomic.h和threads.h,提供了一些可选的(不是必须实现的)管理方法。值得注意的是,要通过各种宏函数来访问原子类型。当一个线程对一个原子类型的对象执行原子操作时,其他线程不能访问该对象。
◆ 12.7 本章小结
类型限定符const、volatile、restrict和_Atomic。const限定符限定数据在程序运行时不能改变。对指针使用const时,可限定指针本身不能改变或指针指向的数据不能改变,这取决于const在指针声明中的位置。volatile限定符表明,限定的数据除了被当前程序修改外还可以被其他进程修改。该限定符的目的是警告编译器不要进行假定的优化。restrict限定符也是为了方便编译器设置优化方案。restrict限定的指针是访问它所指向数据的唯一途径。