C++相关问题

 
 

常用链接

  • 我的随笔
  • 我的评论
  • 我参与的随笔

留言簿(1)

  • 给我留言
  • 查看公开留言
  • 查看私人留言

随笔档案

  • 2009年1月 (3)
  • 2008年12月 (4)

搜索

  •  

最新评论

阅读排行榜

  • 1. 域名与域名解析-DNS原理(1141)
  • 2. 迭代器一些用法(1018)
  • 3. 网络域名解析原理及技术分析(786)
  • 4. 逗号表达式(723)
  • 5. 泛型算法:(473)

评论排行榜

  • 1. 逗号表达式(0)
  • 2. 泛型算法:(0)
  • 3. 迭代器一些用法(0)
  • 4. 流的基本概念(0)
  • 5. C++中extern “C”含义深层探索(0)

Powered by: 博客园
模板提供:沪江博客
C++博客 | 首页 | 发新随笔 | 发新文章 | 联系 | 聚合 | 管理

2009年1月13日

域名与域名解析-DNS原理
域名与域名解析
DNS服务,或者叫域名服务、域名解析服务,就是提供域名与IP地址的相互转换。域名的正向解析是将主机名转换成IP地址的过程,域名的反向解析是将IP地址转换成主机名的过程。通常我们很少需要将IP地址转换成主机名,即反向解析。反向解析经常被一些后台程序使用,用户看不到。
域名系统的工作过程
1.设置您的电脑去向谁查询。
除非您的电脑本身具有域名服务器的功能,否则它不会进行完整的域名查询。您的电脑需要借助于Internet上的某台域名服务器帮助进行域名查询。所以您首先需要设置您的电脑,在需要进行域名查询时,向哪个服务器发出询问。Internet上有许许多多域名服务器,您可以选任何一台作为您首选的域名服务器(“根”服务器除外,请继续读下文)。也就是说,您要告诉您的电脑,当需要进行域名查询时,首先去问哪一台域名服务器。域名服务器接收到您的电脑的查询请求,它会帮助您进行查询,然后将结果返回您的电脑。为了获得最快的响应,一般选择与您的电脑有最好的连接的服务器。在Windows95/98中,如果您是通过Modem上网,选择“拨号网络”-“某个拨号器”-“属性”-“服务器类型”-“TCP/IP设置”,可打开如图1所示窗口。这里的“主控DNS”就是上述首选的域名服务器,您还可以设置“辅助DNS”服务器,它可以在您的“主控DNS”服务器当机(Down)或响应过慢时起作用。如果您的电脑同时还有局域网连接,您需要进行以下设置:“控制面板”-“网络”-“TCP/IP -> 您的网卡”-“属性”-“DNS设置”,看到如图2所示的窗口,设置您的DNS服务器。在Unix上,类似的设置通常保存在 /etc/resolv.conf 文件中。
2.域名的查询过程。
在讲解域名查询过程之前,您需要知道一些背景知识。在Internet上,一个域名要由两台域名服务器提供“权威性的”域名解析。这里的“权威性”,指的是被服务的域名的所有记录是由这两台服务器唯一决定的。虽然Internet上的其他域名服务器上都可能保存有该域名的记录,但那些记录是从这两台“权威性”的域名服务器上拷贝过去的,是非权威性的。这两台域名服务器,和您的域名一起被登记在域名注册管理机构的数据库中。如果是国际域名,域名注册管理机构就是Internic;如果是国内域名,域名注册管理机构就是CNNIC。这两台“权威性的”服务器,一主一辅,保存着相同的记录,主要是为了提高可靠性。域名注册管理机构的数据库的记录最终体现在“根”域名服务器上。目前在Internet上的最顶级“根”域名服务器共有13台,它们被完善地维护着。如果它们全都不工作,Internet就崩溃了(网络仍通,但域名及电子邮件完全不能工作)。根服务器中保存的记录的最本质的信息,就是一个域名由哪两台域名服务器提供解析服务。
以下结合实例讲述域名的查询过程。
当您打开浏览器,访问某个站点时,例如www.hichina.com,您的电脑需要知道
这个站点的IP地址是多少。于是它会自动向您的“主控DNS”服务器发出询问,即“www.hichina.com的IP是多少?”,如果这台域名服务器
对hichina.com这个域名不是“权威性”的,起初它上面并没有关于hichina.com的记录,于是它向根服务器发出一个查询:“hichina.com由什么服务器提供域名解析服务”?根服务器的回答将是:“哦,去问dns1.hichina.com或者dns2.hichina.com吧。他们的IP地址是203.196.4.70及203.196.4.10”。您的主控DNS服务器继而会询问dns1.hichina.com这台域名服务器,还是那个问题,即“www.hichina.com的IP是多少?”,dns1.hichina.com将给出“权威性的”回答。您的主控DNS服务器收到这个回答,一方面将该信息告诉您的电脑,另一方面它会把该信息保存在自己的缓冲区内,如果它再次接到相同的查询,它就直接将刚才缓存了的记录回答给下一个询问者。但是这个缓存的记录有一个失效期,当失效期到达后,您的主控DNS服务器将会自动丢弃缓存的记录。当再有电脑发出同样的查询请求时,将重复前面叙述的完整的过程。可以看出,您的主控DNS服务器“代理”了您的电脑的查询过程,一级一级地进行了查询,我们称之为“递归式”的查询。
对于国内域名,如www.domain.com.cn,查询过程将更加复杂一些。您的电脑向您的主控DNS服务器发出查询请求:“www.domain.com.cn的IP地址是多少?”,如果您的主控DNS服务器不是该域名的“权威性”的服务器,它上面没有关于www.domain.com.cn的记录,于是它向根服务器发出询问:“com.cn由什么服务器提供域名服务?”,根服务器的回答将是:“哦,去问ns.cnc.ac.cn或者sns.edu.cn吧。他们的IP地址是159.226.1.1及202.112.0.34”(注:根服务器实际共列出了7个域名服务器,经笔者测试,其中有些域名服务器查询的结果不正确)。您的主控DNS服务器继而向ns.cnc.ac.cn发出查询:“domain.com.cn由谁提供域名服务?”,nc.cnc.ac.cn将回答:“去问dns1.hichina.com或者dns2.hichina.com吧,他们的IP地址是203.196.4.70及203.196.4.10。”您的主控DNS服务器接着会询问dns1.hichina.com这台域名服务器,“www.domain.com.cn的IP是多少?”,dns1.hichina.com将给出“权威性的”回答。同样,您的主控DNS服务器也将缓存这个记录直至失效期到来。在域名系统中,象ns.cnc.ac.cn或ns.edu.cn这样的服务器就是国内域名的根服务器。
请注意,根服务器不能作为您的“主控DNS”服务器。因为它不会帮助您进行“递归式”的查询。
怎样确定一个域名是否得到了正常的域名服务?
总结上面描述的过程,可以看出:一个域名要想能够被Internet上的用户访问到,必须得到正常的域名服务。这包括:
(1)在根服务器中有记录,这实际上就是进行了域名的注册;
(2)在“权威性的”域名服务器上有记录,即它们为您的域名提供了域名解析服务。这些“权威性的”服务器,就是登记在根服务器中,指定为您的域名提供“权威性”服务的服务器。“权威性的”域名服务器中记录了一个域名下的多个主机的IP地址,如www主机、ftp主机、mail主机等,还有该域名的电子邮件如何投递的记录、上述的失效期等。可能给该域名本身也指定一个IP地址,我们创联万网就是这样做的,其好处就是让用户访问您的网站时,不用写“www”也可以访问得到。
一个域名如果得到了正确的域名服务,那么在世界上的任何地方,Internet用户使用各种软件都能够查询到,如浏览器、telnet、ftp、ping等。
值得指出的是,即使一个域名得到了正确的解析服务,但如果该域名相应的服务器未正常工作(例如没有开机、当机或服务不正常),您仍然访问不到它们。判别它们的方法很简单,因为您的机器的给您的报告不同:域名解析服务不正常时,结果是:找不到该主机;而服务器不正常得到的回答是:主机没有响应。
如果您的域名找不到,如何查找问题?
有时,一个域名在Internet上访问不到,问题在哪里呢?首先,您需要根据浏览器返回的结果判断是属于解析有问题还是您的web服务器出了问题
(注:IE提供的信息比较含糊,Netscape提供的信息更准确)。对于域名解析上的问题,为了帮助用户了解自己域名的工作状况,创联万网开发了Web界面的查询工具“域名追踪器”,它可以帮助您断定域名出现无法访问时的问题所在。因为您看完了本文前面的介绍,知道了域名是怎样工作的,您就可以借助于“域名追踪器”进行查看。“域名追追踪器”可以帮助您查看:
(1) 根服务器是否给您的域名做了正确的解析服务。
(2) 您的域名所登记的“权威性的”服务器是否给您的域名提供了正确的解析服务。
域名追踪器在万网的网站上的位置是:http://bips.hichina.com/maindoc/usr/register_domain/search_domain.php3
一般,如果根服务器没有您的域名的记录,有以下几种可能:
(1) 您的域名是刚刚注册;
(2) 您的域名于付款问题或其他问题被停止了服务;
如果您的域名的“权威性的”服务器未给您的域名提供服务,其原因可能是:
(1) 管理该服务器的机构(网络服务提供商)未做域名解析服务(DNS);
(2) DNS设置错误等;
posted @ 2009-01-13 15:49 zhengyq 阅读(1141) | 评论 (0) | 编辑 收藏
 
