const名叫常量限定符,用来限定特定变量,以通知编译器该变量是不可修改的。习惯性的使用const,可以避免在函数中对某些不应修改的变量造成可能的改动。

1. const修饰基本数据类型

1.1 const修饰一般常量及数组

1
2
const int a=10;               等价的书写方式:     int const a=10;
const int arr[3]={1,2,3};     等价的书写方式:     int const arr[3]={1,2,3};

对于类似这些基本数据类型,修饰符const可以用在类型说明符前,也可以用在类型说明符后,其结果是一样的。在使用这些常量的时候,只要不改变这些常量的值便好。

1.2 const修饰指针变量*及引用变量&

1.2.1 指针*和引用&的一些基本知识

介绍本部分内容之前,先说说指针和引用的一些基本知识。

指针(pointer)是用来指向实际内存地址的变量,一般来说,指针是整型,而且一般的大家会接受十六进制的输出格式。 引用(reference)是其相应变量的别名,用于向函数提供直接访问参数(而不是参数的副本)的途径,与指针相比,引用是一种受限制的指针类型,或者说是指针的一个子集,而从其功能上来看,似乎可以说引用是指针功能的一种高层实现。

关于运算符&和*: 在C++里,沿袭C中的语法,有两个一元运算符用于指针操作:&和*。&是取址符,*是指针符,也就是说,&用于返回变量的实际地址,*用于返回地址所指向的变量,他们应当互为逆运算

在定义变量的引用的时候,&只是个定义引用的标志,不代表取地址。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
using namespace std;

int main()
{
    int a;     // a is an integer
    int *aPtr; // aPtr is a pointer to an integer

    a = 7;
    aPtr = &a;
    cout << "Showing that * and & are inverses of "
         << "each other.\n";
    cout << "a=" << a << "  *aPtr=" << *aPtr << "\n";
    cout << "aPtr = " << aPtr << endl;
    cout << "&aPtr = " << &aPtr << endl;
    cout << "&*aPtr = " << &*aPtr << endl;
    cout << "*&aPtr = " << *&aPtr << endl;
    return 0;
}

运行结果:

1
2
3
4
5
6
Showing that * and & are inverses of each other.
a=7  *aPtr=7
aPtr = 0x61fe1c
&aPtr = 0x61fe10
&*aPtr = 0x61fe1c
*&aPtr = 0x61fe1c

1.2.2 const修饰指针(*)

1
2
3
4
5
6
7
8
int a = 5;
const int b = 5;
const int* p = &a;
a = 4;//合法
*p = 3;//不合法,不能使用指针修改指向数据的值
p = &b;//合法
b = 4;//不合法
*p = 3;//不合法
1
2
3
4
5
int a = 5;
int b = 6;
int * const p = &a;
*p = 4;//合法
p = &b;//不合法,保证指针不会指向别处
1
2
3
4
5
6
int a = 5;
int b = 6; 
const int * const p = &a;   //合法
*p = 4;//不合法
p = &b;//不合法
a = 7;//合法
1
2
3
4
5
6
7
const int a = 5;
int b = 6; 
int * const q = &a;//不合法
const int * const p = &a;//合法
*p = 4;//不合法
p = &b;//不合法
a = 7;//不合法    

可以参考《Effective c++》Item21上的做法,

如果const位于星号*的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量;

如果const位于星*号的右侧,const就是修饰指针本身,即指针本身是常量。

因此,[1]和[2]的情况相同,都是指针所指向的内容为常量,这种情况下不允许对内容进行更改操作,如不能*a = 3 ;

[3]为指针本身是常量,而指针所指向的内容不是常量,这种情况下不能对指针本身进行更改操作,如a++是错误的;

[4]为指针本身和指向的内容均为常量。

1.2.3 const修饰引用(&)

1
2
3
4
5
6
int x = 10;

int &a0 = x;
int const &a1=x;
const int &a2=x;
int &const a3=x; //这种方式C中可用,效果和int &a一样;C++编译器未定义   

2. const应用到函数中

2.1 const 修饰函数参数

如果参数作输出用,不论它是什么数据类型,也不论它采用“指针传递”还是“引用传递”,都不能加const 修饰,否则该参数将失去输出功能。

const 只能修饰输入参数,可以分为三种情况:

  • 值传递的不用 const 修饰

值传递参数一般不需要 const 修饰,因为函数会自动产生临时变量复制实参值。

并且对于一般的 int、double 等内置类型,一般采用值传递方式。

不要将函数 void Func1(int x) 写成 void Func1(const int x) 。同理不要将函数 void Func2(A a) 写成 void Func2(const A a)。其中A 为用户自定义的数据类型。

  • const 修饰指针时,可以防止指针被意外篡改。
1
void Func1(const char* str);
  • const 修饰自定义类型的参数

定义类型的参数如采用值传递,临时对象将会复制参数,需要用构造函数,比较浪费时间,一般采用 const 外加引用传递的方法。

1
2
void Func2(const std::string& str);
void Func3(const A& a); // A为自定义class

2.2 const 修饰函数返回值

  • const 修饰“值传递”方式的的函数返回值,由于函数会把返回值复制到外部临时的存储单元中,加 const 修饰没有任何价值,修饰与不修饰返回值作用一样。

不要把函数 int GetInt(void) 写成 const int GetInt(void)

  • const 修饰以“指针传递”方式的函数返回值,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给加const 修饰的同类型指针。
1
2
3
const char * GetString(void);
char *str = GetString();        // 将出现编译错误:
const char *str = GetString(); // 正确
  • const 修饰“引用传递”作为返回值,此时返回的值不能作为左值使用,既不能被赋值,也不能被修改。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
using namespace std;

class A
{
private:
    int data;
public:
    A(int num):data(num){}
    ~A(){};
    int& get_data()
    {
        return data;
    }
};

int main()
{
    A a(1);
    a.get_data()=3;
    cout<<a.get_data()<<endl; //data=3
    return 0;
}

这个时候为了避免成员函数被修改就需要加上const关键字,这个时候如果试图改变返回值是不允许的。

需要指出的是,如果函数的返回类型是内置类型,比如 int char 等,修改返回值本身就是不合法的!所以 const 返回值是处理返回类型为用户定义类型的情况。

2.3 const 修饰在函数名后面

在函数名后面表示是 C++ 常成员函数,该函数不能修改对象内的任何成员,只能发生读操作,不能发生写操作。

const 修饰成员函数, 根本上是修饰了 this 指针。

注意:const 关键字不能与 static 关键字同时使用,因为 static 关键字修饰静态成员函数,静态成员函数不含有 this 指针,即不能实例化,const 成员函数必须具体到某一实例。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class People
{
 public:
    int talk(void);
    int eat(void) const; // const 成员函数
 private:
    int m_age;

};
int People::eat(void) const
{
    ++m_age; // 编译错误,企图修改数据成员m_num
    talk();  // 编译错误,企图调用非const函数
    return    m_age;
}
  • const 对象只能访问 const 成员函数,而非 const 对象可以访问任意的成员函数,包括 const 成员函数.
  • const 对象的成员是不可修改的,然而 const 对象通过指针维护的对象却是可以修改的.
  • const 成员函数不可以修改对象的数据,不管对象是否具有 const 性质.它在编译时,以是否修改成员数据为依据,进行检查.
  • 然而加上 mutable 修饰符的数据成员,对于任何情况下通过任何手段都可修改,自然此时的 const 成员函数是可以修改它的