extern 在多文件下的理解与使用
extern 是一个关键字,用于在程序中声明外部变量或函数。
变量的声明通常包含变量的类型和名称,但是如果变量定义在一个源文件中,而其它源文件也需要访问这个变量,则需要在其它源文件中声明该变量。此时可以使用 extern 关键字来告诉编译器,该变量已经在其它文件中定义好了,不需要再重新分配存储空间,只需要使用它即可。
例如:
在编译 file2.c 文件时,由于该文件中使用了 extern 关键字声明了变量 x,所以编译器不会分配新的存储空间,而是使用 file1.c 中已经定义好的变量 x。 函数的情况类似,如果一个函数定义在一个源文件中,而其它源文件也需要调用该函数,则需要使用 extern 关键字来声明该函数。
例如:
在编译 main.c 文件时,由于该文件中包含了 func.h 头文件,其中使用了 extern 关键字声明了函数 func,所以编译器知道该函数已经在其它源文件中定义好了,可以直接在链接时链接函数即可。
struct 关键字的理解与柔性数组
在C语言中,struct是一个关键字,用于定义结构体。结构体是由多个不同类型的数据组成的集合,可以将它们组合成一个自定义的数据类型。结构体可以包含不同的数据类型,如整型、浮点型、字符型、数组以及其他结构体等,它们可以组合成复杂的数据结构。
例如学生:
这段代码定义了一个包含姓名、年龄、性别和体重的学生结构体Stu,并在main函数中定义了一个Stu类型的变量s,并为其赋值。接着定义了一个指向Stu结构体的指针p,并将其指向变量s的内存地址。最后,通过指针p访问结构体中的成员变量,并将它们打印出来,分别使用了结构体指针的两种访问方式:(*p).name 和 p->name,它们的作用是一样的。
空结构体的大小
这段代码中定义了一个名为S的结构体,但是结构体里面没有任何成员。在代码中调用了sizeof操作符,对结构体S进行计算其大小的操作,但vs编译器在编译这段代码时会报错。根据C语言的规定,定义一个结构体或者联合体时,至少需要包含一个成员,否则这个结构体是无效的。
这段代码中定义了一个名为S的结构体,但是结构体里面没有任何成员。在代码中调用了sizeof操作符,对结构体S进行计算其大小的操作,但gcc编译器在编译这段代码时不会报错,求得大小为0,并且可以定义变量(可以定义空间大小为0的变量),但是没有空间,不能进行初始化。
柔性数组
#include<stdio.h> #include<stdlib.h> struct S { int num; int arr[]; }; int main() { printf("%d\n", sizeof(struct S));//柔性数组不占用空间 struct S* p = malloc(sizeof(struct S) + sizeof(int) * 10); int i = 0; p->num = 10; for (i = 0; i < p->num; i++) { p->arr[i] = i; } free(p); p = NULL; return 0; }
union 的内存级布局理解
在C语言中,union是一种特殊的数据类型,它允许您在同一内存位置存储不同的数据类型。它类似于结构体,但是它的所有成员共享同一内存空间,因此只能够保存其中一个成员的值。 定义一个union变量的语法与结构体相似,
例如:
这个union类型变量un可以保存整数int、字符char。但是,不能同时保存这两种类型,因为它们共享同一个内存空间。 Union的大小由其最大成员的大小确定,例如上面的例子中,un变量的大小是4字节,因为整形类型的成员是这个联合体中最大的。
基本认识与补充认识
union的内存布局
大小端对于union的影响
小练习
//联合体内部的成员起始地址都是一样的!!! #include<stdio.h> union un { int a; char c[4]; }*p,u;//全局变量*p、u int main() { p = &u; p->c[0] = 0x39; p->c[1] = 0x38; p->c[2] = 0x37; p->c[3] = 0x36; printf("0x%x\n", p->a); return 0; }
enum 关键字的基本理解
enum 与 #define 的区别
typedef 的理解与分类
typedef 与 #define 的区别
1. 定义方式不同
- typedef 是一种类型定义,用于给已有类型取一个新的名字,是在编译期间解析。
- #define 是一种预处理编译指令,用于替换代码中的文本宏,是在预处理阶段解析,即在编译器处理代码之前。
2. 定义的作用域不同
- typedef 定义的作用域是局部的,只在当前定义的作用域内有效。
- #define 定义的作用域是全局的,从定义位置开始到文件末尾都有效。
3. 可读性和维护性不同
- typedef 的定义会产生一个新的类型名,使得代码更容易理解和维护。
- #define 定义的宏不会改变原有的类型或者结构体,容易引起混淆和错误。
4. 可以对已有类型(包括自定义类型)进行重定义
- 使用 typedef 语句,可以为 C 语言中已有的类型定义一个新的名字。
- #define 指令无法直接对 C 语言中已有的类型进行重定义。
总的来说,typedef 适用于定义新的数据类型或别名,而 `#define` 适用于定义常量和函数宏等。
//问题1: typedef int * ptr_t; ptr_t p1,p2; 问:p1,p2分别是什么类型 //问题2: #define PTR_T int* PTR_T p1, p2; 问:p1,p2分别是什么类型
问题1:p1和p2分别是指向int类型的指针变量。由于typedef int * ptr_t; 定义了一个名为ptr_t的类型,它是指向int类型的指针类型,因此ptr_t p1,p2; 相当于 int* p1, p2; 即p1和p2都是指向int类型的指针变量。
问题2中,#define PTR_T int* 定义了一个名为PTR_T的宏,将int*替换为PTR_T。因此 PTR_T p1, p2; 相当于 int* p1, p2; 这里,p1是指向int类型的指针变量,而p2是int类型的普通变量。但是这种定义方式可能会带来理解上的混淆,因为它让人很难区分p1和p2的类型,不建议使用。正确的做法是使用typedef类型定义来明确变量的数据类型。
#include <stdio.h> #define INT32 int typedef int int32; int main() { //unsigned int32 a = 10; C中typedef不支持这种类型的扩展,不能当成简单的宏替换 unsigned INT32 a = 20; //宏简单替换,可以 return 0; }
在typedef中,INT32类型仅被视为一种新的数据类型,C中typedef不支持这种类型的扩展,不能当成简单的宏替换,而在宏定义中,INT32被视为简单的字符替换。
关键字总结
数据类型关键字(12个)
控制语句关键字(12个)
存储类型关键字(5个)
注意:存储关键字,不可以同时出现,也就是说,在一个变量定义的时候,只能有一个。
其他关键字(3个)
现在我们来看看这个有没有问题
typedef static int s_int;
error C2159: 指定了一个以上的存储类,因为static和typedef都是存储类型关键字,而存储类型关键字不可以同时出现,所以不能这样写代码。