C++中Const 的各种用法、建议以及注意事项

Const 的各种用法


此文参考了下面几篇文章并做了一些修改,首先对原作者表示感谢!!
https://blog.csdn.net/gamekit/article/details/53838680
https://blog.csdn.net/z1026544682/article/details/52159877
https://blog.csdn.net/sinat_27456831/article/details/50969140

const 是constant 的缩写,“恒定不变”的意思。被const 修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。所以很多C++程序设计书籍建议:“Use const whenever you need”。

一、修饰全局或局部变量

1. 修饰指针,防止指针被修改

1.1.const指针不能取地址,不能传递给普通指针。

const ClassA* a;
ClassA** b = &a; //编译错误,不能对const指针取地址
ClassA* c = a; //编译错误,不能将const指针转普通指针

1.2. const指针与指向const的指针
当使用带有const的指针时其实有两种意思。一种指的是你不能修改指针本身的内容,另一种指的是你不能修改指针指向的内容。
指向const的指针,它的意思是指针指向的内容是不能被修改的。它有两种写法。

      const int* p; (推荐)
      int const* p;

再说const指针,它的意思是指针本身的值是不能被修改的。它只有一种写法

      int* const p=一个地址;//因为指针本身的值是不能被修改的所以它必须被初始化

举例说明:

int a=3;
int b;

/*定义指向const的指针(指针指向的内容不能被修改)*/ 
const int* p1; 
int const* p2; 

/*定义const指针(由于指针本身的值不能改变所以必须得初始化)*/ 
int* const p3=&a; 

/*指针本身和它指向的内容都是不能被改变的所以也得初始化*/
const int* const p4=&a;
int const* const p5=&b; 

p1=p2=&a; //正确
*p1=*p2=8; //编译错误(指针指向的内容不能被修

*p3=5; //正确
p3=p1; //编译错误(指针本身的值不能改变) 

p4=p5;//编译错误 (指针本身和它指向的内容都是不能被改变) 
*p4=*p5=4; //编译错误(指针本身和它指向的内容都是不能被改变) 

2. 修饰普通类型,说明此变量不能修改,也不能取地址

const int a;
a++; //编译错误
int* c= &a; //编译错误,不能取地址,否则就具备了改a的能力
int b = a;  //可以编译通过,不能改a的值,可以更改b的值

一般情况下建议用const声明全局变量,对比#define好处有:

  1. 便于进行类型检查
    const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查,而对后者只进行字符替换,没有类型安全检查,并且在字符替换时可能会产生意料不到的错误
  2. 防止意外的修改,增强程序的健壮性。
  3. 可以节省空间,避免不必要的内存分配
    const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是象#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而#define定义的常量在内存中有若干个拷贝
  4. 提高了效率
    编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高

3. 修饰引用类型,此引用的值不能被修改。

二、修饰函数参数,与修饰变量类似

1.需要注意:

修饰引用类型参数时,虽然参数的值不能被修改看似失去意义,但是传递const引用时,可以起到不copy对象又能保护对象的目的

void test(const int& a)
{
    a = 2; //编译错误,不能修改const引用类型的值
}
void test(const ClassA& a) //传递的时候,不需要copy一个新的ClassA,又能保护a
{

}

对于非内部数据类型的输入参数,应该将“值传递”的方式改为“const 引用传递”,目的是提高效率。例如将void Func(A a) 改为void Func(const A &a)。

但是对于内部数据类型的输入参数,不要将“值传递”的方式改为“const 引用传递”。否则既达不到提高效率的目的,又降低了函数的可理解性。例如void Func(int x) 不应该改为void Func(const int &x)。

2.2 形参是const的引用或指针时,必须用const修饰

#include <iostream>  
#include <string>  
using namespace std;  
void print_str( string & s)  
{  
      cout<<s<<endl;  
}  
int main()  
{     
      print_str("hello world");  //编译错误,print_str()中,s未用const修饰,可以更改。
      return 0;  
}  

普通形参加不加const限定符对实参没有影响,引用形参和指针形参前面没有const限定符时,实参必须是非const的,而前面有const限定符时对实参也没有什么影响。

三、用const修饰函数返回值,说明函数的返回类型是const的,功能类似于函数参数

此时返回值只能赋值给const修饰的同类型变量

const char* test()
{
     ......
}

char* str = test();     //错误
const char* str = test();   //正确

如果函数返回值采用“值传递方式”,由于函数会把返回值复制到外部临时的存储单元中,加const 修饰没有任何价值。
例如不要把函数int GetInt(void) 写成const int GetInt(void)。
同理不要把函数A GetA(void) 写成const A GetA(void),其中A 为用户自定义的数据类型。

如果返回值不是内部数据类型,将函数A GetA(void) 改写为const A & GetA(void)的确能提高效率。但此时千万千万要小心,一定要搞清楚函数究竟是想返回一个对象的“拷贝”还是仅返回“别名”就可以了,否则程序会出错。
函数返回值采用“引用传递”的场合并不多,这种方式一般只出现在类的赋值函数中,目的是为了实现链式表达

class A 
{ 
    A & operate = (const A &other); // 赋值函数 
}; 
A a, b, c; // a, b, c 为A 的对象

a = b = c; // 正常的链式赋值 
(a = b) = c; // 不正常的链式赋值,但合法 

