文章目录
- 前言
- 一、static?
- 1、static修饰全局变量
- 总结:
- 2、static修饰函数
- 总结:
- 3、static修饰局部变量
- 总结
- 二、const?
- Ⅰ、 const修饰普通变量
- Ⅱ、 const修饰指针变量
- Ⅲ、 const修饰函数
- 1、const修饰函数参数
- 2、const 修饰函数返回值
- Ⅳ、const修饰的变量与常量的区别
- 总结
- 三、volatile?
- 总结
前言
在一些面试过程中。面试官们总喜欢问一些基础的问题来考验面试者们,而其中C语言关键字也是面试官们,爱问的,其中以static、const、volatile尤为爱问,接下来博主将和大家一起讨论一下,这几个常问的关键字;
提示:以下是本篇文章正文内容,下面案例可供参考
一、static?
关键字“static”,译成中文就是“静态的”;在C语言中static可以用来修饰全局变量,也可以用来修饰函数,还有局部变量;接下来,我们分别来讨论一下,static分别修饰这三个东西有什么“妙用”;
1、static修饰全局变量
在C语言中,每一个全局变量和函数都是具有外部链接属性,
什么是外部链接属性呢?
就打一个比分来说,在同一个项目中,我们在另一个源文件中定义的全局变量或者函数,如果我们想在另一个源文件使用的话,我们只需声明一下就可以在本文件内使用了;
比如这样:
以上述为例:有时候呢我们又不想我们在test.c文件里面定义的全局变量和函数被其他源文件使用;怎么办呢?通过对C语言关键字的理解,我们可以在全局变量的前面加一个static关键字加以修饰;这样呢,我们就切断了在test.c文件里面定义的全局变量的外部链接属性;我们也就限定了在test.c文件里面定义的全局变量,只能在本文件内使用,而不能被其他源文件啊给使用;
相当于这样:
这时候我们再在其他源文件里使用i_val编译器就会直接报错了,并且表示自己没有找到一个叫i_val的东西;
总结:
static修饰全局变量,会改变全局变量的作用域;但是不会改变全局变量的生命周期;
2、static修饰函数
static修饰函数其实和static差不多的,都是切断函数的外部链接属性,限定函数的使用范围;
未使用static修饰之前:
使用static修饰过后:
如果这时候我们硬是想在其他源文件内使用并调用fun函数但是又不能取消static修饰,我们有没有办法解决这个问题?答案是肯定的;
既然正面不行的话,我们就从侧面来呗.😁😁😁
我们不是说用static修饰的函数没有了外部链接属性嘛,只能在在本文件内使用嘛,那好!!我就依你,我就在本函数内调用你!!不过这个调是在本文件内在定义一个新函数,在新函数内调用这个被static修饰的函数,就比如这样:
那么到这步,可能你已经明白了下一步怎么做了;
我们这个新定义的函数没有被static修饰啊!,也就是说这个新函数的外部链接属性还在,那我们直接在其他源文件内调用这个新函数不就行了,也就相当于我们调到了那个被static修饰的函数;虽然是间接调用但是我们达到了相同的效果;
效果演示图:
其实这从侧面可以看出static还有另外一个功能:封装函数;
当你写的代码的过程不想被别人看见时,但是又得把函数接口提供给别人使用时,这是static是个不错的选择;
总结:
static修饰函数是会切断函数的外部链接属性的;说人话就是,限定你的函数只能在本源文件内使用,不能被其它源文件文件调用;
3、static修饰局部变量
我们先来看一段代码:
void fun(void)
{static val = 1;val++;printf("%d\n",val);
}int main()
{for(int i=0;i<5;i++)fun();return 0;
}
屏幕上会输出什么呢?
是5个2吗?
来我们运行一下:
运行结果是:2 3 4 5 6
分析:
我们常规的理解,不是说val是个临时变量吗,在函数调用结束的时候,val所开辟的空间不是被释放了嘛,第二次调用的时候又会重新给val赋值,那么打印出来不就应该是2 2 2 2 2 嘛。首先,我们有这个疑惑呢,说明你函数那块还可以哈!但是我们这道题与常规题不一样嘛,在val之前多了个static嘛,那我们就不能忽视了static的作用;在static修饰局部变量时,我们的变量其实并不会常规的在栈区(或者说栈帧)开辟空间,它会在一个叫静态区的地方开辟空间,并把数据放在静态区内;
什么时静态区?
简单点来说就是存放全局变量,常量的地方;在这上面存着的数据生命周期都很长(与程序共“长寿”);
那么这里为什么会打印2 3 4 5 6不就号解释了;
我的val并没有在栈上开辟空间;而是在静态区;故函数调用结束的时候,并不会销毁val,val中的值也就不会被清除掉,第二次调用的时候,val也不会重新开辟新空间了,直接取上次留下的值既行了;
无static修饰:
使用static修饰:
总结
static修饰局部变量,改变了局部变量的生命周期,并未改变局部变量的作用域;
二、const?
const修饰的数据类型是指常类型,常类型的变量或对象的值是不能被更新的。
Ⅰ、 const修饰普通变量
很明显,被const修饰过的变量不能被直接改变;因为经过const修饰过的变量,只具有了可读性,没有了可写性,(或者说编译器不允许你修改);
既然正面不行,我们就来侧面呗;
我们从指针方面去改改试试:
很明显,我们成功了,但是我们必须注意一下上面用的强制转换,因为这左右两边的类型并不一样:
如果不加以强制转换的话就会报出以下警告:
Ⅱ、 const修饰指针变量
int num = 10;int* p = #const int* p = #//1int const* p = #//2int* const p = #//3const int*const p = #//4
其中,我们的1,2种写法效果一样,因为关键字是不能修饰关键字的;
表达的意思就是我们不能通过解引用来改变num的值,(或者说*p对象的值,不能被更新)相当于我们把间接改变num值的路子给堵死了;
但是呢我们可以改变p里面的值;
编译轻松通过(无任何警告,报错)
那现在我们来试试第3种;
很明显,无法通过编译,这时const修饰的是p,p是一个变量,在经过const修饰过后,p中的值无法被改变;
最后我们来试试最后一种:
从形式上我们都可以猜的八九不离十了;
编译很自然的无法通过,因为:*p和p都被const修饰了,那么自然的他们对应的对象的值也就无法更新;
Ⅲ、 const修饰函数
1、const修饰函数参数
void fun(const char* s)
{printf("%s\n",s);
}
int main()
{char a[] = "abcdef";fun(a);return 0;
}
对于那种我们在函数内部不需要进行改变的变量,我们可以加以const修饰,防止我们在后面的编写的时候,不小心,修改了它,导致处bug;
2、const 修饰函数返回值
Ⅳ、const修饰的变量与常量的区别
尽管被const修饰的变量具有了只读性(常属性),但是从根上来说他还是个变量,它依旧不能用来定义数组,它的存放区域还是在栈区,只是这个区域被加上了只读这个属性;对于常量来说肯定是只读的,因为常量并没有被存放在栈区,它被编译器放在了只读区域,从根上就断绝了它想变为一个变量的念想;
总结
1、const修饰变量呢主要是告诉程序员和编译器,我不希望这个变量被修改
2、可以保护被修饰的东西,防止意外的修改,增强程序的健壮性。
3、便于进行类型检查,使编译器对处理内容有更多了解,消除了一些隐患。
三、volatile?
说起这个关键字呢,可能大多数小伙伴都没见过,甚至是大多数程序员也或许未使用过它,因为在目前阶段我们所学的知识不足以支撑我们去使用这个关键字;接下来呢,我就与大家浅谈一下volatile这个关键字;
volatile的中文翻译呢是易变的,不稳定的;volatile关键字和const一样是-种类型修饰符,用它修饰的变量表示可以被某些编译器未知的因素更改,比如操作系统硬件或者其他线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。
int pass = 1;
int main()
{while (pass){//大家觉得这段代码编译器会做那方面的优化;}return 0;
}
分析:从这段代码我们可以看出,这必定是个死循环;
pass也没有更新的条件,因此为了提高运行效率,编译器会自动帮我们做一个小小优化,本来我们读取pass的值是从内存中读取的,但是由于pass没有更新的条件,又为了提高效率,编译器会帮我们把在内存中的pass的值放在寄存器中去,以后每次我们都是从寄存器中读取pass的值,大大提高了我们的效率;
示意图:
对于单线程来说的确实一直是死循环,加不加volatile都是一个结果;
但是,对于多线程来说,pass的值有可能会被改变,pass的值也会被重新写回内存,那么这个死循环也有终止的时候;因此这时候,编译器的优化似乎就有点多此一具了;那么这时候我们就需要volatile来帮助我们终止掉这个优化;
加入volatile:
volatile int pass = 1;
int main()
{while (pass){//大家觉得这段代码编译器会做那方面的优化;}return 0;
}
cpu每次就会从内存读取数据,在逐层送到cpu;这也就保证了我们能稳定的访问内存;减少了bug出现的机率;我们的死循环也有了终止的机会;
示意图:
简单点理解就是:volatile这个关键字,就是希望编译器不要做优化,达到稳定访问内存的目的;
其他问题:
const volatile int a = 10;//这段代码在VS2019x下能编过
是不是觉得很奇怪?
const似乎有点不变的意思;
volatile似乎又有点异变的意思;
是不是感觉有点冲突,
其实呢,我们分开理解就行了const修饰嘛,那么这个变量就具有只读性,不能被直接写入;而volatile就是要求你每次读取都从内存开始;
所以这二者并不冲突;
总结
volatile虽然有易变的意思并不是要求变量一定要改变,而是说变量可能会发生改变,要求编译器放弃优化;