网站首页 > 技术文章 正文
这是一个C语言系列文章,如果是初学者的话,建议先行阅读之前的文章。笔者也会按照章节顺序发布。
在数学中,变量x一般都有其值域,也就是x都可以有哪些值,或者说在什么数值范围内x是有效的。同理,C语言中,每个变量或者函数都有其作用域,也就是在什么范围内这个变量或函数有效(可被编译器找到)。
对于变量,根据作用域的不同被分为两大类——局部变量和全局变量。
局部变量
所谓的局部变量是指在函数体中定义的变量,这些变量的作用域就是函数内部,即函数返回后,这些变量将被销毁。这类变量我们一般称作自动变量。
自动变量的定义形式我们在变量一文中介绍过:
数据类型 变量名;
或
数据类型 变量名 = 初始值;
还有一种自动变量——函数参数,函数参数也是仅在函数内有效的。
此外,C语言还提供了一种作用域在函数内,但函数返回后不会销毁的变量——静态变量。
静态变量是指在函数体中,用如下形式定义的变量:
static 数据类型 变量名;
或
static 数据类型 变量名 = 初始值;
静态变量与自动变量的区别是:每次函数调用时,函数内的自动变量所占用的内存都会被重新分配,其值也会按照语句重新赋值。而静态变量不同,如果使用定义同时初始化形式定义静态变量,则静态变量仅会被初始化一次。并且每次函数调用时静态变量的值都是沿用上一次调用改变后的值,而不会被重置。
举个例子:
#include <stdio.h>
void foo(void)
{
static int a = 1;
++a;
printf("%d\n", a);
}
int main(void)
{
foo();
foo();
return 0;
}
这个例子的输出是:
2
3
原因是,第一次进入foo时,静态变量a被初始化为1,然后自加变为2,所以printf打印的结果是2(第一行)。随后函数返回main,之后再次调用foo函数。这次foo中a不再被重新赋值为1,而是依旧保持上次被修改后的结果,即2。然后再自加变为3,最后打印其值3(第二行)。
下面看一个初学者常犯的错误:
int *return_array(void)
{
int array[2] = {1, 2};
return array;
}
int main(void)
{
int *ret = return_array();
return 0;
}
这是一个典型的错误用法。我们说过,函数内的自动变量的作用域仅限于函数内,当函数返回时,自动变量会被销毁。因此,main中ret指向的数组,其内容将是不可预知的内容,访问其内容可能会导致程序崩溃。
想要正常返回一个数组,利用静态变量是一种解决方案。除此之外,还有一种动态分配内存的方案,将在后续内存管理相关的文章中说明。
全局变量
全局变量是指变量定义于任何函数体之外,且作用域是整个程序范围内的变量。这类变量又分为两类——普通全局变量和静态全局变量。
普通全局变量定义形式如下:
数据类型 变量名;
或
数据类型 变量名 = 初始值;
而静态全局变量的定义形式为:
static 数据类型 变量名;
或
static 数据类型 变量名 = 初始值;
与局部变量中自动变量和静态变量的定义一样,但是含义完全不同的。
静态全局变量与普通全局变量的不同在于作用域范围。普通全局变量是作用于整个程序范围内的,而静态全局变量的作用域则是当前的源文件。
举例:
/*a.c*/
int a = 10;
static int b = 100;
int main(void)
{
foo();
}
/*b.c*/
#include <stdio.h>
void foo(void)
{
printf("a:%d\n", a);
printf("b:%d\n", b);//这句是无法通过编译的
}
如果按照上面代码创建两个源文件并编译,是无法生成可执行程序的,且会报错。
原因有二:
1.正如我注释所写,b是a.c中的静态全局变量,作用域仅在a.c,因此b.c无法访问。
2.全局变量a虽然不是静态全局变量,但在b.c中缺少声明,因此无法使用。
下面我们重写b.c,修正这两个问题:
/* b.c */
#include <stdio.h>
extern int a;
void foo(void)
{
printf("a:%d\n", a);
}
这里,去掉了b的打印,同时增加了全局变量a的声明。
注意,这个全局变量的声明使用了extern关键字。extern关键字用于告知编译器,用其声明的变量或者函数是全局作用域的,需要从可执行程序涉及到的全部源文件中寻找。
同名覆盖
不知是否有读者想过,如果全局变量和局部变量同名,那么函数内的变量的值会是什么呢?
看一个例子:
#include <stdio.h>
int a = 1;
int main(void)
{
int a = 2;
printf("%d\n", a);
return 0;
}
这段代码的执行结果是:2。
这里存在同名覆盖原则:同名的局部变量会覆盖同名的全局变量。
函数作用域
函数的作用域与全局变量的作用域相同,毕竟在C语言中函数内部无法再定义函数。
提供给外部其他源文件使用的函数的声明形式如下:
extern 返回值类型 函数名(参数列表...);
给本文件内使用的函数的声明形式如下:
static 返回值类型 函数名(参数列表...);
并且,函数对编译器的可见性也取决于函数声明的位置,例如:
int main(void)
{
foo();
return 0;
}
static void foo(void);
void foo(void)
{
}
如此声明foo函数,编译器依旧会报错,因为foo函数的定义对main不可见。如果将foo函数的static声明提前到main函数前(即本例中放在第一行),则可正常编译。
块作用域
前面关于语句的文章中并未提及一种特殊的语句——块语句。
这种语句是以大括号({})扩起的,其大括号内部可以是单条语句,也可以是多条语句。
{
...//一条或多条语句
}
这并非是说C语言中看到大括号就是块语句。函数的大括号并不属于块语句,其余则皆为块语句,包括if-else、for、while等结构中涉及大括号的部分。
我们先来看一个例子:
#include <stdio.h>
int main(void)
{
int a = 1;
{
int a = 2;
printf("In block a:%d\n", a);
}
printf("Out of block a:%d\n", a);
return 0;
}
运行结果为:
In block a:2
Out of block a:1
这个例子告诉我们两个事实:
- 块内同名变量将覆盖外层同名变量
- 块内定义的变量在块外无法访问,即块结构内的自动变量会随块结构完结而销毁。
头文件与源文件
之前的文章中,所涉及到的例子都是放在.c文件中的。然而C语言中并不只有.c文件。
在C语言中有两种文件——头文件和源文件。
源文件就是我们所说的文件名后缀以.c结尾的文件,其中的代码一般都是各类函数的定义。
头文件是文件名以.h结尾的文件。这类文件中一般记录一些结构定义、函数声明、变量声明、类型定义等。关于结构体和类型定义我们后续文章会有专门说明。
什么情况下需要头文件呢?我们来看个例子:
/*b.c*/
extern void foo(void);
void bar(void)
{
foo();
}
/*c.c*/
void foo(void)
{
}
可以看到,a.c和b.c都用到了c.c中的foo函数,因此它们都需要声明foo函数。如果这时我对foo函数的返回类型做了修改,那么我需要到声明foo的其他源文件中修改其声明。如果我有20个源文件中都用到了foo呢?那么此时的修改会不会引起混乱呢?因此,头文件就派上了用场。
我们看下修改后的代码:
/*a.c*/
#include "c.h"
extern void bar(void);
int main(void)
{
foo();
bar();
return 0;
}
/*b.c*/
#include "c.h"
void bar(void)
{
foo();
}
/*c.c*/
void foo(void)
{
}
/*c.h*/
extern void foo(void);
如此,我们将foo的extern声明仅写一份放在c.h头文件中。
然后利用预编译的include指令,将c.h的内容引入到需要使用foo函数的a.c和b.c文件中。关于include的更详细介绍,将在预编译宏文章中给出。目前只需要知道,在编译时,include会将其后紧跟的文件名所指定的文件中的内容原封不动展开(可看作复制)进使用该include指令的源文件中,且展开点就是include指令所在位置。
喜欢的小伙伴可以关注码哥,也可以给码哥留言评论,如有建议或者意见也欢迎私信码哥,我会第一时间回复。
感谢阅读!
猜你喜欢
- 2024-09-26 C语言存储类之内部链接的静态变量
- 2024-09-26 C/C++关键字static作用解析(c++中static关键字)
- 2024-09-26 这个offer你吃定了!C++「面试真题」及基本知识点总结
- 2024-09-26 extern关键字详解(explain关键字)
- 2024-09-26 【python 基础篇 十四】python函数的作用域
- 2024-09-26 PHP入门读书笔记(三): 常量和变量
- 2024-09-26 静态变量使用场景?(静态变量使用场景)
- 2024-09-26 C语言static关键字的全面深入探讨
- 2024-09-26 Excel-VBA教程005,什么是变量,变量的类型、变量的作用域?
- 2024-09-26 快速上手系列-C语言之变量的存储类别与作用域
- 1515℃桌面软件开发新体验!用 Blazor Hybrid 打造简洁高效的视频处理工具
- 573℃Dify工具使用全场景:dify-sandbox沙盒的原理(源码篇·第2期)
- 513℃MySQL service启动脚本浅析(r12笔记第59天)
- 487℃服务器异常重启,导致mysql启动失败,问题解决过程记录
- 486℃启用MySQL查询缓存(mysql8.0查询缓存)
- 469℃「赵强老师」MySQL的闪回(赵强iso是哪个大学毕业的)
- 449℃mysql服务怎么启动和关闭?(mysql服务怎么启动和关闭)
- 447℃MySQL server PID file could not be found!失败
- 最近发表
- 标签列表
-
- cmd/c (90)
- c++中::是什么意思 (83)
- 主键只能有一个吗 (66)
- c#console.writeline不显示 (75)
- pythoncase语句 (81)
- es6includes (73)
- windowsscripthost (67)
- apt-getinstall-y (86)
- node_modules怎么生成 (76)
- c++int转char (75)
- static函数和普通函数 (76)
- el-date-picker开始日期早于结束日期 (70)
- js判断是否是json字符串 (67)
- checkout-b (67)
- c语言min函数头文件 (68)
- asynccallback (71)
- localstorage.removeitem (74)
- vector线程安全吗 (70)
- & (66)
- java (73)
- js数组插入 (83)
- mac安装java (72)
- eacces (67)
- 查看mysql是否启动 (70)
- 无效的列索引 (74)