前言
今天我们主要讲解关键字-static
在深入了解static之前,我们先讲一些补充内容
补充-认识多文件和extern关键字
1.头文件(.h)
为什么要有头文件呢?
我们来举个例子
当我们新建一个源文件test.c,和一个main.c,分别写一下以下内容。
如上述代码,我们在main.c里调用test.c里的show函数,并且没有进行声明之类的操作,
那么究竟能不能成功运行呢?
感兴趣的小伙伴可以去试验一下,答案是可以运行的,只是会有几个未定义的警告。
我们继续往下。
我们给test.c函数里在写入一个全局变量 g_val,然后再main.c中用printf打印。
像这样
看起来是完全无法运行的,毕竟已经有红色的下划线了。
让我们来引入一个关键字–extern
我们只需要给g_val 加上extern关键字,上述代码就没有问题了。
extern是变量的声明关键字,我们这里先简单的提一下这个关键字,后期会有专门的extern深度剖析。
当然这里需要注意一下,我们再main.c中用extern 修饰g_val时,千万不能有下述写法
extern int g_val = 100;
这样是不行的,extern是指变量的声明,并没有开辟空间,就无法赋值。
总结一下:所有变量声明的时候,不能设置初始值!!
回到我们的主题?为什么要有头文件呢?
当我们只使用源文件组成一个复杂项目的时候,我们的维护成本会变得很高,因为我们要不断的相互声明变量。我给你要声明一下,给其他的源文件也要声明一下。至此,为了解决这个问题,我们有了头文件。
头文件(.h)–组织项目结构的时候,减少大型项目的维护成本。(举个例子,当我们要改变g_val的名字比如改为g_val1时候,那需要改成百个extern的名字)
既然有了头文件,那么他一定会被多个源文件包含,那么就有可能出现一个问题,头文件被重复包含。这样尽管不会直接报错,但是会出现重复拷贝,导致编译效率降低,我们再这里先暂时给出一个解决方案,具体的原理我们日后再谈。
解决方案:
在文件写入 #pragma once
这样就可以了。
头文件一般都包含哪些内容呢?
- C头文件
- 所有的变量的声明
- 所有的函数的声明
- #define,类型typedef,struct
。。。
让我们来看看我们的头文件
加入c的头文件还有我们的声明,这个程序就已经完美了。
当然,如果我们声明变量和函数的时候,不加extern也不会有问题,但是这里要强调:
变量的声明必须加上extern,因为int g_val这种写法即使声明又是定义(我们和编译器都会傻傻分不清)
函数声明的时候可以加extern,也可以不加。当然这里规范一下还是加上吧。
讲到这里问大家几个问题吧?
1.全局变量可以跨文件吗?
2.函数可以跨文件吗?
当然都是可以的。前提是没有经过特殊的修饰。
那我又产生了问题,在具体的场景中如何限制变量的跨文件访问呢?
一、 最名不副实的关键字–static
直接上结论了,不在赘述了,不想写代码和画图了,大家看好了。
我们还得写一个最经典的代码
如果不加static,那效果是什么样大家都懂吧,就是每一次调用fun函数,函数栈帧里的局部变量都在不停的开辟释放开辟释放开辟释放。
给局部变量加了static,它的生命周期从以前的局部代码块内(存储在栈),变成了全局生命周期(存储在全局数据区)了,但是它的作用域是不变的。
static 修饰全局变量
- static修饰全局变量,该变量只能在本文件中被访问,不能被外部文件直接访问。
- static修饰函数,该函数只能在本文件内被访问,不能再外部其他文件直接访问。
- static修饰局部变量,更改局部变量的生命周期。
这里我们需要做几点说明
对于1和2:
这两点特性直接让我们可以用static 项目维护并且提供了安全保证。我们用c语言可以做到小小的类似封装的效果。
具体怎么做呢?
我们可以给一个源文件中写入一个static全局变量,用一个static函数调用这个全局变量,最后我们可以写一个非static函数来调用static函数,这样一来,就没人能看到函数的功能实现,也无法修改变量的值了,他们只能通过非static来使用这个函数。哈哈哈细吧。
像这样,给别人用show_helper()就好了。
对于第三点说明,我们引入一个概念—C程序地址空间(此处我们暂且称之为此,此概念是操作系统的概念,我们日后必谈)
这可不是内存哦,虽然他们很像拉,人家的正名叫做操作系统进程地址空间。
二、基本的数据类型(内置类型)
我们前面已经说过了,定义变量的本质是:在内存中开辟一块空间,用来保存数据。
而定义一个变量,是需要按类型的,类型决定空间开辟的大小,无需多说。
我们继续来讨论一下,为什么要根据类型,开辟一块空间?直接将内存整体使用不好吗?
答案当然是不好,道理其实很简单,任何时刻,都不是你一个程序在运行,还有许多其他程序也在运行,你整块用了,让别人怎么办啊?你整块用不完的话那不是浪费。
既然上面提到了sizeof那我们也来理解一下sizeof
sizeof是一个关键字它并不是函数虽然他有括号。
sizeof可以求内置类型的大小,他也能hold住自定义类型。
来看看一个有趣的
sizeof(int)*p
这是什么?
哈哈他其实就是 4 * p,和指针没有什么关系的!!