全局变量和局部变量

2023-05-16

一、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 文件。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

全局变量和局部变量 的相关文章

随机推荐

  • Linux网络编程----UDP编程基础知识

    UDP概述 UDP 是 User Datagram Protocol 的简称 xff0c 中文名是用户数据报协议 xff0c 是一个简单的面向数据报的传输层协议 xff0c 在网络中用于处理数据包 xff0c 是一种无连接的协议 UDP 不
  • get 命令汇总

    get 命令汇总 git config 配置 Git 的相关参数 Git 一共有3个配置文件 xff1a 1 仓库级的配置文件 xff1a 在仓库的 git gitconfig xff0c 该配置文件只对所在的仓库有效 2 全局配置文件 x
  • c++ socket简单封装

    简单封装 并不实际应用 框架图 Mysocket 主要是定义TCP和UDP的一些相同的操作 xff0c 需要的他们各自去实现 Mysocket h ifndef MYSOCKET H define MYSOCKET H class MySo
  • pixhawk2飞控接头型号

    BM04B GHS TBT BM05B GHS TBT BM06B GHS TBT
  • 多种视觉SLAM方案对比

    点击上方 小白学视觉 xff0c 选择加 34 星标 34 或 置顶 重磅干货 xff0c 第一时间送达 本文转自 新机器视觉 在SLAM研究中 xff0c 我们通常需要在各数据集上测试各个方案的性能情况 如下主要对表1中6个视觉SLAM方
  • 激光雷达核心技术及行业格局梳理

    点击上方 小白学视觉 xff0c 选择加 34 星标 34 或 置顶 重磅干货 xff0c 第一时间送达 引言 xff1a 车载摄像头是ADAS 的核心传感器 车载摄像头搭载颗数稳步提升 根据 Yole 数据 xff0c 2018 年全球平
  • 2 ROS1通讯编程基础(2)

    2 ROS1通讯编程基础 2 3 配置文件的解读2 3 1 CMakeList txt解读2 3 1 1 find package的配置2 3 1 2 messages services and actions的配置2 3 1 3 动态重配
  • Rviz 使用Arbotix控制机器人运动

    需求 控制机器人模型在 rviz 中做圆周运动 实现流程 安装 Arbotix创建新功能包 xff0c 准备机器人 urdf xacro 文件添加 Arbotix 配置文件编写 launch 文件配置 Arbotix启动 launch 文件
  • VINS问题整理

    VINS的初始化过程 xff1f 首先进行纯视觉SfM xff1a 把滑窗填满 xff0c 然后选择枢纽帧 xff08 和最后一帧有足够的视野重叠保证计算的位姿精度 xff0c 并且和最后一帧有足够的视差保证三角化地图点的精度 xff09
  • 两台ubuntu电脑如何搭建局域网以及通信

    两台ubuntu电脑如何搭建局域网以及通信 功能 xff1a 用自己的电脑代替设备中的电脑进行数据处理 xff0c 以及将最后的结果传给设备电脑 需要做的内容的 xff1a 首先用网线将自己的pc与设备连接起来 1 将自己的笔记本ip地址手
  • PC偏振控制器、锁模激光器技术、AOM声光调制器、相位噪声、锁相环、光耦合器类型

    1 PC 偏振控制器 xff08 1 xff09 什么叫做偏振光 xff1f polarized light 光是一种电磁波 xff0c 电磁波是横波 xff0c 它具有偏振性 xff0c 具有偏振性的光则称为偏振光 具体体现 xff1a
  • 小梅哥——38译码器

    三八译码器 xff0c 即是 3 种输入状态翻译成 8 种输出状态 真值表 代码展示 module decoder 3 8 a b c out input a 输入端口a input b 输入端口b input c 输入端口c output
  • 基本RS触发器(SR锁存器)

    一 前言 SR锁存器 Set Reset Latch 是静态存储单元当中最基本 xff0c 也是电路结构最简单的一种 xff0c 通常由两个或非门或者与非门组成 其中S表示Set xff0c R表示Reset 则S D称为置位端或置1输入端
  • 01-RTOS

    对于裸机而言 xff0c 对于RTOS而言 即 xff1a 对于裸机 xff0c 打游戏意味着不能回消息 回消息意味着不能打游戏 对于RTOS 打游戏和裸机的切换只需要一个时间片节拍 1ms 从宏观来看 就是同时进行的两件事 xff08 但
  • uORB笔记

    不同的类调用同一函数orb subscribe ORB ID vehicle gps position xff0c 来订阅GPS信息是 xff0c 该函数返回的值不同 xff0c 也就是说每个订阅者针对同一主题 xff0c 在调用函数orb
  • STM32 SystemInit()函数学习总结

    拿到程序后如何看系统时钟 xff1f User文件夹 system stm32f4xx程序 xff0c 先找systemcoreclock 系统时钟 xff09 但是这里这么多个系统时钟应该如何选择 点击魔法棒 xff0c 然后点击C C
  • FPGA IP核之PLL四种输出模式的理解

    一 源同步模式 使得进入管脚时的数据和上升沿的相位关系与到达芯片内部第一级寄存器时数据和上升沿的相位关系保持不变 xff08 通过调整内部的布局布线延时做到的 xff0c 用于数据接口 xff0c 特别是高速的情况下 xff09 详细理解
  • FPGA_边沿监测理解

    一 简易频率计设计中为什么一定要获取下降沿 gate a 实际闸门信号 gate a stand 将实际闸门信号打一拍之后的信号 gate a fall s 下降沿标志信号 cnt clk stand Y值 xff0c 即在实际闸门信号下
  • HAL库 STM32 串口通信

    一 实验条件 将STM32的PA9复用为串口1的TX xff0c PA10复用为串口1的RX STM32芯片的输出TX和接收RX与CH340的接收RX和发送TX相连 xff08 收发交叉且PCB上默认没有相连 xff0c 所以需要用P3跳线
  • 全局变量和局部变量

    一 C语言由四种地方可以定义变量 在函数外部定义的是全局变量 xff08 这里的函数包括main函数 xff09 在头文件中定义的是全局变量 在函数或语句块内部定义的是局部变量 函数的参数是该函数的局部变量 全局变量 xff0c 在定义位置