作用域、存储类别与 typedef一、本篇文章要解决什么问题你写的程序越来越复杂——多个函数、多个文件、变量到处都是。这时候有三个问题你必须搞清楚作用域一个变量在代码的哪些地方可见、哪些地方不可见为什么在函数 A 里定义的变量函数 B 里不能用生命周期变量什么时候出生、什么时候销毁为什么函数返回后里面的局部变量就失效了typedefstruct Student每次都要写 struct 太麻烦了怎么给它起个短别名二、先用一个简单例子理解2.1 公司里的话筒和广播局部变量auto你桌上的内部电话——只能你自己用函数内部别人打不进来。你下班后电话就拔了函数返回变量销毁。全局变量公司广播喇叭——整个公司的人都能听到所有函数都能访问24 小时不断电程序运行期间一直存在。static 局部变量你抽屉里的笔记本——只有你能看但不会每天销毁而是持续保留上次写的内容。extern告诉房东隔壁房间有个冰箱我能用——变量定义在别处但我声明我要用。2.2 typedef 就是起外号你的名字是 “Christopher”但朋友都叫你 “Chris”。typedef就是给一个类型起个短的外号——typedef unsigned long long ull;之后你就可以写ull x;代替unsigned long long x;。三、核心知识点讲解3.1 局部变量自动变量 auto在函数内部或复合语句{}块内部定义的变量默认是局部变量voidfunc(void){intx10;// 局部变量只在 func 内部有效}// 出了这个函数x 就不存在了局部变量的生命周期进入函数时创建函数返回时销毁。不同函数可以有同名的局部变量互不影响。图16-1 作用域层级图帮读者建立作用域的层级概念。3.2 全局变量——在所有函数之外定义#includestdio.hintglobalCount0;// 全局变量所有函数都能访问voidincrement(void){globalCount;}intmain(void){increment();increment();printf(count %d\n,globalCount);// 输出 2return0;}全局变量在程序启动时创建程序结束时销毁。它的初始值默认为 0如果不手动初始化。全局变量虽然方便但过度使用会让程序难以理解和维护——任何函数都能改它出问题时很难定位是谁改的。3.3 static——持久的局部变量static 让局部变量记住上一次的值#includestdio.hvoidcountCalls(void){staticintcallCount0;// 只初始化一次callCount;printf(这个函数被调用了 %d 次\n,callCount);}intmain(void){for(inti0;i5;i){countCalls();}return0;}运行结果这个函数被调用了 1 次 这个函数被调用了 2 次 这个函数被调用了 3 次 这个函数被调用了 4 次 这个函数被调用了 5 次如果callCount是普通局部变量每次输出都是 1。但 static 让它在函数调用之间保持值不变。static 也用于全局变量和函数static int x;或static void helper(void)——表示只在当前文件中可见其他文件不能访问。图16-2 static 局部变量生命周期图一图讲清 static 让变量持久化的机制。3.4 extern——跨文件共享变量// file1.cintshared100;// 定义全局变量// file2.cexternintshared;// 声明我在用 file1.c 中定义的 sharedvoidfunc(void){shared;}extern 告诉编译器这个变量在别处定义了你只管用链接时会找到它。图16-3 多文件项目中 extern 和 static 的使用场景图帮助理解多文件协作时的变量可见性控制。3.5 typedef——给类型起别名typedefunsignedlonglongull;ull big12345678901234ULL;// 等价于 unsigned long long bigtypedefstruct{intid;charname[20];doublescore;}Student;// 现在 Student 就是一个类型名Student stu{1001,Tom,90.0};// 不需要再写 structtypedef 和 #define 的区别#definePTRint*// 文本替换typedefint*IPTR;// 类型别名PTR a,b;// → int *a, b; → a 是指针b 是 int陷阱IPTR a,b;// → a 和 b 都是 int *这就是为什么定义指针类型别名时应该用 typedef 而不是 #define。图16-4 typedef vs #define 指针类型陷阱图让读者记住定义指针别名用 typedef。四、完整代码示例一个演示作用域、static 和 typedef 的综合程序#define_CRT_SECURE_NO_WARNINGS#includestdio.h#includestring.h// 用 typedef 简化结构体使用typedefstruct{intid;charname[30];doublescore;}Student;// 全局变量统计总人数staticinttotalStudents0;voidaddStudent(Student arr[],int*count){printf(请输入学号、姓名、成绩);scanf(%d %29s %lf,arr[*count].id,arr[*count].name,arr[*count].score);(*count);totalStudents;}voidprintStudents(constStudent arr[],intcount){printf(\n 学生列表全局统计%d 人\n,totalStudents);for(inti0;icount;i){printf([%d] %d %-10s %.1f\n,i1,arr[i].id,arr[i].name,arr[i].score);}}intmain(void){Student students[50];// 直接用 Student不用 struct 关键字intcount0;intchoice;do{printf(\n1. 添加学生 2. 显示列表 0. 退出\n请选择);scanf(%d,choice);switch(choice){case1:addStudent(students,count);break;case2:printStudents(students,count);break;case0:break;default:printf(无效选择\n);}}while(choice!0);printf(程序结束共服务了 %d 名学生\n,totalStudents);return0;}五、运行结果1. 添加学生 2. 显示列表 0. 退出 请选择1 请输入学号、姓名、成绩1001 Tom 90.5 1. 添加学生 2. 显示列表 0. 退出 请选择1 请输入学号、姓名、成绩1002 Jerry 85.0 1. 添加学生 2. 显示列表 0. 退出 请选择2 学生列表全局统计2 人 [1] 1001 Tom 90.5 [2] 1002 Jerry 85.0 1. 添加学生 2. 显示列表 0. 退出 请选择0 程序结束共服务了 2 名学生六、代码逐行解析typedef 简化结构体定义typedefstruct{intid;charname[30];doublescore;}Student;这行定义了一个匿名结构体并给它起了别名Student。之后用Student s;就可以定义结构体变量不需要写struct。结构体内部也可以有自己的名字Student放在}之后但用匿名结构体typedef 更简洁。static 全局变量的隐藏作用staticinttotalStudents0;在全局范围用static修饰变量表示这个变量只在当前 .c 文件内部可见如果以后拆分成多文件第 17 篇别的文件不能直接访问totalStudents——这是一种封装手段static 还可以让函数只在当前文件中可见如果addStudent前加static这在多文件项目中很常用。七、初学者常见错误错误1在函数外部使用局部变量voidfunc(void){intx10;}intmain(void){printf(%d\n,x);// 错误x 只在 func 内部存在return0;}错误2函数返回局部变量的地址int*getValue(void){intx42;returnx;// 悬空指针函数返回后 x 已销毁}错误3重复初始化 static 局部变量voidfunc(void){staticintx0;// 只在第一次调用时初始化x0;// 如果想每次调用都重置用这行}错误4用 #define 定义指针类型别名#definePTR_INTint*PTR_INT a,b;// b 是 int陷阱// 正确typedef int * PTR_INT;错误5extern 声明和定义搞混// 在 .h 文件中intx10;// 错误头文件中不应定义变量多文件包含会重复定义externintx;// 正确只声明// 在 .c 文件中intx10;// 正确在源文件中定义八、练习题练习题1调用计数器写一个函数int getID(void)每次调用返回一个递增的唯一编号1, 2, 3…。在 main 中调用 5 次输出每次返回的值。使用 static 局部变量实现。练习题2typedef 简化函数指针用 typedef 定义一个函数指针类型Operation指向double func(double, double)。然后用Operation声明一个数组并存放加减乘除四个函数的指针用循环测试。练习题3static 的作用域验证在同一个项目中创建两个 .c 文件或用注释模拟在其中一个文件定义static int secret 42;和int shared 100;在另一个文件中尝试访问这两个变量。观察哪个能访问、哪个不能。九、本篇总结局部变量函数内部可见函数返回后销毁全局变量所有函数可见程序运行期间一直存在static 局部变量只在函数内可见但值在调用之间保持static 全局变量/函数限制为仅在当前文件内可见是多文件编程中的封装手段extern声明变量在其他文件中定义实现跨文件共享typedef 给类型起别名推荐用于简化结构体和函数指针声明不要用 #define 定义指针类型
16_作用域存储类别与typedef
发布时间:2026/5/26 23:00:34
作用域、存储类别与 typedef一、本篇文章要解决什么问题你写的程序越来越复杂——多个函数、多个文件、变量到处都是。这时候有三个问题你必须搞清楚作用域一个变量在代码的哪些地方可见、哪些地方不可见为什么在函数 A 里定义的变量函数 B 里不能用生命周期变量什么时候出生、什么时候销毁为什么函数返回后里面的局部变量就失效了typedefstruct Student每次都要写 struct 太麻烦了怎么给它起个短别名二、先用一个简单例子理解2.1 公司里的话筒和广播局部变量auto你桌上的内部电话——只能你自己用函数内部别人打不进来。你下班后电话就拔了函数返回变量销毁。全局变量公司广播喇叭——整个公司的人都能听到所有函数都能访问24 小时不断电程序运行期间一直存在。static 局部变量你抽屉里的笔记本——只有你能看但不会每天销毁而是持续保留上次写的内容。extern告诉房东隔壁房间有个冰箱我能用——变量定义在别处但我声明我要用。2.2 typedef 就是起外号你的名字是 “Christopher”但朋友都叫你 “Chris”。typedef就是给一个类型起个短的外号——typedef unsigned long long ull;之后你就可以写ull x;代替unsigned long long x;。三、核心知识点讲解3.1 局部变量自动变量 auto在函数内部或复合语句{}块内部定义的变量默认是局部变量voidfunc(void){intx10;// 局部变量只在 func 内部有效}// 出了这个函数x 就不存在了局部变量的生命周期进入函数时创建函数返回时销毁。不同函数可以有同名的局部变量互不影响。图16-1 作用域层级图帮读者建立作用域的层级概念。3.2 全局变量——在所有函数之外定义#includestdio.hintglobalCount0;// 全局变量所有函数都能访问voidincrement(void){globalCount;}intmain(void){increment();increment();printf(count %d\n,globalCount);// 输出 2return0;}全局变量在程序启动时创建程序结束时销毁。它的初始值默认为 0如果不手动初始化。全局变量虽然方便但过度使用会让程序难以理解和维护——任何函数都能改它出问题时很难定位是谁改的。3.3 static——持久的局部变量static 让局部变量记住上一次的值#includestdio.hvoidcountCalls(void){staticintcallCount0;// 只初始化一次callCount;printf(这个函数被调用了 %d 次\n,callCount);}intmain(void){for(inti0;i5;i){countCalls();}return0;}运行结果这个函数被调用了 1 次 这个函数被调用了 2 次 这个函数被调用了 3 次 这个函数被调用了 4 次 这个函数被调用了 5 次如果callCount是普通局部变量每次输出都是 1。但 static 让它在函数调用之间保持值不变。static 也用于全局变量和函数static int x;或static void helper(void)——表示只在当前文件中可见其他文件不能访问。图16-2 static 局部变量生命周期图一图讲清 static 让变量持久化的机制。3.4 extern——跨文件共享变量// file1.cintshared100;// 定义全局变量// file2.cexternintshared;// 声明我在用 file1.c 中定义的 sharedvoidfunc(void){shared;}extern 告诉编译器这个变量在别处定义了你只管用链接时会找到它。图16-3 多文件项目中 extern 和 static 的使用场景图帮助理解多文件协作时的变量可见性控制。3.5 typedef——给类型起别名typedefunsignedlonglongull;ull big12345678901234ULL;// 等价于 unsigned long long bigtypedefstruct{intid;charname[20];doublescore;}Student;// 现在 Student 就是一个类型名Student stu{1001,Tom,90.0};// 不需要再写 structtypedef 和 #define 的区别#definePTRint*// 文本替换typedefint*IPTR;// 类型别名PTR a,b;// → int *a, b; → a 是指针b 是 int陷阱IPTR a,b;// → a 和 b 都是 int *这就是为什么定义指针类型别名时应该用 typedef 而不是 #define。图16-4 typedef vs #define 指针类型陷阱图让读者记住定义指针别名用 typedef。四、完整代码示例一个演示作用域、static 和 typedef 的综合程序#define_CRT_SECURE_NO_WARNINGS#includestdio.h#includestring.h// 用 typedef 简化结构体使用typedefstruct{intid;charname[30];doublescore;}Student;// 全局变量统计总人数staticinttotalStudents0;voidaddStudent(Student arr[],int*count){printf(请输入学号、姓名、成绩);scanf(%d %29s %lf,arr[*count].id,arr[*count].name,arr[*count].score);(*count);totalStudents;}voidprintStudents(constStudent arr[],intcount){printf(\n 学生列表全局统计%d 人\n,totalStudents);for(inti0;icount;i){printf([%d] %d %-10s %.1f\n,i1,arr[i].id,arr[i].name,arr[i].score);}}intmain(void){Student students[50];// 直接用 Student不用 struct 关键字intcount0;intchoice;do{printf(\n1. 添加学生 2. 显示列表 0. 退出\n请选择);scanf(%d,choice);switch(choice){case1:addStudent(students,count);break;case2:printStudents(students,count);break;case0:break;default:printf(无效选择\n);}}while(choice!0);printf(程序结束共服务了 %d 名学生\n,totalStudents);return0;}五、运行结果1. 添加学生 2. 显示列表 0. 退出 请选择1 请输入学号、姓名、成绩1001 Tom 90.5 1. 添加学生 2. 显示列表 0. 退出 请选择1 请输入学号、姓名、成绩1002 Jerry 85.0 1. 添加学生 2. 显示列表 0. 退出 请选择2 学生列表全局统计2 人 [1] 1001 Tom 90.5 [2] 1002 Jerry 85.0 1. 添加学生 2. 显示列表 0. 退出 请选择0 程序结束共服务了 2 名学生六、代码逐行解析typedef 简化结构体定义typedefstruct{intid;charname[30];doublescore;}Student;这行定义了一个匿名结构体并给它起了别名Student。之后用Student s;就可以定义结构体变量不需要写struct。结构体内部也可以有自己的名字Student放在}之后但用匿名结构体typedef 更简洁。static 全局变量的隐藏作用staticinttotalStudents0;在全局范围用static修饰变量表示这个变量只在当前 .c 文件内部可见如果以后拆分成多文件第 17 篇别的文件不能直接访问totalStudents——这是一种封装手段static 还可以让函数只在当前文件中可见如果addStudent前加static这在多文件项目中很常用。七、初学者常见错误错误1在函数外部使用局部变量voidfunc(void){intx10;}intmain(void){printf(%d\n,x);// 错误x 只在 func 内部存在return0;}错误2函数返回局部变量的地址int*getValue(void){intx42;returnx;// 悬空指针函数返回后 x 已销毁}错误3重复初始化 static 局部变量voidfunc(void){staticintx0;// 只在第一次调用时初始化x0;// 如果想每次调用都重置用这行}错误4用 #define 定义指针类型别名#definePTR_INTint*PTR_INT a,b;// b 是 int陷阱// 正确typedef int * PTR_INT;错误5extern 声明和定义搞混// 在 .h 文件中intx10;// 错误头文件中不应定义变量多文件包含会重复定义externintx;// 正确只声明// 在 .c 文件中intx10;// 正确在源文件中定义八、练习题练习题1调用计数器写一个函数int getID(void)每次调用返回一个递增的唯一编号1, 2, 3…。在 main 中调用 5 次输出每次返回的值。使用 static 局部变量实现。练习题2typedef 简化函数指针用 typedef 定义一个函数指针类型Operation指向double func(double, double)。然后用Operation声明一个数组并存放加减乘除四个函数的指针用循环测试。练习题3static 的作用域验证在同一个项目中创建两个 .c 文件或用注释模拟在其中一个文件定义static int secret 42;和int shared 100;在另一个文件中尝试访问这两个变量。观察哪个能访问、哪个不能。九、本篇总结局部变量函数内部可见函数返回后销毁全局变量所有函数可见程序运行期间一直存在static 局部变量只在函数内可见但值在调用之间保持static 全局变量/函数限制为仅在当前文件内可见是多文件编程中的封装手段extern声明变量在其他文件中定义实现跨文件共享typedef 给类型起别名推荐用于简化结构体和函数指针声明不要用 #define 定义指针类型