C++的那些事:const用法面面观

一、const是什么

在 C/C++ 语言中,const关键字是一种修饰符。所谓“修饰符”,就是在编译器进行编译的过程中,给编译器一些“要求”或“提示”,但修饰符本身,并不产生任何实际代码。就 const 修饰符而言,它用来告诉编译器,被修饰的这些东西,具有“只读”的特点。在编译的过程中,一旦我们的代码试图去改变这些东西,编译器就应该给出错误提示。

所以,const修饰符的作用主要是利用编译器帮助我们检查自己代码的正确性。我们使用const在源码中标示出“不应该改变”的地方,然后利用编译器,帮助我们检查这些地方是否真的没有被改变过。如果我们不小心去修改了这些地方,编译器就会报错,从而帮助我们纠正错误。使用const和不使用const,对于最终编译产生的代码并没有影响。

虽然const对于最终代码没有影响,但是尽可能使用const,将帮助我们避免很多错误,提高程序正确率。

二、const可以修饰哪些对象

在上面已经提到过了,const是一种修饰符,那它可以作为哪些对象的修饰符呢?下面列举了一些C/C++中用到const的地方。

1,const变量

2,const指针

3,const引用

4,const类

5,类的const成员变量

6,类的const成员函数

7,const修饰函数的形参与返回值

下面我们分别讨论上面几种情况下,const的用法。

三、const与变量

当一个变量被const修饰后,具有以下几个特点:

1)该变量只能读取不能修改。(编译器进行检查)

2)定义时必须初始化。

3)C++中喜欢用const来定义常量,取代原来C风格的预编译指令define。

1 const int var; // Error:常量 变量"var"需要初始化设定项
2 const int var1 = 42;
3 var1 = 43; // Error:表达式必须是可以修改的左值

上面代码中第一行和第三行都有错误,注释便是编译器给出的错误提示。

另外注意,在使用const变量作为数组的下标时,变量的值一定要是一个常量表达式(在编译阶段就能计算得到结果)。

1 const int sz = 42;
2 int iAr[sz];
3 const int sz1 = size(); // size()必须是一个返回常量的函数
4 int iAr1[sz1];
5
6 int var = 42;
7 const int sz2 = var;
8 int iAr2[sz2]; // error:sz2只有运行时才知道值

四、const与引用

我们知道,引用必须在定义的时候赋值,这样就会所引用的变量绑定在一起并作为它的一个别名,在程序中的其他地方,是不能让引用再与其他对象绑定。这个特性,让引用看起来就像是const对象一样,一旦定义后将不能更改。所以并不存在const的引用。

但是我们却可以引用一个const的对象(变量),我们称之为对常量的引用,与普通的引用不同的时,对常量的引用不能被用作修改它所绑定的对象。

1 const int ci = 1024;
2 const int &r1 = ci;
3 r1 = 42; // Error:r1是对常量的引用
4 int & r2 = ci; //Error:不能将一个非常量引用指向一个常量的对象

我们知道,引用的类型必须与其所引用对象的类型一致,如下面的代码:

double dval = 3.14;
int& ri = dval; // Error:无法用double类型的值初始化int&类型的引用(非常量限定)

上述代码为何不行?

此处ri引用了一个int型的整数。对于ri的操作数应该是整数运算,但是dval却是一个双精度的浮点数而非整数。因此为了确保让ri绑定一个整数,编译器把上述代码变成了如下形式:

double dval = 3.14;
int temp = dval;
int& ri = temp;

其中temp是一个临时变量,而ri绑定了一个临时量,所以当ri改变时,并没有改变davl的值,所以这种引用是无效的。

也许你注意到了,当我们把double变量绑定在一个int&类型上时,编译器提示后有个括号:非常量限定。这说明如果是一个常量的引用,则有可能是通过的,显然下面的代码就没有任何问题:

double dval = 3.14;
const int& ri = dval;

因为在这里,ri是一个常量引用,我们并不想通过ri改变dval的值,只要能读到dval对应的int型的值就行。

五、const与指针

我们知道,指针与引用不同,指针本身是一个对象,所以存在常量指针,这种指针在定义并初始化后,便不能再指向其他变量。用来修饰这种常量指针的const,我们称之为"顶层const"。

与顶层指针对应的是底层指针,这种指针指向一个const修改的对象,这一点上就有点像是常量的引用。

对于指向常量的指针或引用,都有以下规则:

1)可以将一个非const对象的地址赋给一个指向const对象的指针

2)可以将一个非const对象的地址赋给一个指向非const对象的指针

3)可以将一个const对象的地址赋给一个指向const对象的指针

4)不可以将一个const对象的地址赋给一个指向非const对象的指针。

1 int var;
2 const int ci = 42;
3
4 int *p1 =& var;
5 int *p2 = &ci; // Error,const int* 不能用于初始化int*
6 const int *p3 = &var; //ok
7 const int *p4 = &ci; // ok

还有一种指向const对象的const指针,这种指针首先表明,本身是一个const指针,一旦初始化后不能指向其他对象;其次,它本身所指向的对象也是一个常量,即不能通过指针修改对象的值。

const int var = 42;
const int* const p = &var;

这里再强调一点,const只是给编译器看的,我们可以很轻松的骗过编译器,并看看编译器都做了什么:

1 const int var = 42;
2 int* p = (int*)&var;
3 *p = 20;
4 cout << var << endl;     //42
5 cout << *p << endl;     //20

我们在代码的第2行,用一个类型转换强制的,把一个非const指针指向了一个const对象。

但是后面我们通过这个指针来修改这个值,却没有生效,原因呢?

那是因为编译器在编译阶段发现var是一个常量,所以在编译目标代码时已经将var的地方都用42进行了替换。

六、const与类

其实类定义的对象,与普通的变量是一样的,用const修饰时,说明这个类是一个常量类对象,这个对象有下面2个特点:

1)不能改变其成员变量(非mutalbe成员)

2)不能调用其非const成员函数

 1 class AClass{
 2     public:
 3         int m_var;
 4         mutable int m_mutable_var;
 5         void setVar(int var){ m_var = var; }
 6         void printVar(){ cout << m_var; }
 7         void printVar_const()const { cout << m_var; }
 8     };
 9
10     const AClass ac;
11     ac.m_var = 20; // Error:ac是一个const类,不能修改成员变量
12     ac.m_mutable_var = 42; // ok 可以修改mutable修饰的变量
13     ac.setVar(20); // Error: ac不能调用非const成员函数,而且这个成员函数还修改了成员变量的值
14     ac.printVar();// Error:ac不能调用非const成员函数
15     ac.printVar_const(); // ok

七、const与类的成员

1,const成员变量

const 成员变量指的是类中的成员变量为只读,不能够被修改(包括在类外部和类内部)。

1)const 成员变量必须在类的构造函数初始化表达式中被初始化,即使在构造函数体内也不可以。

2)静态 const 成员变量需要在类外部单独定义并初始化(可定义在头文件)

1 class constTestClass
2 {
3 public:
4     const int var;
5     static const int sci;
6 public:
7     constTestClass() :var(42){} // const成员变量必须在类的构造函数初始化列表中初始化
8 };
9 const int constTestClass::sci = 42; // static const成员变量需要在类外单独进行定义和初始化

类对象的实例化过程可以理解为包含以下步骤:首先,开辟整个类对象的内存空间。之后,根据类成员情况,分配各个成员变量的内存空间,并通过构造函数的初始化列表进行初始化。最后,执行构造函数中的代码。由于 const 成员变量必须在定义(分配内存空间)时,就进行初始化。所以需要在够在函数的初始化列表中初始化。const成员在初始化之后,其值就不允许改变了,即便在构造内部也是不允许的。

静态成员变量并不属于某个类对象,而是整个类共有的。静态成员变量可以不依附于某个实例化后的类对象进行访问。那么,静态成员变量的值,应该在任何实例化操作之前,就能够进行改变(否则,只有实例化至少一个对象,才能访问静态成员)。所以,静态成员变量不能够由构造函数进行内存分配,而应该在类外部单独定义,在实例化任何对象之前,就开辟好空间。又由于 const 成员变量 必须初始化,所以静态成员变量必须在定义的时候就初始化。

