超级详细的 C++ const 全面总结

花絮

  • C++ 中的 const 关键字的用法非常灵活,而使用 const 将大大改善程序的健壮性
  • const 是 C++ 中常见的类型修饰符,常类型是指使用类型修饰符 const 说明的类型,常类型的变量或对象的值是不能被更新的

一、const 作用

1. 定义 const 常量

const int max = 100;

2. 便于进行类型检查

const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查,而对后者只进行字符替换,没有类型检查,并且在字符替换时可能产生意料不到的错误

void f(const int i){……}
//对传入的参数进行类型检查,不匹配进行提示

3. 可以保护被修饰的东西

防止意外的修改,增强程序的健壮性

void f(const int i)
{
	i = 10;  // error
	//如果在函数内部修改了i,编译器会报错
}

4. 可以很方便进行参数的调整和修改

同宏定义一样,可以做到不变则已,一变都变


5. 为函数重载提供了一个参考

class A{
	void f();  //一个函数
	void f() const{}; //上一个函数的重载
};

6. 可以节省空间,避开不必要的内存分配

const 定义常量从汇编角度来看,只是给出了对应的内存地址,而不像 #define 一样给出的是立即数。所以,const 定义的常量在程序运行过程中只有一份拷贝,而 #define 定义的常量在内存中有若干份拷贝

#define PI 3.1415926  //常宏量
const double pi 3.1415926  //此时并未将 pi 放入内存中
……
double i = pi; //此时为 pi 分配内存,以后不再分配
double I = PI; //编译期间进行宏替换,分配内存

double j = pi; //没有分配内存
double J = PI; //再一次进行宏替换,又一次分配内存

7. 提高效率

编译器通常不为普通 const 常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它效率也很高


二、const 的使用

1. 定义常量

(1) const 修饰变量,以下两种定义形式在本质上是一样的。它的含义是:const 修饰的类型为 T 的变量 val,它是不可变的。

T const val = value;
const T val = value;

(2) 将 const 改为外部连接,作用扩大到全局,编译时会分配内存,并且可以不进行初始化,仅仅作为声明,编译器认为在程序其他地方进行了定义。

extend const T val = value; 

2. 指针使用 const

(1) 指针本身是常量不可变

如果 const 的位置在*的后面,表示指针本身是常量,但是指针指向的变量的值是可以改变的。简单来说,假如指针 p 指向了 变量 a,那么 p 就只能指向 a,不能再指向其他变量了,但是 a 不是常量,a 的值是可以改变的

int a = 10;
int b = 20;
int* const p = &a;

p = &b;  //error: assignment of read-only variable 'p'
*p = 30;  // 此时 a = 30

(2) 指针所指向的内容是常量不可变

如果你了解 (1),那么这个 const 的效果与 (1) 中 const 的效果刚好相反。如果 const 的位置在*的前面,表示指针指向是内容常量,但是指针本身是可变的。简单来说,假如指针 p 指向了 变量 a,那么 变量 a 就变成常量了,a 中的内容就不能被修改了,但是 p 本身可以改变,p 可以指向其他变量。

int a = 10;
int b = 20;
const int* p = &a;

*p = 30;  //error: assignment of read-only location '* p'
p = &b;  //correct: p 指向了b,*p = 20

(3) 两者都不可变

const int* const p;

(4) 总结

  • 如果 const 位于 * 的左侧,则 const 就是用来修饰指针所指向的变量,即指针指向为常量
  • 如果 const 位于 * 的右侧,则 const 就是修饰本身,即指针本身是常量

3. 函数使用 const

(1) const 修饰函数参数

  • 传递过来的参数在函数内部不可改变(无意义,因为 var 本身就是形参)
void function(const int var);
  • 参数指针所指内容为常量不可变
void function(const int* p);

在这里插入图片描述

  • 参数指针本身是常量不可变
void function(int* const p);

在这里插入图片描述

  • 参数为引用,为了增加效率同时防止修改,修饰引用参数时
void function(const class& A); //引用参数在函数内不可改变
void function(const int &a); //引用参数在函数内为常量不可变

示例一:引用参数在函数内不可改变
在这里插入图片描述
示例二:引用参数在函数内为常量不可变

在这里插入图片描述

这样的一个 const 引用传递和最普通的函数按值传递的效果是一模一样的,它禁止对引用的对象的一切修改,唯一不同的是按值传递会建立一个类对象的副本,然后传递过去,而引用是直接传递地址,所以这种传递比按值传递更有效。另外只有引用的 const 传递可以传递一个临时对象,因为临时对象都是 const 属性,且是不可见的,它短时间存在一个局部域中,所以不能使用指针,只有引用的 const 传递才能捕捉到这个家伙。


(2) const 修饰函数返回值
const 修饰函数返回值其实用的并不多,它的含义和 const 修饰普通变量以及指针的含义基本相同

1. const int func1();  //这个其实没多大意义,因为参数返回就是赋值
2. const int* func2(); //调用时:const int* p = func2(); 可以把 func2() 当作一个变量,即指针内容不可变
3. int* const func3(); //调用时:int* const p = func3(); 以把 func2() 当作一个变量,即指针本身不可变

一般情况下,函数的返回值为某个对象时,如果将其声明为 const 时,多用于操作符的重载。通常,不建议用 const 修饰函数的返回值类型为某个对象或者某个对象的引用的情况。
原因如下:如果返回值为某个对象为 const(比如:const A test = A; )或某个对象的引用为 const(const A& test = A;),则返回值具有 const 属性,则返回实例只能访问类 A 中的公有或保护数据成员和 const 成员函数,并且不允许对其进行赋值操作,这在一般情况下很少用到。


