[讨论]《说C·变量》专题,为说明指针,不得不从头来……

BluntBlade 2005-10-12 10:34:55
痛苦,为什么新手们没兴趣研究语法背后的东西呢……

1 引言
编程的目的是计算,而计算要处理的对象是数据。在要使用计算机进行计算工作,首要工作是解决数据的表示、存储和访问问题。所幸二进制数制体系和基于半导体的硬件平台较好地解决了数据的表示和存储问题,我们只要在逻辑上解决数据的访问问题就可以了(访问问题包括读操作和写操作)。
那么在逻辑上,我们如何访问数据呢?程序运行时,相关数据存储于内存之中(本篇假定数据只存储于内存中,其它情况类同),是一种物理存在。想要从逻辑上访问数据,必须设定一种机制将数据的逻辑表示和其物理存在联系起来。从程序设计语言(以下简称语言)的角度来说,就是要使用某种符号来表示数据,这些符号由语言的实现(即编译器、链接器及相关的开发环境等)进行处理以表现其与数据的物理存在的联系。这种符号表示数据的方法与数学中变量的概念非常类似,因此在大多数语言中,均引入“变量”这一概念作为数据的逻辑表示形式。访问变量,即是访问变量中存储的数据。
让我们再做一个小小的概念变换,即“变量”就是一种“对象”。“对象是一块可以检查和存放的内存区”,对象的大小决定了其存储数据的值域范围,而对象的类型决定了定义在它上面的操作集合。通过语言提供的声明符和定义语句,可以很方便地在程序中开辟出对象。因此研究变量,就是研究变量的声明、定义、大小、类型等等……好吧,好吧,下面先讲讲变量的语法。
...全文
1652 69 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
69 条回复
切换为时间正序
请发表友善的回复…
发表回复
yongjie_2008_java 2010-05-27
  • 打赏
  • 举报
回复
关于指针还是有点迷糊,所谓的指针和指针变量究竟是怎么回事啊?
hot225dog 2006-01-05
  • 打赏
  • 举报
回复
不停的捞帖。。。

痛苦,为什么新手们没兴趣研究语法背后的东西呢……
----------------------------------------------------
我是新手,楼主是老师?要是我的老师就太好了。有一段时间了,苦于找不到学习材料,目前最想了解和掌握语法实现、结合对应的汇编的C。

语法学了2年不到,我看过的差不多的书中涉及到语法实现都不是深入探讨,于是在茫茫书海和帖海中试图想寻些门道,虽然有些收获,但更重要的是浪费了很多鉴别的时间,经常看帖与系统的学习不合.
而且没有标准的材料也总会主观的去猜想是不是如此云云。。

建议社区成立项目,为了共同的爱好,让高手们系统的讲述市面上很缺的但很必须很实在的东西...

关于楼上几位高手的探讨,我现在根本不能从技术上加以分析,听过一句话:"你会的东西不一定能表达出来,能表达出来的一定是会了",所以说除了技术上的分析表达,其他的表述应该考虑到会的人有以上两种可能.

还有关于标准,很好的理解标准,作为标准的用户,可以更好的运用这个语言.在平时学习操作系统设计或是计算机硬件设计的过程中强烈的感觉到作为现在正在使用的各种标准而言,它是各种方案可能是完全相反的两种实现原理博弈而生的,标准的目的会考虑通用、性能等。所以说现在的标准不是最好的(主观的判断),至少不是每个细节都是最好的,所以希望高手们在探讨的时候多多涉及一些标准之后的,我这个新手也好多多学习,即便看不懂也会顶上的,不为其他的,就因为喜欢他。。。
我也一直认为csdn是国内最好的同类社区之一(貌似语法级问题比较多)。

以上是一个学生一些不成熟的看法,切望点播指正,虽然不能当面沏茶拜师,但一定会心存感激的。。。
kobefly 2005-11-29
  • 打赏
  • 举报
回复
学习
megaboy 2005-11-29
  • 打赏
  • 举报
回复
还得作一个小小的更正:the referenced type指的是"a function type,an object type, an incomplete type。因此,那段话第一句话应该这样说:


指针的类型可以派生于函数的类型、对象的类型或者不完整类型,它们被称为引用类型。

ilovedudu 2005-11-29
  • 打赏
  • 举报
回复
顶!
希望争论之后达成共识!期待中!
whyglinux 2005-11-28
  • 打赏
  • 举报
回复
趁热打铁,再分析一下 K&R 说的这句话:

A pointer is a variable that contains the address of a variable.

这里的 pointer 显然应该是指针变量(pointer variable)、而不是指针常量(pointer constant)或其它。只有这样解释,这句话才有成立的可能。但是如果直接说“A pointer variable is a variable that ...”有重复之嫌,所以也可能是只说“A pointer is a variable that ...”的一个原因。

另外,即使是把其中的 pointer 解释为 pointer variable,从这句话的后半部分来看也描述得很不全面的。一个指针中存放的只是“the address of a variable”(变量的地址)吗?显然不是这样的。

我在前面说过,在程序中表示对象的方法有两种:通过变量的方法以及通过引用的方法。变量表示对象是通过定义变量来实现的,由于有一个变量名与此对象相关,这个对象是一个有名对象。另外一种创建对象的方法是通过动态内存分配,这样创建的对象是没有名字的,因此不能通过变量直接访问,只能通过引用进行间接访问。然而,这样的对象当然也是有地址的,也可以放到一个指针(变量)中去。

