`
michael-java
  • 浏览: 18188 次
  • 性别: Icon_minigender_1
  • 来自: 合肥
最近访客 更多访客>>
社区版块
存档分类
最新评论

第九章 指针与const

阅读更多

 

const一词是英文constant的缩写,设立这个关键字的本意,是希望让它所修饰的对象成为一个常量。记得在国家间的外交中,有一个经常用到的术语:“从事与身份不符的活动”,这个const恰恰也正从事着这样的活动,呵呵。C语言可以有三种方法定义一个常量:#define、const和枚举,但只有枚举才是真正的常量,什么是真正的常量?真正的常量是没有存储空间的,是一个右值,这意味着通过任何合法的手段也不会被修改,但被const修饰的对象依然是一个左值,尽管这个对象被const限定,笔者仍然至少可以找到三种合法的手段去修改它,而#define所做的只不过是编译期替换而已,只有枚举常量才能真正做到这一点。const实在不应该被命名为const,这会让人们产生误解,它应该命名为readonly或类似的字眼,意即不能通过被const修饰的对象修改它所指向的对象或者它所代表的对象。但在C的世界里把const称为常量早已是普遍的现象,那我们就只好随大流咯,也称之为常量吧,只要知道它实际上不是真正的常量就行了。

        第七章曾经讨论过const int *p;与int * const p的区别,这两个声明的中文名称常常搞得混乱不堪。第一个声明的const是声明说明符,它修饰p所指向的对象,但p仍然是可变的,这意味着p是一个指向常量的指针,简称常量指针。第二个声明的const是声明符的一部分,它修饰的对象是p,这意味着p是一个常量,而且是一个指针类型的常量,简称指针常量。指针常量又常常被人称为“常指针”或“常指针变量”,常指针变量这个名称有点蹩脚,又常又变的,容易让人摸不着头脑,最好还是不要这样称呼。这里还得再强调一次指针常量与地址常量是不同的,不能把数组名称为指针常量,也不能把指针常量称为地址常量,因为指针常量依然是一个左值,而数组名是一个右值,这里肯定有人会问:“什么?指针常量是一个左值?我没听错吧?”你的确没有听错,C89对于左值是这样定义的:

对象是一个命名的存储区域,左值(lvalue)是引用某个对象的表达式。

换言之,如果一个表达式引用的是一个具有具体存储空间的对象,它就是一个左值!那么既然指针常量是一个左值,为什么却不能给它赋值呢?是因为它受限于赋值表达式的一条规则:赋值表达式的左值不能含有限定词!

        为了防止指针指向的常量被修改,C标准对于指针间赋值有一个规定,就是左值必须包含右值的所有限定词。 这就限定了一个指向const对象的指针不能赋值给指向非const对象的指针,但反过来就允许。这个规定初看上去非常合理,但其效用其实只限于一级指针,二级指针间的赋值即使满足规定也不再安全,下面举个例子:

const int i=10;
const int **p1;
int *p2;
p1 = &p2;
*p1 = &i;
*p2 = 20;

现在你会发现,作为常量的i的值被修改了。i的值被修改的关键原因在*p1=&i;这一句,&i是一个指向常量的一级地址,如果没有二级指针p1,受限于上述规定,作为左值接受这个一级地址的指针就必须也是一个指向常量的一级指针,于是就不能进行下一步赋值20的操作。因此,正由于指向const对象的二级指针p1的出现,使得*p1也是一个指向const的指针,于是*p1=&i能够合法地运行,常量i的值被修改也就成了一个预想中的结果了。有鉴于此,某些编译器也会限定非const二级指针之间的赋值,规定上面的p1=&p2也是非法的。

        第七章介绍声明符的指针部分有一种形式:

* 类型限定符表opt 指针

这种形式产生了一种比较复杂的带const的指针,例如:

const int * const *** const ** const p;

这是一个会让人头晕目眩的表达式,声明符部分嵌套了九次,如何辨认谁是const,谁不是const呢?一旦明白了其中的原则,其实是非常简单的。第一和最后一个const大家都已经很熟悉的了。对于藏在一堆*号中的const,有一个非常简单的原则:const与左边最后一个声明说明符之间有多少个*号,那么就是多少级指针是const的。例如从右数起第二个const,它与int之间有4个*号,那么p的四级部分就是const的,下面的赋值表达式是非法的:

**p = (int *const***)10;
但下面的赋值是允许的:
***p=(int*const**)10;
从左边数起第二个const,它与int之间有1个*,那么p的一级部分是const的,也就是*****p = (int*const***const*)10;是非法的。

分享到:
评论

相关推荐

    C++Primer视频(初级)下载地址

    第4章指针和const限定符 33.第4章C风格字符串 34.第4章创建动态数组 35.第4章新旧代码的兼容 36.第4章多维数 37.第5章算术操作符 38.第5章关系操作符和逻辑操作符 39.第5章位操作符 40.第5章赋值...

    程序员面试宝典-第三版(高清带目录)

     第6章 预处理、const与sizeof  6.1 宏定义  6.2 const  6.3 sizeof  6.4 内联函数和宏定义  第7章 指针与引用  7.1 指针基本问题  7.2 传递动态内存  7.3 函数指针  7.4 指针数组和数组指针  7.5 迷途...

    清华大学计算机课程之《C++程序设计》

    ◇ 第九章 编译预处理 - 课前索引 - 第一节 宏定义 - 第二节 文件包含 - 第三节 条件编译 - 本章小结 - 课后习题 ◇ 第十章 类与对象 - 课前索引 - 第一节 类与对象概述 - 第二节 构造函数与析构函数 - 第三...

    Linux C程序设计大全

    第9章 gdb 第3篇 Linux进程操作 第10章 进程环境 第11章 进程控制 第12章 时间和日历历程 第13章 信号及信号处理 第14章 进程间通信 第15章 线程 第4篇 Linux文件操作 第17章 文件I/O 第18章 文件管理 第19章 目录...

    中国大学MOOC西工大C++课程PPT

    第9讲 函数的定义和使用 第10讲 函数的设计 第11讲 函数的调用 第12讲 作用域、生命期和程序的组织结构 第13讲 数组的定义和使用 第14讲 数组与函数 第15讲 字符串的处理 第16讲 数组的应用 第17讲 指针的定义与使用...

    高质量C C编程指南

    第9章 类的构造函数、析构函数与赋值函数 9.1 构造函数与析构函数的起源 9.2 构造函数的初始化表 9.3 构造和析构的次序 9.4 示例:类STRING的构造函数与析构函数 9.5 不要轻视拷贝构造函数与赋值函数 9.6 示例:类...

    高质量C++_C编程指南

    第9 章 类的构造函数、析构函数与赋值函数 9.1 构造函数与析构函数的起源 9.2 构造函数的初始化表 9.3 构造和析构的次序 9.4 示例:类STRING 的构造函数与析构函数 9.5 不要轻视拷贝构造函数与赋值函数 9.6 示例:类...

    新手必看编程法则C++

    第9章类的构造函数、析构函数与赋值函数 9.1 构造函数与析构函数的起源 9.2 构造函数的初始化表 9.3 构造和析构的次序 9.4 示例:类String的构造函数与析构函数 9.5 不要轻视拷贝构造函数与赋值函数 9.6 示例:类...

    高质量编程C++、C

    第9章 类的构造函数、析构函数与赋值函数 9.1 构造函数与析构函数的起源 9.2 构造函数的初始化表 9.3 构造和析构的次序 9.4 示例:类String的构造函数与析构函数 9.5 不要轻视拷贝构造函数与赋值函数 9.6 示例...

    高质量C++编程指南.PDF

    第9章 类的构造函数、析构函数与赋值函数 9.1 构造函数与析构函数的起源 9.2 构造函数的初始化表 9.3 构造和析构的次序 9.4 示例:类String的构造函数与析构函数 9.5 不要轻视拷贝构造函数与赋值函数 9.6 示例:类...

    高质量C、C++编程指南

    第9 章 类的构造函数、析构函数与赋值函数 9.1 构造函数与析构函数的起源 9.2 构造函数的初始化表 9.3 构造和析构的次序 9.4 示例:类STRING 的构造函数与析构函数 9.5 不要轻视拷贝构造函数与赋值函数 9.6 示例:类...

    高质量C++、C编程指南.doc )

    第9章 类的构造函数、析构函数与赋值函数 69 9.1 构造函数与析构函数的起源 69 9.2 构造函数的初始化表 70 9.3 构造和析构的次序 72 9.4 示例:类STRING的构造函数与析构函数 72 9.5 不要轻视拷贝构造函数与赋值函数...

    C编程思想--chinapub书籍--文本PDF

    第9章 命名控制 157 9.1 来自C语言中的静态成员 157 9.1.1 函数内部的静态变量 157 9.1.2 控制连接 160 9.1.3 其他的存储类型指定符 161 9.2 名字空间 161 9.2.1 产生一个名字空间 162 9.2.2 使用名字空间 163 9.3 ...

    C++高级参考手册(共7章)

    第9章 命名控制 157 9.1 来自C语言中的静态成员 157 9.1.1 函数内部的静态变量 157 9.1.2 控制连接 160 9.1.3 其他的存储类型指定符 161 9.2 名字空间 161 9.2.1 产生一个名字空间 162 9.2.2 使用名字空间 163 9.3 ...

    高质量C++C编程指南(非扫描高清版)(林锐博士)

    第9 章 类的构造函数、析构函数与赋值函数 9.1 构造函数与析构函数的起源. 9.2 构造函数的初始化表. 9.3 构造和析构的次序. 9.4 示例:类STRING 的构造函数与析构函数 9.5 不要轻视拷贝构造函数与赋值函数. ...

    高质量C++/C编程指南

    第9章 类的构造函数、析构函数与赋值函数... 69 9.1 构造函数与析构函数的起源... 69 9.2 构造函数的初始化表... 70 9.3 构造和析构的次序... 72 9.4 示例:类String的构造函数与析构函数... 72 9.5 不要轻视拷贝...

    高级c语言程序编程思想

    第9章 类的构造函数、析构函数与赋值函数 69 9.1 构造函数与析构函数的起源 69 9.2 构造函数的初始化表 70 9.3 构造和析构的次序 72 9.4 示例:类String的构造函数与析构函数 72 9.5 不要轻视拷贝构造函数与赋值函数...

    高质量C/C++编程指南(PDF)

    第9 章 类的构造函数、析构函数与赋值函数 9.1 构造函数与析构函数的起源. 9.2 构造函数的初始化表. 9.3 构造和析构的次序. 9.4 示例:类STRING 的构造函数与析构函数 9.5 不要轻视拷贝构造函数与赋值函数. 9.6 示例...

    C++高级参考手册 完全自学 内容详细 讲解通俗易懂

    第9章 命名控制 9.1 来自C语言中的静态成员 9.1.1 函数内部的静态变量 9.1.2 控制连接 9.1.3 其他的存储类型指定符 9.2 名字空间 9.2.1 产生一个名字空间 9.2.2 使用名字空间 9.3 C++中的静态成员 9.3.1 ...

Global site tag (gtag.js) - Google Analytics