全局变量和局部变量_说明局部变量在哪个文件中声明,在哪个文件中给全局变量中赋初值,并举例说明一个全-程序员宅基地

技术标签: stm32  嵌入式学习  

一、C语言由四种地方可以定义变量

在函数外部定义的是全局变量(这里的函数包括main函数)

在头文件中定义的是全局变量

在函数或语句块内部定义的是局部变量

函数的参数是该函数的局部变量

全局变量,在定义位置之后的任意函数都能访问.

二、区别

存储的区别
全局变量存储在一个程序的data段中的静态数据区

局部变量存储在一个程序的data段中的栈区(stack),我们每定义一个局部变量,栈就会分配一块空间用来存储我们定义的局部变量

 

作用域的区别

作用域是指程序中被定义的变量存在(或生效)的区域,超过该区域变量就不能访问

局部变量的作用域仅限于定义这个变量的函数内部,在一个函数内部定义了,就不能在其它函数内部使用这个变量

#include<stdio.h>
void swap()
{
	int a = 10;  //在swap函数内定义一个局部变量a
}
int main()
{
	swap();
	printf("%d", a);//在主函数内部是不能使用的
 
}

全局变量的作用域是整个源程序,也就是整个工程文件,也就是谁说,定义了一个全局变量,这个变量不仅可以在多个函数内部使用,还可以在同一工程中的其它文件中使用!!(这一点太重要了)

#include<stdio.h>
int a = 10;  //定义一个全局变量a
void swap()
{
	printf("%d", a);
}
int main()
{
	swap();
	printf("%d", a);//在主函数内部是不能使用的
 
}


一. 内部函数&外部函数

函数本质上是全局的,因为定义一个函数的目的就是要被另外的函数调用;如果不加声明的话,一个文件中的函数既可以被本源文件中其他函数调用,也可以被其他源文件中的函数调用,但是也可以指定某些函数不能被其他源文件调用;根据函数能否被其他源文件调用,将函数区分为 内部函数 和 外部函数

1、内部函数

如果一个函数只能被本源文件中其他函数调用,则称为 内部函数;在定义内部函数时,在函数名和函数类型的前面加上 static ,即:

static 类型名 函数名(形参表);
例如:
static int fun(int a, int b);

内部函数又称为 静态函数;使用内部函数,可以使函数的作用域只局限于所在文件,这样即使在不同的文件有同名的函数也互不干扰;

通常把只能由本源文件使用的函数放在文件的开头,前面都冠以 static 使之局部化,其他文件不能引用

2、外部函数

如果在定义函数时,在函数的首部加上关键字 extern ,则此函数是外部函数

extern int fun(int a, int b);

这样函数 fun 就可以为其他文件调用;C 语言规定,如果在定义函数时省略 extern ,则默认为外部函数,但是在调用此函数的其他源文件中,需要对此函数进行声明(extern int max(int a, int b);)!!在对此函数作声明时,要加关键字 extern ,表示该函数 是在其他文件中定义的外部函数;

///file1.c
#include <stdio.h>
 
int main(void)
{
	extern int max(int a, int b);//函数声明
	int c = 5, d = 10;
	printf("max = %d\n", max(c, d));
	return 0;
}
 
///file2.c
int max(int a, int b)
{
	return(a>b?a:b);
}

在file1.c 中声明函数 max 是外部的,此时就没有问题了;实际上,使用 extern 声明就能够在本文件调用其他文件中定义的函数,或者说把该函数的作用域扩展到本文件

由于函数在本质上是外部的,在程序中经常要调用其他文件中的外部函数,为了方便,C语言允许在声明函数时省写 extern,如上面的在 max 前省略 extern 也是可以的;(但还是要声明 不能直接调用)


什么时候需要加声明?

// test1.c
int
fun (int num)
{
    return num * 2;
}

int
main (void)
{
    int local = 10;
    int ret = local + fun (local);
    return ret;
}
// test1.c
int fun (int num);  //加上了声明

int
main (void)
{
    int local = 10;
    int ret = local + fun (local);
    return ret;
}

int
fun (int num)
{
    return num * 2;
}

对比1、2两段代码    把子函数 fun 的实现放在 main 函数的后面并且在 mian 函数之前加上 fun 函数的声明。

