C++高级进阶 第一季:const 详解

零、文章来由

打算将基础知识在看书的同时系统的整理一下,方便大家也方便自己。整理的知识尽量参照书本知识,比网上获取的资料有更高的可信度。

一、从 文字常量和常变量 开始

补充:const并没有想象中的那么简单,详见新博文《C++底层知识 第三季:const详解(二)》

1、文字常量

程序中的特殊标识符表达式,由于同时满足:
(1)不可寻址(放在代码区)
(2)值不可变
所以可视为文字常量。他们是 静态数组名、枚举变量、全局(静态变量)首地址、#define定义的常量

整型文字常量:
(1)前加0表示 八进制
(2)前加0x表示 十六进制
(3)后加L(推荐)或l,表示long类型
(4)后加U(或u)表示无符号数

eg.1024UL

2、常变量Const

同其他变量一样被分配空间,可寻址。

const是在高级语言的语义层面上定义的,是编译器在编译期做语法检测来保证,但是运行时,const变量不是在只读内存中,而是和一般变量一样放在数据区,所以一样可以对其进行修改。

所以:常变量是一种加了特殊限制的变量,理解成“只读”变量

即使是const修饰,也是可以修改的

#include <iostream>
using namespace std;

void ShowValue(const int &i) {
    cout<<i<<endl;
}

int main()
{
    const int j=5;
    void *p=(void *)&j;
    int *ptr=(int *)p;
    (*ptr)++;
    //cout<<j<<endl; //还是会显示5,因为编译器优化的时候将j替换为文字常量5
    //但如果是int i=5; const int j=i; 则无法替换,直接输出j为6
    ShowValue(j); //显示6

    return 0;
}

或:

#include <iostream>
using namespace std;

void ShowValue(const int &i) {
    cout<<i<<endl;
}

int main(int argc,char *argv[])
{
    int tmp = 5;
    const int & aa =tmp;
    ShowValue(++(*((int *)&aa))); //显示6

    return 0;
}

3、常变量替换

如果常变量有初始化赋初值,那编译器将该常变量在其他地方替换成文字常量
但是如果开始不初始化就会错误

如:

void DefineArray(const int n){
     int B[n]={}; //error,数组大小在编译期确定
}

int main(){
     const int m=5;
     int A[m]={}; //ok
}

4、文字常量和常变量寻址

int &r=5; //error,无法寻址文字常量,无法建立引用

const int &r=5; //ok,在数据区开辟一个值为5的无名整数量,然后将引用r与这个整形两绑定

二、const用法

1、const的位置

int const *p; //指向常量的指针(即常指针,const修饰的是int),指向的对象是const型,不可以修改,但是指针p的指向可以修改
int *const p; //指针常量(const修饰的是int*),指针变量p是const型,它的指向不可修改,但是指向的对象可以修改

const和数据类型结合在一起 —>“常类型”。(看成一个整体)

修饰类型时,既可以放在放前面,也可以放在后面;用常类型声明 or 定义变量,const只出现在变量前

const和被修饰类型间不能有其他标识符存在。

引用本身可以理解成一个指针常量

故在引用前使用const没有意义

int & const r4=i; //const是多余的,编译器warning后忽略const存在

const配合二重指针,此例子中const在不同位置,结果不同

#include <iostream>
using namespace std;

int main()
{
    int const **p1; //不是指针常量,指向 int count*(“int const*”是一个 指向整型常量的指针)
    int *const *p2; //不是指针常量,但所指的变量是指针常量(int *const,即指向整型的指针常量,指向不能修改)

    int i=5;
    int j=6;

    const int *ptr1=&i;
    int *const ptr2=&j;

    p1=&ptr1;
    p2=&ptr2;

    cout<<**p1<<endl;
    cout<<**p2<<endl;

    return 0;
}

输出:
5
6

上述p1和p2 赋值有讲究,如果 p1=&ptr2 或 p2=ptr1 就会编译错误

2、const修饰某个类 —> 常对象 和 常函数

const修饰对象–>常对象
const修饰成员函数—>常函数

在常函数中,不允许对任何成员变量进行修改

通过常对象,只能调用该对象的常函数

#include <iostream>
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() const {
    cout<<num<<endl;
}

void A::disp() {
    cout<<"non-const version of disp()"<<endl;
}

int main()
{
    A a1;
    a1.set(3);
    a1.disp();
    A const a2;
    a2.disp();
}

以上注意:
(1)如果常函数声明和定义分开,都需要加const,否则编译错误

只有类的非静态成员函数可以被声明为常函数

