异常处理 - [C++] - guomei的专栏 - CSDN博客

来源:百度文库 编辑:神马文学网 时间:2024/06/13 02:23:16
guomei的专栏
登录 注册 欢迎 退出 我的博客 配置 写文章 文章管理 博客首页   全站 当前博客  空间 博客 好友 相册 留言 用户操作
[留言]  [发消息]  [加为好友]
李泽敏ID:guomei
共13402次访问,排名11165(-2),好友3人,关注者5人。
能精通VC编程,嵌入式开发,以及精通软硬件编程的朋友
李泽敏的文章
原创 16 篇
翻译 0 篇
转载 40 篇
评论 0 篇
订阅我的博客
[编辑]guomei的公告

点击献爱心

[编辑]文章分类
编程指南
留学英美
其他
嵌入式系统开发
全国性的大学生竞赛
信号与信息处理
[编辑]VC(MFC)编程指南
[编辑]留学英美
太傻
寄托家园
存档
2009年09月(1)
2009年08月(1)
2009年05月(6)
2009年04月(6)
2009年03月(1)
2009年02月(25)
2008年07月(4)
2008年06月(1)
2008年05月(8)
2007年06月(3)
异常处理 - [C++] 收藏
2005-09-01
异常处理 - [C++]
344页
第二十一章异常处理
本章讨论C++风格的异常处理。异常处理允许用户以一种有序的方式管理运行时间
错。使用C++的异常处理,用户程序在错误发生时可自动调用一个错误处理程序。异常处理
最主要的优点是自动转向错误处理代码,而以前在大程序中这些代码是由“手工”编制的。
注:异常处理不属于C++原始规范的范畴。它是在1983年间发展起来的。异常处理由建
议的ANSI C++标准定义,被现存大多数C++编译程序所支持。
21.1异常处理的基础
C++异常处理建立在三个关键字基础之上:try、catch和throw。通常,监测异常情况的
程序语句包含在try中。如果try块中发生了异常(也就是错误),则用throw处理。异常由
catch捕获,并得到处理。下面详细讨论这些论点。
如前所述,抛出异常的语句必须在try块中执行(在try块中调用的函数也可能抛出异
常)。任何异常必须由紧跟在抛出异常的try语句之后的catch语句捕获。try和catch的一般
形式如下:
try{
// try block
}
catch(type1 arg){
//catch block

catch (type2 arg){
// catch block
}
catch(type3 arg){
// catch block

catch(typeN arg) {
// catch block

try块必须包括用户程序中监测错误的部分。它们可以短至函数中的几条语句,也可以
是象try块(有效地监测整个程序)中main()函数的代码那样完全包装。
异常发生时,由相应的catch语句去捕获并处理此异常。与一个try相关的catch语句可
能不止一条。至于使用哪条catch语句,则由异常的类型决定。也就是说,如果由catch语句
说明的数据类型与异常情况匹配,则此catch语句(其它catch语句跳过)执行。当捕获一个
异常时,arg将接受它的值。可以捕获任何类型的数据,包括用户创建的类。如果try块中无
345页
异常(错误)发生,则不执行任何catch语句。
throw语句的一般形式如下:
throw exception;
throw必须在try块中或在try块中任何直接或间接调用的函数中执行。exception是被抛出
的一个值。
如果对于抛出的异常没有合适的catch语句,则会发生程序异常终止。如果用户的编译
程序符合建议的ANSI C++标准,那么抛出一个未被处理的异常会引起调用terminate()函
数。缺省时,terminate()调用abort()终止用户程序,但如果用户愿意,则可以定义自己的终
止处理程序。请参阅自己的编译程序库函数参考手册以了解详情。
下面的程序显示了C++异常处理操作的方法:
// A simple exception handling example.
#include
main()
{
cout<<"start\n";
try{// start a block
cout<<"Inside try block\n";
throw 100;//throw an error
cout<<"This will not execute";

catch(int i){//catch an error
cout <<"Caught an exception--value is :";
cout<<i<<"\n";
}
cout<<"End";
return 0;
}
程序显示下面结果:
Start
Inside try block
Caught an exception-- value is: 100
End
仔细阅读上述程序会发现,try块中包含了三条语句和一个处理整型异常的catch(int i)
语句。在try块中,三条语句仅有两条会执行:第一条cout语句和throw语句。一旦有异常被
抛出时,控制权转向catch语句,try块则被终止。也就是说,catch没有被调用,而只是将程序
执行权转给它(程序栈在需要时自动复位)。因此,throw后的cout语句不会执行。
通常,catch语句中的代码试图通过适当的操作纠正错误。如果错误能够排除,catch语
句后的程序继续执行。但通常有错误不能排除,则catch块将调用exit()或abort()以终止程
序。
346页
如上所述,异常的类型必须与catch语句说明的类型匹配。在上面的例子中,如果将
catch语句中的数据类型改为double型,就不能捕获异常情况,会发生异常终止。示例如下:
// This example will not work.
#include<iostream.h>
main()
{
cout<<"start\n";
try{ // start a try block
cout<<"Inside try block\n";
throw 100;// throw an error
cout <<"this will execute";

catch (double i ){// Won’t work for an int exception
cout<<"Caught an exceptyon--value is :";
cout<<i<<"\n";

cout<<"End";
return 0;
}
因为整型异常不能由catch(double i)语句捕获,所以程序产生如下结果:
start
Inside try block
Tag:
编程博客 发表于08:52:00 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-01
模板 - [C++]
332页
第二十章 模板
C++语言相对较新的一个特性是模板。通过模板可以创建样板函数和样板类。在样板
函数或类中,函数和类操作的数据类型说明为参数。因此,可以使用一个带多种不同数据类
型的函数和类,而不必显式记忆针对不同的数据类型的各种具体版本。这里一并讨论样板函
数和样板类。
注:模板不属于C++的原始规范之范畴,它是在1990年增加的。模板由建议的ANSI C
++标准定义,现在已被多数C++编译程序所支持。
20.1 样板函数
样板函数定义了适用于不同数据类型操作的通用集。样板函数操作的数据类型是当作
参数传递给它的。通过这种机制,同样的通用过程即可适用于大范围的数据。读者也许知道,
很多算法在逻辑上是相同的,与所使用的数据类型无关。例如,快速排序算法对整型数组和
浮点数组的操作是一样的,只是被排序的数据类型不一样。通过创建样板函数,可以定义独
立于任何数据的算法属性。这样,在执行函数时,编译程序就对实际使用的数据类型自动产
生正确的代码。从根本上讲,创建一个样板函数也就是创建了一个可自动重载自身的函数。
样板函数由关键字template创建。在C++中,"template(模板)”的普通含义正反映了它
的用途。使用它创建一个描述函数功能的模板(或框架),然后让编译程序填写所需要的细
节。下面是template函数定义的一般形式:
template<class Ttype> ret_type func_name(parameter list);

//body of function

Ttype是函数使用的数据类型的位置保持器名称,该名称可能在函数定义中用到。但
是,它仅是一个位置保持器,编译程序在创建一个函数的特定版本时会自动将它替换为实际
的数据类型。
下面的短程序创建了一个样板函数,用来交换调用的两个变量的值。由于交换两个值的
一般过程与变量类型无关,因此这样创建样板函数是一个好的选择。
//Function template example.
#include<iostream.h>
//This is a function template.
template<class X> void swap(x&a,x&b)

x temp;
temp=a;
a=b;
333页
b=temp;

main()
{
int i=10,j=20;
float x=10.1,Y=23.3;
char a=’x’,b=’z’;
cout <<"Original i,j:"<<i<<’ ’<<j<<endl;
cout <<"Original x,y:"<<x<<’ ’ <<y<<endl;
cout <<"Original a,b:"<<a<<’ ’ <<b<<endl;
swap(i,j);// swap integers
swap(x,y);// swap floats
swap(a,b);// swap chars
cout<<"Swapped i,j:"<<i<<’ ’<<j<cout<<"Swapped x,y"<<x<<’ ’<cout<<"swapped a,b"<a<<’ ’<<b<return 0;
}
让我们仔细研究一下这个程序。
template <class X> void swap(X &a,x&b)
告诉编译程序两件事:创建模板,样板定义开始。这里,X被当作位置保持器的样板类型。在
template后,定义了swap(),用X作为被交换值的数据类型。在main()函数中,用三种数据
类型调用swap()函数:整型、浮点型和字符型。由于swap()是样板函数,故编译程序自动创
建swap()的三种版本——一个用于交换整型值,一个用于交换浮点值,一个用于交换字符。
在讨论模板时,有时在其它一些C++文献中可能遇到下面一些术语。首先,样板函数
(由template语句引导的函数定义)也被称为模板函数(template function)。当编译程序创建
这个函数特定的版本时,也就是创建了生成函数(generate function)。生成函数的行为被称
为实例化(instantiating),换句话说,生成函数是模块函数的具体实例。
从技术上讲,在样板函数定义中,template部分不必与函数名在同一行。例如,下面也是
说明swap()函数的常用方法:
template<class X>
void swap(x&a,x&b)

x temp;
temp=a;
a=b
b=temp;
}
采用这种形式时有一点要注意,即在template语句和样板函数定义之前不能有其它语句。例
如,下面程序段的编译不能通过:
// This will not compile.
template <class X>
334页
int i;/this is an error
void swap(x&a,x&b)

x temp;
temp=a;
a=b;
b=temp;
}
正如注释所指示的,template说明必须直接放在函数定义之前。
20.1.1 带两个样板类型的函数
在template语句中,可以使用逗号分隔列表定义多个样板数据类型。例如,下面的程序
创建了带两个样板类型的样板函数:
#include <iostream.h>
template <class type1,class type2>
void myfunc(type1 x,type2 y)

cout<<x<<’ ’<<y<
main()

myfunc(10,"hi");
myfunc(0.23, 10L);
return 0;
}
在上面的例
Tag:
编程博客 发表于08:51:00 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-01
基于数组的I/O - [C++]
323页
第十九章 基于数组的I/O
除了控制台和文件I/O,c++基于流的I/O系统支持基于数组的I/O。基于数组的I/O
将RAM作为输入设备、输出设备或输入输出设备。基于数组的I/O通过普通的C++流实
现。事实上,前面两章所述的内容均适用于基于数组的I/O。唯一不同的是,基于数组的I/O
的、和流关联的设备是内存。
有些c++书把基于数组的I/O称为RAM内I/O。由于这里的流和所有的C++流一样
能处理格式信息,所以有时也把基于数组的I/O称为RAM内格式(有时还使用过去的术语
“内核格式”,本书使用术语in_RAM和array_based)。
c++基于数组的I/O的作用类似于C的函数sprintf()和sscanf()。它们都把内存当作
输入/输出设备。
要在程序中使用基于数组的I/O,就必须包含strstream.h。
19.1 基于数组的类
基于数组的类有istrstream、ostrstream和strstream。它们分别用来创建输入、输出和输
入/输出流。这些类的基类之一是strstreambuf,它定义了派生类使用的几个底层的具体属
性。除了strstreambuf以外,istream 也是istrstream的基类。类ostrstream包括了类ostream。
strstream也包括了类iostream。所以,所有基于数组的类和“普通”I/O类一样存取相同的成
员函数。
19.2 创建基于数组的输出流
要将一个输出流和一个数组关联起来,可使用下列ostream的构造函数:
ostrstream ostr(char *buf, int size, int mode=ios::out);
其中,buf是指向数组的指针,该数组接收写入流的字符。数组的长度由参数size确定。缺省
时,流以输出方式打开,但也可以将几项或在一起复合为所需的方式(例如,可以包含ios::
app使输出添加在数组中已存在的信息的尾部)。mode的缺省值可以满足大多数的要求。
一旦打开了一个基于数组的输出流,所有对这个流的输出就放在数组中。但是,任何输
出都不能写到数组的限界之外,任何这种企图都会导致错误。
下面是一个介绍基于数组的输出流的简单程序。
#include<strstream.h>
#include<iostream.h>
main()
{
char str[80];
324页
ostrstream outs(str,sizeof(str));
outs<<"Hello";
outs<<99-14<<hex<<" ";
outs.setf(ios::showbase);
outs<<100<cout <<str;// display string on console
return 0;
}
该程序显示Hello 85 0x64。记住,outs是一个和任何其它流相同的流,并且具有相同的
性能。唯一不同的是和outs相关的设备是内存。由于outs是一个流,所以象hex和ends之
类的操纵符是完全有效的。同时也可以使用ostream的成员函数,如setf()。
要使输出的数组以空结束,就必须显式地赋空。上述程序用操纵符ends给串赋空,也可
以使用字符’\0’。
如果读者对上述程序实际发生了什么还不是很清楚,可把它和下面的C程序作一个比
较。这个程序在功能上和上面的C++程序相同,只不过是用sprintf()来组织输出数组罢了。
#include<stdio.h>
main()

char str[80];
sprintf(str,"Hello%d%#x",99-14,100);
printf(str);
return 0;
}
通过调用成员函数pcount()可以确定输出数组的字符个数。其原型为:
int pcount();
pcount()的返回值包含空结束符(如果存在的话)。
下面的程序介绍了pcount()。它报告字符串有17个字符,其中包括16个字符和1个空
结束符。
#include <strstream.h>
#include<iostream.h>
main()

char str[80];
ostrstream outs(str,sizeof(str));
outs<<"Hello";
outs<<34<<" "<<1234.23;
outs<<ends;//null terminate
cout<<outs.pcount();// display how many chars in outs
cout<<" "<<str;
return 0;

325页
19.3 使用数组作输入
要将输入流和数组关联起来,可使用下列istrstream的构造函数:
istrstream istr(char*buf);
其中,buf是指向数组的指针,该数组作为每次向流输入的字符源。 buf所指的数组必须以空
结束。空结束符从不从数组中读取。
下面是一个用字符串输入的例子。
#include <iostream.h>
#include<strstream.h>
main()

char s[]="10 Hello 0x88 12.23 done";
istrstream ins(s);
int i;
char str[80];
float f;
// reading: 10 Hello
ins >>i;ins >>str;
cout<<i<<" "<<str<// reading:0x88 12.23 done.
ins>>i;
ins>>f;
ins>>str;
cout<<hex<<i<<" "<<f<<" "<<str;
return 0;
}
如果要把字符串的一部分作为输入,可使用istrstream的构造函数的下列形式:
istrstrea
Tag:
编程博客 发表于08:50:01 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-01
C++文件I/O(2) - [C++]
18.7跟踪EOF
使用成员函数eof()可以跟踪何时到达文件尾,其原型为:
int eof();
当到达文件尾时,eof()的返回值不为0,否则为0。
注:建议的ANSI C++标准规定eof()返回值类型为布尔型。但目前大多数C++编译程
序还不支持布尔数据类型。实际上,将eof()返回值说明为布尔类型还是整型无关紧要,因为
在任何表达式中的布尔型可以自动转换为整型。
下面的程序以十六进制的ASCII形式显示一个文件的内容。
/* Display contents of specified file
in both ASCII and in hex.
*/
#include
#include
#include
#include <iomanip.h>
#include<stdio.h>
main(int argc,char * argv[])
{
if(argc!=2){
cout << "Usage: Display <filename>\n";
return 1;

ifstream in(argv[1], ios::in | ios::binary);
if(!in){
cout << "Cannot open input file.\n";
return 1;

register int i, j;
int count=0;
char c[16];
cout. setf(ios: : uppercase);
while(!in. eof()){
for(i=0; i<16&&!in.eof(); i++){
in. get(c[i]);

if(i<16) i--;// get rid of eof
for(j=0;j<i;j++)
cout << setw(3) << hex <<(int)c[j];
for(;j<16;j++) cout<<" " ;
314页
cout<<"\t";
for(j=0;j<i;j++)
if(isprint(c[j])) cout<<c[j];
else cout <<".";
cout <<endl;
count++;
if(count==16){
count=0;
cout<<"Press ENTER to continue:";
cin.get();
cout<<endl;

}
in.close();
return 0;
}
如果用这个程序显示它自己的话,则第一屏内容为:
2F 2A 20 44 69 73 70 6C 61 79 20 63 6F 6E 74 65 / * Display conte
6E 74 73 20 6F 66 20 73 70 65 63 69 66 69 65 64 nts of specified
20 66 69 6C 65 D A 20 20 20 69 6E 20 62 6F 74 file. . in bot
68 20 41 53 43 49 49 20 616E 64 20 69 6E 20 68 h ASCII and in h
65 78 2E D A 2A 2F D A 23 69 6E 63 6C 75 64 ex. . */ . . # includ
65 20 3C 69 6F 73 74 72 65 61 6D 2E 68 3E D A e <iostream. h>. .
23 69 6E 63 6C 75 64 65 20 3C 66 73 74 72 65 61 # Include<fstrea
6D 2E 68 3E D A 23 69 6E 63 6C 75 64 65 20 3C m. h>. . # include<
63 74 79 70 65 2E 68 3E D A 23 69 6E 63 6C 75 ctype. h>. . # inclu
64 65 20 3C 69 6F 6D 61 6E 69 70 2E 68 3E D A de <iomanip. h>. .
23 69 6E 63 6C 75 64 65 20 3C 73 74 64 69 6F 2E # include<stdio.
68 3E D A D A 6D 6l 69 6E 28 69 6E 74 20 61 h>. . . . main(int a
72 67 63 2C 206368 61 72 20 2A 61 72 67 76 5B rsc, char * argv[
5D 29 D A 780A20 20 69 66 28 61 72 67 63]).{.. if(argc
21 3D 32 29 20 78DA20 20 20 20 63 6F 75 74 1=2){. . cout
20 3C 3C 20 22 55 73 6167 65 3A 2044 69 73 70 <<"Usage: Disp
Press ENTER to continue:
18.8ignore()函数
使用成员函数ignore()可以从输入流中读取并丢弃字符,其原型为:
istream &ignore(int num=1, int delim=EOF);
它读取并丢弃字符直到忽略num个(缺省为一个)字符或遇到由delim指定的字符(缺
省为EOF)为止。如果遇到了定界字符,不从输入流中移去它。
下面的程序读文件TEST。它忽略字符直到遇到空格或读完10个字符,然后显示文件的
315页
剩余部分。
#include <iostream. h>
#include<fstream.h>
main()

ifstream in("test");
if(!in){
cout << "Cannot open file.\n";
return 1;

/* Ignore up to 10 characters or until first
space is found. */
in.ignore(10,’’);
char c;
while(in){
in.get(c);
cout<<c;
}
in.close();
return 0;

18.9 peek()和putback()
使用peek()可以从输入流中获取下一个字符,但不移去它,其原型为:
int peek();
它返回流中的下一个字符或者到达文件尾时返回EOF。
使用putback()可以获得从一个流中读取的最后一个字符,其原型为:
istream&putback(char c);
其中,c为读取的最后一个字符。
18.10 flush()
在执行输出时,并不立即将数据写入和流
Tag:
编程博客 发表于08:49:06 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-01
C++文件I/O(1) - [C++]
虽然C++的I/O方法形成了一个完整的系统,但文件I/O(特别是磁盘文件I/O)由于
受到本身的限制和特性,因而被当作一种特殊情况专门讲述。因为最普通的文件是磁盘文
件,而磁盘文件具有其它设备不具有的性能和特征。但要记住,磁盘文件I/O只是一般I/O
系统的一个特例,且本章讨论的大多数材料也适用于与其它类型的设备相连的流。
18.1 fstream.h。和文件类
要处理文件I/O,程序中必须包含首标文件fstream.h。它定义了的类包括ifstream、of-
stream和fstream。这些类分别从istream和ostream派生而来。记注,istream和ostream是从
ios派生来的,所以ifstream、ofstream和fstream也存取ios定义的所有运算。
18.2 打开和关闭文件
在C++里,用户通过把文件和流联系起来打开文件。打开文件之前,必须首先获得一个
流。流分为三类:输入、输出和输入/输出。要创建一个输入流,必须说明它为类ifstream;要
创建一个输出流,必须说明它为类ofstream。执行输入和输出操作的流必须说明为类
fstream。例如,下面的程序段创建了一个输入流、一个输出流和一个输入/输出流。
ifstream in; // input
ofstream out; // output
fstream io;// input and output
一旦创建了一个流,把流和文件联系起来的一种方法就是使用函数open()。该函数是
这三个类中每个类的成员。其原型为:
void open(const char * filename, int mode, int access=filebuf::openprot);
其中,filename为文件名,它可以包含路径说明符。mode值决定文件打开的方式,它必须是
下列值中的一个(或多个):
ios::app
ios::ate
ios::binary
ios::in
ios::nocreate
ios::noreplace
ios::out
ios::trunc
305页
用户可以把两个或两个以上的值或在一起得到它们的复合值。下面看看这些值的含义。
包含ios::app导致把所有文件的输出添加在文件尾。它只能用于输出文件。包含ios::
ate导致文件打开时定位于文件尾。虽然如此,在文件的任何位置都可以进行I/O操作。
缺省时,文件以文本方式打开。使用ios::binary值可以使文件以二进制方式打开。文件
以文本方式打开时,会产生不同的字符转换。如回车/换行转换为换行。但当文件以二进制方
式打开时,不发生字符转换。任何文件,不管是包含格式化的文本还是包含原始的二进制数
据,均可以文件方式或二进制方式打开,唯一不同的是是否进行字符转换。
ios::in值说明文件有输入能力,ios::out值说明文件有输出能力。用ifstream创建的流
隐含为输入,用ofstream创建的流隐含为输出。在这些情况下,没有必要提供这些值。
包含ios::nocreate导致函数open()在文件不存在时失败。ios::noreplace值导致函数
open()在文件存在时失败。
ios::trunc值导致已存在的同名文件的内容被破坏且长度被截断为0。
注:建议的ANSI C++标准规定方式参数类型为openmOde,通常为整型。现在大多数工
具简单地将方式参数定义为整型数。
access值决定存取文件的方式,其缺省值为filebuf::openprot,指定为通常文件(filebuf
是由streambuf派生的类)。大多数时候允许access缺省。参阅编译程序手册,找出自己所用
操作环境中该参数的其它选项。例如,文件共享选项一般定义为在网络环境下使用的access
参数。
下面的程序段打开一个普通输出文件:
ofstream out;
out.open(“test”, ios::out);
由于一般情况下使用的是mode缺省值,所以很少象上面这样调用open()。对于ifstream,
mode的缺省值是ios::in;对于ofstream它是ios::out。所以,上面的语句通常表现如下:
out.open("test"); // defaults to output and normal file
如下例所示,要打开一个供输入和输出的流,就必须指定mode的值为ios::in和ios::
out(无缺省值):
fstream mystream;
mystream.open("test",ios::in | ios::out);
如果open()失败,则mystream为0。所以在使用一个文件之前,应使用如下语句进行测
试,以确保打开操作成功。
if(!mystream){
cout<<"Cannot open file.\N";
// handle error
}
虽然使用函数open()打开一个文件完全合适,但在大多数情况下,由于ifstream、of-
stream和fstream类包含自动打开文件的构造函数,所以没有必要调用open()。构造函数有
和open()相同的参数和缺省值。最常见的打开文件的方法是:
306页
ifstream mystream("myfile");// open file for input
如前所述,如果由于某种原因不能打开文件,则关于流的变量的值是0。所以,不管用构造函
数还是显式地调用open()打开文件,都要测试流的值以保证真正打开了文件。
要关闭一个文件,使用成员函数close()。例如,用下面的语句关闭关于流mystream的
文件:
mystream. close(
Tag:
编程博客 发表于08:48:10 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-01
C++的I/O系统基础(2) - [C++]
注意,函数返回类型ostream的流的引用(记住, ostream是从ios派生的、支持输出的类)。
进一步讲,传给函数的第一个参数是对输出流的引用,第二个参数是被插入的对象。插入符
在退出之前必须做的最后一件事是返回stream,这使得插入符可以用在插入链中。
在插入符函数中,可以放置用户希望的任何类型的过程或运算符。也就是说,一个插入
符的作为完全取决于用户自己。为了保持好的编程风格,应该限制插入符向流输出信息的操
作。例如,用插入符计算PI到30个十进制位作为插入符的副作用大概不是一个好主意。
看一个例子,我们为类型phonebook的对象创建一个插入符:
class phonebook{
public:
char name[80];
int areacode;
int prefix;
int num;
phonebook(char *n,int a,int p,int nm)

strcpy(name,n);
areacode=a;
prefix=p;
num=nm;

};
该类包含了一个人的姓名和电话号码。下面是一种为类型phonebook的对象创建插入
符函数的方法。
// Display name and phone number
ostream &operator<<(ostream &stream, phonebook o)

stream << o.name <<" ";
stream<< "("<stream <<o.prefix<<"-" <<o.num <<"\n";
return stream;// must return stream
}
下面的短程序介绍了phonebook插入符函数。
#include <iostream. h>
#include <string.h>
class phonebook{
public:
293页
char name[80];
int areacode;
int prefix;
int num;
phonebook(char *n,int a,int p,int nm)

strcpy(name,n);
areacode=a;
prefix=p;
num=nm;

};
//Display name and phone number.
ostream&operator<<(ostream&stream, phonebook o)

stream<<o. name<<" ";
stream<<"(" <<o.areacode<<")";
stream <<o.prefix <<"-"<< o.num <<"\n";
return stream;// must return stream
}
main()
{
phonebook a("Ted", 111, 555,1234);
phonebook b("Alice", 312, 555, 5768);
phonebook c("Tom", 212, 555, 9991 );
cout<<a<<b<<c;
return 0;
}
该程序产生如下输出:
Ted (111)555-1234
Alice(312) 555-5768
Tom(212)555-9991
在这个程序里,注意phonebook的插入符不是phonebook的成员。尽管这种方法初看
起来似乎很费解,但还是很容易理解的。当一个任意类型的运算符函数是类的成员时,左操
作数(通过this隐式传递)是产生调用运算符函数的对象。而且,这个对象是运算符函数作为
其成员的类的对象。没有办法改变这一点。如果一个重载的运算符函数是类的成员,左操作
数必须是该类的对象。重载插入符时,左操作数是一个流,而右操作数是那个类的对象。所
以,重载的插入符不能是为之重载的类的成员。变量name、areacode、prefix和num在前面的
程序是公有的,所以插入符能存取它们。
插入符不能是它为之定义的类的成员的事实似乎是C++的一个严重缺陷。既然重载
的插入符不是成员,它们又怎么能存取类的专有元素呢?在上面的程序里,所有成员都是公
有的。然而,封装性是面向对象程序设计的基本部仲。要求用插入符输出的所有数据成为公
有与本原则相冲突。解决这个难题的办法是,让插入符成为类的友元。这既满足了传给重载
的插入符的参量是流的要求并授权函数存取它为之重载的类的专有部分。下面是和前面相
294页
同的程序,修改后插入符就成为友元函数。
#include<iostream.h>
#include<string.h>
class phonebook{
//now private
char name[80];
int areacode;
int prefix;
int num;
public:
phonebook(char*n,int a, int p, int nm)

strcpy(name,n);
areacode=a;
prefix=p;
num=nm;

friend ostream &operator<<(ostream&stream,phonebook o);
};
// Display name and phone number.
ostream &operator<<(ostream &stream, phonebook o)

stream<<o.name<<" ";
stream<<"(" <<o.areacode<<")";
stream<<o.prefix<<"-"<<o.num<<"\n";
return stream;// must return stream
}
main()

phonebook a("Ted" , 111, 555, 1234);
phonebook b("Alice", 312, 555, 5768);
phonebook c("Tom", 212, 555, 9991);
cout<<a<<b<<c;
return 0;
}
Tag:
编程博客 发表于08:46:28 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-01
C++的I/O系统基础(1) - [C++]
除了完全支持C的I/O系统外C++还定义了自己的面向对象的I/O系统。和C的I/
O系统一样,C++的I/O系统也完全是集成化的,即c++的I/O系统的那些有差别的地
方,如控制台I/O和磁盘I/O,实际上只是相同机制的不同方面。本章讨论c++面向对象I/
O系统的基础。虽然本章的例子使用的是“控制台”I/O,但这些信息同样适用于其它设备,
如磁盘文件(在第十八章“C++文件I/O”中讨论)。
C的I/O系统是非常丰富、灵活和强大的。既然如此,为什么c++又定义另外一个系
统?答案是C的I/O系统一点也不了解对象。所以,为了使c++完全支持面向对象的程序
设计,有必要建立一个能对用户定义的对象进行操作的面向对象的I/O系统。除了支持对象
以外,使用C++的I/O系统甚至对不广泛(或任何)使用用户定义的对象的程序也有一些间
接的好处。在本章的稍后读者可以看到一些例子。
本章将介绍怎样格式化数据,怎样重载C++的<<和>> I/O运算符,使之用于我
们创建的类,还要弄清楚如何创建所谓操纵符的特殊I/O函数,以提高程序效率。
17.1 C++的流
和C的I/O系统一样C++的I/O系统也是通过流操作的。第九章“ANSI C标准文件
I/O”详细讨论了流,这里不再赘述。概括地讲,流就是既生产信息又消费信息的逻辑设备。
流通过c++系统和物理设备关联。尽管流所关联的物理设备客观上存在着差异,但所有的
流都以同样的方式起作用。正因为如此,相同的c++I/O函数实际上可以操作任何类型的
物理设备。例如,可以用写文件的同一函数写打印机或屏幕。这样做的好处是用户只需要了
解一个接口。
17.2 基本的流类
C++在首标文件iostream.h里提供了对I/O的支持。这个文件定义了支持I/O操作的
类层次。最底层的类叫做streambuf。该类提供了基本的输入和输出操作。如果用户不派生
自己的I/O的类,就不能直接使用 streambuf。类ios位于下一层,它提供了格式化的I/O。从
ios派生出类istream、ostream和iostream,这些类分别用来创建输入流、输出流和输入/输出
流,在下一章读者将看到,其它从ios派生的类支持磁盘文件和RAM内格式化。
ios类包含控制或监视流的基本操作的许多成员函数和变量。本章和下一章提供了对其
成员的许多参考。但要记住,如果以通常方式使用 c++I/O系统,任何流都可以使用ios的
成员。
282页
17.2.1 C++的预定义流
当一个C++程序开始运行时,它就自动打开四个内部流。它们是:
流 含义 缺省设备
cin 标准输入 键盘
cout 标准输出 屏幕
cerr 标准错误输出 屏幕
clog cerr的缓冲形式 屏幕
流cin、cout和cerr对应于C的stdin、stdout和stderr。
缺省时,标准流用来和控制台通信。但在支持I/O重定向的环境下(如DOS、UNIX、
Windows和OS/2),可以把标准流重定向于其它设备或文件。为简单起见,假设本章的例子
不发生重定向。
注:建议的ANSI C++标准也定义了这四种附加流:win、wout、Werr和wlog。这些是标
准流的扩展字符版本。扩展字符类型为wchar_t,通常为16位。扩展字符用来容纳与某些自
然语言相关的大字符集。
17.3 格式化的I/O
C++I/O系统支持格式化的I/O操作。例如,用户可以设置域宽,指定数字基数或确定
显示到十进制小数点后多少位。从根本上讲,用C的printf()和scanf()函数输入或输出的
任何格式都能用C++的<<和>>I/O运算符输入或输出。
有两种相关的但概念不同的格式化数据的方法。第一种是,可以直接存取ios类的各种
成员。尤其是可以设置在ios类中定义的各种格式状态位或调用各种i/O成员函数。第二种
是,可以使用作为表达式一部分的所谓操纵符的特殊函数。
我们将从使用ios成员函数和标志开始讨论格式化的I/O。
17.3.1 用ios成员格式化
ios格式化标志的集合对应于每个流,它们控制着通过流格式化信息的一些方法。在ios
中,这些标志被命名和赋值,一般使用枚举,请看如下示例程序:
// ios formatting flags
enum {
skipws = 0x0001,
left = 0x0002,
right = 0x0004,
internal = 0x0008,
dec = 0x0010,
Oct=0x0020,
hex=0x0040,
showbase = 0x0080,
showpoint = 0x0100,
uppercase = 0x0200,
· showpos = 0x0400,
283页
scientific=0x0800,
fixed=0x1000,
unitbuf=0x2000,
};
与流有关的格式标志编码为一些长整型形式。建议的ANSI C++标准将格式标志的类
型定义为fmtflags,但目前还没有编译程序定义这种类型(当然,不久会有)。实际上,fmtflags
就是通过typedef为长整型定义的一个名称。本书将在引用格式标志时用long类型,因为它
是现在主流C++编译程序所使用的类型。关于这一点,可参考相应的编译程序手册。
当设置skipws标志时,在流上进行输入操作时丢弃空白
Tag:
编程博客 发表于08:45:33 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-01
虚函数和多态性 - [C++]
271页
第十六章虚函数和多态性
C++在编译时和运行时都支持多态性(一个接口,多种算法)。编译时的多态性,由第十
四章讨论的“重载函数和运算符”支持,运行时的多态性用继承和虚函数实现。
16.1虚函数
虚函数就是在基类中说明为virtual 并在派生类中重定义的函数。为了说明一个函数为
虚函数,需在其说明的前面加上关键字virtual。派生类中函数的重定义忽略基类中函数的定
义。从根本上讲,基类中说明的虚函数在很大程度上扮演了说明一般类行为和规定接口的位
置持有者角色。派生类对虚函数的重定义指明了函数(算法)执行的实际操作。换句话说,虚
函数定义通用类,重定义虚函数实现具体算法。
正常存取时,虚函数同任何其它类型的类成员函数完全一样。使虚函数显得如此重要且
能支持运行时的多态性的原因,就在于当通过指针存取时它们的所作所为。正如第十三章讨
论的,一个基类指针能用来指向从该基类派生的任何类。当一个基类指针指向包含虚函数的
派生对象时,c++根据该指针指向的对象的类型决定调用函数的哪一种形式。因此,当它指
向不同的对象时,就执行不同形式的虚函数。
在讨论更多的理论之前,先看看下面的例子:
#include<iostream.h>
class base{
public:
virtual void vfunc(){
cout<<"This is base’s vfunc()\n";

};
class derived1 : public base{
public:
void vfunc(){
cout<<"This is derived1’s vfunc()\n";

};
class derived2: public base{
public:
void vfunc(){
cout<<"This is derived2’s vfunc()\n";
}
};
main()
272页
{
base *p,b;
derived1 d1;
derived2 d2;
// point to base
p=&b;
p->vfunc();// access base’s vfunc()
//point to derived1
p=&d1;
p->vfunc();// access derived1’s vfunc()
// point to derived2
p=&d2;
p->vfunc();// access derived2’s vfunc()
return 0;
}
该程序显示如下:
This is base’s vfunc().
This is derived1’s vfunc().
This is derived2’s vfunc().
如程序所示,在base里明了虚函数vfunc()。注意,关键字virtual放在函数说明的前
面。在derived1和derived2重定义vfunc()时,不再需要关键字virtual (但如果在派生类中
重定义虚函数时包含了它也不算错)。
在这个程序里,base被derived1和derived2继承,在每个类定义中,vfunc()都被重定
义。在main()里,说明了四个变量:
名称 类型
p 基类指针
b 基类对象
d1 derived1的对象
d2 derived2的对象
接着,把b的地址赋给p并通过p调用vfunc().由于p指向类型base的对象,所以执
行与此对应的vfunc()形式。然后,把p设置为d1的地址,并通过p再次调用vfunc().这
次p指向类型derived1的对象。这导致执行derived1::vfunc().最后,把d2的地址赋给p,
且p->vfunc()导致执行在derived2中重定义的那个vfunc()形式。这里的关键就是:p
所指的对象的类型决定了执行vfunc()的哪个形式。此外,在运行时也可以得出这个结论,
且这个过程形成了运行时多态性的基础。
虽然可以通过使用对象名和点运算符这种普通方式调用虚函数,但只有在通过基类指
针存取时才能获得运行时的多态性。例如,拿前面的例子来说,下面的句子在语法上是合法
的:
d2.vfunc(); // calls derived2’s vfunc()
273页
虽然象这样调用虚函数没有错误,但它没有充分利用vfunc()的虚拟特性。
派生类对虚函数的重定义类似于函数重载。因为它们之间存在着一些差别,所以这个术
语不适于虚函数重定义。大概县重要的是,重定义的虚函数的原型必须和在基类中指定的原
型完全匹配。这不同于重载一个普通函数。在普通函数里,返回类型以及参数的个数和类型
都不同(实际上,在重载一个函数时,函数的个数和类型都必须不同!正是通过这些区别,C+
+才能选择重载函数的正确形式)。重定义虚函数时,其原型的所有方面都必须相同。当试图
重定义一个虚函数时改变其原型, C++编译程序就只简单重载函数,因此其虚拟特性就会
失去,另一个重要的规定是,虚函数必须是它所瞩类的成员,而不能是友元。最后,构造函数
不能是虚函数,但析构函数可以是。
鉴于这些限制以及函数重载和虚函数重定义之间的差异,我们用“越位”(overriding)来
描述派生类对虚函数的重定义。
注:包含虚函数的类称为多态类。
16.1.1继承虚瞩性
当继承在函数时,其虚特性也被继承。这意味着,当
Tag:
编程博客 发表于08:44:00 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-01
继承性 - [C++]
254页
第十五章继承性
由于继承允许建立层次分类,所以它是面向对象的程序设计的基石之一。利用继承,用
户可以创建一个定义相关项集合共有特性的一般类。然后,其它更多特定的类可以继承它,
而这些类只增加它们独有的东西。
为了和标准c++的术语保持一致,被继承的类称为基类(base class),继承的类称为派
生类。进而,派生类又可以用来作为另一个派生类的基类。这样,就可以获得多重继承。
C++对继承的支持既丰富又灵活,本章将更详细地介绍它。
15.1基类存取控制
当一个类继承另一个类时,它的一般形式为:
class derived-call-name: access base-class-name{
//body of class
};
当一个类继承另一个类时,基类的成员就变成了派生类的成员。派生类中的基类成员的
存取状态由access确定。基类的存取说明符必须是public 、private或protected。如果三者都
没有指定,而派生类是类,那么缺省为private。如果派生类是结构,那么在没有显式存取说明
符时,缺省为public。下面详细讨论使用public或private的存取(protected说明符在下一节
介绍)。
当基类的存取说明符为public时,基类的所有公有成员成为派生类的公有成员,且基类
的所有受保成员成为派生类的保护成员。在所有情况下,基类的专有成员仍然为基类专
有,且不能被派生类成员存取。例如,下面的程序说明,类型derived的对象可以直接访问基
类的公有成员:
#include <iostream. h>
class base{
int i, j;
public:
void set(int a, int b){i=a;j=b;}
void show() {cout <<i<<" "<<j<<"\n";}
};
class derived : public base{
int k:
public:
derived(int x){k=x;}
void showk (){cout << k <<"\n";}
};
255页
main()
{
derived ob(3);
ob.set(1,2);//access member of base
ob.show();// access member of base
ob.showk();//uses member derived class
return 0;
}
当基类通过使用private存取说明符被继承时,基类的所有公有成员和受保护成员变成
派生类的专有成员。例如,下面的程序由于set()和show()都是derived的专有成员,所以不
能编译:
// This program won’t compile.
#include <iostream.h>
class base{
int i, j;
public:
void set(int a, int b){i=a;j=b;}
void show() {cout<<i<<" "<<j <<"\n";}
};
// Public elements of base are private in derived.
class derived : private base{
int k;
public:
derive(int x) {k=X;}
void showk() { cout<<k<<"\n";}
};
main()

derived ob(3);
ob.set(1,2);// error,can’t access set()
ob.show();// error,can’t access show()
return 0;
}
记住:当基类的存取说明符为private时,基类的公有成员和受保成员变成派生类的专有
成员。这意味着它们仍然可以被派生类的成员存取,但不能被用户程序中既不是基类成员,
也不是派生类成员的那些部分所存取。
15.1.1继承和受保护成员
C++包含的关键字protected给继承机制提供了更大的灵活性。当把一个类的一个成员
说明为protected时,它就不能被程序的其它非成员元素存取了。除了一个重要的例外,对
protected成员的存取和对专有成员的存取是完全相同的,这个例外就是protected成员只能
256页
被它所属的其它成员存取。这个唯一的例外发生在protected成员被继承的时侯。在这种情
况下,protected成员客观上不同于private。
从前一节我们知道,任何派生类都不能存取基类的专有成员。但是,如果把基类当作公
有继承,则基类的受保护成员仍然是派生类的保护成员,且程序的其它部分(包括派生类)能
存取它们,但受保护成员的行为不同。所以,通过使用protected,就可以创建这样的类成员,
既可为它们的类专有,又可以被派生类继承和存取。下面是一个例子:
#include <iostream.h>
class base{
protected:
int i,j;//private to base, but accessible by derived
public:
void set(int a, int b) { i=a;j=b;}
void show() { cout << i <<" " <<j<<"\n";}
};
class derived: public base{
int k;
public:
// derived may access bases i and j
void setk() { k=i*j;}
void showk() { cout << k<<"\n";}
};
main()
derived ob;
ob.set(2,3);//OK, known to derived
ob.show();//OK,known to derived
ob
Tag:
编程博客 发表于08:43:01 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-01
函数和运算符重载(2) - [C++]
14.6.1 使用friend重载++和--
如果要用friend函数重载增值和减值运算符,必须当作引用参数传递操作数。这是因为
friend函数没有this指针。如果保持++和--运算符的原义,那么这些操作就意味着要
修改它们操作的操作数。如果用friend重载这些运算符,那么操作数就当作参数通过值传
递。这意味着friend operator函数不会修改操作数。因为friend operator不给操作数传递this
指针,而是传递操作数的拷贝,对参数的改变不会影响产生调用的操作数。但可通过把传给
friend operator函数的参数指明引用参数来改变这种状况,这就导致对函数里参数的任何改
变都会影响产生调用的操作数。例如,下面的程序用friend函数重载关于loc类的++和--
运算符:
#includeclass loc{
int longitude, latitude;
240页
public:
loc(){};
loc(int lg, int lt){
longitude=lg;
latitude = lt;

void show(){
cout <<longitude <<" ";
cout<<latitude <<"\n";

loc operator=(loc op2);
friend loc operator++(loc &op);
friend loc operator--(loc &op);
};
loc loc::operator=(loc op2)

longitude = op2.longitude;
latitude = op2.latitude;
return *this;//i.e., return object that generated call

// now a friend_use a reference paremeter
loc operator++(loc &op)
{
op.longitude++;
op.latitude++;
return op;

// make op--a friend_use reference
loc operator--(loc &op)

op.longitude--;
op.latitude--;
return op;
}
main()

loc ob1(10, 20),Ob2;
ob1.show();
++Ob1;
ob1.show();//displays 11 21
ob2=ob1++;
ob2.show(); displays 12 22
--ob2;
ob2.show();// displays 11 21
return 0;
241页
}
如果要重载使用friend的增值和减值运算符后缀,只需简单地说明第二个空的整型参
数。下面的例子示出了与loc有关的增值运算符friend后缀的原型:
//friend,postfix version of ++
friend loc operator++(loc &op,int x);
14.6.2 friend operator函数增加了灵活性
在许多情况下,用friend还是成员函数重载运算符在功能上没有什么区别。为了保持最
大程度的封装性,最好用成员函数重载。有一种情况是,用friend重载可增加重载运算符的
灵活性。
当用成员函数重载双目运算符时,运算符的左对象产生对重载operator函数的调用。进
一步讲,指向该对象的指针通过this指针传递。现在,假设某类CL,把该类的对象加上通过
重载成员operator函数定义的整数。 ob是该类的一个对象,下面的表达式是有效的:
ob+100 // valid
在这种情况下,ob产生了对重载一函数的调用,并执行加法操作。如果改写成如下表达
式会怎样呢?
100+ob // invalid
在这种情况下,整数出现在左边。因为整数是内部的类型,所以整数和ob的对象之间
的运算是没有定义的。因此,编译程序不会编译该表达式。可以想象,在某些应用中,总得把
对象放在左边是一个沉重的负担,甚至会带来灾难。
解决这个问题的办法,就是用friend而不是成员函数重载类型C的对象,以便执行加
法。这样,两个变元都被显式地传给operator函数。所以,为了使object+integer和integer+
object都有效,只要简单地重载函数两次即可,每种情况有一个形式。因此,当用两个friend
函数重载一个运算符时,对象就既可以出现在运算符的左边,又可以出现在运算符的右边
了。
下面的程序说明如何使用friend函数定义涉及对象和内部类型的运算。
#include<iostream.h>
class loc{
int longitude, latitude;
public:
loc(){};
loc(int lg, int lt){
longitude=lg;
latitude = lt;

void show(){
cout << longitude<<" ";
cout <<latitude <<"\n";

242页
loc operator+(loc op2);
friend loc operator+(loc op1, int op2);
friend loc operator+(int op1, loc op2);
};
loc loc::operator+(loc op2)
{
loc temp;
temp.longitude=op2.longitude + longitude;
temp.latitude = op2.latitude+latitude;
return temp;

//+ is overload for loc+int
loc operator+(loc op1,int op2)
{
loc temp;
temp.longitude = op1.longitude+op2;
temp.latitude = op1.latitude + op2;
return temp;

//+ is overloaded for int+loc
loc operator+(int op
Tag:
编程博客 发表于08:42:02 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-01
函数和运算符重载(1) - [C++]
函数和运算符重载对C++程序设计非常重要。这两个特征不仅为编译时的多态性提供
大部分支持,而且使得语言具有很大的灵活性和易扩展性。例如,运算符<<和>>的重载
形成了C++输入输出的基本方法。
本章从函数重载开始,以运算符重载结束。尽管运算符重载和函数重载相似,但它处理
的过程有一些细微差别。所以,在尝试重载运算符之前应该完全弄懂函数重载。
14.1函数重载
正如在第十一章中所讨论的,函数重载就是简单地处理两个或两个以上的名字相同的
函数。中心问题是函数的重定义必须使用不同类型的参数或不同数目的参数。只需通过这
种简单的区别,编译程序就知道在任何给定的情况下应该调用哪个函数。例如,下面的程序
通过使用不同类型的参数重载函数myfunc()。
#include <iostream. h>
int myfunc(int i);// these differ in types of parameters
double myfunc(double i );
main()
{
cout<<myfunc(10)<<" " ;// calls myfunc (int i)
cout << myfunc(5.4);// calls myfunc(double i)
return 0;
}
double myfunc(double i)

return i;

int myfunc(int i)
{
return i;
}
下面的程序使用不同数量的参数重载myfunc():
#include<iostream.h>
int myfunc(int i);// these differ in number of parameters
int myfunc(int i, int j);
227页
main()

cout << myfunc(10)<<" " ;// calls myfunc(int i)
cout << myfunc(4,5);// calls myfunc(int i, int j)
return 0;

int myfunc(int i)

return i;

int myfunc(int i, int j)
{
return i*j;
}
它说明函数重载的关键是,函数必须具有不同类型或不同数目的参数。只有返回值类型
不同的函数不能够重载。例如,试图重载myfunc()是无效的:
int myfunc(int i); // Error: differing return types are
float myfunc(int i); // insufficient when overloading.
有时两个函数说明的形式不同,但实际上是相同的。例如,考虑下面的说明:
void f(int*p);
void f(int p[]); //error, *p is same a s p[]
记住,对于编译程序,*p与p[]是相同的。虽然两个原型在参数类型上不同,但事实
上,它们没有区别。
14.1.1函数重载和二义性
有可能出现这样一种情况,即编译程序无法从两个或两个以上重载函数中正确选择。我
们称之为二义性。具有二义性的语句是错误的,因此具有二义性的程序无法编译。
造成二义性的主要原因是C++的自动类型转换。如前所述, c++自动试图把函数调
用时所用的变元转换成函数所期望的变元类型。例如,考虑下面的程序段:
int myfunc(double d);
cout << myfunc(’c’); // not an error,conversion applied
上面的注释指出,由于C++自动将字符。转换为等价的double值,所以上面的语句没
有错误。在C++中,不允许进行这种转换的类型很少。虽然自动类型转换很方便,但这是引
起二义性的主要原因。例如,考虑下面的程序:
#include<iostream.h>
float myfunc(float i);
double myfunc(double i);
228页
main()

cout<<myfunc(10.1) <<" ";// unambiguous, calls myfunc(double)
cout<<myfunc(10);// ambiguous
return 0;

float myfunc(float i)
{
return i;

double myfunc(double i)

return -i;
}
其中,myfunc()被重载,因此它能携带浮点数或双精度类型的变元。在无二义性的行里,要
调用myfunc(double )。这是因为,在C++中,如果不显式指定为float,所有浮点常数都自动
转换为double型。所以该调用没有二义性。但是,当用整数10调用myfunc()时,就出现二
义性了,这是因为编译程序无法知道它应该转换成float还是double,从而就会提示错误信
息,并停止编译程序。
前面的例子说明它不是关于double和float引起二义性的myfunc()的重载,而是用引
起二义性的类型不定的变元对myfunc()的特定调用。换句话说,它不是错误的myfunc()的
重载,而是一种特殊的请求。
下面是C++自动类型转换引起二义性的又一个例子:
#include
char myfunc(unsigned Char ch);
char myfunc(char ch);
main()

cout <<myfunc(’c’);// this calls myfunc(char)
cout<<myfunc(88)<<" " ;/ / ambiguous
return 0;

char myfunc ( unsigend char ch)

return ch-1;

char myfunc(char ch)

return ch+1;
}
在C++里,unsigned char和char并非天生具有二义性,但当用技型数88调用myfunc
Tag:
编程博客 发表于08:41:00 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-01
数组、指针和引用(2) - [C++]
在这个程序里,neg()带有一个作为参数的、指向符号要求反的整数的指针。所以,neg()带
着x的地址显式地被调用。进而,在neg()里,运算符*必须用来存取i所指的变量。这样
就生成了C里的引用调用。在C++里,这可以通过使用引用参数完全自动地实现。
为了创建一个引用参数,在参数的名字前加上&即可。下面是如何用引用说明neg()的
例子:
void neg(int &i);
它告诉编译程序使i成为调用neg()时变元的别名。也就是说,i是一个隐含指针,自动
引用调用neg()所使用的变元。一旦i成为引用,就再没有必要使用运算符*了(即使是合法
的)。相反地,每次使用i时,对neg()所调用的变量,它都隐式地是一个引用。因此,在调用
neg()时,就再不必(即使合法)在变无名前加运算符&了,编译程序会自动完成这项工作。
下面是前一个程序的引用形式:
#include <iostream.h>
void neg (int &i);// i now a refereuce
main()

int x;
X=10;
cout << x<<“negated is”;
neg(x);// no longer need the&operator
cout<<x<<"\n";
return 0;
216页
}
void neg(int&i)

i=-i;//i is now a reference, don’t need *
}
复习:当创建一个引用参数时,这个参数将自动引用(隐式地指向)用于调用函数变元。
所以,语句i=-i实际上是对X操作,而不是x的拷贝。没有必要再把运算符&用于变元
了。同时,在函数中,引用参数无需用运算符*就可以直接使用。
当把一个值赋给一个引用时,实际上是把该值赋给引用所指的变量,理解这一点很重
要。函数参数是用来调用函数的变量。
在函数里,改变引用参数所指的内容是不可能的,也就是说,象如下neg()中的语句
i++;
递增在调用中使用的变量的值,不能使i指向某个新的位置。
下面是另一个例子,程序用引用参数交换调用时所带变量的值(swap()函数是典型的
引用调用参数传递的例子)。
#include<iostream.h>
void swap(int &i, int &j);
main()

int a,b,c,d;
a=1;
b=2;
c=3;
d=4;
cout<<"a and b:”<<a<<" "<<b<<"\n";
swap(a,b);// no&operator needed
cout<<"a and b:"<<a<<" "<<b<<"\n";
cout<<"C and d:”<<c<<" "<<d<<"\n";
swap(c,d);
cout<<"c and d:"<<c<<" "<<d<<"\n";
return 0;

void swap(int &i, int &j)
{
int t;
t=i;// no * operator needded
i=j;
j=t;

217页
程序显示如下:
a and b 1 2
a and b: 2 1
c and d: 3 4
c and d 4 3
13.7.2 向对象传递引用
在第十二章里,我们讲述过,当对象作为变元传给函数时,就创建了该对象的一个拷贝。
此外,在创建拷贝时,不调用该对象的构造函数。然而,当函数结束时,要调用拷贝的析构函
数。如果由于某种原因不想调用析构函数,只需通过引用传递对象(在本书的后面可以看到
这种例子)。通过引用传递时,不创建对象的拷贝。这意味着当函数结束时,没有对象作为参
数撤销,也不调用参数的析构函数。例如,试试下面的程序:
#include<iostream.h>
class cl{
int id;
public:
int i;
cl(int i);
~cl();
void neg(cl&o){o,i=-o.i;}// no temporary created
};
cl::cl(int num)

cout << "Constructing " << num<<"\n";
id=num;

cl::~cl()
{
cout<<"Destructing”<<id<<"\n";

main()
{
cl o(1);
o.i=10;
o.neg(0);
cout<<o.i<<"\n";
return 0;
}
}
下面是该程序的输出:
Constructing 1
-10
218页
Destructing 1
由此可见,cl的析构函数只被调用了一次。如果。是通过值传递的,在neg()里就会再
创建一个对象,并且在neg()结束时撤销对象时,将再一次调用析构函数。
记住,当通过引用传递参数时,在函数内部对对象的改变将影响调用的对象。
13.7.3 返回引用
函数可以返回引用。这对于允许函数用在赋值语句的左边有令人吃惊的效果。例如,考
虑这个简单程序:
#include<iostream.h>
char&replace(int i);// return a reference
char s[80]=“Hello There";
main()

replace(5)=’x’;// assign x to space after Hello
cout<<s;
return 0;

char&replace(int i)

return s[i];
}
这个程序把Hello和There之间的空格用X替换,即程序显示HelloXthere。看一看它
是如何实现的。
如上所示,replace()说明为返回字符数组的引用。replace()编码时,它返回一个通过变
元i指定的s元素的引用。然后在main()中把字符X赋给replace
Tag:
编程博客 发表于08:40:00 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-01
数组、指针和引用(1) - [C++]
指针及其数组,在c语言里是非常重要的。因此,指针和数组在C++提供的增强特征
中自然也就很重要了。事实上,指针对C++是如此重要,以致增加了一种叫做引用(refer-
ence)的新形式的指针。本章介绍和对象有关的数组、指针和引用。
13.1对象数组
在c++中,对象数组是可能存在的。说明和使用对象数组的语法和其它任何类型的变
量完全相同。例如,下面的程序使用了一个含三个元素的对象数组:
#include<iostream.h>
class cl{
int i;
public:
void set_i(int j){i=j;}
int get_i(){return i;}
};
main()
{
cl ob[3];
int i;
for(i=0;i<3; i++) ob[i].set_i(i+1);
for(i=0; i<3;i++)
cout<<ob[i].get_i()<<"\n";
return 0;
}
该程序在屏幕上显示数字1、2和3。
如果类定义一个参数化的构造函数,则可以象对其它类型的数组一样通过指定初始表
来初始化数组的每个对象,但初始化列表的准确形式则由对象构造函数所需的参数个数而
定。对于其构造函数只带一个参数的对象,可利用通常的数组初始化语法指定初始值列表。
当创建数组中的每个元素时,表中的每个值只传给构造函数。例如,下面的程序和前一个使
用初始化的程序稍微有些区别:
#include<iostream.h>
class cl{
int i;
public:
206页
Cl(int j) {i=j;}//constructor
int get_i(){return i;}
};
main()
{
cl ob[3] ={1,2,3};// initializers
int i;
for(i=0;i<3; i++)
cout<return 0;
}
该程序在屏幕上也显示数字1、2和3。
如果对象的构造函数需要两个或多个变量,则必须使用稍有不同的初始化形式,如下所
示:
#include
class cl{
int h;
int i;
public:
cl(int j, int k){h=j;i=k;}//constructor
int get_i(){return i;}
int get_h(){return h;}
};
main()
{
cl ob[3]={
cl(1,2),
cl(3,3),
cl(5,6)
};//initializers
int i;
for(i= 0;i<3; i++){
cout<<ob[i].get_h();
cout<<",";
cout<<ob[i].get_i()<<"\n";
}
return 0;
}
上面示例中,cl 的构造函数有两个参数,因此需要两个变元。也就是说不能使用缩写初
始化格式,而是使用上例所示的“长格式”。当然,在构造函数只需一个变元时也可以用长格
式,只是这种情况下用缩写格式更简便而已。
13.1.1创建初始化与未初始化数组
如果要创建对象的初始化和未初始化数组,就会发生一些特殊的情况,考虑下面的类:
207页
class cl{
int i;
public:
cl(int j) { i=j;}
int get_i() {return i;}
};
其中,在cl 中定义的构造函数需要一个参数。这暗示这种类型被说明的任何数组都将初始
化,也就是说,不允许有如下的数组说明:
cl a[9]; // error, constructor requires initializers
这个语句(正如当前定义的cl)是不合法的,其原因是没有指定参数,它暗示cl有一个无参
数的构造函数,但是,cl并没有这个函数。由于没有有效的构造函数适用于这个说明,所以
编译程序会报出一个错误。解决这个问题,需要重载一个不带参数的构造函数。这样,就允
许某些数组初始化,而某些不初始化。例如,下面是改进的cl形式:
class cl{
int i;
public:
cl(){ i=0;} // called for non_initialized arrays
cl(int j){i=j;}//called for initialized arrays
int get_i(){return i;}
};
根据上面的类,下面的两个语句都是允许的:
cl a1[3] ={ 3,5, 6} ; // initialized
cl a2[34];//uninitialized
13.2指向对象的指针
象可以有指向其它类型变量的指针一样,也可以有指向对象的指针。当存取一个给出指
向对象的指针的类成员时,使用箭头(->)运算符而不用点(.)运算符。下面的程序说明如
何存取一个给出指向对象的指针的对象:
#include <iostream. h>
class cl{
int i;
public:
cl(int j){i=j;}
int get_i(){return i;}
};
main()
{
cl ob(88),*p;
p=&ob;//get address of ob
208页
cout<<p->get_i();// use -> to call get_i)
return 0;
}
当指针递增时,它指向它所指类型的下一个元素。例如,整型指针指向下一个整数。通
常,所有指针的计算都取决于说明的指针所指的数据类型。指向对象的指针也是如此。例如,
下面的程序在将ob的起始地址赋给一个指针后,使用这个指针存取数组ob的所有三个元
素。
#include<iostream.h>
class cl{
int i;
public:
cl(){i=0;}
cl(int j{i=j;}
int get_i() {return i;}
};
main
Tag:
编程博客 发表于08:39:01 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-01
类和对象(2) - [C++]
其中,类amount存取了coins类中定义的units类型说明符(和在units枚举中定义的名
字),因为amount是coins的友元。
理解这一点很重要,即当一个类是另一个类的友元时,它只能存取另一个类中定义的名
字,但不能继承该类。特别地,第一个类的成员不能成为友元类的成员。
友元类较少使用。支持友元类是为了允许处理一些特定的情况。
12.6内联函数
C++中的一个重要特性是内联函数,通常用在类中。由于本章经常用到它(甚至于本书
后面部分也经常用到),因此,这里讨论一下内联函数。
在C++中,用户可以创建实际上不调用的短函数。相反地,它们的代码在每次调用的程
序行里得到扩展。这个过程类似于使用类函数的宏。为了使一个函数在程序行里进行代码扩
展而不被调用,只要在函数前面加上关键字inline即可。例如,在下面的程序中,函数max()
在行内扩展而不调用:
#include
inline int max(int a, int b)
{
return a>b?a:b
}
main()
{
cout<<max(10,20);
191页
cout<<“”<<max(99,88);
return 0;
}
通过编译,上述的程序等价于下面的程序:
#include
main()
{
cout<<10<20?10:20;
cout<<" " <<99>88?99:88;
return 0;
}
内联函数是C++的一个重要补充的原因是,它们能使程序员写出非常有效的代码。因
为类一般需要一些经常被执行的接口函数(提供存取私有数据),因此,这些函数的效率在C
++中是非常重要的。我们知道,每次调用函数时,调用和返回机制会产生数量可观的开销。
典型的情况是,当调用一个函数时,变元要进栈,各种寄存器内容要保存;函数返回时,又要
恢复它们的内容。问题就是这些指令要占去时间。但是,如果函数在行内扩展,上述那些操作
就不存在了。当然,虽然函数行内扩展能产生较快的执行速度,但由于重复编码会产生较长
的代码,因此,最好只内联那些非常小的函数,即只内联扩展那些明显影响程序性能的函数。
象register说明符一样,inline对编译程序来说是一种请求,而不是命令。编译程序可以
选择忽略它。
一些编译程序不能内联所有类型的函数。例如,通常编译程序不能内联递归函数。必须
查阅自己的编译程序用户手册以了解对内联的限制。记住,如果一个函数不能内联,它就被
当作一个正常函数来调用。
内联函数可以是类的成员函数。例如,下面是一个完全有效的C++程序:
#include<iostream.h>
class myclass{
int a,b;
public:
void init(int i, int j);
void show();
};
inline void myclass::init(int i, int j)
{
a=i;
b=j;
}
inline void myclass::show()
{
cout<<a<<" " <<b<<"\n";
}
192页
main()
{
myclass x;
x.init(10,20);
x.show();
return 0;
}
12.7 在类中定义内联函数
在类说明中定义短函数是可能的。如果一个函数是在类说明中定义的,它将自动地被转
换成内联函数(如果可能的话)。没有必要(但不是错误),在函数说明的前面加上关键字in-
line。例如,改写上面的程序,使init()和show()的说明包含在myclass的说明中:
#includeclass myclass{
int a,b;
public:
// automatic inline
void init(int i int j){a=j;b=j;}
void show() {cout<<a<<" "<<b<<"\n";}
};
main()

myclass x;
x.init(10, 20);
x.show();
return 0;
}
注意myclass中函数代码的格式。由于内联函数非常短,这种编码风格具有代表性。但
是,用户可以随意来用喜欢的格式。例如,下面是一个改写类说明非常有效的方法:
#include
class myclass{
int a,b;
public:
// automatic inline
void init(int i, int j)
{
a=i;
b=j;

void show()
{
193页
cout<<a<<“”<<b<<"\n";

};
从技术上讲,由于I/O语句一般比函数调用的开销要大得多,所以在这里内联函数
show()的意义就不大了。但在C++程序中,所有短成员函数一般都在它们的类中定义。(事
实上,在专业水平的C++代码中,在类说明之外定义短成员函数是少见的。)
记住,构造函数和析构函数也可以是内联的——或缺省地定义,或显式地定义。
12.8 参数化的构造函数
向构造函数传递变元是可能的。这些变元用于帮助在创建对象时初始化这些对象。为了
创建参数化的构造函数,只要和对任何别的函数一样简单地给构造函数加上参数即可。在定
义构造函数体时,用这些参数去初始化对象。例如,下面是一个包含带参数的构造函数的类:
#in
Tag:
编程博客 发表于08:38:01 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-01
类和对象(1) - [C++]
在C中,类构成了实现C++面向对象程序设计的基础。特别地,类用来定义对象的属
性。事实上,类是C++封装的基本单元。本章将详细讨论类及对象。
12.1 类
用关键字class创建类。一个类说明定义了一个连接代码和数据的新类型。这个新类型
而后又可以用来定义该类的对象。类是逻辑抽象的概念,而对象是物理存在的,也就是说对
象是类的实体。类说明在语法上同结构相似。在第十一章“C++概述”里,给出了一个简化的
类说明的一般格式。在这里我们给出一个完整的类说明的一般格式,它不继承任何别的类。
class class_name{
private data and functions
access_specifier :
data and functions
access_specifier :
data and functions
access_specifier :
data and functions
}object_list;
其中,object_list是任选项。如果出现,它说明类的对象。access_specifier为下面三个C++关
键字之一:
public
private
protected
缺省时,一个在类中说明的函数和数据属该类专有,只能被该类的成员存取。然而,如果
用public存取说明符,函数的数据就可以被程序其它部分存取了。一种存取说明符一经使用,
其作用就保持到或遇到别的存取说明符或到达类说明结束。为转换到专有说明,可以使用
private存取说明符。protected存取说明符只有在涉及继承性时才用得到(参见第十五章“继承
性”)。
在类说明中,可以在一个类说明内部频繁改变存取说明。也就是说,可以为了某些说明
转换为public存取说明,而后又回到private。下面这个例子中的类说明说明了这一特性:
181页
#include<iostream.h>
#include<string.h>
class employee{
char name[80];
public:
void putname(char * n);
void getname(char * n);
private :
double wage;
public:
void putwage(double w);
double getwage();
};
void employee:: putname(char * n)
{
strcpy(name,n);
}
void employee:: getname(char * n)
{
strcpy(n, name);
}
void employee::putwage(double w)
{
wage=w;
}
double employee::getwage()
{
return wage;
}
main()
{
employee ted;
char name[80];
ted.putname("Ted Jones");
ted.putwage(75000);
ted.getname(name);
cout<<name<<"makes$";
cout <<ted.getwage()<<" per year.";
return 0;
}
其中,employee是一个简单的类,它用来存储雇员的姓名和薪水。注意,public存取说明符使
用了两次。
实际上,大多数C++程序员往往会象下面的示例一样给employee类编码,即把归类在
182页
一起的所有private元素和public元素放在一起:
class employee {
char name[80];
double wage;
public:
void putname(char * n);
void getname(char * n);
void putwage(double w);
double getwage();
};
虽然可以在一个类说明中频繁使用不同的存取说明符,但这样做的唯一好处只是从直
观上把一个类分成几部分,使程序变得易读易懂。然而,对于编译程序来说,使用多重存取说
明符无异于使用单个存取说明符。事实上,大多数程序员很容易发现,每个类中只包含一个
private、一个protected及一个public段。
在类中说明的函数称为成员函数。成员函数可以存取类所属的任何元素,其中包括所有
的专有元素。作为类的元素的变量称为成员变量或数据成员。总之,类的任何元素都可当作
该类的成员。
对于类成员也存在一些限制。非静态成员变量没有初始程序,成员不能成为当前正被定
义的类的对象(虽然,一个成员可能是当前正被定义的类的指针),成员不能说明为extern
或register或auto。
通常,可以使一个类的所有数据成员为该类专有。这是实现封装方法的一部分。然而,
有时也需要使一个或几个变量成为公有(例如,为了获得高的执行速度,频繁使用的变量就
需要全程存取)。一旦一个变量成为公有,在用户程序的所有地方都可以存取这个变量。存
取公有成员的语法与实现函数调用一样:指定对象的名称、运算符:: 和变量名称。下面这个
简单的程序说明了如何直接存取一个公有变量。
#include <iostream. h>
class myclass{
public:
int i,j,k;// accessible to entire program
};
main()
{
myclass a,b;
a.i=100;// direct access of i,j, and k
a.j=4;
a.k=a.i*a.j;
b.k=12;// remember,a.k and b.k are different
cout<<a.k<<" " <<b.k;
return 0;
}
183页
12.2结构和类
c++将标准C结构的作
Tag:
编程博客 发表于08:37:00 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-01
C++的专有特征(2) - [C++]
::叫作用域分辨符。实际上,上述编码告诉编译程序push()的这个形式属于类stack换
句话说,这个push()在stack的作用域之内。在C++中,几个不同的类可以用同一个函数
名。编译程序通过作用域分辨符和类名来判断哪一个函数属于哪一个类。
如果要指定但不属于该类一部分的成员函数,就必须使用与该类对象相关联的格式:对
象名加点运算符再加函数名。这个规则适用于存取数据成员和成员函数。例如,下面是给对
象stack1调用init():
stack stack1,stack2;
stack1.init();
169页
这里,产生了两个对象(stack1和stack2),并初始化stack1。 stack1和stack2是两个分离的对
象。这意味着,初始化stack1时并不初始化stack2。stack1和stack2之间的仅有的关系就是
它们是同一类型的对象。
一个成员函数可以直接调用另一个成员函数,或直接调用数据成员而无需使用点操作
符。只有不属于该类的代码调用成员函数时,才使用变量名和点运算符。
下面的程序把前面零碎的程序块串在一起以说明stack类:
#include<iostream.h>
#define SIZE 100
//This creates the class stack.
class stack{
int stck[SIZE];
int tos;
public:
void init();
void push (int i);
int pop();
};
void stack::init()

tos=0;

void stack:: push(int i)

if(tos==SIZE){
cout <<"Stack is full.";
return;
}
stck[tos]=i;
tos++;

int stack∶∶pop()

if(tos=20){
cout <<"Stack underflow.";
return 0;

tos--;
return stck[tos];

main()

stack stack1,stack2;// create two stack objects
stack1.init();
stack2.init();
170页
stack1.push(1);
stack2.push(2);
stack1. push(3);
stack2. push(4);
cout<<stackl.pop()<<" ";
cout << stack1. pop() <<" ";
cout << stack2.poP()<<" ";
cout<<stack2.pop()<<"\n";
return 0;
}
记住,对象的专有部分只能被它的成员函数存取。例如,如下语句:
stack1.tos = 0; // error
不能放在前面这个程序的main()函数中,因为tos是专有的。
习惯上,大多数C程序把main()函数作为程序的第一个函数。而在上一个程序中,stack
的成员函数是在main()函数之前定义的。虽然没有规定一定要这样(可以在程序的任何位
置定义),但这是编写C++程序最常使用的方法(但非成员函数一般还是在主函数之后定
义)。本书将沿袭这个习惯。当然,在实际应用中,和程序相关的类通常包含在首标文件里。
11.5 函数重载
函数重载是C++获得多态性的途径之一。在C++中,两个或两个以上的参数说明不同
的函数可以共享同一个名字。在这种情况下,共享同一名字的函数叫做被重载,而这个过程
叫做函数的重载。
为了说明函数重载的重要性,先考虑一下实际上在所有C/C++编译程序的标准库中能
找到的三个函数:abs()、labs()和fabs()。函数abs()返回一个整数的绝对值,labs()返回一个
长整数的绝对值,fabs()返回一个双精度数的绝对值。尽管这三个函数处理几乎完全相同的
事情,但是在C中却要用三个稍有不同的名字来表示三个基本相似的任务。这就使情况从
概念上讲变得比实际更为复杂。尽管每个函数的基本概念是相同的,但程序员不得不记住三
件事。但是,在C++中,这三个函数只用一个名字,如下所示:
#include <iostream.h>
// abs is overloading three ways
int abs(int i);
double abs(double d);
long abs(long l);
main()

cout<cout << abs(-11.0)<<"\n";
171页
cout<<abs(一9Lu)<<"\n";
return 0;

int abs(int i)

cout << "using integer abs()\n";
return i<0 ?-i:i;

double abs(double d)

cout <<"using double abs()\n";
return d<0.0 ?-d:d;

long abs(long l)
{
cout <<"using long abs()\n";
return l<0?-l:l;
}
该程序创建了三个相似但不相同的名为abs()的函数,每一个返回其变元的绝对值。编
译程序根据变元的类型决定在每一种情况下需要调用哪个函数。重载函数的价值在于允许
用一个共同的名字存取相关的函数的集合。因此abs()代表了将要执行的一般动作。留给编
译程序的任务就是在一个特定的环境下选择一个正确的具体形式。程序员只需记住要执行
的一般动作。正是由于多态性,要记住三件事减为只需记住一件事了。这个例子非常平常,
但是如果拓展这个概念,就会发现多态性是如何帮助用户管理非常复杂程序的。
一般来说,要重载一个
Tag:
编程博客 发表于08:36:06 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-01
C++的专有特征(1) - [C++]
这一部分将讨论C++的专有特征,即不同于C的那些特征(C++的类C特征已在第一
部分介绍过)。C++本质上是C的一个高级集合,所以几乎所有有关C的知识都适用于C+
+。因为C++对C的大多数强化是为了支持面向对象的程序设计(oop,所以这部分还将
讨论面向对象程序设计的理论和价值。
注:这部分假设读者已熟悉如何用C语言编程。了解C语言是学习C++的前提,所以
如果读者还不了解C语言,就必须先花些时间学习一下。
第十一章C++概述
本章概括了C++的关键概念。C++是面向对象的程序设计语言,而它的面向对象的特
性是相互密切联系的。在许多实例中,这种密切联系使得在描述C++的一个特性时不想牵
涉到另外的特性十分困难。而C++的面向对象的特性在很多方面是相互缠绕的,因此要讨
论一个特性就必需预先了解一个或多个别的特性。为了说明这个问题,本章先简单介绍C+
+的最重要的特征和概念,以后各章再加以详细论述。
11.1C++的起源
C++是C的扩充版本。C++对C的扩充首先是由Bjarne Stroustrup于1980年在美国
新泽西州玛瑞惠尔的贝尔实验室提出的。他开始把这种新的语言叫做"含类的c",到1983
年才改名为C++。
尽管C++的祖先C是世界上最受喜爱和应用最广的专业程序设计语言之一,但C++
的发明是必需的。这主要是由程序设计的复杂性所决定的。这些年来,计算机程序变得越来
越复杂。即使C语言是相当好的程序设计语言,也有其局限性。在C里,一旦程序代码达到
25000至100000行,它就会变得十分复杂,全面掌握就很困难了。而C++的目的正是要扫
清这个障碍。C++的本质就是让程序员理解和管理更大更复杂的程序。
Stroustrup对C作了许多的补充以支持面向对象的程序设计(OOP)。下一节对“面向对
象的程序设计”有精确解释。Stroustrup宣称C++的某些面向对象的特点受到另一种所谓
Simula67的面向对象语言的启发。所以,C++代表着两种强大的程序设计的结合。
自问世以来,C++经历了三次主要修订,第一次在1985年,第二次是在1989年,第三
次是开始制定ANSI C++标准,第一稿写于1993年1月25日。ANSI C++委员会保留了
Stroustrup定义的所有特性,并另外增加了一些新的特性。
标准化过程相当缓慢,C++标准在最终被采纳之前,可能还要经过多年。但是要记住,
C++仍然在不断改进,一些特性还在修改。但本书介绍的大部份内容已不再修改,它应用
于所有现有的C++编译程序,也符合建议的ANSIC++标准。
162页
在C++发明时,Stroustrop知道维持C的原来的精髓,如效率、灵活性以及程序员而不
是语言所掌握的基础原理是很重要的,同时增加了对“面向对象程序设计”的支持。令人欣慰
的是,他的目标达到了。C++仍然给程序员提供了对C的自由控制以及管理对象的能力。
C++的面向对象的特点,用Stroustrup的话说,就是“使程序结构清晰、易于扩展、易于维护
而不失其效率”。
尽管C++当初的设计本意是帮助管理大型程序,但其用途并不仅限于此。事实上,C+
+的面向对象的特性可有效地用于实际的程序设计工作。C++常常用于设计编辑器、数据
库、个人文件系统以及通讯程序等。而且,由于C++共享C的效率,所以用C++可以构成很
多高性能的系统软件。
11.2面向对象的程序设计是什么
“面向对象的程序设计”(OOP)是一种进行程序设计的新方法。自从计算机发明以来,
程序设计的方法为了适应越来越复杂的程序发生了剧烈的变化。例如,计算机刚发明时,程
序设计是通过计算机的前控制板用二进制机器指令打孔完成的。当程序长度只有几百条指
令时,这种方法是可行的。随着程序的发展,发明了汇编语言,程序员用符号代表机器指令,
能够处理更大更复杂的程序了。随着程序的进一步发展,出现了高级语言,它给程序员提供
了更多的处理复杂性的工具。第一种广泛流行的语言当然是FoRTRAN。虽然FORTRAN
是令人难忘的第一步,但是它不是支持清晰、易懂程序的语言。
六十年代诞生了结构化的程序设计。这是诸如C语言和Pascal语言支持的方法。结构
化语言的应用使得有可能很容易编写复杂性适度的程序。但是,一旦设计达到一定的程度,
即使使用结构化的程序设计方法也会变得无法控制,它的复杂性已经超出了程序员的管理
限度。
考虑这一点,在程序设计发展道路上的每一个里程碑,就创建一种方法使程序员应付日
益增长的复杂性。每前进一步,新方法都吸取了以前方法的优点并加以发展。今天,许多设
计已经接近或达到结构化方法的工作极限。为了解决这个问题,面向对象的程序设计方法便
应运而生。
面向对象的程序设计吸取了结构化程序设计的先进思想,并把它们同几个支持用户用
新方法进行程序设计的有力的概念结合在一起。一般地讲,在用面向对象的方式进行程序设
计时,都先把问题分为由相关部分组成的组,每一部分考虑和组相关的代码和数据,同时,把
这些分组按层次关系组织起来,最后,把这些分组转换为叫做对象的独立单元。
Tag:
编程博客 发表于08:35:23 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-01
C语言的预处理程序和注释 - [C++]
151页
第十章C语言的预处理程序和注释
C或C++程序的源代码中可包括各种编译指令。这些指令称为预处理命令。虽然它们
实际上不是C/C++语言的一部分,但却扩展了C程序设计的环境。本章还将介绍注释。
10.1预处理程序
预处理程序包括下列指令:
#if #include
#ifdef #define
#ifndef #undef
#else #line
#elif #error
#endif #pragma
所有预处理指令都以符号#开头。
每个预处理指令必须单独占用一行。例如
#include<stdio.h> #include
是无效的。
10.2 #define
指令#define定义了一个标识符及一个串。在源程序中每次遇到该标识符时,就用定义
的串替换它。标识符定义为宏名,将替换过程称为宏替换。指令的一般形式为:
#define macro_name char_sequence
注意,该语句没有分号。在标识符和串之间可以有任意个空格,串一旦开始,就只能由一
新行结束。
例如,如希望TRUE取值为1,FALSE取值为0,可说明两个宏名:
#define TRUE 1
#define FALSE 0
这使得在源程序中每次遇到TRUE或FALSE就用0或1代替。下例在屏幕上打印012。
printf("%d%d%d",FALSE,TRUE,TRUE+1);
宏名一旦定义,即可成为其它宏名定义中的一部分。例如,下面代码定义了ONE,TOW
及THREE的值。
152页
#define one 1
#define Two one+one
#define THREE one+two
宏替换仅仅是以串代替相应的字符串。因此,如果希望定义一个标准错误信息,可编写
如下代码:
#define E_MS "standard error on input\n"
/* …* /
printf(E_MS);
编译程序遇到标识符E_MS时,就用串"standrad error on input\n" 替换。对于编译程
序,printf()语句的实际形式如下:
printf("standard error on input\n");
如果在串中含有标识符,则不进行替换。例如:
#define XYZ "this a test"
printf("XYZ");
该程序段不打印"this is a test",而打印“XYZ”。
如果字符序列超过一行,可以在该行末尾用一反斜杠续行。例如:
#define LONG_STRING "this is a very long\
string that is used as an example"
c/c++语言程序普遍使用大写字母定义标识符。这种约定使程序中的宏替换一目了
然。最好是将所有的#define放到文件的开始处或独立的文件中,而不是将它们分散到整个
程序中。
宏替换的最一般用途是定义常量的名字。例如,某一程序定义了一个数组,而它的几个
子程序要访问数组时,不应直接以常量定义数组大小,最好是用#define语句定义它。在需
要用数组大小的地方使用宏名。这样在需要改变数组大小时,只需在改变#define语句后,
再重新编译程序即可。例如:
#define MAX_SIZE 100
/* ……* /
float balance[MAX_SIZE];
/*……* /
for(i=0;i<MAX_SIZE;i++) printf("%f",balance[i]);
因为MAX_SIZE定义了balance数组的大小,因此,如果将来balance的大小需要改变,
则只需改变MAX_SIZE的定义即可。在编译程序时,所有引用MAX_SIZE的地方均自动
得到修改。
10.2.1定义类函数宏
#define命令的另一个有用特性是,宏可以有变元。每次遇到宏名时,与之相联的变元
均由程序中的实际变元来代替。这种宏的形式称为类函数宏。例如:
153页
#include<stdio.h>
#define ABS(a) (a)<0?-(a):(a)
void main(void)
printf("abs of -1 and 1:%d%d",ABS(-1),ABS(1));
当编译该程序时,宏定义中的a将被-1和1代换,包含a的括号确保了每种情形下的正
确替换。例如,去掉括号后,表达式
ABS(10-20)
将被转换成:
10-20<0?-10一20 : 10-20
显然错误了。
用类函数宏代换实函数的一大好处是,宏替换增加了代码的执行速度,因为不存在函数
调用开销。如果类函数宏很大,则增加速度就会由于重复编码而增加了程序长度。
注意:尽管参数化的宏定义很有价值,然而在本章的第二部分,我们将看到C++中有一
种不依赖宏定义的更有效途经,即创建内联代码。
10.3 #error
#error指令强迫编译程序停止编译。它主要用于程序调试。指令的一般形式为:
#error error_message
error_message不用双引号括起来。当遇到#error命令时,错误信息随着编译的其它信
息一同显示。
10.4 #include
指令#include告诉编译程序将另一源文件嵌入带有#include的源文件中,被读入的源
文件必须用双引号或尖括号括起来,例如:
#include "stdio.h"
#include
这两行代码均使c/c++编译程序读入并编译用于处理磁盘文件库的子程序。
将文
Tag:
编程博客 发表于08:12:53 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-01
ANSI C的标准文件I/O - [C++]
134页
第九章ANSI C的标准文件I/O
如第八章所述,C语言不包含任何I/O语句,而所有I/O操作都是通过调用C语言标准
库的函数实现的。这种方法使得C语言的文件系统相当强大和灵活。C语言的I/O系统还
允许数据以内部二进制代码或易于阅读的文本格式方式传送,这样可以很容易地创建满足
各种需要的文件。
本章对ANSI C文件系统及其最常见的函数进行了概述,并集中对这些函数的用法进
行了讨论。
9.1
ANSI C标准定义了一套完整的I/O函数,可用于读写任意类型的数据。相反,Unix C
标准由两个不同的文件系统来处理I/O操作,第一种方法与ANSI C标准定义的方法类似,
通常称这种方法为缓冲文件系统(有时称为格式或高级文件系统),第二种方法为类Unix
文件系统(有时称为非格式或非缓冲文件系统),由老的Unix标准定义。ANSI C标准没有
定义类Unix文件系统,这是因为两个文件系统是冗余的。同时,类Unix文件系统也可能不
适用于某些环境,但这些环境又支持C语言。
同样,ANSI C++标准也不支持类Unix文件系统,但所有C++编译程序支持ANSI C
文件系统。因为类Unix文件系统与C++程序设计无关,且不为ANSIC或ANSI C++标准
所定义,因此本书不予讨论。
9.2 C语言与C++语言的I/O
因为C语言是C++的基础,那么C语言的文件系统与C++有什么关系呢?下面简单地
讨论这个问题。
C++支持全部ANSI C语言的文件系统。所以,如果将来要将C语言代码转换为C++
代码,则不必改变所有的I/O程序。但是,C++也定义了自己的、面向对象的I/O系统,包括
I/O函数和I/O运算符。C++的I/O系统完全包括了ANSIC语言的I/O系统。通常,如果
用C++编写一个面向对象的程序,就可能用到面向对象的I/O系统;否则,就可以自由使用
面向对象的文件系统和ANSI C语言文件系统(但大多数C++程序员选择使用C++的I/O
系统,其原因参见本书第二部分)。
9.3 流和文件
在开始讨论ANSI文件系统之前,了解流和文件这两个概念间的差别是很重要的。C的
135页
输入/输出系统给程序员提供了一个独立于物理设备进行信息存取的友好界面。C的i/o系
统提供的程序员和使用设备之间的一级抽象叫做流,物理设备叫做文件。了解流和文件如何
相互作用是很重要的。
注:流和文件的概念对本书第二部分讨论的C++I/O系统也是非常重要的。
9.3.1流
C文件系统可在包括终端、磁盘驱动器和磁带驱动器的众多设备上工作。不管各种设备
有多大差异,ANSI文件系统都把它们转换成称之为“流”的逻辑设备,且所有的流都与它们
的行为相似。因为流具有极大的设备无关性,所以向一个磁盘文件写操作的同一函数也可以
完成向另一种设备(如控制台)的写操作。有两种类型的流:文本流和二进制流。
9.3.1.1文本流
文本流是由字符组成的序列,ANSI C标准允许(但并不规定)一个文本流以换行字符
终止一行。换行字符是可选的,它是由实现决定的(实际上,大多数C/C++编译器都不需要
换行字符)。在文本流中,特定字符的转换是由主机环境要求的,例如,新行必须以回车/换行
符对作标记。因而,写(或读)的字符与存储在外设中的字符间并无一一对应的关系。同样,
由于可能的转换,写(或读)字符的数量可能与外设中存储的字符不一致。
9.3.1.2二进制流
二进制流是指字节序列,它在外设中的存储是一对一的,也就是说,不存在字符转换。因
此,写(或读)字节的数量与外设中存储的字节一致。然而,标准规定二进制流允许根据实现
情况决定空字节的数目来填充其后。例如,这些空字节有时用来补充信息以填满磁盘某个扇
区。
9.3.2文件
在C语言中,文件这个逻辑概念是用于从磁盘文件到终端或打印机的任何东西。流通
过完成打开操作与某文件联系起来。某文件一旦打开,里面的信息就可以在程序与该文件之
间交换。
并非所有的文件都有此功能。例如,磁盘文件支持随机访问,但终端却不行。这就说明
了C语言中I/O系统的一个重要特性:所有的流都是相同的,但文件却不同。
如果文件支持定位需求,那么打开文件也使文件位置指针初始化到文件头上。当字符从
某个文件读出或写到某文件中时,文件位置指针加1,因此可以遍历整个文件。
文件通过关闭操作与说明流挂钩。为输出而打开的流,在关闭文件时与它相关的流写到
外设中。这一过程通常叫做清仓,它保证了在磁盘缓冲区中不留下任何信息。当程序结束时
所有文件都自动关闭,并通过main()或exit()返回操作系统。如果程序因故障崩溃或调用
abort()而终止,则文件不关闭。
与文件相关的流都有一个类型为FILE的控制结构。该结构在stdio.h中定义,不能对
它进行任何修改操作。
在C语言中,把流和文件分开是没有必要的,只要记住这保持接口的一致性这个原则
即可。在C语言中,程序只需考虑流的作用范围,这样利用一个文件系统便能完成所有的I/
O操作。而C语言的I/O系统自动将原始的输入或输出转换成早期管理的流。
136页
9.4文件系
Tag:
编程博客 发表于08:11:31 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-01
控制台I/O - [C++]
119页
第八章控制台I/O
本章和下一章讨论C语言的I/O系统。在C语言中,输入和输出通过库函数完成。C语
言的I/O系统是非常精细的工程,它提供了在设备之间传送数据的灵活、一致的结构。然而,
C语言系统非常庞大,涉及几个不同的函数。
C语言支持控制台I/O和文件I/O。从技术上讲,控制台I/O和文件I/O间只有微小的
差别。然而,从概念上讲,它们是两个截然不同的范畴。本章详细介绍控制台I/O函数。第
九章将介绍文件I/O系统以及两种系统是如何相互作用的。
本章只讨论ANSIC标准中定义的那些控制台I/O函数。ANSIC标准没有描述不同的
屏幕控制操作(如光标位置)或图象显示函数,因为不同机器的操作变化很大。相反,标准C
控制台I/O函数只完成基于TTY(电传打字机)的输出。大多数编译程序都支持屏幕控制和
图形函数以适应特殊环境(用户可通过查看编译器用户手册,得到有关非标准I/O函数的说
明)。
本章中提到的控制台I/O函数从键盘输入在屏幕输出(普遍的用法都如此)。然而,这些
函数实际上作为标准输入和标准输出I/O操作的目标、源或两者都是。确切地说,标准输入
和标准输出也可重定向到其它设备。这些概念将在第九章中讨论。
8.1一个重要的应用说明
本书的第一部分使用了C语言定义的I/O系统。虽然C++完全支持类CI/O函数,但
C++还是定义了自己的面向对象的I/O系统。因而,如果书写面向对象的程序,就可以使用
C++专用的I/O系统,而不必采用本章说明的ANSICI/O系统。类C的I/O系统出现在本
书中有下面三个原因:
对今后几年来说,C和C++将共存。因而,多数程序将是C和C++代码的复合。不久,
将C语言程序“升级”成C++程序是很普遍的。因此,必须了解C和C++I/O系统的
知识,例如C++I/O。为了把类C的I/O函数改变成相应的面向对象的C++I/O函
数,有必要掌握C和C++I/O系统是如何操作的。
掌握类C的I/O系统基本原理是学习C++面向对象的I/O系统的基础(两者有许多
共同的概念)。
在某些情况下(例如,非常短的程序),使用C语言的非面向对象的I/O方法比面向对
象的I/O方法要容易。
另外,有个默认的原则,就是任何C++程序员也必须是C程序员,因而若不掌握C语
言的I/O系统,就会限制用户的专业水平。
8.2读写字符
最简单的控制台I/O函数是getchar()和putchar()。前者从键盘读一字符,后者把字符

120页
显示在屏幕上。函数getchar()等待敲键并返回其值。通常,getchar()也可以自动“回送”敲入
字符到屏幕上。函数putchar()在屏幕的当前光标位置上显示它的字符变量。
getchar()和putchar()的原型如下:
int getchar(void);
int putchar(int c);
这些函数的首标文件为STDIO.H。如原型所示,getchar()函数返回一个整数值。也可
以指定这个值为char变量,因为低位字节包含一个字符(高位字节通常均为0)。如果有错,
getchar( )返回EOF。
对putchar()而言,虽然putchar()带一个整数参数通常可用一个字符变量调用它,但仅
其低位字节实际被输出到屏幕上。 putchar()函数返回被写入的字符,若操作失败返回EOF
(宏eof定义于stdio.h中,通常其值为-1)。
下面的程序从键盘输入字符串并在屏幕上以与原来不同的形式显示,即把原来的大写
字母打印成小写字母,把原来的小写字母打印成大写字母。当用户敲入一个‘. ’时,该程序结
束。
#include
#include
void main(void)
{
char ch;
printf(“ Enter some text (type a period to quit).\n”);
do{
ch =getchar();
if(islower(ch))ch=toupper(ch);
else ch =tolower(ch);
putchar(ch);
} while(ch!=‘.’);
}
8.2.1 getchar()的有关问题
getchar()有一个潜在的问题。 ANSI C标准定义的getchar()是与C原始的UNIX版本
兼容的,原始的UNIX中,getchar()以回车键终止输入,这是因为原始的UNIX系统的是行
缓冲终止输入的,在键入的字符传送给程序以前都必须敲入一个回车键,因为getchar()每
次调用只输入一个字符,行缓冲器使得一个或多个不期待的字符也随getchar()的返回而留
在了输入流中。尽管标准描述getchar()可以用交互式函数来实现,但很少见。因而,先前的
程序有不期望的情况发生也就不足为奇了。
8.2.2 getchar()的替代
getchar()在一个交互式环境中无法有效地实现。在这种情况下,可以利用其它函数来
完成从键盘中读字符。虽然ANSI C标准没有提供确保交互式输入的其它函数,但实际上所
有C编译器都含有交互式键盘输入函数。这些函数没在ANSI中定义,但当getchar()不能
121页
满足程序员需求时可使用它们。

Tag:
编程博客 发表于08:09:55 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-01
结构、联合、枚举和用户定义的类型 - [C++]
104页
第七章结构、联合、枚举和
用户定义的类型
C语言提供了建立用户数据类型的五种不同方式。
结构:它是在一个名字下的一组变量,有时也称之为聚集数据类型。
位域:它是结构的一种变形,允许对字中的位进行访问。
联合:它可将同一块内存定义成两种或多种不同的变量类型。
枚举:它是一个命名的整数常量列表。
使用typedef:它对一个已存在的类型产生一个新的名字。
7.1结构
结构是用同一个名字引用的变量的集合体,它提供了将相关信息组织在一起的手段。结
构定义形成一个建立结构变量的模板。组成结构的变量称为结构的元素。
一般情况下,结构的所有元素在逻辑上都是相关的。例如在通信录中,姓名和住址通常
就是用结构表示的。下面的一段代码说明了如何建立一个定义了姓名和住址的结构摸板。关
键字struct告诉编译程序已定义了一个结构模板。
struct addr{
char name[30];
char street[100];
char city[20];char state[2];
unsigned long int zip;
};
注意,该定义以分号结束。这是因为一个结构定义就是一个语句。结构标识addr标识了
这个特殊的数据结构,并成为其类型说明符。这个代码段并未真正说明任何变量,它仅仅定
义了数据的形式。要用这个结构定义一个实际的变量addr,必须写成如下形式:
struct addr addr_info;
该语句说明了一个类型为addr的结构变量addr_info。定义结构的本质是定义一个复杂
变量类型而不是变量。只有说明了这种类型的变量,它才真正存在。
注:在C++语言中,定义一个结构以后,只需使用标识符名就可以定义这种类型的变
量,而不必在开头加上关键字struct。例如,为定义一个结构变量addr,可以写成:
addr addr_info;
C与C++之所以不同,原因在于C中的结构标识符名不是一个完整的类型名,而在C+
+中则是一个完整的类型名。但要记住,在C++的程序中使用C的类型定义也是合法的。
定义了结构变量之后(例如addr_info),C/C++编译程序自动为所有变量申请足够的空
间。表7.1示出了addr_info在内存中是如何存放的。这里假定字符占1个字节,长整型占
4个字节。
105页
表7-1addr_info在内存中的结构
Name 30字节 State 3字节
Street 40字节 ZIP 4字节
City 20字节
可以在定义结构的同时,说明一个或多个变量。例如:
struct addr{
char name[30];
char street[40];
char city[20];
char state[3];
unsigned long int zip;
}addr_info,binfo,cinfo;
定义了一个结构类型addr,并说明了这个类型的变量addr_info、binfo、cinfo。如果只需要一
个结构变量,则不必使用结构标识。所以
struct{
char name[30];
char street[40];
char city[20];
char state[3];
unsigned long int zip;
} addr_info;
说明了一个结构变量addr_info,其结构同前。
定义结构的一般形式为:
struct tag{
type member_name;
type member_name;
type member_name;
} structure_variables;
其中,tag或structure_variables可以省略,但不能两个同时省略。
7.1.1引用结构元素
单个结构元素用圆点运算符引用。例如,下面的代码把邮政编码12345赋给前面说明过
的结构变量addr_info中的域zip。
addr_info.zip=12345;
结构变量名后跟一个圆点和元素名即可引用单个结构元素。访问结构元素的一般形式为:
structure_name.element_name
因此,要把邮政编码打印到屏幕上,可以这样写:
printf(“%d”,addr_info.zip);
106页
该语句打印结构变量addr_info中的zip变量中的邮政编码。
与此相似,字符数组addr_info.name可用来调用gets(),如下所示:
gets(addr_info.name);
它传递一个指向元素name起始位置的字符指针。
因为name是一个字符数组,因此,如果要访问addr_info.name的单个字符,则可对
name使用下标。例如,可以用下面的代码一次一个字符地打印addr_info.name的内容:
register int t;
for (t=0; addr_info.name[t];++t)
putchar( addr_info.name[t]);
7.1.2结构赋值
可以把一个结构包含的信息赋给同一类型的另一个结构,也就是说,并非一定要将所有
元素分别赋值,可以用一个赋值语句:
#include
void main(void)
{
struct{
int a;
int b;
}X,y;
x.a=10;
y=x; / * assign one structure to another * /
printf(“%d”,y.a);
}
赋值后,y.a的值是10。
7.2结构数组
结构最常见的用途之一是用在
Tag:
编程博客 发表于08:06:23 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-01
函数 - [C++]
87页
第六章函数
函数是C语言的基本构件,是所有程序活动的舞台。这是C语言最重要的特征之一。
6.1 函数的一般形式
函数的一般形式是:
type_specifier functiOn_name(parameter list)
{
bOdy of the function
}
类型说明符规定了函数返回的数据的类型,函数可以返回除数组以外的任何类型数据。
如果没有类型说明符出现,则编译程序假定函数返回一个整型值。参数表是用逗号分隔的变
量表,各变量表由变量类型和变量名组成。当函数被调用时,变量根据该类型接收变元的值。
一个函数可以没有参数,这时参数表为空。但即使没有参数,括号仍然是必需的。
一般的变量说明可以将多个变量用逗号隔开,列在一个公共类型说明符后面说明成同
一类型。而函数参数却必须单独定义,每个函数参数必须同时具有类型说明符和参数名。参
数说明的一般形式是:
f(type varname1, type varname2,..., type varnameN)
例如,下面的两个函数参数说明,一个是正确的,一个是不正确的:
f(int i, int k, float j);/* correct */
f(int i,k,float j); /* incorrect */
6.2 函数作用域的规则
“一种语言的作用域规则”是一组确定一段代码是否知道或者可访问另一段代码或数据
的规则。
每一个函数都是一个独立的代码块。一个函数的代码是属该函数专有的,除调用这个函
数的语句之外,任何其它函数中的任何语句都不能访问这些代码(例如,用goto语句跳转到
另一个函数内部是不可能的)。构成一个函数体的代码对程序的其它部分是隐藏的,除非它
使用了全局变量或数据,它既不能影响程序的其它部分,也不受程序的其它部分的影响。换
句话说,由于两个函数有不同的作用域,定义在一个函数内的代码和数据不能与定义在另一
个函数内的代码或数据相互作用。
在函数内部定义的变量称为局部变量。局部变量随着函数的运行而生成,随着函数的退
出而消亡。因此局部变量不能在两次函数调用之间保持其值。只有一个例外,就是用存储类
88页
型符static说明时,才能使编译程序在存储管理方面象对待全局变量那样对待它,但其作用
域仍然被限制在该函数的内部(第二章“表达式”详细讨论了全局变量和局部变量)。
C/C++语言中的所有函数的作用域都处在同一级别上。这就是说,把一个函数定义在
另一个函数内部是不可能的。这也是为什么C或是C++语言从理论上讲不是块结构型语言
的原因。
6.3函数变元
如果一个函数要使用变元,就必须说明接受变元值的变量。这些变量被称作函数的形式
参数,它们就象函数的其它局部变量那样活动,随着函数进入而建立,随着函数退出而消亡。
如下面函数所示,形式参数的说明应位于函数名之后:
/ * return 1 if c is part of string s; 0 otherwise */
is_in(char * s, char c)
{
while(*s)
if(*s==c) return 1;
else s++;
return 0;
}
函数is_in()有两个参数:s和c。如果字符。在串s中,函数返回1,否则返回0。
就象使用局部变量那样,可以给函数的形式参数赋值,或者把它们用在任何合法的表达
式中。即使这些变量执行接受传给函数的变元的值的特殊任务,用户也可以象使用局部变量
那样使用它们。
6.3.1传值调用与引用调用
一般说来,有两种方法可以把变元传给子程序。第一种方法叫“传值调用”(call by
value)。这种方法是把变元的值复制到子程序的形式参数中,所以子程序中参数的任何改变
都不会影响变元。
“引用调用”(call by reference)是把变元传给子程序的第二种方法。这种方法是把变元
的地址复制给形式参数。在子程序中,这个地址用来访问调用中所使用的实际变元。这意味
着,参数的变化会影响调用时所使用的变元。
C语言使用传值调用来传递变元。一般来说,这意味着函数的代码不能改变调用函数时
使用的变元。看下面程序:
#include<stdio.h>
int sqr(int x);
void main(void)
{
int t=10;
printf(“%d%d”,sqr(t),t);
89页
}
sqr(int x)
{
x=x*x;
return(X);
}
在这个例子中,传递给函数sqr()的变元值10是复制给参数x的。当执行赋值语句x=
x*x时,被修改的仅仅是局部变量x。用于调用sqr()函数的变量t的值仍然是10。因此,输
出为100 10。
记住,传给函数的只是变元值的复本。所有发生在函数内的变化都不会影响调用函数时
使用的变量。
6.3.2引用调用的建立
即使C语言的常规参数传递方法是传值调用,用户仍然能够通过将指针传给变元而不
是传递变元自身的方法来实现引用调用。这样变元地址就被传给函数,因而函数内部的代码
就可以改变函数外部的变元值了。
指针可以象其它值那样向函数传递。当然必须把形式参数说明为指针类型。例如,下面
的用来交换两个整数变元值的swap()函数,就说明了这一点:
void swap(int * x, int *y)
{
int temp;
temp=* x
Tag:
编程博客 发表于08:01:01 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-01
指针 - [C++]
73页
第五章指针
正确地理解和使用指针对于成功地进行C语言(及C++)程序设计是至关重要的。其
理由有三个:第一,指针为函数提供了修改其调用变元的方法;第二,指针的使用为C语言
中动态分配例程提供了支持;第三,使用指针可以提高某些程序的效率。在第二部分可以看
到,C++中,指针还起着别的重要作用。
指针作为C语言之最强特征的同时,又是C语言最危险的特征。例如,未初始化的指针
(或含非法值的指针)常常导致系统的崩溃。此外,指针极易被错用,而这类错误将会产生难
以发现的程序故障。
由于指针非常有用又容易被滥用,所以本章详细地讨论了有关指针的问题。
5.1 指针是什么
指针是包含内存地址的变量,该地址是内存中另一个对象(其它变量)的存储位置。若一
个变量包含的是另一个变量的地址,那么就叫第一个变量指向第二个变量。如图5-1所示。
图5-1一个变量指向另一个变量
5.2 指针变量
要使一个变量成为指针,就必须对其如此说明,即一个指针说明包括一个基类型,一个
*及变量名。说明指针变量的一般形式为:
type * name;
其中,type(类型)是C语言的任何一种有效类型,name(名字)是指针变量的名称。
指针的基类型定义了该指针所指向变量的类型。从技术上讲,任何类型的指针均可指向
内存中的任何位置,但因所有的指针运算都是与它的基类型相关联的,所以正确说明指针非
常重要。
74页
5.3指针运算符
有两个特殊的指针运算符:&和*。&是一元运算符,功能是返回其操作数的内存地
址(记住:一元运算符仅需一个操作数)。例如:
m=&count;
将变量count的内存地址存入m。这个地址是变量在机器内部的存储单元,与count的值无
关。记住,指针运算符&返回的是紧跟其后的变量的地址。因此,前面的赋值语句可以解释
为“m接收了count的地址”。
为了更好地理解内存地址的赋值,假设变量count在2000号内存单元存储其值,且
count的值为100,那么,前面的赋值语句执行后,m的值为2000。
另一个操作符*与&正相反,它作为一元运算符,返回的是存放在紧跟其后的地址中
变量的值。例如,m中存放的是变量count的内存地址,那么:
q=*m;
将把count的值存入q中。因为100是存放在2000号内存单元中的数,而2000即是m中的
内存地址,所以q的值成为100。记住,*的运算可以理解为“间接寻址”,这样就可以把上面
的赋值语句解释为“q得到存放在地址m处的值”。
由于字符*既是乘号又是间接寻址运算符,而字符&既是逻辑与算符又是求地址运
算符,所以它们常常令初学者感到迷惑。但是这些指针运算符与算术运算符之间并无关系。
&和*这两个指针运算符的优先级比所有的算术运算符都要高,但有一个例外,那就是一
元减法,其优先级与&和*是相等的。
必须保证指针变量所指向的数据类型是正确的。例如,当把一个指针说明为int类型
后,编译程序便假定它存放的任何地址都指向一个整型变量而不管它是否真是如此。因为C
语言允许将任一地址赋给指针变量,所以下面的程序段在编译时不会有任何错误信息(或仅
有警告信息,这取决于使用的编译程序),但并不产生期望的结果:
void main (void )
float x,y;
Int*p;
/*The next statement causes p(which is an integer pointer)to point to a float.*/
p=&x;
/*The next statement does not operate as expected.*/
y=*p;
}
x的值不会赋给Y因为p被说明为整型指针,故只有2个字节的信息传递给Y而不是
通常构成浮点数的8个字节。
注:在C++中,不经过显式类型转换,将一种指针类型转换成另外一种指针类型是非法
的。因此,如果即使试图将前面的程序作为C++(而不是C程序进行编译,编译也不能通
75页
过。然而,所述错误类型依然在C++中以更间接的方式出现。
5.4指针表达式
一般来说,含有指针的表达式应遵循C语言的表达式规则。在这一节里,我们将讨论指
针表达式的几个特殊方面
5.4.1指针赋值
与任何变量相同,指针也可以出现在赋值语句的右端,将它的值赋给另一指针。例如,
#include
void main(void)
{
int x;
int *p1,*p2;
p1=&x;
p2=p1;
printf("%p",p2);/*print the address of x,not x’s value! */
}
p1和p2都指向X通过使用%p这个printf()的格式修饰符显示x的地址,该修饰符使
printf()按主机使用的格式显示一个地址。
5.4.2指针运算
指针只有两种算术运算符:加和减。为了理解指针运算的过程,假设p1是一个整型指
针,当前值为2000,同时假定整数是2字节的,那么
p1++;
运算后,p1的值变为2002,而不是2001!因为p1每递增一次,就将指向下一个整数,递减也
是一样。例如,假定p1的值为2000,那么
p1--;
将使p1的值为1998。
总结上面的例子,可以归纳出指针运算的规则。指针每递增一次,就指向下一个基类型
元素的内存单元;指针每递减一次,就指向上一个元素的内存单元。在使用字符指针
Tag:
编程博客 发表于08:00:00 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-01
第四章数组和字符串 - [C++]
58页
第四章数组和字符串
数组是一个由若干同类型变量组成的集合,引用这些变量时可用同一名字。数组中特定
的元素通过下标访问。在C语言中,所有数组均由连续的存储单元组成,最低地址对应于数
组的第一个元素,最高地址对应于数组的最后一个元素。数组可以是一维的,也可以是多维
的。C语言中最常用的数组是字符串,它只是以空字符结尾的字符数组。字符串的这种实现
方法使C语言具有比其它语言更强的功能和更高的效率。
在C语言中,数组和指针是密切相关的,讨论其中之一往往牵涉到引用另外一个。本章
重点讨论数组,第五章将详细讨论指针。只有读完了这两章,才能透彻地理解C语言的这些
构成。
4.1 一维数组
一维数组的一般说明形式为:
type var_name[size];
如同说明其它变量一样,数组必须显示地说明,以便编译程序为它们分配内存空间。在
上式中,type说明了数组的基本类型,即数组中每个元素的类型,size定义了数组元素的个
数。例如,为了说明一个类型为double的具有100个元素的数组balance,可使用如下语句:
double balance[100];
在C语言中,所有的数组均以零作为第一个元素的下标。因此,如下语句:
char p[10];
说明了一个包含从p[0]到p[9]10个元素的字符数组。下列程序将数字0到99装入一个整型数
组:
void main (void)

int x[100];/* this reserves 100 integer elements */
int t;
for(t=0;t<100; ++t)x[t]=t;
}
容纳一个数组所要求的存储空间的大小直接与它的类型及规模有关。一维数组的总字
节数按如下公式计算:
总字节数=sizeof(类型)* 数组长度
C语言并不检查数组的边界,因此,数组的两端都有可能越界而使其它变量甚至程序代
码被破坏。在需要的时侯,进行数组的边界检查是程序员的职责。例如,下列代码可以正确编
59页
译,但因for循环导致数组count越界,所以是不正确的。
int count[10],i;
/ * this causes count to be overrun */
for(i=0;i<100; i++) count[i]=i;
一维数组在本质上是由同类信息构成的表,它们按下标顺序存放在连续存储单元中。例
如,图4-1是数组3在内存中的情况。假设数组a说明如下,且从内存地址1000开始:
char a[7]
图4-1含7个元素、从内存地址1000开始的字符数组
4.2 产生指向数组的指针
产生一个指向数组的第一个元素的指针,仅需指定它的名称,而不用下标。例如,给定
int sample[10];
可以使用名字sample产生一个指向第一个元素的指针。例如,下面的程序段把sample
的第一个元素的地址赋给p:
int*p;
int sample[10];
p=sample;
使用运算符&也可以指明一个数组的第一个元素的地址。例如,sample和&sample[0]
产生同样的结果。但在专业人员编写的C/C++代码中,几乎见不到象&sample[0]这种用
法。
4.3 向函数传递一维数组
在C语言中,不能把整个数组作为变元传给函数,但可以通过指明不带下标的数组名
向函数传递一个指向数组的指针。例如,下列程序段把i的地址传给funcl():
void main (void)

int i[10];
func1(i);
}
函数若要接收一维数组,可用下面三种方法之一来说明形式参数:(1)指针;(2)有界数
60页
组;(3)无界数组。例如,为接收i,可以这样说明函数func1():
void func1(int * x) / * pointer*/
{
.
.
.
}
或者
void func1(int x[10])/ * sized array */
{
.
.
.
}
也可为:
void func1(int x[])/* unsized array */
{
.
.
.
}
这三种说明方法的产生的效果是相同的,它们都通知编译程序将接收一个整型指针。在
第一种说明中,确实使用了指针。在第二种说明中,使用的是标准的数组说明。在最后一种说
明中,使用了改进型的数组说明,它只说明了函数将要接收一个具有一定长度的整型数组。
读者会发现,就函数而言,数组究竟有多长并不重要,因为C语言并不进行数组的边界检
验。实际上,就编译程序而言,下列程序也是可行的。
void func1(int x[32])

.
.
.
}
因为C语言编译程序只产生命令函数func1()接收一个指针的代码,并不真正产生一
个包含32个元素的数组。
4.4字符串
显然,一维数组的最普通的用法是作为字符串。在C语言中,字符串被定义为一个以空
字符终结的字符数组。空字符被定义为‘\0’并且为0。因此,在说明字符数组时,必须比它
要存放的最长字符串多一个字符。例如,假定要定义一个存放长度为10的字符串数组str,可
以写成:
char s[11];
这样就给字符串末尾的空字符保留了空间。
61页
尽管C语言没有字符串数据类型,但却允许使用字符串常量。字符串常量是由双引号
括起来的字符表。例如:
Tag:
编程博客 发表于07:59:01 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-01
C语言的语句(2) - [C++]
3.3.2 fOr循环的变化形式
前面已对for循环的一般形式进行了讨论,然而C语言还提供了for循环语句的许多
变化形式,它们可以增强循环的效率、灵活性及对某些程序环境的适用性。
for循环语句的一个最常见的变化形式是,通过使用逗号运算符,允许两个或两个以上
的变量控制循环(见第二章“表达式”。逗号运算符可以将许多表达式连接成串,类似于“do-
this-and-this方式)。例如,在下面这个循环中,x和y都是循环变量,并都在for语句中被初
始化:
for(x=0,y=0; x+y<10; ++x){
y=getchar( );
y=y-‘0’;/* substract the ASCII code for 0
from y */

逗号将两个初始化语句隔开。每次循环重复,x增大,y的值由键盘输入。x和y必须取
正确值才能使循环终止。尽管y的值是由键盘输入的,但是y必须被初始化为0,这样做使它
的值在条件表达式第一次计算之前得到定义。如果y未定义,y的值可能为10(随机或是前
面的程序已经用到过),从而使测试的条件为假,导致循环不能进行。
在下面的这个例子中,函数converge()给出了多个循环控制变量。converge()函数是显
示一个字符串,方法是从屏幕两端同时显示字符,在某一指定行的中央汇合。这就要求能将
光标定位在屏幕上各个间断的点上。由于C/C++运行的环境很广,所以没有定义光标位置
47页
函数。但实际上,所有C/C++的编译程序都提供了这样的函数,只是函数的名称可能不一
样。下面这个程序使用Borland的gotoxy()函数来设置光标位置(它需要首标文件CONIO.
H)。
/ * Borland version */
#include<stdio.h>
#include
#include<string.h>
void converge(int line, char * message);
void main (void )

converge(10,“This is a test of converge()·”);

/ * This function displays a string starting at the left
side of the specified line。 It writes characters
from both the ends converging at the middle。
*/
void converge(int line, char * message)
{
int i,j;
for(i=1,j=strlen(message); i<j; i++,j--){
gotoxy(i,line); printf(“%c”, message[i-1];
gotoxy(j,line); printf(“%c”, message[j-1];


Microsoft的与gotoxy()等价的函数是,settextposition(),首标文件为graph.h。上面的
程序用Microsoft C/C++语言改写为:
/ * Microsoft version.*/
#include<stdio.h>
#include<graph.h>
#include<string.h>
void converge(int line,char * message);
void main (void)

converge(10, “This is a test of converge().”);

/*This function displays a string starting at the left
side of the specified line, It writes characters
from both the ends converging at the middle。*/
void converge(int line, char * message)

int i,j;
for(i=1,j=strlen(message);i<j; i++,j--){
_settextposition(line,1);
48页
printf(“%c”, message[i-1]);
_settextposition (line,j);
printf(“%c”, message[j-1]);

}
如果用的是不同的C/C++编译程序,则应查找用户手册,找出光标设置函数。
在两个版本的converge()函数中, for ,&循环使用循环控制变量i和j从两个相反的方向
来索引字符串。随着不断的循环,i增加,j减小。当i等于或大于j时,循环终止,这样,就保
证了所有字符都写在了屏幕上。
条件表达式并不一定非要以某一目标值对循环控制变量进行测试。事实上,条件可以是
任何关系或逻辑语句,这意味着可以使用几种终止条件进行判断。
例如,可以用下面的函数向远程系统注册。使用者有三次机会输入口令,如果三次输入
的口令均不正确,或者输入正确了,那么循环就结束。
void sign_on(void)

char str[20];
int x;
for(x=0; x<3 &&strcmp(str,“password”);++x){
printf(“enter password please:”);
gets(str);

if(x=23)return;
/* else log user in…*/
}
这里使用的strcmp()函数是一个标准库函数,它对两个串进行比较,如果这两个串匹
配,则返回0。
·切记,for循环语句的三部分中的每一部分,都可以由任何有效的C表达式组成。实际
上这些表达式可能与for 循环中的三部分的形式无关。带着这个问题考虑下面的例子。
#include <stdio.h>
int sqrnum (int num);
int readnum(void);
int prompt (void);
void main (void)
{
int t;
Tag:
编程博客 发表于07:58:18 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-01
C语言的语句(1) - [C++]
本章讨论C语言的语句。在大多数情况下,语句是程序中可执行的部分。也就是说,一条
语句表示一个行为。ANSI C 语言标准(建议的ANSI C++语言标准)将语句分为以下几种:
(1)选择 (4)标号
(2)迭代 (5)表达式
(3)转移 (6)块
选择语句包括if和switch(通常,“选择语句”也叫做“条件语句”。但本书根据ANSI标
准使用“选择”一词)。迭代语句包括while、for和do-while,通常又叫做循环语句。转移语句
有break、continue、goto和return。标号语句包括case和default(与switch语句一起讨论)
以及标号语句(与goto一起讨论)。表达式语句是构成一个有效的C语言表达式的语句。块
语句简单地说就是代码块(注意,块是以{开始,并以}结束的)。建议的ANSI C++标准也将
块语句定义为复合语句。
注:C++增加了另外两种语句类型:try块和说明语句。这两种语句将在第二部分讨论。
很多语句依赖于一些条件测试的结果,因此我们下面从温习真值和假值的概念开始。
3.1 C语言的真值和假值
C语言的许多语句都是根据条件表达式来决定程序的进一步执行。条件表达式的取值
为真或假。和许多别的计算机语言不同,C语言没有给真和假定义一个确切的数值,而是把
包括负数在内的任何非零值指定为真值,零为假值。这种处理真值和假值的方法可以使大量
的程序获得极为高效的编码。
注:虽然建议的ANSIC++标准定义了一种布尔数据类型bool(只有真值和假值),但C
++仍遵从与C语言相同的关于真值和假值的概念。
3.2 选择语句
C语言支持两种类型的选择语句:if和switch。另外,在某些特定情况下,操作符?是if的
另一种表示形式。
3.2.1 if语句
if语句的一般形式是:
if(expression)statement;
else statement;
根据C语言的语法,语句项可以是一个单独语句、一个语句块或什么都没有(空语句的
情况)。else从句是可选的。
37页
如果if表达式取真值(除0以外的任何值),则执行if的语句或语句块,否则,如果else
存在的话,就执行else的语句或语句块。切记,每次或是执行与if相关的代码,或是执行与
else相关的代码,但绝对不会两者同时执行。
根据ANSI标准,控制if的条件表达式必须给出一个标量结果。标量可以是整数、字
符、浮点或者指针类型。但在控制条件语句的时候很少使用浮点值,因为它会减慢运行速度。
cpu需执行若干条指令才能完成浮点操作,而对于整数或字符操作,所需指令相对来说就
少多了。
下面是关于if的示例程序。该程序是一个简单的“猜幻数”游戏。在游戏者猜对时,打印
“**Right**”。程序中运用C语言的随机数产生函数rand()来生成幻数,它是0到
32767之间的任意数。rand()需要STDLIB.H首标文件。
/ * Magic number program # 1.*/
#include
#include
void main(void)
{
int magic;/ * magic number*/
int guess;/ * user’ s guess */
magic = rand() ; / * generate the magic number */
printf(“guess the magic number:”);
scanf(“%d”,&guess);
if(guess==magic) printf(“* * Right * *”);
}
进一步改写上面的游戏程序,使用else语句,使得在游戏者猜错了数字时,也能够打印
出信息。
/ * Magic number program # 2. */
#include<stdio.h>
#include
void main (void)
{
int magic;/ * magic number */
int guess;/ * user’ s guess */
magic=rand(); / * generate the magic number * /
printf(“guess the magic number :”);
scanf(“%d”,&guess);
if(guess==magic) printf(“* *Right* *”);
else printf(“Wrong”);
}
38页
3.2.2嵌套的if语句
一个嵌套if语句或者是另一个if的实体,或者是另一个else的实体。在程序设计中,嵌
套的if语句是很常见的。注意,在C语言中,else语句是与在同一块内跟它最近且无else的
if语句相结合的。例如:
if(i){
if(j) statement 1;
if(k) statement 2; / * this if */
else statement 3; / * is associated with this else */
}
else statement 4; / * associated with if(i)*/
正如注释说明的,最后一个else并不与if(j)关联,这是因为它们不在同一个块内。相
反,最后一个else是与if(i)关联的。同时,因为if(k)是离它最近的一个if,所以块内的else
是和if(k)关联的。
ANSI标准规定,编译程序至少要支持15层嵌套,而实际上往往比这还要多。更重要的
是,ANSI C++标准建议C++程序至少支持256级嵌套。但很少需要级数过多的嵌套,过多
使用嵌套会混淆算法的实际意义。
使用嵌套if语句还可以进一步改进幻数游戏程序
Tag:
编程博客 发表于07:57:42 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-01
表达式(2) - [C++]
注意,不要把字符和串混为一谈。单个字符常量是由单引号括起来的,如‘a’而“a”却是
由一个字符组成的串常量。
2.8.3 反斜线字符常量
大部分打印字符都可以作为字符常量放在单引号中,但个别字符例外,例如,回车不可
能从键盘输入。因此,C语言设置了特别的反斜线字符常量。
C语言提供了几种特别的反斜线代码(见表2-2),以便能输入这些特殊字符。使用反斜
线代码代替对应的ASCII码也确保了程序的可移植性。
例如,下面的程序运行结果是:在新的一行横向跳格,然后输出串“this is a test”。
#include <stdio.h>
void main(void)
{
printf(“\n\t This is a test”);
return 0;
}
表2-2反斜线代码
代码 含义
\b 退格
\f 格式馈给
\n 换行
\r 回车
\t 横向跳格
\" 双引号
\’ 单引号
\0 空值
\\ 反斜线
\v 竖向跳格
\a 报警(计算机响铃)
\n 八进制常量(其中N是八进制常量)
\xN 十六进制常量(其中N是十六进制常量)
2.9 运算符
C语言的内部运算符很丰富,并且比其它计算机语言发挥的作用更大。C语言有三大类
运算符:算术、关系与逻辑、位操作。另外,还有一些特殊的运算符用于完成特殊的任务。
2.9.1 赋值运算符
在C语言中,用户可以在任何有效的C表达式中使用赋值运算符。大多数计算机语言
(如pascal、BASIC和FORTRAN)都不这样,它们将赋值处理为一种特殊情形的语句。赋值
的一般形式为:
22页
variable-name=expression;
表达式可以是最简单的单个常量,也可以是复杂的变量。象BASIC和FORTRAN一
样,C语言用单个等号表示赋值,而不采用Pascal和Modula-2中的“:=”形式。其中,目标即
左部必须是变量或指针,而不能是函数或常量。
通常,在C文献中,读者可在编译错误信息中看到两个概念:左值(lvalue)和右值
(rvalue)。简单地说,左值是任何出现于赋值语句左边的对象,通常称为“变量”,右值是任何
出现于赋值语句右边的表达式,即表达式的值。
2.9.2 赋值中的类型转换
将一种类型变量和另一种类型变量混在一起使用时,会导致类型转换。当赋值语句中出
现类型转换时,赋值语句右边的值(表达式一侧)被转换成左边(目的变量)的类型:
int x;
char ch;
float f;
void func(void)
{
ch=x; /* line=1 */
X=f; /*line=2 */
f=ch; /* line=3 */
f=x; /*line=4 */
}
第一行中整数变量x左边的高位码被删除,将低8位送给ch。如果x是256与0之间的数,那
么ch与x的值相同,否则ch的值仅取x的低字节。第二行中,x接受f的整数部分。第三行,
f将ch的8位整数值转换为等值的浮点数。第四行做相同的转换,但转换的对象是整数。
在进行整数到字符、长整数到整数和整数到短整数的转换时,基本规则是将相应的高位
字移去。这意味着,由整数转换成字符时将丢掉8位,由长整数转换成整数时将丢掉16位。
表2-3绘出了这些类型转换的简要说明。切记,类型转换有如下两个重点。
表2-3类型转换的结果(假定字长为16位)
目标类型 表达式类型 可能丢失的信息
signed char char 如值大于127则作为负数处理
char short int 高8位
char int 高8位
char long int 高24位
int long int 高16位
int float 小数部分和可能更大部分
float double 按精度截断结果
double long double 按精度截断结果
(1) int到float或float到double等的转换不增加精度或准确度,这些转换种类仅改变
23页
值的表示形式。
(2)有些编译程序(如处理器)在将char变量转换为integer或float时,不管char变量值
的大小如何,均作为正数处理。而另一些编译程序在进行转换时,若char变量值大于127,则
作为负数处理。
一般说来,应将char用于字符,但在需要的场合也使用int、short int或signed char int,
以避免移植时出现问题。
采用逐次转换的方法可得出表2-3中未直接给出的转换。例如,从double转换成int,首
先要从double到float,然后再从float到int。
2.9.3 多重赋值
C语言允许在一个语句中用多重赋值运算符给多个变量赋与同一值。例如,下面对x,
y,z同时赋值0:
x=y=z=0;
通常用这种形式给具有共用值的变量赋值。
2
Tag:
编程博客 发表于07:56:00 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-01
表达式(1) - [C++]
本章介绍C语言的最基本要素——表达式。读者将发现,C语言拥有比其它语言更普
遍、更高效的表达式。表达式由C语言的原子要素数据和运算符构成,数据可以是变量或常
量。与大多数其它语言一样,C语言支持不同的数据类型。同时,C语言还提供多种运算符。
2.1 五种基本数据类型
C语言有五种基本数据类型:字符、整型、浮点、双精度浮点和无值,分别用char、int、
fLoat、doublE和void来表示。读者将看到,C语言中的其它数据类型都是从这些基本类型演
变而来的。这些数据的长度和范围因处理器的类型和C语言编译程序的实现而异。一般来
讲,一个字符为1个字节,一个整数为2个字节,但不能肯定,用户的程序在移植时不出现字
节溢出之类的现象。 ANSI C强调的是每种数据类型的最小范围,而不是字节长度。
注:C语言定义了五种数据类型C++又增加了两种:bool 和 wchar_t。这两种数据类型
将在本书的第二部分介绍。
浮点值的准确范围取决于它们是如何实现的。通常,整型对应于宿主机中一个字的长
度。char,类型值通常用于容纳由ASCII字符集定义的值。字符地集以外的值可由不同的编
译程序处理。
float和double类型的取值范围依浮点数的表示方法而定。然而,不管用什么表示方法,
数值都相当大。ANSI标准描述浮点值的最小范围是le-37~le+37。浮点型精度的最小位
数见表2-1。
注:建议的ANSI c++标准没有定义基本数据类型的最小范围和大小,而只要求数据
满足一定的要求。例如,int通常具有机器提供的自然长度。所有的c++编译程序均满足
ANSI C语言标准规定的最范围。c++编译程序在首标文件中定义基本数据类
型的大小和范围。
类型void有两个用处,它解释说明为无值返回或产生一般指针的函数。这在以后章节
中会讲到。
2.2修饰基本类型
除void类型外,基本类型的前面都可以有各种修饰符。修饰符用来改变基本类型的意
义,以便更准确地适应各种情况的需求。修饰符有:
signed (有符号) long (长型符)
unsigned (无符号) short (短型符)
修饰符signed、Short、long和unsigned适用于整数类型,修饰符signed、unsigned适用于
字符类型。其中,long还适用于double类型。
9页
表2-1给出了所有根据ANSI标准的类型组合、近似位长和最小范围(这些值也适用于
C++类型定义)。
表2-1 ANSI标准一的数据类型定义
类型 近似长度(bit) 最小范围
char(字符型) 8 一127~127
unsigned char(无符号字符型) 8 0~255
signed char(有符号字符型) 8 -127~127
int(整型) 16 -32767~32767
unsigned int(无符号整型) 16 0~65535
signed int(有符号整型) 16 同int
short int(短整型) 16 同int
unsigned short int(无符号短整型)16 0~65535
signed short int(有符号短整型)16 同int
long int(长整型) 32 -2147483647~2147483647
signed long int(有符号长整型) 32 同long int
unsigned long int(无符号长整型)32 0~4294967295
float(浮点型) 32 6位有效数字
double(双精度型) 64 10位有效数字
long double(长双精度型) 128 10位有效数字
signed对整型的修饰是多余的,但仍允许使用,因为整数的缺省定义是有符号数。signed
最重要的用途是,在char被缺省定义为unsigned的应用中修饰char型。
有符号整数和无符号整数的区别是对整数最高位的解释。若指定一个有符号整数,那么
C编译程序生成代码时将整数最高位作为符号标志。若符号标志是0,则数值为正;若符号
标志是1,则数值为负。
通常,负数采用2的补码形式,即对数的各位(符号标志除外)求反,若对该数加1,则置
符号标志为1。
有符号整数在大量的算法中是很重要的,但其绝对值仅为无符号数值的一半。例如,
32767:
01111111 11111111
如果高位设置为1,数值将变为-32767。但是,对于无符号整数,高位置1时,数值将变
为65535。
2.3标识符命名
在C/C++中,标识符是对变量、函数、标号和其它各种用户自定义对象的命名。标识符
的长度可以是一个或多个字符。标识符的第一个字符必须是字母或下划线,随后的字符必须
是字母、数字或下划线。这里列举一些正确的和错误的标识符命名实例。
10页
正确形式 错误形式
count 1count
text23
Tag:
编程博客 发表于07:55:13 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-01
C++语言基础 - [C++]
1页 第一部分C++语言基础 C语言
本部分讨论了C++的类C特性。读者也许知道,C++建立在C语言的基础上。在发
明C++语言时,就是以C语言为出发点的。在C语言的基础上增加了一些新的特性,并对
其进行扩展以支持面向对象的程序设计。但C++的类C特性永远不会被放弃。
在现在的形式中,C++是ANSI标准C语言的增强版。事实上,ANSI C语言的标准是
建议的ANSI C++标准的基础性文件,因此任何C++的编译程序从定义上讲也是C语言的
编译程序。由于C++建立在C语言的基础上,所以如果不会用C语言的编程,便不能用C+
+编程。构成C语言基础的许多基本概念也构成了C++基础。由于C++是C语言的扩展,
因而本部分的一切描述也都适于C++。C++的语言特性将在本书的第二部分详述。C++的
类C特性专门占用一部分内容,这是为了使具有丰富C语言编程经验的读者能够方便、迅
速地找到C++的特征信息,而不必重复涉足他们早已掌握的信息。通过第一部分,可以注意
到C++和C语言的细微区别。
注意,本书第一部分摘引了《最新C语言精华(第三版)》(电子工业出版社1997年2月
出版)的部分内容,如果读者对C语言特别感兴趣,那么阅读这本书将会很有帮助。它全面
介绍了ANSI C标准和标准C库函数,此外,还有许多C语言的应用实例。
第一章 C语言回顾
本章的目的是介绍C语言的概貌、起源、应用情况及构成原理。由于C++是建立在C
语言之上的,因此本章也介绍了C++产生的历史背景。
1.1 C语言的起源
C语言是由Dennis Ritchie发明并首先在配备UNIX操作系统的DEC PDP-11计算机
上实现的。C语言是一种比较古老的语言BCPL发展过程中的产物。 BCPL是由Martin
Richards开发的,它影响了由KenThompson发明的B语言,而B语言又导致了C语言在70
年代的发展。
多年来,UNIX操作系统上配备的C语言一直被作为C语言的公认标准(见Brian
Kernighan和Dennis Ritchie的《程序设计语言C》,Englewood Cliffs,N.J.: Prentice-Hall,
1978)。随着微型机的发展,出现了一大批C语言系统。但令人惊异的是,大部分C语言系统
高度兼容(也就是说,在一个系统上编写的程序可以在另一个系统上编译成功)。由于当时不
存在统一的标准,因而不同的实现系统存在着差异和不相容。为了改变这种局面,ANSI在
1983年初夏成立了一个委员会以制订一劳永逸的C语言标准。标准化的工作进行了六年
(比我们预想的时间要长得多)。ANSI C语言标准在1989年12月被采纳,于1990年初开始
使用。现在,主流C/C++编译程序都遵守ANSI C语言标准。并且,ANSI C语言标准是建
2页
议的ANSI C++语言标准的基础性文件。
1.2 C语言是中级语言
C语言通常被称为中级计算机语言。这并不意味着它的功能差,难以使用,或者比BA-
SIC、Pascal那样的高级语言原始,也不意味着它象汇编语言那样,会给使用者带来类似的麻
烦。C语言之所以被称为中级语言,是因为它把高级语言的成分同汇编语言的功能结合起来
了。表1-1示出了C在计算机语言中所处的地位。
表1-1 C在计算机语言中的地位
高级 Ada
Modula-2
Pascal
COBOL
FORTrAN
BASIC
中级 C++
C
FOKTH
低级 Macro-assembler
Assembler
作为中级语言,C支持对位、字节和地址这些有关计算机功能的基本成分进行操作。C
语言非常容易移植。可移植性表现为可将某种计算机写的软件改编到另一种机器上实现。举
例来说,如果在DOS环境下编写的程序可以很方便地修改并在windows,环境下运行,则这
个程序是可移植的。
所有的高级语言都支持数据类型的概念。一个数据类型定义了一个变量的取值范围和
可在其上操作的一组运算。常见的数据类型是整型、字符型和实型。虽然C语言有五种基本
数据类型,但与Pascal和Ada 相比,它算不上强类型语言。C语言支持几乎所有的类型转换。
例如,字符型和整型数据能够自由地混合在大多数表达式中。
与高级语言不一样的是,C语言不支持诸如数组越界等运行错误检查,检查运行错误是
程序员的责任。
此外,C语言并不严格要求在变量和参数之间的类型一致。读者也许在以往的编程中注
意到,计算机高级语言一般要求变元和接收变元的参数之间必须具有完全相同的类型。但
是,C语言不必如此;相反,C语言允许变量具有任意的类型,而只要求能够合理转换成相应
参数的类型。也就是说,C语言提供了所有类型之间的自动转换以实现这一点。
C语言的特色是它支持对位、字(字节)和指针的直接操作。这使得它非常适于经常进行
上述操作的系统程序设计。
C
Tag:
编程博客 发表于07:53:43 | 阅读全文 | 评论 0 | 编辑 | 推荐
2005-09-01
VC-MFC FAQ整理 - [VC专栏]
Q 如何处理ComboBox中的回车键?避免退出程序?
A 在一般的EDIT中采用的方法是处理PretranlateMessage(),执行代码
CWnd *pWnd = GetFocus();
if(pWnd != NULL)
{
if(pWnd == GetDlgItem(IDC_EDIT1)
{
...//IDC_EDIT1具有焦点
}
}
但在ComboBox中好象不同,是ComboBox的编辑控件得到了焦点,所以判断代码:
BOOL CDlg::PreTranslateMessage(MSG *pMsg)
{
if(pMsg->message==WM_KEYDOWN && pMsg->wParam == VK_RETURN)
{
CWnd *pWnd = GetFocus();
if(pWnd != NULL)
{
if(pWnd->GetParent() == GetDlgItem(IDC_COMBO1)//更改ID
{
return TRUE;
}
}
}
return CDialog::PreTranslateMessage(pMsg);
}
//-------------------------------------------------
Q 动态创建的组合框如何设置下拉列表框的高度?
A m_combo.Create(WS_CHILD | WS_VISIBLE | WS_VSCROLL CBC_SORT | CBC_DROPDOWNLIST | WS_TABSTOP, CRect(320,10,580,280),this,114);
//CRect的最后一个参数(这里是280)就表示下拉大小
//-------------------------------------------------
Q 是否能不选择下拉列表样式而禁止用户输入值,有什么方法可以实现?
A 将下拉列表的编辑控件设置为只读的,方法如下:
CComboBox *pcombo;
CWnd *pWnd = pcombo->GetWindow(GW_CHILD);
while(pWnd)
{
char classname[256];
::GetClassName(pWnd->m_hWnd,classname,256)
if(strcmp(classname,"edit") == 0)
{
CEdit *pEdit;
pEdit = new CEdit();
pEdit->SubClassWindow(pWnd->m_hWnd);
pEdit->SetReadOnly();
pWnd = pWnd->GetNextWindow();
delete pEdit;
}
if(pWnd)
pWnd = pWnd->GetNextWindow();
}
//-------------------------------------------------
Q ComboBox的自定义弹出菜单,想在右击组合框的编辑部分的时候弹出菜单?
A 一种方法就是在CCustomCombo的OnCtlColor函数里进行,生成ComboBox中编辑框的子类,示例:
HBRUSH CCustomCombo::OnCtlColor(CDC *pDC,CWnd *pWnd,UINT nCtlColor)
{
if(nCtlColor == CTLCOLOR_EDIT)
{
if(m_edit.GetSafeHwnd()==NULL)
m_eidt.SubClassWindow(pWnd->GetSafeHwnd());
}
HBRUSH hbr = CComboBox::OnCtlColor(pDC,pWnd,nCtlColor);
return hbr;
}
//其中m_edit是CEdit类的实现,它在WM_RBUTTONUP上显示右键菜单
//-------------------------------------------------
Q 如何给按钮加位图
A
对动态创建的按钮:
CButton button;
button.Create(_T("My Button"),WS_CHILD | WS_VISIBLE | BS_BITMAP,CRect(10,10,60,50),pParentWnd,1);
button.SetBitmap(::LoadBitmap(NULL,MAKEINTRESOURCE(IBM_CHECK)));
或者修改风格:
UINT Style = Button.GetButtonStyle();
Style = Style | BS_BITMAP;
Button.SetBitmap(::LoadBitmap(NULL,MAKEINTRESOURCE(IBM_CHECK)));
//-------------------------------------------------
Q 如何在CButton派生类中以及父对话框中捕获BN_CLICKED消息?
A 于WM_NOTIFY消息相反,通知消息BN_CLICKED作为WM_COMMAND消息发送。因此应用程序应该使用ON_CONTROL_REFLECT_EC而不是ON_NOTIFY_REFLECT
//-------------------------------------------------
Q 如何判断某个对象是否具有当前焦点?
A return (CWnd::GetFocus() == pWnd);
//-------------------------------------------------
Q 如何设置编辑控件的数字限制属性?
A
long Style = GetWindowLong(m_EditCtrl.m_hWnd,GWL_STYLE);
Style |= ES_NUMBER;
SetWindowLong(m_EditCtrl.m_hWnd,GWL_STYLE,Style);
//-------------------------------------------------
Q 希望在LISTCTRL中显示文件,如何才能得到explorer使用的相同图象?
A 可以将系统的ImageList加到LISTCTRL上,然后用具有SHGFI_ICON标志的SHGetFileInfo获取适当的图标索引:
//图象列表设置
HIMAGELIST himagelist;
SHFILEINFO fi;
CImageList m_smalllist;
//得到系统小图标列表的句柄
himagelist = (HIMAGELIST)SHGetFileInfo((LPCTSTR)_T("C:\\"),0,&fi,sizeof(SHFILEINFO),SHGFI_SYSICONINDEX | SHGFI_SMALLICON);
//添加到小图象列表
m_smalllist.Attach(himagelist);
//设置LISTCTRL的图象列表
m_listCtrl.SetImageList(&m_smalllist,LVSIL_SMALL);
//分离图象列表
m_smalllist.Detach();
//-------------------------------------------------
Q 如何在列表的任何一列显示图标,而不是第一列?
A
LV_ITEM item;
...
item.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_STATE | LVIF_PARAM;
item.iItem = ...//设置行号
item.lParam = ...//如何需要就设置lparam参数
item.iSubItem = ...//设置列号,从0开始的
item.stateMask = LVIS_STATEIMAGEMASK;
it
发表于 @ 2009年02月22日 18:40:00 | 评论( 0 ) | 编辑| 举报| 收藏
旧一篇:VC单位线程是如何处理消息的.txt  | 新一篇:VC++中使用使用winnet类获取网页内容
相关文章 给guomei的留言只有注册用户才能发表评论!登录注册姓   名:
校验码:
Copyright © guomei
Powered by CSDN Blog
IBM DB2 Express-C V9.7下载  1、Windows (32bit)
2、Windows (64bit)
3、Linux for System x86
4、Linux on Power Systems
5、Linux for System x (64 bit)
6、Sun Solaris 10 on x86-64
7、Mac OS X Leopard x64
最新资讯、热门下载、技术应用、案例分析、专家视点··· 尽在IBM DW专区
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/guomei/archive/2009/02/22/3922829.aspx
异常处理 - [C++] - guomei的专栏 - CSDN博客 深入探索Win32结构化异常处理 - 冒险岛的专栏 - CSDN博客 Windows XP中的新型向量化异常处理 - 冒险岛的专栏 - CSDN博客 C 的可移植性和跨平台开发[3]:异常处理 - 【编程随想】的技术博客 - CSDN博客 C语言宏的学习: - henry19850318的专栏 - CSDN博客 C语言宏的学习: - henry19850318的专栏 - CSDN博客 C Recommend Book List - ehui928的专栏 - CSDN博客 C语言字符串函数大全 - amossavez的专栏 - CSDN博客 javacard mask.c 文件结构 - tccth4091的专栏 - CSDN博客 baozhengw的专栏 - CSDN博客 请问各位大侠:C++的异常处理(try、catch、throw)与C语言的Setjmp()、Longjmp()机制有什么区别?非常感谢! C/C / C 语言 - CSDN社区 community.csdn.net C-Free4.1专业版注册码破解 - xiufeng_wang的专栏 - CSDN博客 keil c编译器错误与解决方法 - babylon_0049的专栏 - CSDN博客 电脑速度变慢的原因及处理方法 - ydshang的专栏 - CSDN博客 AGPS简介 - kv110的专栏 - CSDN博客 OpenMAX简介 - shenbin1430的专栏 - CSDN博客 Android flinger - simmer_ken的专栏 - CSDN博客 windows 命令 - orangeman1982112的专栏 - CSDN博客 JNDI概述 - tanghongru1983的专栏 - CSDN博客 指针 - syhhl007的专栏 - CSDN博客 变量命名 - yszwn的专栏 - CSDN博客 什么是PLL - JasonCao的专栏 - CSDN博客 VC积累 - cherryt的专栏 - CSDN博客 fms技术 - wanglilin2000的专栏 - CSDN博客