啃书《C++ Primer Plus》之 const修饰符修饰 类对象 指针 变量 函数 引用
发布日期:2021-06-23 17:48:06 浏览次数:2 分类:技术文章

本文共 4430 字,大约阅读时间需要 14 分钟。

笔者正在学习C++语言,啃书系列将会持续更新,希望可以同大家一起学习,一起进步。

系列文章:


const修饰符是C与C++语言中很常见也很常用的修饰符,但是他的用法很多很杂,在书上多次出现。由于这是一个非常重要的关键字,本文就来针对这个关键字进行总结:

在这里插入图片描述


const与变量

我们最先说起的 const 的用途是它最常见的,用于修饰一个变量。const 本意是"常量的",意味着一个被 const 修饰的变量是只读的,即在初始化之后便不能再修改它的值。因此,被 const 修饰的变量常被当做常量来使用。

在此,需要强调一个关键点:

const常量必须进行初始化!!
const常量必须进行初始化!!
const常量必须进行初始化!!

特别的,我们需要讨论下声明在全局的 const 常量。

const与宏定义

在C++的新标准中,声明在全局的 const 常量的连接性默认为内部的,也就是说一个声明在全局的 const 常量是自带 static 修饰的。(连接性为内部表明,这个变量的作用域为同一个文件中该变量的声明语句之后的全部区域,而其他文件则不能访问。有关进一步的内容,我们将在内存模型那一节谈到。)

这就使得 const全局变量起到了宏定义常量的作用,那么他们的区别有哪些呢?

  1. 二者的产生作用的方式与阶段不同。宏定义是在预处理阶段,并使用类似于搜索替换的方式。而 const 修饰的常量则是在程序编译阶段进行初始化的
  2. 另外,const 是有类型的,而宏定义是没有类型的,这在程序员使用常量方面的帮助很大,他能够有效的避免类型问题。
  3. 还有一点重要的是,const 修饰的变量是被当做特殊的变量看待的,他们具有连接性,更方便进行文件间常量的相互使用

const与类和对象

除了基本变量之外,类对象或是结构也是经常被 const 修饰的对象,而 const 在类与对象上的使用不仅限于修饰对象,也包括修饰类的成员变量以及类变量。

const修饰的类对象

同 const 修饰一般变量很相似的是,被 const 修饰的类对象也是在初始化后不能再发生改变,这里的改变主要指的是其中的成员变量,但是有一种关键字 mutable 可以修饰成员变量,使得即使对象是被 const 修饰的,也可以改变这个成员的值。

请看下面的例子:

#include
using namespace std;class A{
public: int k; mutable int s; A(int k,int a) {
this->k = k; this->s = s; }};int main(){
const A a(1,2); a.s = 0; //这条语句是正确的 a.k = 0; //这条语句是错误的}

const成员常量

除了修饰整个对象之外,const还可以用来修饰成员变量。与修饰一般的变量一样,被修饰的成员变量必须在声明时对其进行初始化,此后,它将成为对象中的常量,其值不可被改变。

需要注意的是,const修饰符与 mutable 修饰符是冲突的,二者不能同时用于修饰同一个成员变量。(一个允许修改,一个不允许修改,产生冲突不难理解)

另外,const 修饰的成员变量并非是类的常量。他们是随对象而产生的,要注意区分接下来要说的类常量。

在这之前,先看一个例子:

#include
using namespace std;class A{
public: const int k = 0;};int main(){
A a,b; cout << "the address of a.k is : " << &a.k << endl; cout << "the address of b.k is : " << &b.k << endl;}

结果显示,a和b的成员常量并非同一个变量:在这里插入图片描述

const类常量

一个变量想要变成类常量需要两步:

  1. 变成类变量
  2. 变成常量

要完成这两步,需要对成员变量使用 static 和 const 修饰符。在这之后,这个常量就属于类了,它随类一同出现,不由对象的存在而决定。

看下面的例子:

class A{
public: static const int k = 0;};

不过通常,我们也在类中使用枚举充当类常量。


const与函数

const 在类中的使用不仅限于修饰对象或是成员,他还可以修饰类中的成员方法。

const成员函数

当一个对象被 const 修饰时,意味着不能对其中的成员进行修改(不考虑 mutable)。而当调用该成员方法时,我们不能保证内部的成员会在方法中被隐式的修改,这时,就需要使用const来修饰这个成员函数来表示这个函数不会对对象成员进行任何修改。

它的写法如下:

声明: 返回类型 函数名(参数列表) const;定义: 返回类型 函数名(参数列表) const{}

我们还是来用一段代码演示:

#include
using namespace std;class A{
public: void fA() const; void fB();};int main(){
const A a; a.fA(); //这条语句是合法的 a.fB(); //这条语句是不合法的}void A::fA() const{
cout << "hello world" << endl;}void B::fB() const{
cout << "hello world" << endl;}

可以看到,尽管函数 fA 与 fB 从类型到内容都十分相似,但是因为 fB 没有被 const 修饰,不允许被 const 修饰的 a 对象调用它的 fB 函数。


const与引用

引用是给变量或对象起别名的操作,使用 const 修饰引用,会使得这个变量获得不可修改的特性。

(有关引用的知识可参考啃书系列的 《C++ 引用》那里也详细介绍了 const 修饰引用)

const修饰引用

被 const 修饰的引用不可以改变变量的值,但是没有被修饰的原变量却可以修改值。

可参考如下代码:

#include
using namespace std;int main(){
int a = 10; const int &b = a; b = 15; //这条语句是不合法的 a = 15; //这条语句是合法的}

接着,我们来讨论在使用 const 修饰引用时会出现的特殊机制:临时变量,它是编译器对引用时出现错误的妥协,由于我们使用 const 修饰引用保证了以后不对其进行修改,因此创建一个值相同的临时变量来代替原来导致错误的变量的做法是可以接受的。

被 const 修饰的引用在初始化时,会在如下两种情况下产生临时变量:

  • 当被引用的变量类型正确但不是左值时
  • 当被引用的变量类型不正确但可以被转变成正确类型时

届时,引用指向的是临时变量而非初始化提供的变量。

对于第一种情况,编译器会产生一个临时变量用于存储提供的右值并让引用变量指向他;
对于第二种情况,编译器会产生一个正确类型的变量,并将数值复制给这个变量,随后使引用变量指向他。

下面的代码演示了两种情况:

#include
using namespace std;int main(){
int a; //b 和 c将会引用临时变量 long long &b = a; //类型错误但可以转换,b 将会指向临时变量而非 a int &c = 10; //类型正确但不是左值,c 将会指向临时变量}

返回const引用

当函数返回引用被 const 修饰是,它代表着这个函数的返回引用作为左值是被 const 修饰的,不可以直接对其进行更改包括调用它的非 const 成员函数。

但是它只禁止了返回值作为左值被更改,我们仍然可以把它当做右值接下来,再对其进行改动。
可参考下面代码:

#include
using namespace std;class A{
public: int age = 0; A(int age){
this->age = age;} const A &grow(){
age++;return *this;}};int main(){
A tom(10); tom.grow().grow(); //这是不允许的 tom = tom.grow(); tom.grow(); //这是允许的}

const与指针

接下来我们讨论 const 修饰在指针上面的奇妙用途,由于指针包含了指向地址和被指向的变量两部分。因此对指针的修饰也有两种:

const修饰指针

使用 const 修饰指针本身,修饰了指针的指向关系的只读性。这种修饰的写法如下:

类型名 * const 指针名 = 地址;

需要注意的值,由于修饰的是指向的地址,因此这里必须给指针以初值。

这样修饰的指针是不被允许改变指向的地址的,因此,下面的代码存在问题:

#include
using namespace std;int main(){
int a = 10; int b = 20; int * const p = &a; p = &b; //这一步是不合法的}

const修饰指向变量

接着,是另一种修饰方式,使用 const 修饰指针指向的内容为 const 。它的写法如下:

const 类型名 *指针名;

这里仅表明指针指向的变量不可被修改,与指针指向哪里无关,所以不必要初始化。

另外需要注意的是,这里的不可修改只表明不可以通过该指针对值进行修改,而不管是否可以通过其他途径对值进行修改。

还想说的是,const 对指针的两种修饰并不矛盾,二者可以并存,可以写成这样:

const 类型名 * const 指针名 = 地址;

表明恒指向某一个地址且不能同过该指针改变地址下的值。

最后,考虑到引用的底层是通过指针实现的以及引用的特性。可以推测一个引用指针版本应该是使用 const 修饰引用本身。

而在引用前面加上 const 修饰就相当于再在指针声明处加上对指向变量的修饰。

转载地址:https://wayne-lee-jlu.blog.csdn.net/article/details/105098725 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:Nacos惊现安全漏洞修复后问题仍旧存在
下一篇:中台化改造实践--Logback动态配置 Appender (二)

发表评论

最新留言

第一次来,支持一个
[***.219.124.196]2024年04月14日 19时26分12秒