网络域名解析原理及技术分析
资料1:
与域名相关的服务包含两项:域名注册和域名解析。域名必须先注册后,才能使用。没有注册的域名是无效的。域名注册必须先付费才能开通。在国际惯例上,域名注册不提供免费试用。金万维提供免费的二级域名解析服务。
  Internet上的计算机是通过IP地址来定位的,给出一个IP地址,就可以找到Internet上的某台主机。而因为IP地址难于记忆,又发明了域名来代替IP地址。但通过域名并不能直接找到要访问的主机,中间要加一个从域名查找IP地址的过程,这个过程就是域名解析。
  域名注册后,注册商为域名提供免费的静态解析服务。一般的域名注册商不提供动态解析服务,如果需要用动态解析服务,需要向动态域名服务商(如金万维)支付域名动态解析服务费。
  负责将域名解析成为IP地址的服务器,叫做域名解析服务器,英文简称是DNS。Internet上所有的DNS通过域的层次关系连接在一起。当Internet用户打开浏览器,输入一个网址的时候,比如http://www.gnway.com,用户的计算机并不知道www.gnway.com是哪一台主机,因此计算机向Internet的DNS发出查询请求,DNS将查询到的IP地址返回给用户的计算机,用户计算机就可以根据IP地址连接www.gnway.com主机,把网页取下来。


