c++ 学习之 const 关键字

c++ 学习之 const 关键字

本文对 c++ 中的 const 关键字的使用方式进行总结。

含义

关键字 const 是常量类型的修饰符,常量类型的变量或者对象的值是不能被修改更新的。

作用

使用 const 关键字可以有以下的作用:

  1. 可以定义常量;
  2. 相比#define 定义的数据类型只是替换,const 类型数据可以借助编译器进行类型检查;
  3. 防止变量或者对象被修改,增加程序的健壮性;
  4. 可以节省内存空间,避免不必要的内存分配。

const 数据与宏定义的区别

由 const 定义的常量,与#define 宏定义的常量,是有区别的。

由 const 定义的常量,编译器可以对其常量类型是否符合类型定义进行检查,而#define 宏定义是没有数据类型的,在编译时,编译器只是执行简单的字符串替换,不能进行类型安全检查。

const 定义的常量,从汇编角度看,只是给出了对应的内存地址,而不是像 define 宏定义给出的是立即数。因此,const 定义的常量在程序运行期间,只有一份拷贝,而 define 宏定义定义的常量在内存中存在多份拷贝。

const 对象默认为文件局部变量

对于未被 const 修饰的变量,默认为 extern,比如下例子:

文件 1:

int ext;    //未被const修饰,默认为extern

文件 2:

#include 
extern int ext;     //通过extern,其实引用的是文件1中的变量
int main()
{
    std::cout << ext << std::endl;
}

而被 const 修饰的变量,默认为文件局部变量,因此,要使得 const 变量能够被其他的 cpp 文件访问到,在定义 const 变量时,必须显式地指出该 const 变量为 extern。如下例子:

文件 1:

//const变量若需要被其他文件访问到,需显式声明extern
//const变量在定义后就不能修改,因此需定义时就赋予初始值
extern const int ext = 12;

文件 2:

#include 
extern const int ext;       //通过extern,其实引用的是文件1中的变量
int main()
{
    std::cout << ext << std::endl;
}

应用

const 关键字可以应用的地方包括:

  1. 定义常量
  2. 定义指针或者指针指向的对象
  3. 函数中使用 const
  4. 类中使用 const

定义常量

使用 const 定义常量时,需注意:

  1. 定义完常量值后,不可更改
  2. 常量在定义后就不能修改,因此,必须在定义时就初始化

比如:

const int num = 10;
num = 12; //error: const常量在定义后,就不能被修改

const int i,j = 0; //error: const常量定义时就必须初始化,这里i未初始化

指针与 const

与 const 相关的指针,包括两种大类:

  1. 指向 const 对象的指针,即 const 修饰指针所指向的对象,指针本身不是常量,可修改;
  2. 执行对象的 const 指针,即 const 修饰的是指针本身,指针本身是常量,不可修改。

对于如何区分 const 与指针的关系,总结为:

  1. 如果 const 位于指针符号(*)的左侧,则 const 修饰的是指针所指的对象;
  2. 如果 const 位于指针符号(*)的右侧,则 const 修饰的是指针本身,即指针为常量。

比如:

const char * a;  //const修饰的是指针指向的对象,对象为常量,指针非常量
char const * a;  //同上

char * const a;  //const修饰的是指针本身,即指针为常量

const char * const a;  //指向const对象的const指针,即指针与指向的对象均为常量

对于指向常量的指针,比如:

const int * ptr;
*ptr = 10;  //error: 指针指向的对象为常量,不可通过指针修改对象

ptr 是一个指向 int 类型 const 对象的指针,const 修饰的是 int 类型的对象,即 ptr 所指向的对象,而不是 ptr 本身。因此,ptr 在定义时可以不初始化。不能通过 ptr 修改 int 类型对象,因为 int 类型对象被 const 修饰。

另外需注意的点:

  1. 不能使用 void * 指针保存 const 对象的地址,必须使用 const void * 类型的指针保存 const 对象的地址;
  2. 允许将非 const 对象的地址,赋给指向 const 对象的指针,但是不能通过指针修改对象的值,必须通过其他方式修改。

举例:

const int p = 10;
const void * vp = &p;
void * vp = &p;  //error: 必须使用const void * 类型的指针保存const对象的地址

const int *ptr;
int val = 3;
ptr = &val;  //ok: 允许将非const对象地址赋予指向const对象的指针
*ptr = 4;  //error: 不能通过ptr来修改val的值,即使val为非const对象

对于 const 修饰的指针,即常量指针,在定义时必须初始化,且后续常量指针不能被修改。比如:

int num = 0;
int * const ptr = #  //ok: ptr为const指针,定义时必须被初始化
int b = 2;
ptr = &b;  //error: ptr为const指针,不能被修改

函数中使用 const

在函数中使用 const 关键字,主要在两处:

  1. const 修饰函数的返回值
  2. const 修饰函数的参数

对于 const 修饰函数的返回值,与 const 修饰普通变量与指针的含义相同,比如:

const int func();  //合法,但是无意义
const int * func();  //返回的指针指向的对象为常量
int * const func();  //返回的指针为常量

对于 const 修饰函数的参数,一些例子为:

//表示函数的形参var为常量,但是无意义,因为var为形参,在函数内不会改变
void func(const int var);

//表示函数的入参指针ptr为常量,但是无意义
void func(int * const ptr);

//表示函数的入参src指向的字符串不可变
//因此函数体内若试图修改src的内容,则编译器将报错
void func(char * dst, const char * src);

//效率较低,函数体内将产生A类型的临时对象复制参数a
//临时对象的构造,复制,析构等过程将消耗资源
void func(A a);

//为了提高效率,引用传递,不会产生临时对象
//但是函数体内可通过引用改变a的值
void func(A & a);

//增加const修饰,此时依然是引用传递,但是函数体内无法修改a的值
void func(const A &a);

类中使用 const

在设计类时,一个原则是,对于不改变数据成员的成员函数,都要在函数后面加上 const,而对于改变数据成员的成员函数则不加 const。

类的成员函数后面加 const,表明这个函数不会对这个类对象的数据成员做任何的改变操作。所以,有 const 修饰的成员函数只能读取数据成员,不能改变数据成员,而没有 const 修饰的成员函数对数据成员是可读可写的。

在一个类中,任何不会修改数据成员的函数,都应该声明为 const 类型。

如果在编写 const 成员函数时,不慎修改了数据成员,或者调用了其他非 const 成员函数,编译器将报错。这样可以及时发现程序的错误,提高程序的健壮性。

准确地说,成员函数中的 const,修饰的是指向对象的 this 指针。例如:

class A
{
public:
    func(int);
};

注意,这里的 func 函数其实具有两个参数,一个是 int 类型的入参,另一个是隐含的 A * const this

如果不想让函数 func 修改参数的值,可以将函数原型修改为 func(const int)。但是如果不运行函数修改 this 指针指向的对象呢?

因为 this 指针是隐含的参数,无法使用 const 直接修饰 this,因此可以将 const 加在函数的后面,即 func(int) const,这样,this 指令就被修饰为 const A * const this。通过这样的修饰,函数 func 就无法修改类对象的成员数据了,如果试图修改,则编译器将会报错。

下面是一个例子:

#include 
#include 

using namespace std;

class Student
{
public:
    Student(string str=NULL, double sco=0.0);
    void set_student(string str, double sco);
    string get_name() const;
    double get_score() const;
    void display();
    void display() const;
private:
    string name;
    double score;
};

//构造函数
Student::Student(string str, doubel sco)
{
    name = str;
    score = sco;
}

//修改成员数据,必须为非const
void Student::set_student(string str, doubel sco)
{
    name = str;
    score = sco;
}

//不修改成员数据,可使用const修饰
string Student::get_name() const
{
    return name;
}

//不修改成员数据,可使用const修饰
double Student::get_score() const
{
    return score;
}

void Student::display()
{
    cout << "调用非const: " << endl;
    cout << "name: " << name << ", score: " << score << endl;
}

//const成员函数可以重载非const成员函数
void Student::display() const
{
    cout << "调用const: " << endl;
    cout << "name: " << name << ", score: " << score << endl;
}

//外部函数
//如果对象非cons型,则不管get_name()与get_socre()函数是否为const成员函数,都可以调用
//如果对象为const型,则get_name()与get_socre()函数必须为const成员函数 
void display(const Student &stu)                 
{
    cout<<"外部函数:"<

总结:

  1. const 对象只能访问 const 成员函数,而非 const 对象可以访问任意的成员函数,包括 const 成员函数;
  2. const 对象的成员是不可修改的,但是 const 对象通过指针维护的成员却是可以修改的;
  3. const 成员函数不可以修改对象的成员数据,不管对象是否为 const;
  4. 作为一个良好的编程风格,在声明成员函数时,若该成员函数并不对成员数据进行修改,则应该尽可能地将该成员函数使用 const 修饰;
  5. 使用 mutable 修饰的成员数据,在任何情况下都可以被修改,此时 const 成员函数是可以修改 mutable 修饰的成员数据。

你可能感兴趣的:(c++ 学习之 const 关键字)