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

小说软件哪个好用免费  时间:2021-01-21  阅读:()

文件状态[]草稿文件[√]正式文件[]更改正式文件文件标识:当前版本:1.
0作者:林锐博士完成日期:2001年7月24日版本历史版本/状态作者参与者起止日期备注V0.
9草稿文件林锐2001-7-1至2001-7-18林锐起草V1.
0正式文件林锐2001-7-18至2001-7-24朱洪海审查V0.
9,林锐修正草稿中的错误目录前言6第1章文件结构111.
1版权和版本的声明111.
2头文件的结构121.
3定义文件的结构131.
4头文件的作用131.
5目录结构14第2章程序的版式152.
1空行152.
2代码行162.
3代码行内的空格172.
4对齐182.
5长行拆分192.
6修饰符的位置192.
7注释202.
8类的版式21第3章命名规则223.
1共性规则223.
2简单的Windows应用程序命名规则233.
3简单的Unix应用程序命名规则25第4章表达式和基本语句264.
1运算符的优先级264.
2复合表达式274.
3if语句274.
4循环语句的效率294.
5for语句的循环控制变量304.
6switch语句304.
7goto语句31第5章常量335.
1为什么需要常量335.
2const与#define的比较335.
3常量定义规则335.
4类中的常量34第6章函数设计366.
1参数的规则366.
2返回值的规则376.
3函数内部实现的规则396.
4其它建议406.
5使用断言416.
6引用与指针的比较42第7章内存管理447.
1内存分配方式447.
2常见的内存错误及其对策447.
3指针与数组的对比457.
4指针参数是如何传递内存的477.
5free和delete把指针怎么啦507.
6动态内存会被自动释放吗507.
7杜绝"野指针"517.
8有了malloc/free为什么还要new/delete527.
9内存耗尽怎么办537.
10malloc/free的使用要点547.
11new/delete的使用要点557.
12一些心得体会56第8章C++函数的高级特性578.
1函数重载的概念578.
2成员函数的重载、覆盖与隐藏608.
3参数的缺省值638.
4运算符重载648.
5函数内联658.
6一些心得体会68第9章类的构造函数、析构函数与赋值函数699.
1构造函数与析构函数的起源699.
2构造函数的初始化表709.
3构造和析构的次序729.
4示例:类String的构造函数与析构函数729.
5不要轻视拷贝构造函数与赋值函数739.
6示例:类String的拷贝构造函数与赋值函数739.
7偷懒的办法处理拷贝构造函数与赋值函数759.
8如何在派生类中实现类的基本函数759.
9一些心得体会77第10章类的继承与组合7810.
1继承7810.
2组合80第11章其它编程经验8211.
1使用const提高函数的健壮性8211.
2提高程序的效率8411.
3一些有益的建议85参考文献87附录A:C++/C代码审查表88附录B:C++/C试题93附录C:C++/C试题的答案与评分标准97前言软件质量是被大多数程序员挂在嘴上而不是放在心上的东西!
除了完全外行和真正的编程高手外,初读本书,你最先的感受将是惊慌:"哇!
我以前捏造的C++/C程序怎么会有那么多的毛病"别难过,作者只不过比你早几年、多几次惊慌而已.
请花一两个小时认真阅读这本百页经书,你将会获益匪浅,这是前面N-1个读者的建议.

一、编程老手与高手的误区自从计算机问世以来,程序设计就成了令人羡慕的职业,程序员在受人宠爱之后容易发展成为毛病特多却常能自我臭美的群体.

如今在Internet上流传的"真正"的程序员据说是这样的:真正的程序员没有进度表,只有讨好领导的马屁精才有进度表,真正的程序员会让领导提心吊胆.

真正的程序员不写使用说明书,用户应当自己去猜想程序的功能.
真正的程序员几乎不写代码的注释,如果注释很难写,它理所当然也很难读.

真正的程序员不画流程图,原始人和文盲才会干这事.
真正的程序员不看参考手册,新手和胆小鬼才会看.
真正的程序员不写文档也不需要文档,只有看不懂程序的笨蛋才用文档.

真正的程序员认为自己比用户更明白用户需要什么.
真正的程序员不接受团队开发的理念,除非他自己是头头.
真正的程序员的程序不会在第一次就正确运行,但是他们愿意守着机器进行若干个30小时的调试改错.

真正的程序员不会在上午9:00到下午5:00之间工作,如果你看到他在上午9:00工作,这表明他从昨晚一直干到现在.

……具备上述特征越多,越显得水平高,资格老.
所以别奇怪,程序员的很多缺点竟然可以被当作优点来欣赏.
就象在武侠小说中,那些独来独往、不受约束且带点邪气的高手最令人崇拜.
我曾经也这样信奉,并且希望自己成为那样的"真正"的程序员,结果没有得到好下场.

我从读大学到博士毕业十年来一直勤奋好学,累计编写了数十万行C++/C代码.
有这样的苦劳和疲劳,我应该称得上是编程老手了吧我开发的软件都与科研相关(集成电路CAD和3D图形学领域),动辄数万行程序,技术复杂,难度颇高.
这些软件频频获奖,有一个软件获得首届中国大学生电脑大赛软件展示一等奖.
在1995年开发的一套图形软件库到2000年还有人买.
罗列出这些"业绩",可以说明我算得上是编程高手了吧可惜这种个人感觉不等于事实.
读博期间我曾用一年时间开发了一个近10万行C++代码的3D图形软件产品,我内心得意表面谦虚地向一位真正的软件高手请教.
他虽然从未涉足过3D图形领域,却在几十分钟内指出该软件多处重大设计错误.
让人感觉那套软件是用纸糊的华丽衣服,扯一下掉一块,戳一下破个洞.
我目瞪口呆地意识到这套软件毫无实用价值,一年的心血白化了,并且害死了自己的软件公司.

人的顿悟通常发生在最心痛的时刻,在沮丧和心痛之后,我作了深刻反省,"面壁"半年,重新温习软件设计的基础知识.
补修"内功"之后,又觉得腰板硬了起来.
博士毕业前半年,我曾到微软中国研究院找工作,接受微软公司一位资深软件工程师的面试.
他让我写函数strcpy的代码.

太容易了吧错!
这么一个小不点的函数,他从三个方面考查:(1)编程风格;(2)出错处理;(3)算法复杂度分析(用于提高性能).
在大学里从来没有人如此严格地考查过我的程序.
我化了半个小时,修改了数次,他还不尽满意,让我回家好好琢磨.
我精神抖擞地进"考场",大汗淋漓地出"考场".
这"高手"当得也太窝囊了.
我又好好地反省了一次.

