您的位置:首页 > 其它

变量的声明、定义、extern、static总结

2017-07-13 22:06 441 查看

1. 变量的声明和定义

变量定义:

所谓定义就是编译器创建一个对象,并且为变量分配一块存储空间,并给它取上一个名字,这个名字就是我们经常所说的变量名或对象名。在程序中对象有且仅有一个定义。例如int a 在声明的时候已经建立了存储空间。

变量声明:声明有两重含义:

第一重含义:告诉编译器,这个名字已经匹配到一块内存上了,下面的代表用到该变量或对象是在别的地方定义的,声明可以出现多次。

第二重含义:告诉编译器,这个名字我先预定了,别的地方再也不能用它作为变量名或对象。

定义与声明最重要的区别是:

定义创建了对象并为这个对象分配了内存,声明没有分配内存;例如extern int a其中变量a是在别的文件中定义的。

思考一个问题:为什么会出现对象的声明?

声明的最终目的是为了提前使用,即在定义之前使用,如果不需要提前使用,就没有单独声明的必要了。

看一个例子:

void a();  //告诉编译器,存在一个a的函数,至于在哪定义,你自己去找
void b()
{
a(); //虽然不知道a长啥样,但是反正它声明过了,知道确实存在这么个函数,那就继续编译下去
}
void a()  //这里是定义
{
printf( “a”);
}


再看一个例子:有两个文件a.c b.c,他俩分别编译,最后链接在一起。

/*文件名:a.c
*/
extern int i;       //告诉编译器,有个整数i存在
void a();           //告诉编译器有个函数a存在
void b()
{
a();        //既然告诉编译器a存在了,那就继续编译下去
i++;      //既然告诉编译器i存在了,那就继续编译下去
}


/*文件名:b.c
*/
int i;       //补全i的定义,否则最后链接时,i被声明却不定义,链接器将报错
void a()    //补全a的定义
{
printf( “a”);
}


2. 关键字:extern

extern用于声明变量或者函数是一个外部变量,也就是告诉编译器这个变量或者函数是在别的文件中定义的,编译的时候不要报错,在链接的时候按照字符串寻址可以找到这个变量或者函数。

2.1 extern修饰变量的声明

extern将变量的能见度延伸到了整个程序,我们知道变量声明和定义的地方,可以在整个程序的任何地方使用他们。

/*文件名:a.c
*/
#include <stdio.h>
extern int a;               //外部变量提前声明
extern int exe(int x);  //外部函数提前声明
int main(int argc, char *agrv[])
{
printf("%dn",exe(a));  //这里可以使用变量a的原因是前面声明了a,链接器最后在b.c中找到了a变量
return 0;
}


/*文件名:b.c
*/
#include <stdio.h>
int BASE=2;     //变量定义
int a=10;
int exe(<
d99e
span class="hljs-keyword">int x)
{
int i;
int ret=1;
for(i=0;i<x;i++)
{
ret*=BASE;
}
return ret;
}


2.2 extern修饰函数的声明

默认情况下,声明和定义函数,都有一个extern的前缀,这意味着声明和定义函数时,前面不写extern,它也是默认存在的,例如下面的代码。

int fun(int arg1, char agr2);


声明的函数前面没有extern,但编译器会在前面加上关键字extern,表示此函数时外部函数,可供其他文件调用,如下.

extern int fun(int arg1, char agr2);


举一个例子:

/*文件名:a.c
/*在该文件中定义一个函数fun,如果想让该函数被其他文件访问,那么该函数必须是外部函数,完整的定义是加extern前缀
*/
#include <stdio.h>
extern void fun(){ /*不过该extern完全可以省略,因为默认情况下,所有函数就是外部函数*/
printf("调用了fun函数");
}


/*文件名:b.c
/*该函数调用a.c中的fun函数
*/
#include <stdio.h>
void foo()
{
fun();
}


这个做法肯定不行,因为foo函数根本不知道fun函数的存在,那该怎么调用呢?看下面这个方法行不行。

/*文件名:c.c
/*该函数调用a.c中的fun函数
*/
#include <stdio.h>
#include "a.c"
void foo()
{
fun();
}


