关键字

C语言的关键字一共有32(C89) + 5(C99),至于后面的先不介绍啦

1. 数据类型关键字

数据类型关键字

  1. 数据类型的大小
  • char : 1个字节 [-128,127] [0,255]
  • int : 一般是4个字节(对于16位操作系统则是2个字节)
  • short : 不少于2个字节
  • long : 至少四个字节
  • float : 一般4个字节
  • double : 一般8个字节
  • 指针的大小: 指针就是地址 32位操作系统就是 4个字节 64位操作系统就是 8个字节
  1. struct的大小
    想要知道 struct的大小 得先了解一下数据对齐
  • 2.1 数据对齐
    一般为了加快读取速度,连续定义的变量地址也不一定是连续的,比如:
        int main()
        {
	            int a;
	            char b;
	            double c;
	            printf("%p %p %p\r\n",&a,&b,&c);
	            printf("%d %d\r\n",sizeof(int),sizeof(int *));
	            return 0;
        }

变量的起始地址都是能整数自己的所占用的字节数
0x7fff49cc26ac(整除4) 0x7fff49cc26ab(整除1) 0x7fff49cc26a0(整数8)

  • 2.2 struct的大小
    (1) 起始地址:能够整除自己的最大数据类型成员所占字节数
    (2) 每个成员地址偏移量的对齐
    (3) 结构体总大小的对齐:按照最大数据类型成员所占字节的整数倍
    在这里插入图片描述

  • 2.3 强制对齐
    #pragma pack(n)

#pragma pack(2)
struct stu
{
    char a;
    int b;
    char c;
};
#pragma pack()
//     结果就是8
  • 2.4 根据struct的成员变量推导起始地址—offset_of宏
    #define offset_of(type, member) ((size_t)&(((type )0)->member))
    #define container_of(type,member,ptr) ((type
    )((size_t)ptr - offset_of(type,member)))
    核心思想就是定义一个 起始地址为0的指针 通过找到对应的成员变量的地址 从而就能确定偏移量了
    在这里插入图片描述

  • 2.5 位域

        // 定义一个模拟IP头部分字段的结构体
        struct IPHeader {
            unsigned int version : 4; // 版本号,占4位(实际int 站32位)
            unsigned int headerLen : 4; // 头部长度,占4位(以32位字为单位)
            unsigned int typeOfService : 8; // 服务类型,占8位
            unsigned int totalLength : 16; // 总长度,占16位
        };
        // 好处是减少了结构体占用的空间
        // 坏处是编译器可能不会支持找地址了
        应用的话 比如对寄存器的操作
        struct ControlRegister {
            unsigned int ENABLE : 1;   // Bit 0
            unsigned int MODE : 1;     // Bit 1
            unsigned int PRIORITY : 2; // Bits 2-3
            unsigned int RESERVED : 4; // Bits 4-7
        }; // 实际上就是占用了8位 1个字节 但是这样操作寄存器的可读性会好很多(当然一不小心超了是啥样就没人知道了)
  1. union
    联合体:大小决定于占用空间更大的成员 同时要注意按最大数据类型成员的对齐方式对齐
    union stu{
        char a[10];
        int c;
    };

占用了10个字节,但是对齐方式按int的方式对齐 所以最终占用了12个字节

  • 3.1 匿名联合体
        struct test_uion
        {
            int height;
            char num;
            union {
                char name[10];
                int id; 
            };
        };
      //你可以用name操作 也可以用id操作 因为本质上这里就是**一块12字节的内存** 名字不同操作方式不同
  • 3.2 union的妙用:测试大小端
    0x1234 34是低位置字节 12是高位置字节
    大端: 高地址存放低位置字节
    小端: 高地址存放高位置字节
    union test_edian {
    char a[2];
    short c;
    }
    union test_edian test;
    test.c = 0x1234;
    栈的地址增长方向是高地址向着地地址
    所以 printf(“%x”,test.a[0]); // 0x12是大端 0x34是小端
  1. enum枚举
    枚举的大小并不固定 编译器会进行优化

2. 存储类关键字

