当前位置: 首页 > news >正文

C++ const 详解

前言:

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;
cont 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

相关文章:

  • QT之QML
  • QNX是什么
  • QT中的pro变量解析
  • Qt编译debug和release版本--CONFIG(debug,debug|release)
  • QMake指南(Pro文件指南)
  • Qt常用命令和pro参数
  • Qt creator中项目的构建配置和运行设置的步骤
  • UTF-8中Bom和无 Bom区别
  • MSVC编译器介绍
  • QT解决MSVC中文乱码问题
  • qt中的toUtf8, toLatin1, Local8bit, toUcs4
  • C++队列queue用法详解
  • C++中,new/delete和malloc/free的区别
  • C++多线程讲解
  • 高速摄像机
  • 【React系列】如何构建React应用程序
  • Android交互
  • es6--symbol
  • FastReport在线报表设计器工作原理
  • Java 最常见的 200+ 面试题:面试必备
  • java2019面试题北京
  • js 实现textarea输入字数提示
  • Laravel Mix运行时关于es2015报错解决方案
  • python 学习笔记 - Queue Pipes,进程间通讯
  • Shadow DOM 内部构造及如何构建独立组件
  • socket.io+express实现聊天室的思考(三)
  • V4L2视频输入框架概述
  • Vue UI框架库开发介绍
  • 回顾2016
  • 码农张的Bug人生 - 初来乍到
  • 每天一个设计模式之命令模式
  • 如何设计一个微型分布式架构?
  • 世界编程语言排行榜2008年06月(ActionScript 挺进20强)
  • 微信公众号开发小记——5.python微信红包
  • 新书推荐|Windows黑客编程技术详解
  •  一套莫尔斯电报听写、翻译系统
  • 赢得Docker挑战最佳实践
  • 正则与JS中的正则
  • 宾利慕尚创始人典藏版国内首秀,2025年前实现全系车型电动化 | 2019上海车展 ...
  • 扩展资源服务器解决oauth2 性能瓶颈
  • ​猴子吃桃问题:每天都吃了前一天剩下的一半多一个。
  • (1/2) 为了理解 UWP 的启动流程,我从零开始创建了一个 UWP 程序
  • (Git) gitignore基础使用
  • (HAL库版)freeRTOS移植STMF103
  • (附源码)spring boot建达集团公司平台 毕业设计 141538
  • (附源码)springboot金融新闻信息服务系统 毕业设计651450
  • (力扣记录)1448. 统计二叉树中好节点的数目
  • (七)Knockout 创建自定义绑定
  • (五)Python 垃圾回收机制
  • (一)插入排序
  • (转)linux下的时间函数使用
  • (转)shell中括号的特殊用法 linux if多条件判断
  • **PHP分步表单提交思路(分页表单提交)
  • .md即markdown文件的基本常用编写语法
  • .Net IE10 _doPostBack 未定义