(2)如果一个类的两个成员函数,返回值、函数名、参数列表完全相同,其中之一是const,则重载。因为 常成员函数的参数传入this指针是const Class*类型的,参数不同,导致函数签名不同。

非只读对象(如a1)调用某个函数(如 disp()),先找非const版本,如果没有,再调用const版本。而常对象,只能调用类中定义的常函数,否则编译器报错。

如果一个非const对象(如a1)调用函数,同时有const和非const版本的函数,我们希望其调用const函数。就必须建立该对象的常引用,或指向该对象的常指针来达到目的。如: (const A&)a1.disp(); 或 (const A *)&a1->disp();

(3)常对象创建后,其数据成员不允许在修改。所以显示构造函数来初始化该对象非常重要。

常对象,全体成员数据成员都是常量看待。
类对象的非静态常量成员必须在构造函数中初始化,且只能借助初始化列表进行。

3、const修饰函数参数+函数返回值

#include <iostream>
using namespace std;

void disp1(const int &ri){
    cout<<ri<<endl;
}

void disp2(const int i){
    cout<<i<<endl;
}

const int disp3(const int& ri){
    cout<<ri<<endl;
    return ri;
}

int& disp4(int& ri){
    cout<<ri<<endl;
    return ri;
}

const int& disp5(int& ri){
    cout<<ri<<endl;
    return ri;
}

int main(int argc,char* argv[])
{
    int n=5;
    disp1(n);
    disp2(n);
    disp3(n);
    disp4(n)=6; //修改引用返回值
    disp5(n);//disp5(n)=6;是错误的
}

注意:
(1)const修饰参数,主要作用是被引用对象被指向对象,如果只是形参,就没有多少意义。如:void disp2(const int i),这里的i在函数中改不改变,加不加const没什么影响。

不但如此,同时定义一个相似的用const修饰参数和不用const修饰参数的函数,会引起重定义错误。比如:任何整型表达式的值,都可以传给int型参变量,也可以传给const int型参变量,故不重载。

(2)当返回值是一个普通数据,而非引用,const修饰也没多少意义。因为函数返回值是一个非左值,本来就不能改变其值。故其上 const int disp3(const int& ri),对返回值修饰然并卵。

(3)如果返回值为引用,用const修饰可以阻止对被引用对象修改,disp5(n)=6;是错误的

(4)常见的对const的误解。

误解一:用const修改的变量值一定是不能改变的。const修饰的变量可通过指针可间接修改。

如:

const int j=5;
void *p=(void *)&j;
int *ptr=(int *)p;
(*ptr)++;

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

三、const_cast 的用法

1、作用

const_cast 是 C++ 运算符,作用是去除符合类型中的const或者volatile

当大量使用const_cast是不明智的,只能说程序存在设计缺陷。使用方法见下例:

void constTest(){
    int i;
    cout<<"please input a integer:";
    cin>>i;
    const int a=i;
    int& r=const_cast<int&>(a);//若写成int& r=a;则发生编译错误
    ++r;
    cout<<a<<endl;
}
int main(int argc,char* argv[])
{
    constTest();
    return 0;
}

输入:
5

输出:
6

总结:
(1)const_cast运算符的语法形式是const_cast< type> (expression)。 括号不可省略

(2)const_cast只能去除目标的const或者volatile属性,不能进行不同类型的转换。只能将 const type* 转换为 type*,或者 const type & 转换为 type &。
如下转换就是错误的:

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

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

int j = const_cast< int> (i);

(4)利用传统的C语言中的强制类型转换也可以将 const type* 类型转换为 type* 类型,或者将 const type& 转换为 type& 类型。但是使用 const_cast 会更好一些,因为 const_cast 写法复杂(提醒程序猿不要轻易转换),转换能力较弱,目的明确,不易出错,易查bug;而C风格的强制类型转换能力太强,风险较大。


参考资料

[1]陈刚.C++高级进阶教程[M].武汉:武汉大学出版社,2008.

时间: 2024-10-03 15:36:50

C++高级进阶 第一季:const 详解的相关文章

C++高级进阶 第四季:const详解(二) 常量折叠

一.文章来由 const详解之二 二.const 代替 #define const最初动机就是代替 #define. const 优于 #define: (1) #define没有类型检查,const在编译期(而不是预编译期)做类型检查: (2)const方便调试和定位bug. 所以应该完全用const代替#define 三.头文件中的const (1)要使用const代替#define,同样需要把const定义放进头文件(或其他格式文件,include即可).这样通过包含头文件,可把const

