百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT知识 > 正文

C/C++的const常量总结(c++语言const)

liuian 2025-07-07 20:09 39 浏览

所谓常量,就是在代码运行过程中值恒定不变的标识符,该标识符的值可以是一个常数,也可以是字符串。


在C/C++中,通常使用define宏定义或者const来定义常量,比如:

#define PI 3.1415926
const int a = 5;

接下来我们来总结const常量的相关用法与注意事项。


01

为什么使用常量?


1. 使用常量可增加程序的可读性。

程序员应该都有这种经历:看别人的代码时为代码中某个数字而纠结,不知道这个数字表示什么意义,甚至自己写的代码,过一段时间再回头看,有可能都不记得代码里某个数字代表什么意思。比如计算一天包含的秒数,如果按照下式计算,虽然没错,但很可能会让人懵逼。24代表什么?两个60又分别代表什么?

int s = 24*60*60;

比较推荐的做法是类似这样的,这样看起来就比较清晰了:

const int oneday_hour = 24;
const int onehour_minute = 60;
const int oneminute_second = 60;
const int oneday_second = oneday_hour * onehour_minute * oneminute_second;


2. 使用常量可减少出错的概率。


程序员写代码时,经常需要在多个地方使用同一个数字或者同一个字符串,如果都是直接输入数字或者字符串,万一某个地方写错了,造成该处与其它地方的数字或字符串不一致,很可能导致严重bug。

比如以下代码,多个地方要用到Π值3.1415926,如果把3.1415926定义为一个常量,使用起来就方便多了,也不容易出错:

//不好的使用示例
double a = sin(3.1415926*5);
double b = cos(3.1415926*10);
double b = sin(3.1415926*100)*cos(3.1415926/2);


//推荐的使用示例
const double PI = 3.1415926;
double a = sin(PI*5);
double b = cos(PI*10);
double b = sin(PI*100)*cos(PI/2);


3. 使用常量可以减少代码维护的工作量。


使用常量的情况下,程序员维护代码时如果要修改代码中某个数值,他只需要修改常量的值,那么代码中多处用到该常量的地方也就自己改了过来,而不需要程序员去一个地方一个地方地修改。

比如以下代码,计算b、c、d都要要到5.0这个数值,如果哪一天需求改变,需要把5.0改为20.0,在不好的示例中需要修改三行代码,在我们推荐的示例中只需要修改一行代码——把常量a的值改为20.0即可:

//不好的使用示例
float b = 5.0*3;
float c = 5.0 + 10;
float d = exp(5.0);


//推荐的使用示例
const float a = 5.0;
float b = a*3;
float c = a + 10;
float d = exp(a);


4. 使用常量可提升程序的健壮性


编写代码时,我们经常希望某一个变量的值不被意外改变,或者某指针指向的对象不被意外改变,又或者某指针指向的对象的值不被意外改变(这里可能有点懵逼,读者暂时不用纠结,下文我们再详说)。然而在代码的汪洋大海中,难免会有意外发生,我们能做的就是使用常量来减小这种意外发生的概率。

比如以下函数,指针p作为输入参数,在该函数内部我们不希望p由其原本指向的地址被改为指向别的地址,同时希望指针p指向地址的值是可以改变的,那么可以使用const来修饰p,这样p指向的地址就不会被意外改变啦~

void func(int *const p)
{
    int a = 3;
    
    p = &a;  //这样的操作是无效的,因为指针p被const修饰,其指向的地址不能被改变
 
    *p = a;  //这样的操作是有效的,p指向地址的值会被修改为a的值
}



02

const常量与define常量的区别


在C/C++中,有const和define两种定义常量的方式,不过const常量相比define常量有更多的优点:


1. const常量有数据类型,define宏定义常量没有,编译器会对const常量可以进行数据类型的安全检查,但是对于define宏定义,编译器只是将其进行字符替换,这样的字符替换很容易出错。比如以下代码,如果使用不加括号的宏定义,将不能正确计算(a+b)/5.0,而是变成计算a+(b/5.0)了。

//define宏定义的做法
#define a 2.0
#define b 9.0
#define c1 a+b   //不好的定义方法
#define c2 ((a)+(b))  //推荐的定义方法
void func1(void)
{
    float d1 = c1/5.0;  //本意是想计算(a+b)/5.0,但字符替换使计算变成了a+b/5.0
    float d2 = c2/5.0;  //正确计算了((a)+(b))/5.0 = (a+b)/5.0
}


