局部变量、全局变量、局部函数、全局函数、static、extern-------全解

本文详细介绍了C/C++编程中的变量作用域,包括全局变量、局部变量、静态全局变量和静态局部变量的特性及使用场景。同时,探讨了全局函数和内部函数(静态函数)的区别,以及extern和static关键字在扩展作用域和限制作用域中的作用。extern用于在不同文件间共享变量和函数,而static则限制变量和函数在本文件内可见。此外,还讨论了头文件引用和extern声明的注意事项,以及如何避免命名冲突。

局部变量、全局变量、局部函数、全局函数、static、extern

在这里插入图片描述

可见域和作用域

在这里插入图片描述

作用域是这样一个区域,标识符在程序的这个区域内是有效的。C++的作用域主要有四种:函数原型作用域、块作用域、类作用域和文件作用域。
标识符的可见性是指在程序的某个地方是否是有效的,是否能够被引用被访问。程序运行到某一处时,能够访问的标识符就是在此处可见的标识符。

变量的声明与定义:

  • Int a : 是定义型声明,既是声明又是定义,需要分配存储空间
  • Extern a : 是引用型声明,只是声明,不需要分配存储空间

1、全局变量与局部变量

全局变量:

  • 编译时分配内存,放在静态存储区里,习惯:首字母大写;。
  • 可以使用extern扩展全局变量的作用域,如extern a;,可以在外部文件中扩展,也可以在同一文件中扩展(如函数B在调用的时候使用了a,但a还没定义)
全局变量具有全局作用域。全局变量只需在一个源文件中定义,就可以作用于所有的源文件。当然,其他不包含全局变量定义的源文件需要用extern 关键字再次声明这个全局变量。
全局变量具有全局作用域。全局变量只需在一个源文件中定义,就可以作用于所有的源文件。当然,其他不包含全局变量定义的源文件需要用extern 关键字再次声明这个全局变量。

extern 是用来扩展作用域的

(当编译的时候,遇到extern时,会先从本文件中查找全局变量的定义,如果找到,就在本文件中扩展作用域;如果没有找到,就在链接的时候,从外部文件中查找全局变量的定义,如果找到,就将作用域扩展到本文件,否则报错)

  • 静态的全局变量:全局变量使用static修饰,仅限于本文件中使用,也就是说加了static那么作用域就被限定在文件里面
静态全局变量也具有全局作用域,它与全局变量的区别在于如果程序包含多个文件的话,它作用于定义它的文件里,不能作用到其它文件里,即被static关键字修饰过的变量具有文件作用域。这样即使两个不同的源文件都定义了相同名字的静态全局变量,它们也是不同的变量
认真看
static和extern不能一起用。extern修饰全局变量和函数,被修饰的变量和函数可以在别的文件里使用。而static修饰的变量和函数作用范围仅限于定义它的文件内部。 两者是冲突的。

局部变量:

  • 在需要的时候分配内存
  • 存储类别:auto(动态存储区)、static(静态存储区)、(register(在CPU的寄存器里,更快))auto类型,在函数执行区间存在,执行结束,变量被撤销内存收回
  • 静态局部变量:static int a,虽然调用结束后仍然存在,但其他函数不能引用,因为它是局部变量。
静态局部变量具有局部作用域,它只被初始化一次,自从第一次被初始化直到程序运行结束都一直存在,它和全局变量的区别在于全局变量对所有的函数都是可见的,而静态局部变量只对定义自己的函数体始终可见

2、全局函数与内部函数

  • 函数默认是全局的,可以被其他文件中的函数调用,外部需要引用该函数的时候,需要使用函数的原型声明一下这就是include头文件的作用,如extern int f(int a);,

    函数原型通知系统,该函数在本文件中稍后定义,或在其他文件中定义。

  • 可以被外部文件引用,也可在在本文件中内其他函数引用(如果用户自定义的函数在主调函数的后面,应该再主调函数中对被调函数做声明。也可进行外部的声明(在文件的开头声明))

  • 内部函数(静态函数):只能在本文件中调用,Static void f(int a)

它只能在声明它的文件当中可见,不能被其它文件使用。

链接过程解释

