《编写高质量代码:改善c程序代码的125个建议》——建议11:尽量使用const声明值不会改变的变量

简介:

本节书摘来自华章计算机《编写高质量代码:改善c程序代码的125个建议》一书中的第1章,建议11,作者:马 伟 更多章节内容可以访问云栖社区“华章计算机”公众号查看。

建议11:尽量使用const声明值不会改变的变量

从字面上理解,const是constant 的缩写,是恒定不变的意思,也翻译为常量、常数等。正是因为这一点,看到const关键字,很多人就认为被const 修饰的值是常量。其实,在C语言中,关键字const的功能非常强大,它不仅可以用来修饰普通变量、数组变量与指针变量等,还可以用来修饰函数的参数、返回值与函数本身。这些将会在后面的章节逐一详细讲解,本节只讨论使用const来修饰变量的情况。
对变量来说,const关键字可以限定一个变量的值不允许被改变,从而保护被修饰的东西,防止意外修改发生,这在一定程度上可以提高程序的安全性和可靠性。但是要正确地使用const变量,我们必须弄清以下几点。
1.const变量是只读变量,不是常量
提到const变量,总有人喜欢把它和常量混为一谈。其实const变量不是常量,准确地说它应该是只读的变量。为了让大家更好地区分这两个概念,我们来看下面的例子:

const int array_num=10;
int arr[array_num]={1,2,3,4,5,6,7,8,9};

在标准的ANSI C中,上面的这种写法是错误的,因为数组的大小应该是个常量,而const int array_num只是一个只读变量。虽然常量也是只读的、不可修改的(例如10、‘C’等),但只读变量却不等于常量,只读变量被编译器限定为不能够被修改。
实际上,根据编译过程及内存分配来看,这种用法本来应该是合理的,只是ANSI C对数组的规定限制了它。而在C++中,上面的这种写法确实是正确的。
2.确保变量的值不被修改
可以说,const关键字的最大作用就是确保变量赋初值之后,其值不会被任何程序修改。如下面的示例代码所示:

const int array_num=10;
array_num++;        // 错误,不可以被修改

很显然,语句“array_num++”是错误的。同样,对于外部变量,const一样可以确保其变量的值不被修改,如下面的代码所示:

extern const int i;     // 正确的引用
extern const int i="10";    // 错误,不可以被再次赋值

尽管如此,网上仍然流传着一种修改const变量的说法,如代码清单1-30所示。

代码清单1-30 const变量使用示例
#include <stdio.h>
int main(void)
{
    const int i1=10;
    int *p1=(int *)&i1;
    int *p2=p1;
    *p1=100;
    printf("%d %d %d\n",i1,*p1,*p2);
    printf("%p %p %p\n\n",&i1,p1,p2);
    return 0;
}

有人说,这样const变量就能被修改了,但运行上面的程序,const变量的值根本没有改变,代码清单1-30的运行结果如图1-45所示。




图1-45 代码清单1-30的运行结果
在图1-45中,虽然“i1, p1,p2”所得的值不同,但“&i1,p1,p2”却是同一个地址,既然是同一地址,为什么输出不一样的值?
为了进一步查看其原因,我们在代码清单1-30中添加如下一条语句:
printf("%dn", ((int )0x0012FF74));
这样就可以直接输出地址所存的值了。得到的结果在我们的预料之中,为“100”。或许大家认为这样就改变了const变量的值。
其实,“int p1=(int )&i1;”表面上给了它们相同的指针,但是const变量的值是保存在数据段(只读)的,通过地址0x0012FF74查内存文件可得知其属于堆栈段。也就是说,虽然地址相同,但const变量读取的是数据段,而通过指针读取到的是堆栈段。
由此可见,const提供了一种保护机制,能在编译阶段阻止其变量的值被修改。但它并不能完全防止在程序的内部(甚至是外部)来修改这个值。也就是说,const变量是只读变量,既然是变量,那么就可以获取其地址,然后修改其值。因此,当汇编成二进制之后,这种保护机制就不复存在了。
3.节省空间,避免不必要的内存分配
使用const变量除了可以确保变量值不被修改之外,同时它还可以节省存储空间,避免不必要的内存分配。通常,编译器并不为普通const 只读变量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的值,没有了存储与读内存的操作,从而提高效率。
相关文章
|
6月前
|
缓存 监控 程序员
Python中的装饰器是一种特殊类型的声明,它允许程序员在不修改原有函数或类代码的基础上,通过在函数定义前添加额外的逻辑来增强或修改其行为。
【6月更文挑战第30天】Python装饰器是无侵入性地增强函数行为的工具,它们是接收函数并返回新函数的可调用对象。通过`@decorator`语法,可以在不修改原函数代码的情况下,添加如日志、性能监控等功能。装饰器促进代码复用、模块化,并保持源代码整洁。例如,`timer_decorator`能测量函数运行时间,展示其灵活性。
51 0
|
7月前
|
存储 C# 容器
掌握 C# 变量:在代码中声明、初始化和使用不同类型的综合指南
变量是用于存储数据值的容器。 在 C# 中,有不同类型的变量(用不同的关键字定义),例如: int - 存储整数(没有小数点的整数),如 123 或 -123 double - 存储浮点数,有小数点,如 19.99 或 -19.99 char - 存储单个字符,如 'a' 或 'B'。Char 值用单引号括起来 string - 存储文本,如 "Hello World"。String 值用双引号括起来 bool - 存储具有两个状态的值:true 或 false
111 2
|
移动开发 小程序 JavaScript
uniapp进行条件编译的两种方法?小程序端和H5的代表值是什么
在 UniApp 中,可以使用条件编译来根据不同的平台(小程序、H5 等)进行不同的代码处理。有两种主要的方法来实现条件编译:使用 mp 属性和条件注释。