//推荐的const常量的做法
const float a = 2.0;
const float b = 9.0;
const float c = a + b;
void func2(void)
{
    float d = c/5.0;  //正确计算了(a+b)/5.0
}


2. 有一些调试工具可以对const常量进行调试,但却无法调试define宏定义常量。


因此,我们还是建议尽量使用const常量。



03

建议的const常量定义规则


1. const常量应在.c或.cpp文件中定义,尽量不要在头文件中定义,因为假如头文件被多个.c或.cpp文件包含,那么定义于头文件中的常量将被多次重复定义,很可能造成严重错误。比如以下代码:

推荐的做法,在.c或.cpp文件中定义:

const int x = 15;

不建议的做法,在头文件中定义:

#ifndef _TEST_H_
#define _TEST_H_


const int x = 15;


#endif

虽然不建议在头文件中定义常量,但是对于全局常量,也就是我们不仅希望该常量能在其定义的.c或.cpp文件中使用,还可以在别的.c或.cpp文件中使用,那么可以在头文件中对其进行extern声明,这样一来只要别的.c或.cpp文件include了该头文件,它也就可以使用该常量了,比如以下代码:

#ifndef _TEST_H_
#define _TEST_H_


extern const int x;


#endif


2. 如果一个常量与别的常量有联系,在定义的时候尽量把它们的联系包含进去:

//不推荐的做法
const int a = 5;
const int b = 5*100;
const int c = 5 + 5*100 - 64;


//建议的做法,在定义时把a、b、c的联系包含了进去
const int a = 5;
const int b = a*100;
const int c = a + b - 64;



04

容易混淆的const常量定义


1. 某个量的值恒定不变:

const float x = 5.0;  //定义x并将x的值初始化为5.0之后,在程序运行期间x的值将保持5.0不变


2. 指针指向的地址恒定不变,但可以通过指针来修改其指向地址的值:

float x = 1.23;
//初始化指针p指向x的地址之后,p将不能改为指向其它地址
float *const p = &x;  


//无效操作
float a = 6.3;
p = &a;


//有效操作
*p = 100.0;  //等效于x = 100.0


3. 指针指向的地址可以改变,但不能通过指针来修改其指向地址的值,该情况与上述第2种情况相反:

float x = 1.23;
//初始化指针p指向x的地址之后,将不能通过指针p来修改x的值
float const *p = &x;  


//有效操作
float a = 6.3;
p = &a;


//无效操作
*p = 100.0;  //这样并不能修改x的值,x的值还是1.23


另外,以下两种定义方式是等效的,都属于此种情况:

float const *p;
const float *p;



05

const常量与类


在类内部定义的const常量,仅在该类的某个对象的生命周期内是恒定不变的,对于整个类而言却是可以改变的,也即该类的不同对象可以将该常量初始化为不同的值。

声明类时,其内部的const常量成员是不能被初始化的,比如以下代码是有问题的,声明类A时len的值并不能被初始化为50,而是一个不确定的值。

class A
{
    const int len = 50;  //实际len的值并不能被初始化为50,而是一个不确定的值
    int x[len];
}

不过类内部的const常量成员可以通过构造函数的初始化表进行初始化:

class A 
{ 
  const int len;
  A(int length);  //构造函数  
};


A::A(int length):len(length)  //构造函数的初始化表
{ 


} 


A  a(100); // 对象a的len值被初始化为100 
A  b(200); // 对象b的len值被初始化为200 


如果非要定义在类的所有对象中都恒定不变的常量,可以通过以下两种方法:


1. 使用枚举来实现,如以下代码,不过这样做的缺陷是枚举定义的len1和len2只能是整型数,不能是浮点数。

class A 
{
  enum { len1 = 20, len2 = 58}; //len1和len2的值在所有对象中都是不变的
  int x1[len1];  
  int x2[len2]; 
};


2. 使用static关键字,加上static修饰之后,常量len不再保存在存储对象的区域(堆、栈等),而是保存到了静态存储区,因此对所有该类的对象来说len是恒定不变的常量。

class A
{
   static const int len = 30;
   int x[len];
}




欢迎关注“萌萌哒程序猴”微信公众号,接下来会不定时更新更加精彩的内容,敬请期待~

相关推荐