比如说,在一个工程的common.h中定义了一个全局变量 int test;那么在整个工程的作用范围内,该变量都是存在的,在编译的时候会将其保存在整个工程全局的变量表中,文件(.h.cpp)只要使用声明extern int test;就可使用该变量,而不用包含该变量的头文件common.h,因为该变量的作用域是全局的。

和全局变量一样,大多数的非类成员函数基本上都定义为全局的因为函数默认为全局相当于加了extern。我们可以分两种情况讨论,第一种情况,void func();被声明在commom.h中,实现体在common.cpp中。这种情况和全局变量的情况一样,整个工程内的任何程序都可以通过extern voud func();来使用该函数,而不需要包含头文件common.h。当然了,还可以通过包含头文件的方式使用func();。工程内的任何文件都可以包含common.h而不会出现名字冲突问题,因为common.h中只包含了函数的声明而没有实现体,任何包含了common.h的文件因此也就只包含了函数的声明,因此编译是没有问题的。至于函数的实现体,会在链接的时候自动到对应的obj(由对应的cpp生成)文件中寻找

当把一个全局函数的声明和实现都放在common.h中时,如果有2个以上文件都包含了common.h,在编译的时候就无法通过,因为每包含一次common.h,就会在全局空间内保存一个func()实现体(最终会保存在obj固定的全局区域),如此一来,就会有多个func()实现体保存在全局空间中(分别保存在多个obj中,在链接的时候会整合成一个总的exe),从而导致命名冲突。所以,一般都会使用class来封装函数,可以避免很多的命名冲突问题。

还有一种方法可以避免全局函数的命名冲突,可以将函数的声明和实现都放在common.h中,但是要加上static声明,即static void func(){};加上static就表明该函数只在本文件(common.h)中可用,在包含该头文件的obj全局空间内不会保存func()的实现体,任何文件要想使用func(),只能通过包含头文件(或者说是包含该函数的作用域)的方式来使用它,不能通过extern void func();来直接使用,因为此时的func()以不在整个工程全局空间的控制范围内。

因为static 限定文件作用域
static 限定文件作用域

还有一种情况,如果将函数的声明放在头文件中,将函数的实现放在对应的cpp文件中,并且将实现体加上了static声明,那么此时的static没有太大作用,因为既然将函数体放在了cpp中,别人一般不会包含你的cpp文件,也就不存在命名冲突问题了,除非有几个文件非要包含你的cpp文件不可,那么此时的static就派上用场了

其实只要对编译的过程理解了,这些问题都很容易搞懂。下面简单讲一下编译的原理。

程序写好之后会先进行编译,在编译阶段,编译器会为每个cpp文件生产一个.obj文件该文件就保存了每个文件中的全局变量或者全局函数的标识符如果一个文件使用了另外一个文件中的全局函数并且没有包含其头文件,在编译的时候,编译器首先会寻找是否使用了extern声明,如果找到了,此时编译器就放心了,它知道这个函数是在别的地方定义了,在链接的时候会自动找到的,所以编译器就编译通过了。

编译完之后就会对每个.obj文件进行整合链接,最终生成可执行的exe文件。在链接阶段,此时的所有相关的.obj文件都被放到了整个工程的全局空间内,链接器会检查所有的.obj文件是否存在全局变量或者函数的命名冲突问题,如果多个obj文件都包含了某个全局函数的实现体,此时编译器就会报命名冲突错误,因为每个obj包含的不是函数的声明,而是结结实实的函数体,大家都知道,函数可以被声明多次,但是函数体只能定义一次,那么此时的函数体被每个obj都定义了一次,肯定会导致命名冲突问题。

所以加了static的静态函数编译的时候就放在.obj文件内,也就不会产生链接冲突

解决的办法上面也说了,只能将函数体移到cpp文件中,头文件中只包含函数的声明,由此只有该cpp文件对应的obj文件中存在函数体,那么链接的时候果断不会有问题了;或者将函数体声明为static,此时的每个obj文件中就不会有该函数的定义了,该函数在编译阶段已经被整合到obj代码中了,不会出现在obj的全局空间中,所以在链接的时候不会有问题。

链接器的作用就是将每个单独的obj文件按照代码中的依赖关系进行整合(其中就包括对全局空间变量或者函数进行整合),最终生成exe文件。

这里写图片描述

extern