jquery高级编程的最佳实践详解

 这篇文章主要介绍了jquery高级编程的最佳实践详解,学习JQ的朋友一定需要这个,参考下吧 加载jQuery   1.坚持使用CDN来加载jQuery,这种别人服务器免费帮你托管文件的便宜干嘛不占呢.点击查看使用CDN的好处,点此查看一些主流的jQuery CDN地址.    代码如下: <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.mi

C++高级进阶 第二季:mutable 关键字

零.文章来由 打算将基础知识在看书的同时系统的整理一下,方便大家也方便自己.整理的知识尽量参照书本知识,比网上获取的资料有更高的可信度. 一.作用 mutable 用来解决常函数中不能修改对象的数据成员的问题. 如果在一些情况下,希望在常函数中仍然可以修改某个成员变量的值,就在该变量前加上mutable.能在保证常量对象大部分数据成员仍然"只读"情况下,实现对个别成员的修改. #include <iostream> #include <string> using

百度网站改版工具高级规则URL替换规则详解

网站随着规模.业务.技术的变化进行改版是在所难免的,但网站改版也有一定的风险,特别是网站链接结构的改变容易遭到搜索引擎的惩罚,轻则降权重则被K,为了最大限度的降低风险,站长们一般通过301永久重定向技术进行新旧链接的替换,不同的搜索引擎对301的反应速度是不一样的,GOOGLE作为目前世界上最成熟的搜索引擎,反应较灵敏,而百度在这方面反应较迟缓(有点老年滞呆的感),但日前百度终于推出了301改版工具,关于工具的使用,请参见百度站长平台资料, 网站改版的注意事项?http://zhanzhang.

高级MPLS/VPN的拓扑结构详解

1,不同VRF之间的互联:(内部网与外部网络的集成) 关于这个话题终于在书上找到了正解了. 一直有一个疑问缠绕着我和一些朋友,如果两个VRF(公司)之间有相互访问的业务需要ISP进行开通.那么如果两个VRF内网网段IP地址段是重复的,这个时候有什么解决方案. 在<MPLS VPN体系架构>第十二章P206下面有一个注意: 对于这种链接方式,两个不同的VPN客户中的IP地址空间应该具有唯一性,如果地址重复,则这种连接性将被破坏,因为站点将从不同的地方受到一组相同的路由. 也就是说,不通. 下面就

Java进阶学习:jar打包详解_Java编程

文章来源:pconline 作者:fivesky jar文件听说过吗,没有?或者陌生!好,没关系,这就是我们的第一站:打包发布. 为什么会有这个玩意呢,首先,这是jar的全称:JavaTM Archive (JAR) file,是的,就是java存档文件.这有点类似zip文件,想一想它是干什么的用的呢,压缩!?没错就是要压缩,将我们原先零散的东西放到一下,重新组织,所有这些目的只有一个:方便!好了,不用管他是怎么压缩的,我们的重点是哪些是我们要压缩的(输入),还有压缩成了什么(输出),进而将它发

Python实现Const详解_python

python语言本身没有提供const,但实际开发中经常会遇到需要使用const的情形,由于语言本身没有这种支出,因此需要使用一些技巧来实现这一功能 定义const类如下 复制代码 代码如下: import sys class Const(object):     class ConstError(TypeException): pass     def __setattr__(self, key, value):         if self.__dict__.has_key(key):  

Win8高级安全Windows防火墙配置详解

Win8高级安全windows防火墙 此图为高级安全windows防火墙打开后界面,与win7一样. Windows 8针对每一个程序为用户提供了三种实用的网络连接方式: - 允许连接:程序或端口在任何的情况下都可以被连接到网络. - 只允许安全连接:程序或端口只有IPSec保护的情况下才允许连接到网络. - 阻止连接:阻止此程序或端口的任何状态下连接到网络. 三种方式一眼便知,只要你设定了某程序为"阻止连接"状态,该程序自然也就从此无法连入网络,想要自动更新或是联网工作那是不可能的事

php smarty高级缓存的配置方法详解

对于访问流量比较大.数据不需要及时更新的网站而言,充分使用缓存技术可以有效的减轻服务器压力.smarty模板引擎中也内置了功能强大的缓存技术,只需要简单配置即可使用smarty的缓存功能.开启smarty高级缓存只需要在smarty的配置文件中设置好缓存存放的目录.开启缓存状态.设定缓存的有效期即可,具体使用方法如下:  代码如下 复制代码 /***********smarty.php***********/ <?php  include ('./smarty/Smarty.class.php'