目录
const 限定符
使用变量的好处是当我们觉得变量值不合适时可以随时调整,但是这也会带来弊端,比如容易无意间改变了它的值,为了避免这种情况,可以用关键字const对变量的类型加以限定。
初始化
用const限定符定义的对象必须初始化。
在指针和引用这篇博客中,我曾介绍了对于const 指针和const 引用的初始化方式。
其实const类型的对象能执行大部分非const类型对象的操作,只有对const类型的对象执行改变其内容的操作时,该限定符才起作用,不仅如此,如果利用一个对象去初始化另外一个对象,则它们是不是const都无所谓。
int i = 10; const int ci = i; //正确:i的值被拷贝给了ci int j = ci; //正确:ci的值被拷贝给了j
拷贝一个对象的值并不会改变被拷贝对象的值,一旦拷贝完成,新的对象和被拷贝对象就是两个独立的对象
const修饰指针
顶层const:指针本身是个常量
低层const:指针所指向的对象是个常量或者引用绑定的对象是一个常量
- 对于对象类型为
基本数据类型和类情况,其const都是顶层const
class A {
public: int val; }; int main() {
const int i = 10; //不能改变i的值,这是一个顶层const const double d = 1.2; //不能改变d的值,这是一个顶层const const char c = 'a'; //不能改变c的值,这是一个顶层const const A object_A; //不能改变object_A的值,这是一个顶层const return 0; }
- 对于对象类型是
指针的情况,其对象即可以是顶层const,也可以是底层const
int i = 10; int j = 20; const int *p1 = &i; //允许改变指针p1的值,即可以改变其指向,但不允许改变i的值,这是一个底层const p1 = &j; //使p1从指向i改为指向j cout << *p1 << endl; //输出j的值,20 int* const p2 = &i; //允许改变i的值,但是不允许改变指针p2的值,这是一个顶层const *p2 = 15; //改变指针p2指向的对象的值,即i cout << *p2 << endl; //输出i的值,15 const int *const p3 = &i;//不允许改变i的值,也不允许改变指针p3的值,右边的const顶层const,左边的const底层const p3 = &j; //错误,意图改变p3的值 *p3 = 10; //错误,意图改变指针p3指向对象的值 const int&r = i; //不允许改变引用r所绑定对象的值,这是一个底层const r = 10; //错误,意图改变引用r所绑定对象的值
很多时候我们分不清const到底是作用于指针本身还是指针所指向的对象,基于此,我们可以通过看const右边是变量名还是数据类型来判别,
注意:int const 和const int是一样的
举例:
上面的p1,由于const右边是数据类型int,所以该const作用于指针所指向的对象
上面的p2,由于const右边是指针p2,所以该const作用于指针本身
- 由于指针时对象,而引用不是,所以
声明引用的const都是底层const
const参数传递
值传递
- 对于内置的基本数据类型,一般这种情况不需要 const 修饰,因为函数会自动产生临时变量复制实参值。
const void func(const int i) { cout << i << endl; i++;//错误,i的值不能修改 }
传递指针或引用
- 对于自定义类型的参数,需要临时对象复制实参值,而对于临时对象的构造,需要调用构造函数,比较浪费时间,因此我们一般采取 const 外加
引用传递的方法。并且对于一般的 int、double 等内置类型,我们不采用引用的传递方式。class Test { public: int val; }; const void func(const Test&object) { }除了const引用,使用const传递指针,即
指针传递,也可以防止对象被意外篡改。const void func1(const int *i) { cout << *i << endl; int j = 20; i = &j;//正确 *i = 10;//错误,不能改变指针所指向的对象 } const int func2(int*const i) { cout << *i << endl; int j = 20; i = &j;//错误,不能改变指针的指向 *i = 10;//正确 } const int func3(const int*const i) { cout << *i << endl; int j = 20; i = &j;//错误,不能改变指针的指向 *i = 10;//错误,不能改变指针所指向的对象 }对于
引用传递和指针传递的区别,在指针和引用这篇博客中有详细介绍
const函数返回
- const 修饰内置类型的返回值,修饰与不修饰返回值作用一样。
- const 修饰自定义类型的作为返回值,此时返回的值不能作为左值使用,既不能被赋值,也不能被修改。
- const 修饰返回的指针或者引用,根据返回的是
顶层const还是底层const来进行相应的操作
const成员函数
class Test{
public: int val; int TestFunc()const{
return val; } }; void func(const Test& object){
cout << object.TestFunc(); }
如果 TestFunc() 去掉 const 修饰,即使在函数func中没有改变object的值,编译器也认为函数会改变对象的值从而报错,所以对于不需要改变对象内容的函数我们尽量都作为 const 成员函数。
mutable关键字
class Test{
public: int m; mutable int n; void TestFunc()const{
m++;//错误 n++;//正确 } };
constexpr 限定符
常量表达式
常量表达式:指值不会改变并且在编译过程就能得到结果的表达式;字面值、用常量表达式初始化的const对象也是常量表达式。
字面值类型:算术类型、引用和指针都属于字面值类型,自定义类、IO库,string类型则不属于字面值类型,不能被定义成constexpr;
const int i = 10; //字面值是常量表达式 const int j = i+1; //j是常量表达式 int k = i; //k不是const对象,所以k不是是常量表达式 const int m = get_val(); //m的值直到运行时才能获得,所以m不是常量表达是
constexpr变量
从上面我们可以看出,使用const进行声明时,我们需要人为的验证赋给const对象的初始值是不是常量表达是,在复杂系统中,有时候很难分辨,而使用constexpr进行声明时,可以由编译器来验证变量是不是常量表达式
constexpr函数
constexpr函数:指能用于常量表达式的函数,其定义方式和普通函数类型;
定义规则:
- 函数的返回类型及所有形参类型都是字面值类型
- 函数体中只有一条return 语句
constexpr int func(int n) {
return n; } int main() {
int n=10; const int m=10; constexpr int i = func1(10);//正确,i是一个常量表达式 constexpr int j = func1(n);//错误,n不是字面值 constexpr int k = func1(m+1);//正确,k是一个常量表达式 return 0; }
const和constexpr区别
- 对于修饰对象来说,const并未区分出编译期常量和运行期常量,constexpr限定在了编译期常量
- 在constexpr声明中如果定义了一个指针,限定符constexpr仅对指针有效,与指针所指的对象无关。
const int*p1=nullptr; //p1是一个指向常量的指针 constexpr int*p2=nullptr; //p2是一个指向整数的常量指针 constexpr const int*p3=nullptr; //p3是一个指向常量的常量指针
参考
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/228346.html原文链接:https://javaforall.net
