深刻理解C语言标准 |
您所在的位置:网站首页 › c语言最新标准c17支持VLA吗 › 深刻理解C语言标准 |
前言
本文章内容有点长,建议耐心看完,会对C语言的语法有更深的印象。 C语言的起源C语言起源于B语言,B语言起源于ALGOL(简称:A语言)。可以画图表示: A语言和B语言已经落伍了,我们可以不用管它们。 C语言的特点C语言具有高效、强大而灵活,尤其在嵌入式开发。C语言比较靠近底层(除了汇编之外),学习它之后,学习任何语言都将事功半倍。 C语言的标准概述C语言刚开始诞生的时候没有统一的标准,后来出现了一个K&R标准,但是这个标准并不完善。只定义了C语言而没有定义C库。 C89标准又可以成为ANSI C 或者 ISO C,因为C89标准是在1989年由美国标准协会(ANSI C)发布的,在1990年由国际标准化组织(ISO)在ANSI C的基础之上发布C90标准。但是C90标准很少有人提,一般C89和C90统称为C89,因为改动量并不大。 C99标准在C89之上修订了一些细节以及增加更广的国际字符集支持 C11标准增加了一些新特性,主要多了多线程支持 C18标准没有引入新的语言特性,只对C11进行了补充和修正 比较老的标准了,现在估计找不到编译器支持这个标准了。它与C89标准最大的不同在于函数定义: // 参数名在圆括号指定 //参数类型在左花括号之前声明 //如果没有声明某个参数的类型,则默认为int类型 power(base,n) int base,n; { int i,p; p=1; for(i = 1;i printf("error\n"); int a; a = 10 } //如果我这样写,反而能编译通过 void fun() { printf("fun\n"); { int a; a = 10; } } 引入了++运算符和–运算符 main() { int i; i = 0; printf("%d\n",i++); printf("%d\n",i--); printf("%d\n",++i); printf("%d\n",i); } 外部变量和内部变量,外部变量即全局变量,内部变量即局部变量 //内部变量,只有在函数被调用的时候存在,函数执行完消失 int fun() { int i;//内部变量 i=0; } //外部变量,意思是在函数外部定义的变量,程序执行期间一直存在,可以被任何函数访问 //例如main.c文件中定义了一个外部变量 int g_y; int main() { g_y = 0; g_y++; printf("%d\n",g_y); } //假如我在a.c文件中要使用这个变量,那么我可以这样写: extern int g_y; void bFun() { g_y++; printf("%d\n",g_y); } //该程序示例地址:https://gitee.com/SFlow/blog-project/tree/master/GlobalVariableextern 代表声明的意思,声明可以多次声明(说明的意思),但是定义(分配内存空间)只能定义一次 C 语言中用外部变量(全局变量),而在C++中要使用类的静态成员变量代替外部变量。(为了良好的编码习惯) 字符串常量可以连接起来 char* s = "hello," "world"; //等价于 char* s = "hello,world"; //不管引号中间有空格或者换行都可以连接起来 const 用来限定指定的内存空间不被修改我们以编译器分配内存的角度去看 可以看出如果有指针类型,并且被const修饰,那么指针指向的内存空间不能被修改。 如果const修饰数组类型,那么数组整个内存块不能被修改。 如果const 修饰存放地址的内块,那么就不能修改存放地址的内存块 只要记住,只要有指针类型(除了被两个const修饰),那么编译器会分配两处内存,一个是变量的地址,另一个是变量存储的内容。 const程序地址:https://gitee.com/SFlow/blog-project/tree/master/constC 静态staticstatic 可以修饰外部变量和内部变量,static修饰的变量在程序运行期间一直存在。 static 不能被extern修饰,也不能出现只声明,只能被定义,这个规则限制了使用范围。 当static修饰外部变量时,意味着这个变量只能在该源文件中使用。 //a.c文件 static int gs_a = 10; void func1() { gs_a++; printf("%d\n",gs_a); } //b.c文件 void fun2() { //printf("%d\n",gs_a);//error,不能这样用 }当static修饰内部变量时,意味着该变量只能被该函数使用。 //main.c文件,这里不能对s_a进行声明,因为extern不能修饰带有static的变量或函数 void func() { static int s_a = 10; printf("%d\n",s_a); } void func1() { //printf("%d\n",s_a);//error,不能这样用 } int main() { func(); }static可以修饰函数,当修饰函数时,意味着这个函数只能在该源文件中使用。(PS:没有声明你怎么用?) 指针数组和数组指针指针数组,很明显突出的是指针,比如:int* p[3]代表三块连续的内存空间,每个内存空间都存放着一个地址。 数组指针,突出的是数组,比如 int (*p)[3] 代表一块内存空间,这块内存空间存放着一个地址,地址所在位置为三块连续的内存空间。 数组指针让我们想到使用函数指针,比如:void (*fun1)(int a,int b),fun1指向的函数所在的地址。 这块我倒是不常用,我用的话会用到二级指针即 int** p,比较灵活。数组不支持动态分配内存,所以比较死板。 main入口函数带有两个参数,第一个参数(argc)的值表示运行程序时参数的个数,第二个参数(argv)指向字符串数组的指针,每个字符串对应一个参数。 argv[0]的值是启动该程序的程序名,因此argc的值至少为1。 假设我们的程序名称叫echoT,那么我们在命令行输入:echoT hello word。那么我们在程序中就能接收到这三个值。 #include int main(int argc,char* argv[]) { printf("argc = %d", argc); printf("argv[0] = %s", argv[0]); printf("argv[1] = %s", argv[1]); printf("argv[2] = %s", argv[2]); return 0; }和函数声明有点类似,但是有不同。比如: int fun1(int a,int b);//函数声明 int (*fun1)(int a,int b);//定义函数指针 typedef //typedef 把一种类型定义了别名 typedef char* PCHAR;//将char* 起了一个别名叫PCHAR struct point { int x; int y; }; //将struct point 起了别名叫POINT typedef struct point { int x; int y; }POINT; //可以简化函数指针的使用 typedf int (*fun1)(int a,int b); fun1 m_fun;//定义了一个函数 //省略给函数赋值,一般是加载dll的时候动态定位函数地址 m_fun();//调用函数 联合体我学习的时候学过,但是工作的时候没有用过。 union tag{ int x; float m; char* b; }; //相当于储物盒,里面分成一个个小格子,每个格子大小一致。 //这样意味着,格子的大小要和最大的数据类型所占的空间一样,每个格子有可能塞不满 行宏(__LINE__)和文件宏(__FILE__) printf("line = %d,file = %s", __LINE__, __FILE__); C99标准 增加了inline关键字可以把函数指定为内联函数,这样可以解决一些频繁调用的函数大量消耗栈空间(栈内存)的问题。 增加了restrict关键字 //没有添加restrict之前,可以通过任意指针变量去改变指针所指向的值 int* p = (int*)malloc(10*sizeof(int)); int* p1 = p; p1[0] = 10; p1[1] = 20; //添加之后只能通过这个变量去改变指针所指向的值 int* restrict p = (int*)malloc(10*sizeof(int)); p[0] = 30; 增加了_Complex关键字,来表示复数这个没有用过,也许有些行业有用吧? float _Complex a; /* 该变量包含两个float值,实部和虚部 */ double _Complex b; /* 与上述类似 */ long double _Complex c; //使用头文件可以更方便的完成复数问题(虚数单位i写作大写的I): double complex a = 1.3; /* 1.3 */ double complex b = 2.3 + 4 * I; /* 2.3+4i */ double complex c = 5.3 * I; /* 5.3i */ 增加了_Imaginary关键字,来表示虚数同上 float _Imaginary a; /* 虚数类型的实部为0 */ double _Imaginary b; /* 与上述类似 */ long double _Imaginary c; //使用头文件可以更方便的完成复数问题(虚数单位i写作大写的I): double imaginary c = 5.3 * I; /* 5.3i */ 增加了一种新的数据类型:_Bool类型 //_Bool类型长度为1,只能取值范围为0或1 _Bool a = 1; _Bool b = 2; /* 使用非零值,b的值为1 */ _Bool c = 0; _Bool d = -1; /* 使用非零值,d的值为1 */支持long long 数据类型 变量声明不必放在语句块开头 int fun() { printf("fun\n"); int a; printf("a\n"); a = 10; } for(int i=0;i int a; float b; char c; }; alignof(Foo) //值为4,对齐长度 sizeof(Foo) //结构体的总大小:12 //void *aligned_alloc( size_t alignment, size_t size ); //分配 size 字节未初始化的存储空间,按照 alignment 指定对齐。 size 参数必须是 alignment 的整数倍。 //aligned_alloc 是线程安全的 int *p2 = aligned_alloc(1024, 10*sizeof *p2); printf("1024-byte aligned addr: %p\n", (void*)p2); free(p2); _Noreturn_Noreturn是个函数修饰符,位置在函数返回类型的前面,声明函数无返回值 #include #include #include // 在 i 0 时退出 noreturn void stop_now(int i) // 或 _Noreturn void stop_now(int i) { if (i > 0) exit(i); } int main(void) { puts("Preparing to stop..."); stop_now(2); puts("This code is never executed."); } _Generic_Generic支持轻量级范型编程,可以把一组具有不同类型而却有相同功能的函数抽象为一个接口 _Generic( ‘a’, char: 1, int: 2, long: 3, default: 0) //与switch类似 _Static_assert()静态断言,在编译时刻进行。 #include int main(void) { // 测试数学是否正常工作 static_assert(2 + 2 == 4, "Whoa dude!"); // 或 _Static_assert(... // 这会在编译时产生错误。 static_assert(sizeof(int) int area_code; long phone_number; }; }; int main(void) { struct person jim = {"jim", 'F', 28, 65, {21, 58545566}}; printf("%d\n", jim.area_code); } 多线程头文件定义了创建和管理线程的函数,新的存储类修饰符_Thread_local限定了变量不能在多线程之间共享。 VS2019移除了对threads.h的支持,所以windows下不能用这个。 引用于使用C11新增的多线程支持库-threads.h进行多线程编程 上面提到VS主要目标是支持C++,对C的支持是次要的。 quick_exit()又一种终止程序的方式,当exit()失败时用以终止程序。 time.h新增timespec结构体,时间单位为纳秒,原来的timeval结构体时间单位为毫秒。struct timespec 定义: typedef long time_t; #ifndef _TIMESPEC #define _TIMESPEC struct timespec { time_t tv_sec; // seconds long tv_nsec; // and nanoseconds }; #endif C18标准C18 没有引入新的语言特性,只对 C11 进行了补充和修正。 结尾至此,对C语言标准的简单总结就完了。简单来看,其实从C89开始,其他并没有太大变化。下一篇将对C++标准做个简单总结。 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |