一、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。

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

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

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

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

四、const与引用

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

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

 const int ci = ;
const int &r1 = ci;
r1 = ; // Error:r1是对常量的引用
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对象的指针。

 int var;
const int ci = ; int *p1 =& var;
int *p2 = &ci; // Error,const int* 不能用于初始化int*
const int *p3 = &var; //ok
const int *p4 = &ci; // ok

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

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

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

 const int var = ;
int* p = (int*)&var;
*p = ;
cout << var << endl; //
cout << *p << endl; //

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

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

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

六、const与类

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

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

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

 class AClass{
public:
int m_var;
mutable int m_mutable_var;
void setVar(int var){ m_var = var; }
void printVar(){ cout << m_var; }
void printVar_const()const { cout << m_var; }
}; const AClass ac;
ac.m_var = ; // Error:ac是一个const类,不能修改成员变量
ac.m_mutable_var = ; // ok 可以修改mutable修饰的变量
ac.setVar(); // Error: ac不能调用非const成员函数,而且这个成员函数还修改了成员变量的值
ac.printVar();// Error:ac不能调用非const成员函数
ac.printVar_const(); // ok

七、const与类的成员

1,const成员变量

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

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

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

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

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

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

2,const成员函数

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

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

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

 class constTestClass
{
public:
int var;
const int ci;
mutable int mci;
public:
void setVar(int i);
void setMci(int i)const;
};
void constTestClass::setVar(int i)
{
var = i; // ok
mci = i; // ok
ci = i; // Error:ci是一个const对象不能修改
}
void constTestClass::setMci(int i)const
{
var = i; // ok
mci = i; // ok mutable成员变量可以被const成员函数修改
ci = i; // Error
}

在成员函数调用的过程中,都有一个 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修饰实际上没有任何意义,因为实参在传递给实参时是传递了一份副本,原实参是不会变化的。

 int main(void)
{
int var = ;
fun(var);
cout << var << endl; // print 42
return ;
}
void fun( int i)
{
i = ;
}

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

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

 void fun( const int* p)
{
*p = ; // error
int var = ;
p = &var; // 可以改变p本身的值
}
void fun(const int& p)
{
p = ; // error,p是一个指向const对象的引用
}

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

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

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

参考博文:

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

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

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

  1. c++ const用法小结

    const用法 1,定义全局变量的内存分配问题 #define  Pi_1  3.14       //使用#define宏 const double Pi_2 = 3.14    //使用const ...

  2. const用法

    一.const作用 二.const用法 1.修饰一般常量   修饰符const可以用在类型说明符前,也可以用在类型说明符后. 例如: ; ; 2.修饰常数组  修饰符const可以用在类型说明符前,也 ...

  3. 【转】话说C语言const用法

    原文:话说C语言const用法 const在C语言中算是一个比较新的描述符,我们称之为常量修饰符,意即其所修饰的对象为常量(immutable). 我们来分情况看语法上它该如何被使用. 1.函数体内修 ...

  4. static 与单例模式、auto_ptr与单例模式、const 用法小结、mutable修饰符

    一.static 与单例模式 单例模式也就是简单的一种设计模式,它需要: 保证一个类只有一个实例,并提供一个全局访问点 禁止拷贝  C++ Code  1 2 3 4 5 6 7 8 9 10 11 ...

  5. const用法详解(转)

    http://www.cnblogs.com/StudyRush/archive/2010/10/06/1844690.html 面向对象是C++的重要特性. 但是c++在c的基础上新增加的几点优化也 ...

  6. C++之常指针,指针常量,函数指针,const用法总结

    1.const char *p,char const *p,char * const p 对于C++而言,没有const * 修饰符,所以,const只可以修饰类型或者变量名.因而const char ...

  7. 十一、微信小程序-var、let、const用法详解

    let命令 基本用法 ES6 新增了let命令,用来声明变量.它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效. { let a = 10; var b = 1; } a // ...

  8. 【三支火把】---C语言const用法总结

    C语言关键字const相信对于不少C语言新手是既陌生又熟悉的,好像经常见,但是却不知道为何用,怎么用?学习至此,总结一下const的用法,使用程序来帮助你理解该关键字,希望能帮到像我一样的新手. 我看 ...

  9. typedef,static,const用法

    一.typedef主要功能是定义一个已存在类型的别名,但是和宏并存 宏与typedef区别 1.宏定义只是简单的字符串替换 2.typedef定义的类型是类型的别名,typedef后面是一个整体声明, ...

随机推荐

  1. IIS负载均衡-Application Request Route详解第一篇: ARR介绍(转载)

    IIS负载均衡-Application Request Route详解第一篇: ARR介绍 说到负载均衡,相信大家已经不再陌生了,本系列主要介绍在IIS中可以采用的负载均衡的软件:微软的Applica ...

  2. LINQ to SQL更新数据库操作(转载)

    使用LINQ to SQL建模Northwind数据库 在这之前一起学过LINQ to SQL设计器的使用,下面就使用如下的数据模型: 当使用LINQ to SQL设计器设计以上定义的五个类(Prod ...

  3. EL表达式从request和session中取值

    在Action中保存登录的基本信息:request.getSession().setAttribute("adminid", str); 在JSP页面中:${sessionScop ...

  4. IMAP(Internet Mail Access Protocol,Internet邮件访问协议)以前称作交互邮件访问协议(Interactive Mail Access Protocol)。

    IMAP(Internet Mail Access Protocol,Internet邮件访问协议)以前称作交互邮件访问协议(Interactive Mail Access Protocol).IMA ...

  5. ASP.NET MVC 随想录——开始使用ASP.NET Identity,初级篇(转)

    ASP.NET MVC 随想录——开始使用ASP.NET Identity,初级篇   阅读目录 ASP.NET Identity 前世今生 建立 ASP.NET Identity 使用ASP.NET ...

  6. PHP中array_chunk的用法

    转自:http://cn2.php.net/manual/zh/function.array-chunk.php (PHP 4 >= 4.2.0, PHP 5) array_chunk — 将一 ...

  7. 一个Java对象到底占用多大内存?

    最近在读<深入理解Java虚拟机>,对Java对象的内存布局有了进一步的认识,于是脑子里自然而然就有一个很普通的问题,就是一个Java对象到底占用多大内存? 在网上搜到了一篇博客讲的非常好 ...

  8. 什么是A记录?MX记录?CNAME记录?它们都有些什么用途?

    什么是A记录?什么是MX记录?CNAME记录又是什么?它们都有些什么用途? 好,下面就用我浅陋经验给大家介绍一下: 1. A记录:WEB服务器的IP指向 A (Address) 记录是用来指定主机名( ...

  9. MySQL数据库InnoDB引擎下服务器断电数据恢复

    说明: 线上的一台MySQL数据库服务器突然断电,造成系统故障无法启动,重新安装系统后,找到之前的MySQL数据库文件夹. 问题: 通过复制文件的方式对之前的MySQL数据库进行恢复,发现在程序调用时 ...

  10. UIWebView

    本地html string文件 loadHTMLString: 本地/远程文件 loadRequest