11---
22chapter : 1
33cpp_standard :
4- - 11
4+ - 11
55description : 深入理解 C 语言的作用域规则、存储类别和链接性,掌握 static 的三种用法
66difficulty : beginner
77order : 8
88platform : host
99prerequisites :
10- - 控制流:让程序学会选择和重复
10+ - 控制流:让程序学会选择和重复
1111reading_time_minutes : 20
1212tags :
13- - host
14- - cpp-modern
15- - beginner
16- - 入门
17- - 基础
13+ - host
14+ - cpp-modern
15+ - beginner
16+ - 入门
17+ - 基础
1818title : 作用域与存储类别
1919---
20+
2021# 作用域与存储类别
2122
2223如果你写过超过两个源文件的项目,大概率已经踩过这样的坑:两个文件里都定义了一个叫 ` count ` 的全局变量,编译的时候链接器一脸懵逼地告诉你 ` multiple definition ` 。或者更隐蔽的情况——你在某个 ` .c ` 文件里定义了一个辅助函数,结果别的文件不小心也调用了它,后来你改了那个函数的实现,调用方毫无预警地崩了。
@@ -261,13 +262,13 @@ extern int kValue = 42; // 千万别这么干!
261262
262263这三者的关系可以用一个表格来总结:
263264
264- | 声明位置 | 关键字 | 链接性 | 作用域 | 生命周期 |
265- | --- | --- | --- | --- | --- |
266- | 函数内 | (无) | 无 | 块 | 自动 |
267- | 函数内 | ` static ` | 无 | 块 | 静态 |
268- | 函数外 | (无) | 外部 | 文件 | 静态 |
269- | 函数外 | ` static ` | 内部 | 文件 | 静态 |
270- | 函数外 | ` extern ` | (取决于首次声明) | 文件 | 静态 |
265+ | 声明位置 | 关键字 | 链接性 | 作用域 | 生命周期 |
266+ | -------- | -------- | ------------------ | ------ | ----- --- |
267+ | 函数内 | (无) | 无 | 块 | 自动 |
268+ | 函数内 | ` static ` | 无 | 块 | 静态 |
269+ | 函数外 | (无) | 外部 | 文件 | 静态 |
270+ | 函数外 | ` static ` | 内部 | 文件 | 静态 |
271+ | 函数外 | ` extern ` | (取决于首次声明) | 文件 | 静态 |
271272
272273这个表格值得多看几眼——注意函数外的 ` static ` 改变的是链接性(从外部变成内部),而不是作用域或生命周期。
273274
@@ -455,6 +456,58 @@ void counter_reset(void);
455456
456457请自行实现 ` counter.c ` 。
457458
459+ ### 练习 1 参考答案
460+
461+ main.c
462+
463+ ``` c
464+ #include < stdio.h>
465+ #include " counter.h"
466+
467+ int main (void) {
468+ printf("%d\n",counter_get()); //输出应当是0
469+ counter_increment();
470+ printf("%d\n",counter_get()); //输出应当是1
471+ counter_increment();
472+ printf("%d\n",counter_get()); //输出应当是2
473+ counter_reset();
474+ printf("%d\n",counter_get()); //输出应当是0
475+ return 0;
476+ }
477+ ```
478+
479+ counter.h
480+
481+ ```c
482+ #ifndef MODERNCPP_PRE8_1_COUNTER_H
483+ #define MODERNCPP_PRE8_1_COUNTER_H
484+
485+ void counter_increment(void);
486+ int counter_get(void);
487+ void counter_reset(void);
488+
489+ #endif //MODERNCPP_PRE8_1_COUNTER_H
490+ ```
491+
492+ counter.c
493+
494+ ``` c
495+ #include " counter.h"
496+
497+ static int counter = 0 ;
498+ void counter_increment (void) {
499+ counter++;
500+ }
501+
502+ void counter_reset(void) {
503+ counter = 0;
504+ }
505+
506+ int counter_get(void) {
507+ return counter;
508+ }
509+ ```
510+
458511### 练习 2:多文件符号可见性
459512
460513创建三个文件 `a.c`、`b.c`、`main.c`。要求:
@@ -471,21 +524,123 @@ void counter_reset(void);
471524// 各 .c 文件的实现留给你
472525```
473526
527+ ### 练习 2 参考答案
528+
529+ main.c
530+
531+ ``` c
532+ #include < stdio.h>
533+ #include " a.h"
534+ #include " b.h"
535+
536+ int main (void) {
537+
538+ printf("%d\n",kSharedValue); //输出应当是0
539+ set_kSharedValue(100);
540+ printf("%d\n",kSharedValue); //输出应当是100
541+
542+ return 0;
543+ }
544+ ```
545+
546+ a.h
547+
548+ ```c
549+ #ifndef MODERNCPP_PRE8_2_A_H
550+ #define MODERNCPP_PRE8_2_A_H
551+
552+ extern int kSharedValue;
553+
554+ #endif //MODERNCPP_PRE8_2_A_H
555+ ```
556+
557+ b.h
558+
559+ ``` c
560+ #ifndef MODERNCPP_PRE8_2_B_H
561+ #define MODERNCPP_PRE8_2_B_H
562+
563+ void set_kSharedValue(int value);
564+
565+ #endif // MODERNCPP_PRE8_2_B_H
566+ ```
567+
568+ a.c
569+
570+ ``` c
571+ #include < stdio.h>
572+ #include " a.h"
573+
574+ int kSharedValue = 0 ;
575+ static void helper_a (void) {
576+ printf("need help?");
577+ }
578+ ```
579+
580+ b.c
581+
582+ ```c
583+ #include <stdio.h>
584+ #include "b.h"
585+ #include "a.h"
586+
587+
588+ static void helper_a(void) {
589+ printf("need help?");
590+ }
591+ void set_kSharedValue(int value) {
592+ kSharedValue = value;
593+ }
594+ ```
595+
474596### 练习 3:延迟初始化
475597
476598用 ` static ` 局部变量实现一个 ` get_config ` 函数:第一次调用时执行初始化(打印 "Initializing..." 并设置默认值),后续调用直接返回已初始化的值,不再重新初始化。
477599
478600``` c
601+ typedef struct {
602+ int max_connections; //建议设为5
603+ int timeout_ms; //建议设为500
604+ const char* server_name; //建议设为localhost
605+ } Config;
606+
607+ const Config* get_config (void);
608+ ```
609+
610+ > 提示:`static` 局部变量只在第一次进入函数时被初始化——正好可以用来实现"只初始化一次"的语义。
611+
612+ ### 练习 3 参考答案
613+
614+ ```c
615+ #include <stdio.h>
616+
479617typedef struct {
480618 int max_connections;
481619 int timeout_ms;
482620 const char* server_name;
483621} Config;
484622
485623const Config* get_config(void);
486- ```
487624
488- > 提示:`static` 局部变量只在第一次进入函数时被初始化——正好可以用来实现"只初始化一次"的语义。
625+ int main(void) {
626+ get_config(); //应当输出"Initializing..."
627+ printf("%d %d %s\n",get_config()->max_connections, get_config()->timeout_ms, get_config()->server_name); //应当输出"5 500 localhost"
628+ return 0;
629+ }
630+
631+ const Config* get_config(void) {
632+ static Config config;
633+ static int initialized = 0;
634+ if (!initialized) {
635+ printf("Initializing...\n");
636+ config.max_connections = 5;
637+ config.timeout_ms = 500;
638+ config.server_name = "localhost";
639+ initialized = 1;
640+ }
641+ return &config;
642+ }
643+ ```
489644
490645## 参考资源
491646
0 commit comments