杂货边角(4):C语言static, inline, volatile, const等关键字解析

1. static关键字解析

ANSI标准规定了C具备32个关键字,其中绝大多数并没有特别之处,除了涉及到存储类型的几个关键字,而咱们的static关键字即是属于存储类型声明的关键字一类:

1. auto: 声明该变量标识符是存放在栈上的(局部变量的默认修饰符),编译器自动完成,现今不须要手动声明,故而该修饰符几乎不用;
2. register: 声明寄存器变量,咱们知道变量存放在寄存器中将会很是有利于关于该变量的读写加速,咱们在编译原理的代码优化知道,寄存器使用优化是目前编译器的核心功能,考虑大现今编译器的强大功能,这个修饰符也几乎不用;web

3. extern: 声明该变量或函数的定义在其余文件处,全局变量和函数都是默认extern的,故而这也是声明全局变量的初始化全局变量能够不在一块儿的缘由;缓存

4. static: 更改存储位置和访问权限
(1) static修饰局部变量,是为了改变局部变量的存储段,从栈的临时区变换成.data段,延长了声明周期;
(2) static修饰全局变量,是为了限制全局变量的访问范围,让此全局变量变成本文件内可访问;
(3) static修饰函数,也是为了限制函数的访问范围,static函数本文件内可见;
(4) static修饰类成员变量,该变量归类全部,全体实例对象共享一份;
(5) static修饰类成员函数,该函数只能用来配置操做static类成员变量。多线程

2.inline关键字解析

其实关于inline修饰符的使用每每要和宏联系在一块儿,好比以下的表达式的宏定义
#define ExpressionName(var1, var2) (var1+var2)*(var1-var2);
优势:这种宏定义形式的代码替换工做是在预编译阶段实现,没有参数压栈,代码生成等一系列操做,效率高,这是这种宏定义被使用的主要缘由;

缺点:可是这种宏定义形式只是作了预处理器符号表中的简单替换,不能主动地进行参数有效性检测 ,也不能享受C++编译器严格的类型检查和提示WARNING的强大功能,而且宏表达式的返回值也不能被强制转换成可转换的合适的类型,正是基于这些考虑,推出inline修饰的内联函数,关于inline修饰符的具体使用,C99标准和GCC编译器额外扩展的定义存在必定区别,点击查看详情svg

  1. inline修饰的内联函数是在编译阶段,同文件内的调用点处不使用call process这种过程调用指令,而是直接将内联函数的汇编码直接填写在同文件内的调用处
  2. inline定义的类的内联函数,函数的代码被放入符号表中,在使用时是直接进行替换的(像宏同样展开),没有了调用的开销,效率高;
  3. inline能够做为某个类的成员函数修饰符,能够在函数体内部使用所在类的保护成员及私有成员。

总结来看:宏表达式的替换是字符串替换,发生在词法分析和预编译阶段;inline内联函数的替换是汇编代码级别的填充,发生在编译阶段。函数

3. volatile关键字解析

前面说到过编译器优化,有时会进行想固然的优化,好比会充分利用变量当前在寄存器中的缓存,可是由于编译器并没有进程交互的概念,因此这种盲目的缓存读取优化可能会致使数据更新不一样步的状况,因此用 volatile关键字来修饰一些进程间或多线程共享信息变量,声明对该变量的每次读取都须要从内存中提取最新的数据。通常用在如下几个地方:
a、并行设备的硬件寄存器(如:状态寄存器);
b、一个中断服务子程序中会访问到的非自动变量(Non-automatic variables) ;
c、多线程应用中被几个任务共享的变量 。

总结来看:正如volatile的字面意思–“易挥发,不稳定的”,volatile修饰符使用的变量确实处于实时多变的状态。优化

4. const关键字解析

如今来看看最复杂的也是迷惑性最强的 const修饰符, const既能够修饰变量也能够修饰函数,先来看看修饰变量的情形:
一、以const修饰的常量值,具备不可变性,避免数字的直接使用,使用具备可读性的名称(const int MAX_SUM = 100;
二、C++编译器不为普通const常量分配空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有存储和读内存的操做,使得它效率更高;
三、 const修饰符是左结合的,故而考虑对于以下两种状况的分析:
int const *A; const绑定在int上,说明是修饰的int数值, 限定*A指向的内容不可变,指针A自己可变;
int * const A; const绑定在int* 指针上,限定指针A自己不可变,指针指向的内容可变

关于用于函数修饰的情形,可参考这篇文章。const修饰函数主要有三个方面能够修饰:
1. 形参修饰
(a) 值传递:由于是在栈上建立临时对象,事后即删,故而无需const保护,即不存在void Func(const int source)
(b) 指针传递: void Func(const char* source)即是规定函数内部不能对source指针指向的内容进行任何修改;
(c) 引用(别名)传递:若是传输对象size较大,建立临时对象不划算,采用引用传递,可是也面临着和指针传递同样的内容修改风险,void Func(const ArrayList &source)即是保证函数内部不能对source进行更改。this

2. 返回值修饰:返回值的传递方式也是分为三种的
(a) 值传递返回值::函数内部会在栈上创建一个临时对象用来传递返回值,事后即删,一样无需const保护;
(b) 指针传递返回值:以“指针传递”方式的函数返回值加const修饰,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给加const修饰的同类型指针,不然何来const保护做用的传承?编码

const char * GetString(void);
//以下语句将出现编译错误:
char*  str = GetString();
//正确的用法是
const char * str = GetString();

(c) 引用(别名)传递返回值:函数返回值采用“引用传递”的场合并很少,由于这是一直在同一个数据对象进行操做,一个数据对象若是存在较多的别名,而且仍是不可控的,这是很可怕的,故而这种方式通常只出如今类的赋值函数中,目的是为了实现链式表达。spa

3. 类成员函数功能声明
使用在C++的类函数修饰场景,声明在函数中不改变类成员,若是在函数中存在更改类成员的操做,则编译器编译时会主动指出错误,因此这种用法是有点借助编译器自我约束自我提醒的意思,可提升程序的健壮性。借用.net

classStack
{
public:
   void Push(int elem);
   int Pop(void);
   int GetCount(void) const; // const 成员函数,
   /*
   **其实const 修饰的是this*对象指针,故而本质上和const修饰符的
   **this是隐含参数,const无法直接修饰它,就加在函数的后面了。const修饰*this是本质,
   **至于说“表示该成员函数不会修改类的数据。不然会编译报错”之类的说法只是一个现象,
   **根源就是由于*thisconst类型的
   */

private:
  int  m_num;
  int  m_data[10];
};

int Stack::GetCount(void)const
{
   m_num++; // 编译错误,在const成员函数中修改类成员m_num
   Pop();   // 编译错误,在const成员函数中调用非const成员函数,
   /*
   **非const函数的行为没法保证不修改类成员的,故而const成员函数不能调用非const成员函数
   **一样的道理,若是声明 const classStack demo;即声明该类的const对象,则该对象是不能
   **调用它的非const成员函数的,由于非const成员函数会修改类成员数据,这就不是const对象了
   **可是const对象持有的指针变量指向的内容是能够修改的,由于const对象只须要保证指针不变就能够
   **指针指向的数据变不变并不直接归属于const对象的const性质
   */
   return  m_num;
}

关于const成员函数的其余几点说明:

  1. const成员函数不能够修改类的任何成员数据(除了下面的特例), 编译器在编译阶段以该函数是否修改为员数据为依据,进行合法性判断;
  2. 加上mutable修饰符的数据成员,对于任何状况下经过任何手段均可修改,天然此时的const成员函数内部是能够mutable变量的。