酷酷的猴子 · WordPress基础之谷歌GSC与GA统计 ...· 3 月前 · |
俊逸的热带鱼 · 壳聚糖-阿拉伯胶复凝聚物与肉桂精油中的肉桂醛 ...· 3 月前 · |
要出家的吐司 · Starting ...· 5 月前 · |
气宇轩昂的眼镜 · 【Java】导出的压缩文件名重复则在后面叠加 ...· 8 月前 · |
函数指针 局部变量 const extern |
https://durant35.github.io/2017/07/06/programPearls_inline$static$const$extern$volatile/ |
踏实的瀑布
2 月前 |
本文是关于C/C++语言中常见的五个修饰符:
static
、
const
、
extern
、
inline
、
volatile
的含义和用法,在阅读本文前,建议先通过
《声明/定义/初始化/赋值》
了解有关C/C++中定义、声明等概念以及变量声明和定义的区别。
static
是为了表示退出一个块后仍然存在的局部变量;随后,
static
在C中有了第二种含义:用来表示不能被其它文件访问的全局变量和函数。为了避免引入新的关键字,所以仍使用static关键字来表示这第二种含义。
static
变量,即静态局部变量,因为是局部变量,已经是内部连接了。
0x00
(对于整型为0;对于字符数组为
'\0'
),某些时候这一特点可以减少程序员的工作量。
static
修饰局部变量
static
修饰局部变量后,该变量只在初次运行时进行初始化工作,且只进行一次。
static
全局变量,因为是全局变量,已经是静态存储了。
static
前缀的全局变量和函数都具有全局可见性(源程序中的其它文件也能访问)。
static
修饰函数和修饰全局变量
.c
或
.cpp
文件在编译时,预处理器首先递归包含头文件,形成一个含有所有必要信息的单个源文件,这个源文件就是一个编译单元。这个编译单元会被编译成为一个同名的目标文件(
.o
或
.obj
),链接程序把不同编译单元中产生的符号联系起来,构成一个可执行程序。
static
的作用仅限于隐藏。
extern
(下文会介绍)声明也无法使用它;准确地说,作用域是从声明之处开始,到文件结尾处结束:在定义之处前面的同一文件的那些代码行也不能使用它,想要使用就得在前面再加
extern
。
类名::???
的方式访问。
.cpp
)初始化。静态成员变量本质上是全局变量,在类的所有实例对象中共享一份。
static
修饰的同名全局变量,那么这些变量互不干扰。
const
修饰的东西(变量/函数)都受到强制保护,程序中使用
const
可以预防意外的变动,在一定程度上提高程序的健壮性,但是程序中使用过多的
const
,可能加大代码的阅读难度。
const
修饰普通变量
#define
预处理指令,只是简单的值替代,缺乏类型的检测机制;C++引入
const
关键字:
1 |
const datatype name=value; |
const
常量分配存储空间,而是将它们保存在符号表中 $\Longrightarrow$ 编译期间的常量,没有了内存存储等操作,效率更高。
const
修饰的变量(用来修饰函数的形参除外)必须在声明时进行初始化;一旦一个变量被
const
修饰,在程序中除初始化外对这个变量进行的赋值都是错误的。
const
修饰指针:指针常量 vs 常量指针
1 |
// 指针常量 |
const
是一个左结合的类型修饰符,它与其左侧的类型合为一个类型修饰符。
const
修饰函数的参数
1 |
// 常量指针:以防意外改动指针指向数据内容 |
const
限定。
const
修饰函数的返回值
1 |
const char* getString(void); |
const
限定类的成员函数
1 |
class MyClass { |
const
只能放在函数声明的尾部,大概是因为其它地方被占用了。
const
函数。
const
有很大区别:在C语言中用
const
修饰的变量仍然是一个变量;而在C++中用
const
修饰后,就变成常量了。
1 |
const int n=5; |
extern
用在变量或者函数的声明前,用来说明“此变量/函数是在别处定义的,要在此处
引用
”
extern
置于变量或者函数前,标识变量或者函数的定义在别的文件中,提示编译器遇到此变量或函数时在其他模块(其他
.o
文件)中寻找其定义。
extern
的作用域
extern
声明,还取决于外部变量本身是否能够被引用到,即变量的作用域:要求被引用的变量的链接属性必须是外链接的,通常是全局变量。
extern
声明的位置对其作用域也有关系,例如在某个函数中的声明就只能在该函数中调用,在其它函数中不能调用。
extern
?因为用
extern
会加速程序的编译过程,这样能节省时间。
#include
这些函数声明的头文件,
extern
的引用方式要直截了当、简洁的多:想引用哪个函数就用
extern
声明哪个函数,这会加速程序编译,确切的说预处理过程,节省时间;在大型C程序编译过程中,这种加速会更加明显。
extern
共享全局函数/全局变量
.h
文件中通过
extern
修饰进行声明,在
.c
/
.cpp
文件的变量定义与函数实现与普通变量、普通函数一致;要调用该文件中的函数和变量,只需要把
.h
文件用
#include
包含进来即可。
1 |
// 声明全局变量 |
1 |
// 外部变量声明 |
extern
还有另外一个作用:用于指示C或者C++函数的调用规范。
extern "C"{...}
声明要引用的函数
,这是给链接器用的,告诉链接器在链接的时候用C函数规范来链接。
1 |
extern "C" { |
extern "C"{...}
进行链接指定,告诉编译器,请保持我的函数名,不要进行任何重命名。
1 |
extern "C" { |
1 |
#define expression(x,y) (x+y)*(x-y) |
inline
修饰全局函数,保留了上述方式的优点,又能有效避免相应的不足。
inline
内联函数代码被
放入符号表中,调用时直接进行替换(像宏一样展开)
,没有了调用的开销,效率也很高。
inline
成员函数 $\Longrightarrow$ 访问保护成员或私有成员
inline
内联函数函数体应简单
inline
函数足够简单:不能包含复杂的结构控制语句(while/switch),不能出现递归。
set/get
函数。
inline
函数声明和定义(实现)放在头文件中最合适
1 |
volatile int i=10; |
volatile
指出变量是随时可能发生变化的,每次使用该变量的时候都必须从其地址中读取,因此编译器生成的
int b=i;
的汇编代码会重新从变量 i 的地址读取数据放在变量 b 中;优化的做法(没有
volatile
)是:编译器发现两次从变量 i 读数据的代码间没有对 i 进行过操作,会自动把上次读的 i 的数据(一般的编译器可能会将其拷贝放在寄存器中以加快指令的执行速度)放在变量 b 中 $\Longrightarrow$
volatile
可以保证对特殊地址的稳定访问。
volatile
与编译器优化
load
/
store
指令)。
volatile
总是与优化有关,编译器有一项技术叫做
数据流分析
,分析程序中的变量在哪里赋值?在哪里使用?在哪里失效,分析结果可以用于常量合并、常量传播等优化,进一步可以死代码消除。编译器对常规内存进行优化的时候,这些优化是透明的,而且效率很好;但有时候这些优化不是程序所需要的,这时可以用
volatile
禁止这些优化:
volatile
变量缓存在寄存器中:在多任务、中断等环境下,变量可能被其他程序改变。
volatile
变量的读写不会被优化掉:如果你对一个变量赋值但后面没用到,编译器常常可以省略那个赋值操作,然而万一这个赋值是对 Memory Mapped的 IO 资源(比如LEDs)进行操作呢!
volatile
。
volatile
。
volatile
说明,因为每次对它的读写都可能有不同的意义。
volatile
很可能会增加代码尺寸和降低性能!
const
又是
volatile
,比如只读的状态寄存器。