资料2
DNS,DomainNameSystem或者DomainNameService(域名系统或者余名服务)。域名系统为Internet上的主机分配域名地址和IP地址。用户使用域名地址,该系统就会自动把域名地址转为IP地址。域名服务是运行域名系统的Internet工具。执行域名服务的服务器称之为DNS服务器,通过DNS服务器来应答域名服务的查询。
1、DNS就是域名服务器,他的任务就是确定域名的解析,比如A记录MX记录等等。
2、任何域名都至少有一个DNS,一般是2个。但为什么要2个以上呢?因为DNS可以轮回处理,第一个解析失败可以找第二个。这样只要有一个DNS解析正常,就不会影响域名的正常使用。
3、如何确定域名的DNS
很简单,到www.internic.net/whois.html输入你要查询的域名就可以看到了。这个是国际域名管理中心。唯一的权威。只要这里能查到某个域名,就表示域名是生效的。它说你什么时候到期,就是什么时候到期。
4、有效的DNS表示当前正在起作用的DNS服务器是谁,比如查询结果是NS.XINNETDNS.COM、NS.XINNET.CN(新网信海)就表示当前域名是由NS.XINNETDNS.COM、NS.XINNET.CN(新网信海)负责解析。其他DNS的设置,都是无效的。
5、DNS是可以修改的。修改以后需要24-72小时以后,全世界才能刷新过来。internic的信息一般在24小时以后可以看到。另外,修改的过程,并不表示域名会停止解析,只要你在2边都做好了解析。如果生效了就是新的DNS在起作用。如果没生效。就是旧的DNS在起作用。要么生效,要么不生效。不存在2个都不起作用的时间。
6、DNS是有缓存的。
1)访问者的电脑;2)你的ISP接入商。
简单举例:比如你访问www.askbaidu.com,你的电脑首先查询本机上有没有缓存www.askbaidu.com的记录。如果有就直接调用不再去查寻。就是说如果你前面刚访问过www.askbaidu.com,这个时候就算电信的DNS和NS.XINNETDNS.COM、NS.XINNET.CN(新网信海)都不能解析。也是能够正常解析出域名的。
清除本机DNS缓存方法很简单。关闭IE然后清除历史记录,或者重启电脑。然后还有一个就是isp接入商的DNS的缓存。isp就是当地网络接入商。比如我们这里的福建电信;福州网通、南平铁通等等。每个地方都是不一样的。isp的DNS和NS.XINNETDNS.COM、NS.XINNET.CN(新网信海)这样的DNS是不同的。NS.XINNETDNS.COM、NS.XINNET.CN(新网信海)只负责具体的解析,不负责缓存。isp的DNS只负责查询和缓存,不负责解析。
简单描述下刚才访问www.askbaidu.com的情况。如果本机上不存在www.askbaidu.com的记录。你的电脑就会去查询当地ISP的DNS。isp的DNS只有缓存。就是说他会检查有没有www.askbaidu.com的缓存。如果有,他就直接把www.askbaidu.com的记录发送给用户。用户也就能访问了。如果ISP的缓存里面也没有www.askbaidu.com的记录,那么他进一步去查询askbaidu.com的DNS是什么?然后再到对应的DNS上直接去取得数据,并返回给用户。当第一个用户访问了www.askbaidu.com以后,isp的dns上也就开始缓存了www.askbaidu.com的记录。以后他就不必再去NS.XINNETDNS.COM、NS.XINNET.CN(新网信海)去找了。除非有新的域名,他才会去查。比如访问bbs.askbaidu.com的时候,他就要重新去查了。
7、isp的DNS缓存是有时间限制的。一般是1个小时。前后2次间隔1个小时的话,他就去域名的DNS上重新取得数据。这里说的是最前面一次和当前的比较。也就是说如果时间差距较大,就重新去域名的DNS服务器上找。所以刷新就变的很有必要,否则缓存了一次以后。域名记录改了以后。ISP就永远不去找新的记录了。知道了这个原理以后,大家就会明白,为什么原来没有的记录注册并生效会很快。修改的话生效会很慢。就是因为缓存的原因。但如果没有缓存,访问的效率会很低,因为任何一次输入www.askbaidu.com都得跑到NS.XINNETDNS.COM、NS.XINNET.CN(新网信海)去查询记录。
备注:很多域名商的域名解析系统也不是实时刷新的。一般会设置下时间,比如10分钟.就是说,你设置了一个新的A记录以后,域名服务器会在10分钟内为你添加。目的就是为了节约服务器资源。怕客户的DNS不断的刷新记录。刷新记录肯定需要消耗一定的资源。而且刷新过程中是不能解析的。另外刷新过程大概5秒。就是说这个5秒内域名商的的DNS是不能用的。
posted @ 2009-01-13 13:34 zhengyq 阅读(786) | 评论 (0) | 编辑 收藏
 

2009年1月11日

C++中extern “C”含义深层探索
 
C++中extern “C”含义深层探索
1.引言
C++语言的创建初衷是“a better C”,但是这并不意味着C++中类似C 语言的全局变量
和函数所采用的编译和链接方式与C 语言完全相同。作为一种欲与C 兼容的语言,C++保留了
一部分过程式语言的特点,因而它可以定义不属于任何类的全局变量和函数。但是,C++毕竟
是一种面向对象的程序设计语言,为了支持函数的重载,C++对全局函数的处理方式与C 有明
显的不同。
2.从标准头文件说起
某企业曾经给出如下的一道面试题:
面试题
为什么标准头文件都有类似以下的结构?
#ifndef _TEST_H
#define _TEST_H
#ifdef __cplusplus
extern "C" {
#endif
/*...*/
#ifdef __cplusplus
}
#endif
#endif /* _TEST_H */
分析
显然,头文件中的编译宏“#ifndef _TEST_H、#define _TEST_H、#endif” 的作用是防止
该头文件被重复引用。
那么
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __cplusplus
}
#endif
的作用又是什么呢?
3.深层揭密extern "C"
extern "C" 包含双重含义,从字面上即可得到:首先,被它修饰的目标是“extern”的;
其次,被它修饰的目标是“C”的。让我们来详细解读这两重含义。
被extern "C"限定的函数或变量是extern 类型的;
extern 是C/C++语言中表明函数和全局变量作用范围(可见性)的关键字,该关键字告
诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。记住,下列语句:
extern int a;
仅仅是一个变量的声明,其并不是在定义变量a,并未为a 分配内存空间(特别注意:实
际上现在一般的编译器都会对上述语句作声明处理,但链接器在链接过程中如果没有发现该
变量的定义,一般会在第一次遇到该变量声明的地方,自动定义)。变量a 在所有模块中作为
一种全局变量只能被定义一次,否则会出现连接错误。
通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字
extern 声明。例如,如果模块B 欲引用该模块A 中定义的全局变量和函数时只需包含模块A
的头文件即可。这样,模块B 中调用模块A 中的函数时,在编译阶段,模块B 虽然找不到该
函数,但是并不会报错;它会在连接阶段中从模块A 编译生成的目标代码中找到此函数。
与extern 对应的关键字是static,被它修饰的全局变量和函数只能在本模块中使用。因
此,一个函数或变量只可能被本模块使用时,其不可能被extern “C”修饰。
被extern "C"修饰的变量和函数是按照C 语言方式编译和连接的;
未加extern “C”声明时的编译方式
首先看看C++中对类似C 的函数是怎样编译的。
作为一种面向对象的语言,C++支持函数重载,而过程式语言C 则不支持。函数被C++编
译后在符号库中的名字与C 语言的不同。例如,假设某个函数的原型为:
void foo( int x, int y );
该函数被C 编译器编译后在符号库中的名字为_foo,而C++编译器则会产生像
_foo_int_int 之类的名字(不同的编译器可能生成的名字不同,但是都采用了相同的机制,
生成的新名字称为“mangled name”)。
_foo_int_int 这样的名字包含了函数名、函数参数数量及类型信息,C++就是靠这种机制
来实现函数重载的。例如,在C++中,函数void foo( int x, int y )与void foo( int x, float
y )编译生成的符号是不相同的,后者为_foo_int_float。
同样地,C++中的变量除支持局部变量外,还支持类成员变量和全局变量。用户所编写程序的
类成员变量可能与全局变量同名,我们以"."来区分。而本质上,编译器在进行编译时,与函
数的处理相似,也为类中的变量取了一个独一无二的名字,这个名字与用户程序中同名的全
局变量名字不同。
未加extern "C"声明时的连接方式
假设在C++中,模块A 的头文件如下:
// 模块A 头文件 moduleA.h
#ifndef MODULE_A_H
#define MODULE_A_H
int foo( int x, int y );
#endif
在模块B 中引用该函数:
// 模块B 实现文件 moduleB.cpp
#i nclude "moduleA.h"
foo(2,3);
实际上,在连接阶段,连接器会从模块A 生成的目标文件moduleA.obj 中寻找
_foo_int_int 这样的符号!
加extern "C"声明后的编译和连接方式
加extern "C"声明后,模块A 的头文件变为:
// 模块A 头文件 moduleA.h
#ifndef MODULE_A_H
#define MODULE_A_H
extern "C" int foo( int x, int y );
#endif
在模块B 的实现文件中仍然调用foo( 2,3 ),其结果是:
(1)模块A 编译生成foo 的目标代码时,没有对其名字进行特殊处理,采用了C 语言的
方式;
(2)连接器在为模块B 的目标代码寻找foo(2,3)调用时,寻找的是未经修改的符号名_foo。
如果在模块A 中函数声明了foo 为extern "C"类型,而模块B 中包含的是extern int
foo( int x, int y ) ,则模块B 找不到模块A 中的函数;反之亦然。
所以,可以用一句话概括extern “C”这个声明的真实目的(任何语言中的任何语法特
性的诞生都不是随意而为的,来源于真实世界的需求驱动。我们在思考问题时,不能只停留
在这个语言是怎么做的,还要问一问它为什么要这么做,动机是什么,这样我们可以更深入
地理解许多问题):
实现C++与C 及其它语言的混合编程。
明白了C++中extern "C"的设立动机,我们下面来具体分析extern "C"通常的使用技巧。
4.extern "C"的惯用法
(1)在C++中引用C 语言中的函数和变量,在包含C 语言头文件(假设为cExample.h)
时,需进行下列处理:
extern "C"
{
#i nclude "cExample.h"
}
而在C 语言的头文件中,对其外部函数只能指定为extern 类型,C 语言中不支持extern
"C"声明,在.c 文件中包含了extern "C"时会出现编译语法错误。
笔者编写的C++引用C 函数例子工程中包含的三个文件的源代码如下:
/* c 语言头文件:cExample.h */
#ifndef C_EXAMPLE_H
#define C_EXAMPLE_H
extern int add(int x,int y);
#endif
/* c 语言实现文件:cExample.c */
#i nclude "cExample.h"
int add( int x, int y )
{
return x + y;
}
// c++实现文件,调用add:cppFile.cpp
extern "C"
{
#i nclude "cExample.h"
}
int main(int argc, char* argv[])
{
add(2,3);
return 0;
}
如果C++调用一个C 语言编写的.DLL 时,当包括.DLL 的头文件或声明接口函数时,应加
extern "C" { }。
(2)在C 中引用C++语言中的函数和变量时,C++的头文件需添加extern "C",但是在C
语言中不能直接引用声明了extern "C"的该头文件,应该仅将C 文件中将C++中定义的extern
"C"函数声明为extern 类型。
笔者编写的C 引用C++函数例子工程中包含的三个文件的源代码如下:
//C++头文件 cppExample.h
#ifndef CPP_EXAMPLE_H
#define CPP_EXAMPLE_H
extern "C" int add( int x, int y );
#endif
//C++实现文件 cppExample.cpp
#i nclude "cppExample.h"
int add( int x, int y )
{
return x + y;
}
/* C 实现文件 cFile.c
/* 这样会编译出错:#i nclude "cExample.h" */
extern int add( int x, int y );
int main( int argc, char* argv[] )
{
add( 2, 3 );
return 0;
}
如果深入理解了第3 节中所阐述的extern "C"在编译和连接阶段发挥的作用,就能真正
理解本节所阐述的从C++引用C 函数和C 引用C++函数的惯用法。对第4 节给出的示例代码,
需要特别留意各个细节。
posted @ 2009-01-11 15:21 zhengyq 阅读(268) | 评论 (0) | 编辑 收藏
 