c.c中的做法是包含fun函数所在的源文件a.c,我们知道#include的作用纯粹就是内容的拷贝,所有又相当于

#include <stdio.h>
extern fun(){
printf("调用了fun函数");
}
void foo()
{
fun();
}


这么一看好像是对的,在foo函数前面定义了一个fun函数,然后在foo函数中调用这个fun函数。从语法上看是对的,所以编译是通过的,但是这个程序不可能运行,因为在链接的时候会报错,因为我们已经在a.c中定义了fun函数,现在又在b.c中定义fun函数,C语言规定不允许有同名的外部函数,链接的时候linker会发现在a.o和b.o中定义了同一个函数,会直接报错。

但是我们的思路是想告诉foo函数知道fun函数的存在,正确的做法是在foo函数前面对fun函数进行提前声明(注意不是定义)。

#include <stdio.h>
extern void fun();     //当你要把其他源文件中定义的外部函数拿过来用,完整的做法应用先用extern关键字,表示链接的时候,链接器要去其他源文件寻找该文件的定义
void foo()
{
fun();
}


再举一个例子:

在 b.h 文件中声明好 b.c 的函数,使用时,只需要在 a.c 中包含 #include “b.h” 头文件即可,这样就可以使用 b.c 的接口函数了,在实际工程中,通常也是采用这种方式, .c 文件中实现函数,.h文件中声明函数接口,需要调用.c文件的函数接口时,只需包含.h文件即可。

//a.c
include <stdio.h>
#include "b.h"

int main(int argc, char *argv[])
{
int x = 10, y = 5;
printf("x = 10, y = 5\n");
printf("x + y = %d\n", add(x, y));
printf("x - y = %d\n", sub(x, y));
printf("x * y = %d\n", mult(x, y));
printf("x / y = %d\n", div(x, y));
return 0;
}


//b.h
#ifndef __F_H
#define __F_H
extern int add(int, int);
extern int sub(int, int);
extern int mult(int, int);
extern int div(int, int);
#endif


//b.c
#include <stdio.h>

int add(int x, int y)
{
return (x + y);
}

int sub(int x, int y)
{
return x - y;
}

int mult(int x, int y)
{
return x * y;
}

int div(int x, int y)
{
if (y != 0) {
return (x / y);
}

printf("mult()fail second para can not be zero!\n");
return -1;
}


3. 关键字:static

3.1 变量类型

先来介绍一些C语言的变量类型。

C语言中对变量的说明包括两方面的内容:变量类型以及变量的存储类型。变量类型如:int(整形),char(字符型)是用来说明变量所占用的内存空间的大小。变量存储类型用来说明变量的作用范围。

C语言的变量存储类型有:自动类、寄存器类、静态类和外部类。

局部变量

是指在函数内部说明的变量(有时也称为自动变量),用关键字auto进行说明。 所有的非全局变量都被认为是局部变量,所以auto实际上从来不用。局部变量在函数调用时自动产生,但不会自动初始化, 随函数调用的结束,这个变量也就自动消失了,下次调用此函数时再自动产生,还要重新赋值,退出时又自动消失。

静态变量

是用关键字static声明。根据变量的类型可以分为静态局部变量和静态全程变量。

(1)静态局部变量

它与局部变量的区别在于:在函数退出时,这个变量始终存在,但不能被其它函数使用,当再次进入该函数时,将保存上次的结果。其它与局部变量一样。

(2)静态全局变量

静态全局变量就是指只在定义它的源文件中可见而在其它源文件中不可见的变量。它与全局变量的区别是:全局变量可以再说明为外部变量(extern),被其它源文件使用,而静态全局变量却不能再被说明为外部的,即只能被所在的源文件使用。

(3)静态变量生命周期

静态变量在程序运行之前创建,在程序的整个运行期间始终存在,直到程序结束。

外部变量

是用关键字extern声明。为了使变量除了在定义它的源文件中可以使用外,还可以被其它文件使用,就要将全程变量通知每一个程序模块文件,此时可用 extern来说明。

寄存器变量

