今天,我们介绍两个关键字,extern和sizeof
我们先提出一个问题?
假如我们创建两个源文件,一个叫做test.c,另一个叫做main.c,那么我们在test.c中定义的函数show()是否可以在main函数中进行调用?
答案是可以的,如下图
这个是我们创建的test.c文件,这里面的代码表示我们现在test.c文件夹中定义函数,然后我们在main.c文件中进行调用
我们生成解决方案:
可以看到有些警告提醒我们的函数show未定义,我们进行运行试试
可以发现,函数成功的调用了,所以得出结论,不同的源文件,在一个文件中定义的函数,在另一个文件中可以调用(虽然有警告)
这时候我们有个疑问:对于变量来说是否同样适用呢?
答案是错误的,如图所示
在test函数中,我们这样新创建一个变量g_val,初始化值为100,我们在main函数中打印这个变量,看看结果会是如何?
可以发现,在main函数中,很明显地出现下划线错误提示,表示我们的g_val未定义
得出结论:在一个源文件中定义的变量,在另一个源文件中是无法使用这个变量的
那么有没有方法能够使用这个呢?
可以,我们这个时候引入extern函数
extern表示声明整型 g_val,我们进行调用
我们可以发现,成功打印了变量。
那这个写法行吗?extern int g_val=100;
我们进行运行,结果如图
为什么不行呢?我们进行解释
答:因为=号就相当于初始化或赋值,初始化或者赋值都会创建空间,但是我们的extern是申明,所以并不会创建空间,所以不能有等号
总结:所有的变量在初始化的时候,不能有初始值!
假如我们有很多源文件,这些源文件都需要调用我们在test.c中创建的变量和函数,那是不是我们要在每一个源文件中都需要进行申明,这是不是太复杂了
总结:单纯的使用源文件,组织项目结构的时候,项目越大越复杂的时候,维护所需要的成本就越高
有没有一种方法能够单独创建一个文件,想要调用某个变量或者函数,在文件中进行申明即可,这时候我们就引入了头文件
头文件:组织项目结构的时候,减少大型项目的维护成本问题.
源文件中全部是定义,头文件中全部是声明
大部分头文件都是被多个源文件所包含
可能会产生一个问题:头文件被重复包含的问题
如何解决这个问题呢?
答:在代码最前面加上#pragma once
头文件中都可以包含哪些代码呢?
答:1:c头文件
2:所有的变量的声明
3:所有的函数的声明
4:#define宏定义 typedef 结构体
例如,我们首先在头文件中写出我们所要写出的代码
然后我们在源文件中进行声明,
注意,引用头文件时需要用#include"xxx"的形式
如何正确的使用头文件呢?
答:在test.c源文件中,我们一般写的代码是变量的定义和函数的定义,如图所示
int g_val = 100;
void show()
{
printf("hello,show\n");
return 0;
}
在test.h头文件中,我们一般写的代码是声明还有一些必要的头文件,为了避免头文件被重复包含的问题,我们还要在头文件的最前面加上#pragma once,代码如图所示
#define _CRT_SECURE_NO_WARNINGS
#pragma once
#include<stdio.h>
#include<Windows.h>
extern int g_val;
在main.c源文件中,我们应该首先声明我们自己创建的头文件test.h,例如#include"test.h",注意要用引号,我们在main.c的源文件的主体则是我们所构建的main函数,例如打印变量和调用函数,如图所示
#include"test.h"
int main()
{
printf("%d\n", g_val = 100);
show();
system("pause");
return 0;
}
我们运行代码,结果如图所示
但我们的解决方案中是有报警告的
说我们的show函数未定义,
但是我们依旧成功运行了代码,原因是什么?
答: 首先,我们的代码进行编译,这时候,我们的两个源文件之间是独立的,所以我们的show函数是未定义的,程序进行报警,但是在我们链接的过程中,多个文件整合成一个文件,这时候我们的程序如果能找到show函数的定义式,那么报警作用也就自动消除了
这里我们得到一个结论:在不同的源文件中,变量是必须要声明的,函数是可以不声明的,但是函数不声明的情况下在部分编译器下容易报错,要想不产生报错信息,函数的声明是必需的,如何进行函数的声明呢?
答:函数的声明结构包括返回值类型,函数名,形参列表,要注意是没有函数体的,原因是:函数体其实和变量的初始化或赋值类似,变量的初始化或者赋值,会开辟空间,存储数据,而函数体也会开辟空间,存储的是代码,这个代码通常情况下是不会被写入的。因为不管是变量的声明,还是函数的声明,其结果都不会开辟空间,所以我们不可以带上函数体。理论是只要是声明,我们都可以把我们的extern带上,所以函数的声明结构如图所示
extern void show();
总结:问题1,头文件的意义是什么?
答:为了便于我们的项目组织和维护项目
问题2:头文件的组成结构是什么?
答:首先,#pragma once,为了防止头文件被重复包含
2:main函数中对应的库函数的头文件,例如#include<stdio.h>
3:我们要在源文件中使用的变量和函数的声明
问题3:函数的声明和变量的声明是否都需要在前面加上extern
答:变量的声明必须加上extern,函数的声明建议加上extern
假如我们都不加extern,
我们进行运行,可以发现程序正常运行
并且解决方案中没有警告
那是不是就说明函数和变量的声明都可以不加extern,答案是错误的,原因如下
答:int g_val;这串代码是参数的声明还是参数的定义?说声明的话也很对,说定义的话,只不过没有初始化,可以发现,这种情况容易混淆,所以我们变量的声明必须加上extern
函数的声明可加可不加,原因是函数的声明和定义的区别主要是函数体的有无决定的,函数的定义有函数体,函数的声明没有函数体
函数的声明和定义以及调用要尽量保持一致
接下来,我们两个问题
问题1:全局变量可以跨文件访问吗?
答:可以
问题2:函数可以跨文件访问吗?
答:可以
在具体的应用场景,有没有可能我们不想让全局变量或者函数跨文件访问,而只想在本文件内部被访问
例如
我们在test.c的变量创建的前面加上static,我们进行运行
可以发现,出现错误,并且这里的错误是链接型错误,证明我们在链接的过程中没有找到对应的g_val变量
结论1:static修饰的全局变量,只能在本文件内部被访问,不能被外部文件直接访问
当我们用static修饰函数时
我们进行运行
可以发现,依旧产生错误
结论2:static修饰的函数,只能在本文件内部被访问,不能被外部文件直接访问
static限制全局变量,限制的是全局变量的作用域
当static修饰局部变量的时候,我们该如何思考呢?
答:我们先写这样一串代码
void fun()
{
int i = 0;
i++;
printf("i=%d\n", i);
}
int main()
{
for (int i = 0; i < 10; i++)
{
fun();
}
return 0;
}
这串代码打印的结果是什么呢?
答:
为什么是10个1呢,因为我们的i变量是局部变量,局部变量出函数会自动释放,所以就一直打印1
我们用static修饰i呢?
void fun()
{
static int i = 0;
i++;
printf("i=%d\n", i);
}
int main()
{
for (int i = 0; i < 10; i++)
{
fun();
}
return 0;
}
我们进行运行
可以发现,结果是1-10,原因可能是局部变量i出函数并没有被销毁,我们进行验证
int*p = NULL;
void fun()
{
static int a = 100;
p = &a;
}
int main()
{
fun();
printf("%d\n", *p);
return 0;
}
我们首先创建一个指针,这个指针是一个全局变量,我们用p接受被static修饰的局部变量a的地址我们在main函数中调用fun函数,正常情况下,我们的局部变量a出函数后就会直接释放,那么我们的*p访问的应该是乱码,我们进行运行
我们可以发现,结果为100,所以局部变量并没有被释放
总结:static修饰局部变量时,更改的是局部变量的生命周期,如何改变的呢?把局部变量的生命周期改成全局变量的生命周期
那是不是static修饰局部变量直接把局部变量变成全局变量呢?
答:并不是,例如
void fun()
{
static int a = 100;
}
int main()
{
printf("%d", a);
return 0;
}
如果a被更改为全局变量的话,打印的结果为100,我们进行运行发现
代码直接报错,并表示
a是未定义的标识符
所以总结:static修饰局部变量,能够更改局部变量的生命周期,而不能更改局部变量的作用域
其中,局部变量是存放在栈区的,栈区的特点是具有临时性,遵循后进先出的规则
static修饰的局部变量是存放在全局区和静态数据区的,特点是在整个进程的运行的生命周期中,局部变量始终是有效的
void fun()
{
static int i = 0;
i++;
printf("i=%d\n", i);
}
这里的i只会初始化一次,也就是第一次