注意所有的存储类关键字不能一起使用!
auto extern register static 这四个不能组合

  1. static 关键字
    • static的作用
      • static 修饰函数/全局变量:这个函数/全局变量不能在本文件外被调用了
      • static修饰的变量放在数据段或者bss段(未初始化)–此时变量生命期为整个程序(局部静态变量不随着函数调用退出而被销毁)
      • 作用域:局部静态变量在{}内 / 全局静态变量被限制在这个文件当中
  2. auto关键字
    auto变量存放在栈上 一般随着函数调用结束就返回了
  3. register关键字
    建议把变量放在cpu寄存器中
    此时就不要对这个变量取地址了 不能对寄存器取地址
  4. extern关键字
    外部变量声明,是指这是一个已在别的地方定义过的对象
    extern关键字在链接期间起作用

3.控制类关键字

  这个就没啥好解释的 别用错了就行

在这里插入图片描述
在这里插入图片描述

3. 其他

  const volatile typedef sizeof

  1. sizeof 关键字
    以字节为单位计算数据类型大小
    int a[10]; //sizeof(a)的大小就是40
  • 1.1sizeof 和 strlen的区别
    • strlen计算字符串的长度,以’\0’为结尾
    • sizeof计算的是数据类型
      比如 const char *str = “123456”;
      sizeof(str) 结果就是8 因为str是个指针 在64位操作系统占8个字节
      strlen(str) 结果是6 这是字符串的长度
    • sizeof(“\0”); //大小是2
    • sizeof(a++);//++直接没用了
  • 不用sizeof求大小
        #define mysizeof(type)  ((char * )(&value + 1) - (char *)(&value))
  1. const
  • 2.1 常量指针与指针常量
    • 常量指针:指向的值不能变 但是指针自己可以变
      const int *p = &a; *p = 5;//错误的 p = &b;//正确的
    • 指针常量:指针是常量不能变,但指针指向的值可以变
      int * const p = &a;
      p = &c;//错误的 *p = 5;//正确的
      // 所以引用就是指针常量
      const int * const p; // 啥都不能变
  • 2.2 const与 #define
    • define 是在预处理阶段起作用 而 const是在编译运行阶段起作用
    • define 本身只是字符串替换 不会像const一样占用数据段空间
    • const 定义变量本身是有类型的 会有类型检查
    • const 在调试的时候方便 define不能调试 预处理阶段就取消了
    • define 的定义可以被undefine掉 更加灵活
  1. volatile
    防止编译器去优化变量,每次都去内存取而不是寄存器缓存的
    在这里插入图片描述
  • 3.1 什么时候用volatile
    中断可能会修改这个变量的时候
    并行设备的硬件寄存器

  • 3.2 volatile int * 和 int * volatile

    • volatile int *
      修饰的是int 说明这个指针指向的变量是易变的
    • int * volatile
      说明这个指针是异变的
  • 3.3 const 和 volatile能连用吗
    当然可以
    在这里插入图片描述

    虽然warning了 但是内个值还是被改变了
    为啥呢?

    • const只在编译期间有用, 表示一个变量在程序编译期不能被修改且不能被优化
    • volatile表示在程序运行期,变量值可修改,但每次用到该变量的值都要从内存中读取,以防止意外错误
      比如对于某些只读寄存器的访问 它里面的值虽然是只读的 但是外部他也可能把值变了 那假如编译器优化了 就完了
  1. typedef
  • 4.1 typedef和宏的区别
    • typedef是关键字 编译会进行检查; define是预处理阶段的简单替换
    • 比如 #define my_ptr int* my_ptr a,b; //此时b就是int类型
    • 但是 typedef int* my_ptr; my_ptr a,b; // 这就是俩指针
  • 4.2 typedef的用处–定义一个好看一点的函数指针
    在这里插入图片描述

4. C99新增

  1. inline关键字
    内联函数 就是建议编译器把这个函数在调用位置直接展开
    • 相较于普通函数
      • 减少了函数调用入栈/出栈的开销
      • 但是增加了代码体积
    • 内联函数与宏
      • 内联函数有参数的检查;宏没有只是替换
      • 函数支持调试;而宏在预处理阶段替换后就无了 不支持调试
    • 内联函数为什么定义在头文件中
      因为这样做直接包含这个头文件就可以像宏一样被使用了,如果放在头文件但是不用static修饰,被多个头文件包含时就会报错
  2. _Bool关键字
    布尔类型的
  3. restrict关键字
    告诉编译器该指针是访问某一内存区域唯一途径。
Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