教你把多个视频合并成一个视频的方法

一.情况介绍当你有一个m3u8文件和一个目录,目录中有连续的视频片段,这些片段可以连成一段完整的视频。m3u8文件打开后像这样:m3u8文件,可以理解为播放列表,里面是播放视频片段的顺序。视频片段像这...

零代码编程:用kimichat合并一个文件夹下的多个文件

一个文件夹里面有很多个srt字幕文件,如何借助kimichat来自动批量合并呢?在kimichat对话框中输入提示词:你是一个Python编程专家,完成如下的编程任务:这个文件夹:D:\downloa...

Java APT_java APT 生成代码

JavaAPT(AnnotationProcessingTool)是一种在Java编译阶段处理注解的工具。APT会在编译阶段扫描源代码中的注解,并根据这些注解生成代码、资源文件或其他输出,...

Unit Runtime:一键运行 AI 生成的代码,或许将成为你的复制 + 粘贴神器

在我们构建了UnitMesh架构之后,以及对应的demo之后,便着手于实现UnitMesh架构。于是,我们就继续开始UnitRuntime,以用于直接运行AI生成的代码。PS:...

挣脱臃肿的枷锁:为什么说Vert.x是Java开发者手中的一柄利剑?

如果你是一名Java开发者,那么你的职业生涯几乎无法避开Spring。它如同一位德高望重的老国王,统治着企业级应用开发的大片疆土。SpringBoot的约定大于配置、SpringCloud的微服务...

五年后,谷歌还在全力以赴发展 Kotlin

作者|FredericLardinois译者|Sambodhi策划|Tina自2017年谷歌I/O全球开发者大会上,谷歌首次宣布将Kotlin(JetBrains开发的Ja...

kotlin和java开发哪个好,优缺点对比

Kotlin和Java都是常见的编程语言,它们有各自的优缺点。Kotlin的优点:简洁:Kotlin程序相对于Java程序更简洁,可以减少代码量。安全:Kotlin在类型系统和空值安全...

移动端架构模式全景解析:从MVC到MVVM,如何选择最佳设计方案?

掌握不同架构模式的精髓,是构建可维护、可测试且高效移动应用的关键。在移动应用开发中,选择合适的软件架构模式对项目的可维护性、可测试性和团队协作效率至关重要。随着应用复杂度的增加,一个良好的架构能够帮助...

颜值非常高的XShell替代工具Termora,不一样的使用体验!

Termora是一款面向开发者和运维人员的跨平台SSH终端与文件管理工具,支持Windows、macOS及Linux系统,通过一体化界面简化远程服务器管理流程。其核心定位是解决多平台环境下远程连接、文...

预处理的底层原理和预处理编译运行异常的解决方案

若文章对您有帮助,欢迎关注程序员小迷。助您在编程路上越走越好![Mac-10.7.1LionIntel-based]Q:预处理到底干了什么事情?A:预处理,顾名思义,预先做的处理。源代码中...

为“架构”再建个模:如何用代码描述软件架构?

在架构治理平台ArchGuard中,为了实现对架构的治理,我们需要代码+模型描述所要处理的内容和数据。所以,在ArchGuard中,我们有了代码的模型、依赖的模型、变更的模型等,剩下的两个...

深度解析:Google Gemma 3n —— 移动优先的轻量多模态大模型

2025年6月,Google正式发布了Gemma3n,这是一款能够在2GB内存环境下运行的轻量级多模态大模型。它延续了Gemma家族的开源基因,同时在架构设计上大幅优化,目标是让...

比分网开发技术栈与功能详解_比分网有哪些

一、核心功能模块一个基本的比分网通常包含以下模块:首页/总览实时比分看板:滚动展示所有正在进行的比赛,包含比分、比赛时间、红黄牌等关键信息。热门赛事/焦点战:突出显示重要的、关注度高的比赛。赛事导航...

设计模式之-生成器_一键生成设计

一、【概念定义】——“分步构建复杂对象,隐藏创建细节”生成器模式(BuilderPattern):一种“分步构建型”创建型设计模式,它将一个复杂对象的构建与其表示分离,使得同样的构建过程可以创建...

构建第一个 Kotlin Android 应用_kotlin简介

第一步:安装AndroidStudio(推荐IDE)AndroidStudio是官方推荐的Android开发集成开发环境(IDE),内置对Kotlin的完整支持。1.下载And...