2008年04月18日 星期五 上午 05:37
——专制与民主
田中芳树,在我的心中那是一个很了不起的作家,他用那短短的一支笔就描绘出一个宏大的银河星际战争,构造了莱因哈特,吉尔菲艾斯,杨威利等传说般的神奇人物。在银河英雄传说中,不但描绘了各种各样的战争和在战争中涌现出的一个又一个的英雄人物,还道出了作者对民主制度和专制制度的深刻思考。通篇小说也没有给出一个对于专制与民主孰优孰劣的结论,只是给我们一个深刻的感受。
莱因哈特,一个专制帝国中成长起来的年轻皇帝,与他的好友吉尔菲艾斯一起,推翻了统治了大部分银河系四百年的高登巴姆王朝,打碎了腐朽的贵族阶级,重新建立了一个新的银河帝国,这个帝国,因为这俊美无比的年轻皇帝而充满了朝气与活力,由于这个皇帝无比绝伦的才智与器量,还有那充满霸气的战意,聚集了一群杰出的将领,击败了自由行星同盟,实现了对大部分银河星系的统治,成为这个银河真正最强大的存在。
杨威利,在民主国家中,一个在无意中建立了无数功勋的元帅,虽然对民主国家的低效率以及一些政治家的龌龊感到深深不满,但对民主制度却有着无比坚定的信仰,他对个人的自由追求永不放弃,以及对军事专制充满了高度警惕,他时刻提醒自己,不要用他的功勋和威望来影响政治,不能称为下一个鲁道夫。正因为如此,虽然他拥有着比帝国皇帝莱因哈特更杰出的军事才能,但却因为民主国家一些政治家的制掣,数次放弃了战略优势。不过因为他对民主制度的呵护,使得在尔发星系以及伊谢尔伦要塞保留了一些民主的种子。
地球,在这部小说中,扮演了一个极不光彩的角色,作者用地球来隐喻那些死不退出历史舞台的淘汰者,用一个又一个卑鄙的手段,来恢复自己已经逝去的荣光,犹如生在阴沟里的生物,难掩满身腐臭的气味。我想,在这部小说,地球应该是独立于专制和民主的存在,而是充满龌龊与阴险的宗教势力。
在银河英雄传说中,最令我感慨的是鲁道夫创建银河帝国的理由,民主制度成了培育专制制度的肥沃土壤,由于民众信仰的缺失,需要一个强大而有力的存在来指导他们,来替他们安排好一切,而无需自己去费力思考。鲁道夫的产生,让我想起了德意志希特勒的成功,他也是一个由狂热的民众推选出来的元首,一个在民主制度下诞生的专制帝国,正是由于希特勒,他整合了德意志民族的人民,以极其高效的国家机器发动了第二次世界大战。通篇小说,
作者对民主制度的批判令人深思,民主政治选出来的领袖却是一个充满了谎言和无耻的人物,一步一步带领着民主政治走向了死亡。使得我认为民主政治的选举是一个充满了表演和口才的游戏。谁掌握了国家的话语权,谁就能够掌握人民与国家。正因为如此,当整个社会走向糜烂之时,人民过度自由与放纵,一个强有力的人物就容易整合社会,从而产生了专制。
对于专制制度,这是一个需要依赖独裁者自身优良素质的制度,专制的国家,总在明君,庸君,昏君的不断循环中走下去,对人民,对舆论的极端的控制,使得下层充满了对个人自由的无限追求,这又迫使民主革命的产生,又从专制走向民主。
现今的地球,虽然大部分是民主国家,不过这是由于地球资源的不够充分,不能满足全人类的需要,所以才产生了国家与国家,人民与人民的制衡,使得一个国家走向了民主,就不容易产生专制。银河帝国的产生却是由于物质的极大丰富,人民走向了放纵与信仰缺失而造成的结果。 |
2008年01月14日 星期一 上午 06:45
目前学习 MFC Windows编程 深入浅出MFC,虽然工具古老,但我仍然为这个工具里面所焕发的思想而深深地震撼着.从未想到,人类竟然有着如此的智慧将一个复杂的对象用复杂的思想层层包装,而后通过简单的工具来实现复杂的功能.或许这是一条不归路,我愿意永远也沉浸在0和1的海洋之中.我的生命已经别无选择,没有了友人,没有了爱人,计算机已经是我最后的追求与生命.但愿这一切的知识能够让我忘却一切的悲伤与无奈. |
2007年12月24日 星期一 下午 11:52
去年的火箭比赛让我看的十分痛快,无论是失败还是胜利,每一场火箭队比赛对我而言,都是一场享受.我因为艾弗森而喜欢上NBA,因姚明而喜欢火箭,最后喜欢范甘迪指教的防守风格.在其他人觉得火箭比赛单调无味的时候,我则非常喜欢上这种节奏.对我而言,防守赢得季后赛之类的名言并不如何理解.仅仅单纯的喜欢上火箭和对手在场上不断打铁的场景.觉得范甘迪很有趣,教导的火箭防守得对方不能得分,连自己很多时候也无法得分.所以火箭比赛打铁声尤其的多.正因如此,我则越来越喜欢这种比赛,每一场火箭队的比赛我都期望能够看到直播.曾写了两篇帖子赞美范甘迪,其实去年火箭在范甘迪的教导之下,已经足够稳定,并且有充分的发展空间.
现在火箭高层让范甘迪离开,在我看来,这是一种短视的行为,虽然阿德尔曼的执教战绩非常华丽,但让一个已经适应了范甘迪防守体系的球队重新适应阿德尔曼的进攻体系,似乎要花上不少的时间.而且阿德尔曼似乎并不怎么重视防守.对姚明的使用让我颇感微词,不知道为何总要将姚明拉到外线投篮,我觉得范甘迪已经知道如何去使用姚明了,可惜,让一个新的教练重新学习如何使用姚明,似乎要花不少的时间.常规赛这么多场比赛下来,火箭的进攻并没有很大的提高,反而在防守方面给人越来越"惊喜"的感觉,前面一场对太阳,半场让对手拿到了将近70分!自这场比赛以后,不再为火箭的胜负而悲喜,就算姚明在,我已经不再特别喜欢火箭的比赛了.或许,由于范甘迪的缘故,我已经越来越不喜欢一个不怎么重视防守的球队.
不知为何,对失去的总是很想念,人或许就是这么矛盾吧,希翼火箭队的进攻能够今早华丽起来,或许到时能够有些兴趣特别关注火箭的比赛.
|
2007年12月13日 星期四 下午 06:05
最近几乎没什么好的美国电视剧可看,着实让人感到郁闷。对我而言,港剧和国内电视剧显得太过稚嫩,或者我不大喜欢那些都市或厚重的主题吧。喜欢上美国电视剧,大概是从很久以前的C.S.I开始的,后来C.S.I.每集都是破案,审美疲劳之后,换了科幻主题,或者超级英雄主题.这方面的代表为《星际之门》、《超人前传》、《The 4400》…
不过现在《超人前传》已经断栏,最新的一集要在明年才会放出来,《越狱》《The 4400》已经很久没有消息了,似乎都要到明年才会出来吧,唯一还可以每周等待的只有《星际之门》。故而我再去翻翻有无其他的电视剧看看。
很多人喜欢看《英雄》,不过我不大喜欢,因为里面有个小日本说日语,且拥有超能力,可能又有人要说我这个人是小愤青,看不得日本的好。不过我不推荐现在的一些抵制日货的态度,毕竟日本也有很多东西是很不错的,全球化经济下单纯的抵制日货显得有些幼稚了。只是对于电视剧而言,如果有日本英雄,我还是不大乐意看的,这和爱国无关,只是对这个有731部队的国家,制造南京大屠杀的民族提不起好感而已。
说到这里,昨天看了一个新剧介绍,片名《life》,中文译名为 《重获新生》,讲述一个警察含冤入狱十二载,后经律师帮忙,证明了他无罪,而后无罪释放。大约获得了五千万赔偿金。出狱后以在狱中学会的佛学态度对待生活,并重新当上了警察,暗地里追查当年令他入狱的案件。我当时就想知道这样一个人入狱十二年后会以怎样的心态重新看待社会,看待生活,并如何理解人生。
已经看了三集,总体上感觉不错,虽然案件显得有些简单,不像其他美国电视剧那样复杂和离奇。但很喜欢主角出狱后的所作所为,离奇的言行下是对案件,对细节更深的了解。以及学会了如何控制怒火,控制自身的情绪。并很喜欢盯着他人的眼睛,试图从他人眼睛中看到一切,毕竟 眼睛是心灵之窗。而且善于从嫌疑人的角度考虑问题,发现以前按规章办事时永远发现不了的细节。
对了,看到第三集的时候,感觉美国警察的权力很大,主角出狱后很喜欢新车,开车的时候喜欢飙车。他心底中仍然爱着前妻,所以很喜欢刁难他前妻现在的丈夫。一次开车中,看到这位可怜的丈夫,就立刻将警笛放在车顶,一路狂追,令其下车接受检查。我到现在才知道,美国警察还可以客串交警。第三集看到前妻与现任丈夫开车的时候,又拉上警笛,追上后令那丈夫下车,并进入车内和前妻交谈,最后还竟然强吻了她,也没见那个可怜的丈夫发飙,主角太彪悍了哇。
另外感觉美国老夫少妻好像很正常,不像在中国那样会受到道德谴责,主角的父亲也娶了一个很年轻的女孩,看来杨翁的婚事在美国那边不算什么。
|
2007年11月17日 星期六 上午 03:02
9.4 函数重载解析细节
如果函数实参的类型是在一个名字空间中声明的,则该名字空间中与被调用函数同名的成员函数也将被加入到候选函数集中.
namespace NS {
lass C { /* ... */ };
oid takeC( C& );
}
//COBJ的类型是在名字空间NS中被声明的类C
NS::C cobj;
int main(){
//在调用点没有takeC()可见
take(cobj); //ok:调用NS::takeC(C&)
//因为实参类型是NS::C
//所以考虑在名字空间NS中声明的takeC()
return 0;
}
在嵌套的域中被声明的函数隐藏了而不是重载了外围域中的同名函数.这种情况下的候选函数是在嵌套域中被声明的函数,即没有被该函数调用隐藏的函数.
char* format(int);
void g(){
char* format(double);
char* format(char*);
format(3); //调用format(double)
}
在调用点上可见的using声明也可以引入候选函数.
namespace libs_R_us{
int max(int,int);
double max(double,double);
}
char max(char,char);
void func(){
//名字空间的函数不可见
//这三个调用分别调用全局函数max(char,char)
max(97,65);
max(35.5,76.6);
max('J','L');
}
下面不同
namespace libs_R_us{
int max(int,int);
double max(double,double);
}
char max(char,char);
using libs_R_us::max;
void func(){
max(97,65); //调用 libs_R_us::max(int,int)
max(35.5,76.6); //调用 libs_R_us::max(double,double)
max('J','L'); //调用::max(char,char)
}
*******************************************************
namespace libs_R_us{
int max(int,int);
double max(double,double);
}
char max(char,char);
void func(){
//using 声明
//全局max(char,char)被隐藏
using libs_R_us::max;
max(97,65); //调用 libs_R_us::max(int,int)
max(35.5,76.6); //调用 libs_R_us::max(double,double)
max('J','L'); //调用libs_R_us::max(int,int)
}
using指示符使名字空间可见就好像它们是在名字空间之外,在定义名字空间的位置上被声明的一样.
namespace libs_R_us{
int max(int,int);
double max(double,double);
}
char max(char,char);
void func(){
//using 声明
using namespace libs_R_us;
max(97,65); //调用 libs_R_us::max(int,int)
max(35.5,76.6); //调用 libs_R_us::max(double,double)
max('J','L'); //调用::max(char,char)
}
9.4.2 可行函数
对于有多个参数的候选函数来说,只要函数调用中的一个实参不能被转换成候选函数参数表中相应的参数,它就将马上被排除在可行函数集合之外.
9.4.3 最佳可行函数
最佳可行函数是具有与实参类型匹配最好的参数的可行函数.最佳可行函数是满足下列条件的可行函数:
1. 用在实参上的转换不比调用其他可行函数所需要的转换更差
2. 在某些实参上的转换要比其他可行函数对该参数的转换更好.
int arr[3];
void putValues(const int*);
int main(){
putValues(arr); //在转换序列中有2个转换
//数组到指针,限定修饰转换
return 0;
}
转换序列的等级是构成该序列最坏转换的等级
左值转换--->提升或者标准转换--->限定修饰转换
左值转换:从左值到右值的转换,从数组到指针的转换和从函数到指针的转换.
namespace libs_R_us {
int max(int,int);
double max(double,double);
}
//using声明
using libs_R_us::max;
void func()
{
char c1,c2;
max(c1,c2); //调用libs_R_us::max(int,int)
}
提升比标准转换好,所以选择int
限定转换具有精确匹配的等级,但是如果两个转换序列前面都相同,只是一个序列尾部有个额外的限定转换,则另一个没有额外限定转换的序列比较好.
void reset(int *);
void reset(const int *);
int *pi;
int main(){
reset(pi); //没有限定转换的好,选择void reset(int *)
return 0;
#include <vector>
void manip(vector<int> &);
void manip(const vector<int> &);
vector<int>f();
extern vector<int> vec;
int main(){
manip(vec); // manip(vector<int> &)
manip(f()); //manip(const vector<int> &),f()是一个右值,不能被初始化
return 0;
} |
2007年11月17日 星期六 上午 03:01
9.3 参数类型转换
在函数重载解析的第二步中,编译器确定"可以应用在函数调用的实参上的,将其转换成每个可行函数中相应参数类型"的转换,并将其划分等级.这种等级有二钟可能:
1. 精确匹配
实参与函数参数的类型精确匹配.
2. 与一个类型转换匹配.
实参不直接与参数类型匹配,但是它能转换成这样的类型
3. 无匹配
精确匹配的等级类别可能存在的转换如下:
从左值到右值的转换
从数组到指针的转换
从函数到指针的转换
限定修饰符转换
与一个类型转换匹配分为:提升,标准转换和用户定义的转换.
9.3.1 精确匹配的细节
枚举类型定义了一个唯一的类型,它只与枚举类型中的枚举值以及被声明为该枚举类型的对象精确匹配.
enum Tokens ( INLINE = 128; VIRTUAL = 129; );
Tokens curTok = INLINE;
enum Stat {Fail,Pass};
extern void ff(Tokens);
extern void ff(Stat);
extern void ff(int);
int main(){
ff(Pass); //精确匹配 ff(Stat)
ff(0); //精确匹配ff(int)
ff(curTok); //精确匹配ff(Tokens)
}
当一个函数期望一个按值传递的实参,而该实参又是一个左值的时候,就会执行从左值到右值的转换.
#include <string>
string color("purple");
void print(string);
int main(){
print(color); //精确匹配: 从左值到右值的转换.
return 0;
}
一个引用表示一个左值,所以当一个函数有一个引用参数时,被调用的函数接受一个左值,因此不会有从左值到右值的转换被应用到相应的引用参数的实参上.
int ai[3];
void putValues(int *)
int main(){
// ...
putValues(ai); //精确匹配:从数组到指针的转换
return 0;
}
从函数到指针的转换.
和数组指针一样,函数类型的参数自动被转换成指向函数的指针.函数类型的实参也自动被转换成函数指针类型.即使发生了这种转换,该实参扔被看作是函数指针类型参数的精确匹配
int lexicoCompare(const string &,const string &);
typedef int (*PFI)(const string &,const string &);
void sort(string *,string *, PFI);
string as[10];
int main()
{
// ...
sort(as,as+sizeof(as)/sizeof(a[0]-1),lexicoCompare); //精确匹配:从函数到指针的专函
return 0;
}
限定修饰转换,只影响指针.它将限定修饰符const或volatile加到指针指向的类型上.
int a[5] = { 4454,7864,92,421,938 };
int *pi=a;
bool is_equal(const int *, const int *);
int func(int *parm){
if(is_qual(pi,parm))
//...
return 0;
}
在函数is_equal()被调用之前:实参pi和parm被从int型指针转换成指向const int型的指针.
extern void takeCI(const int);
int main(){
int li=...;
takeCI(li);//无转换发生
return;
}
精确匹配可以用一个显示强制转换强行执行
extern void ff(int);
extern void ff(void *);
ff(0xffbc);//调用ff(int)
强行调用ff(void *)
ff(reinterpert_cast<void *>(0xffbc)); //调用ff(void*)
9.3.2 提升的细节
char,unsigned char或short型的实参被提升为int型
float型的实参被提升到double类型
枚举类型的实参被提升到下列第一个能够表示其所有枚举常量的类型:int unsigned int,long或unsigned long.
布尔型的实参被提升为int型.
枚举型的提升
enum Stat {Fail,Pass};
extern void ff(int);
extern void ff(char);
int main(){
// ok: 枚举常量Pass被提升到int
ff(Pass); //ff(int)
ff(0); //ff(int)
return 0;
}
9.3.3 标准转换的细节
1.整值类型转换
2.浮点转换
3.浮点-整值类型转换
4.指针转换
5.bool转换
extern void manip(long);
extern void manip(float);
int main() {
manip(3.14); //错误:二义性 文字常量是double型的
//manip(float)也不会好到哪里
return 0;
}
显式强制转换赖解决二义性的问题
manip(static_cast<long>(3.14));
或
manip(3.14F)
标准指针转换看起来有些违反直觉.0可以被转换成任何指针类型: 这样的创建的指针值被成为空指针值.同时值0也可以是任何整型常量表达式.
void set(int *);
int main(){
// 从0到int*的指针转换应用到两个实参上
set(0L);
set(0x00);
return 0;
}
枚举类型不是整型,仍为0的枚举型值不能被转换成指针类型.
enum EN { zr = 0 };
set(zr); //错误: zr不能被转换到 int*
常量表达式0属于int,该类型优先考虑,有int的函数不会产生二义性.
最后一种指针转换允许将任何指针类型的实参转换成void*型的参数,因为void*是通用的数据类型指针,所以它可以存放任何数据类型的指针值.
#include <string>
extern void reset(void *);
int func(int *pi,string *ps){
//...
reset(pi); //指针转换: int* 到void*
//...
reset(ps); //指针转换: string* 到void*
return 0;
}
只有指向数据类型的指针才可以用指针标准转换其转换成void*
9.3.4 引用
实参的类型永远不会是引用类型,当实参是一个引用时,该实参是一个左值,它的类型是引用所指的对象的类型.
int i;
int &ri=i;
void print(int);
int main(){
print(i); // int型的左值参数
print(ri); //同样
return 0;
}
标准转换和提升对引用和不是引用一样
int obj;
void frd(double &);
int main(){
frd(obj); //错误: 参数必须是const double &
return 0;
}
引用参数和实参的匹配结果有以下两个可能
1.实参是引用参数的合适的初始值.
2.实参不能初始化引用参数,实参不能被用来调用该函数
int obj;
void frd(double &);
int main(){
frd(obj); //错误: 参数必须是const double &
return 0;
}
实参类型是int,必须被转换成double以匹配引用参数的类型.该转换的结果是个临时值.因为这种引用不是const型的,所以临时值不能被用来初始化该引用.
class B;
void takeB(B&);
B giveB();
int main(){
takeB(give()); //错误:参数必须是const B&
return 0;
}
在两种情况下,如果引用参数是const型的引用,则实参就是参数的精确匹配
void print(int);
void print(int&);
int iobj;
int &ri=iobj;
int main(){
print(iobj); //错误:二义
print(ri); //错误:二义
print(86); //ok:调用print(int)
return 0;
} |
2007年11月17日 星期六 上午 02:59
9.1 重载函数声明
9.1.2 怎样重载一个函数名
在C++中,可以为两个或多个函数提供相同的名字,只要它们的每个参数表唯一就行:
或者是参数的个数不同,或者是参数类型不同.
int max(int,int);
int max(const vector<int> &);
int max(const matri &);
// 重载函数
void print(const string &);
void print(vector<int> &);
// 声明同一个函数
void print(const string &str);
void print(const string &);
usigned int max(int i1,int i2);
int max(int,int); //错误: 只有返回类型不同
// 声明同一个函数
int max( int *ia, int sz );
int max( int *,int = 10 );
// typedef并不引入一个新类型
typedef double DOLLAR;
// 错误:相同参数表,不同返回类型
extern DOLLAR calc( DOLLAR );
extern int calc( double );
//声明同一函数
void f(int);
void f(const int);
// 声明了不同的函数
void f(int*);
void f(const int*);
// 也声明了不同的函数
void f( int& );
void f( const int& );
9.1.4 重载与域
重载函数集合中的全部函数都应在同一个域中声明.例如,一个声明为局部的函数将隐藏而不是重载一个全局域中声明的函数.
#include<string>
void print(const string &);
void print( double ); // overloads print()
void fooBar( int ival )
{
// 独立的域:隐藏print()的两个实例
extern void print( int );
// 错误: print( const string & )
print("Value: ");
print( ival ); //ok:print( int )可见
}
#include <string>
namespace IBM{
extern void print(const string &);
extern void print(double); //重载print()
}
namespace Disney {
//独立的域
//没有重载IBM的print()
extern void print(int);
}
namespace libs_R_us {
int max(int,int);
int max(double,double);
extern void print(int);
extern void print(double);
}
// using声明
using libs_R_us::max;
using libs_R_us::print(double); //错误
void func()
{
max(87,65); //调用libs_R_us::max(int,int)
max(35.5,76.6);//调用libs_R_us::max(double,double);
}
用户不能在using声明中为一个函数指定参数表.
#include <string>
namespace libs_R_us{
extern void print( int );
extern void print( double );
}
extern void print( const string & );
// libs_R_us::print(int) 和 libs_R_us::print(double)
// 重载 print( const string & )
using libs_R_us::print;
void fooBar(int ival)
{
printf("Valus: "); //调用全局printf( const string & )
printf( ival ); // 调用libs_R_us::print(int);
}
namespace libs_R_us{
extern void print( int );
extern void print( double );
}
void print( int );
// libs_R_us::print(int) 和 libs_R_us::print(double)
// 重载 print( const string & )
using libs_R_us::print; //错误:print(int)的重复声明
void fooBar(int ival)
{
printf( ival ); // 哪一个print?::print(int) 还是libs::print?
}
using指示符也是如此,声明引起重载.
9.1.5 extern "c" 和重载函数
链接指示符只能指定重载函数集中的一个函数
// 错误: 在一个重载函数集中有两个 extern "C"函数
extern "C" void print (const char *);
extern "C" void print (int);
class SmallInt {/*...*/};
class BigNum {/*...*/};
//这个C函数可以在C和C++程序中调用
//C++函数可以处理C++类参数
extern "C" double calc(double);
extern SmallInt calc(const SmallInt&);
extern BigNum calc(const BigNum&);
SmallInt si = 8;
int main() {
calc(34);
calc(si);
//...
return 0;
}
9.1.6 指向重载函数的指针
extern void ff(vector<double>);
extern void ff(unsigned int);
// pf1 指向ff(unsigned int)
void (*pf1)(unsigned int)=&ff;
编译器查找重载函数集合里与指针指向的函数类型只有相同的返回类型和参数表的函数
赋值的工作方式与之类似.如果一个重载函数的地址被赋值给一个函数指针,则该函数指针的类型被用来选择赋值符号右边的函数.如果编译器没有找到与指针类型匹配的函数,则赋值就是错误的.
matrix calc(const matrix &);
int calc(int,int);
int (*pc1)(int,int)=0;
int (*pc2)(int,double)=0;
//...
//ok:匹配int calc(int,int)
pc1=&calc;
//错误:无匹配: 无效的第二个参数类型
pc2=&calc;
9.2 重载解析的三个步骤
1.确定函数调用考虑的重载函数的集合,确定函数调用中实参表的属性.
2.从重载函数集合中选择函数,该函数可以在(给出实参个数和类型)的情况下用调用中指定的实参进行调用.
3.选择与调用最匹配的函数. |
2007年11月17日 星期六 上午 02:58
8.5.5 ODR和名字空间成员
一个名字空间可以在多个文件中被声明.
// primer.h
namespace cplusplus_primer {
// ...
void inverse( matrix & );
}
// use1.C
#include "primer.h"
// 在use1.C 中声明 cplusplus_primer::inverse()
// use2.C
#include "primer.h"
// 在use2.C 中声明 cplusplus_primer::inverse()
名字空间成员也是一个全局实体.
符合ODR要求,只能被定义一次.
1. 作为名字空间成员的函数和对象的声明被放在头文件中,该文件将被包含在要使用该名字空间的文件中.
// primer.h
namespace cplusplus_primer {
class matrix { /* ... */ };
// 函数声明
extern matrix :operator+( const matrix& m1, const matrix& m2 );
extern void inverse( matrix & );
//对象声明
extern bool error_state;
}
2. 这些成员的定义可以出现在某一个实现文件中.
// ---- primer.C ----
#include "primer.h"
namespace cplusplus_primer {
// 函数声明
extern matrix :operator+( const matrix& m1, const matrix& m2 )
{ /*...*/ }
extern void inverse( matrix & )
{ /*...*/ }
//对象声明
extern bool error_state = false;
}
extern是可选的
8.5.6 未命名的名字空间
未命名的名字空间的定义局部于一个特定的文件,不能跨越多个文本文件
// ----- SortLib.C -----
namespace {
void swap(double *d1,double *d2){ /*...*/ }
}
可以用swap()的简短格式引用它,没有必要用域操作符引用未命名名字空间的成员.
8.6 使用名字空间成员
8.6.1 名字空间别名
名字空间别名可以用来把一个较短的同义词与一个名字空间名关联起来.
namespace International_Business_Machines
{ /* ... */ }
则
namespace IBM = International_Business_Machines;
名字空间别名也可以指向一个嵌套的名字空间
namespace mlib = cplusplus_primer::MatrixLib;
8.6.2 using声明
using开头,后面是名字空间成员名.using声明中的成员名必须是限定修饰名.
namespace cplusplus_primer {
namespace MatrixLib {
class matrix {/*...*/};
// ...
}
}
// 名字空间成员 matrix的using声明
using cplusplus_primer::MatrixLib::matrix;
在遇到using声明之后,在全局域中或其嵌套的域中使用matrix都将引用该名字空间成员.
在遇到using声明之后,在全局域中或其嵌套的域中使用matrix都将引用该名字空间成员.它有一个域,它引入的名字从该声明开始直到其所在的域结束都是可见的.
有以下特性:
1.它在该域中必须唯一.
2.由外围域中的声明引入的相同名字被隐藏.
3.它被嵌套域中的相同名字的声明隐藏.
8.6.3 using 指示符
我们可以使用using声明让c++以前的库兼容
#include "primer.h"
using cplusplus_primer::matrix;
using cplusplus_primer::inverse;
//因为using声明,名字matrix和inverse可以不加限制修饰地被使用
void func( matrix &m ){
// ...
inverse( m );
}
翻新一个使用名字空间库的新版本
using开头,其次namespace,然后是名字空间名.
// using 指示符: cplusplus_primer 的所有成员都变成可见的
using namespace cplusplus_primer;
//名字matrix和inverse可以不加限制修饰地被使用
void func( matrix &m ){
// ...
inverse( m );
}
下面指出了一些问题
namespace blip {
int bi=16,bj=15,bk=23;
//其他声明
}
int bj=0;
void manip(){
using namespace blip;//using 指示符
++bi; //设置blip::bi为17
++bj; //错误:二义性,全局bj还是blip::bj?
++::bj;//OK
++blip::bj;//OK
int bk=97;//局部bk隐藏blip::bk
++bk;//设置局部bk为98
}
8.6.4 标准名字空间std
#include <vector>
#include <string>
#include <iterator>
int main(){
// 与标准输出绑定的输入流迭代器
istream_iterator<string> infile( cin );
// 标记了"流结束"的输入流迭代器
istream_iterator<string> eos;
// 用cin输入的值初始化svec
vector<string> svec( infile, eos );
// 处理 svec
}
不能通过编译
最简单
using namespace std;
其次
using std::istream_iterator;
using std::string;
using std::cin;
using std::vector;
上面最好放在头文件中. |
2007年11月17日 星期六 上午 02:57
函数被调用之前,函数体必须先被声明
extern为声明但不定义一个对象提供了一个方法
头文件提供了两个安全保证.
第一,保证所有文件都包含同一个全局对象或函数的同一份声明.
第二,如果需要修改声明,则需要改变一个头文件.
// --- token.h ----
typedef unsigned char uchar;
const uchar INLINE=128;
// ...
const uchar LT=...;
const uchar GT=...;
extern uchar lastTok;
extern int addToken( uchar );
inline bool is_relational( uchar tok )
{ return (tok >= LT && tok <= GT); }
// ---- lex.C ----
#include "token.h"
// ...
// ----- token.C -----
#include "token.h"
// ...
第二个考虑是头文件不应该含有非inline函数或对象的定义
// ----- 头文件 -----
const int buf_chunk = 1024;
extern char *const bufp;
// ----- 程序文本文件 -----
char *const bufp = new char[buf_chunk];
虽然bufp被声明为const,但是它的值却无法在编译时刻被计算出来(它的初始化值是一个要求调用库函数的new表达式).如果bufp在头文件被初始化,那么它将在每个包含它的文件中被定义.
8.4.2 auto_ptr
auto_ptr是C++标准库提供的类模板
auto_ptr对象被初始化为指向由new表达式创建的动态分配对象.当auto_ptr对象的生命期结束时,动态分配的对象被自动释放.
#include<memory>
三种形式
auto_ptr< type_pointed_to > identifier( ptr_allocated_by_new );
auto_ptr< type_pointed_to > identifier( auto_ptr_of_same_type );
auto_ptr< type_pointed_to > identifier;
type_pointed_to代表有new表达式创建的对象的类型.
auto_ptr< int > pi(new int(1024));
方式和普通指针相同
if(*pi!=1024)
else *p *=2;
get()返回底层指针 reset重新定义
new (place_address) type -specifier
创建在已经分配好位置的内存
8.5 名字空间定义
定义一个名字空间,从而把库中的名字隐藏在全局名字空间之外.
namespace cplusplus_primer {
class matrix { /*...*/ };
void inverse ( matri & );
}
名字空间成员的名字会自动地与该名字空间名复合或被其限定修饰.
例 cplusplus_primer::matri,cplusplus_primer::inverse()
在程序中我们可以用限定修饰名来使用名字空间成员cplusplus_primer的成员.
void func( cplusplus_primer::matri &m )
{
//...
cplusplus_primer::inverse(m);
return m;
}
名字空间别名,using声明,using指示符
8.5.1 名字空间定义
该名字在它被定义的域中必须是唯一的.
在名字空间名之后是有花括号{}括起来的声明块.类,变量(带有初始化),函数(带有定义)以及模板
该名字在它被定义的域中必须是唯一的.在名字空间名之后是有花括号括起来的声明块.类,变量(带有初始化),函数(带有定义)以及模板.
namespace cplusplus_primer{
class matri { /*...*/ };
void inverse ( matrix & );
matrix operator+ ( const matrix &m1, const matrix &m2 )
{ /*...*/ }
const double pi = 3.1416;
}
名字空间的定义不一定是连续的.
namespace cplusplus_primer{
class matri { /*...*/ };
const double pi = 3.1416;
}
namespace cplusplus_primer{
void inverse ( matrix & );
matrix operator+ ( const matrix &m1, const matrix &m2 )
{ /*...*/ }
}
对生成一个库很有帮助,容易将库的源代码组织成接口和实现部分
// 名字空间的这部分定义了库接口
namespace cplusplus_primer{
class matri { /*...*/ };
const double pi = 3.1416;
void inverse ( matrix & );
matrix operator+ ( const matrix &m1, const matrix &m2 );
}
// 名字空间的这部分定义了库实现
namespace cplusplus_primer{
void inverse ( matrix & )
{ /*...*/ }
matrix operator+ ( const matrix &m1, const matrix &m2 )
{ /*...*/ }
}
使用我们的库的程序可能这样:
// ---- user.C ----
// 定义库的接口
#include "primer.h"
void func( cplusplus_primer::matrix &m )
{
//...
cplusplus_primer::inverse( m );
}
8.5.2 域操作符(::)
//定义库接口
#include "primer.h"
//错误:不能找到matrix的声明
void func( matrix &m );
//定义库接口
#include "primer.h"
class matrix { /* 用户定义 */ }
//OK:找到全局matrix类型
void func( matrix &m );
域操作符也可用来引用全局名字空间的成员.
::menber_name
8.5.3 嵌套名字空间
// ---- primer.h ----
namespace cplusplus_primer {
// 第一个嵌套域
// 定义了库的matrix 部分
namespace MatrixLib {
class matrix { /*...*/ };
const *double pi = 3.1416;
matrix operator+ ( const matrix &m1, const matrix &m2 );
void inverse ( matrix & );
//...
}
// 第二个嵌套域
// 定义了库的Zoology 部分
namespace AnimalLib {
class ZooAnimal { /* ... */ };
class Bear : public ZooAnimal { /* ... */ };
class Raccoon : public Bear { /* ... */ };
// ...
}
}
可以引用
cplusplus_primer::MatrixLib::matrix
cplusplus_primer::MatrixLib::inverse
声明Tyep先找MatrixLib然后才是cplusplus_primer
typedef double Type;
namespace cplusplus_primer {
typedef int Type; // 隐藏 ::Type
namespace MatrixLib {
int val;
// Type: 找到cplusplus_primer 中的声明
int func(Type t) { //该Type为int
double val; // 隐藏 MatrixLib::val
val = ...;
}
//...
}
}
8.5.4 名字空间成员定义
我们也可以在名字空间定义之外定义名字空间成员,注意被外围名字空间名限定修饰
// ---- primer.C ----
#include "primer.h"
//全局域定义
cplusplus_primer::MatrixLib::matrix cplusplus_primer::MatrixLib::operator+
( const matrix& m1, const matrix& m2 )
{ /* ... */ }
operator+()的定义可以使用名字空间成员名的简短形式.注意返回类型必须被限定修饰.
operator+()的参数表和函数体中,任何声明或表达式中都可以使用简短形式的名字空间成员名.
// ---- primer.C ----
#include "primer.h"
//全局域定义
cplusplus_primer::MatrixLib::matrix cplusplus_primer::MatrixLib::operator+
( const matrix& m1, const matrix& m2 )
{
//声明一个类型为 cplusplus_primer::MatrixLib::matrix的局部变量
matrix res;
// calculate the sum of two matrix objects
return res;
}
虽然名字空间成员可以被定义在名字空间定义之外,但只有包围该成员声明的名字空间才可能包含它的定义.例如operator+()可在全局域,cplusplus_primer或MatrixLib中定义
// ---- primer.C ----
#include "primer.h"
namespace cplusplus_primer {
MatrixLib::matrix MatrixLib::operator+
( const matrix& m1, const matrix& m2 ) { /* ... */ }
}
只有当一个名字空间成员在名字空间定义中已经被声明过,它才能在该名字空间定义之外被定义. |
2007年11月16日 星期五 下午 04:53
7.1 概述
7.2 函数原型
7.2.1 函数返回类型
函数类型和内置数组类型不能用做返回类型。
// 非法:数组不能做返回类型
int[10] foo_bar();
类类型和容器类型可以被直接返回
7.2.2 函数参数表
没有任何参数的函数可以用空参数表或含有单个关键字void的参数表来表示
int fork(); // 隐式的void参数表
int fork(void); //等价声明
7.2.3 参数类型检查
C++是一种强类型语言。每个函数调用的实参在编译期间都要经过类型检查。若实参类型与相应的参数类型不匹配,如果有可能,就会应用一个隐式的类型转换。
7.3 参数传递
按值传递是参数传递合理的缺省机制
不适合的情况
·当大型的类对象必须作为参数传递时,对实际的应用程序而言,分配对象并拷贝到栈中的时间和空间开销过大。
·当实参的值必须被修改时。
两种方法,一参数被声明为指针,二参数被声明为引用
7.3.1 引用参数
当参数是引用时,函数接收的是实参的左值而不是值的拷贝。
一,必须将一个参数改变成指针来允许改变实参的值
二,向主调函数返回额外的结果
三,向函数传递大型类对象
对于引用参数,函数可以访问被指定为实参的类对象,而不必在函数的活动记录中拷贝它。
把参数声明为const型的引用,可以避免拷贝用作实参的大型类对象,同时又防止函数修改实参的值。
7.3.2 引用和指针参数的关系
引用必须被初始化为指向一个对象,一旦初始化了,它就不能再指向其他对象,一旦初始化了,它就不能再指向其他对象。
指针可以指向一系列不同的对象也可以什么都不指向。
如果一个参数可能在函数中指向不同的对象,或者这个参数可能不指向任何对象,则必须使用指针参数。
引用参数的一个重要用法是,它允许我们在有效地实现重载操作符的同时,还能保证用法的直观性。
7.3.3 数组参数
在C++中,数组永远不会按值传递,它是传递第一个元素的指针
void putValues(int[10]);被编译器视为 void putValues(int*);
数组的长度与参数声明无关
// 三个等价的putValues()声明
void putValues( int* );
void putValues( int[] );
void putValues( int[ 10 ] );
数组被传递为指针,意味着:
·在被调函数内对参数数组的改变将被应用到数组实参上而不是本地拷贝上。可以通过把参数类型声明为const来表明不希望改变数组元素。
·数组长度不是参数类型的一部分
将参数声明为数组的引用。当参数是一个数组类型的引用时,数组长度成为参数和实参类型的一部分,编译器检查数组实参的长度与在函数参数类型中指定的长度是否匹配。
// 参数为10个int的数组
// parameter is a reference to an array of 10 ints
void putValues(int (&arr)[10]);
int main(){
int i,j[2];
putValues(i); //错误:实参不是10个int的数组
putValues(j); //错误:实参不是10个int的数组
return 0;
}
7.3.4 抽象容器类型参数
容器类型还是声明为引用参数比较好。
引用参数 向函数传递大型类对象
#include <vector>
vector<int>::const_iterator look_up(const vector<int> &vec,int value,int &occurs){
vector<int>::const_iterator res_iter=vec.end();
occurs=0;
for(vector<int>::const_iterator iter=vec.begin();iter!=vec.end();++iter)
if(*iter==value){
if(res_iter==iter)
res_iter=iter;
occurs++;
}
return res_iter;
}
引用参数对操作符号重载非常有意义.
数组参数
#include <iostream>
using namespace std;
const lineLength=12;
void putValues(int *ia,int sz)
{
cout<<"("<<sz<<")<";
for(int i=0;i<sz;++i)
{
if(i%lineLength==0 && i)
cout<<"\n\t";
cout<<ia[i];
if(i%lineLength!=lineLength-1 && i!=sz-1)
cout<<",";
}
cout<<">\n";
}
当参数是一个数组类型的引用时,数组长度成为参数和实参类型的一部分,编译器检查数组实参的长度与在函数参数类型中指定的长度是否匹配.
void putValues(int (&arr)[10]);
int main(){
int i,j[2];
putValues(i);//错
putValues(j);//错,不是10个int的数组
return 0;
}
函数模板是一种"其代码在广泛的不同参数类型上保持不变"的机制.
template <class Type>
void putValues(Type *ia,int sz)
{
}
模板参数 Type 关键字class表示模板参数代表一个类型.
putValues
多维数组的参数类型检查只检查多维数组实参中除了第一维之外的所有维的长度与参数的是否相同
matrix声明成一个二维数组,每行由10个列元素构成,其可以被等价地声明为
int (*matrix)[10] 其括号不能去掉
抽象容器类型参数
#include <iostream>
#include <vector>
using namespace std;
const lineLength=12;
void putValues(vector<int> vec)
{
cout<<"("<<vec.size()<<")<";
for(int i=0;i<vec.size();++i)
{
if(i%lineLength==0 && i)
cout<<"\n\t";
cout<<vec[i];
if(i%lineLength!=lineLength-1 && i!=vec.size()-1)
cout<<",";
}
cout<<">\n";
}
int main(){
int i,j[2];
vector<int> vec1(1);
vec1[0]=i;
putValues(vec1);
vector<int> vec2;
for(int ix=0;ix<sizeof(j)/sizeof(j[0]);++ix)
vec2.push_back(j[ix]);
putValues(vec2);
return 0;
}
void putValues(const vector<int> &)
const的引用
7.3.5 缺省实参
char *screenInit(int height=24,int width=80,char background=' ');
如果提供了实参,则它将覆盖缺省的实参值,否则函数将使用缺省实参值.
函数声明可以为全部或部分参数指定缺省实参,在左边参数的任何缺省实参被提供之前,最右边未初始化参数必须被提供缺省参数.
一个参数只能在一个文件中被指定一次缺省实参.
是最可能使用缺省实参的参数出现在后面.
重新声明函数定义的时候
缺省实参可定义以前为缺省的实参
7.3.6 省略号
用省略号指定函数参数表
它们的出现告诉编译器,当函数被调用时,可以有0个或多个实参,而实参的类型未知.
void foo(parm_list,...);
void foo(...);
参数声明后的逗号是可选的
7.4 返回一个值
如果返回值是一个大型类对象,用引用(或指针)返回类型比按值返回类对象效率要高得多.
返回引用应当注意的两个错误。
1.返回一个指向局部对象的引用。局部对象的生命期随函数的结束而结束。在函数结束后,该引用变成未定义内存的别名。
这种情况下,返回类型应该被声明为非引用类型。
2.函数返回一个左值。对返回值的任何修改都将改变被返回的实际对象。
为防止对引用返回值的无意修改,返回值应该被声明为const
7.6 inline函数
inline函数最好放在头文件中.
7.7 链接指示符:extern "C"
程序员用链接指示符告诉编译器,该函数是用其他的程序设计语言编写的既可以是单一语句(single statement)形式,也可以是复合语句(compound statement)形式
//单一语句形式的链接指示符
extern "C" void exit(int);
//复合语句形式的链接指示符
extern "C" {
int printf(const char* ...);
int scanf(const char* ...);
}
//复合语句形式的链接指示符
extern "C"{
#include<cmath>
}
链接指示符不能出现在函数体中.放在头文件中更合适.
可以用extern "C"链接指示符来使C++函数为C程序可用.
//函数calc()可以被C程序调用
extern "C" double calc(double dparm){/*...*/}
P320练习
7.9 指向函数的指针
7.9.4 函数指针的数组
int (*testCases[10])();
testCases声明为一个拥有10个元素的数组.每个元素都是一个指向函数的函数指针,该函数没有参数,返回类型为int.
7.9.4 函数指针的数组
int (*testCases[10])();
testCases声明为一个拥有10个元素的数组.每个元素都是一个指向函数的函数指针,该函数没有参数,返回类型为int.
//typedef 是声明更易读
typedef int (*PFV)();
PFV testCases[10];
初始化
int lexicoCompare( const string &, const string & );
int sizeCompare( const string &, const string & );
typedef int (*PFI2S) ( const string &, const string & );
PFI2S compareFuncs[2]=
{
lexicoCompare,
sizeCompare
}
也可以声明
PFI2S (*pfCompare)[2]=&compareFuncs;
调用lexicoCompare
//两个等价的调用
pfCompare[0](string1,string2);//编写
((*pfCompare)[0])(string1,string2);//显式
P330 例子 |
2007年11月16日 星期五 下午 04:51
6.12 生成文本文件位置map
一般地,当我们只想知道一个值是否存在时,set最有用处。希望存储(也可能修改)一个相关的值时,map最为有用。
在这两种情况下,元素都是以有序关系存储的,以此支持高效率的存储和检索。
string query("pickle");
vector<location> *locat;
// 返回与"pickle"相关的vector<location>
locat=text_map[query];
为了使用map,我们必须包含相关的头文件:
#include <map>
6.12.1 定义并生成map
为定义map对象,我们至少要指明键与值的类型。
map<string,int>word_count;
typedef pair<short,short> location;
typedef vector<location> loc;
map<string,loc*> text_map;
#include <map>
#include <string>
map<string,int> word_count;
word_count[string("Anna")]=1;
word_count[string("Danny")]=1;
word_count[string("Beth")]=1;
// 等等
其中值先被缺省构造函数初始化,再被赋值,效率低下。
// the preferred single element insertion method
word_count.insert(
map<string,int>::
value_type(string("Anna"),1)
);
6.12.2 查找并获取map中的元素
下标操作符给出了获取一个值的最简单方法。
// map<string,int> word_count;
int count = word_count["wrinkles"];
有缺点,只有当map中存在这样一个键的实例时,该代码才会正常。如果不存在这样的实例,使用下标操作符会引起插入一个实例。
string("wrinkles"),0
有两种方法:
1.Count(keyValue):count()返回map中keyValue出现的次数。如果返回值非0,我们就可以安全地使用下标操作符。
int count=0;
if(word_count.count("wrinkles"))
count=word_count("wrinkles");
2.Find(keyValue):如果实例存在,则find()返回指向该实例的iterator.如果不存在,则返回等于end()的iterator。
int count=0;
map<string,int>::iterator it=word_count.find("wrinkles");
if(it!=word_count.end())
count=(*it).second;
6.12.3 对map进行迭代
6.12.5 从map中删除元素
为了删除一个独立的元素,我们传递给erase()一个键值或iterator。为了删除一列元素,我们传递给erase()一对lieator
#include <map>
multimap<key_type,value_type> multimapName;
// 按string索引,存有list<string>
multimap<string,list<string>> synonyms;
#include <set>
multiset<type> multisetName;
对于multimap或multiset,一种迭代策略是联合使用find()返回的iterator(指向第一个实例)和有count()返回的值。
#include <map>
#include <string>
void code_fragment()
{
multimap< string, string > authors;
string search_item( "Alain de Botton" );
// ...
int number = authors.count( search_item );
multimap< string,string >::iterator iter;
iter = authors.find( search_item );
for ( int cnt = 0; cnt < number; ++cnt, ++iter )
do_something( *iter );
// ...
}
更精彩的策略是使用由multiset和multimap的特殊操作equal_range()返回的iterator对值
如果这个值存在,则第一个iterator指向该值的第一个实例,且第二个iterator指向这个值的最后实例的下一位置。
#include <map>
#include <string>
#include <utility>
void code_fragment()
{
multimap< string, string > authors;
// ...
string search_item( "Haruki Murakami" );
while ( cin && cin >> search_item )
switch ( authors.count( search_item ))
{
// 不存在,继续往下走
case 0:
break;
// 只有一项,使用普通的find()操作
case 1: {
multimap< string,string >::iterator iter;
iter = authors.find( search_item );
// do something with element
break;
}
// 出现多项...
default:
{
typedef multimap< string,string >::iterator iterator;
pair< iterator, iterator > pos;
// pos.first 指向第一个出现
// pos.second 指向值不再出现的位置
pos = authors.equal_range( search_item );
for ( ; pos.first != pos.second; pos.first++ )
// 对每个元素进行操作
}
}
}
插入和删除操作与关联容器set和map相同。equal_range()可以用来痛殴共iterator对,以便便基础要被删除的多个元素的范围
#include <multimap>
#include <string>
typedef multimap< string, string >::iterator iterator;
pair< iterator, iterator > pos;
string search_item( "Kazuo Ishiguro" );
// authors是一个 multimap<string,string>
// 这等价于authors.erase( search_item );
pos = authors.equal_range( search_item );
authors.erase( pos.first, pos.second );
每次插入增加一个元素
typedef multimap<string,string>::value_type
multimap<string,string> authors;
// 引入Barth的第一个键
authors.insert( valType(
string( "Barth, John" ),
string( "Sot-Weed Factor" )));
// 引入Barth 下的第二个键
authors.insert( valType(
string( "Barth, John" ),
string( "Lost in the Funhouse" )));
不支持下标操作
authors[ "Barth, John" ]; // 错误: multimap
头文件 #include <stack>
empty() 是否为空,真返回true,否则返回false
size() 栈中元素的个数
pop() 删除,但不返回栈顶元素
top() 返回,但不删除栈顶元素
push(item) 放入新的栈顶元素
6.17 队列和优先级队列
#include <queue>
empty()
sieze()
pop() 删除,但不返回对手元素,在priority中,队首元素代表优先级最高的元素。
front() 返回,但不删除对首元素。
back() 返回,但不删除队尾元素
top() 返回,但不删除priority_queue的优先级最高的元素。只能应用在priority_queue上
push(item) 在队尾放入一个新元素:对于priority_queue,将根据优先级排序 |
2007年11月16日 星期五 下午 04:51
6.6.1 删除操作
erase()方法
pop_back()方法
string searchValue("Quasimodo");
list<string>::iterator iter=find(slist.begin(),slist.end(),searchValue);
if(iter!=slist.end())
slist.erase(iter);
删除由iterator标记的一段范围内的元素
//删除容器中的所有元素
slist.erase(slist.begin(),slist.end());
//删除由iterator标记的一段范围内的元素
list<string>::iterator first,last;
first=find(slist.begin(),slist.end(),val1);
last=find(slist.begin(),slist.end(),val2);
//...检查first和last的有效性
slist.erase(first,last);
6.6.2 赋值和对换
#include <list>
#include <vector>
int ia[ 6 ] = { 0, 1, 2, 3, 4, 5 };
vector<string> svec;
list<double> dlist;
// the associated header file
#include <algorithm>
// 如果找到,find()返回指向该元素的iterator
// 对于数组,返回指针
vector<string>::iterator viter;
list<double>::iterator liter;
int *pia;
pia = find( &ia[0], &ia[6], some_int_value );
liter = find( dlist.begin(), dlist.end(), some_double_value );
viter = find( svec.begin(), svec.end(), some_string_value );
=和swap是不同的
vector <string,allocator> *retrieve_text(){
string file_name;
cout<<"please enter file name: ";
cin>>file_name;
ifstream infile(file_name.c_str(), ios::in);
if(!infile){
cerr<<"oops! unable to open file "
<<file_name<<"--bailing out!\n";
exit(-1);
}
else cout<<'\n';
vector<string,allocator> *lines_of_text=new vector<string,allocator>;
string textline;
typedef pair<string::size_type,int> stats;
stats maxline;
int linenum=0;
while(getline(infile,textline,'\n')){
cout<<"line read:"<<textline<<'\n';
if(maxline.first<<textline.size()){
maxline.first=textline.size();
maxline.second=linenum;
}
line_of_text->push_back(textline);
linenum++;
}
return lines_of_text;
}
6.8 找到一个子串
string类提供了一套查找函数,都以find的各种变化形式命名。
find()是最简单的实例,给出一个字符串,它返回匹配子串的第一个字符的索引位置。或者返回一个特定的值
使用string::size_type保存从find()返回的索引值。
find_first_of()查找与被搜索字符串中任意一个字符相匹配的第一次出现,并返回它的索引位置。
substr()操作生成现有string对象的子串的一个拷贝。它的第一个参数指明开始的位置。第二个可选的参数指明子串的长度。
string::npos 表明没有位置,对应find函数
rfind()查找最后(即最右)的指定子串的出现。
find_first_not_of()查找第一个不与要搜索字符串的任意字符相匹配的字符
find_last_of()查找字符串中的“与搜索字符串任意元素相匹配”的最后一个字符。
find_last_not_of()查找字符串中的“与搜索字符串任意字符全不匹配”的最后一个字符。
6.9 处理标点符号
6.10 任意其它格式的字符串
6.11 其他string操作
basic_string::erase
iterator erase(iterator first, iterator last);
iterator erase(iterator it);
basic_string& erase(size_type p0 = 0, size_type n = npos);
string_object.insert( position, new_string );
insert函数 assign函数 append函数 swap函数
数组溢出有std::out_of_range异常
Compare函数 replace函数 |
2007年11月16日 星期五 下午 04:51
顺序容器 List Vector deque(双端队列)
第三个顺序容器为双端队列deque,发音为“deck”,它提供了与vector相同的行为,但是对首元素的有效插入和删除提供了特殊的支持。
关联容器 map(映射) set(集合)
set包含一个单一键值,有效支持关于元素是否存在的查询。
map和set都只包含每个键的唯一出现,即每个键只允许出现一次。multimap(多映射)和mutiset(多集合)支持同一个键的多次出现。
6.1 我们的文本查询系统
vector 表示一段连续的内存区域,每个元素被顺序存储在这段内存中
list 表示非连续的内存区域,通过一对指向首尾元素的指针双向连接起来
·如果我们需要随机访问一个容器,则vector要比list好得多。
·如果我们已知要存储元素的个数,则vector又是一个比list好的选择。
·如果我们需要的不只是在容器两端插入和删除元素,则list显然要比vector好。
·除非我们需要在容器首部插入和删除元素,否则vector要比deque好。
对于小的数据类型,vector的性能要比list好得多,而对于大型的数据类型则相反,list的性能要好得多
6.4 定义一个顺序容器
头文件:
#include <vector>
#include <list>
#include <deque>
#include <map>
#include <set>
容器对象的定义以容器类型的名字开始,后面是所包含的元素的实际类型.
vector<string> svec;
list<int> ilist;
empty()操作符判断是否为空.
push_back()方法为插入到容器的尾部.
push_front()方法插入到容器的首部,list和deque支持
另外,我们或许希望为容器指定一个显式的长度,长度可以是常量,也可以是非常量表达式:
容器中的每个元素都被初始化为“该类型相关联的缺省值”。对于整数,将用缺省值0初始化所有元素。对于string类,每个元素都将用string的缺省构造函数初始化。
我们还可以指定一个值,并用它来初始化每个元素。
定义容器的大小
list<int> ilist(list_size);
可初始化容器
list<int> ilist(list_size,-1);
vector<string> svec(24,"pooh");
重新定义容器长度
svsc.resize(2*svec.size());
//将新元素初始化为"piglet"
svec.resize(2*svec.size(),"piglet")
可用现有的容器对象初始化一个新的容器对象.
vector<string>avec2(avec);
list<int>ilist2(ilist)
每个容器支持一组关系操作符,我们可以用来比较两个元素,这些关系操作符分别是:
等于、不等于、小于、大于、小于等于以及大于等于。
如果所有元素相等且两个容器含有相同数目的元素,则两个容器相等。否则它们不相等。
第一个不相等元素的比较决定了两个容器的小于或者大于关系。
我们能够定义的容器的类型有三个限制
·元素类型必须支持等于操作符
·元素类型必须支持小于操作符
·元素类型必须支持一个缺省值
iterator
设其对象为iter,++iter向前移动迭代器,使其指向下一个元素.*iter指向元素的值
容器的begin(),end()成员函数返回一个相应的iterator
for(iter=container.begin();iter!=container.end();++iter)
do_something_with_element(*iter);
iterator的定义
//vector<string> vec;
vector<string>::iterator iter=vec.begin();
vector<string>::iterator iter_end=vec.end();
iterator算术运算只适用于vector或deque,不适用于list
6.6 顺序容器操作
string son("Danny");
list<string>::iterator iter;
iter=find(slist.begin(),slist.end(),son);
slist.insert(iter,spouse);
插入到iterator指向的位置的前面
insert()方法插入指定数量的元素
svec.insert(svec.begin(),10,anna);
插入待插入值的范围
svec_two.insert(svec_two.begin()+svec_two.size()/2,svec.begin(),svec.end()) |
2007年11月16日 星期五 下午 04:49
5.1 简单语句和复合语句
复合语句是由一对花括号括起来的语句序列。复合语句被视为一个独立的单元,它可以出现在程序中任何单个语句可以出现的地方。
5.2 声明语句
声明语句出现在被定义对象首次被使用的局部域内。
5.3 if语句
if语句的语法形式
if ( condition )
statement
在condition中定义的对象,只在与if相关的语句或语句块中可见。
if-else的语法形式
if ( condition )
statement1
else
statement2
作为程序员,我们将会在某个地方犯错误--有时甚至是很愚蠢的错误。如果错误发生了,最重要的是接受发生错误的事实,并保持警惕性,在条件允许的情况下尽可能严格的测试和检查代码。
5.4 switch语句
程序员什么时候希望省略break语句,允许程序执行多个case标签?
一种情况是,两个或多个值由相同的动作序列来处理。
或者强调这种情形代表的是一个要被匹配的范围:
default标签给出了无条件else子句的等价体。
isalpha()是标准C库的一个例程:如果它的参数是一个英文字母,则返回值true。为了使用它,程序员必须包含系统头文件ctype.h
把一条声明语句放在case或default相关联的语句中是非法的,除非它被放在一个语句块中。
case illegal_definition:
// 错误:声明语句必须被放在语句块中
sring file_name=get_file_name();
//...
break;
5.5 for循环语句
for循环的语法形式如下:
for(init-statement;condition;expression)
statement
它的计算顺序如下:
1.在循环开始,执行一次init-statement.
2.执行condition。如果它的计算结果为true,则执行复合语句。
3.执行expression
在init-statement中可以定义多个对象,但只能出现一个声明语句,因此所有对象都必须是相同的类型。
5.6 while语句
while循环的语法形式如下:
while ( condition )
statement
1.计算condition
2.如果condition为true,则执行statement
5.7 do while语句
|
2007年11月16日 星期五 下午 04:35
4.1 什么是表达式
4.3 等于 关系或逻辑操作符
一个对象只能被初始化一次,也就是它被定义的时候,但在程序中可以被赋值多次.
赋值操作符的左操作数必须是左值--即,它必须有一个相关联的、可写的地址值。
数组对象本身不能被赋值,只有它包含的元素才能被赋值。
4.5 递增和递减操作符
我们可以用一个算术数据类型的值对复数初始化或赋值,但是相反的情形不被自动支持。
读取复数的一部分可以用成员访问语法real()和imag()
double re = complex_obj.real();
double im = complex_obj.imag();
或者等价的非成员语法
//等价于上面的成员语法
double re = real( complex_obj );
double im = imag( complex_obj );
复数支持四种复合赋值操作符:+=,-=,*=,/=
sizeof操作符的作用是返回一个对象或类型名的字节长度
三种形式:
sizeof (type name);
sizeof ( object );
sizeof object;
返回值的类型时size_t,这是一种与机器相关的typedef定义,我们可以在cstddef头文件中找到它的定义.
应用在指针类型上的sizeof操作符返回的是包含该类型地址所需的内存长度.
应用在引用类型上的sizeof操作符返回的是包含被引用对象所需的内存长度.
sizeof操作符是在编译时刻计算,因此可以被看作常量表达式
动态分配一个对象数组
int *pia=new int[10];
从空闲存储区中分配一个数组,其中含有10个int型对象,并用它的地址初始化pin,而数组的元素没有被初始化(除了类对象的缺省构造函数).
所有从空闲存储区分配的对象都是未命名的,new表达式并不返回实际被分配的对象,而是返回这个对象的地址.
使用完后必须释放所占空间.
逗号表达式是一系列由逗号分开的表达式,这些表达式从左向右计算,逗号表达式的结果是最右边表达式的值.
bitset 操作
test(pos) pos位是否为1?
any() 任意位是否为1?
none() 是否没有位为1?
count() 值是1的位的个数
size() 位元素的个数
[pos] 访问pos位
flip() 翻转所有的位
flip(pos) 翻转pos位
set() 将所有位置1
set(pos) 将pos位值1
reset() 将所有位置0
reset(pos) 将pos位置0
#include <bitset>
bitset三种声明方式.
bitset<32>bitvec;
bool is_set=bitvec.any();
bool is_not_set=bitvec.none();
int bits_set=bitvec.count();
显示转换
static_cast<type>(value);
显示类型转换
//指示编译器把double转换成int
ival = static_cast< int >( 3.541 ) + 3;
4.14.1 隐式类型转换
1.在混合类型的算术表达式中,最宽的数据类型成为目标转换类型.
2.用一种类型的表达式赋值给另一种类型的对象.目标转换类型是被赋值对象的类型.
3.把一个表达式传递给一个函数调用,表达式的类型与形式参数的类型不相同.目标转换类型是形式参数的类型
4.把一个函数返回一个表达式,表达式的类型与返回类型不相同,目标转换类型是函数的返回类型
4.14.2 算数转换
两条通用的指导原则:
1.为防止精度损失,如果必要的话,类型总是被提升为较宽的类型.
2.所有含有小于整数的有序类型的算术表达式.在计算之前,其类型都会被转换成整型.
4.14.3 显式转换
显式转换也被称为强制类型转换,包括下列命名的强制类型转换操作符:static_cast、dynamic_cast、const_cast、和reinterpret_cast。
在C++中,不存在从void*型指针到特殊类型的指针之间的自动转换.
把void*型的指针赋值给任何任意显式类型时,C++要求显式强制转换.
执行显式强制转换的第二个原因是希望改变通常的标准转换.
进行显式强制转换的第三个原因是要避免出现多种转换可能的歧义情况
显式转换符号的一般形式如下:
cast-name< type >( expression );
enum mumble { first = 1, second, third };
extern int ival;
mumble mums_the_word = static_cast< mumble >( ival );
只有当ival的值为1,2,3的时候才会正确.
reinterpre_cast通常对于操作数的位模式执行一个比较低层次的重新解释.它的正确性很大程度上依赖于程序员的主动管理.
4.14.4 旧式强制类型转换
只有当我们为C语言或标准C++之前的编译器编写代码时才使用这种语法.
// C++强制转换符号
type(expr);
// C语言强制转换符号
(type)expr; |
|
|
summericeyl
男, 27岁
江西
上次登录: 3月 4日
加为好友
|