首页 > 编程笔记 > C++笔记 阅读:41

C++ const的用法(非常详细)

程序的存在就是为了处理数据,程序中的数据非常重要,谁也不想让自己的数据未经授权就被随意修改而导致最终结果出错。因此,我们在 C++ 程序中通过设置类成员的访问级别来防止外界非法访问类中的数据,以此保护数据的安全性。

但 C++ 认为仅仅在类中设置访问级别对数据的保护还不够,所以专门增加了 const 关键字来保护数据,防止数据被非法修改。

我们知道,使用各种类型的变量来保存数据,如果想保护某个变量的值,使之保存的数据不被修改,可以在定义该变量时,在数据类型前加上 const 关键字进行声明。这样,一旦有人试图修改这个变量的值,编译器就会报错,从而使我们发现并阻止这种非法访问,确保数据的安全性。

使用 const 修饰变量的语法格式如下:
const 数据类型 变量名;
例如,可以这样来保护数据:
const double PI = 3.14159;
PI = 3.14;    // 有const保护,行不通
用 const 声明(或称为修饰)后,变量就具有了 const 属性。当在程序中试图修改这个变量的值时,编译器就会报错。

C++ const声明指针变量的两种形式

如果我们要使用 const 声明普通数据类型的变量,只需在定义变量时在数据类型前加上 const 关键字即可。但如果我们要使用 const 声明一个指针类型的变量,则有两种形式:
int N = 0;
const int* pInt1 = &N;  // 第一种形式:常量整型指针
int* const pInt2 = &N;  // 第二种形式:整型常量指针
由于 const 关键字位置的不同,这两种形式所表达的含义也各不相同。第一种形式定义的是一个常量整型指针,而第二种形式定义的是一个整型常量指针。这听起来像在说绕口令,实际上这两种形式确实有所不同。

为了明确这两种形式的区别,我们可以把声明语句以“*”为界,分割成两个部分,如下图所示。


图 1 使用“*”分割常量整型指针和整型常量指针的声明语句

经过这样的分割后,很容易看出其中的区别:
因此,使用 const 声明或修饰指针变量,可以选择只保护指针指向的数据,或者只保护指针本身的值。我们甚至可以在“*”左右都加上 const,这样既保护它所指向的数据,也保护指针本身:
const int* const pInt3 = &N;  // 双保险
有了 const 的看管,再加上编译器的帮助,我们自然不用担心数据被非法修改了。

C++ const代替#define定义常变量

例如,定义表示圆周率的 PI 常量,可以采用下面的两种方式:
// 定义宏PI
#define PI 3.1415926
// 定义常量PI
const double PI = 3.14159
这两种方式在语法上都是合法的,但第二种方式比第一种方式好。原因在于,如果使用 #define 定义宏 PI,PI 会在预处理过程中被替换成具体的数字 3.14159,而宏的名称不会出现在程序的符号表中。

符号表是编译器在编译程序的过程中收集、记录和使用的程序代码中的语法符号的类型和特征等相关信息。这些信息一般以表格的形式存储于系统中,如常数表、变量名表、函数名表等。

如果使用宏,那么在调试时可能会遇到一个反复出现的数值,但不知道它的含义,这可能会给程序的调试带来一定的麻烦。相比之下,使用 const 定义常变量,不仅可以保证 PI 值不会被修改,变量名还会出现在符号表中,便于调试,同时可以进行类型检查,借助编译器减少错误。因此,在需要定义常量时,应优先选择使用 const。

C++ const修饰函数参数

因为用 const 声明的变量具有不可修改性,所以常用于给函数的传入参数加上 const 关键字,以表示这是一个只用于传入数据的参数,防止它在函数内部被非法修改而引起其他错误。

相应地,对于负责传出数据的参数,因为在函数内部会被修改,自然就不应加上 const。因此,有无 const,可以清晰地表示函数参数是传入数据还是传出数据。

例如:
// 复制字符串函数
char* strcpy(
    char* destination,  // 目标字符串,会被修改,所以没有加上 const 进行声明
    const char* source   // 源字符串,不会被修改,所以加上 const 进行声明
);

C++ const声明类成员函数

在类的定义中,除通过设置不同的访问级别来保护类内部的成员数据不会被修改外,const 关键字也能提供额外的保护。

如果某个成员函数在语义上不会修改类的内部数据,例如 Rect 类的 GetArea() 函数,它只是用来得到矩形的面积,而不应该去修改矩形的任何数据。为了让这样的函数只做它该做的事,防止它修改类内部的数据,在声明函数时,可以在函数末尾加上 const 关键字。

负责监督的 const 关键字会检查该成员函数是否意外地修改了类的成员变量(做了不该做的事),一旦发现这类操作,编译器会给出错误提示信息,从而保护成员变量。例如:
// 矩形类
class Rect
{
public:
    // ...
    // 获得矩形面积,不应该修改类的数据,所以在函数末尾加上 const
    int GetArea() const
    {
        // 试图在用 const 声明的成员函数内修改类的成员变量
        // 会导致一个编译错误
        m_nW = 10;
        return m_nW * m_nH;  // 只读访问成员变量
    }
    // 设置矩形的长和宽
    // 函数参数 nW 和 nH 只是用于传入数据,所以加上 const
    // 但在函数内部,会修改类的成员数据,所以没有加 const
    void SetRect(const int nW, const int nH)
    {
        m_nW = nW;  // 修改成员变量
        m_nH = nH;
    }
private:  // 设置私有访问级别保护数据
    int m_nW = 0;
    int m_nH = 0;
};
在这段代码中,GetArea() 成员函数使用 const 进行声明,表示这是对类的一个只读访问。如果试图在 GetArea() 函数内部修改该类的数据,编译器会报出错误,这有助于我们检查程序代码中对数据的非法修改,并纠正程序的错误。

除可以发现用 const 声明的成员函数中对数据的非法修改外,当在一个 const 声明的常量对象上调用非 const 声明的成员函数尝试对常量对象进行修改时,编译器也会发现这种非法修改,并报出编译错误来提示我们进行修正。例如:
// 定义一个用 const 声明的常量对象
const Rect rect;
// 在常量对象上调用非 const 声明的成员函数是非法的
rect.SetRect(3,4);
// 在常量对象上调用以 const 声明的成员函数是合法的
int n = rect.GetArea();
const 不仅可以看管单个变量,还可以看管函数参数,把 const 关键字放到类成员函数的末尾时,还可以保护整个类的数据。一旦发现对它所保护的数据的非法访问,编译器就会给出错误提示信息。因此,有了 const 的保护,程序中的数据就不会被非法访问了。

相关文章