1.const和变量
例如:
const int a = 10;
这将使得a的值为一个常量,后面无法改变,需要注意 使用const修饰的变量必须被初始化const int a;
这样的代码会使得编译失败。
2.const和引用
int a = 10;
const int &b = a;
使用const修饰的引用将无法改变引用对象的值,即类似
b = 5;
这样的代码会使得编译失败。
所以这样的引用方式常常可以用在函数形参中,来保证函数内部只能对对象进行“读”操作而无法进行“写”操作。举个例子
inline int max(const int &a,const int &b)
{
return a<b?b:a;
}
上述代码是正确的,只对被引用的对象进行“读”操作。
inline voidswap(const int &a,const int &b)
{
a ^= b;
b ^= a;
a ^= b;
}
上述代码会使得编译失败,因为尝试改变被const修饰的引用对象的值。
3.const 和 指针
最常见的例子,
int a = 10;
int *const p1 = &a;
const int *p2 = &a;
分别阐述两个const的作用,
讲讲区分方式:从右往左读,第一个const修饰的是p1,
第二个const修饰的是 整个(int *p2)。
第一个const的作用:修饰p,表示指针p1的值本身是一个常量,无法被改变,即类似
int b = 20;
p1 = &b;
这样的代码是错误的,因为p1被const修饰后,值无法改变。但是
*p1 = 15;
这行代码没有错。
第二个const的作用:修饰整个(int * p2),表示p2指针指向的对象的值无法被改变,是一个指向常量的指针,即
*p2 = 15;
这行代码是错误的,但是
int b = 20;
p2 = &b;
这样的代码没有错
4.顶层const 和 底层const
所谓顶层const 是指 变量本身的值不可变,那这个const就是一个顶层const
底层const值 指针变量指向的对象不可变,那这个const为一个底层const
int a = 10;
int *const p1 = &a;//这个是底层const
const int *p2 = &a;//这个是顶层const
5.constexpr
constexpr也是一个修饰符,用于表示要修饰的变量变成一个常量表达式
即 constexpr int a = 10;
这样的常量表达式需要满足的要求是程序在编译过程就可以 get到这个变量的值,而不是在运行阶段才能get到。例如
srand(time(0));
constexpr int a = rand();
就会使得编译失败,因为rand函数在运行阶段才能获得返回值,a才能被初始化。
比较下面两行代码
int a = 10;
constxpr int *p1 = &a;
const int *p2 = &a;
这两行代码看起来类似,但是事实上p1和p2的类型是不同的。
从上文的描述中可以获知得const int *p2 = &a;
中的const是一个底层const,表示指向的对象的值无法被改变,是一个指向常量的指针。
而constxpr int *p1 = &a;
则表示 p1 本身 是一个常量,无法改变,也就相当于一个 顶层const。
6.const 和 类的成员函数
可以在类中成员函数声明的最后加上const修饰符,作用是使得该成员函数 无法对类中的对象的值进行改变。
template<typename T>
class stack{
private:
std::vector<T> elems;
public:
pop();
push();
top()const;
bool empty()const;
}
如上面的例子中,这是一个stack类数据结构的声明,其中成员函数 pop 和 push 的作用是往栈中添加、删除元素,这就会对类中的elems对象就行改变,所以没有用const修饰;而top函数和empty函数只是简单的返回头元素 和 判断是否为空,并没有对对象进行改变,所以加了const修饰。
7. const 和 define宏定义
1)const定义的常量有类型,#define定义的没有类型。
编译时可以对前者进行类型安全检查,而后者仅仅是简单的文本替换。
2)const定义的常量在编译时分配内存,只展开一次。
#define定义的常量是在预编译时进行替换,不分配内存,但是在编译过程中会多次展开,浪费资源。
3)作用域不同,const定义的作用域为该变量的作用域范围。而#define定义的常量
作用域为它的点到程序结束,当然也可以在某个地方用#undef取消。