前言:

const在C/C++中重要性很大,如果单纯理解为“常量”那就错了,这一篇总结一下C++中const的详细的用法。

一、const 修饰变量

限定一个变量不允许被改变。使用const在一定程度上可以提高程序的安全性和可靠性

const int a;            // a 代表一个常整型数
int const a;            // a 代表一个常整型数
const int *a;           // a 代表一个指向常整型数的指针,即a的值是可变的,但是*a是不能变的,函数的一些形参经常会用到
int * const a;          // a 代表一个常指针,即a的值是不可变的,但是*a是可变的
int const * a const;    // a代表的是一个指向常整型数的常指针

主要看const修饰的是什么?

1、const + 类型

说明const修饰的是类型想让类型为常量,也就是这个类型的值为常量

例如const int *a; 本来是定义一个指针变量,指针变量所指向的值为int类型。const修饰int 类型,那也就是说这个int 类型的值为常量,也就是说指针指向的值为常量。

2、const + 变量

说明const修改的变量,想让这个变量为常量。

例如int *const a; 本来是定义一个指针变量,指针变量所指向的值为int 类型。const修饰变量,那就是说这个指针变量为常量,不可变。指针所指向的int类型的值是可变的。

3、const 与 指针

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

上面定义了一个常指针,指向的数据 a 是不变的。即不能修改 *p 的值。

*p += 1; //INVALID
cin >> *p; //INVALID

尝试修改 *p 的值,是不被允许的,因为p指向的为const 的值。

但是,如果修改a是可以的,因为a是普通变量。

a = 30; //VALID
const int a = 20;
int *p = &a; //INVALID

上面定义一个可变的指针,指向一个const 的值,这个是不允许的。可以想下,如果这里允许,那样可以修改a 的值,*p = 30; 这样a 的const状态就荒谬了。C++禁止将const 地址赋给非const 的指针。如果非要这样做,可以通过const_cast 强制转换。

这里修改为:

const int* p = &a; //VALID

如果将指针指向指针,则情况将更复杂一点。上面讲到如果是一级间接关系,可以将非const的指针赋给const的指针。例如,

int a = 10;
int *p = &a;
const int *pt = p;

上面的操作都是允许的,只不过需要注意的是,*p 是可以修改的,但是*pt 就不能修改。

然而,进入二级间接关系时,与一级间接关系一样将const 和非const混合的指针赋值方式将不再安全。例如,

const int **p2;
int *p1;
const int a = 20;
p2 = &p1; //这里是不被允许的,编译的时候会报错
*p2 = &a;
*p1 = 10;

上面将非const 的指针p1的地址赋值给const 指针p2,因此可以使用p1来修改const数据。因此,仅当只有一层间接关系(如指针指向基本数据类型)时,才可以将非const 地址或指针赋给const 指针。

4、尽可能的使用const

将指针参数声明为指向常量数据的指针有两个理由:

● 这样可以避免由于无意间修改数据而导致的编程错误;

使用const 使得函数能够处理const 和非const 实参,否则只能接受非const 数据。

例如,

有个常量数据array定义如下,

const int array [] = {1, 2, 3, 4, 5};

有个函数func定义如下,

void func(int array[], int n) { ... }

将上面常量数组作为参数传入,是不允许的,需要将参数改为const。

二、const 修饰函数

1、const 修饰函数参数

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

const 只能修饰输入作用的参数。

如果输入参数为指针,加上const 之后起到保护指针意外修改的作用。

例如,

void StringCopy(char* strDest, const char* strSource);

这里strSource 加上const 修饰,就是为了防止strSource 会被意外的修改实参中指针指向一段内存地址,调用函数之后,函数会产生一个临时指针变量,这个变量的地址肯定跟实参的指针地址不一样,但是这两指针指向的内存是同一块。形参加上const 修饰之后,保护了这一块内存地址不被修改,如果刻意修改这一块内存,编译器会报错。

如果输入参数为引用,加上const 之后不但起到了保护作用,也提高了程序效率。

例如,

void func(A a); //这里的A类型为用户定义的类型

这个函数在调用的时候会生成一个临时对象,而且会调用拷贝构造函数,函数结束还要析构。如果改成引用void func(A &a); 只是相当于实参的一个别名,不会产生临时变量。所以,如果是自定义类型,建议用引用作为函数形参。

但是,即使用引用,如果只是作为输入用,最好加上const 修饰,这样就不会改变实参的值,如果刻意修改,编译器会报错。所以,函数最后改成void func(const A& a);

当然,不是所有的数据类型都需要用应用,例如,

void func(int x);

对于内部类型的参数,不存在构造、析构的过程,产生临时变量、值的复制过程很快,无需用引用。

2、const 修饰函数返回值

如果是值传递,没有必要将返回值用const 修饰,例如,

const int func();

函数的返回值为一个int 类型的值,这个只是个临时的值,没有必要用const 修饰,最终这个值会复制给接受它的变量。

如果返回值为引用,同上面形参,都可以提高效率。但是一般返回引用的地方并不是很多,一般会出现在类的赋值函数中。而且,用const 修饰返回值为引用类型的更少。

如果返回值为指针,加上const修饰之后,同样的内容是不能修改的。不过有一点需要注意,接受的变量也必须是const 修饰。例如,

const char* func();
char* str = func();

上面这样调用是不对的,需要将变量str 用const修饰,改为:const char* str = func();

3、const  修饰成员函数