因此,一个指针不仅可以存放变量的地址,也可以存放一个非变量表示的对象的地址。所以,单从 K&R 的这句话来看对术语的界定模糊、对问题的描述不全面,不可以作为规则来使用。

bestlife 2005-11-28
  • 打赏
  • 举报
回复
搬个凳子来看
whyglinux 2005-11-28
  • 打赏
  • 举报
回复
>> 我晕,int *const constant_ptr;不是变量?你怎么学指针的?constant_ptr仍然是一个左值!!!

你说的没错,变量指针和常量指针都是说的指针是变量的情况。发完之后再一看,对我的论点没有支持力。不过,我不是还说了“pointer constant”(指针常量)的情况吗?那又怎么解释呢?

从你引用的标准原文来看,你已经承认了指针是一种类型,对吗?

另外,我一再强调引用文章的时候还要联系注意上下文。先把上面的标准原文引用完整:

A pointer type may be derived from a function type, an object type, or an incomplete
type, called the referenced type. A pointer type describes an object whose value
provides a reference to an entity of the referenced type. A pointer type derived from
the referenced type T is sometimes called ‘‘pointer to T’’. The construction of a
pointer type from a referenced type is called ‘‘pointer type derivation’’.

首先你要明白,标准中的 object 和 function 是区别开来的:函数不是对象,对象也不是函数,Ok ?

从第一句话中可以看出,一个指针类型可以用来表示一个函数的地址、一个对象的地址以及一个非完整类型的地址。Ok ?

而第二句话只是对上述三种情况之一的“对象”进行了描述。那么这句话是说指针类型只(only)描述对象,而不能用来描述函数以及非完整类型吗?答案不是很清楚吗?
fixopen 2005-11-28
  • 打赏
  • 举报
回复
呵呵,拍别人的砖总是很爽的。

但我不想拍砖,我觉得楼主的意思已经表达的很清楚了,意图已经基本达到,能让初学者甚至不是初学者学到一些东西,错误是难免的,一则源于尽量的口语化倾向(这样才容易理解),二则是没有人的知识结构是完美的。

至于 megaboy(飞天御剑流之杀神一刀斩),我觉得简直就是当年的pora的初级版,呵呵,这绝对没有任何贬义,相信大家都记得pora的功力。希望能进一步发展到pora的水平,而不要搞出一些自己没有完全清楚的概念(或者没有办法完全说清楚的概念)来批评别人。
megaboy 2005-11-28
  • 打赏
  • 举报
回复
现在我要出去办事,回来再给你来个大洗脑!!
megaboy 2005-11-28
  • 打赏
  • 举报
回复
指针还有非变量的吗?是的,还有“constant pointer”(常量指针)。
------------------------------------------------------------
我晕,int *const constant_ptr;不是变量?你怎么学指针的?constant_ptr仍然是一个左值!!!
megaboy 2005-11-28
  • 打赏
  • 举报
回复
对于这个领域,C/C++标准是最终裁决。
--------------------------------------
你这句话说得没错,C标准是最终裁决。但是谁一直在引用标准?你有这样做过吗??你有引用过哪怕一丁点标准来证明你自己吗?但是,首先要讨论的问题是:你看过标准吗?


我也知道,对于自己信仰的东西,很难容得别人来说三道四,因为打破自己的信仰是一件非常痛苦的事。
------------------------------------------------------------
你的信仰难容别人说三道四,偶很理解。但是,不是说因为那是自己的信仰,就是正确的!谁经历过这样的痛苦?你吗????我就经历过这样的痛苦!实话跟你说,当初我初学C的时候,学的就是潭那两本破书《C语言程序设计》和《C++语言程序设计》,我以前,跟你现在一样,也是认为指针就是地址,但随着对C逐步深入了解,真正拿起一本标准来阅读,发现根本不是这么回事!这其中的痛苦,你知道吗?


关于你以后的解释,通篇都是对我本人的否定,而没有对我的论点进行否定,所以不和你争论。
------------------------------------------------------------------------------
请你指出来,哪一点是针对你本人的??为什么你不能争论?因为你无法反驳来自标准与C发明人的根据!这是不容怀疑的!


可以看出你到现在还在把理解为“指针”就是指“指针变量”,不承认有“指针类型”这个概念。如果你知道了C/C++中还有指针类型,那么你就明白了什么是指针变量、什么是指针常量、什么是常量指针。
关于2,google的搜索结果已经说明了你的“所谓指针变量这个概念,其实是中国人自己杜撰出来的”。如果你不相信这个结果,也好,请拿出你的证据证明你的这个论断。
-----------------------------------------------------------------------
这个根据,早就说了,C89/C99没有pointer variable或者variable of pointer这个概念,标准里面一直说的,只有pointer。

什么是pointer type?偶就给你说说标准里面是怎么说的:

A pointer type describes an object whose value provides a reference to an entity of the referenced type. A pointer type derived from the referenced type T is sometimes called ‘‘pointer to T’’. The construction of a pointer type from a referenced type is called ‘‘pointer type derivation’’.

标准已经清楚地表明了,a pointer type描述的是一个对象!
whyglinux 2005-11-28
  • 打赏
  • 举报
回复
再给一个证据。

我又查了一下C99标准,查找“pointer variable”,没有任何结果。查找“variable pointer”倒是找到了一个(p115, 6.7.5.1.3),下面是原文:

EXAMPLE The following pair of declarations demonstrates the difference between a ‘‘variable pointer to a constant value’’ and a ‘‘constant pointer to a variable value’’.
const int *ptr_to_constant;
int *const constant_ptr;
The contents of any object pointed to by ptr_to_constant shall not be modified through that pointer, but ptr_to_constant itself may be changed to point to another object. Similarly, the contents of the int pointed to by constant_ptr may be modified, but constant_ptr itself shall always point to the same location.

这里特别指出了“variable pointer”(变量指针)。什么?指针还有非变量的吗?是的,还有“constant pointer”(常量指针)。不仅如此,还有“pointer constant”(指针常量,可在标准中查到原文),比如我们熟悉的 NULL 就是。
megaboy 2005-11-28
  • 打赏
  • 举报
回复
再补充一点,其实pointer type那段话已经表明了,pointer is a object。就是那一句:

A pointer type describes an object whose value provides a reference to an entity of the referenced type.

这里所说的object,并不是pointer所指向的东西,而是pointer本身!
whyglinux 2005-11-28
  • 打赏
  • 举报
回复
To megaboy(飞天御剑流之杀神一刀斩)

关于我的第一条,你为什么不给出结果。希望你认真查一下。
1. 建议你查一下C或者C++标准,有没有“pointer type”,这个问题不就清楚了吗?

可以看出你到现在还在把理解为“指针”就是指“指针变量”,不承认有“指针类型”这个概念。如果你知道了C/C++中还有指针类型,那么你就明白了什么是指针变量、什么是指针常量、什么是常量指针。

关于2,google的搜索结果已经说明了你的“所谓指针变量这个概念,其实是中国人自己杜撰出来的”。如果你不相信这个结果,也好,请拿出你的证据证明你的这个论断。

关于你以后的解释,通篇都是对我本人的否定,而没有对我的论点进行否定,所以不和你争论。只有你对我提出的论点感兴趣的时候,我才有兴趣和你作进一步的讨论。

我也知道,对于自己信仰的东西,很难容得别人来说三道四,因为打破自己的信仰是一件非常痛苦的事。但是幸好我们讨论的问题不是思想领域上的,而是在C/C++领域。对于这个领域,C/C++标准是最终裁决。
megaboy 2005-11-28
  • 打赏
  • 举报
回复
更抽象地说,pointer应该是个对象。这样在概念上可以统一起来,解释起来也较“指针变量”容易得多。
----------------------------------------------------------
这样的观点就正确了。pointer就是一个object,而address不是一个object,是一个rvalue,因此pointer不是address。
megaboy 2005-11-28
  • 打赏
  • 举报
回复
“函数不是对象”这是一种过时的观点,在C89发布之前,不少编译器的确不把函数看作一个对象,但c89 committee纠正了这个观点。也许你从来没有想过为什么C89/C99在函数名是一个右值的情况下仍然允许&func这种东西?ANSI及ISO在rationale of c99(当然还有rationale of c89)中解释了这一点:

6.5.3.2 Address and indirection operators

Some implementations have not allowed the & operator to be applied to an array or a function. (The construct was permitted in early versions of C, then later made optional.) The C89 Committee endorsed the construct since it is unambiguous, and since data abstraction is enhanced by allowing the important & operator to apply uniformly to any addressable entity.

这段话说明了&func的意义,并不是对函数名取地址,而是对一个函数对象取地址,也正因为如此,&func所代表的地址值才跟func一样。K&R也在其书第二版中表达了函数是一个对象的意思:

4.3 External Variables

A C program consists of a set of external objects, which are either variables or functions.


不过,在这里讨论函数是否一个对象属于题外话,我们要谈的,不是指针所指向的东西是不是一个对象,而是指针本身是不是一个对象!

我认为你并没有弄明白C89/C99关于pointer type那段话的意思,这段话的大意是:指针的类型被称为引用类型,它可以派生于函数的类型、对象的类型或者不完整类型。它描述一个对象,这个对象的值提供一个对引用类型实体的引用,因此,它有时候又被称为指向T类型的指针。这段话所表明的是:对于一个指针p,如果它指向一个整数对象,那么这个指针的类型就叫整数指针,如果它指向的对象是一个函数,那么它就叫函数指针,如此类推。而不是你所理解的pointer is a type!

megaboy 2005-11-28
  • 打赏
  • 举报
回复
2. 建议在 google 中输入“pointer variable”,看看有几百万条目跟这是有关的。再在中文网页中查找“指针类型”,其条目数不过才区区几十万。然后再看看你的结论。
---------------------------------------------------------------------
一个问题的根据是在google中搜索条目,哪个条目多就是哪个对么?简直是天大的笑话,用一句不客气的话说:简直乱放屁!



3. 注意:函数的返回值是一个临时生成的“无名”对象,跟表达式的值是一样的。“无名对象”顾名思义就是没有名字来表示的对象,怎么可能是用“变量”来表示呢。能用变量表示的对象不会是无名对象吧。
.........................
因此,这并不是说“里面所写的概念、语法、定义、规定通通都不算数”(不过不排除这种可能性),你还要结合上下文正确理解它们才行。
----------------------------------------------------------------
我敢说,你连标准都没见过。什么是object,什么是value of expression、什么是lvalue、什么是rvalue你都还没弄清楚。自己先去找本C89/C99看看,然后逐一回答标准是怎么给上述四个东西定义的!



如果没有上下文的话,“你是一个人”这句话根据强调的重点不同可以有多种理解。但是无论怎样,你不能根据这句话判断“我是学生”或者“我是教师”的真伪。正如我在上面所说的:“指针”可以指类型、对象(变量)以及值(内存地址)等多种概念。
-----------------------------------------------------
自己先去翻翻K&R这本书的上下文。连书都没看过,谈什么书?