2008年12月22日

流的基本概念

一、流类的继承关系

在C++语言中,为数据的输入和输出定义了一系列类库,主要包括:

ios,istream,ostream,iostream,ifstream,ofstream,fstream,istrstream,ostrstream,strstream等,其中ios为根基类,其余都是它的直接或间接派生类。其关系如图13-1所示。

图13-1 类库间的关系

 


ios为根基类,它直接派生四个类:输入流类istream、输出流类ostream、文件流基类fstreambase和串流基类strstreambase,输入文件流类同时继承了输入流类和文件流基类(当然对于根基类是间接继承),输出文件流类ofstream同时继承了输出流类和文件流基类,输入串流类istrstream同时继承了输入流类和串流基类,输出串流类ostrstream同时继承了输出流类和串流基类,输入输出流类iostream同时继承了输入流类和输出流类,输入输出文件流类fstream同时继承了输入输出流类和文件流基类,输入输出串流类strstream同时继承了输入输出流类和串流基类。

二、流类库的文件包含关系

各个流类在文件中的包含关系如表13-1。

表13-1 流类包含关系

分 类

类 名

说 明

包含文件

抽象流基类

ios

流基类

ios

输入流类

istream

通用输入流类和其他输入流的基类

istream

ifstream

输入文件流类

fstream