extern可置于变量或者函数前,以表示变量或者函数的定义在别的文件中告诉编译器用的

提示编译器遇到此变量或函数时,在其它模块中寻找其定义

如果想声明一个变量而非定义它,就在变量名称前加关键字extern,而且不要显示地初始化变量。任何包含了显示初始化的声明即成为定义。我们能给由extern关键字标记的变量赋一个初始值,但是这么做也就抵消了extern的作用。extern语句如果包含初始值就不再是声明,而变成定义了。

某些时候有这样一种const变量,它的初始值不是一个常量表达式,但又确实有必要在文件间共享。这种情况下,我们不希望编译器为每个文件分别生成独立的变量。相反,我们想让这类const对象像其它(非常量)对象一样工作,也就是说,只在一个文件中定义const,而在其它多个文件中声明并使用它。解决的办法是,对于const变量不管是声明还是定义都添加extern关键字,这样只需定义一次就可以了。如果想在多个文件之间共享const对象,必须在变量的定义之前添加extern关键字。

参考链接

  • 一个c文件需要调用另一个c文件里的变量或者函数,而不能从.h文件中调用变量。
  • extern int a = 5与int a = 5意义是一样的,都是定义。而extern int a;是声明。但会产生一条警告。
  • 对于函数而言,和引用变量是一样的,如果需要调用其他.c文件中的函数,在文件中的函数声明前加extern即可,不加extern而直接声明函数也可以,因为声明全局函数默认前面带有extern
  • 如果不想让其他.c文件引用本文件中的变量,加上static即可。

建议最优用法

例如a.c文件中定义int a = 5和一个函数,在a.h里写extern int a;,如果要在其他文件里调用a这个变量和函数,直接#include<a.h>即可。如下图

1.一个c文件需要调用另一个c文件里的变量或者函数,而不能从.h文件中调用变量

只能引用另外一个.c文件里的变量或者函数,不能引用.h文件里的变量,当然,也非常不建议在.h文件里定义变量。因为在.h文件里定义变量,如果这个.h文件被多个.c文件包含则会报重复定义的错误,文章末尾详讲。

例1:在a.h文件中定义一个变量,在b.c文件中引用,会报错,未定义这个变量

因为.h里面的定义不会生成用到.obj文件中所以再链接过程中自然也就链接不到

例2:在a.c文件中定义一个变量,在b.c文件中引用,成功调用

2.extern int a = 5与int a = 5意义是一样的,都是定义。而extern int a;是声明。但会产生一条警告

例1:在a.h文件中使用extern int a = 5定义一个变量,在b.c文件中引用,会报warning,但程序可以运行

3.引用函数

引用变量是一样的,如果需要调用其他.c文件中的函数,在文件中的函数声明前加extern即可,不加extern而直接声明函数也可以,因为声明全局函数默认前面带有extern。见下面例子

例1:a.c中定义一个fun函数,b.c中要引用这个函数,b.c中的声明extern int fun();int fun();没有任何区别,如下两个图

所以include也是一样的,include就是拷贝了一份声明过去,且全局函数默认extern

4.如果不想让其他.c文件引用本文件中的变量,加上static即可

static 表示静态的变量,限制此变量作用域在一个源文件内,其他文件不能用extern来引用此变量,不仅适用于变量,函数也可以。如下图例子,b.c文件就不能引用a.c文件里的静态变量ckx

5.extern和include的区别

include相当于把include .h文件直接带入到本源文件里,比如在b.c文件里include "a.h",==就相当于把a.h文件里所有定义的变量和函数全部拷贝了一份放入了b.c里,==一个项目里,一个.h文件可能会被多个.c源文件包含,这样编译的时候就会报重复定义的错误。而且尽量不要在.h里定义变量,这是一个不好的习惯。

例子:在a.h里定义一个变量,a.cb.c都包含这个.h文件,编译的时候就会报错。

6.头文件被C++程序引用

extern "C"的主要作用就是为了能够正确实现C++代码调用其他C语言代码。加上extern "C"后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。

示例一个头文件写法

#ifndef __pub_h__
#define __pub_h__

#include "type.h"

#ifdef __cplusplus
extern "C"{
#endif

void fun(int p);

#ifdef __cplusplus
	}
#endif


#endif
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值