我把反省后的心得体会写成文章放在网上传阅,引起了不少软件开发人员的共鸣.
我因此有幸和国产大型IT企业如华为、上海贝尔、中兴等公司的同志们广泛交流.
大家认为提高质量与生产率是软件工程要解决的核心问题.
高质量程序设计是非常重要的环节,毕竟软件是靠编程来实现的.

我们心目中的老手们和高手们能否编写出高质量的程序来不见得都能!
就我的经历与阅历来看,国内大学的计算机教育压根就没有灌输高质量程序设计的观念,教师们和学生们也很少自觉关心软件的质量.
勤奋好学的程序员长期在低质量的程序堆中滚爬,吃尽苦头之后才有一些心得体会,长进极慢,我就是一例.

现在国内IT企业拥有学士、硕士、博士文凭的软件开发人员比比皆是,但他们在接受大学教育时就"先天不足",岂能一到企业就突然实现质的飞跃.
试问有多少软件开发人员对正确性、健壮性、可靠性、效率、易用性、可读性(可理解性)、可扩展性、可复用性、兼容性、可移植性等质量属性了如指掌并且能在实践中运用自如.
"高质量"可不是干活小心点就能实现的!

我们有充分的理由疑虑:(1)编程老手可能会长期用隐含错误的方式编程(习惯成自然),发现毛病后都不愿相信那是真的!

(2)编程高手可以在某一领域写出极有水平的代码,但未必能从全局把握软件质量的方方面面.

事实证明如此.
我到上海贝尔工作一年来,陆续面试或测试过近百名"新""老"程序员的编程技能,质量合格率大约是10%.
很少有人能够写出完全符合质量要求的if语句,很多程序员对指针、内存管理一知半解,…….

领导们不敢相信这是真的.
我做过现场试验:有一次部门新进14名硕士生,在开欢迎会之前对他们进行"C++/C编程技能"摸底考试.
我问大家试题难不难所有的人都回答不难.
结果没有一个人及格,有半数人得零分.
竞争对手公司的朋友们也做过试验,同样一败涂地.

真的不是我"心狠手辣"或者要求过高,而是很多软件开发人员对自己的要求不够高.

要知道华为、上海贝尔、中兴等公司的员工素质在国内IT企业中是比较前列的,倘若他们的编程质量都如此差的话,我们怎么敢期望中小公司拿出高质量的软件呢连程序都编不好,还谈什么振兴民族软件产业,岂不胡扯.

我打算定义编程老手和编程高手,请您别见笑.
定义1:能长期稳定地编写出高质量程序的程序员称为编程老手.
定义2:能长期稳定地编写出高难度、高质量程序的程序员称为编程高手.

根据上述定义,马上得到第一推论:我既不是高手也算不上是老手.
在写此书前,我阅读了不少程序设计方面的英文著作,越看越羞惭.
因为发现自己连编程基本技能都未能全面掌握,顶多算是二流水平,还好意思谈什么老手和高手.
希望和我一样在国内土生土长的程序员朋友们能够做到:(1)知错就改;(2)经常温故而知新;(3)坚持学习,天天向上.
二、本书导读首先请做附录B的C++/C试题(不要看答案),考查自己的编程质量究竟如何.
然后参照答案严格打分.

(1)如果你只得了几十分,请不要声张,也不要太难过.
编程质量差往往是由于不良习惯造成的,与人的智力、能力没有多大关系,还是有药可救的.
成绩越差,可以进步的空间就越大,中国不就是在落后中赶超发达资本主义国家吗只要你能下决心改掉不良的编程习惯,第二次考试就能及格了.

(2)如果你考及格了,表明你的技术基础不错,希望你能虚心学习、不断进步.
如果你还没有找到合适的工作单位,不妨到上海贝尔试一试.

(3)如果你考出85分以上的好成绩,你有义务和资格为你所在的团队作"C++/C编程"培训.
希望你能和我们多多交流、相互促进.
半年前我曾经发现一颗好苗子,就把他挖到我们小组来.

(4)如果你在没有任何提示的情况下考了满分,希望你能收我做你的徒弟.

编程考试结束后,请阅读本书的正文.
本书第一章至第六章主要论述C++/C编程风格.
难度不高,但是细节比较多.
别小看了,提高质量就是要从这些点点滴滴做起.
世上不存在最好的编程风格,一切因需求而定.
团队开发讲究风格一致,如果制定了大家认可的编程风格,那么所有组员都要遵守.
如果读者觉得本书的编程风格比较合你的工作,那么就采用它,不要只看不做.
人在小时候说话发音不准,写字潦草,如果不改正,总有后悔的时候.
编程也是同样道理.

第七章至第十一章是专题论述,技术难度比较高,看书时要积极思考.
特别是第七章"内存管理",读了并不表示懂了,懂了并不表示就能正确使用.
有一位同事看了第七章后觉得"野指针"写得不错,与我切磋了一把.
可是过了两周,他告诉我,他忙了两天追查出一个Bug,想不到又是"野指针"出问题,只好重读第七章.

光看本书对提高编程质量是有限的,建议大家阅读本书的参考文献,那些都是经典名著.

如果你的编程质量已经过关了,不要就此满足.
如果你想成为优秀的软件开发人员,建议你阅读并按照CMMI规范做事,让自己的综合水平上升一个台阶.
上海贝尔的员工可以向网络应用事业部软件工程研究小组索取CMMI有关资料,最好能参加培训.

三、版权声明本书的大部分内容取材于作者一年前的书籍手稿(尚未出版),现整理汇编成为上海贝尔网络应用事业部的一个规范化文件,同时作为培训教材.

由于C++/C编程是众所周知的技术,没有秘密可言.
编程的好经验应该大家共享,我们自己也是这么学来的.
作者愿意公开本书的电子文档.

版权声明如下:(1)读者可以任意拷贝、修改本书的内容,但不可以篡改作者及所属单位.

(2)未经作者许可,不得出版或大量印发本书.
(3)如果竞争对手公司的员工得到本书,请勿公开使用,以免发生纠纷.
预计到2002年7月,我们将建立切合中国国情的CMMI3级解决方案.
届时,包括本书在内的约1000页规范将严格受控.
欢迎读者对本书提出批评建议.
林锐,2001年7月第1章文件结构每个C++/C程序通常分为两个文件.
一个文件用于保存程序的声明(declaration),称为头文件.
另一个文件用于保存程序的实现(implementation),称为定义(definition)文件.

C++/C程序的头文件以".
h"为后缀,C程序的定义文件以".
c"为后缀,C++程序的定义文件通常以".
cpp"为后缀(也有一些系统以".
cc"或".
cxx"为后缀).