当解析到 main 函数中的 fun 函数调用时,编译程序就会向前寻找 fun 函数的实现或声明,当发现 fun 函数的声明时,编译程序就会知道 fun 函数的实现在主函数的之后,编译程序便会继续正常编译。
在一些编译器编译程序时,就算在 main 函数之后实现 fun 函数,main 函数之前不加 fun 函数的声明,也不会有出错信息,但正确的语法是要求加上的。


为什么写代码需要头文件这种东西?

引入头文件
上面的程序结构安排使人感觉即利于修改又便于维护,但仔细一分析还是发现一个问题。
问题:当我写了一个 test2.c 程序文件,里面的 main 函数也需要调用 fun 函数时,我需要在 test2.c 程序文件在加入 fun 函数的实现。当我有许多 test 程序文件,里面的 main 函数都需要调用 fun 函数时,我需要在每一个 test 程序文件中都加入 fun 函数的实现。这样的工作重复而又无意义。
解决这个问题最简单的方法就是引入头文件。我可以写一个 test.h 的自定义头文件,把 fun 函数的实现放进去,每一个调用 fun 函数的 test 程序文件只需要引入头文件 test.h 即可
这里需要解释一下编译第一阶段——预处  理。预处理器(cpp)根据以字节#开头的命令,修改原始的C程序。比如 test.c 中第1行的 #include “test.h” 命令告诉预处理器读取自定义头文件 test.h 的内容,并把它直接插入到程序文本中。结果就得到了另一个C程序,通常是以 .i 作为文件扩展名。这里所说的插入是直接把整个系统头文件copy到程序文本#include的位置。并且加上一些标志信息。
下面代码展示:

// test.h
#ifndef TEST_H
#define TEST_H
int
fun (int num)
{
    return num * 2;
}
#endif
// test.c
#include "test.h"

int
main (void)
{
    int local = 10;
    int ret = local + fun (local);
    return ret;
}

经过预处理后,得到 test.i 程序文件。

// test.i
# 1 "test.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 31 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 32 "<command-line>" 2
# 1 "test.c"
# 1 "test.h" 1
int
fun (int num)
{
	return num * 2;
}
# 2 "test.c" 2

int
main (void)
{
	int local = 10;
	int ret = local + fun (local);
	return ret;
}

可以发现 test.h 自定义头文件被复制到了原先 #include 的位置,并且加上了一些标志信息(那些绿色的信息)。因为我们的 #include 在主函数之前,所以在 test.i 程序文件中,fun 函数的实现在 main 函数的前面,因此我们不需要任何函数声明。

这样做很快就会发现另一个问题。
问题:当我因为需要修改 main 函数后,在重新编译的过程中,因为 test.c 文件中有 #include “test.h”,预处理后得到 test.i 预处理文件,fun 函数的实现被复制到 test.i 文件中,fun 函数也需要重新编译一遍。当 fun 函数的实现较为简单时,这样做没有太大的影响,但是当 fun 函数的实现较为复杂时,这样做十分不利于调试和维护。
解决这个问题最好的方法就是引入 fun.c 文件,把 fun 函数的实现放在 fun.c 文件中,在 test.h 自定义头文件中放入 fun 函数的声明。代码如下:

// fun.c
int
fun (int num)
{
    return num * 2;
}
// test.h
#ifndef TEST_H
#define TEST_H
extern int fun (int num);
#endif
// test.c
#include "test.h"

int
main (void)
{
    int local = 10;
    int ret = local + fun (local);
    return ret;
}

这样做我们可以对 test.c 和 fun.c 这两个文件分开预处理、编译、汇编。当得到 fun.o 和 test.o 两个可重定位目标文件时,用链接器将这两个文件连接成可执行目标文件 test 。

gcc -E test.c -o test.i  // 预处理
gcc -S test.i -o test.s  // 编译
gcc -c test.s -o test.o  // 汇编
gcc -E fun.c -o fun.i    // 预处理
gcc -S fun.i -o fun.s    // 编译
gcc -c fun.s -o fun.o    // 汇编
gcc fun.o test.o -o test // 链接