2,const成员函数

const成员函数指的是,此函数不应该修改任何成员变量。

1)传给const成员函数的this指针,是指向 const 对象 的 const 指针。

2)const成员函数,不能够修改任何成员变量,除非成员变量被 mutable 修饰符修饰。

 1 class constTestClass
 2 {
 3 public:
 4     int var;
 5     const int ci;
 6     mutable  int mci;
 7 public:
 8     void setVar(int i);
 9     void setMci(int i)const;
10 };
11 void constTestClass::setVar(int i)
12 {
13     var = i; // ok
14     mci = i; // ok
15     ci = i;  // Error:ci是一个const对象不能修改
16 }
17 void constTestClass::setMci(int i)const
18 {
19     var = i;    // ok
20     mci = i;    // ok  mutable成员变量可以被const成员函数修改
21     ci = i;    // Error
22 }

在成员函数调用的过程中,都有一个 this 指针被当做参数隐性地传递给成员函数(可能通过栈,也可能通过CPU寄存器)。这个this指针,指向调用这个函数的对象(这样,成员函数才能找到成员变量的地址,从而对其进行操作)。这个this指针,是个 const指针,不能修改其指向(你不希望这个对象的函数,修改了那个对象的成员变量,对吧?)。

传递给const成员函数的this指针,指向一个const对象。也就是说,在const成员函数内部,这个this指针是一个指向const对象的const指针。

mutable 修饰符使得const函数的行为有了一些灵活性。相当于提醒编译器,这个成员变量比较特殊,就不要进行任何只读检查了。

为什么 const 对象只能够调用const成员函数呢?,其实是这样的。由于对象本身通过 const 修饰,那么指向这个对象的指针也就是指向const对象的const指针了。换句话说,指向这个对象的this指针就是指向const对象的const指针。一般成员函数要求的this指针为:指向对象的const指针。所以此处发生了参数不匹配,无法进行调用。而 const 成员函数要求的this指针,恰恰是 指向const对象的const指针。所以依然能够调用。

八、const与函数

将函数的形参用const修饰是希望实参在函数内部不被修改,而一般函数接口可能会遇到以下三种情况:

1,const对象

2,指向const对象的指针

3,绑定const对象的引用

4,返回值是一个const对象

首先,我们看const对象的形参,这种接口用const修饰实际上没有任何意义,因为实参在传递给实参时是传递了一份副本,原实参是不会变化的。

 1 int main(void)
 2 {
 3     int var = 42;
 4     fun(var);
 5     cout << var << endl; // print 42
 6     return 0;
 7 }
 8 void fun( int i)
 9 {
10     i = 10;
11 }

通过上面代码可以看出,实参如果只能过值进行传递,函数接口不用const修改,也不会令实参的值改变。

而通过指针或引用传递给函数时,函数就可以通过形参来改变实参的值,这里如果需要对实参进行保护,则需要在函数接口声明形参为指向const类型的指针或引用。

 1 void fun( const int* p)
 2 {
 3     *p = 42; // error
 4     int var = 10;
 5     p = &var;  // 可以改变p本身的值
 6 }
 7 void fun(const int& p)
 8 {
 9     p = 42; // error,p是一个指向const对象的引用
10 }

有的时候,我们需要函数的返回值是一个const对象,比如我们考虑一个有理数据类,我们给类定义了一个*的重载。

1 class Rational{
2 // ....
3 };
4 const Rational operator* (const Rational& lhs, const Rational& rhs);
5 Rational a, b, c;
6 a*b = c; // Error,因为左端为一个const对象

如果上面代码中重载操作符返回对象不是const类型,则a*b=c这个式子就成立,实际上这与我们的内置类型的算术运算原则违背了,而我们希望我们设计的类的操作意义要像内置内类一样。

参考博文:

[1]:C/C++中const修饰符用法总结

[2]:C++const变量使用技巧总结

时间: 2024-10-06 00:53:34

C++的那些事:const用法面面观的相关文章

C++ Primer 学习笔记_25_类与数据抽象(11)--const 用法小结、static与const以及static const(const static)