const 修饰的成员函数为了保护成员变量,要求const 函数不能修改成员变量,否则编译会报错。
声明的时候const放在函数最后,例如,

class MyClass {
public:
    void func(int x) const;
};

const 函数的几个规则:

1)const 对象只能访问const 成员函数,非const 的对象可以访问任何成员函数,包括const 成员函数。

2)如果函数名、参数、返回值都相同的const成员函数和非const成员函数是可以构成重载,那么const对象调用const成员函数,非const对象默认调用非const的成员函数。

3)const成员函数可以访问所有成员变量,但是只能访问const的成员函数

4)非const成员函数,可以访问任何成员,包括const成员成员函数。

5)const成员函数不能修改任何的成员变量,除非变量用mutable修饰。

这一点可以理解为,在const的成员函数中成员变量都变成const属性,无法再次修改。如果函数的返回类型为此成员变量的引用,那必须也要加上const修饰。例如,

class MyClass {
public:
    int & func() const { return value; }
private:
    int value;
};

value为成员变量,func函数为const成员函数,如果要返回value的引用,这里的func函数返回值必须要加上const,改为,

const int& func() const { return value; }

当然,还有其他修改方法: 

● 把int value 改成mutable int value。mutable修饰的变量使之在const函数中可以被改变的  

● return value 改成。 return const_cast(value)。const_cast去掉了const性质。  

● 把引用去掉,写成返回值类型的。  

● 把函数后面的const去掉。  

● 返回值不是类的成员变量。           

int& func() const{ int temp = value; return temp; }

三、实例说明

#include "test.h"
 
class Test9 {
public:
    Test9() : vTest1(1), vTest2(1) { cout << "Test9 constructor.\n"; }
    ~Test9() { cout << "Test9 destructor.\n"; }
    void test1();
    void test2() const;
 
    void funcForTest9(const int *);
 
    //int &test6()const { return vTest1; }//const成员函数中的成员变量变为const 修饰,返回值必须要要const
 
private:
    void test3() { cout << "Test9 test3()\n"; }
    void test4() const { cout << "Test9 test4()\n"; }
 
    void test5() { cout << "Test9 test5().\n"; }
    void test5() const { cout << "Test9 test5()const.\n"; }
 
    void test6();
 
    int vTest1;
    const int vTest2;
};
 
void Test9::test1() {
    cout << "Test9 test1.\n";
 
    cout << "value of vTest1 is " << vTest1 << endl;
    cout << "value of vTest2 is " << vTest2 << endl;
 
    vTest1 = 10;//普通成员函数是可以修改成员变量
 
    //const 和非const的成员函数都可以访问
    test3();
    test4();
 
    test5();//调用的是非const的成员函数
 
    /*cout << endl;
    test6();*/
}
 
void Test9::test2() const{
    cout << "Test9 test2.\n";
 
    cout << "value of vTest1 is " << vTest1 << endl;
    cout << "value of vTest2 is " << vTest2 << endl;
 
    //vTest1 = 10;//const成员函数不能修改成员变量
 
    //test3();//const成员函数只能访问const函数,不能访问非const的成员函数
    test4();
 
    test5();//调用的是const的成员函数
}
 
void Test9::funcForTest9(const int* x) {//这里参数表示x指向的值不能修改
    cout << "address of pointer x is " << &x << endl;
    cout << "pointer x is " << x << endl;//这里跟实参是一个地址
    cout << "value of pointer x is " << *x << endl;//这里是实参的值
 
    int a = 20;
    cout << "\naddress of a is " << &a << endl;
 
    //*x = a; //编译器会报错,左值必须是可修改的
 
    x = &a;//代表着之前的*x不能修改,x可以修改
    cout << "address of pointer x is " << &x << endl;
    cout << "pointer x is " << x << endl;
    cout << "value of pointer x is " << *x << endl;
}
 
void Test9::test6() {
    const int **p2;
    cout << "address of p2 is " << &p2 << endl;
    cout << "value of p2 is " << p2 << endl;
    int *p1;
    cout << "address of p1 is " << &p1 << endl;
    cout << "value of p1 is " << p1 << endl;
    const int a = 10;
    cout << "address of a is " << &a << endl;
    cout << "value of a is " << a << endl;
    //p2 = &p1; //二级间接关系,是不允许非const 指针赋给const指针
    cout << "address of p2 is " << &p2 << endl;
    cout << "value of p2 is " << p2 << endl;
    *p2 = &a;
    cout << "address of p2 is " << &p2 << endl;
    cout << "value of p2 is " << p2 << endl;
    cout << "value of *p2 is " << *p2 << endl;
    cout << "address of p1 is " << &p1 << endl;
    cout << "value of p1 is " << p1 << endl;
    *p1 = 10;
}
 
void Test::test9() {//这里是给main调用的入口函数
    int x = 10;
    cout << "address of x is " << &x << endl;
 
    int *p = &x;
    cout << "address of pointer p is " << &p << endl;
    cout << "pointer p is " << p << endl;
    cout << "value of pointer p is " << *p << endl;
    cout << endl;
 
    Test9 obj;
    obj.funcForTest9(p);
 
    cout << endl;
    obj.test1();
 
    cout << endl;
    const Test9 obj_1;
    //obj_1.test1();//const对象,不能访问非const成员函数。
    cout << endl;
    obj_1.test2();
    //obj_1.vTest1 = 10;//不可以访问const 对象的任何成员
}

转自:C++ const 详解_私房菜的博客-CSDN博客_c++ const

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