当我们需要修改 main 函数时,只需要对 test.c 文件重新预处理、编译、汇编。得到 fun.o 后重新与 test.o 链接一下就可以得到可执行目标文件 test 。
因为 main 函数中调用了 fun 函数,而 fun 函数的实现又没有与 main 函数在同一文件中,所以需要在 test.c 文件中 main 函数之前加上 fun 函数的外部声明,以此来表明 fun 函数的实现在另一个文件中。而 fun 函数的声明在自定义头文件 test.h 中,所以在 test.c 文件中 mian 函数之前加上 #include “test.h” 。

查看预处理 test.c 后得到的 test.i 文件可以检验这个观点。

// test.i    
# 1 "test.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 31 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 32 "<command-line>" 2
# 1 "test.c"
# 1 "test.h" 1


extern int fun (int num);//预处理把test.h里的东西加载进来 test.h里有函数声明 所以这里把声明加载进来
# 2 "test.c" 2

int
main (void)
{
    int local = 10;
    int ret = local + fun (local);
    return ret;
}

如果在 test.c 文件中没有 #include “test.h” ,在编译过程中便会出现警告信息:warning : implicit declaration of function ‘fun’


联系系统头文件

如果查看过系统头文件,便可以发现:在系统头文件中都是一些函数声明和宏定义,而没有任何函数的实现。我们调用库函数时必须要包含特定的系统头文件,就是因为系统头文件中有我们使用库函数的外部声明
拿 hello.c 程序文件来举例,代码如下:

#include <stdio.h>

int main()
{
	printf("hello, world\n");
	return 0;
}

因为我们的程序文件中调用了库函数 printf ,所以需要包含头文件 #include <stdio.h> ,因为系统头文件 stdio.h 中有 printf 函数的声明,预处理得到 hello.i 文件后在 main 函数之前便会有 printf 函数的外部声明,接下来的编译过程便不会出现警告信息。当经过汇编阶段得到 hello.o 可重定位目标文件后,编译器会自动链接 printf.o 可重定位文件,最终生成可执行目标文件


当我们修改 main 函数后重新编译时,编译器会把 hello.c 程序文件重新预处理、编译、汇编得到 hello.o 可重定位目标文件,然后与 printf.o 链接生成可执行目标文件。编译器当然不会重新预处理、编译、汇编 printf.c 文件。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_42747105/article/details/129840069

智能推荐

【MySQL】mysql The server time zone value “乱码” 错误_the server time zone value 乱码-程序员宅基地