另外,即使是把其中的 pointer 解释为 pointer variable,从这句话的后半部分来看也描述得很不全面的。一个指针中存放的只是“the address of a variable”(变量的地址)吗?显然不是这样的。
----------------------------------------------------------------
我真怀疑你连K&R是谁都不知道。你这句话是在表明这样一个信息:C的发明人把自己发明的东西写错了!!



我在前面说过,在程序中表示对象的方法有两种:通过变量的方法以及通过引用的方法。变量表示对象是通过定义变量来实现的,由于有一个变量名与此对象相关,这个对象是一个有名对象。另外一种创建对象的方法是通过动态内存分配,这样创建的对象是没有名字的,因此不能通过变量直接访问,只能通过引用进行间接访问。然而,这样的对象当然也是有地址的,也可以放到一个指针(变量)中去。
----------------------------------------------------------------------
一看你写“有名对象”、“无名对象”就知道你所持的是那种关于对象的谬论。自己先翻翻标准是怎么给“对象”下定义的!
whyglinux 2005-11-27
  • 打赏
  • 举报
回复
To BluntBlade(无锋之刃·回炉再造)

我只是提醒你我注意到的一些问题,至于你接不接受那是你的事。

1. 等待着你的新版文字。

2. 数据类型是什么。有一个简单的规则,就是:把你声明语句中的变量名去掉,剩下的就是这个变量代表的类型。可以按照这个规则来检验 int* pi; 以及 double* pd;是否是相同的指针类型。

>> void意味着零长度

void 是一种不完整数据类型,即它的长度不能确定。如果void是零长度的话,表明它的长度已经确定,与不完整数据类型的定义矛盾。

>> 最后,所谓“pointer variable”,也只不过是Chinese English而已。
“pointer variable”不是Chinese English,在 google 中搜一下就可以明白了。

>> 更抽象地说,pointer应该是个对象。

不,如果把 pointer 理解为是 pointer type 就好办了。
whyglinux 2005-11-27
  • 打赏
  • 举报
回复
1. 建议你查一下C或者C++标准,有没有“pointer type”,这个问题不就清楚了吗?

2. 建议在 google 中输入“pointer variable”,看看有几百万条目跟这是有关的。再在中文网页中查找“指针类型”,其条目数不过才区区几十万。然后再看看你的结论。

3. 注意:函数的返回值是一个临时生成的“无名”对象,跟表达式的值是一样的。“无名对象”顾名思义就是没有名字来表示的对象,怎么可能是用“变量”来表示呢。能用变量表示的对象不会是无名对象吧。

4. 如果我说:你是一个人,按照你的意思,这根本不能说明你是一个人,是不是非得要这样说:你只是一个人,才能说明你是一个人???

如果没有上下文的话,“你是一个人”这句话根据强调的重点不同可以有多种理解。但是无论怎样,你不能根据这句话判断“我是学生”或者“我是教师”的真伪。正如我在上面所说的:“指针”可以指类型、对象(变量)以及值(内存地址)等多种概念。

在比如:“小花是一只猫”,我对朋友说“小花是我的好朋友”,那么并不矛盾吧。如果这时朋友在我耳边叫嚣“小花不过是一只猫而已”,那你说他是什么意思呢?