1.
1版权和版本的声明版权和版本的声明位于头文件和定义文件的开头(参见示例1-1),主要内容有:(1)版权信息.
(2)文件名称,标识符,摘要.
(3)当前版本号,作者/修改者,完成日期.
(4)版本历史信息.
/**Copyright(c)2001,上海贝尔有限公司网络应用事业部*Allrightsreserved.
**文件名称:filename.
h*文件标识:见配置管理计划书*摘要:简要描述本文件的内容**当前版本:1.
1*作者:输入作者(或修改者)名字*完成日期:2001年7月20日**取代版本:1.
0*原作者:输入原作者(或修改者)名字*完成日期:2001年5月10日*/示例1-1版权和版本的声明1.
2头文件的结构头文件由三部分内容组成:(1)头文件开头处的版权和版本声明(参见示例1-1).
(2)预处理块.
(3)函数和类结构声明等.
假设头文件名称为graphics.
h,头文件的结构参见示例1-2.
【规则1-2-1】为了防止头文件被重复引用,应当用ifndef/define/endif结构产生预处理块.

【规则1-2-2】用#include格式来引用标准库的头文件(编译器将从标准库目录开始搜索).
【规则1-2-3】用#include"filename.
h"格式来引用非标准库的头文件(编译器将从用户的工作目录开始搜索).
【建议1-2-1】头文件中只存放"声明"而不存放"定义"在C++语法中,类的成员函数可以在声明的同时被定义,并且自动成为内联函数.
这虽然会带来书写上的方便,但却造成了风格不一致,弊大于利.
建议将成员函数的定义与声明分开,不论该函数体有多么小.

【建议1-2-2】不提倡使用全局变量,尽量不要在头文件中出现象externintvalue这类声明.
//版权和版本声明见示例1-1,此处省略.
#ifndefGRAPHICS_H//防止graphics.
h被重复引用#defineGRAPHICS_H#include//引用标准库的头文件…#include"myheader.
h"//引用非标准库的头文件…voidFunction1(…);//全局函数声明…classBox//类结构声明{…};#endif示例1-2C++/C头文件的结构1.
3定义文件的结构定义文件有三部分内容:定义文件开头处的版权和版本声明(参见示例1-1).
对一些头文件的引用.
程序的实现体(包括数据和代码).
假设定义文件的名称为graphics.
cpp,定义文件的结构参见示例1-3.
//版权和版本声明见示例1-1,此处省略.
#include"graphics.
h"//引用头文件…//全局函数的实现体voidFunction1(…){…}//类成员函数的实现体voidBox::Draw(…){…}示例1-3C++/C定义文件的结构1.
4头文件的作用早期的编程语言如Basic、Fortran没有头文件的概念,C++/C语言的初学者虽然会用使用头文件,但常常不明其理.
这里对头文件的作用略作解释:(1)通过头文件来调用库功能.
在很多场合,源代码不便(或不准)向用户公布,只要向用户提供头文件和二进制的库即可.
用户只需要按照头文件中的接口声明来调用库功能,而不必关心接口怎么实现的.
编译器会从库中提取相应的代码.

(2)头文件能加强类型安全检查.
如果某个接口被实现或被使用时,其方式与头文件中的声明不一致,编译器就会指出错误,这一简单的规则能大大减轻程序员调试、改错的负担.

1.
5目录结构如果一个软件的头文件数目比较多(如超过十个),通常应将头文件和定义文件分别保存于不同的目录,以便于维护.

例如可将头文件保存于include目录,将定义文件保存于source目录(可以是多级目录).

如果某些头文件是私有的,它不会被用户的程序直接引用,则没有必要公开其"声明".
为了加强信息隐藏,这些私有的头文件可以和定义文件存放于同一个目录.

第2章程序的版式版式虽然不会影响程序的功能,但会影响可读性.
程序的版式追求清晰、美观,是程序风格的重要构成因素.

可以把程序的版式比喻为"书法".
好的"书法"可让人对程序一目了然,看得兴致勃勃.
差的程序"书法"如螃蟹爬行,让人看得索然无味,更令维护者烦恼有加.
请程序员们学习程序的"书法",弥补大学计算机教育的漏洞,实在很有必要.

2.
1空行空行起着分隔程序段落的作用.
空行得体(不过多也不过少)将使程序的布局更加清晰.
空行不会浪费内存,虽然打印含有空行的程序是会多消耗一些纸张,但是值得.
所以不要舍不得用空行.

【规则2-1-1】在每个类声明之后、每个函数定义结束之后都要加空行.
参见示例2-1(a)【规则2-1-2】在一个函数体内,逻揖上密切相关的语句之间不加空行,其它地方应加空行分隔.
参见示例2-1(b)//空行voidFunction1(…){…}//空行voidFunction2(…){…}//空行voidFunction3(…){…}//空行while(condition){statement1;//空行if(condition){statement2;}else{statement3;}//空行statement4;}示例2-1(a)函数之间的空行示例2-1(b)函数内部的空行2.
2代码行【规则2-2-1】一行代码只做一件事情,如只定义一个变量,或只写一条语句.
这样的代码容易阅读,并且方便于写注释.

【规则2-2-2】if、for、while、do等语句自占一行,执行语句不得紧跟其后.
不论执行语句有多少都要加{}.
这样可以防止书写失误.

示例2-2(a)为风格良好的代码行,示例2-2(b)为风格不良的代码行.
intwidth;//宽度intheight;//高度intdepth;//深度intwidth,height,depth;//宽度高度深度x=a+b;y=c+d;z=e+f;X=a+b;y=c+d;z=e+f;if(width=2000)良好的风格if(year>=2000)不良的风格if((a>=b)&&(c=b&&cFunction(不要写成b->Function();示例2-3代码行内的空格2.
4对齐【规则2-4-1】程序的分界符'{'和'}'应独占一行并且位于同一列,同时与引用它们的语句左对齐.

