本文档性质为个人学习笔记。
在阅读《C Primer Plus(第六版)中文版》时,记录下的需要备忘的知识点,主要关注在C99和C11特性的描述上。
第17章 高级数据表示 未做记录,因为暂时用不到那里。记录的要点主要是提醒备查所以不会过于详细。
但是编译器只识别前63个字符。对于外部标识符,只允许使用31个字符。(以前C90只允许6个字符,这是一个很大的进步,旧式编译器通常最多只允许使用8个字符。)实际上,你可以使用更长的字符,但是编译器会忽略超出的字符。
P385:在以前的标准中,编译器识别局部标识符前31个字符和外部标识符前6个字符。…外部变量名比局部变量名的规则严格,是因为外部变量名还要遵循局部环境规则,所受的限制更多。
C99新增关键字signed、const、enum、volatile
C11新增关键字_Alignas、_Alignof、_Atomic、_Bool、_Complex、_Generic、_Imaginary、_Noreturn、_Static_assert、_Thread_local
C99新增数据类型:long long、unsigned long long int、unsigned long long
例如:
精确宽度整数类型:int32_t等
最小宽度类型:int_least8_t等
最快最小宽度类型:int_fast8_t等
最大整数类型:intmax_t、uintmax_t等
同时提供相应的输入和输出,但是编译器对C99实现程度却各不相同。
ISO/IEC/IEEE 60559:2011 IEEE 754
IEEE 754(e)规定了计算机系统中浮点运算的格式和方法-具有单精度、双精度、扩展精度和扩展精度的标准函数和扩展函数,并推荐了数据交换的格式。 定义了异常条件,并指定了对这些条件的标准处理。 它提供了一种使用浮点数进行计算的方法,无论处理是在硬件、软件还是两者的组合中进行,都将产生相同的结果。 给定相同的输入数据,计算结果将是相同的,与实现无关。 数学处理中的错误和错误条件将以一致的方式报告,而不管实现如何。
复数的实部和虚部类型都基于实浮点类型来构成:
float _Complex
double _Complex
long double _Complex
float _Imaginary
double _Imaginary
long double _Imaginary
对于早期C,还要知道sizeof和strlen()返回的实际类型值(通常是unsigned或unsigned long)。
该头文件让bool成为_Bool的别名,还把true和false分别定义为1和0的常量。包含该头文件后,写出来的代码可以与C++兼容,因为C++把bool、true、false定义为关键字。
如果没有声明函数的类型,旧版本的编译器会假定函数的类型是int。
该特性可以初始化指定的数组元素。例如只初始化数组中的最后一个元素,对于传统的C初始化语法,必须初始化最后一个元素之前的所有元素,才能初始化它。而C99规定,可以在初始化列表中使用带方括号的下标指明待初始化的元素。
int arr[6] = {[5] = 212};
对于一般的初始化,在初始化一个元素后,未初始化的元素都会被设置为0。
指定初始化器的两个重要特性:
一、如果指定初始化器后面有更多的值,那么后面这些值将被用于初始化指定元素后面的元素。
二、如果再次初始化指定的元素,那么最后的初始化将会取代之前的初始化。
在C99标准之前,声明数组时只能在方括号中使用整型常量表达式。所谓整型常量表达式,是由整型常量构成的表达式。
sizeof表达式被视为整型常量,但是(与C++不同),const值不是。
C99/C11允许在声明变长数组时使用const变量。
C99创建了一种新型数组,称为变长数组(variable-length array)或简称VLA。
C11放弃了这一创新的举措,把VLA设定为可选,而不是语言必备的特性。
C99引入变长数组主要是为了让C成为更好的数值计算语言。
变长数组有一些限制。变长数组必须是自动存储类别,这意味着无论在函数中声明还是作为函数形参声明,都不能使用static或extern存储类别说明符。而且不能在声明中初始化它们。
变长数组中的“变”不是指可以修改已创建数组的大小。而是在创建数组时,可以使用变量指定数组的维度。
变长数组在声明时应保证其使用的变量在其之前被声明。
int sum2d(int rows, int cols, int ar[rows][sols]);//正确
int sum2d(int ar[rows][sols], int rows, int cols);//错误
C99/C11规定,可以省略原型中的形参名,但是在这种情况下,必须使用星号来代替省略的维度。
int sum2d(int, int, int ar[*][*]);
字面量是除符号常量以外的字面量。发布C99标准的委员会认为,如果有代表数组和结构内容的复合字面量,在编程时会更方便。
对于数组,复合字面量类似数组初始化列表,前面是用括号括起来的类型名。例如:
(int [2]){10, 20}
初始化有名数组时可以省略数组大小,复合字面量也可以省略大小。
因为复合字面量是匿名的,所以不能先创建再使用它,必须在创建的同时使用它。使用指针记录地址就是一种用法。
int * pt1;
pt1 = (int [2]){10, 20};
复合字面量是提供只临时需要的值的一种手段。复合字面量具有块作用域,这意味着一旦离开定义复合字面量的块,程序将无法保证该字面量是否存在。
而在以前,具有块作用域的变量都必须声明在块的开头。而现在可以这样写:
for(int i = 0; i<10; i++)
{
printf("A C99 Feature: i = %d", i);
}
为了适应这个新特性,C99把块的概念扩展到包括for循环、while循环、do while循环和if语句所控制的代码。即使这些代码没有用花括号括起来,也算是块的一部分。
函数作用域(function scope)仅用于goto语句的标签。
C90新增的两个属性:恒常性(constancy)和易变性(volatility)。这两个属性可以分别使用关键字const和volatile来声明,以这两个关键字创建的类型是限定类型(qualified type)。
C99标准新增了第三个限定符:restrict,用于提高编译器优化。restrict允许编译器优化某部分代码以更好地支持计算。**它只能用于指针,表明该指针是访问数据对象的唯一且初始的方式。
int * restrict p = (int *) malloc(10 * sizeof(int));
编译器不会检查用户是否遵循这一限制,但是无视它后果自负。
C11标准新增了第四个限定符:_Atomic。C11提供一个可选库,由stdatomic.h管理,以支持并发程序设计,而且_Atomic是可选支持项。
要通过各种宏函数来访问原子类型。当一个线程对一个原子类型的对象执行原子操作时,其他线程不能访问该对象。
_Atomic int hogs; //hogs是一个原子类型的变量
atomic_store(&hogs, 12);//stdatomic.h中的宏
C99为限定符增加了一个新属性:它们现在是幂等的(idempotent)。其意思是可以在一条声明中多次使用同一个限定符,多余的限定符将被忽略。
void ofmouth(int * const a1, int * restrict a2, int n);//以前的风格
void ofmouth(int a1[const], int a2[restrict], int n);//C99允许
根据新标准,在声明函数形参时,指针表示法和数组表示法都可以使用这两个限定符。
static的情况不同,因为新标准为static引入了一种与以前用法不相关的新用法。现在,static除了表明静态存储类别变量的作用域或链接外,新的用法告知编译器如何使用形式参数。例如:
double stick(double ar[static 20]);
static的这种用法表明,函数调用中的实际参数应该是一个指向数组首元素的指针,且该数组至少有20个元素。
第一,如果以传统的一种写模式打开一个现有文件,fopen()会把该文件的长度截为0,这样就丢失了文件的内容。但是使用带x字母的写模式,即使fopen()操作失败,原文件的内容也不会被删除。
第二,如果环境允许,x模式的独占特性使得其它程序或线程无法访问正在被打开的文件。
语法是把类型名放在圆括号中,后面紧跟一个用花括号括起来的初始化列表。例如:
(struct book) {"Name", "Author", 6.99}
有以下特性:
第一,该数组不会立即存在。
第二,使用这个伸缩型数组成员可以编写合适的代码,就好像它确实存在并具有所需数目的元素一样。
声明一个伸缩型数组成员有如下规则:
1.伸缩型数组成员必须是结构的最后一个成员
2.结构中必须至少有一个成员
3.伸缩数组的声明类似于普通数组,只是它的方括号中是空的
实际上,C99的意图不是使用含有伸缩型数组成员的结构声明变量,而是希望你声明一个指向该结构的指针,使用malloc分配足够的空间,以便存储结构中的常规成员和伸缩型数组成员需要使用的空间。
对于这类带有伸缩型数组成员的结构,有以下特殊的处理需求:
1.不能用结构赋值或拷贝
2.不要以按值方式把这种结构传递给结构
3.不要使用带伸缩型数组成员的结构作为数组成员或另一个结构的成员
struct pic_size
{
int h;
int w;
int other[20];
};
struct pic
{
int id;
struct pic_size size;//嵌套结构成员
};
struct pic p1 = {17, {1920, 1080, {10, 20, 30}}};
在C11中,可以使用嵌套的匿名成员结构定义:
struct pic
{
int id;
struct {int h; int w; int other[20];};
};
初始化的方式相同。在访问时简化了步骤,匿名结构中的成员可以直接由如pic.w的形式访问。
匿名联合和匿名结构的工作原理相同,即匿名联合是一个结构或联合的无名联合成员。
C11的对齐特性比用位填充字节更自然,它们还代表了C在处理硬件相关问题上的能力。这种上下文中,对齐指的是如何安排对象在内存中的位置。
_Alignof运算符给出一个类型的对齐要求,在关键字_Alignof后面的圆括号中写上类型名即可:
size_t d_align = _Alignof(float);
可以使用_Alignof说明符指定一个变量或类型的对齐值。但是,不应该要求该值小于基本对齐值。例如,如果float类型的对齐要求是4,不要请求其对齐值是1或2。
该说明符用作声明的一部分,说明符后面的圆括号内包含对齐值或类型:
_Alignof(double) char c1;
_Alignof(8) char c2;
unsigned char _Alignof(long double) c_arr[sizeof(long double)];
在程序中包含stdalign.h头文件后,就可以把aligns和alignof分别作为_Alignas和_Alignof的别名。这样做可以与C++关键字匹配。
C11在stdlib.h库还添加了一个新的内存分配函数,用于对齐动态分配的内存。该函数原型如下:
void *alignof_alloc(size_t alignment, size_t size);
第1个参数代表指定的对齐,第2个参数是所需的字节数,其值应是第1个参数的倍数。与其他内存分配函数一样,要使用free()函数释放之前分配的内存。
C99标准提供一个名为__func__的预定义标识符,它展开为一个代表函数名的字符串(该函数包含该标识符)。
那么__func__必须具有函数作用域,而从本质上看宏具有文件作用域。因此,__func__是C语言的预定义标识符,而不是预定义宏。
#line指令重置__LINE__和__FILE__宏报告的行号和文件名。
#error指令让预处理器发出一条错误消息,该消息包含指令中的文本。
泛型编程(generic programming)指那些没有特定类型,但是一旦指定一种类型,就可以转换成指定类型的代码。
C11新增了一种表达式,叫做泛型选择表达式(generic selection expresson),可根据表达式的类型(即表达式的类型是int、double还是其他类型)选择一个值。
泛型选择表达式不是预处理器指令,但是在一些泛型编程中它常用作#define宏定义的一部分。
编译器查看内联函数的定义(也是原型),可能会用函数体中的代码替换函数调用,也就是说,效果相当于在函数调用的位置输入函数体中的代码。编译器优化内联函数必须知道该函数定义的内容。这意味着内联函数定义与函数调用必须在同一个文件中。一般情况下,内联函数都具有内部链接。
标准规定具有内部链接的函数可以成为内联函数,还规定了内联函数的定义与调用该函数的代码必须在同一个文件中。因此,最简单的方法是使用函数说明符inline和存储类别说明符static。
#include
inline static void example()//内联函数定义/原型
{
printf("This message is from a inline function.");
}
int main()
{
...
example();//内联函数调用
...
}
一般不在头文件中放置可执行代码,内联函数是个特例。
与C++不同的是,C还允许混合使用内联函数定义和外部函数定义(具有外部链接的函数定义)。
C99新增inline关键字时,它是唯一的函数说明符(关键字extern和static是存储类别说明符,可应用于数据对象和函数)。C11新增了第二个函数说明符_Noreturn,表明调用完成后函数不返回主调函数。
exit()函数是_Noreturn函数的一个示例。
assert()表达式是在运行时进行检查。C11新增了一个特性:_Static_assert声明,可以在编译时检查assert()表达式。
因此,assert()可以导致正在运行的程序中止,而_Static_assert()可以导致程序无法通过编译。
_Static_assert()接受两个参数,第1个参数是整形常量表达式,第2个参数是一个字符串。如果第一个表达式求值为0(或false),编译器会显示该字符串,而且不编译该程序。
————2020-1-9@燕卫博————