因此,这并不是说“里面所写的概念、语法、定义、规定通通都不算数”(不过不排除这种可能性),你还要结合上下文正确理解它们才行。
加载更多回复(49)
主体:(一) 一、C++概述 (一) 发展历史 1980年,Bjarne Stroustrup博士开始着手创建一种模拟语言,能够具有面向对象的程序设计特色。在当时,面向对象编程还是一个比较新的理念,Stroustrup博士并不是从头开始设计新语言,而是在C语言的基础上进行创建。这就是C++语言。 1985年,C++开始在外面慢慢流行。经过多年的发展,C++已经有了多个版本。为次,ANSI和ISO的联合委员会于1989年着手为C++制定标准。1994年2月,该委员会出版了第一份非正式草案,1998年正式推出了C++的国际标准。 (二) C和C++ C++是C的超集,也可以C是C++的子集,因为C先出现。按常理,C++编译器能够编译任何C程序,但是C和C++还是有一些小差别。 例如C++增加了C不具有的关键字。这些关键字能作为函数和变量的标识符在C程序中使用,尽管C++包含了所有的C,但显然没有任何C++编译器能编译这样的C程序。 C程序员可以省略函数原型,而C++不可以,一个不带参数的C函数原型必须把void写出来。而C++可以使用空参数列表。 C++中new和delete是对内存分配的运算符,取代了C中的malloc和free。 标准C++中的字符串类取代了C标准C函数库头文件中的字符数组处理函数。 C++中用来做控制态输入输出的iostream类库替代了标准C中的stdio函数库。 C++中的try/catch/throw异常处理机制取代了标准C中的setjmp()和longjmp()函数。 二、关键字和变量 C++相对与C增加了一些关键字,如下: typename bool dynamic_cast mutable namespace static_cast using catch explicit new virtual operator false private template volatile const protected this wchar_t const_cast public throw friend true reinterpret_cast try bitor xor_e and_eq compl or_eq not_eq bitand 在C++中还增加了bool型变量和wchar_t型变量: 布尔型变量是有两种逻辑状态的变量,它包含两个值:真和假。如果在表达式中使用了布尔型变量,那么将根据变量值的真假而赋予整型值1或0。要把一个整型变量转换成布尔型变量,如果整型值为0,则其布尔型值为假;反之如果整型值为非0,则其布尔型值为真。布儿型变量在运行时通常用做标志,比如进行逻辑测试以改变程序流程。 #include iostream.h int main() { bool flag; flag=true; if(flag) cout<明一下:某些编译器无法编译该程序(不支持该数据类型)。 三、强制类型转换 有时候,根据表达式的需要,某个数据需要被当成另外的数据类型来处理,这时,就需要强制编译器把变量或常数由声明时的类型转换成需要的类型。为此,就要使用强制类型转换,格式如下: int* iptr=(int*) &table; 表达式的前缀(int*)就是传统C风格的强制类型转换(typecast),又可称为强制转换(cast)。强制转换告诉编译器把表达式转换成指定的类型。有些情况下强制转换是禁用的,例如不能把一个结构类型转换成其他任何类型。数字类型和数字类型、指针指针之间可以相互转换。当然,数字类型和指针类型也可以相互转换,但通常认为这样做是不安全而且也是没必要的。强制类型转换可以避免编译器的警告。 long int el=123; short i=(int) el; float m=34.56; int i=(int) m; 上面两个都是C风格的强制类型转换,C++还增加了一种转换方式,比较一下上面和下面这个书写方式的不同: long int el=123; short i=int (el); float m=34.56; int i=int (m); 使用强制类型转换的最大好处就是:禁止编译器对你故意去做的事发出警告。但是,利用强制类型转换使得编译器的类型检查机制失效,这不是明智的选择。通常,是不提倡进行强制类型转换的。除非不可避免,如要调用malloc()函数时要用的void型指针转换成指定类型指针。 四、标准输入输出流 在C语言中,输入输出是使用语句scanf()和printf()来实现的,而C++中是使用类来实现的。 #include iostream.h main() //C++中main()函数默认为int型,而C语言中默认为void型。 { int a; cout<>a; /*输入一个数值*/ cout<明都被编译器认为是注释,这种注释不能换行。C++中仍然保留了传统C语言的注释风格/*……*/。 C++也可采用格式化输出的方法: #include iostream.h int main() { int a; cout<>a; cout<变量的标识符在程序中出现。 五、函数参数问题 (一) 无名的函数形参 声明函数时可以包含一个或多个用不到的形式参数。这种情况多出现在用一个通用的函数指针调用多个函数的场合,其中有些函数不需要函数指针声明中的所有参数。看下面的例子: int fun(int x,int y) { return x*2; } 尽管这样的用法是正确的,但大多数C和C++的编译器都会给出一个警告,参数y在程序中没有被用到。为了避免这样的警告,C++允许声明一个无名形参,以告诉编译器存在该参数,且调用者需要为其传递一个实际参数,但是函数不会用到这个参数。下面给出使用了无名参数的C++函数代码: int fun(int x,int) //注意不同点 { return x*2; } (二) 函数的默认参数 C++函数的原型中可以声明一个或多个带有默认值的参数。如果调用函数时,省略了相应的实际参数,那么编译器就会把默认值作为实际参数。可以这样来声明具有默认参数的C++函数原型: #include iostream.h void show(int=1,float=2.3,long=6); int main() { show(); show(2); show(4,5.6); show(8,12.34,50L); return 0; } void show(int first,float second,long third) { cout<a; for(int i=1;i<=10;i++) //C语言中,不允许在这里定义变量 { static int a=0; //C语言中,同一函数块,不允许有同名变量 a+=i; cout<<::a<< <size; int *array=new int[size]; for(int i=0;i变量 在C++中,引用是一个经常使用的概念。引用型变量是其他变量的一个别名,我们可以认为他们只是名字不相同,其他都是相同的。 1.引用是一个别名 C++中的引用是其他变量的别名。声明一个引用型变量,需要给他一个初始化值,在变量的生存周期内,该值不会改变。& 运算符定义了一个引用型变量: int a; int& b=a; 先声明一个名为a的变量,它还有一个别名b。我们可以认为是一个人,有一个真名,一个外号,以后不管是喊他a还是b,都是叫他这个人。同样,作为变量,以后对这两个标识符操作都会产生相同的效果。 #include iostream.h int main() { int a=123; int& b=a; cout<变量本身是这样的)。这就称为以引用方式调用。把参数的值传递到被调用函数内部的拷贝中则称为以传值方式调用。 #include iostream.h void display(const Date&,const char*); void swapper(Date&,Date&); struct Date { int month,day,year; }; int main() { static Date now={2,23,90}; static Date then={9,10,60}; display(now,Now: ); display(then,Then: ); swapper(now,then); display(now,Now: ); display(then,Then: ); return 0; } void swapper(Date& dt1,Date& dt2) { Date save; save=dt1; dt1=dt2; dt2=save; } void display(const Date& dt,const char *s) { cout<dt; if(dt>0 && dt<4) { const Date& bd=getdate(dt); cout<说,也就是数据类型的设计和实现。 一、类的设计 1.类的声明 class 类名 { private: //私有 ... public: //公有 ... }; 2.类的成员 一般在C++类中,所有定义的变量和函数都是类的成员。如果是变量,我们就叫它数据成员如果是函数,我们就叫它成员函数。 3.类成员的可见性 private和public访问控制符决定了成员的可见性。由一个访问控制符设定的可访问状态将一直持续到下一个访问控制符出现,或者类声明的结束。私有成员仅能被同一个类中的成员函数访问,公有成员既可以被同一类中的成员函数访问,也可以被其他已经实例化的类中函数访问。当然,这也有例外的情况,这是以后要讨论的友元函数。 类中默认的数据类型是private,结构中的默认类型是public。一般情况下,变量都作为私有成员出现,函数都作为公有成员出现。 类中还有一种访问控制符protected,叫保护成员,以后再。 4.初始化 在声明一个类的对象时,可以用圆括号()包含一个初始化表。 看下面一个例子: #include iostream.h class Box { private: int height,width,depth; //3个私有数据成员 public: Box(int,int,int); ~Box(); int volume(); //成员函数 }; Box::Box(int ht,int wd,int dp) { height=ht; width=wd; depth=dp; } Box::~Box() { //nothing } int Box::volume() { return height*width*depth; } int main() { Box thisbox(3,4,5); //声明一个类对象并初始化 cout<说,只能允许一个析构函数,析构函数不能有参数,并且也没有返回值。析构函数的作用是完成一个清理工作,如释放从堆中分配的内存。 我们也可以只给出析构函数的形式,而不给出起具体函数体,其效果是一样的,如上面的例子。但在有些情况下,析构函数又是必需的。如在类中从堆中分配了内存,则必须在析构函数中释放 主体:(三)类的转换 C++的内部数据类型遵循隐式类型转换规则。假设某个表达市中使用了一个短整型变量,而编译器根据上下文认为这儿需要是的长整型,则编译器就会根据类型转换规则自动把它转换成长整型,这种隐式转换出现在赋值、参数传递、返回值、初始化和表达式中。我们也可以为类提供相应的转换规则。 对一个类建立隐式转换规则需要构造一个转换函数,该函数作为类的成员,可以把该类的对象和其他数据类型的对象进行相互转换。声明了转换函数,就告诉了编译器,当根据句法判定需要类型转换时,就调用函数。 有两种转换函数。一种是转换构造函数;另一种是成员转换函数。需要采用哪种转换函数取决于转换的方向。 一、转换构造函数 当一个构造函数仅有一个参数,且该参数是不同于该类的一个数据类型,这样的构造函数就叫转换构造函数。转换构造函数把别的数据类型的对象转换为该类的一个对象。和其他构造函数一样,如果声明类的对象的初始化表同转换构造函数的参数表相匹配,该函数就会被调用。当在需要使用该类的地方使用了别的数据类型,便宜器就会调用转换构造函数进行转换。 #include iostream.h #include time.h #include stdio.h class Date { int mo, da, yr; public: Date(time_t); void display(); }; void Date::display() { char year[5]; if(yr<10) sprintf(year,0%d,yr); else sprintf(year,%d,yr); cout<tm_mon+1; yr=tim->tm_year; if(yr>=100) yr-=100; } int main() { time_t now=time(0); Date dt(now); dt.display(); return 0; } 本程序先调用time()函数来获取当前时间,并把它赋给time_t对象;然后程序通过调用Date类的转换构造函数来创建一个Date对象,该对象由time_t对象转换而来。time_t对象先传递给localtime()函数,然后返回一个指向tm结构(time.h文件中声明)的指针,然后构造函数把结构中的日月年的数值拷贝给Date对象的数据成员,这就完成了从time_t对象到Date对象的转换。 二、成员转换函数 成员转换函数把该类的对象转换为其他数据类型的对象。在成员转换函数的声明中要用到关键字operator。这样声明一个成员转换函数: operator aaa(); 在这个例子中,aaa就是要转换成的数据类型的符。这里的类型符可以是任何合法的C++类型,包括其他的类。如下来定义成员转换函数; Classname::operator aaa() 类名标识符是声明了该函数的类的类型符。上面定义的Date类并不能把该类的对象转换回time_t型变量,但可以把它转换成一个长整型值,计算从2000年1月1日到现在的天数。 #include iostream.h class Date { int mo,da,yr; public: Date(int m,int d,int y) {mo=m; da=d; yr=y;} operator int(); //声明 }; Date::operator int() //定义 { static int dys[]={31,28,31,30,31,30,31,31,30,31,30,31}; int days=yr-2000; days*=365; days+=(yr-2000)/4; for(int i=0;i明,虽然Tester类中有一个以Date型变量为参数的构造函数,编译器却不会把它看作是从Date到Tester的转换构造函数,因为它的声明中包含了explicit修饰符。 七、表达式内部的转换 在表达式内部,如果发现某个类型和需要的不一致,就会发生错误。数字类型的转换是很简单,这里就不举例了。下面的程序是把Date对象转换成长整型值。 #include iostream.h class Date { int mo, da, yr; public: Date(int m,int d,int y) { mo=m; da=d; yr=y; } operator long(); }; Date::operator long() { static int dys[]={31,28,31,30,31,30,31,31,30,31,30,31}; long days=yr; days*=365; days+=(yr-1900)/4; //从1900年1月1日开始计算 for(int i=0;i说过了,私有数据成员不能被类外的其他函数读取,但是有时候类会允许一些特殊的函数直接读写其私有数据成员。 关键字friend可以让特定的函数或者别的类的所有成员函数对私有数据成员进行读写。这既可以维护数据的私有性,有可以保证让特定的类或函数能够直接访问私有数据。 1.友元类 一个类可以声明另一个类为其友元,这个友元的所有成员函数都可以读写它的私有数据。 #include iostream.h class Date; class CustomDate { int da,yr; public: CustomDate(int d=0,int y=0) { da=d; yr=y; } void display() const {cout<指针和引用。但是不可以使用那些需要知道预引用的类的定义细节的语句,如声明该类的一个实例或者任何对该类成员的引用。 4.显式友元预引用 也可以不使用预引用,这只要在声明友元的时候加上关键自class就行了。 #include iostream.h class CustomDate { int da,yr; public: CustomDate(int d=0,int y=0) { da=d; yr=y; } void display() const {cout<明了一个可以访问两个类私有数据成员的友元函数是如何将在两个类之间架起桥梁的。 #include iostream.h class Time; class Date { int mo,da,yr; public: Date(int m,int d,int y) { mo=m; da=d; yr=y;} friend void display(const Date&, const Time&); }; class Time { int hr,min,sec; public: Time(int h,int m,int s) { hr=h; min=m; sec=s;} friend void display(const Date&, const Time&); }; void display(const Date& dt, const Time& tm) { cout << dt.mo << '/' << dt.da << '/' << dt.yr; cout << ' '; cout << tm.hr << ':' << tm.min << ':' << tm.sec; } int main() { Date dt(2,16,97); Time tm(10,55,0); display(dt, tm); return 0; } 主体:(五)析构函数和this指针 一、析构函数 前面的一些例子都没有析构函数,这是因为所用到的类在结束时不需要做特别的清理工作。下面的程序给出了一新的Date类,其中包括一个字符串指针,用来表示月份。 #include iostream.h #include string.h class Date { int mo,da,yr; char *month; public: Date(int m=0, int d=0, int y=0); ~Date(); void display() const; }; Date::Date(int m,int d,int y) { static char *mos[] = { January,February,March,April,May,June, July,August,September,October,November,December }; mo=m; da=d; yr=y; if(m!=0) { month=new char[strlen(mos[m-1])+1]; strcpy(month, mos[m-1]); } else month = 0; } Date::~Date() { delete [] month; } void Date::display() const { if(month!=0) cout<指针month。 析构函数在删除month指针时,可能会出现一些问题。当然从这个程序本身来看,没什么麻烦;但是从设计一个类的角度来看,当Date类用于赋值时,就会出现问题。假设上面的main()修改为“ int main() { Date birthday(8,11,1979); Date today; today=birthday; birthday.display(); return 0; } 这会生成一个名为today的空的Date型变量,并且把birthday值赋给它。如果不特别通知编译器,它会简单的认为类的赋值就是成员对成员的拷贝。在上面的程序中,变量birthday有一个字符型指针month,并且在构造函数里用new运算符初始化过了。当birthday离开其作用域时,析构函数会调用delete运算符来释放内存。但同时,当today离开它的作用域时,析构函数同样会对它进行释放操作,而today里的month指针是birthday里的month指针的一个拷贝。析构函数对同一指针进行了两次删除操作,这会带来不可预知的后果。 如果假设today是一个外部变量,而birthday是一个自变量。当birthday离开其作用域时,就已经把对象today里的month指针删除了。显然这也是不正确的。 再假设有两个初始化的Date变量,把其中一个的值赋值给另一个: Date birthday(8,11,1979); Date today(12,29,2003); today=birthday; 问题就更复杂了,当这两个变量离开作用域时,birthday中的month的值已经通过赋值传递给了today。而today中构造函数用new运算符给month的值却因为赋值被覆盖了。这样,birthday中的month被删除了两次,而today中month却没有被删除掉。 二、重载赋值运算符 为了解决上面的问题,我们应该写一个特殊的赋值运算符函数来处理这类问题。当需要为同一个类的两个对象相互赋值时,就可以重载运算符函数。这个方法可以解决类的赋值和指针的释放。 下面的程序中,类中的赋值函数用new运算符从堆中分配了一个不同的指针,该指针获取赋值对象中相应的值,然后拷贝给接受赋值的对象。 在类中重载赋值运算符的格式如下: void operator = (const Date&) 后面我们回加以改进。目前,重载的运算符函数的返回类型为void。它是类总的成员函数,在本程序红,是Date类的成员函数。它的函数名始终是operator =,参数也始终是同一个类的对象的引用。参数表示的是源对象,即赋值数据的提供者。重载函数的运算符作为目标对象的成员函数来使用。 #include iostream.h #include string.h class Date { int mo,da,yr; char *month; public: Date(int m=0, int d=0, int y=0); ~Date(); void operator=(const Date&); void display() const; }; Date::Date(int m, int d, int y) { static char *mos[] = { January,February,March,April,May,June, July,August,September,October,November,December }; mo = m; da = d; yr = y; if (m != 0) { month = new char[strlen(mos[m-1])+1]; strcpy(month, mos[m-1]); } else month = 0; } Date::~Date() { delete [] month; } void Date::display() const { if (month!=0) cout<指针所占用的内存返还给堆。接着,如果源对象的month指针已经初始化过,就用new运算符为对象重新分配内存,并把源对象的month字符串拷贝给接受方。 重载的Date类赋值运算符函数的第一个语句比较了源对象的地址和this指针。这个操作取保对象不会自己给自己赋值。 三、this指针 this指针是一个特殊的指针,当类的某个非静态的成员函数在执行时,就会存在this指针。它指向类的一个对象,且这个对象的某个成员函数正在被调用。 this指针的名字始终是this,而且总是作为隐含参数传递给每一个被声明的成员函数,例如: void Date::myFunc(Date* this); 实际编程时函数的声明不需要包含这个参数。 当程序中调用某个对象的成员函数时,编译器会把该对象的地址加入到参数列表中,感觉上就好象函数采用了上面所示的声明,并且是用如下方式来调用的: dt.myFunc(& dt); 静态成员函数不存在this指针。 当调用某个对象的成员函数时,编译器把对象的地址传递给this指针,然后再调用该函数。因此,成员函数你对任何成员的调用实际上都隐式地使用了this指针。 1.以this指针作为返回值 使用this指针可以允许成员函数返回调用对象给调用者。前面的程序中重载赋值运算符没有返回值,因此不能用如下的形式对字符串进行赋值: a=b=c; 为了使重载的类赋值机制也能这样方便,必须让赋值函数返回赋值的结果,在这里就是目标对象。当赋值函数执行时,其返回值也恰好是this指针所指的内容。 下面的程序对前面那个程序进行了修改,让重载赋值运算符返回了一个Date对象的引用。 #include iostream.h #include string.h class Date { int mo,da,yr; char *month; public: Date(int m=0, int d=0, int y=0); ~Date(); void operator=(const Date&); void display() const; }; Date::Date(int m, int d, int y) { static char *mos[] = { January,February,March,April,May,June, July,August,September,October,November,December }; mo = m; da = d; yr = y; if (m != 0) { month = new char[strlen(mos[m-1])+1]; strcpy(month, mos[m-1]); } else month = 0; } Date::~Date() { delete [] month; } void Date::display() const { if (month!=0) cout<指针 在应用程序中,如果数据结构里有指向自身类型的成员,那么使用this指针会提供更多的方便。下面的程序中建立了一个类ListEntry的链表。 #include iostream.h #include string.h class ListEntry { char* listvalue; ListEntry* preventry; public: ListEntry(char*); ~ListEntry() { delete [] listvalue; } ListEntry* PrevEntry() const { return preventry; }; void display() const { cout< name; if (strncmp(name, end, 3) == 0) break; ListEntry* list = new ListEntry(name); if (prev != 0) prev->AddEntry(*list); prev = list; } while (prev != 0) { prev->display(); ListEntry* hold = prev; prev = prev->PrevEntry(); delete hold; } return 0; } 程序运行时,会提示输入一串姓名,当输入完毕后,键入end,然后程序会逆序显示刚才输入的所有姓名。 程序中ListEntry类含有一个字符串和一个指向前一个表项的指针。构造函数从对中获取内存分配给字符串,并把字符串的内容拷贝到内存,然后置链接指针为NULL。析构函数将释放字符串所占用的内存。 成员函数PrevEntry()返回指向链表前一个表项的指针。另一个成员函数显示当前的表项内容。 成员函数AddEntry(),它把this指针拷贝给参数的preventry指针,即把当前表项的地址赋值给下一个表项的链接指针,从而构造了一个链表。它并没有改变调用它的listEntry对象的内容,只是把该对象的地址赋给函数的参数所引用的那个ListEntry对象的preventry指针,尽管该函数不会修改对象的数据,但它并不是常量型。这是因为,它拷贝对象的地址this指针的内容给一个非长常量对象,而编译器回认为这个非常量对象就有可能通过拷贝得到的地址去修改当前对象的数据,因此AddEntry()函数在声明时不需要用const。 主体:(六)类对象数组和静态成员 一、类对象数组 类的对象和C++其他数据类型一样,也可以为其建立数组,数组的表示方法和结构一样。 #include iostream.h class Date { int mo,da,yr; public: Date(int m=0,int d=0, int y=0) { mo=m; da=d; yr=y;} void display() const { cout<说过,不带参数或者所有参数都有默认值的构造函数叫做默认构造函数。如果类中没有构造函数,编译器会自动提供一个什么都不做的公共默认构造函数 。如果类当中至少有一个构造函数,编译器就不会提供默认构造函数。 如果类当中不含默认构造函数,则无法实例化其对象数组。因为实例花类对象数组的格式不允许用初始化值来匹配某个构造函数的参数表。 上面的程序中,main()函数声明了一个长度为2的Date对象数组,还有一个包含初始化值的单个Date对象。接着把这个初始化的Date对象赋值给数组中第一个对象,然后显示两个数组元素中包含的日期。从输出中可以看到,第一个日期是有效日期,而第二个显示的都是0。 当声明了某个类的对象数组时,编译器会为每个元素都调用默认构造函数。 下面的程序去掉了构造函数的默认参数值,并且增加了一个默认构造函数。 #include class Date { int mo, da, yr; public: Date(); Date(int m,int d,int y) { mo=m; da=d; yr=y;} void display() const { cout <变量,必须在类定义之外来定义该成员。 1.静态数据成员 静态数据成员相当于一个全局变量,类的所有实例都可以使用它。成员函数能访问并且修改这个值。如果这个静态成员是公有的,那么类的作用域之内的所有代码(不论是在类的内部还是外部)都可以访问这个成员。下面的程序通过静态数据成员来记录链表首项和末项的地址。 #include iostream.h #include string.h class ListEntry { public: static ListEntry* firstentry; private: static ListEntry* lastentry; char* listvalue; ListEntry* nextentry; public: ListEntry(char*); ~ListEntry() { delete [] listvalue;} ListEntry* NextEntry() const { return nextentry; }; void display() const { cout<name

70,024

社区成员

发帖
与我相关
我的任务
社区描述
C语言相关问题讨论
社区管理员
  • C语言
  • 花神庙码农
  • 架构师李肯
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