4. 类相关 const

(1) const 修饰成员变量
const 修饰类的成员变量,表示成员常量,不能被修改,同时它也只能在初始化列表中赋值

class A{
	const int a;  //成员常量不能被修改
	……
	A(int _a) : a(_a){}  //只能在初始化列表中赋值 
};

【举个栗子】
在这里插入图片描述
(2) const 修饰成员函数

  1. const 修饰类的成员函数,则该成员函数不能修改类中任何非 const 成员函数,一般写在函数的最后来修饰
class A{
	void func() const{} //常成员函数,它不能改变对象的成员变量,也不能调用类中任何非 const 成员函数
};

【举个栗子】
在这里插入图片描述

  1. 对于 const 类对象/指针/引用,只能调用类的 const 成员函数。因此,const 修饰成员函数的最重要的作用就是限制对于 const 对象的使用
  • const 成员函数不被允许修改它所在对象的任何一个数据成员
    对于第 1 点,const 成员函数不被允许修改它所在对象的任何一个数据成员。这里有一个注意点,如果类中存在指针类型的数据即便是 const 函数也只能保证不修改该指针的值,并不能保证不能修改该指针指向的内容
    【举个栗子】
class Name {
public:
	void setName(const string &s) const;
private:
    char *m_sName;
};

void Name::setName(const string &s) const {
//    m_sName = s.c_str();      // 错误!不能修改m_sName;

for (int i = 0; i < s.size(); ++i)
    m_sName[i] = s[i];    // 不好的风格,但不是错误的
}

m_sName 不能通过setName()函数修改,但是 setName函数可以修改其所指向的字符。

  • const 成员函数能够访问对象的 const 成员,而其他函数不可以。

这个结论不严谨。const function 的本质,其实就是会在函数内部,把当前调用者的 this 指针转换成 const this,以保证当前函数对数据成员只有读的权限,没有写的权限。如果在非 const 成员函数里只访问了 const 成员变量,但没有进行修改操作,这时候是可以通过编译的。
【举个栗子】

class A
{
 private:
     const int a;
 public:
    A(int _a) : a(_a){}
    int getA(){ cout << a; } // ok,可以通过
    int setA(){ a = 30; } //error
};

(3) const 修饰类对象/对象指针/对象引用

  • const 修饰类对象表示该对象为常量对象,其中的任何成员都不能被修改,对于对象指针和对象引用也是一样
  • const 修饰的对象,该对象的任何非 const 成员函数都不能被调用,因为任何非 const 成员函数会有修改成员变量的企图

【举个栗子】

class A
{
 private:
     const int a;
     int b;
 public:
    A(int _a, int _b) : a(_a), b(_b){}
    int getA() const{ return a; }
    int getB(){return b; }
    void setB(){ b= 30; }
};
int main ()
{
  const A a(20, 10);
  //a.setB(); //error
  //a.getA(); //correct,const 对象只能调用const成员函数
  a.getB(); //error,const 对象不能调用非const成员函数
  return 0;
}

[结论]

  • const对象可以访问const成员函数,但是不能访问非const成员函数
  • 非const对象可以调用const 成员函数和非const成员函数
  • const成员函数可以调用其他const成员函数,不能调用其他非const成员函数
  • 非const成员函数内可以调用其他const成员函数和非const成员函数

三、将 const 类型转换成非 const 类型

【方法】:采用const_cast进行转换
【用法】:const_cast <type_id> (expression)
【作用】:该运算符用来修改类型的 const 或 volatile 属性。除了const或volatile 修饰之外,type_id 和 expression 类型是一样的。

  • 常量指针被转化成非常量指针,并且仍然指向原来的对象
  • 常量引用被转换成非常量引用,并且仍然指向原来的对象
  • 常量对象被转换成非常量对象

四、使用 const 的一些建议

  1. 要大胆的使用const,这将给你带来无尽的益处,但前提是你必须搞清楚原委;
  2. 要避免最一般的赋值操作错误,如将const变量赋值,具体可见思考题;
  3. 在参数中使用const应该使用引用或指针,而不是一般的对象实例,原因同上;
  4. const在成员函数中的三种用法(参数、返回值、函数)要很好的使用;
  5. 不要轻易的将函数的返回值类型定为const;
  6. 除了重载操作符外一般不要将返回值类型定为对某个对象的const引用;
  7. 任何不会修改数据成员的函数都应该声明为const 类型。

五、补充重要说明

  1. 类内部的常量限制:使用这种类内部的初始化语法的时候,常量必须是被一个常量表达式
  2. 初始化的整型或枚举类型,而且必须是static和const形式。
  3. 如何初始化类内部的常量:一种方法就是static 和 const 并用,在外部初始化,例如:
    class A { public: A() {} private: static const int i; file://注意必须是静态的! };
    const int A::i=3;
    另一个很常见的方法就是初始化列表: class A { public: A(int i=0):test(i) {} private: const int i; };
    还有一种方式就是在外部初始化,
  4. 如果在非const成员函数中,this指针只是一个类类型的;如果在const成员函数中,this指针是一个const类类型的;如果在volatile成员函数中,this指针就是一个
    volatile类类型的。
  5. new返回的指针必须是const类型的。

参考链接:关于C++ const 的全面总结

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值