一.const 用法总结 1.可以对const 的用法做个小总结: const int n = 100;  //定义常量 const Test t(10); const int & ref = n;   //const引用 int& ref = n;  //Error [const与指针] const int* p; //const出现在*前面,表示*p是常量 (*p = 200; //Error) int * const p2;  //const出现在*后面,表示p2是常量 (p2 =

C++中const用法总结

1. const修饰普通变量和指针const修饰变量,一般有两种写法:const TYPE value;TYPE const value;这两种写法在本质上是一样的.它的含义是:const修饰的类型为TYPE的变量value是不可变的.对于一个非指针的类型TYPE,无论怎么写,都是一个含义,即value只不可变.例如:const int nValue:         //nValue是constint const nValue:    // nValue是const但是对于指针类型的TYPE,

#define static const 用法 (转)

1.Define用法: define主要是用于宏常量定义的,使程序看起来更简洁明了,方便代码维护,#define定义的实质只是一个常数的名字,没有具体数据类型的,没有分配内存空间.在编译是会被编译器替换为该常数.每次使用该宏定义,就要进行编译并分配空间,若一个程序中多次使用define定义的数据,则就会有多份拷贝.这么做是为了提高程序的可读性,但安全性相对差点. 2.const用法: const定义的全局数据变量,其基本作用和define相同,但又在define的基础上增加了好多功能.const

【转】话说C语言const用法

原文:话说C语言const用法 const在C语言中算是一个比较新的描述符,我们称之为常量修饰符,意即其所修饰的对象为常量(immutable). 我们来分情况看语法上它该如何被使用. 1.函数体内修饰局部变量.例:void func(){const int a=0;} 首先,我们先把const这个单词忽略不看,那么a是一个int类型的局部自动变量,我们给它赋予初始值0. 然后再看const. const作为一个类型限定词,和int有相同的地位.const int a;int const a;是

const用法-read only

const用法详解 const 只读(read only) 示例一:只读的int型变量 int main(int argc,const char * argv[]){     const int a1;     int const a2;      //    a1 = 2; //    a2 = 3;     printf("%d\n",a1);     printf("%d\n",a2);    } 上面这个例子,在声明int类型的变量a1和a2时,用const

const 用法详解

const用法详解 面向对象是C++的重要特性. 但是c++在c的基础上新增加的几点优化也是很耀眼的 就const直接可以取代c中的#define 以下几点很重要,学不好后果也也很严重 const 1. 限定符声明变量只能被读 const int i=5; int j=0; ... i=j;   //非法,导致编译错误 j=i;   //合法 2. 必须初始化 const int i=5;    //合法 const int j;      //非法,导致编译错误 3. 在另一连接文件中引用co

C++中的const用法(2)

前面写过一篇博客介绍const用法:C++中的const用法 今天发现有个忙点,特此补充. 我们知道,一般const修饰指针时有三种情况. <span style="font-size:18px;">const int *p;</span> 这表示p指向一个int型的const变量,但是指针本身并不是const. int a = 0; int *const p = &a; 这种情况表示指针是const,一旦初始化不能指向另外的数据. int a = 0;

C/C++——const用法完整总结

一直以来都没有总结const修饰符的用法,总是感觉const的用法太多,要记住的情况太多,不能够掌握所有的情况,所以总是不了了之.但是如果没有彻底掌握const用法的话,心里总是感觉知识还欠缺很多,还要很多知识点没有掌握.如果自己真正的总结完之后,其实也只有这么多用法. const修饰数据成员,成员函数,类对象. const修饰数据成员 修饰数据变量,保证数据变量只能在定义的时候进行初始化,并且在程序的执行过程中不能被再次赋值. 1.修饰类中的数据成员变量,可以直接在声明的时候进行初始化(con

const用法归纳总结 C++

非常好的一篇分析const的总结归纳, 在此谢谢原作者:http://blog.csdn.net/zcf1002797280/article/details/7816977 在普通的非 const成员函数中,this的类型是一个指向类类型的 const指针.可以改变this所指向的值,但不能改变 this所保存的地址.在 const成员函数中,this的类型是一个指向 const类类型对象的 const指针.既不能改变 this所指向的对象,也不能改变 this所保存的地址. 关键字:Const