istringstream

输入串流类

sstream

输出流类

ostream

通用输出流类和其他输出流的基类

ostream

ofstream

输出文件流

fstream

ostringstream

输出串流

sstream

输入/输出流类

iostream

通用输入/输出流类和其他输入/输出流类的基类

istream

fstream

输入/输出文件流类

fstream

stringstream

输入/输出串流类

sstream

流缓冲区类

streambuf

抽象流缓冲区基类

streambuf

filebuf

磁盘文件的流缓冲区类

fstream

stringbuf

串的流缓冲区类

sstream

当程序需要进行标准I/O操作的时候,必须包含头文件iostream。

当程序需要进行文件I/O操作的时候,必须包含头文件fstream。

当程序需要进行串I/O操作的时候,必须包含头文件sstream。

三、预定义流类对象

C++语言系统不仅定义了大量的输入输出流库供用户使用,还定义了四个常用的类对象,方便用户进行标准的I/O操作,如表13-2。

表13-2 C++常用的四个类对象

名称

说 明

流 类

cin

代表标准输入设备(键盘),又叫标准输入流或cin流。

istream

cout

代表标准输出设备(显示器),又叫标准输出流或cout流。

ostream

cerr

错误信息输出设备(显示器),非缓冲输出。

clog

错误信息输出设备(显示器),缓冲输出。

 【例13-1-1】 下边的代码用到标准I/O的实现。

源代码:

/* 例13-1-1,13-1-1.cpp */

#include<iostream>

using namespace std;

void main()

{

     int a, b, c;

     cout << "input a=";           //标准输出流(数据从输出流流向标准输出设备)

     cin >> a;                           //标准输入流(数据从标准输入设备流向输入流)

     cout << "input b=";    //标准输出流(数据从输出流流向标准输出设备)

     cin >> b;                           //标准输入流(数据从标准输入设备流向输入流)

     c = a + b;

     cout << "c=" << c << endl;       //标准输出流(数据从输出流流向标准输出设备)

}

 例文件I/O的实现

【例13-1-2】 标准I/O的实现。

源代码:

/* 例13-1-2,13-1-2.cpp */

#include<iostream>

#include<fstream>

#include<string>

using namespace std;

void main()

{

       char* s="This is a file.";

       ofstream my_ofstream("code.dat"); //定义一个输出文件流对象

       my_ofstream.write(s,strlen(s));        //调用该输出文件流对象的write()操作

       my_ofstream.close();                       //关闭输出文件流对象

       ifstream my_ifstream("code.dat");           //定义一个输入文件流对象

       char ch;

       while(my_ifstream.get(ch))               //循环把文件中所有字符读入输入文件流对象

       {

              cout<<ch;

       }

       my_ifstream.close();                        //关闭输入文件流对象

}

posted @ 2008-12-22 17:24 zhengyq 阅读(313) | 评论 (0) | 编辑 收藏
 
迭代器一些用法
如果用户需要反方向迭带遍历整个容器,reverse_iterator适配器可以满足这个要求。库以一种一致的方式扩展了基本的C++范例,因此一个是C/C++的程序员能够很容易的开始使用库。例如,库有一个合并的函数模板。当用户有两个数组a和b要合并成c时,可以这样实现:

int a[1000];

int b[2000];

int c[3000];

...

merge(a, a + 1000, b, b + 2000, c);

如果用户想要合并一个vector和一个list(二者都是库中的模板类),并把结果放到一个新申请的未初始化的空间中,可以这样实现:

vector<Employee> a;

list<Employee> b;

...

Employee* c = allocate(a.size() + b.size(), (Employee*)0);

merge(a.begin(), a.end(), b.begin(), b.end(),

raw_storage_iterator<Employee*, Employee>(c));

