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

2023-10-12 22:32

本文主要是介绍杂货边角(4):C语言static, inline, volatile, const等关键字解析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1. static关键字解析

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

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

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编译器额外扩展的定义存在一定区别,点击查看详情。

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

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

3. volatile关键字解析

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

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

4. const关键字解析

现在来看看最复杂的也是迷惑性最强的 const修饰符, const既可以修饰变量也可以修饰函数,先来看看修饰变量的情形:
1、以const修饰的常量值,具有不可变性,避免数字的直接使用,使用具有可读性的名称(const int MAX_SUM = 100;
2、C++编译器不为普通const常量分配空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有存储和读内存的操作,使得它效率更高;
3、 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进行更改。

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

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

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

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

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_numPop();   // 编译错误,在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变量的。

这篇关于杂货边角(4):C语言static, inline, volatile, const等关键字解析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/198892

相关文章

Linux中shell解析脚本的通配符、元字符、转义符说明

《Linux中shell解析脚本的通配符、元字符、转义符说明》:本文主要介绍shell通配符、元字符、转义符以及shell解析脚本的过程,通配符用于路径扩展,元字符用于多命令分割,转义符用于将特殊... 目录一、linux shell通配符(wildcard)二、shell元字符(特殊字符 Meta)三、s

C#使用yield关键字实现提升迭代性能与效率

《C#使用yield关键字实现提升迭代性能与效率》yield关键字在C#中简化了数据迭代的方式,实现了按需生成数据,自动维护迭代状态,本文主要来聊聊如何使用yield关键字实现提升迭代性能与效率,感兴... 目录前言传统迭代和yield迭代方式对比yield延迟加载按需获取数据yield break显式示迭

使用SQL语言查询多个Excel表格的操作方法

《使用SQL语言查询多个Excel表格的操作方法》本文介绍了如何使用SQL语言查询多个Excel表格,通过将所有Excel表格放入一个.xlsx文件中,并使用pandas和pandasql库进行读取和... 目录如何用SQL语言查询多个Excel表格如何使用sql查询excel内容1. 简介2. 实现思路3

c# checked和unchecked关键字的使用

《c#checked和unchecked关键字的使用》C#中的checked关键字用于启用整数运算的溢出检查,可以捕获并抛出System.OverflowException异常,而unchecked... 目录在 C# 中,checked 关键字用于启用整数运算的溢出检查。默认情况下,C# 的整数运算不会自

Go语言实现将中文转化为拼音功能

《Go语言实现将中文转化为拼音功能》这篇文章主要为大家详细介绍了Go语言中如何实现将中文转化为拼音功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 有这么一个需求:新用户入职 创建一系列账号比较麻烦,打算通过接口传入姓名进行初始化。想把姓名转化成拼音。因为有些账号即需要中文也需要英

Go语言使用Buffer实现高性能处理字节和字符

《Go语言使用Buffer实现高性能处理字节和字符》在Go中,bytes.Buffer是一个非常高效的类型,用于处理字节数据的读写操作,本文将详细介绍一下如何使用Buffer实现高性能处理字节和... 目录1. bytes.Buffer 的基本用法1.1. 创建和初始化 Buffer1.2. 使用 Writ

深入理解C语言的void*

《深入理解C语言的void*》本文主要介绍了C语言的void*,包括它的任意性、编译器对void*的类型检查以及需要显式类型转换的规则,具有一定的参考价值,感兴趣的可以了解一下... 目录一、void* 的类型任意性二、编译器对 void* 的类型检查三、需要显式类型转换占用的字节四、总结一、void* 的

使用Python实现批量访问URL并解析XML响应功能

《使用Python实现批量访问URL并解析XML响应功能》在现代Web开发和数据抓取中,批量访问URL并解析响应内容是一个常见的需求,本文将详细介绍如何使用Python实现批量访问URL并解析XML响... 目录引言1. 背景与需求2. 工具方法实现2.1 单URL访问与解析代码实现代码说明2.2 示例调用

SSID究竟是什么? WiFi网络名称及工作方式解析

《SSID究竟是什么?WiFi网络名称及工作方式解析》SID可以看作是无线网络的名称,类似于有线网络中的网络名称或者路由器的名称,在无线网络中,设备通过SSID来识别和连接到特定的无线网络... 当提到 Wi-Fi 网络时,就避不开「SSID」这个术语。简单来说,SSID 就是 Wi-Fi 网络的名称。比如

SpringCloud配置动态更新原理解析

《SpringCloud配置动态更新原理解析》在微服务架构的浩瀚星海中,服务配置的动态更新如同魔法一般,能够让应用在不重启的情况下,实时响应配置的变更,SpringCloud作为微服务架构中的佼佼者,... 目录一、SpringBoot、Cloud配置的读取二、SpringCloud配置动态刷新三、更新@R