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

相关文章

使用Jackson进行JSON生成与解析的新手指南

《使用Jackson进行JSON生成与解析的新手指南》这篇文章主要为大家详细介绍了如何使用Jackson进行JSON生成与解析处理,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. 核心依赖2. 基础用法2.1 对象转 jsON(序列化)2.2 JSON 转对象(反序列化)3.

Springboot @Autowired和@Resource的区别解析

《Springboot@Autowired和@Resource的区别解析》@Resource是JDK提供的注解,只是Spring在实现上提供了这个注解的功能支持,本文给大家介绍Springboot@... 目录【一】定义【1】@Autowired【2】@Resource【二】区别【1】包含的属性不同【2】@

SpringCloud动态配置注解@RefreshScope与@Component的深度解析

《SpringCloud动态配置注解@RefreshScope与@Component的深度解析》在现代微服务架构中,动态配置管理是一个关键需求,本文将为大家介绍SpringCloud中相关的注解@Re... 目录引言1. @RefreshScope 的作用与原理1.1 什么是 @RefreshScope1.

Java并发编程必备之Synchronized关键字深入解析

《Java并发编程必备之Synchronized关键字深入解析》本文我们深入探索了Java中的Synchronized关键字,包括其互斥性和可重入性的特性,文章详细介绍了Synchronized的三种... 目录一、前言二、Synchronized关键字2.1 Synchronized的特性1. 互斥2.

Java的IO模型、Netty原理解析

《Java的IO模型、Netty原理解析》Java的I/O是以流的方式进行数据输入输出的,Java的类库涉及很多领域的IO内容:标准的输入输出,文件的操作、网络上的数据传输流、字符串流、对象流等,这篇... 目录1.什么是IO2.同步与异步、阻塞与非阻塞3.三种IO模型BIO(blocking I/O)NI

Python 中的异步与同步深度解析(实践记录)

《Python中的异步与同步深度解析(实践记录)》在Python编程世界里,异步和同步的概念是理解程序执行流程和性能优化的关键,这篇文章将带你深入了解它们的差异,以及阻塞和非阻塞的特性,同时通过实际... 目录python中的异步与同步:深度解析与实践异步与同步的定义异步同步阻塞与非阻塞的概念阻塞非阻塞同步

C语言中的数据类型强制转换

《C语言中的数据类型强制转换》:本文主要介绍C语言中的数据类型强制转换方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录C语言数据类型强制转换自动转换强制转换类型总结C语言数据类型强制转换强制类型转换:是通过类型转换运算来实现的,主要的数据类型转换分为自动转换

利用Go语言开发文件操作工具轻松处理所有文件

《利用Go语言开发文件操作工具轻松处理所有文件》在后端开发中,文件操作是一个非常常见但又容易出错的场景,本文小编要向大家介绍一个强大的Go语言文件操作工具库,它能帮你轻松处理各种文件操作场景... 目录为什么需要这个工具?核心功能详解1. 文件/目录存javascript在性检查2. 批量创建目录3. 文件

C语言实现两个变量值交换的三种方式

《C语言实现两个变量值交换的三种方式》两个变量值的交换是编程中最常见的问题之一,以下将介绍三种变量的交换方式,其中第一种方式是最常用也是最实用的,后两种方式一般只在特殊限制下使用,需要的朋友可以参考下... 目录1.使用临时变量(推荐)2.相加和相减的方式(值较大时可能丢失数据)3.按位异或运算1.使用临时

使用C语言实现交换整数的奇数位和偶数位

《使用C语言实现交换整数的奇数位和偶数位》在C语言中,要交换一个整数的二进制位中的奇数位和偶数位,重点需要理解位操作,当我们谈论二进制位的奇数位和偶数位时,我们是指从右到左数的位置,本文给大家介绍了使... 目录一、问题描述二、解决思路三、函数实现四、宏实现五、总结一、问题描述使用C语言代码实现:将一个整