通常在比较注重在执行速度的情况下使用。其思想是告诉编译程序把该变量放在某个CPU寄存器中。因为数据在寄存器中操作比在内存中快,这样就提高了程序代码的执行速度。寄存器变量的说明是在变量名及类型之前加上关键字register。值得注意的是取地址运算符&不能作用于寄存器变量。

3.2 static修饰变量

static的第一个作用是:修饰变量。变量分又分局部变量和全局变量,但它们都存在内存的静态区。

静态全局变量,作用域仅限于变量被定义的文件中,其他文件即使用extern声明也没法使用他。准确的说作用域是从定义除开始,到文件结尾处结束,在定义处前面的那些代码行均不能使用它。

/*奇怪的是linux里面编译不通过,在vs2017编译通过*/
#include <stdio.h>
extern int i;
void a()
{
i = 10;
printf("%d\n", i);

}
static int i;
int main()
{
int j = i;
a();
i = 5;
a();
return 0;

}


静态局部变量,在函数体里面定义的,就只能在这个函数用了,同一个源程序中的其他函数也用不了。由于被static修饰的变量总是存在内存的静态区,所以即使这个函数运行结束,这个静态变量的值还是不会被销毁,函数下次使用时仍然能用到这个值。

static int j;   //定义一个静态局部变量j
void fun1()
{
static int i = 0; //定义一个静态局部变量i
i++;
}
void fun2()
{
j = 0;
j++;
}
int main()
{
for( k = 0; k < 10; k++)
{
fun1();
fun2();
}
return 0;
}


3.3 static修饰函数

static的第二个作用是:修饰函数。函数前面加static使得函数称为静态函数,区别于非静态函数(外部函数)。但此处“static”的含义不是指存储方式,而是指函数的作用域仅局限于本文将(所以又称为内部函数)。使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其他文件中的函数同名。

举一个例子

/*文件名:one.c
*其中定义了一个内部函数one
*/
#include <stdio.h>
static void one()
{
}


/*文件名:one.h
*其中声明了函数one
*/
#ifndef __one_h
#define __one_h

void one();
#endif


/*文件名:main.c
*其中调用了one函数
*/
#inclue <stdio.h>
#include "one.h"

int main()
{
one();
return 0;
}


编译如下:

||=== Build: Debug in 111 (compiler: GNU GCC Compiler) ===|
C:\Users\lrhu\Documents\C\111\one.c|3|warning: 'one' defined but not used [-Wunused-function]|
obj\Debug\main.o||In function `main':|
C:\Users\lrhu\Documents\C\111\main.c|6|undefined reference to `one'|
||error: ld returned 1 exit status|
||=== Build failed: 2 error(s), 1 warning(s) (0 minute(s), 0 second(s)) ===|


这里报了两个错误,第一个是undefined refernece to ‘one’意思是one这个标识符没有被定义,也就是找不到one,第2个错误表明是链接时出现错误了。

但这个程序是可以编译成功的,因为我们在main函数之前声明了one函数(#include “one.h”),这个函数可以理解为:在语法上骗一下main函数,告诉它one函数是存在的,所以从语法角度main函数是可以调用one函数的。究竟这one函数存不存在,有没有被定义呢?编译器是不管的,编译器只会检测单个文件的语法合不合理,并不检测函数有没有被定义,只有在链接的时候才会检测这个函数存不存在,也就是有没有被定义。

static声明内部函数的例子

#include <stdio.h>
static void fun();   //声明函数fun为内部函数
int main(int argc, int *argv[])
{
fun();
return 0;
}

static void fun()
{
printf("调用了内部函数fun");
}


3.4 编译与链接

所谓编译:就是单独检查每个源文件的语法是不是合理,并不会检查每个源文件之间的关联关系,一个源文件编译成功就生成一个目标文件,在linux里面是.o,在windows里面是.obj。

所谓链接:就是检查目标文件的关联关系,将相关联的目标文件组合在一起,生成可执行文件。

4. 参考

http://www.cnblogs.com/longyi1234/archive/2010/04/09/1708565.html

http://segmentfault.com/a/1190000000715515

《c语言深度解剖第二版》
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: