C++学习——C++中const的新花样

2023-10-15 06:12
文章标签 c++ 学习 const 新花样

本文主要是介绍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的新花样的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/215861

相关文章

Win32下C++实现快速获取硬盘分区信息

《Win32下C++实现快速获取硬盘分区信息》这篇文章主要为大家详细介绍了Win32下C++如何实现快速获取硬盘分区信息,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 实现代码CDiskDriveUtils.h#pragma once #include <wtypesbase

C++ Primer 标准库vector示例详解

《C++Primer标准库vector示例详解》该文章主要介绍了C++标准库中的vector类型,包括其定义、初始化、成员函数以及常见操作,文章详细解释了如何使用vector来存储和操作对象集合,... 目录3.3标准库Vector定义和初始化vector对象通列表初始化vector对象创建指定数量的元素值

C++实现回文串判断的两种高效方法

《C++实现回文串判断的两种高效方法》文章介绍了两种判断回文串的方法:解法一通过创建新字符串来处理,解法二在原字符串上直接筛选判断,两种方法都使用了双指针法,文中通过代码示例讲解的非常详细,需要的朋友... 目录一、问题描述示例二、解法一:将字母数字连接到新的 string思路代码实现代码解释复杂度分析三、

C++一个数组赋值给另一个数组方式

《C++一个数组赋值给另一个数组方式》文章介绍了三种在C++中将一个数组赋值给另一个数组的方法:使用循环逐个元素赋值、使用标准库函数std::copy或std::memcpy以及使用标准库容器,每种方... 目录C++一个数组赋值给另一个数组循环遍历赋值使用标准库中的函数 std::copy 或 std::

C++使用栈实现括号匹配的代码详解

《C++使用栈实现括号匹配的代码详解》在编程中,括号匹配是一个常见问题,尤其是在处理数学表达式、编译器解析等任务时,栈是一种非常适合处理此类问题的数据结构,能够精确地管理括号的匹配问题,本文将通过C+... 目录引言问题描述代码讲解代码解析栈的状态表示测试总结引言在编程中,括号匹配是一个常见问题,尤其是在

使用C++实现链表元素的反转

《使用C++实现链表元素的反转》反转链表是链表操作中一个经典的问题,也是面试中常见的考题,本文将从思路到实现一步步地讲解如何实现链表的反转,帮助初学者理解这一操作,我们将使用C++代码演示具体实现,同... 目录问题定义思路分析代码实现带头节点的链表代码讲解其他实现方式时间和空间复杂度分析总结问题定义给定

C++初始化数组的几种常见方法(简单易懂)

《C++初始化数组的几种常见方法(简单易懂)》本文介绍了C++中数组的初始化方法,包括一维数组和二维数组的初始化,以及用new动态初始化数组,在C++11及以上版本中,还提供了使用std::array... 目录1、初始化一维数组1.1、使用列表初始化(推荐方式)1.2、初始化部分列表1.3、使用std::

C++ Primer 多维数组的使用

《C++Primer多维数组的使用》本文主要介绍了多维数组在C++语言中的定义、初始化、下标引用以及使用范围for语句处理多维数组的方法,具有一定的参考价值,感兴趣的可以了解一下... 目录多维数组多维数组的初始化多维数组的下标引用使用范围for语句处理多维数组指针和多维数组多维数组严格来说,C++语言没

Java深度学习库DJL实现Python的NumPy方式

《Java深度学习库DJL实现Python的NumPy方式》本文介绍了DJL库的背景和基本功能,包括NDArray的创建、数学运算、数据获取和设置等,同时,还展示了如何使用NDArray进行数据预处理... 目录1 NDArray 的背景介绍1.1 架构2 JavaDJL使用2.1 安装DJL2.2 基本操

c++中std::placeholders的使用方法

《c++中std::placeholders的使用方法》std::placeholders是C++标准库中的一个工具,用于在函数对象绑定时创建占位符,本文就来详细的介绍一下,具有一定的参考价值,感兴... 目录1. 基本概念2. 使用场景3. 示例示例 1:部分参数绑定示例 2:参数重排序4. 注意事项5.