【规则2-4-2】{}之内的代码块在'{'右边数格处左对齐.
示例2-4(a)为风格良好的对齐,示例2-4(b)为风格不良的对齐.
voidFunction(intx){…//programcode}voidFunction(intx){…//programcode}if(condition){…//programcode}else{…//programcode}if(condition){…//programcode}else{…//programcode}for(initialization;condition;update){…//programcode}for(initialization;condition;update){…//programcode}While(condition){…//programcode}while(condition){…//programcode}如果出现嵌套的{},则使用缩进对齐,如:{…{…}…}示例2-4(a)风格良好的对齐示例2-4(b)风格不良的对齐2.
5长行拆分【规则2-5-1】代码行最大长度宜控制在70至80个字符以内.
代码行不要过长,否则眼睛看不过来,也不便于打印.

【规则2-5-2】长表达式要在低优先级操作符处拆分成新行,操作符放在新行之首(以便突出操作符).
拆分出的新行要进行适当的缩进,使排版整齐,语句可读.

if((very_longer_variable1>=very_longer_variable12)&&(very_longer_variable3Draw();//类的成员函数【规则3-1-8】用正确的反义词组命名具有互斥意义的变量或相反动作的函数等.

例如:intminValue;intmaxValue;intSetValue(…);intGetValue(…);【建议3-1-1】尽量避免名字中出现数字编号,如Value1,Value2等,除非逻辑上的确需要编号.
这是为了防止程序员偷懒,不肯为命名动脑筋而导致产生无意义的名字(因为用数字编号最省事).

3.
2简单的Windows应用程序命名规则作者对"匈牙利"命名规则做了合理的简化,下述的命名规则简单易用,比较适合于Windows应用软件的开发.

【规则3-2-1】类名和函数名用大写字母开头的单词组合而成.
例如:classNode;类名classLeafNode;//类名voidDraw(void);//函数名voidSetValue(intvalue);//函数名【规则3-2-2】变量和参数用小写字母开头的单词组合而成.
例如:BOOLflag;intdrawMode;【规则3-2-3】常量全用大写的字母,用下划线分割单词.
例如:constintMAX=100;constintMAX_LENGTH=100;【规则3-2-4】静态变量加前缀s_(表示static).
例如:voidInit(…){staticints_initValue;//静态变量…}【规则3-2-5】如果不得已需要全局变量,则使全局变量加前缀g_(表示global).

例如:intg_howManyPeople;//全局变量intg_howMuchMoney;//全局变量【规则3-2-6】类的数据成员加前缀m_(表示member),这样可以避免数据成员与成员函数的参数同名.

例如:voidObject::SetValue(intwidth,intheight){m_width=width;m_height=height;}【规则3-2-7】为了防止某一软件库中的一些标识符和其它软件库中的冲突,可以为各种标识符加上能反映软件性质的前缀.
例如三维图形标准OpenGL的所有库函数均以gl开头,所有常量(或宏定义)均以GL开头.

3.
3简单的Unix应用程序命名规则第4章表达式和基本语句读者可能怀疑:连if、for、while、goto、switch这样简单的东西也要探讨编程风格,是不是小题大做我真的发觉很多程序员用隐含错误的方式写表达式和基本语句,我自己也犯过类似的错误.

表达式和语句都属于C++/C的短语结构语法.
它们看似简单,但使用时隐患比较多.
本章归纳了正确使用表达式和语句的一些规则与建议.

4.
1运算符的优先级C++/C语言的运算符有数十个,运算符的优先级与结合律如表4-1所示.
注意一元运算符+-*的优先级高于对应的二元运算符.
优先级运算符结合律从高到低排列从左至右类型)sizeof从右至左*/%从左至右+-从左至右>从左至右从左至右==!
=从左至右&从左至右^从左至右|从左至右&&从左至右||从右至左:从右至左从左至右表4-1运算符的优先级与结合律【规则4-1-1】如果代码行中的运算符比较多,用括号确定表达式的操作顺序,避免使用默认的优先级.

由于将表4-1熟记是比较困难的,为了防止产生歧义并提高可读性,应当用括号确定表达式的操作顺序.
例如:word=(high=b&&c="或"=-EPSINON)&&(x0)*pbTo++=*pbFrom++;returnpvTo;}示例6-5复制不重叠的内存块assert不是一个仓促拼凑起来的宏.
为了不在程序的Debug版本和Release版本引起差别,assert不应该产生任何副作用.
所以assert不是函数,而是宏.
程序员可以把assert看成一个在任何系统状态下都可以安全使用的无害测试手段.
如果程序在assert处终止了,并不是说含有该assert的函数有错误,而是调用者出了差错,assert可以帮助我们找到发生错误的原因.

很少有比跟踪到程序的断言,却不知道该断言的作用更让人沮丧的事了.
你化了很多时间,不是为了排除错误,而只是为了弄清楚这个错误到底是什么.
有的时候,程序员偶尔还会设计出有错误的断言.
所以如果搞不清楚断言检查的是什么,就很难判断错误是出现在程序中,还是出现在断言中.
幸运的是这个问题很好解决,只要加上清晰的注释即可.
这本是显而易见的事情,可是很少有程序员这样做.
这好比一个人在森林里,看到树上钉着一块"危险"的大牌子.
但危险到底是什么树要倒有废井有野兽除非告诉人们"危险"是什么,否则这个警告牌难以起到积极有效的作用.
难以理解的断言常常被程序员忽略,甚至被删除.
[Maguire,p8-p30]【规则6-5-1】使用断言捕捉不应该发生的非法情况.
不要混淆非法情况与错误情况之间的区别,后者是必然存在的并且是一定要作出处理的.

【规则6-5-2】在函数的入口处,使用断言检查参数的有效性(合法性).
【建议6-5-1】在编写函数时,要进行反复的考查,并且自问:"我打算做哪些假定"一旦确定了的假定,就要使用断言对假定进行检查.

【建议6-5-2】一般教科书都鼓励程序员们进行防错设计,但要记住这种编程风格可能会隐瞒错误.
当进行防错设计时,如果"不可能发生"的事情的确发生了,则要使用断言进行报警.

6.
6引用与指针的比较引用是C++中的概念,初学者容易把引用和指针混淆一起.
一下程序中,n是m的一个引用(reference),m是被引用物(referent).

intm;int&n=m;n相当于m的别名(绰号),对n的任何操作就是对m的操作.
例如有人名叫王小毛,他的绰号是"三毛".
说"三毛"怎么怎么的,其实就是对王小毛说三道四.
所以n既不是m的拷贝,也不是指向m的指针,其实n就是m它自己.

引用的一些规则如下:(1)引用被创建的同时必须被初始化(指针则可以在任何时候被初始化).
(2)不能有NULL引用,引用必须与合法的存储单元关联(指针则可以是NULL).
(3)一旦引用被初始化,就不能改变引用的关系(指针则可以随时改变所指的对象).

以下示例程序中,k被初始化为i的引用.
语句k=j并不能将k修改成为j的引用,只是把k的值改变成为6.
由于k是i的引用,所以i的值也变成了6.

inti=5;intj=6;int&k=i;k=j;//k和i的值都变成了6;上面的程序看起来象在玩文字游戏,没有体现出引用的价值.
引用的主要功能是传递函数的参数和返回值.
C++语言中,函数的参数和返回值的传递方式有三种:值传递、指针传递和引用传递.

以下是"值传递"的示例程序.
由于Func1函数体内的x是外部变量n的一份拷贝,改变x的值不会影响n,所以n的值仍然是0.
voidFunc1(intx){x=x+10;}…intn=0;Func1(n);coutFunc();//p是"野指针"}函数Test在执行语句p->Func()时,对象a已经消失,而p是指向a的,所以p就成了"野指针".
但奇怪的是我运行这个程序时居然没有出错,这可能与编译器有关.

7.
8有了malloc/free为什么还要new/deletemalloc与free是C++/C语言的标准库函数,new/delete是C++的运算符.
它们都可用于申请动态内存和释放内存.

对于非内部数据类型的对象而言,光用maloc/free无法满足动态对象的要求.
对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数.
由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free.

因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete.
注意new/delete不是库函数.

我们先看一看malloc/free和new/delete如何实现对象的动态内存管理,见示例7-8.

classObj{public:Obj(void){coutInitialize(初始化//…a->Destroy();//清除工作free(a);//释放内存}voidUseNewDelete(void){Obj*a=newObj;//申请动态内存并且初始化//…deletea;//清除并且释放内存}示例7-8用malloc/free和new/delete如何实现对象的动态内存管理类Obj的函数Initialize模拟了构造函数的功能,函数Destroy模拟了析构函数的功能.
函数UseMallocFree中,由于malloc/free不能执行构造函数与析构函数,必须调用成员函数Initialize和Destroy来完成初始化与清除工作.
函数UseNewDelete则简单得多.

所以我们不要企图用malloc/free来完成动态对象的内存管理,应该用new/delete.
由于内部数据类型的"对象"没有构造与析构的过程,对它们而言malloc/free和new/delete是等价的.

既然new/delete的功能完全覆盖了malloc/free,为什么C++不把malloc/free淘汰出局呢这是因为C++程序经常要调用C函数,而C程序只能用malloc/free管理动态内存.

如果用free释放"new创建的动态对象",那么该对象因无法执行析构函数而可能导致程序出错.
如果用delete释放"malloc申请的动态内存",理论上讲程序不会出错,但是该程序的可读性很差.
所以new/delete必须配对使用,malloc/free也一样.

7.
9内存耗尽怎么办如果在申请动态内存时找不到足够大的内存块,malloc和new将返回NULL指针,宣告内存申请失败.
通常有三种方式处理"内存耗尽"问题.

(1)判断指针是否为NULL,如果是则马上用return语句终止本函数.
例如:voidFunc(void){A*a=newA;if(a==NULL){return;}…}(2)判断指针是否为NULL,如果是则马上用exit(1)终止整个程序的运行.
例如:voidFunc(void){A*a=newA;if(a==NULL){coutvoidoutput(intx);//函数声明voidoutput(floatx);//函数声明voidoutput(intx){coutclassBase{public:voidf(intx){coutf(42);//Base::f(int)42pb->f(3.
14f);//Base::f(float)3.
14pb->g(Derived::g(void)}示例8-2-1成员函数的重载和覆盖8.
2.
2令人迷惑的隐藏规则本来仅仅区别重载与覆盖并不算困难,但是C++的隐藏规则使问题复杂性陡然增加.
这里"隐藏"是指派生类的函数屏蔽了与其同名的基类函数,规则如下:(1)如果派生类的函数与基类的函数同名,但是参数不同.
此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆).

(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字.
此时,基类的函数被隐藏(注意别与覆盖混淆).

示例程序8-2-2(a)中:(1)函数Derived::f(float)覆盖了Base::f(float).
(2)函数Derived::g(int)隐藏了Base::g(float),而不是重载.
(3)函数Derived::h(float)隐藏了Base::h(float),而不是覆盖.
#includeclassBase{public:virtualvoidf(floatx){coutf(3.
14f);//Derived::f(float)3.
14pd->f(3.
14f);//Derived::f(float)3.
14//Bad:behaviordependsontypeofthepointerpb->g(3.
14f);//Base::g(float)3.
14pd->g(3.
14f);//Derived::g(int)3(surprise!
)//Bad:behaviordependsontypeofthepointerpb->h(3.
14f);//Base::h(float)3.
14(surprise!
)pd->h(3.
14f);//Derived::h(float)3.
14}示例8-2-2(b)重载、覆盖和隐藏的比较8.
2.
3摆脱隐藏隐藏规则引起了不少麻烦.
示例8-2-3程序中,语句pd->f(10)的本意是想调用函数Base::f(int),但是Base::f(int)不幸被Derived::f(char*)隐藏了.
由于数字10不能被隐式地转化为字符串,所以在编译时出错.
classBase{public:voidf(intx);};classDerived:publicBase{public:voidf(char*str);};voidTest(void){Derived*pd=newDerived;pd->f(10);//error}示例8-2-3由于隐藏而导致错误从示例8-2-3看来,隐藏规则似乎很愚蠢.
但是隐藏规则至少有两个存在的理由:写语句pd->f(10)的人可能真的想调用Derived::f(char*)函数,只是他误将参数写错了.
有了隐藏规则,编译器就可以明确指出错误,这未必不是好事.
否则,编译器会静悄悄地将错就错,程序员将很难发现这个错误,流下祸根.

假如类Derived有多个基类(多重继承),有时搞不清楚哪些基类定义了函数f.
如果没有隐藏规则,那么pd->f(10)可能会调用一个出乎意料的基类函数f.
尽管隐藏规则看起来不怎么有道理,但它的确能消灭这些意外.

示例8-2-3中,如果语句pd->f(10)一定要调用函数Base::f(int),那么将类Derived修改为如下即可.

classDerived:publicBase{public:voidf(char*str);voidf(intx){Base::f(x);}};8.
3参数的缺省值有一些参数的值在每次函数调用时都相同,书写这样的语句会使人厌烦.
C++语言采用参数的缺省值使书写变得简洁(在编译时,缺省值由编译器自动插入).

参数缺省值的使用规则:【规则8-3-1】参数缺省值只能出现在函数的声明中,而不能出现在定义体中.

例如:voidFoo(intx=0,inty=0);//正确,缺省值出现在函数的声明中voidFoo(intx=0,inty=0)//错误,缺省值出现在函数的定义体中{…}为什么会这样我想是有两个原因:一是函数的实现(定义)本来就与参数是否有缺省值无关,所以没有必要让缺省值出现在函数的定义体中.
二是参数的缺省值可能会改动,显然修改函数的声明比修改函数的定义要方便.

【规则8-3-2】如果函数有多个参数,参数只能从后向前挨个儿缺省,否则将导致函数调用语句怪模怪样.

正确的示例如下:voidFoo(intx,inty=0,intz=0);错误的示例如下:voidFoo(intx=0,inty,intz=0);要注意,使用参数的缺省值并没有赋予函数新的功能,仅仅是使书写变得简洁一些.
它可能会提高函数的易用性,但是也可能会降低函数的可理解性.
所以我们只能适当地使用参数的缺省值,要防止使用不当产生负面效果.
示例8-3-2中,不合理地使用参数的缺省值将导致重载函数output产生二义性.

#includevoidoutput(intx);voidoutput(intx,floaty=0.
0);voidoutput(intx){cout(b)(a):(b)语句result=MAX(i,j)+2;将被预处理器解释为result=(i)>(j)(i):(j)+2;由于运算符'+'比运算符':'的优先级高,所以上述语句并不等价于期望的result=((i)>(j)(i):(j))+2;如果把宏代码改写为#defineMAX(a,b)a)>(b)(a):(b))则可以解决由优先级引起的错误.
但是即使使用修改后的宏代码也不是万无一失的,例如语句result=MAX(i++,j);将被预处理器解释为result=(i++)>(j)(i++):(j);对于C++而言,使用宏代码还有另一种缺点:无法操作类的私有数据成员.
让我们看看C++的"函数内联"是如何工作的.
对于任何内联函数,编译器在符号表里放入函数的声明(包括名字、参数类型、返回值类型).
如果编译器没有发现内联函数存在错误,那么该函数的代码也被放入符号表里.
在调用一个内联函数时,编译器首先检查调用是否正确(进行类型安全检查,或者进行自动类型转换,当然对所有的函数都一样).
如果正确,内联函数的代码就会直接替换函数调用,于是省去了函数调用的开销.
这个过程与预处理有显著的不同,因为预处理器不能进行类型安全检查,或者进行自动类型转换.
假如内联函数是成员函数,对象的地址(this)会被放在合适的地方,这也是预处理器办不到的.

C++语言的函数内联机制既具备宏代码的效率,又增加了安全性,而且可以自由操作类的数据成员.
所以在C++程序中,应该用内联函数取代所有宏代码,"断言assert"恐怕是唯一的例外.
assert是仅在Debug版本起作用的宏,它用于检查"不应该"发生的情况.
为了不在程序的Debug版本和Release版本引起差别,assert不应该产生任何副作用.
如果assert是函数,由于函数调用会引起内存、代码的变动,那么将导致Debug版本与Release版本存在差异.
所以assert不是函数,而是宏.
(参见6.
5节"使用断言")8.
5.
2内联函数的编程风格关键字inline必须与函数定义体放在一起才能使函数成为内联,仅将inline放在函数声明前面不起任何作用.
如下风格的函数Foo不能成为内联函数:inlinevoidFoo(intx,inty);//inline仅与函数声明放在一起voidFoo(intx,inty){…}而如下风格的函数Foo则成为内联函数:voidFoo(intx,inty);inlinevoidFoo(intx,inty)//inline与函数定义体放在一起{…}所以说,inline是一种"用于实现的关键字",而不是一种"用于声明的关键字".
一般地,用户可以阅读函数的声明,但是看不到函数的定义.
尽管在大多数教科书中内联函数的声明、定义体前面都加了inline关键字,但我认为inline不应该出现在函数的声明中.
这个细节虽然不会影响函数的功能,但是体现了高质量C++/C程序设计风格的一个基本原则:声明与定义不可混为一谈,用户没有必要、也不应该知道函数是否需要内联.

定义在类声明之中的成员函数将自动地成为内联函数,例如classA{public:voidFoo(intx,inty)自动地成为内联函数}将成员函数的定义体放在类声明之中虽然能带来书写上的方便,但不是一种良好的编程风格,上例应该改成://头文件classA{public:voidFoo(intx,inty);}//定义文件inlinevoidA::Foo(intx,inty){…}8.
5.
3慎用内联内联能提高函数的执行效率,为什么不把所有的函数都定义成内联函数如果所有的函数都是内联函数,还用得着"内联"这个关键字吗内联是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率.
如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少.
另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间.
以下情况不宜使用内联:(1)如果函数体内的代码比较长,使用内联将导致内存消耗代价较高.
(2)如果函数体内出现循环,那么执行函数体内代码的时间要比函数调用的开销大.

类的构造函数和析构函数容易让人误解成使用内联更有效.
要当心构造函数和析构函数可能会隐藏一些行为,如"偷偷地"执行了基类或成员对象的构造函数和析构函数.
所以不要随便地将构造函数和析构函数的定义体放在类声明中.

一个好的编译器将会根据函数的定义体,自动地取消不值得的内联(这进一步说明了inline不应该出现在函数的声明中).

8.
6一些心得体会C++语言中的重载、内联、缺省参数、隐式转换等机制展现了很多优点,但是这些优点的背后都隐藏着一些隐患.
正如人们的饮食,少食和暴食都不可取,应当恰到好处.
我们要辨证地看待C++的新机制,应该恰如其分地使用它们.
虽然这会使我们编程时多费一些心思,少了一些痛快,但这才是编程的艺术.

第9章类的构造函数、析构函数与赋值函数构造函数、析构函数与赋值函数是每个类最基本的函数.
它们太普通以致让人容易麻痹大意,其实这些貌似简单的函数就象没有顶盖的下水道那样危险.

每个类只有一个析构函数和一个赋值函数,但可以有多个构造函数(包含一个拷贝构造函数,其它的称为普通构造函数).
对于任意一个类A,如果不想编写上述函数,C++编译器将自动为A产生四个缺省的函数,如A(void)缺省的无参数构造函数A(constA&a)缺省的拷贝构造函数~A(void)缺省的析构函数A&operate=(constA&a);//缺省的赋值函数这不禁让人疑惑,既然能自动生成函数,为什么还要程序员编写原因如下:(1)如果使用"缺省的无参数构造函数"和"缺省的析构函数",等于放弃了自主"初始化"和"清除"的机会,C++发明人Stroustrup的好心好意白费了.

(2)"缺省的拷贝构造函数"和"缺省的赋值函数"均采用"位拷贝"而非"值拷贝"的方式来实现,倘若类中含有指针变量,这两个函数注定将出错.

对于那些没有吃够苦头的C++程序员,如果他说编写构造函数、析构函数与赋值函数很容易,可以不用动脑筋,表明他的认识还比较肤浅,水平有待于提高.

本章以类String的设计与实现为例,深入阐述被很多教科书忽视了的道理.
String的结构如下:classString{public:String(constchar*str=NULL);//普通构造函数String(constString&other);//拷贝构造函数~String(void)析构函数String&operate=(constString&other);//赋值函数private:char*m_data;//用于保存字符串};9.
1构造函数与析构函数的起源作为比C更先进的语言,C++提供了更好的机制来增强程序的安全性.
C++编译器具有严格的类型安全检查功能,它几乎能找出程序中所有的语法问题,这的确帮了程序员的大忙.
但是程序通过了编译检查并不表示错误已经不存在了,在"错误"的大家庭里,"语法错误"的地位只能算是小弟弟.
级别高的错误通常隐藏得很深,就象狡猾的罪犯,想逮住他可不容易.

根据经验,不少难以察觉的程序错误是由于变量没有被正确初始化或清除造成的,而初始化和清除工作很容易被人遗忘.
Stroustrup在设计C++语言时充分考虑了这个问题并很好地予以解决:把对象的初始化工作放在构造函数中,把清除工作放在析构函数中.
当对象被创建时,构造函数被自动执行.
当对象消亡时,析构函数被自动执行.
这下就不用担心忘了对象的初始化和清除工作.

构造函数与析构函数的名字不能随便起,必须让编译器认得出才可以被自动执行.
Stroustrup的命名方法既简单又合理:让构造函数、析构函数与类同名,由于析构函数的目的与构造函数的相反,就加前缀'~'以示区别.

除了名字外,构造函数与析构函数的另一个特别之处是没有返回值类型,这与返回值类型为void的函数不同.
构造函数与析构函数的使命非常明确,就象出生与死亡,光溜溜地来光溜溜地去.
如果它们有返回值类型,那么编译器将不知所措.
为了防止节外生枝,干脆规定没有返回值类型.
(以上典故参考了文献[Eekel,p55-p56])9.
2构造函数的初始化表构造函数有个特殊的初始化方式叫"初始化表达式表"(简称初始化表).
初始化表位于函数参数表之后,却在函数体{}之前.
这说明该表里的初始化工作发生在函数体内的任何代码被执行之前.

构造函数初始化表的使用规则:如果类存在继承关系,派生类必须在其初始化表里调用基类的构造函数.

例如classA{…A(intx);//A的构造函数};classB:publicA{…B(intx,inty);//B的构造函数};B::B(intx,inty):A(x)//在初始化表里调用A的构造函数{…}类的const常量只能在初始化表里被初始化,因为它不能在函数体内用赋值的方式来初始化(参见5.
4节).

类的数据成员的初始化可以采用初始化表或函数体内赋值两种方式,这两种方式的效率不完全相同.

非内部数据类型的成员对象应当采用第一种方式初始化,以获取更高的效率.
例如classA{…A(void)无参数构造函数A(constA&other);//拷贝构造函数A&operate=(constA&other);//赋值函数};classB{public:B(constA&a);//B的构造函数private:Am_a;//成员对象};示例9-2(a)中,类B的构造函数在其初始化表里调用了类A的拷贝构造函数,从而将成员对象m_a初始化.

示例9-2(b)中,类B的构造函数在函数体内用赋值的方式将成员对象m_a初始化.
我们看到的只是一条赋值语句,但实际上B的构造函数干了两件事:先暗地里创建m_a对象(调用了A的无参数构造函数),再调用类A的赋值函数,将参数a赋给m_a.

B::B(constA&a):m_a(a){…}B::B(constA&a){m_a=a;…}示例9-2(a)成员对象在初始化表中被初始化示例9-2(b)成员对象在函数体内被初始化对于内部数据类型的数据成员而言,两种初始化方式的效率几乎没有区别,但后者的程序版式似乎更清晰些.
若类F的声明如下:classF{public:F(intx,inty);//构造函数private:intm_x,m_y;intm_i,m_j;}示例9-2(c)中F的构造函数采用了第一种初始化方式,示例9-2(d)中F的构造函数采用了第二种初始化方式.

F::F(intx,inty):m_x(x),m_y(y){m_i=0;m_j=0;}F::F(intx,inty){m_x=x;m_y=y;m_i=0;m_j=0;}示例9-2(c)数据成员在初始化表中被初始化示例9-2(d)数据成员在函数体内被初始化9.
3构造和析构的次序构造从类层次的最根处开始,在每一层中,首先调用基类的构造函数,然后调用成员对象的构造函数.
析构则严格按照与构造相反的次序执行,该次序是唯一的,否则编译器将无法自动执行析构过程.

一个有趣的现象是,成员对象初始化的次序完全不受它们在初始化表中次序的影响,只由成员对象在类中声明的次序决定.
这是因为类的声明是唯一的,而类的构造函数可以有多个,因此会有多个不同次序的初始化表.
如果成员对象按照初始化表的次序进行构造,这将导致析构函数无法得到唯一的逆序.
[Eckel,p260-261]9.
4示例:类String的构造函数与析构函数//String的普通构造函数String::String(constchar*str){if(str==NULL){m_data=newchar[1];*m_data='\0';}else{intlength=strlen(str);m_data=newchar[length+1];strcpy(m_data,str);}}//String的析构函数String::~String(void){delete[]m_data;//由于m_data是内部数据类型,也可以写成deletem_data;}9.
5不要轻视拷贝构造函数与赋值函数由于并非所有的对象都会使用拷贝构造函数和赋值函数,程序员可能对这两个函数有些轻视.
请先记住以下的警告,在阅读正文时就会多心:本章开头讲过,如果不主动编写拷贝构造函数和赋值函数,编译器将以"位拷贝"的方式自动生成缺省的函数.
倘若类中含有指针变量,那么这两个缺省的函数就隐含了错误.
以类String的两个对象a,b为例,假设a.
m_data的内容为"hello",b.
m_data的内容为"world".

现将a赋给b,缺省赋值函数的"位拷贝"意味着执行b.
m_data=a.
m_data.
这将造成三个错误:一是b.
m_data原有的内存没被释放,造成内存泄露;二是b.
m_data和a.
m_data指向同一块内存,a或b任何一方变动都会影响另一方;三是在对象被析构时,m_data被释放了两次.

拷贝构造函数和赋值函数非常容易混淆,常导致错写、错用.
拷贝构造函数是在对象被创建时调用的,而赋值函数只能被已经存在了的对象调用.
以下程序中,第三个语句和第四个语句很相似,你分得清楚哪个调用了拷贝构造函数,哪个调用了赋值函数吗Stringa("hello");Stringb("world");Stringc=a;//调用了拷贝构造函数,最好写成c(a);c=b;//调用了赋值函数本例中第三个语句的风格较差,宜改写成Stringc(a)以区别于第四个语句.
9.
6示例:类String的拷贝构造函数与赋值函数//拷贝构造函数String::String(constString&other){//允许操作other的私有成员m_dataintlength=strlen(other.
m_data);m_data=newchar[length+1];strcpy(m_data,other.
m_data);}//赋值函数String&String::operate=(constString&other){//(1)检查自赋值if(this==&other)return*this;//(2)释放原有的内存资源delete[]m_data;//(3)分配新的内存资源,并复制内容intlength=strlen(other.
m_data);m_data=newchar[length+1];strcpy(m_data,other.
m_data);//(4)返回本对象的引用return*this;}类String拷贝构造函数与普通构造函数(参见9.
4节)的区别是:在函数入口处无需与NULL进行比较,这是因为"引用"不可能是NULL,而"指针"可以为NULL.

类String的赋值函数比构造函数复杂得多,分四步实现:(1)第一步,检查自赋值.
你可能会认为多此一举,难道有人会愚蠢到写出a=a这样的自赋值语句!
的确不会.
但是间接的自赋值仍有可能出现,例如//内容自赋值b=a;…c=b;…a=c;//地址自赋值b=&a;…a=*b;也许有人会说:"即使出现自赋值,我也可以不理睬,大不了化点时间让对象复制自己而已,反正不会出错!
"他真的说错了.
看看第二步的delete,自杀后还能复制自己吗所以,如果发现自赋值,应该马上终止函数.
注意不要将检查自赋值的if语句if(this==&other)错写成为if(*this==other)(2)第二步,用delete释放原有的内存资源.
如果现在不释放,以后就没机会了,将造成内存泄露.

(3)第三步,分配新的内存资源,并复制字符串.
注意函数strlen返回的是有效字符串长度,不包含结束符'\0'.
函数strcpy则连'\0'一起复制.

(4)第四步,返回本对象的引用,目的是为了实现象a=b=c这样的链式表达.
注意不要将return*this错写成returnthis.
那么能否写成returnother呢效果不是一样吗不可以!
因为我们不知道参数other的生命期.
有可能other是个临时对象,在赋值结束后它马上消失,那么returnother返回的将是垃圾.
9.
7偷懒的办法处理拷贝构造函数与赋值函数如果我们实在不想编写拷贝构造函数和赋值函数,又不允许别人使用编译器生成的缺省函数,怎么办偷懒的办法是:只需将拷贝构造函数和赋值函数声明为私有函数,不用编写代码.

例如:classA{…private:A(constA&a)私有的拷贝构造函数A&operate=(constA&a);//私有的赋值函数};如果有人试图编写如下程序:Ab(a);//调用了私有的拷贝构造函数b=a;//调用了私有的赋值函数编译器将指出错误,因为外界不可以操作A的私有函数.
9.
8如何在派生类中实现类的基本函数基类的构造函数、析构函数、赋值函数都不能被派生类继承.
如果类之间存在继承关系,在编写上述基本函数时应注意以下事项:派生类的构造函数应在其初始化表里调用基类的构造函数.
基类与派生类的析构函数应该为虚(即加virtual关键字).
例如#includeclassBase{public:virtual~Base(){cout和#include"filename.
h"有什么区别3、const有什么用途(请至少说明两种)4、在C++程序中调用被C编译器编译后的函数,为什么要加extern"C"声明5、请简述以下两个for循环的优缺点//第一个for(i=0;i=-EPSINON)&&(x="或"和#include"filename.
h"有什么区别(5分)答:对于#include,编译器从标准库路径开始搜索filename.
h对于#include"filename.
h",编译器从用户的工作路径开始搜索filename.
h3、const有什么用途(请至少说明两种)(5分)答:(1)可以定义const常量(2)const可以修饰函数的参数、返回值,甚至函数的定义体.
被const修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性.

4、在C++程序中调用被C编译器编译后的函数,为什么要加extern"C"(5分)答:C++语言支持函数重载,C语言不支持函数重载.
函数被C++编译后在库中的名字与C语言的不同.
假设某个函数的原型为:voidfoo(intx,inty);该函数被C编译器编译后在库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字.

C++提供了C连接交换指定符号extern"C"来解决名字匹配问题.
5、请简述以下两个for循环的优缺点(5分)for(i=0;i

极光KVM(限时16元),洛杉矶三网CN2,cera机房,香港cn2

极光KVM创立于2018年,主要经营美国洛杉矶CN2机房、CeRaNetworks机房、中国香港CeraNetworks机房、香港CMI机房等产品。其中,洛杉矶提供CN2 GIA、CN2 GT以及常规BGP直连线路接入。从名字也可以看到,VPS产品全部是基于KVM架构的。极光KVM也有明确的更换IP政策,下单时选择“IP保险计划”多支付10块钱,可以在服务周期内免费更换一次IP,当然也可以不选择,...

Spinservers美国圣何塞服务器$111/月流量10TB

Spinservers是Majestic Hosting Solutions,LLC旗下站点,主营美国独立服务器租用和Hybrid Dedicated等,数据中心位于美国德克萨斯州达拉斯和加利福尼亚圣何塞机房。TheServerStore.com,自 1994 年以来,它是一家成熟的企业 IT 设备供应商,专门从事二手服务器和工作站业务,在德克萨斯州拥有 40,000 平方英尺的仓库,库存中始终有...

RackNerd美国大硬盘服务器促销:120G SSD+192TB HDD,1Gbps大带宽,月付$599,促销美国月付$服务器促销带宽

racknerd怎么样?racknerd最近发布了一些便宜美国服务器促销,包括大硬盘服务器,提供120G SSD+192TB HDD,有AMD和Intel两个选择,默认32G内存,1Gbps带宽,每个月100TB流量,5个IP地址,月付$599。价格非常便宜,需要存储服务器的朋友可以关注一下。RackNerd主要经营美国圣何塞、洛杉矶、达拉斯、芝加哥、亚特兰大、新泽西机房基于KVM虚拟化的VPS、...

小说软件哪个好用免费为你推荐
马云将从软银董事会辞职马云离职??什么原因?????租车平台哪个好想网上租车,选什么平台好?杀毒软件哪个好什么杀毒软件比较好呢??录屏软件哪个好有什么好用的游戏录屏软件推荐吗?燃气热水器和电热水器哪个好燃气热水器与电热水器的优缺点?法兰绒和珊瑚绒哪个好法兰绒和珊瑚绒哪个好被套好少儿英语哪个好少儿英语哪个好宝来和朗逸哪个好新宝来和新朗逸选哪个?好纠结!!杰士邦和杜蕾斯哪个好杜蕾斯好用还是杰士邦好要?海克斯皮肤哪个好LOL用100块是抽海克斯好还是抽蛮王的生化领主的活动还是直接买皮肤好
骨干网 naning9韩国官网 阿里云os godaddy主机 patcha 远程登陆工具 网站被封 云全民 警告本网站美国保护 台湾谷歌地址 169邮箱 免费活动 qq对话框 卡巴斯基免费试用 优酷黄金会员账号共享 云营销系统 东莞服务器托管 注册阿里云邮箱 114dns 带宽测试 更多