C++中的const关键字的用法非常灵活,而使用const将大大改善程序的健壮性,const关键字是一种修饰符。修饰符本身,并不产生任何实际代码。就 const 修饰符而言,它用来告诉编译器,被修饰的这些东西,具有只读的特点。在编译的过程中,一旦我们的代码试图去改变这些东西,编译器就应该给出错误提示。
const的作用
const用法分类
常变量: const 类型说明符 变量名
常引用: const 类型说明符 &引用名
常数组: 类型说明符 const 数组名[大小]
常指针: const 类型说明符* 指针名 ,类型说明符* const 指针名
常对象: 类名 const 对象名
常数据成员: const 类型说明符 数据成员名
常成员函数: 类名::fun(形参) const(此成员函数为只读)
在常变量(const 类型说明符 变量名)、常引用(const 类型说明符 &引用名)、常对象(类名 const 对象名)、 常数组(类型说明符 const 数组名[大小])中, const 与“类型说明符”或“类名”(其实类名是一种自定义的类型说明符) 的位置可以互换
const常量与宏定义的常量的区别:
这个区别用从几个角度来说:
1.const 定义的常数是变量 也带类型, #define 定义的只是个常数 不带类型。
2. define是在编译的预处理阶段起作用,而const是在 编译、运行的时候起作用。
3.define只是简单的字符串替换,没有类型检查。而const有对应的数据类型,是要进行判断的,可以避免一些低级的错误。
由于define只是简单的字符串替换会导致边界效应,具体举例可以参考下面代码:
#define N 2+3 //我们预想的N值是5,我们这样使用N
double a = N/2; //我们预想的a的值是2.5,可实际上a的值是3.5
4.占用的空间不同
#define PI 3.14 //预处理后 占用代码段空间
const float PI=3.14; //本质上还是一个 float,占用数据段空间
5. const常量可以进行调试的,define是不能进行调试的,因为在预编译阶段就已经替换掉了
C 与C++中const的区别:
C语言 :
1、 const 定义的变量可以初始化,也可以不初始化
2、 const修饰的量不能用于左值
3、不能当作常量来使用,其实const修饰的应该叫做常变量
4、 常变量const的编译方式,和普通变量的编译方式一样
C++:
1、 const修饰的变量一定要初始化
2、const定义的量叫做 常量,这是真常量,可以用其来定义数组长度
3、常量const在编译方式,在编译时期,将常量的值将其替换,因此有时会退化为常变量。
4、 const定义的量叫常量,有时退化为常变量 例如 如下代码:
int a = 10;
const int b = a;
//编译时期a替换b 但是编译时期a是不明确的值 此时和普通变量没区别(除了不能作为左值)
出现const的代码段中,一般出现问题主要原因
1.作为左值
const int a = 10;
a=20;
2.把const量的地址或者引用给泄露出去
const int a = 10;
int *p = &a; // 泄露a的地址,可能会修改a,可修改为 const int *p = &a
int &q = a;
const和一级指针的结合
const修饰的是最近的类型,const右边有*时 考虑const 没有不考虑
int a = 10;
const int *p = &a;// *p是常量不能改值 指针可以随便值
int const *p = &a;
int *const p = &a; //p是不能改的值
也可以使用以下公式(自己总结)
const int* == int *const // ok
int * == const int * // err
const int* == int * // ok
const和二级指针的结合
int a = 10;
int *p = &a;
// const int ** = int ** err
// int ** = const int ** err
// int * = const int * err
// const int * = int * ok
// int *const * = int** ok
// int ** = int *const * err
// int **const = int** ok
// int *const * = const int** err
请选择下面哪些代码是错误的?__A__
A.
int a = 10;
const int *p = &a;
int *q = p;
B.
int a = 10;
int *const p = &a;
int *q = p;
C.
int a = 10;
int *const p = &a;
int *const q = p;
D.
int a = 10;
int *const p = &a;
const int *q = p;
请判断下面哪些代码是错误的?_ BCD___
A.
int a = 10;
int *p = &a;
int *&q = p;
B.
int a = 10;
int *const p = &a;
int *&q = p;
C.
int a = 10;
const int *p = &a;
int *&q = p;
D.
int a = 10;
int *p = &a;
const int *&q = p;
请判断下面哪些代码有错误?_ ADE____
A.
int a = 10;
int *p = &a;
const int **q=&p;
B.
int a = 10;
int *p = &a;
int * const *q = &p;
C.
int a = 10;
int *p = &a;
int **const q = &p;
D.
int a = 10;
int *const p = &a;
int **q = &p;
E.
int a = 10;
const int *p = &a;
int * const * q = &p;
分析i
nt a = 10;
const int *p = &a;
int *q = p; // int * = const int * err
int a = 10;
int *const p = &a;
int *q = p;
int a = 10;
int *const p = &a;
int *const q = p;
int a = 10;
int *const p = &a;
const int *q = p; // const int * = int *const const右边有* 编译器考虑 const int * = int * ok
int a = 10;
int *p = &a;
int *&q = p;
int a = 10;
int *const p = &a;
int *&q = p;// int ** = int * const * err
int a = 10;
const int *p = &a;
int *&q = p;// int**q = &p int ** = const int ** err
int a = 10;
int *p = &a;
const int *&q = p;// const int ** = int ** err
int a = 10;
int *p = &a;
const int **q = &p; // const int ** = int ** err
int a = 10;
int *p = &a;
int *const*q = &p;
int a = 10;
int *p = &a;
int **const q = &p;
int a = 10;
int *const p = &a;
int **q = &p;// int ** = int * const * err
int a = 10;
const int *p = &a;
int *const* q = &p; // int *const * = const int** err
cosnt修饰指针
const修饰指针,涉及到两个很重要的概念,顶层const和底层cosnt
指针自身是一个对象,它的值为一个整数,表明指向对象的内存地址。因此指针长度所指向对象类型无关,在32位系统下为4字节,64位系统下为8字节。进而,指针本身是否是常量以及所指向的对象是否是常量就是两个独立的问题。
顶层const(top-level const): 指针本身是个常量
底层const(low-level const): 指针指向对象是一个常量
int a = 1;
int b = 2;
const int* p1 = &a;
int* const p2 = &a;
根据从内到外,由近到远读符号的规则
p1依次解读为:p1是个指针(*),指向一个int型对象(int),该对象是个常量(const)。 因此这是一个底层cosnt
p2依次解读为:p2是个常量(const),p2是个指针(*),指向一个int对象(int)。 因此这是一个顶层const
const修饰函数参数
const修饰参数是为了防止函数体内可能会修改参数原始对象。因此,有三种情况可讨论:
- 函数参数为值传递:值传递(pass-by-value)是传递一份参数的拷贝给函数,因此不论函数体代码如何运行,也只会修改拷贝而无法修改原始对象,这种情况不需要将参数声明为const。
- 函数参数为指针:指针传递(pass-by-pointer)只会进行浅拷贝,拷贝一份指针给函数,而不会拷贝一份原始对象。因此,给指针参数加上顶层const可以防止指针指向被篡改,加上底层const可以防止指向对象被篡改。
- 函数参数为引用:引用传递(pass-by-reference)有一个很重要的作用,由于引用就是对象的一个别名,因此不需要拷贝对象,减小了开销。这同时也导致可以通过修改引用直接修改原始对象(毕竟引用和原始对象其实是同一个东西),因此,大多数时候,推荐函数参数设置为pass-by-reference-to-const。给引用加上底层const,既可以减小拷贝开销,又可以防止修改底层所引用的对象。
const修饰函数返回值
令函数返回一个常量,可以有效防止因用户错误造成的意外。
比如
if (a*b = c)
如果a,b,c都是如同int的内置类型,编译器会直接报错
因为对于内置类型的*操作返回的不是一个左值,因此不能放在=的左边。为什么会出现这种情况呢?可能用户只是想比较是否相等,却打字打漏了一个等号(ORZ)。
使用const的一些建议
· 要大胆的使用const,这将给你带来无尽的益处,但前提是你必须搞清楚原委;
· 要避免最一般的赋值操作错误,如将const变量赋值,具体可见思考题;
· 在参数中使用const应该使用引用或指针,而不是一般的对象实例,原因同上;
· const在成员函数中的三种用法(参数、返回值、函数)要很好的使用;
· 不要轻易的将函数的返回值类型定为const;
· 除了重载操作符外一般不要将返回值类型定为对某个对象的const引用;
· 任何不会修改数据成员的函数都应该声明为const 类型。