全局变量和局部变量

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

  • 2
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值