其中 begin()和end()是容器的成员函数,它们返回正确的迭代器类型或者类似指针的对象,该迭代器或对象允许merge去做这项工作.raw_storage_iterator是一个适配器,它允许算法通过调用相应的拷贝构造函数把结果直接放到没有初始化的内存空间中。很多种情况下象通过常规数据结构一样迭带通过整个输入/输出流也是很有用的。例如,如果我们想要合并两个数据结构并把它们存储到一个文件中,那么能够避免为结果产生一个辅助数据结构,而直接把结果存到相应文件中去将是一种很好的方法。库同时提供了istream_iterator 和 ostream_iterator模板类,以便使库的多数算法能够和代表同类数据集合的I/O流一起工作。这里是一个从标准输入读取数字文件的程序,移走所有在命令参数不可见的输入,并把结果写到标准的输出中。

main(int argc, char** argv) {

if (argc != 2) throw(”usage: remove_if_divides integer\n”);

remove_copy_if(istream_iterator<int>(cin), istream_iterator<int>(),

ostream_iterator<int>(cout, ”\n”),

not1(bind2nd(modulus<int>(), atoi(argv[1]))));

}

所有的工作都是由remove_copy_if来做的,它一个个读整数直到输入迭代器和流的结束迭代器相等,后者是由无参的构造函数构造的。(一般,所有的算法以一种“从这到那”的方式工作,取两个分别表示输入开始和结束的迭代器做为参数)。然后remove_copy_if通过cout确定的迭代器传递检测到输出流。remove_copy_if使用的函数对象从modulus<int>构造而来,modulus<int>是二元谓词,输入参数I和j,返回I%j。由于remove_copy_if需要的是一元谓词,需要用bind2nd绑定modulus<int>的第二个参数,使之成为一元谓词。这里modulus<int>的第二个参数是命令行参数atoi(argv[1])。然后使用notl函数适配器得到这个一元谓词的否定。

一个更加现实的例子是一个过滤程序,该程序输入一个文件,然后随机的打乱它的行。

main(int argc, char**) {

if (argc != 1) throw(”usage: shuffle\n”);

vector<string> v;

copy(istream_iterator<string>(cin), istream_iterator<string>(),

inserter(v, v.end()));

random_shuffle(v.begin(), v.end());

copy(v.begin(), v.end(), ostream_iterator<string>(cout));

}

这个例子中,copy从标准输入移动行到一个vector中,但是由于这个vector没有提前分配空间,所以它用一个插入的迭代器一行行插入到vector中。(这项技术允许所有的复制函数在常规的覆盖模式和插入模式都能够工作)。然后random_shuffle随机打乱vector并再次调用copy把它复制到cout流中。

posted @ 2008-12-22 10:36 zhengyq 阅读(1018) | 评论 (0) | 编辑 收藏
 
泛型算法:

所有算法的前两个参数都是一对iterators:[first,last),用来指出容器内一个范围内的元素。
每个算法的声明中,都表现出它所需要的最低层次的iterator类型。

70个算法:
accumulate() 元素累加
adjacent_difference() 相邻元素的差额
adjacent_find() 搜寻相邻的重复元素
binary_search() 二元搜寻
copy() 复制
copy_backward() 逆向复制
count() 计数
count_if() 在特定条件下计数
equal() 判断相等与否
equal_range() 判断相等与否(传回一个上下限区间范围)
fill() 改填元素值
fill_n() 改填元素值,n 次
find() 搜寻
find_if() 在特定条件下搜寻
find_end() 搜寻某个子序列的最后一次出现地点
find_first_of() 搜寻某些元素的首次出现地点
for_each() 对范围内的每一个元素施行某动作
generate() 以指定动作的运算结果充填特定范围内的元素
generate_n() 以指定动作的运算结果充填 n 个元素内容
includes() 涵盖於
inner_product() 内积
inplace_merge() 合并并取代(覆写)
iter_swap() 元素互换
lexicographical_compare() 以字典排列方式做比较
lower_bound() 下限
max() 最大值
max_element() 最大值所在位置
min() 最小值
min_element() 最小值所在位置
merge() 合并两个序列
mismatch() 找出不吻合点
next_permutation() 获得下一个排列组合
泛型演算法(Generic Algorithms)与 Function Obje4 cts
nth_element() 重新安排序列中第n个元素的左右两端
partial_sort() 局部排序
partial_sort_copy() 局部排序并复制到它处
partial_sum() 局部总和
partition() 切割
prev_permutation() 获得前一个排列组合
random_shuffle() 随机重排
remove() 移除某种元素(但不删除)
remove_copy() 移除某种元素并将结果复制到另一个 container
remove_if() 有条件地移除某种元素
remove_copy_if() 有条件地移除某种元素并将结果复制到另一个 container
replace() 取代某种元素
replace_copy() 取代某种元素,并将结果复制到另一个 container
replace_if() 有条件地取代
replace_copy_if() 有条件地取代,并将结果复制到另一个 container
reverse() 颠倒元素次序
reverse_copy() 颠倒元素次序并将结果复制到另一个 container
rotate() 旋转
rotate_copy() 旋转,并将结果复制到另一个 container
search() 搜寻某个子序列
search_n() 搜寻「连续发生 n 次」的子序列
set_difference() 差集
set_intersection() 交集
set_symmetric_difference() 对称差集
set_union() 联集
sort() 排序
stable_partition() 切割并保持元素相对次序
stable_sort() 排序并保持等值元素的相对次序
swap() 置换(对调)
swap_range() 置换(指定范围)
transform() 以两个序列为基础,交互作用产生第三个序列
unique() 将重复的元素摺叠缩编,使成唯一
unique_copy() 将重复的元素摺叠缩编,使成唯一,并复制到他处
upper_bound() 上限
-- 以下是 heap 相关演算法 --
make_heap() 制造一个 heap
pop_heap() 从 heap 内取出一个元素
push_heap() 将一个元素推进 heap 内
sort_heap() 对 heap 排序