如果将赋值函数的返回值加const 修饰,那么该返回值的内容不允许被改动。上例中,语句 a = b = c 仍然正确,但是语句 (a = b) = c 则是非法的。

四、用const修饰函数,说明函数不会修改成员变量的值

1. 任何不会修改数据成员的函数都应该声明为const 类型。

如果在编写const 成员函数时,不慎修改了数据成员,或者调用了其它非const 成员函数,编译器将指出错误,这无疑会提高程序的健壮性。以下程序中,类stack 的成员函数GetCount 仅用于计数,从逻辑上讲GetCount 应当为const 函数。编译器将指出GetCount 函数中的错误。

class Stack 
{ 
public: 
    void Push(int elem); 
    int Pop(void); 
    int GetCount(void) const; // const 成员函数 
private: 
    int m_num; 
    int m_data[100]; 
}; 
int Stack::GetCount(void) const 
{ 
    ++ m_num; // 编译错误,企图修改数据成员m_num 
    Pop(); // 编译错误,企图调用非const 函数 
    return m_num; 
} 

2. const成员函数存在的另一个意义在于它能被const常对象调用。

我们都知道,在定义一个对象或者一个变量时,如果在类型前加一个const,如const int x;,则表示定义的量为一个常量,它的值不能被修改。但是创建的对象却可以调用成员函数,调用的成员函数很有可能改变对象的值,比如下面这段程序:

const list myList;
mylist.DeleteNode(3);//错误,DeleteNode是非const成员函数,删除一个节点会改变length的值
mylist.GetLength();//正确,Get length()未改变任何值,被声明为const成员函数

3. mutable类型

然而,有些时候,我们却必须要让const函数具有修改某个成员数据值的能力。比如一些内部的状态量,对外部用户无所谓,但是对整个对象的运行却大有用处,如支持缓存的技术。遇到这种问题,我们可以把一个成员数据定义为mutable(多变的),它表示这个成员变量可以被const成员函数修改却不违法。比如下面定义了一个is_valid类成员:

class List
{
Private:
    ...
    mutable bool is_valid;
    ...
Public:
    bool checkList() const
    {
        if(length >=1) 
            return is_valid = true;
        else 
            return is_valid = false; // 正确
    };
}

这样,即使像CheckList这样的const成员函数修改它也是合法的。
但需要注意的时,不可滥用mutabe描述符,如果在某个类中只有少数一部分是被允许const常量函数修改的,使用mutable是再合适不过的。如果大部分数据都定义为mutable,那么最好将这些需要修改的数据放入另一个独立的对象里,并间接地访问它。

3. 在相同的函数参数及相同的名字的情况下,const函数与非const函数可以构成重载函数,但是const成员函数不能改变任何的非静态变量

  • const对象默认调用const成员函数,非const对象默认调用非const成员函数;
    若非const对象想调用const成员函数,则需要显示的转化,例如(const Student&)obj.getAge();
    若const对象想调用非const成员函数,同理进行强制类型转换const_cast < Student&>(constObj).getAge();(注意constObj一定要加括号)

当类中只有一种函数存在的情况

  • 非const对象可以调用const成员函数或者非const成员函数
  • const对象只能调用const成员函数,若直接调用非const成员函数编译器会报错。
    const 成员

函数的声明看起来怪怪的:const 关键字只能放在函数声明的尾部,大概是因为其它地方都已经被占用了。

五、关于Const函数的几点规则:

5.1 const使用规则

a. const对象只能访问const成员函数,而非const对象可以访问任意的成员函数,包括const成员函数.
b. const对象的成员是不可修改的,然而const对象通过指针维护的对象却是可以修改的.
c. const成员函数不可以修改对象的数据,不管对象是否具有const性质.它在编译时,以是否修改成员数据为依据,进行检查.
e. 然而加上mutable修饰符的数据成员,对于任何情况下通过任何手段都可修改,自然此时的const成员函数是可以修改它的

5.2 几点建议:

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

5.3 补充重要说明

  1. 类内部的常量限制:使用这种类内部的初始化语法的时候,常量必须是被一个常量表达式初始化的整型或枚举类型,而且必须是static和const形式。
  2. 如何初始化类内部的常量:一种方法就是static 和 const 并用,在外部初始化,例如:
class A { 
public: 
    A() {} 
private: 
    static const int i; //注意必须是静态的! 
};
const int A::i=3;

另一个很常见的方法就是初始化列表:

class A {
public: 
    A(inti=0):test(i) {} 
private: 
    const int i; 
};

还有一种方式就是在外部初始化。
3. 如果在非const成员函数中,this指针只是一个类类型的;如果在const成员函数中,this指针是一个const类类型的;如果在volatile成员函数中,this指针就是一个volatile类类型的。
4. new返回的指针必须是const类型的。

六、将Const类型转化为非Const类型的方法

采用const_cast 进行转换。
用法:const_cast (expression)
该运算符用来修改类型的const或volatile属性。除了const 或volatile修饰之外, type_id和expression的类型是一样的。
· 常量指针被转化成非常量指针,并且仍然指向原来的对象;
· 常量引用被转换成非常量引用,并且仍然指向原来的对象;
· 常量对象被转换成非常量对象。

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值