深刻理解C语言标准

您所在的位置:网站首页 c语言最新标准c17支持VLA吗 深刻理解C语言标准

深刻理解C语言标准

2023-07-25 08:34| 来源: 网络整理| 查看: 265

前言

本文章内容有点长,建议耐心看完,会对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进行了补充和修正

K&R标准

比较老的标准了,现在估计找不到编译器支持这个标准了。它与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/GlobalVariable

extern 代表声明的意思,声明可以多次声明(说明的意思),但是定义(分配内存空间)只能定义一次

C 语言中用外部变量(全局变量),而在C++中要使用类的静态成员变量代替外部变量。(为了良好的编码习惯)

字符串常量可以连接起来 char* s = "hello," "world"; //等价于 char* s = "hello,world"; //不管引号中间有空格或者换行都可以连接起来 const 用来限定指定的内存空间不被修改

我们以编译器分配内存的角度去看

char* const fd = "eee"; // fd = "eee";// error,不能修改fd那块内存块 *fd = "ddd";// 正确,可以修改指针指向的那块内存空间

可以看出如果有指针类型,并且被const修饰,那么指针指向的内存空间不能被修改。

如果const修饰数组类型,那么数组整个内存块不能被修改。

如果const 修饰存放地址的内块,那么就不能修改存放地址的内存块

只要记住,只要有指针类型(除了被两个const修饰),那么编译器会分配两处内存,一个是变量的地址,另一个是变量存储的内容。

const程序地址:https://gitee.com/SFlow/blog-project/tree/master/constC

静态static

static 可以修饰外部变量和内部变量,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,比较灵活。数组不支持动态分配内存,所以比较死板。

#include void initData(int* p, int len) { int i = 0; static int startNumber = 0; for (; i int i = 0; for (; i int n = 10; int p0 = 7; int p1 = 6; int i = 1; int j = 0; int** p = malloc(n * sizeof(int*)); p[0] = malloc(p0 * sizeof(int)); //初始化p[0] initData(p[0],p0); for (; i printfInt(p[i],p1); printf("-------上面是%d号指针的数据--------\n",i); } return 0; } 命令行参数

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