C/C++ const
发布日期:2021-06-29 19:18:24 浏览次数:2 分类:技术文章

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

const 是 C 语言的关键字,经 C++ 扩充,功能变得强大,用法复杂。const 用于定义一个常变量(只读变量)。当 const 与指针、引用、函数等结合起来使用时,情况会变得更加复杂。下面将从七个方面总结 const 的用法。

1.const 位置

const 位置较为灵活,一般来说,除了修饰一个类的成员函数外,const 不会出现在一条语句的最后。示例如下:

#include 
using namespace std;int main(int argc,char* argv[]) { int i=5; const int v1=1; int const v2=2; const int* p1; int const * p2; // 以下三条语句报编译错误,为什么? //const * int p3; //int* const p3=&v1; //int * const p3; int * const p3=&i; const int* const p4=&v1; int const * const p5=&v2; const int & r1=v1; int const & r2=v2; // 以下语句报编译错误,为什么? //const & int r3; // 以下语句报警告,并忽略 const int& const r4=i; cout<<*p4<

阅读以上程序,得出如下结论:

(1)程序输出结果是 1,以上程序演示const的位置与它的语义之间的关系,看似复杂,实际有规律可循。
(2)const和数据类型结合在一起时形成所谓的“常类型”,利用常类型可申明或定义常变量。const用来修饰类型时,既可以放在类型前面,也可以放在类型后面,如 const int iint const i 是合法且等价的。用常类型申明或定义变量时,const 只会出现在变量前面。
(3)const 和被修饰的类型之间不能有其他标识符。
(4)int const * pint * const p是不同的申明语句,前者 const 修饰的是 int,后者 const 修饰的是int*。前者表示指针 p 指向整型常变量(指针所指单元的内容不允许修改),而指针本身可以指向其他的常变量,即p 为指向常量的指针——常量指针。后者表示指针 p 本身的值不可修改,一旦 p 指向某个整型变量之后就不能指向其他的变量,即 p 是个指针常量。
(5)引用本身可以理解为指针常量,在引用前使用 const 没有意义。上例中int & const r4=i;中 const 是多余的,即没有引用常量的说法,只有常引用。常引用指被引用对象是一个常量,不允许通过引用修改被引用对象的值。

在很多情况下,为表达同一种语义,可将 const 放在不同的位置。但在某些情况下,const 只能放在特定的位置,考查 const 配合二重指针的例子,代码如下:

int main(int argc,char* argv[]) {	// const 配合二重指针	int const **p1;	int* const * p2;	int i=5;	int j=6;	const int * ptr1=&i;	int * const ptr2=&j;	p1=&ptr1;	p2=&ptr2;	cout<<**p1<< " "<<**p2<

阅读以上代码得出如下结论:

(1)程序的运行结果是:5 6;
(2)int const **p1int* const * p2申明的二重指针 p1 和 p2 的含义完全不同。p1 不是指针常量,其指向的变量类型是int const *(指向整型常量的指针)。P2 也不是指针常量,其指向的变量类型是int* const(整型指针常量),所以 p1=&ptr2p2=&ptr1 均产生编译错误。

2.const 对象和对象的 const 成员

const 定义一个基本类型的变量是不允许修改该变量的值。const 修饰类的对象称为常对象,const 修饰的类成员函数称为常函数。考查如下代码:

#include 
using namespace std;class A { int num;public: A(){num =5;}; void disp(); void disp() const; void set(int n){num=n;};};void A::disp() { cout<<"Another version of disp()"<

程序执行结果是:

Another version of disp()5

阅读以上程序,得出如下结论:

(1)常函数的申明与定义分开进行时,两边都要使用const关键字,否则发生编译错误。

(2)只有类的非静态成员函数可以被申明为常函数,原因是静态成员函数不含this指针,属于类级别的函数。其它类型的函数(如外部函数等)不能被申明为常函数。

(3)一个类的两个成员函数,如果函数的返回值类型、函数名、函数的参数列表完全相同,一个是常函数,一个是普通函数,那么它们构成重载关系。如上例中的void disp()和void disp() const,被定义成class A的成员函数,由于C++把this指针也作为参数评估的一部分,那么它们最终会被看作void disp(A*)void disp(const A*),从而构成重载。

(4)非只读对象(如a1)调用某个函数时,先寻找它的非const函数版本,如果没有找到,再调用它的const函数版本。而常对象(a2),只能调用类中定义的常函数,否则出现编译错误。

(5)存在const和非const版本的成员函数时,普通对象若想调用const函数,应该通过建立该对象的常引用或指向该对象的常指针。如上面的程序,要用对象a1调用常函数disp(),可以使用如下语句:

((const A&)a1).disp();//或者((const A*)&a1)->disp();

(6)非只读对象中,也可以将部分数据成员定义为常量,称为类对象的常量成员。类对象的非静态常量成员必须在构造函数中初始化,且只能借助于初始化列表,因为初始化列表才是初始化,构造函数中通过赋值运算符进行的是赋值,并非初始化。

3.const 修饰函数参数和返回值

在定义函数时常用到 const,主要用来修饰参数和返回值。其目的是让编译器为程序员做变量的只读性检查,以使程序更加健壮。考查如下代码:

void disp1(const int &ri) {	cout<
<

程序运行结果:

55556

阅读以上代码得出如下结论:

(1)const 修饰传递调用的形参申明为常量,没有实用价值。如上例中的void disp2(cons tint i)这样的申明没有意义,因为形参 i 的改变并不影响实参的值。
(2)函数的返回值是值类型时,被 const 修饰没有意义,因为此时返回值是一个非左
值,本身就不能改变,上例中const int disp3(cons tint& ri)对返回值的 const 限定是多余的。
(3)const 修饰值类型的形参时,不构成函数重载,如void disp(const int i)void disp(int i)。但当 const 修饰非值类型(引用、指针)的形参时构成函数重载,如void disp(const int& i)void disp(int& i)

4.常见对 const 的误解

(1)误解一:用 const 修改的变量值一定是不能改变的。当 const 修饰的局部变量存储在非只读存储器中,通过指针可间接修改;

(2)误解二:常引用或常指针,只能指向常变量,这是一个极大的误解。常引用或者常指针只能说明不能通过该引用(或者该指针)去修改被引用的对象,至于被引用对象原来是什么性质是无法由常引用(常指针)决定的。

5. 将 const 类型转化为非 const 类型

使用 C++ 中 cons_cast 运算符可去除复合类型中的 const 或 volatile 属性。大量使用 const_cast 是不明智的,只能说明程序存在设计缺陷。使用方法见下例:

void constTest() {	int i;	cout<<"please input a integer:";	cin>>i;	const int a=i;	int& r=const_cast
(a);//若写成int& r=a;则发生编译错误 ++r; cout<
<

程序输入5,输出6。阅读以上程序,得出如下结论:

(1)const_cast 运算符的语法形式是 const_cast<type_id> (expression)
(2)const_cast 只能去除目标的 const 或者 volatile 属性,不能进行不同类型的转换。如下转换是错误的:

const int A={1,2,3};char* p=const_cast
(A);//不能由const int[]转换为char*

(3)一个变量被定义为只读变量(常变量),那么它永远是常变量。cosnt_cast取消的是间接引用时的改写权限,而不能改变变量本身的 const 属性。

(4)利用传统的 C 语言中的强制类型转换也可以将 const type* 类型转换为 type* 类型,或者将 const type& 转换为 type& 类型。但是使用 const_cast 会更好一些,因为 const_cast 转换能力较弱,目的单一明确,不易出错,而 C 风格的强制类型转换能力太强,风险较大,故建议不要采用 C 风格的强制类型转换。

6.C++ const 与 C const 的区别

先说一下 C 中 const 与 #define 的区别。#define是宏定义,定义的内容是存放在符号表中的文字常量,不能寻址。const修饰的是常变量,是可寻址的,且具有外部连接性。

在 <<C++编程思想>> 中提到, C++ const 与 C const 的区别是 C++ const 变量默认为内部连接(Internal Linkage),也就是说const仅在const被定义过的源文件里可见,而在连接时不能被其他编译单元看到。但在C中的const变量是具有外部连接性的。参见如下代码,有两个源文件 main.cpp 和 const.cpp。

//const.cppconst int a=1;//main.cpp#include 
using namespace std; extern const int a;int main(int argc,char* argv[]){ cout<<"b:"<
<

上面的这段代码在 VS2017 是不能编译通过的,提示错误如下:

error LNK2001: 无法解析的外部符号 "int const a" (?a@@3HB)

但是将两个源文件缀名改为 .c,采用 C 语言的编译器编译的话,就可以通过。<<C++编程思想>>中还提到,通常C++编译器并不为 const 变量创建存储空间,相反它把这个定义保存在它的符号表里,除非像 extern const int a; 使用 extern 进行定义(另外一些情况,如取一个 const 地址),那么 C++ 编译器会为const变量分配存储空间。这是因为 extern 意味着变量具有外部连接性,因此必须分配存储空间,也就说会有多个不同的编译单元引用它,所以它必须有存储空间来提供寻址的能力。

这里需要注意,通常情况下,extern 不是定义变量的一部分,常用于申明,不会分配存储空间。但是如果在定义 const 变量时使用 extern,那么说明该const变量具有外部连接性,促使 C++ 编译器为 const 变量分配存储空间,看来 extern 与 const 结合时的用法很是耐人寻味啊。

此外,还需要注意的是为什么使用 const 定义变量时,C++ 编译器并不为 const 变量创建存储空间,相反把这个定义保存在的符号表里。那是因为编译时会进行常量折叠。常量折叠是一种被很多现代编译器使用的编译优化技术,在编译时简化常量表达式的一个过程。简单来说就是将常量表达式计算求值,并用求得的值来替换表达式,放入常量表,可以算作一种编译优化。

7.extern const 使用注意事项

如果在同一个源文件定义 const 变量,使用 extern const 去前置申明它时,会发生什么情况,考察如下代码:

#include 
using namespace std; extern const int a;int main(int argc,char* argv[]) { cout<<"a:"<
<

这段代码编译不通过,报如下错误:

1>main.obj : error LNK2001: 无法解析的外部符号 "int const a" (?a@@3HB)

这时,在定义const int a=8前面加上extern即可,看来,extern const申明和定义变量需成对出现。 如果使用 extern const 来前置申明一个不具有外部连接性的 const 变量,是会报错的,因为使用 extern 申明变量的前提是变量具有外部连接性。


参考文献

[1] 陈刚.C++高级进阶教程[M].武汉:武汉大学出版社,2008.C1.3 const 的用法&C1.4 const_cast 的用法.P4-12

[2] C++编程思想.机械工业出版社.2015
[3] 百度百科.常量折叠

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

上一篇:C++ 引用的本质
下一篇:自动化测试工具QTP的使用实例

发表评论

最新留言

能坚持,总会有不一样的收获!
[***.219.124.196]2024年04月27日 21时25分54秒