杂货边角(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

相关文章

基于Python实现多语言朗读与单词选择测验

《基于Python实现多语言朗读与单词选择测验》在数字化教育日益普及的今天,开发一款能够支持多语言朗读和单词选择测验的程序,对于语言学习者来说无疑是一个巨大的福音,下面我们就来用Python实现一个这... 目录一、项目概述二、环境准备三、实现朗读功能四、实现单词选择测验五、创建图形用户界面六、运行程序七、

Python中配置文件的全面解析与使用

《Python中配置文件的全面解析与使用》在Python开发中,配置文件扮演着举足轻重的角色,它们允许开发者在不修改代码的情况下调整应用程序的行为,下面我们就来看看常见Python配置文件格式的使用吧... 目录一、INI配置文件二、YAML配置文件三、jsON配置文件四、TOML配置文件五、XML配置文件

Spring中@Lazy注解的使用技巧与实例解析

《Spring中@Lazy注解的使用技巧与实例解析》@Lazy注解在Spring框架中用于延迟Bean的初始化,优化应用启动性能,它不仅适用于@Bean和@Component,还可以用于注入点,通过将... 目录一、@Lazy注解的作用(一)延迟Bean的初始化(二)与@Autowired结合使用二、实例解

使用Go语言开发一个命令行文件管理工具

《使用Go语言开发一个命令行文件管理工具》这篇文章主要为大家详细介绍了如何使用Go语言开发一款命令行文件管理工具,支持批量重命名,删除,创建,移动文件,需要的小伙伴可以了解下... 目录一、工具功能一览二、核心代码解析1. 主程序结构2. 批量重命名3. 批量删除4. 创建文件/目录5. 批量移动三、如何安

python使用fastapi实现多语言国际化的操作指南

《python使用fastapi实现多语言国际化的操作指南》本文介绍了使用Python和FastAPI实现多语言国际化的操作指南,包括多语言架构技术栈、翻译管理、前端本地化、语言切换机制以及常见陷阱和... 目录多语言国际化实现指南项目多语言架构技术栈目录结构翻译工作流1. 翻译数据存储2. 翻译生成脚本

Go语言中三种容器类型的数据结构详解

《Go语言中三种容器类型的数据结构详解》在Go语言中,有三种主要的容器类型用于存储和操作集合数据:本文主要介绍三者的使用与区别,感兴趣的小伙伴可以跟随小编一起学习一下... 目录基本概念1. 数组(Array)2. 切片(Slice)3. 映射(Map)对比总结注意事项基本概念在 Go 语言中,有三种主要

C语言中自动与强制转换全解析

《C语言中自动与强制转换全解析》在编写C程序时,类型转换是确保数据正确性和一致性的关键环节,无论是隐式转换还是显式转换,都各有特点和应用场景,本文将详细探讨C语言中的类型转换机制,帮助您更好地理解并在... 目录类型转换的重要性自动类型转换(隐式转换)强制类型转换(显式转换)常见错误与注意事项总结与建议类型

MySQL 缓存机制与架构解析(最新推荐)

《MySQL缓存机制与架构解析(最新推荐)》本文详细介绍了MySQL的缓存机制和整体架构,包括一级缓存(InnoDBBufferPool)和二级缓存(QueryCache),文章还探讨了SQL... 目录一、mysql缓存机制概述二、MySQL整体架构三、SQL查询执行全流程四、MySQL 8.0为何移除查

在Rust中要用Struct和Enum组织数据的原因解析

《在Rust中要用Struct和Enum组织数据的原因解析》在Rust中,Struct和Enum是组织数据的核心工具,Struct用于将相关字段封装为单一实体,便于管理和扩展,Enum用于明确定义所有... 目录为什么在Rust中要用Struct和Enum组织数据?一、使用struct组织数据:将相关字段绑

使用Java实现一个解析CURL脚本小工具

《使用Java实现一个解析CURL脚本小工具》文章介绍了如何使用Java实现一个解析CURL脚本的工具,该工具可以将CURL脚本中的Header解析为KVMap结构,获取URL路径、请求类型,解析UR... 目录使用示例实现原理具体实现CurlParserUtilCurlEntityICurlHandler