本文介绍C语言的三个很重要的概念:
先介绍一个概念:翻译单元
C语言中有两种文件,头文件.h
,代码文件.c
翻译单元指的是包含头文件,并且将头文件展开以后的代码文件.c,而每个翻译单元都有一个文件作用域
,实际上,他是编译器编译的完整对象单元
变量的作用域其实是一个范围,只有在这个范围以内变量才是可用的,作用域是可以嵌套的,变量使用的永远是最内层声明或者定义
的那个。注意,声明的变量也算,看下面的例子:
#include
int main(void)
{
float PI = 2.14;
printf("PI = %f\n",PI);
for(int i = 0;i<1;i++)
{
extern float PI;
printf("PI = %f\n",PI);
}
return 0;
}
这个程序运行的结果如下:
PI = 2.140000
PI = 3.141593
我在另外一个calc.c文件中定义了一个PI值,所以第二次打印的时候这个extern表示这是一个声明,他的定义在另外一个翻译单元中。
#include "calc.h"
const float PI=3.1415926;
一对大括号括起来就形成了一个块作用域
在一个块作用域内声明的变量的可用范围是从这个块中变量定义处
到包含该定义块的结尾,块作用域需要注意几个问题:
函数的参数虽然在块的外面,但是他也属于函数所在的块
。可以将这些情况理解为两个块
,比如for语句,整个for语句看成是一个块,for后面的循环体执行逻辑作为这个块的子块,这样很多问题的比较容易理解了,看下面的例子:#include
int main(void)
{
for(int i = 0;i<10;i++)
{
printf("%d|",i);
int i = 6;
printf("%d, ",i);
}
printf("\n");
return 0;
}
输出结果:
0|6, 1|6, 2|6, 3|6, 4|6, 5|6, 6|6, 7|6, 8|6, 9|6,
在第一次执行循环体的时候,定义了一个i = 6,但是再一次回到for,执行i++的时候依然是用的在for语句定义的i,我们可以把for语句这样写一下:
{//第一层块
int i = 0;
执行for循环
{//第二层块
printf("%d|",i);
int i = 6;
printf("%d, ",i);
}
}
这样每次执行for循环的时候内部定义的i就失效了,使用的还是外部定义的i变量
函数作用域指的是即使在函数的内层块中,也会自动提升作用域到整个函数
,这种作用域只用于goto的标签
(是不是很没有用!!),也就是即使goto的标签定义在一个函数内层块中,也不能在另一个块中定义相同的标签,因为标签的作用域是整个函数,看下面例子:
#include
int main()
{
goto b;
{
b:
printf("bbb");
}
return 0;
}
运行会打印字符串bbb,虽然标签b定义在一个函数内层块中,但是他具有函数作用域
。
否则,我们可以在一个函数的另一个块中
定义一个同名的标签跳转,这样我们如果使用goto语句的话就太混乱了。所以C语言才有函数作用域这么个东西
函数原型作用域针对函数的声明中形参起作用
,形参的作用域从定义开始到函数原型定义结束,一般这种情况是针对于函数声明中有可变数组的情况下,比如下面的声明:
void reset(int m,int n,int arr[m][n],int val);
如果一个变量定义在函数外面,就说该变量具有文件作用域,定义在文件中的函数也具有文件作用域,也就是文件中所有函数都可以直接使用
,根据变量或函数定义是否添加static关键字
,可以分为具有内部链接的文件作用域和具有外部链接的文件作用域
内部链接的文件作用域
:只有当前文件内可以使用外部链接的文件作用域
:外部文件也可以使用,不过外部文件使用时需要使用extern进行声明
,这样编译器编译的时候就会在符号表中为该变量或者函数添加符号,链接器在链接的时候就会根据符号去别的文件中寻找定义。变量的生命周期就是变量在程序运行过程中可用的时间段,主要有四种:
静态变量也叫全局变量,是指在程序运行过程中一直存在的变量
,所谓静态变量,就是指变量不是存储在运行栈或者运行堆上,而是放在固定的内存位置,比如.data数据块
等地方。C语言有如下几种静态变量:
static关键字定义的变量
,该变量虽然作用域只限于当前定义的函数内部,但是在整个程序运行期间都存在,并且我们通过指针使用该变量,看下面例子:#include
int* get();
int main(void)
{
int c = *get();
printf("%d\n",c);
return 0;
}
int* get()
{
static int a = 10;
return &a;
}
运行结果
10
线程变量是通过使用关键字_Thread_local声明的变量,每个线程都会获取变量的一个单独的备份,看下面的例子:
#include
#include
_Thread_local int threadVar = 10;
void threadHandle();
int main(void)
{
int c = 0;
for(int i = 0;i<6;i++)
{
pthread_t threadNo;
if((c = pthread_create(&threadNo, NULL, threadHandle, NULL)) != 0)
{
printf("thread create failed. errno:%d",c);
continue;
}
}
return 0;
}
void threadHandle()
{
threadVar++;
printf("threadHandle::%d\n",threadVar);
pthread_exit(0);
}
输出
threadHandle::11
threadHandle::11
threadHandle::11
threadHandle::11
threadHandle::11
局部变量是指在块内部定义的变量,该变量或者在函数执行期间通过寄存器保存,或者保存在程序栈上
。该变量在块结束后就失效。
注意:自动变量不会自动初始化
,所以一定要初始化自动变量,不然值是不确定的
动态变量是指变量的定义和内存分配是通过用户控制,在程序运行期间动态执行的
,动态变量一般分配在程序堆上,通过制定的函数接口来分配和释放
使用动态函数需要引用头文件
#include
该函数接受一个参数:所需的内存字节数,然后去找合适的空闲内存块,返回内存的地址。
void *malloc(size_t __size)
注意:
这个函数分配的内存是未初始化的
如果分配内存失败,返回NULL
该函数接受一个参数,就是前面malloc返回的内存指针,该函数的作用是释放分配的内存
。
可以看出动态内存非常灵活,比如我们可以在程序运行过程中动态创建数组,链表等数据结构,但是动态分配的内存在程序运行期间会一直增加,除非你用free函数释放内存。并且,有些操作系统,即使应用程序结束,动态分配的内存也不会释放,所以,使用动态内存需要注意一下几点:
calloc和malloc一样,也是用于动态分配内存,但是calloc是按照存储单元
分配内存,该函数接受两个参数,第一个参数是所需的存储单元的数量,第二个参数是存储单元占用的字节数。比如下面的例子:
long* newmem = (long*)calloc(100,sizeof(long));
这种分配方式有个好处就是即使一个系统上long类型占用的字节数不是四个字节,该函数分配的内存依然够用。
free函数也可用于释放calloc分配的内存
。
之所以要有链接的概念,是因为我们为了编写大型应用程序,需要将程序进行模块化,分成一个个的翻译单元,比如我们创建一个游戏引擎
的程序,需要
寻找在外部定义的变量或者函数的地址
C语言中能被外部函数或者内存函数使用的变量或者函数只有两种,前面讲过,就是具有文件作用域的变量或函数:
外部使用时必须使用extern进行声明
,我们通常把一个翻译单元的外部链接变量或者函数使用extern在头文件中集中进行声明,别的翻译单元只需要引用该头文件即可使用