文章浏览阅读7.8k次。稚语希听– 你忘了想起,我忘了忘记…mysql8以上版本时区问题:The server time zone value乱码XXXX异常类似:The server time zone value ‘�й���׼ʱ��’ is unrecognized or represents more than one time zone. You must configure either the server or JDBC driver (via the serverTimezone configuratio_the server time zone value 乱码

【WebApi】————.net WebApi开发(一)_webapi .net-程序员宅基地

文章浏览阅读8.5k次。【1】.部署环境.net4及以上版本。【2】.vs2010 开发需单独安装vs2010 sp1和mvc4mvc4:http://www.asp.net/mvc/mvc4【3】.开发1.新建项目选择ASP.net MVC 4 Web应用程序2.选择Web API 3.在新建立的项目里面有已经生成的webapi模版其中App_Start文件夹下WebApiCo..._webapi .net

几招教你阻止百度搜索自动跳转百度APP(其他网站也适用)!_百度自动跳转app怎么解决-程序员宅基地

文章浏览阅读10w+次,点赞15次,收藏33次。最近阿虚看到个消息说「百度」发布了新政策,禁止网站通过搜索引擎打开后折叠内容强迫下载APP客户端听起来似乎是百度难得良心一回?但实际上该政策仅限于手机百度APP内如果你是通过浏览器用百度搜索则与新政策完全没关系正好前不久不少粉丝来问过我这样一个问题:怎么屏蔽手机浏览器上的「跳转某某APP打开查看」提示那今天阿虚就来教一下怎么解决吧,毕竟这东西的确是有点烦人…屏蔽「跳转某某APP打开查看」这个问题我细看了下,还得分俩类:文章只能显示部分,然后提示你需要安装APP才能查看的,这种应该是大_百度自动跳转app怎么解决

PHP快速入门12-异常处理,自定义异常、抛出异常、断言异常等示例_php 抛出异常-程序员宅基地

文章浏览阅读843次。PHP的异常处理机制可以帮助我们在程序运行时遇到错误或异常情况时,及时发出警告并停止程序继续运行。下面是10个例子,分别展示了PHP异常处理的不同用法。_php 抛出异常

linux 清空docker容器日志_linux清理docker容器log-程序员宅基地

文章浏览阅读221次。【代码】linux 清空docker容器日志。_linux清理docker容器log

青岛大学开源OJ平台搭建_github oj开源-程序员宅基地

文章浏览阅读7.3k次,点赞3次,收藏15次。源码地址为:https://github.com/QingdaoU/OnlineJudge可参考的文档为:https://github.com/QingdaoU/OnlineJudgeDeploy/tree/2.0一、安装所依赖的环境sudo apt-get update && sudo apt-get install -y vim python-pip curl g..._github oj开源

随便推点

docker安装及部署mysql_docker部署mysql-程序员宅基地

文章浏览阅读1.5k次,点赞2次,收藏9次。docker安装与mysql部署_docker部署mysql

联想笔记本G510升级固态硬盘(SSD)血泪教程!!!_联想g510更换固态硬盘-程序员宅基地

文章浏览阅读8.5w次,点赞23次,收藏55次。#联想笔记本G510升级固态硬盘(SSD)血泪教程!!!用了5年的联想笔记本G510,经过了四年的游戏历程,然后四年后还老当益壮的挣扎在我工作的战斗一线,是我并肩作战多年,比兄弟还要亲的兄弟,虽然此时已经身躯残破,反应迟缓我依旧不舍得抛弃它(主要是没钱!)然后为了我个人的用户体验决定花少量的票子,让它多挣扎一会,最好是能坚持到我度过贫困期. 下面是我升级的悲催历程! - 首先为了提升运行速..._联想g510更换固态硬盘

问题记录——正则表达式匹配控制符_正则表达式匹配控制字符-程序员宅基地

文章浏览阅读910次。问题前端用xterm.js通过websocket连接docker虚拟终端,返回的字符中包括如下字符串,其中有两个控制字符,“ESC"和"BEL” ,想通过正则表达式匹配这一段字符,然后去掉这段字符:参考文档控制字符编码表转义符对照表通过上面查询得知,"ESC"和"BEL"这两个控制符的ASCII码分别为:十进制为27和7,十六进制为0x1B和0x07,转义符分别为:\e和\a代码**注意:**直接使用ASCII码匹配是不行的,一定要用转义符才行。如下测试代码中,只有regex3才能匹_正则表达式匹配控制字符

Android RIL框架分析-程序员宅基地

文章浏览阅读1.5k次。1.RIL框架 RIL,Radio Interface Layer。本层为一个协议转换层,提供Android Telephony与无线通信设备之间的抽象层。 Android RIL位于Telephony Frameworks之下,Modem之上的,根据源码,RIL可以分为两个部分:Frameworks 框架层中的java程序,简称RILJ。HAL层中C/C++程序,简称RILC,RILC具体的又包括LibRIL、Rild和Reference-RIL这三个部分。 Andr..._ril框架

Python编程基础:第六节 math包的基础使用Math Functions_ps math function-程序员宅基地

文章浏览阅读565次。第六节 math包的基础使用前言实践前言我们通常会对数值型变量进行计算,这里我们给出一些常用的函数用于辅助你的计算过程。常用的数学计算函数均在math包。实践首先我们导入math包,并定义一个浮点型变量pi将其赋值为3.14:import mathpi = 3.14如果我们需要计算浮点型变量四舍五入后的计算结果,用函数round()即可:print(round(pi))>>> 3如果我们需要向上取整,那就需要函数math.ceil():print(math.cei_ps math function

canal异常 Could not find first log file name in binary log index file_canal could not find first log file name in binary-程序员宅基地

文章浏览阅读4.4k次,点赞3次,收藏2次。Could not find first log file name in binary log index file问题解决解决过程问题最近在使用canal来监测数据库的变化,处理变动的数据。由于有一段时间没有用了,这次启动在日志文件中看到这个异常 Could not find first log file name in binary log index file,详细信息如下:2020-12-16 19:14:42.053 [destination = tradeAndRefund , addr_canal could not find first log file name in binary log index file