const浅析
前言
c++中使用到const
的地方有很多, 而且const
本身也针对不同的类型可能有不同的含义, 比如对指针就有顶层和底层. 本节就是探讨关于C++中const
的在不同的地方不同表现或含义.
const
关于const :
const
修饰的对象一旦创建一般就不能改变, 所以对于const
对象必须进行初始化.int i = 0;
const int j; // error. 必须进行初始化
const int j = 0;
初始化时并不关心初始化对象的是
const
还是非const
int i = 0;
const int j = i; // i 是非const也可以
const
不能改变const int i = 0;
i = 1; // error const的对象一般不能进行修改
引用对象的类型必须与其所引用对象的类型一致
int i = 0;
int &j = i;
double &size = j; // error. size与j的类型不一致
- 因为以上引用的规则, 所以
const
类型的引用只能被const
的对象引用
int i = 0;
const int &size = i;
int &j = size; // error. size的类型为const int, j的类型为 int. 两者并不匹配
引用类型对应的例外
int size = 0;
const double &i = size; // size与i的类型虽然不一致, 但是因为const的原因使得等式成立
原因 : 虽然i与size两者的类型并不一致, 但是初始化i时, 编译器会为size生成一个临时量(
double j = size;
), 然后i最终绑定在这个临时量上(const double &i = j )
. i 之所以能绑定在一个临时量上, 还是因为const
的对象不能被修改, 则i 无法被修改, 保障了临时量不会被改变.注意 i实际绑定在临时量上, 并没有绑定在size上
int size = 0;
const double &i = size;
size = 1; // i 实际值并没有改变, 它绑定的是临时量不是size
- 因为以上引用的规则, 所以
修改
const
对象的值int i = 0;
const int size = i;
const int &j = i;
const_cast<int&>(size) = 1; // 将size的值修改为1
i = 2; // 因为j绑定i, i被修改则j也被修改
因为
const
只是对修饰的对象限制其不能修改, 不能保证对象一定是常量, 所以能保证是常量的对象最好都定义成constexpr
. 对constexpr不清楚的可以看一下constexpr浅析
顶层与底层const概念
顶层const
: 指针本身是一个常量(即地址不允许改变).
其实我们一直都有在用顶层const, 比如int i = 0;
, 这就是一个顶层const, 因为 i 的地址不会改变, 只有值会被改变.
int size = 0, i = 0; // 其实是顶层const
int *const p = &size; // const直接修饰指针本身, 顶层const
p = &i; // error. p是顶层const
*p = 1; // 顶层const可以直接修改值
底层const
: 指针所指的对象是一个常量(指针本身是可以修改的, 只是指向的值不能进行修改).
int size = 0, i = 0;
const int * p = &size; // const直接修饰指针指向的对象, 底层const
ptr = &i; // ptr可以重新指向其他地址, 因为是底层const
*ptr = 1; // error. 底层const不能直接修改指向的值
当然我们可以将一个对象修饰为既是顶层又是底层
int size = 0;
const int * const p = &size; // 既是顶层又是底层
const int i = 0; // 既是顶层又是底层
有一点一定要注意 : 顶层const被拷贝时会忽略掉顶层const
const int i = 0;
int size = i; // 这里的顶层const被忽略掉了
auto j = i; // 此时 auto 推断出 j的类型为 int , i 的顶层const被忽略了
const与重载函数
在重载函数时, const
类型的参数可能会在处理顶层const与底层const的时候出现问题. 具体什么问题分析之后再来总结.
void Ccount(int ccount) {} // ccount为顶层const
void Ccount(const int ccount) {} // error. ccount也是为顶层const, 赋值时会忽略掉顶层const, 就与上面的函数一样了
void Ccount_pointer(int *ccount) {} // ccount为顶层const
void Ccount_pointer(int *const ccount) {} // error. ccount也是为顶层const, 赋值时会忽略掉顶层const, 就与上面的函数一样了
上面可以看出来, 因为顶层const会被忽略, 所以顶层const与另外顶层const不能被区分出来.
// error. 在函数调用的时候有二义性, 并不能区分调用哪一个函数, 在编译期间报错.
void const_reference(int i) {} // i 是顶层const. 参数类型为 int
void const_reference(int &i) {} // i 是顶层const. 参数类型为 int
// 下面都没有问题
void const_reference(int &i) {} // i 是顶层const. 参数类型为 int
void const_reference(const int &i) {} // i 是底层const. 参数类型为const int
void const_pointer(int *i) {} // 顶层const
void const_pointer(const int *i) {} // 底层const
因为引用对象的类型必须相同, 所以int &i
与const int &i
有区别, 前者类型为int
, 后者类型为const int
, 所以后者是底层const.
上面可以看出来, 因为底层const不会被忽略, 底层与底层有区分, 所以可以底层const可以用来重载.
const与类的常量成员函数
如果const
放在函数名的前面其意义只是告诉编译器返回类型是const
类型的常量而已, 但是如果把const
放在函数名后那就又是另一种情况了, 我们这里主要分析的就种情况.
const int const_func(int i) {return i;} // 这里函数返回的是const类型的, 即常量
int const_func(int i) const {return i;} // error. const不能直接放在普通函数名的后面, 只能放在成员函数(类函数)名的后面,原因之后分析.
定义一个简单的类
class A {
private: int nun;
public: int const_func() const {return 0;} // success
};
// 如果将函数改为
int const_func() const { ++num; return 0;} // error
int const_func() const
函数中const
是告诉编译器, 类中定义的非静态变量都不能进行修改. 原因在于类的所有成员函数都会隐式的传入this 指针
, 即上面的成员函数被修改为
int const_func(const A * const this) { ++num; return 0;}
this
指针本身就是顶层const, 而放在函数名后面的const是为了修饰this指针的, 但是因为this指针不能显示的被传入, 所以const只能放在函数名后.
知道了这里const
修饰的是this 指针
, 所以this->i
就不能被修改了, 而静态成员不是属于实例化类本身, 也就没有this指向静态变量, 所以可以在以上类型的函数中修改静态变量.
const
放在成员函数名后面的函数我们称为常量成员函数
但是有的时候非要在以上函数中改变某个变量的值怎么办? c++中有mutable
关键字, 就是允许这样的特例发生. mutable
就是告诉编译器, num
可以在任何函数中进行修改.
class A {
private: mutable int nun;
public: void const_func() const {++num;} // success
};
const与类
我们在定义类的实例化时, 可能会将类实例化定义为const
, 即
class A {
private: int nun;
public: int const_func() {return 0;}
};
A a;
const A ca;
a.const_func(); // success
ca.const_func(); // error
上面出错的原因在于ca的类型为const
, 所以与之对应的函数应该是常量成员函数, 所以最好在定义类函数实现时, 重载一个常量成员函数.
const与类静态成员
同样上面的类为例子
class A {
private: static int nun = 0; // error
public: int const_func() const {++num; return 0;} // success. 原因上面分析了
};
在类中定义的静态变量不能在类中初始化, 必须在类外进行初始化, 不然报错. 所以上面应该在类外改为int A::num = 0;
.
但是有一个例外 :
class A {
private: const static int nun = 0; // success
};
因为const
要求必须在创建的时候就需要对其初始化, 所以上式的例子才成立.
const与typedef
当我们不愿意每次都定义指针的时候, 就想到用typedef
来定义指针类型. 即:
typedef char * Str;
char *str1 = "hello";
const char *str2 = "hello";
const Str str3 = "hello";
对其进行相同的操作
str1[0] = 'a';
str2[0] = 'a'; // error
str3[0] = 'a'; // success
str1++;
str2++; // success
str3++; // error
以上面的执行的操作可以看出来typedef
不仅仅只是一个替换, 它将const Str str3
转换为了char *const str3
而不是跟str2一样.
原因是 : char *
重写声明之后, 真实的数据类型变成了char
而不是char *
, 反而*
成了声明符的一部分了, 导致const Str
的数据类型为const char
, 而*
修饰const char
, 也就成了常量指针.
总结
本节汇总了部分关于const
用法的注意点, 可能看起来会很晕, 也不是一次性就容易记住, 希望在看的时候最好也进行验证是最好的. 最主要记住底层const
和顶层const
, 怎样重载, 基本很多的问题都是衍生.
const浅析的更多相关文章
- openssl 1.1.1 reference
openssl 1.1.1 include/openssl aes.h: # define HEADER_AES_H aes.h: # define AES_ENCRYPT 1 aes.h: # de ...
- JavaScript中const、var和let区别浅析
在JavaScript中有三种声明变量的方式:var.let.const.下文给大家介绍js中三种定义变量的方式const, var, let的区别. 1.const定义的变量不可以修改,而且必须初始 ...
- 浅析const标识符在C++函数的功能
范例: class matrix { public: matrix(){}; const double getvalue(const unsigned row, const unsigned colu ...
- 浅析const、let与var
以前无论声明变量还是常量,总是使用var一勺端,知道接触了es6之后,发现原来变量.常量的声明其实是很讲究的. 这里简单来谈谈var.const与let. 1.var.var声明的变量没有块级作用域, ...
- const关键字浅析
1 const变量 const double PI = 3.14159; 定义之后不能被修改,所以定义时必须初始化. const int i, j = 0; // error: i is unini ...
- MySQL:浅析 Impossible WHERE noticed after reading const tables
使用 EXPLAIN 执行计划的时候,在 Extra 中偶尔会看到这样的描述: Impossible WHERE noticed after reading const tables 字面上的意思是: ...
- 浅析匿名函数、lambda表达式、闭包(closure)区别与作用
浅析匿名函数.lambda表达式.闭包(closure)区别与作用 所有的主流编程语言都对函数式编程有支持,比如c++11.python和java中有lambda表达式.lua和JavaScript中 ...
- Linux模块机制浅析
Linux模块机制浅析 Linux允许用户通过插入模块,实现干预内核的目的.一直以来,对linux的模块机制都不够清晰,因此本文对内核模块的加载机制进行简单地分析. 模块的Hello World! ...
- Erlang/OTP 17.0-rc1 新引入的"脏调度器"浅析
最近在做一些和 NIF 有关的事情,看到 OTP 团队发布的 17 rc1 引入了一个新的特性“脏调度器”,为的是解决 NIF 运行时间过长耗死调度器的问题.本文首先简单介绍脏调度器机制的用法,然后简 ...
随机推荐
- Nodejs创建HTTPS服务器
Nodejs创建HTTPS服务器 从零开始nodejs系列文章,将介绍如何利Javascript做为服务端脚本,通过Nodejs框架web开发.Nodejs框架是基于V8的引擎,是目前速度最快的Jav ...
- #HDU 3790 最短路径问题 【Dijkstra入门题】
题目: 最短路径问题 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total ...
- LINKs: Xamarin.Forms + Prism
LINK 1 - How to use Prism with Xamarin.Forms http://brianlagunas.com/first-look-at-the-prism-for-xam ...
- 深入理解 JBoss 7/WildFly Domain 模式启动过程
概述 JBoss 7/WildFly 以 domain 模式启动时会启动多个 JVM.比如例如以下通过启动脚本启动 domain 模式: ./domain.sh 启动后我们查看进程: [kylin@l ...
- Dungeon Game -- latched
The demons had captured the princess (P) and imprisoned her in the bottom-right corner of a dungeon. ...
- ios14--购物车优化2
// // ViewController.m // 03-综合练习 // // Created by xiaomage on 15/12/28. // Copyright © 2015年 小码哥. A ...
- ios10--拳皇动画
/** 图片的两种加载方式: 1> imageNamed: a. 就算指向它的指针被销毁,该资源也不会被从内存中干掉, b. 放到Assets.xcassets的图片,默认就有缓存, c. 图片 ...
- poj2104 k-th number 主席树入门讲解
poj2104 k-th number 主席树入门讲解 定义:主席树是一种可持久化的线段树 又叫函数式线段树 刚开始学是不是觉得很蒙逼啊 其实我也是 主席树说简单了 就是 保留你每一步操作完成之后 ...
- 在IIS上搭建WebSocket服务器(三)
编写客户端代码 1.新建一个*.html文件. ws = new WebSocket('ws://192.168.85.128:8086/Handler1.ashx?user=' + $(" ...
- Dice (HDU 4652)
题面: m 面骰子,求1. 出现n个连续相同的停止 ;2. 出现n个连续不同的停止的期望次数.(n, m ≤ 10^6 ) 解析: 当然要先列式子啦. 用f[i](g[i])表示出现i个连续相同(不相 ...