posted @ 2008-12-22 10:09 zhengyq 阅读(473) | 评论 (0) | 编辑 收藏
 

2008年12月20日

逗号表达式

1.逗号(,)和加号(+)都是运算符,为什么逗号,不可以编译期间确定?加号+却可以?int a[2,3];错误! int a[2+3];正确!

Answer:加号"+"是一个运算符(operator),但是逗号","却有两种用法。第一种用法是作为分隔符(separator),比如我们最常见到的int i ,j ,k;但是还有一种用法,比较不常见,作为逗号运算符(comma operator)使用,也叫顺序运算符(sequence operator),最常见的使用是在for语句中,例如

for( int i = 0, j = i; i < max; ++i )…;

其实顾名思义,既然叫顺序运算符,那就是从左往右一个一个的求值,最后整个表达式的结果是最后一个求值的结果。例如:

int i,j,k;

i = 2, j = i+2, k = 3*j, i + j + k;

第二个语句从左往右一个一个的求值,i=2, j=i+2=4, k=3*j=12, i+j+k=18, 整个表达式结果是18,类型是i+j+k的类型int。

但是在所有的运算符优先级中,逗号运算符的优先级是最低的,而且标准也规定在逗号运算符中的表达式求值是动态确定的,既然是动态确定,那当然是不可以编译期间确定。因此,int a[2,3];中的逗号运算符表达式需要在运行时确定是3,但是数组的个数必须在编译期间确定,矛盾,编译不可能通过。标准中有规定,[const_expression],数组个数应该是一个const_expression,但是在这个const_expression中,comma operator是不可以使用的。【注2】

注2:请参考ANSI C++标准5.19

现在我们来做一个假设,如果逗号运算符表达式可以在编译时确定,也就是说int a[2,3];即为int a[3];那么一个有fortran背景的程序员第一次看到这个表达式,他肯定会认为是int a[2][3];以后的麻烦肯定就是如影随形。因此,标准禁止这种用法是非常明智的。不过,有一些编译器对C语言做了一些扩展,比如著名的GNU家族的gcc编译器,因此这个语句在gcc下是可以通过的,但是请记住,gcc是C语言编译器,g++才是C++语言编译器。下面的例子:

int a[2,3,4];

VC7.1不能通过;DEVC++4.9.7可以通过,表示int a[4];

2.如下的数组初始化,不能用static修饰数组,怎么改?

       class A

       {

        public:

            A():a({1,2}) {}   //这样不行!

        private:

            const int a[2];

        };

Answer: 一个在类中,使用const修饰的,非静态数组不能被显示初始化。但是在这个问题中,数组a是个常量数组,因此它又不可能在构造函数体内被初始化。数组a不能被初始化的本质原因在于a是一系列连续对象的集合,它不能代表一个对象。例如:

int a[2],b[2]={1,2};

a=b;//不合法,a不能被赋值!

A只是一个数组名,它有两个意义,1.sizeof(a)中,a表示整个数组,sizeof(a)结果是整个数组所占内存的字节数;2.int j = a[1]中,a[1]是*(a+1)的另一种写法,a的值就是数组首元素的地址。上面的例子有两种解决办法。

(1).将a[2]转移到类层次中,即将const int a[2]改为static const int a[2],你可以认真的想一想,既然a[2]是const,那么每一个对象真的需要单独的一份a[2]吗?大部分时候答案应该是no。现在类定义如下:

       class A

       {

        public:

            A(){}

        private:

            static const int a[2];

        };

const int A::a[2] = {1,2};//在实现文件中。

(2)将数组改为指针,即const int a[2]改为const int* const a; 现在类定义如下:

                const int ca[2] = {1,2};//注意

       class A

       {

        public:

            A():a(ca){}

        private:

            const int* const a;

        };

posted @ 2008-12-20 09:43 zhengyq 阅读(723) | 评论 (0) | 编辑 收藏
 
仅列出标题