C++学习——C++中const的新花样
以下内容源于C语言中文网的学习与整理,非原创,如有侵权请告知删除。
关于C语言中const的用法,见《C语言const的用法详解》。
一、C++中const的新花样
在C语言中,const用来限制一个变量,表示这个变量不能被修改,我们称这样的变量为常量。
在C++中,const 的含义并没有改变,只是对细节进行了一些调整,以下是最主要的两点。
1、C++中的 const 更像编译阶段的 #define
先来看下面的两条语句:
const int m = 10;
int n = m;
我们知道,变量是要占用内存的,即使被 const 修饰也不例外。m、n 两个变量占用不同的内存,int n = m;表示将 m 的值赋给 n,这个赋值的过程在C和C++中是有区别的。
在C语言中,编译器会先到 m 所在的内存取出一份数据,再将这份数据赋给 n。
在C++中,编译器直接将 10 赋给 n,没有读取内存的过程,和int n = 10;的效果一样。换言之,C++ 中的常量更类似于下面的 #define命令,是一个值替换的过程,只不过 #define是在预处理阶段替换,而常量是在编译阶段替换。
#define m 10
C++ 对 const 的处理少了读取内存的过程,优点是提高了程序执行效率,缺点是不能反映内存的变化,一旦 const 变量被修改,C++ 就不能取得最新的值。
有读者提出疑问,const 变量不是禁止被修改吗?对,这种说法没错!不过这只是语法层面上的限制,通过指针仍然可以修改。下面的代码演示了如何通过指针修改 const 变量:
#include <iostream>int main(){const int n = 10;//&n得到的指针的类型是const int *,必须强制转换为int *后才能赋给pint *p = (int*)&n;*p = 99; //修改const变量的值printf("%d\n", n);return 0;
}
将上述代码放到.c文件中,以C语言的方式编译,运行结果为99。将上述代码放到.cpp文件中,以C++的方式编译,运行结果就变成了10。
这种差异正是由于C和C++对 const 的处理方式不同造成的。在C语言中,使用 printf 输出 n 时会到内存中获取 n 的值,这个时候 n 所在内存中的数据已经被修改成了 99,所以输出结果也是 99。而在C++中,printf("%d\n", n);语句在编译时就将 n 的值替换成了 10,效果和printf("%d\n", 10);一样,不管 n 所在的内存如何变化,都不会影响输出结果。
当然,这种修改常量的变态代码在实际开发中基本不会出现,本例只是为了说明C和C++对 const 的处理方式的差异:C语言对 const 的处理和普通变量一样,会到内存中读取数据;C++ 对 const 的处理更像是编译时期的#define,是一个值替换的过程。
2、C++中全局 const 变量的可见范围是当前文件
普通全局变量的作用域是当前文件,但是在其他文件中也是可见的,使用extern声明后就可以使用,这在《C语言头文件的编写》一章中进行了深入讲解。
下面是多文件编程的演示代码:
代码段1(源文件1):
#include <stdio.h>int n = 10; //修改为const int n = 10;
void func();int main(){func();printf("main: %d\n", n);return 0;
}
代码段2(源文件2):
#include <stdio.h>extern int n;void func(){printf("module: %d\n", n);
}
不管是以C还是C++的方式编译(分别将源文件后缀设置为.c\.cpp),运行结果都是:
module: 10
main: 10
接着我们修改代码段1,在 n 的定义前面加 const 限制。
如果以C的方式编译,则修改后的代码仍然能够正确编译,运行结果和上面也是一样的。这说明C语言中的 const 变量在多文件编程时的表现和普通变量一样(作用域是当前文件,但是在其他文件中也是可见的),除了不能修改,没有其他区别。
如果以C++的方式编译,则修改后的代码就是错误的。这是因为 C++ 对 const 的特性做了调整,C++ 规定,全局 const 变量的作用域仍然是当前文件,但是它在其他文件中是不可见的,这和添加static关键字的效果类似。虽然代码段2中使用 extern 声明了变量 n,但是在链接时却找不到代码段1中的 n。
由于 C++ 中全局 const 变量的可见范围仅限于当前源文件,所以可以将它放在头文件中,这样即使头文件被包含多次也不会出错,请看下面的例子。
module.h 代码:
const int n = 10;
void func();
module.cpp 代码:
#include <stdio.h>
#include "module.h"void func(){printf("module: %d\n", n);
}
main.cpp 代码:
#include <stdio.h>
#include "module.h"int main(){func();printf("main: %d\n", n);return 0;
}
运行结果:
module: 10
main: 10
C和C++中全局 const 变量的作用域相同,都是当前文件,不同的是它们的可见范围:C语言中 const 全局变量的可见范围是整个程序,在其他文件中使用 extern 声明后就可以使用;C++中 const 全局变量的可见范围仅限于当前文件,在其他文件中不可见,所以它可以定义在头文件中,多次引入后也不会出错。
如果你使用的是 GCC,那么可以通过添加 extern 关键字来增大 C++ 全局 const 变量的可见范围,如下所示:
extern const int n = 10;
这样 n 的可见范围就变成了整个程序,在其他文件中使用 extern 声明后就可以使用了。不过这种方式只适用于 GCC,不适用于 VS/VC。
3、总结
C++ 中的 const 变量虽然也会占用内存,也能使用&获取得它的地址,但是在使用时却更像编译时期的#define;#define也是值替换,可见范围也仅限于当前文件。
很多C++教程在对比 const 和 #define 的优缺点时提到,#define 定义的常量仅仅是字符串的替换,不会进行类型检查,而 const 定义的常量是有类型的,编译器会进行类型检查,相对来说比 #define 更安全,所以鼓励大家使用 const 代替 #define。
这种说法虽然没错,但是我没有发现不进行类型检查有什么隐患,我在使用 #define 的过程中也没有发现什么缺点,所以我对上面的观点持保留态度,也请大家多多指点。
二、const成员变量与const成员函数
在类内部,如果你不希望某些数据被修改,可以使用const关键字加以限定。const 可以用来修饰成员变量和成员函数。
1、const成员变量
const 成员变量的用法和普通 const 变量的用法相似,只需要在声明时加上 const 关键字。
初始化 const 成员变量只有一种方法,就是通过构造函数的初始化列表,见博文的一.5。
2、const成员函数
const 成员函数也称为常成员函数,常成员函数需要在声明和定义的时候,在函数头部的结尾加上 const 关键字。
const 成员函数可以使用类中的所有成员变量,但是不能修改它们的值,这种措施主要还是为了保护数据而设置的。比如我们通常将 get 函数(读取成员变量的函数的名字通常以get开头,后跟成员变量的名字,所以通常将它们称为 get 函数)设置为常成员函数。
请看下面的例子:
class Student{
public:Student(char *name, int age, float score);void show();//声明常成员函数char *getname() const;int getage() const;float getscore() const;
private:char *m_name;int m_age;float m_score;
};Student::Student(char *name, int age, float score): m_name(name), m_age(age), m_score(score){ }
void Student::show(){cout<<m_name<<"的年龄是"<<m_age<<",成绩是"<<m_score<<endl;
}
//定义常成员函数
char * Student::getname() const{return m_name;
}
int Student::getage() const{return m_age;
}
float Student::getscore() const{return m_score;
}
三、const对象(常对象)
在C++中,const 也可以用来修饰对象,称为常对象。
定义常对象的语法和定义常量的语法类似:
const class_name object_name(params);
//或者
class_name const object_name(params);
当然你也可以定义 const 指针:
const class_name *p = new class_name(params);
//或者
class_name const *p = new class_name(params);
一旦将对象定义为常对象之后,不管是上面哪种形式,该对象就只能访问被 const 修饰的成员(包括 const 成员变量和 const 成员函数),因为非 const 成员可能会修改对象的数据(编译器也会这样假设),C++禁止这样做。
常对象使用举例:
#include <iostream>
using namespace std;class Student{
public:Student(char *name, int age, float score);
public:void show();char *getname() const;int getage() const;float getscore() const;
private:char *m_name;int m_age;float m_score;
};Student::Student(char *name, int age, float score): m_name(name), m_age(age), m_score(score){ }
void Student::show(){cout<<m_name<<"的年龄是"<<m_age<<",成绩是"<<m_score<<endl;
}
char * Student::getname() const{return m_name;
}
int Student::getage() const{return m_age;
}
float Student::getscore() const{return m_score;
}int main(){const Student stu("小明", 15, 90.6);//stu.show(); //errorcout<<stu.getname()<<"的年龄是"<<stu.getage()<<",成绩是"<<stu.getscore()<<endl;const Student *pstu = new Student("李磊", 16, 80.5);//pstu -> show(); //errorcout<<pstu->getname()<<"的年龄是"<<pstu->getage()<<",成绩是"<<pstu->getscore()<<endl;return 0;
}
本例中,stu、pstu 分别是常对象以及常对象指针,它们都只能调用 const 成员函数。
相关文章:
C++学习——C++中const的新花样
以下内容源于C语言中文网的学习与整理,非原创,如有侵权请告知删除。 关于C语言中const的用法,见《C语言const的用法详解》。 一、C中const的新花样 在C语言中,const用来限制一个变量,表示这个变量不能被修改…...
【Linux环境搭建】五、Linux(CentOS7)编译源码安装Subversion
Subversion(简称为SVN)是一种版本控制系统,能够管理和跟踪项目开发中的代码变化。Subversion最初由CollabNet公司开发,现已成为Apache软件基金会的顶级项目之一。 Subversion使用一个中央仓库来存储所有项目文件和历史记录。开发人…...
微信小程序入门讲解【超详细】
一. 微信小程序简介 1.1 什么是小程序 2017年度百度百科十大热词之一 微信小程序(wei xin xiao cheng xu),简称小程序,英文名Mini Program,是一种不需要下载安装即可使用的应用( 张小龙对其的定义是无需安装…...
AtCoder ABC239G 最小割集
题意 传送门 AtCoder ABC239G Builder Takahashi 题解 将原图中每个节点拆为入点 v v v 与出点 v ′ v v′,对于原图任一边 ( u , v ) (u,v) (u,v) 则 u ′ → v , v → u u\rightarrow v, v\rightarrow u u′→v,v→u 连一条容量为 ∞ \infty ∞ 的边&…...
Simple RPC - 01 框架原理及总体架构初探
文章目录 概述RPC 框架是怎么调用远程服务的?客户端侧的逻辑服务端侧的逻辑完整流程 客户端是如何找到服务端地址的呢?核心:NamingService跨语言的RPC实现原理 RPC 框架的总体结构对外接口服务注册中心如何使用业务服务接口客户端服务端 模块…...
VScode运行C/C++
VScode运行C/C VScode的安装这里不讲 一、mingw64的下载 二、VS code打开文件夹与创建C文件 ----------------这一步给萌新看,有C和VScode的基础可跳过---------------- 1.创建一个文件夹 2.vscode打开刚刚创建的文件夹 3.新建文件,在输入文件名1.c后…...
#智能车项目(三)串口初始化
串口1初始化 初始化串口1PA9 PA10 流程 1、声明结构体 GPIO_InitTypeDef GPIO_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; USART_InitTypeDef USART_InitStructure; 2、打开时钟 // 打开串口GPIO的时钟 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA , ENABLE); /…...
网络通信错误代码列表 HTTP 、FTP
HTTP 1xx(临时响应):表示临时响应并需要请求者继续执行操作的状态代码。 100 (继续) 请求者应当继续提出请求。服务器返回此代码表示已收到请求的第一部分,正在等待其余部分。 101 (切换协议…...
最新开源ThinkPHP6框架云梦卡社区系统源码/亲测可用(全新开发)
源码简介: 最新开源ThinkPHP6云梦卡社区系统源码,它是一款基于ThinkPHP 6框架开发的开源社区系统源码。该系统源码具有强大而稳定的后端架构,和简洁易操作的前端界面,能够给人们提供完整的社区功能和更具体的服务。 全新云梦卡社…...
[ROS2系列] ubuntu 20.04测试rtabmap
目录 背景: 一、配置 turtlebot3 二、安装RTAB-Map ROS2包: 三、启动 Turtlebot3 模拟器: 四、启动 RTAB 地图: 五、启动导航(nav2_bringup应安装软件包): 背景: 1、设备&…...
【Java学习之道】JavaFx 框架与组件介绍
引言 如果你曾经尝试过使用Java编写一个漂亮的窗口应用程序,那么你一定知道JavaFX这个强大的工具。JavaFX是Java 8中引入的一个GUI开发框架,它提供了丰富的组件和功能,使得我们可以轻松地创建出功能强大、界面美观的桌面应用程序。无论你是想…...
Windows bat 脚本设计-开机自启动服务的方法、bat 调用另外的 bat 脚本 -没有java环境也能运行jar,在不安装jdk下如何运行jar包
目录 一、start.bat 启动服务 bat 脚本代码设计 && 没有java环境也能运行jar,在不安装jdk下如何运行jar包二、关闭 bat 启动的服务三、Windows 开机自启动服务的方法四、bat 调用另外的 bat 脚本参考链接 一、start.bat 启动服务 bat 脚本代码设计 &&am…...
zabbix触发器与动作
一、触发器(Trigger) 1、概念: 在 Zabbix 中,触发器用于监测 Zabbix 监控系统中的各种指标和条件,并在特定条件满足时触发警报。(触发器用于定义监控项的报警阈值) 2、触发器对象:…...
华纳云:Nginx服务器可视化配置问题怎么解决
Nginx服务器可视化配置问题通常是由于缺少适当的工具或配置导致的。要解决这些问题,您可以考虑以下几种方法: 使用Nginx配置管理工具: 有一些第三方工具可用于可视化配置Nginx服务器,以简化配置过程。其中一些工具包括cPanel、Ple…...
C指针与一维二维数组、数组指针与指针数组、函数指针_数组的理解使用
文章目录 一、指针数组 和 数组指针① 数组指针② 指针数组 二、函数指针与函数指针数组① 函数指针② 函数指针数组 三、指针与一维、二维数组① 指针与一维数组② 指针与二维数组 一、指针数组 和 数组指针 ① 数组指针 数组指针(Array Pointer)是指…...
安装运行vue-element-admin的报错问题-解决办法
文章目录 1.第一处2.第二处3.安装运行 在使用vue-element-admin时,使用命令安装: npm install -registryhttps://registry.npm.taobao.org会报错,不通过。需要修改两处。 1.第一处 在目录vue-element-admin-master\src\components\Markdown…...
高数笔记03:几何、物理应用
图源:文心一言 本文是我学习高等数学几何、物理应用的一些笔记和心得,希望可以与考研路上的小伙伴一起努力上岸~~🥝🥝 第1版:查资料、画导图~🧩🧩 参考资料:《高等数学 基础篇》武…...
js + selenium 获取chatgpt的accessToken
chatgpt的accessToken非常有用,在做web api对接时,因为登录超时 会刷新accessToken let elements document.querySelectorAll(.token-string);let concatenatedText [8,9,10].map(index > {return elements[index] ? elements[index].textContent …...
Spring MVC 十一:中文乱码
SpringMVC的中文乱码问题其实已经不是什么问题了,无非就是配置编码方式->解决问题。 但是由于SpringMVC可以通过:xml方式配置、Servlet3.0方式配置,以及是否使用EnableWebMvc等,不同配置方式下,解决中文乱码问题的…...
Excel恢复科学技术法显示的数据
Excel中输入位数较大的数据时,软件会自动使用科学计数法显示。很多时候并不需要这样的计数格式,所以需要把它转变为普通的数字格式 操作方法 选中单元格/列/行》右键》设置单元格式 在打开的窗口中,切换到“数字”选项卡,点击“自…...
Day131 | 灵神 | 回溯算法 | 子集型 子集
Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣(LeetCode) 思路: 笔者写过很多次这道题了,不想写题解了,大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...
解锁数据库简洁之道:FastAPI与SQLModel实战指南
在构建现代Web应用程序时,与数据库的交互无疑是核心环节。虽然传统的数据库操作方式(如直接编写SQL语句与psycopg2交互)赋予了我们精细的控制权,但在面对日益复杂的业务逻辑和快速迭代的需求时,这种方式的开发效率和可…...
大数据零基础学习day1之环境准备和大数据初步理解
学习大数据会使用到多台Linux服务器。 一、环境准备 1、VMware 基于VMware构建Linux虚拟机 是大数据从业者或者IT从业者的必备技能之一也是成本低廉的方案 所以VMware虚拟机方案是必须要学习的。 (1)设置网关 打开VMware虚拟机,点击编辑…...
【第二十一章 SDIO接口(SDIO)】
第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...
CentOS下的分布式内存计算Spark环境部署
一、Spark 核心架构与应用场景 1.1 分布式计算引擎的核心优势 Spark 是基于内存的分布式计算框架,相比 MapReduce 具有以下核心优势: 内存计算:数据可常驻内存,迭代计算性能提升 10-100 倍(文档段落:3-79…...
【项目实战】通过多模态+LangGraph实现PPT生成助手
PPT自动生成系统 基于LangGraph的PPT自动生成系统,可以将Markdown文档自动转换为PPT演示文稿。 功能特点 Markdown解析:自动解析Markdown文档结构PPT模板分析:分析PPT模板的布局和风格智能布局决策:匹配内容与合适的PPT布局自动…...
比较数据迁移后MySQL数据库和OceanBase数据仓库中的表
设计一个MySQL数据库和OceanBase数据仓库的表数据比较的详细程序流程,两张表是相同的结构,都有整型主键id字段,需要每次从数据库分批取得2000条数据,用于比较,比较操作的同时可以再取2000条数据,等上一次比较完成之后,开始比较,直到比较完所有的数据。比较操作需要比较…...
Docker拉取MySQL后数据库连接失败的解决方案
在使用Docker部署MySQL时,拉取并启动容器后,有时可能会遇到数据库连接失败的问题。这种问题可能由多种原因导致,包括配置错误、网络设置问题、权限问题等。本文将分析可能的原因,并提供解决方案。 一、确认MySQL容器的运行状态 …...
写一个shell脚本,把局域网内,把能ping通的IP和不能ping通的IP分类,并保存到两个文本文件里
写一个shell脚本,把局域网内,把能ping通的IP和不能ping通的IP分类,并保存到两个文本文件里 脚本1 #!/bin/bash #定义变量 ip10.1.1 #循环去ping主机的IP for ((i1;i<10;i)) doping -c1 $ip.$i &>/dev/null[ $? -eq 0 ] &&am…...
【题解-洛谷】P10480 可达性统计
题目:P10480 可达性统计 题目描述 给定一张 N N N 个点 M M M 条边的有向无环图,分别统计从每个点出发能够到达的点的数量。 输入格式 第一行两个整数 N , M N,M N,M,接下来 M M M 行每行两个整数 x , y x,y x,y,表示从 …...
