C/C++中const关键字的用法及其与宏常量的比较
1.const关键字的性质
简单来说:const关键字修饰的变量具有常属性。 即它所修饰的变量不能被修改。
2.修饰局部变量
const int a = ;
int const b = ;
这两种写法是等价的,都是表示变量的值不能被改变,需要注意的是,用const修饰变量时,一定要给变量初始化,否则之后就不能再进行赋值了,而且编译器也不允许不赋初值的写法:
在C++中不赋初值的表达一写出来,编译器即报错,且编译不通过。


在C中不赋初值的表达写出来时不报错,编译时只有警告,编译可以通过。而当你真正给它赋值时才会报错,那么没有初值也不能被赋值这样的变量有什么用哪?

const chsr* p = "qwerty"; //const用于修饰常量静态字符串
如果没有const的修饰,我们可能会在后面有意无意的写p[4]=’x’这样的语句,这样会导致对只读内存区域的赋值,然后程序会立刻异常终止。有了const,这个错误就能在程序被编译的时候就立即检查出来,这就是const的好处。让逻辑错误在编译期被发现。(这个特性在C/C++中相同)
3.修饰指针
常量指针是指针所指向的内容是常量,不可被修改。
const int * n = &a;
int const * n = &a;
上面两种写法也是等价的,性质如下:
1)常量指针不能通过这个指针改变变量的值,但是可以通过其他的引用来改变变量的值的。
const int *n = &a;
*n = b;
上面的写法报错
int c = ;
const int *n = &a;
a = ;
a = c;
这样赋值是可以的。
常量指针指向的值不能改变,但是指针本身可以改变,即常量指针可以指向其他的地址。
int a = ;
int b = ;
const int *n = &a;
n = &b;
2)指针常量是指指针本身是个常量,不能在指向其他的地址,写法如下:
int a = ;
int b = ;
int * const n = &a;
*n = b;
5 b = a;
而这么写是错误的
int a = ;
int b = ;
int c = ;
int * const n = &a;
n = &b;
它们的区别在于const的位置,可以这样记忆:const在“*”前面时它修饰(*n),而*n是n所指向的变量,所以是常量指针,const在“*”后面时它修饰(n),使指针变为常量,所以是指针常量。
指向常量的常指针
const int * const p= &a;
int const * const p= &a;
指针指向的位置不能改变并且也不能通过这个指针改变变量的值,但是依然可以通过变量赋值,或其他的普通指针改变变量的值。
(这种用法在C和C++中是相同的。)
4.修饰引用
int a = ;
int const &a = b;
const int &a = b;
两种定义形式在本质上是一样的
5.函数中使用const
(1)修饰函数参数
根据const修饰指针的特性,const修饰函数的参数也是分为三种情况
void StrCopy(char *strdes, const char *strsrc);//防止修改指针指向的内容
其中 strsrc是输入参数,strdes是输出参数。给 strsrc 加上 const 修饰后,如果函数体内的语句试图改动 sresrc 的内容,编译器将指出错误。
void swap ( int * const p1 , int * const p2 ) //防止修改指针指向的地址
指针p1和指针p2指向的地址都不能修改。
void test ( const int * const p1 , const int * const p2 ) //以上两种的结合
另外当参数为引用时
void function(const Class& Var); //引用参数在函数内不可以改变
void function(const TYPE& Var); //引用参数在函数内为常量不可变
(这样的一个const引用传递和最普通的函数按值传递的效果是一模一样的,他禁止对引用的对象的一切修改,唯一不同的是按值传递会先建立一个类对象的副本, 然后传递过去,而它直接传递地址,所以这种传递比按值传递更有效.另外只有引用的const传递可以传递一个临时对象,因为临时对象都是const属性, 且是不可见的,他短时间存在一个局部域中,所以不能使用指针,只有引用的const传递能够捕捉到这个家伙。)
(2)修饰函数返回值
如果给以“指针传递”方式的函数返回值加 const 修饰,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给加const 修饰的同类型指针。
const int * fun2() //调用时 const int *pValue = fun2();
//我们可以把fun2()看作成一个变量,即指针内容不可变。
c.int* const fun3() //调用时 int * const pValue = fun2();
//我们可以把fun2()看作成一个变量,即指针本身不可变。
const int fun1() //这个其实无意义,因为参数返回本身就是赋值。
6.修饰类相关
(1)用const修饰的类成员变量,只能在类的构造函数初始化列表中赋值,不能在类构造函数体内赋值。
class A
{
public:
A(int x) : a(x) // 正确
{
//a = x; // 错误
}
private:
const int a;
};
(2)const修饰成员函数
用const修饰的类成员函数,在该函数体内不能改变该类对象的任何成员变量, 也不能调用类中任何非const成员函数。一般写在函数的最后来修饰。
class A
{
public:
int& getValue() const
{
// a = 10; // 错误
return a;
}
private:
int a; // 非const成员变量
};
(3)const修饰类对象/对象指针/对象引用
用const修饰的类对象表示该对象为常量对象,该对象内的任何成员变量都不能被修改。对于对象指针和对象引用也是一样。
因此不能调用该对象的任何非const成员函数,因为对非const成员函数的调用会有修改成员变量的企图。
class A
{
public:
void funcA() {}
void funcB() const {}
};
int main
{
const A a;
a.funcB(); // 正确
a.funcA(); // X const A* b = new A();
b->funcB(); // 正确
b->funcA(); // X
}
(4)在类内重载成员函数
class A
{
public:
void func() {}
void func() const {} // 重载
};
另外,const数据成员只在某个对象生存期内是常量,而对整个类而言是可变的,因为类可以创建多个对象,不同对象的const数据成员值可以不同。
class A
{
public:
A(int size)
: _size(size) // 正确
{}
private:
const int _size;
};
A a(); //对象a的_size值为10
A b(); //对象b的_size值为20
那么,怎样才能建立在整个类中都恒定的常量呢?用枚举常量。
class A
{
public:
enum{SIZE1 = , SIZE2 = };//枚举常量
private:
int arr1[SIZE1];
int arr2[SIZE2];
};
枚举常量不会占用对象的存储空间(整个枚举类型只占四个字节),它们在编译时被全部求值。但缺点是隐含数据类型只能是整数,最大值有限,且不能表示浮点数。
7.修饰全局变量
全局变量的作用域是整个文件,我们应该尽量避免使用全局变量,以为一旦有一个函数改变了全局变量的值,它也会影响到其他引用这个变量的函数,导致除了bug后很难发现,如果一定要用全局变量,我们应该尽量的使用const修饰符进行修饰,这样方式不必要的以为修改,使用的方法与局部变量是相同的。
8.const常量与宏常量的区别
(1).便于进行类型检查
const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查,而对后者只进行字符替换,没有类型安全检查,并且在字符替换时可能会产生意料不到的错误(边际效应)。
//例子:
void f(const int i) { .........} //对传入的参数进行类型检查,不匹配进行提示
(2)可以节省空间,避免不必要的内存分配
const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是象#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而#define定义的常量在内存中有若干个拷贝。
#define PI 3.14159 //常量宏
const doulbe Pi=3.14159; //此时并未将Pi放入ROM中
......
double i=Pi; //此时为Pi分配内存,以后不再分配!
double I=PI; //编译期间进行宏替换,分配内存
double j=Pi; //没有内存分配
double J=PI; //再进行宏替换,又一次分配内存!
(3)提高了效率
宏定义是一个“编译时”概念,在预处理阶段展开,不能对宏定义进行调试,生命周期结束于编译时期。const常量是一个“运行时”概念,在程序运行时使用,类似于一个只读数据。
编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高
(4)可以保护被它修饰的东西
防止意外的修改,增强程序的健壮性。
void f(const int i) { i=;//error! } //如果在函数体内修改了i,编译器就会报错
(5)为函数重载提供了一个参考
class A
{
......
void f(int i) {......} //一个函数
void f(int i) const {......} //上一个函数的重载
......
};
(6)定义域不同
void f1 ()
{
#define N 12
const int n ;
}
void f2 ()
{
cout<<N <<endl; //正确,N已经定义过,不受定义域限制
cout<<n <<endl; //错误,n定义域只在f1函数中。若想在f2中使用需定义为全局的
}
(7)做函数参数
宏定义不能作为参数传递给函数;const常量可以在函数的参数列表中出现。
9.const_cast
const_cast运算符用来修改类型的const或volatile属性。
(1)常量指针被转化成非常量的指针,并且仍然指向原来的对象;
(2)常量引用被转换成非常量的引用,并且仍然指向原来的对象。
void func()
{
const int a = ;
int* p = const_cast<int*> (&a);
*p = ;
std::cout<<*p; //
std::cout<<a; //
}
注:C++中使用const 常量而不使用宏常量,即const 常量完全取代宏常量。
10.const与volatile
//注意使用C++编译器编译(源文件使用. cpp后缀)
//或者在C编译器中编译期间打开优化选项, 比如:
// gcc test. c -O2
#include <stdi o. h>
int main()
{
const int num = ;
int *p = (int *) #
*p = ;
printf("%d\n", num) ;
return ;
}/
/看看程序输出的结果是什么?
只要编译器适当的对代码进行优化, 这里就可能输出: 10, 而不是我们改变之后的值。因为编译器在编译期间, 可能对代码进行优化。当编译器看到这里的num被const修饰, 从语义上讲这里的num是不期望被改变(不改变)的,那优化的时候就可以把num的值存放到寄存器(以提高访问的效率) 中。 以后只要使用num的地方都去寄存器中取, 那即使num对应的内存中的值发生变化, 寄存器也是感知不到的。 所以造成输出10的结果。
//当我们对代码进行如下修改:
#include <stdi o. h>
int main()
{
//使用volati le关键字对num修饰。
volatile const int num = ;
int *p = (int *) #
*p = ;
printf("%d\n", num) ;
return ;
}
这里我们可以看到, 当我们对*p做了修改之后, num的输出变成了20。这里 volatile 这个关键字起到关键的作业。
作用:
编译时不优化, 执行时不缓存, 每次需从内存中读出(保证内存的可见性) 。
使用场景:
用于多线程或多CPU编程。
ps:高质量C/C++第5章、第11章。
C/C++中const关键字的用法及其与宏常量的比较的更多相关文章
- C/C++中const关键字的用法及其与宏定义的比较
1.const关键字的性质 简单来说:const关键字修饰的变量具有常属性. 即它所修饰的变量不能被修改. 2.修饰局部变量 ; ; 这两种写法是等价的,都是表示变量的值不能被改变,需要注意的是,用c ...
- C++中const 的各种用法
C++中const 关键字的用法 const修饰变量 const 主要用于把一个对象转换成一个常量,例如: ; size = ; // error: assignment of read-only v ...
- C++中const简介及用法
1.const简介 C++中的const关键字的用法非常灵活,而使用const将大大改善程序的健壮性,本人根据各方面查到的资料进行总结如下,期望对朋友们有所帮助. Const 是C++中常用的类型修饰 ...
- C/C++ 中 const 修饰符用法总结
C/C++ 中 const 修饰符用法总结 在这篇文章中,我总结了一些C/C++语言中的 const 修饰符的常见用法,供大家参考. const 的用法,也是技术性面试中常见的基础问题,希望能够帮大家 ...
- C++中typename关键字的用法
我在我的 薛途的博客 上发表了新的文章,欢迎各位批评指正. C++中typename关键字的用法
- c#多线程中Lock()关键字的用法小结
本篇文章主要是对c#多线程中Lock()关键字的用法进行了详细的总结介绍,需要的朋友可以过来参考下,希望对大家有所帮助 本文介绍C# lock关键字,C#提供了一个关键字lock,它可以把一段 ...
- 解读typescript中 super关键字的用法
解读typescript中 super关键字的用法 传统的js,使用prototype实现父.子类继承.如果父.子类有同名的方法,子类去调用父类的同名方法需要用 “父类.prototype.metho ...
- const关键字:终于拥有真正的常量声明语句
本文首发于个人网站:const关键字:终于拥有真正的常量声明语句 你好,今天大叔想和你唠扯唠扯 ES6 新增的关键字 -- const.在说 const 关键字之前,大叔先和你唠唠大叔自己对 cons ...
- C++中const关键字的使用总结
C++中使用const关键字来修饰常量,下面从两个方面总结:变量和成员函数. 变量:const可以修饰普通变量.指针(数组)和结构体. 1.const修饰普通变量是最简单的情形.这样的用法多为在程序中 ...
随机推荐
- 《Effective C++》继承与面对对象设计:条款32-条款40
条款32:确定你的public继承塑模出is-a关系 public继承意味着is-a.适用于base class身上的每一个函数也一定适用于derived class. 条款33:避免遮掩继承而来的名 ...
- linux中去掉^M的方法
转:https://blog.csdn.net/sty945/article/details/80347901 (1)是用VI的命令: 在命令模式下运行命令 :%s/^M//g 回车 注意:手动输入该 ...
- Server酱
功能:从服务器推报警和日志到手机上的工具 使用: 发送消息非常简单,只需要向以下URL发一个GET或者POST请求: https://sc.ftqq.com/SCU34756Ta63843ce61a3 ...
- CAN报文 Intel 格式与Motorola 格式的区别
当一个信号的数据长度不超过 1 个字节(8 位)时,Intel 与 Motorola 两种格式的 编码结果没有什么不同,完全一样.当信号的数据长度超过 1 个字节(8 位)时,两者的编码结果出现 了明 ...
- mysql配置记录
[mysqld] datadir=/app/data/mysql socket=/app/data/mysql/mysql.sock symbolic-links=0 validate_passwor ...
- Error: Can't resolve 'babel-loader'
在控制台中运行命令“webpack”,出现错误:“ERROR in Entry module not found: Error: Can't resolve 'babel-loader' in.... ...
- Cucumber常用关键字
常用关键字(中英文对应) 对应的测试用例 Feature(功能) test suite (测试用例集) background(背景) Scenario(场景) test case(测试用例) Sc ...
- Androidstudio 使用git插件提交代码
1.androidstudio中的项目已经推送到git仓库上(与仓库已经建立了联系) 参见: 2.右键目录--git---commit directory : 3.填写相应的commit Messig ...
- RocketMQ_问题_启动控制台console报错,connect to <null> failed
配置如图所示: 问题如图所示: 明明配置了nameserver参数为啥还是connect to null呢? 我的rocketmq部署在CentOS7虚拟机上,原来是没关闭防火墙,执行:systemc ...
- GIT-Bonobo.Git.Server的使用
GIT-Bonobo.Git.Server的使用 登录 (默认用户:admin/admin) 创建用户: 创建团队,勾选用户 创建新库 保存完成!