用C++语言求相容关系的最大相容类。。最好先写出算法步骤,在对步骤用C语言进行描述
2个回答
展开全部
-------------------------------------------------- ------------------------------
标准C + +作为一门新的语言
>学习标准C + +作为一门新语言
作者Bjarne Stroustrup的
译者陈炜
为C与C + +语言,不要打扰后的权利。这个问题是问C + +之父的方式。
侯捷注:本文是2001/04年度“程序员”杂志的一篇文章。译笔流畅,全面的技术。
承译者蒋涛先生,陈巍先生和“程序员”杂志负责人承诺,
这里转载,以满足台湾读者,非常感谢你。
没有陈炜先生及蒋涛先生的同意,任何人都不要转载本文。
-------------------------------------- ------------------------------------------
C / C + +用户的日志可能,1999年
学习标准C + +作为一门新语言
由Bjarne Stroustrup
---- -------------------------------------------------- --------------------------
进口
你想获得最大的优势是标准的C + + [三重测试数据,我们必须重新思考的方式来写C + +程序。重新思考的方式之一是,认为C + +应该学习如何(和教育)。我们要强调的是,什么样的编程技术?首先,我们应该学习这门语言的哪一部分?我们希望强调,真正的代码的哪些部分?
这篇文章的一些简单的C + +程序进行比较,其中一些以现代风格(使用标准库),写一些传统的C风格的写作。从这些简单的例子中汲取的教训,在大型程序仍然是重要的。从广义上讲,本文主张以C + +为??一个更高层次的语言使用依赖于语言的抽象,提供简洁大方,风格,但低级别的效率。
我们都希望该程序很容易写,正确的执行,便于维护,效率可以接受的。这意味着,我们应该最接近这个理想的方式来使用C + +(或任何其他语言)。我想C + +的族群一直无法消化的标准C + +所带来的一切设施;重新考虑使用C + +中,我们取得了一些重要的改进和实现这些理想。重要的编程风格,重点是要充分利用标准C + +的配套设施,而不是在这些设施。
主要的改进是关键,通过使用该库,减少的规模和复杂性,我们写的代码。简单的例子,我要证明和量化这些减少的程度。这样一个简单的实例中可能出现的任何C + +进口课程。
由于减少的规模和复杂性,我们也减少了开发时间,降低维护成本,并降低测试成本。另外很重要的一点是,通过利用图书馆,我们也可以简化学习C + +。对于一个小程序,只是为了取得好成绩,这样简化应该是充足的。然而,专业的程序员,非常效率的要求,我们可以预期增强的编程风格,以满足严格的要求,现代服务业,商业信息和即时响应,而且只有在不牺牲效率的情况下。为此,我显示了测量的结果,证明,降低的复杂性和效率,而不会丢失。最后,我还讨论了这种观点的学习和教育C + +的影响。
复杂性复杂性
尽量考虑一个主题,这是非常适合作为一种编程语言课程实践(翻译:第一个实践的过程,是“世界你好”):
输出的提示一句“请输入你的姓名
阅读的名称
输出的”Hello “
标准C + +,显而易见的答案是:
#包括,/ /获取标准的I / O设备
#包括字符串/ /获取标准字符串设施的
廉政的main()
{
/ /标准库的访问权限
使用命名空间std;
cout <字符串名称;
CIN >>
法院<<“Hello”的<<名<<'\ n';
}
一个真正的初学者我们要解释的整体结构。什么是main()#做吗?使用的是什么做的?此外,我们要了解法令规范琐碎的菊\ n意义,其中应该添加一个分号┅。 / a>
然而,就在这个计划的主要组成部分之间的概念是很简单的,叙述的主题,法律是不同的,我们必须学会这个方法,这是非常简单的:字符串的字符串(字符串)cout是一个输出端(输出设备)]是我们使用了一个写入到输出装置的操作员┅等。
为了进行比较,这里是传统的C风格的解决方案[注1]:
包括 / /获得标准的I / O设备>
:()
{
const int的最大值= 20; / /名称的最大长度的19
烧焦名称[MAX];
printf的(“请输入您的姓名:\ n”);
/ /读取的字符到
scanf的(“%s”的名称);
printf(“请您好%s \ n”,名称)的名称;
0;
}
显然,与未成年人 - 只是稍微 - 改变比C + +风格的版本更复杂,因为我们要解释的阵列和主逻辑奇怪的符号%?。主要的问题是没有价值的,这个简单的C-风格的答案。如果用户输入的长度超过19名(所谓的19日,20减去1是上面指定的,扣除使用将C风格字符串结束字符),这个程序就完成了。
有些人认为这个伪劣的事实不会造成伤害,如果在“适当的解决方案。不过,即便如此,有争议的线,这仅仅是“可以接受”达不到“良好”的境界。理想情况下,我们不应该让一个新手用户面对一个简单的程序崩溃。
C-风格的程序如何能够是相同的C + +风格的程序的行为权宜?首先,我们可以使用的scanf适中,以避免数组溢出位(数组溢出):
/ /获取标准的I / O设备
诠释分钟()
{
const int的最大值20;
>字符名称[MAX];
printf的(“请输入你的名字:\ n”);
scanf函数(“%19S”,名称); / /读取最多19个字符
printf的(“你好%S \ n”,名称);
返回0;
}
标准是什么方法,可以直接使用在scanf格式串缓冲区的大小符号的类型,所以我必须使用整数字面常量以上。这是一个糟糕的风格,也是未来维护的一个滴答作响的定时炸弹。下面是专家级的方法,但对于初学者是很难启口:
字符FMT [10];
/ /生成一个格式字符串,如使用简单的%s可能导致溢出(溢出)
的sprintf(FMT,“%%%DS”MAX-1);
/ /读取最多MAX-1字符的名称。
scanf函数(FMT);
糟糕进入,这一计划将超过大小的缓冲区“字符切,但我们希望该字符串的输入和增长。为此,我们必须抽象下降到一个较低的水平,单个字符的处理:
#包括
#
#
无效退出()
{
/ /写入错误消息,并留下
fprintf(stderr中,“内存耗尽\ n”); BR />退出(1);
}
廉政的main()
{
整数最大值= 20;
/ /配置缓冲区:
字符*名称=(char *)的最大的malloc();
(名称== 0)退出();
的printf(“请输入你的名字:\ n” );
/ /跳过领先的空白
而(真){
整数C = getchar函数();
(C = EOF)打破; / /结束文件
如果(isspace(C)){
ungetc函数(C状态,stdin);
突破;
}
}
INT I = 0;
而(真){
整数C = getchar函数();
(C =='\ n'| | C == EOF){
/ /加在年底结束的字符0
名称[I] = 0;
破;
}
名称[] = C
(== MAX-1){/ /缓冲区填满
最大= +最大;
名称=(CHAR *)的realloc(名称,最大值);
>如果(名称== 0)退出();
}
ITT;
}
printf的(“你好%S \ n”,名称); BR />免费(名称); / /释放内存
返回0;
}
以前的版本,这个版本要复杂得多。加上一个“跳过前导空白”治疗,我感到有些邪恶的,因为我不明白的题目叙述为提高这方面的需求,但跳过开头的空白是正常的行为,后来在其他版本中也将这样做。
有些人可能会认为,这情况并没有那么糟糕。最有经验的C程序员和C + +程序员在实际应用中(顺利的话)可能已经写了一些这方面的东西。我们甚至可以认为,如果你不能写这样的程序,你可以不是一个专业的程序员,但想想这些东西的概念是对初学者的负担。上面的程序使用了七种不同的标准的C函数,处理的字符输入一个很琐碎的水平上,利用指标,和处理自己的自由空间(自由存储区,译注:通常是堆)。使用realloc malloc的(不是新的),这把我们带入的大小和类型转换[注2]的问题,在这样的一个小程序,是什么的最佳做法的内存耗尽的问题可能会发生的呢?答案是显而易见的。我只是在这里做某些事情,来结束这个讨论退化成另一件无关紧要的话题。通常的C-风格的做法必须认真思考什么样的方式可以形成一个良好的基础,更深层次的教学和最后的实际使用。
总之,为了解决了原来简单的问题,但核心问题本身,我有回路测试,存储空间的大小,指标,转换,管理和明确的自由空间。这种编程风格是充满机会的错误。由于长期积累的经验,我能够避免任何重大的大小差别错误(关一)或内存配置错误。我的脸流的I / O,一个坚定的典型的初学者错误:读取一个字符(而不是int),而忘记检查EOF。在C + +标准库的时代,还没有出现,也就不足为奇了,许多教师无法摆脱这些不值钱的东西,,搁置他们教。不幸的是,许多学生只注意到这个低级的风格“不够好”,比写C + +风格的兄弟,他们开发了一个非常难以打破的习惯,并留下一个容易犯错的轨迹。
过去41行的C-风格的编程,功能等价的C + +风格的程序只有10行扣除的基本架构,比30:4。更重要的是不仅短,几行的C + +风格的其本身的性质是比较容易理解的。C + +的风格和C-风格两个版本的行数和复杂的概念是很难客观衡量,但我认为C + +风格的版本10的优势:1。
效率效率
>无害的,如上面的例子程序,效率是不是一个真正的主题。面对这种方案,简化和安全的重点是(类型)。然而,真正的系统往往是由一些非常注重效率的成分。对于这样的系统,问题就变成了:“我们可以给更高层次的抽象? “
重视程序的运行效率的考虑,下面是一个简单的例子:
未知的元素,每个元素做阅读一些行动
最简单,最明显的例子我能想到的做一些动作,在程序中涉及的所有元素的一系列双精度计算的平均浮点数从输入设备(平均)和中间值(中位数),这里是传统的C风格的解决方案:
/ / C风格的解决方案:
#包括的
包括中
/ /函数,以后的qsort()使用。
诠释比较(常量无效* P,常量无效* Q) BR /> {
录得双P0 = *(双)P;
录得双Q0 = *(双)Q;
(P0 Q0)返回1;
>(PO <QO)返回-1;
返回0;
}
无效退出(){
与fprintf(stderr中,“记忆用尽\ n“);
出口(1);
}
:(INT ARGC的char *的argv [])
{ RES = 1000; / /初始配置
字符*文件的argv [2];
双* BUF =(双)的malloc(sizeof(双)的* RES); <BR /如果(BUF == 0)退出();
双位数= 0;
双平均值= 0;
廉政n = 0;
> FILE * FIN =打开(文件中,“r”); / /打开文件作为输入(读)
双D;
而(fscanf(FIN,“LG”,&D)== 1){
(N = RES){
RES + =;
BUF =(双)的realloc(缓冲区大小(双)* RES);
如果(BUF == 0)退出();
}
缓冲区[N + +] = D;
/ /四舍五入的错误倾向
平均值=(N = 1) D:意思是+(D-均值)/ N;
}
的qsort(buf中,N,sizeof(双)的,比较);
( N){
诠释中旬= N / 2;
中位数=(N%2)?BUF [中]:(BUF-1中] + BUF [中])/ 2;
>}
printf的(“的元素个数=%d个,中位数=%G,平均=%g \ n”,
?,中位数,平均数);
/>免费(缓冲区);
}
这里是常见的C + +解决方案:
/ / C + +标准库的解决方案:
包括
】#包括的文件,
包括中
使用命名空间std;
主(INT ARGC,字符*的argv [])
{
字符*文件的argv [2];
矢量的缓冲区;
双位数= 0;
双平均值= 0;
fstream的鳍(文件,使用ios :: in);
双D;
而(FIN >> D){ BR /> buf.push_back(D);
buf.size平均=(()== 1)?
D:意思是+(D-均值)/ buf.size();
>}
排序(buf.begin(),buf.end());
如果(buf.size()){
诠释MID = buf.size() / 2;
中位数=
(buf.size()%2)?
缓冲区[中]:(BUF-1中] + BUF [中])/ 2; ...... />}
cout <<“请的元素个数=”<< buf.size()
“,中位数=”<<中位数< <<意味着<<'\ n';
}
这两个程序的大小不再像前面的例子中,贫富悬殊的差异(43:24,空白行不计算在内)。在共同的元素,如main()函数的声明,以及中间值中扣除不能被删除?计算(13号线),两个不同的行数的关键是20:11。 “输入和存储在C + +风格的程序循环和排序动作已大大缩短(”输入并保存的数目行环路差异:排序9的动作的数目的行中的差异:1),更重要的在C + +版本,每行包含的逻辑是简单得多,更 - 的正确性的机会,当然,也更加多了。
再次,内存管理在C + C-风格的显式的内存管理程序中的realloc出现在C + +风格的节目载体建构和push_back函数将取消的malloc +风格的方案实施的比喻,载体会自动增长,当元素被添加到push_back的。在C-风格的节目,realloc的行动,以及“配置的内存大小的轨道行动。在C + +风格的节目,我靠的异常处理(异常处理)记录的内存被耗尽。 C-风格的节目,据我了解,测试,以避免可能出现的内存耗尽问题。
也就不奇怪了,C + +版本更容易得到正确的。我剪切和粘贴的方式从C-风格的版本的C + +风格的版本。我忘了包括入口中的左我,?忘记使用buf.size;另外,编译器不支持局域网内的(本地)使用指令,迫使我将它移动到主要的。修正了4个错误,该程序可以被正确执行。
对于一个初学者,qsort的很奇怪。为什么你必须给元素的数量吗? (因为数组的元素不知道自己的号码)为什么你给的规模增加一倍吗? (不知道的qsort排序单位双打)。你为什么写那个丑陋的,用于比较值加倍的功能吗? (由于的qsort需要一个指向函数的指针,因为它不知道它的排序元素类型)为什么qsort的比较函数接受const void *的参数,而不是char *参数吗? (因为qsort的,可以责令非字符串值),void *的是什么意思呢?之前由const,这是什么意思呢? (好了,以后我们再回过头来谈论这个话题,并解释这些问题),对于初学者来说,这将是困难的,让他们看着。与此相反的解释的sort(v.begin(),v.end()),它是非常容易:简单排序(V)是相对简单的,但有时我们想要做的那种容器的一部分,因此,一般的方法是指定排序的操作范围。
为了比较的效率,我必须首先确定有多少笔式输入,使效率有意义的。由于50000文档(s),但也只有一半这个方案比第二少,所以我选择了50万笔的输入和500万笔的输入,进行比较。结果显示在表一
/读取,排序和输出优化前优化后的浮点数
C + + CC / C + +比C + + CC / C + +比
50万笔3.5 6.1 1.74 2.5 5.1 2.04
文件(S)5,000,000 38.4 172.6 4.49 27.4 126.6 4.62
BR />键在这个比例的数字。比值大于1,表示C + +风格的版本比较快。比较的语言,库,编程风格,大家都知道是非常棘手的,所以请不要让这些简单的试验基础上的激进的结论。这些比率的平均的结果,在不同的机器上执行几次。不同的执行环境中的相同的程序,在此期间,所不同的是小于一个百分比。我也运行我的C风格的编程ISO C严格兼容的版本,符合市场预期,在此期间,没有任何区别的效率。
我期待的C + +风格的节目会快一点点,一点点。测试不同的C + +编译器实现的作品,我发现执行结果有一个令人惊讶的变化。的时候,在一个小数据量的情况下进行的C-风格的版本比C + + - 风格的版本。然而,这种情况下,关注的焦点是,我们可以面对目前已知的技术,提供了一个更高层次的抽象和更好的保护错误。我使用的是C + +编译器既广泛又便宜 - 比研究实验室的玩具。 ,编译器可以提供更有效率,当然,也适用于结果。
找到一些人愿意支付的便利和更好的错误预防比3,10,甚至是50以上,这个想法也并不少见。然而,这些好处,再加上2倍,4倍的速度,这是非常壮观的和有吸引力的。这些数字应该是一个C + +库的供应商愿意接受的最低。为了了解所花的时间,我进行了一些额外的测试(见表2)。
B /读取一个浮点数和排序。要理解的输入动作的成本的成本,再加上一个“生成”,用于生成随机数的函数。
500000号文件(S):
优化前优化后
C + + CC / C + +比C + + CC / C + +比< /读取数据读取数据生成0.6 0.3 0.5 0.4 0.3 0.75
读入读排序3.5 6.1 1.75 2.5 5.1 2.04
生成和分类和排序生成和排序2.0 3.5 1.75 0.9 2.1 2.8 1.33 2.0 2.8 1.4
产生2.6 2.89
5,000,000号文件(S):
优化前优化后
C + + CC / C + +比C + + CC / C + +比
读取数据读取21.5 29.1 1.35 21.3 28.6 1.34
产生的数据产生7.2 4.1 0.57 5.2 3.6 0.69
读入和排序的排序38.4 172.6 4.49 27.4 126.6 4.62 BR />生产排序生成和排序24.4 147.1 6.03 11.3 100.6 8.9
当然,“读”刚刚读入的数据,“阅读与排序”只是读取数据,并排序,他们将不会产生任何输出。投入成本,以获得更好的感觉,“生成”是用来产生一个随机数,而不是从输入设备读取数据。
在其他的例子,其它的编译器,我想,C + + I / O流比stdio稍微慢一点。以前版本的程序,而不是使用cin文件流,确实是这样的。有些C + +编译器,文件I / O比CIN快速的太多理由至少部分原因是由于拙劣处理的CIN和COUT之间的绑定。然而,上述值,C + +风格的I / O像C-风格的I / O效率。
如果你改变这些程序,让他们阅读和排序的对象是一个整数而不是浮点数,并不会改变的相对效率 - 虽然我们可能会惊喜地发现,这种的变化对C + +风格的节目,这的确是非常简单(只有两个变化,C-风格的程序需要12个变化)。为了便于维护,是一个好兆头。在“生成”测试的差异表明,成本的配置。一个向量的push_back加应该就像一个数组中的malloc / free一样快,但其实不是这样。这样做的原因是很难在优化过程中,将“什么事也没有做初始值设置列(初始)”删除通话行动。幸运的是,由配置造成的成本,在面对输入成本(生成的配置要求)触发几乎总是如此之小。至于排序,如预期,比qsort的速度更快,主要是由于比较操作是在样线,扩大(内联),qsort函数必须调用功能。
这是很难选择的效率问题的一个例子很好的说明了。的意见,我从我的同事们阅读和比较的“价值”是不够的现实主义,应改为“字符串”和排序。所以我写了下面的程序:
#包括
包括文件
包括中
包括字符串
使用命名空间std;
:(INT ARGC,字符*的argv [])
{
字符*文件的argv [2] / /输入文件名的文件
的char * OFILE = ARGV [3]; / /文件名的输出文件
矢量的缓冲区;
fstream的鳍(文件,使用ios :: in);
串D;
(getline函数(翅,D))
buf.push_back(D);
的排序(buf.开始(),buf.end());
fstream的FOUT(OFILE,内部监督办公室出来),“”
副本(buf.begin(),buf.end的()
的ostream_iterator连用的(FOUT,“\ n”));
}
我把它改写为C型,并尽量让读取的字符优化。 C + +风格的版本执行 - 即使是在洗脸后手动调整和优化的C-风格的版本(消除字符串复制操作)。对于一个小的输出量,没有显着的差异,但大量的数据,排序再次击败qsort的,因为其更好的线路启动(内联),表三。
表/读取,排序和输出的字符串
C + + CC / C + +
比C,去除
字符串复制操作后,
C / C + +比
文件(500000)8.4 9.5 1.13 8.3 0.99
2,000,000号文件(S)37.4 81.3 2.17 76.1 2.03
我使用两百万的字符串,因为我没有足够的主内存来容纳500万字符串,而不会造成分页更换(分页)。
为了了解所花费的时间,我故意省略的程序执行(见表4)。的字符串,我准备一个相对较短的时期(平均七个字符)。
表IV /读取和输出的字符串 - 故意遗漏排序
C + +的风景/ C + +
比C,删除优化后
C / C + +比
文件(500000)2.5 3.0 1.2 2 0.8
文件(S)2,000,000 9.8 12.6 1.29 8.9 0.91
注意,字符串是一个完美的用户自定型别,它是唯一的标准库的一部分。如果我们可以串效率和完善,我们可以自我刻板印象,因为其他的用户没有得到有效的和精致的。
为什么我要讨论的编程风格和教学用品的效率?由于编程风格和技术,我们被教导要服务于现实世界中的问题。 C + +来创建用于在大型系统中,严格规范了系统的效率。因此,我认为,有些C + +会导致教育的编程风格和技术的人,只有在玩具方案不仅效率低,不能是相同的,失败,从而使人们停止学习。上述测量结果显示,如果你的C + +风格是非常依赖于泛型编程(泛型编程)和具体类型不,为了提供一个更简单,更达到“类型安全(安全型)”的代码,其效率与传统的C风格的长的短的。的风格的面向对象(面向对象),可以得到类似的结果。
标准库的实现效率的工作表现,有一个显着的差异,这是一个重要的问题。的决定在很大程度上依赖于标准库(或广为流传的非标准库)的程序员,是很重要的编程风格,你应该能够在不同的系统至少有公认的效率。我惊恐地发现,我的测试程序在一个系统中,C + +的风格和C风格的快一倍以上,在另一个系统上,但只有一半的速度。如果系统超过四程序员不接受的。至于我能理解的是,这种变异是由于经济基本面引起的,所以没有图书馆实施过于夸张的努力,我们应该能够实现效率的一致性。更优化的库,也许是最简单的方式来提高性能标准C + +的认知和实际效率。是的,编译器的实现非常努力工作,以消除不同的编译器之间的效率差异;在效率方面的措施,真正的作者的标准库的影响更大。
显然,上面的C + +风格的解决方案相比,简化了编程逻辑在C-风格的解决方案,可以实现C + +标准库。这样的比较是不足够的,对他们不公平?我不认为如此。 C + +的一个重要形态的能力,其支持的图书馆,精致,高效的。上述简单的程序,以显示任何应用程序的优点,可以保持 - 只要有保持精致和高效的图书馆。 C + +组的挑战,扩大领域,让程序员能够享受这些好处。换句话说,我们必须更多的应用,设计和实现一个精致,高效的图书馆,并让这些库被广泛使用。
学习C + +
即使是专业的程序员,这是不可能开始的整个语言学习完成后的第一个完整的画面,然后开始使用它。编程语言都应该分段学习,小规模的演习,以测试各种设施。因此,我们总是在熟悉的方式学习一门语言。真正的问题是,“我首先要学会的语言?”但是,“我应该先学习语言的一部分吗?”
在这个问题上,传统的答案是先学C + +与C兼容子集。但是,从的角度来看,我不认为这是一个很好的答案。这样的学习会导致过早专注于低级别的细节。这将迫使学生过早地面临着许多技术上的困难和模糊的编程风格和设计上的问题,又压抑了很多有趣的事情。在本文中,前面两个例子说明了这一点。 C + +库提供了更好的支持,更好的代表性类型的测试,当然,在“C优先投了反对票的做法。但是,请注意,我也不能说是”纯粹?
标准C + +作为一门新的语言
>学习标准C + +作为一门新语言
作者Bjarne Stroustrup的
译者陈炜
为C与C + +语言,不要打扰后的权利。这个问题是问C + +之父的方式。
侯捷注:本文是2001/04年度“程序员”杂志的一篇文章。译笔流畅,全面的技术。
承译者蒋涛先生,陈巍先生和“程序员”杂志负责人承诺,
这里转载,以满足台湾读者,非常感谢你。
没有陈炜先生及蒋涛先生的同意,任何人都不要转载本文。
-------------------------------------- ------------------------------------------
C / C + +用户的日志可能,1999年
学习标准C + +作为一门新语言
由Bjarne Stroustrup
---- -------------------------------------------------- --------------------------
进口
你想获得最大的优势是标准的C + + [三重测试数据,我们必须重新思考的方式来写C + +程序。重新思考的方式之一是,认为C + +应该学习如何(和教育)。我们要强调的是,什么样的编程技术?首先,我们应该学习这门语言的哪一部分?我们希望强调,真正的代码的哪些部分?
这篇文章的一些简单的C + +程序进行比较,其中一些以现代风格(使用标准库),写一些传统的C风格的写作。从这些简单的例子中汲取的教训,在大型程序仍然是重要的。从广义上讲,本文主张以C + +为??一个更高层次的语言使用依赖于语言的抽象,提供简洁大方,风格,但低级别的效率。
我们都希望该程序很容易写,正确的执行,便于维护,效率可以接受的。这意味着,我们应该最接近这个理想的方式来使用C + +(或任何其他语言)。我想C + +的族群一直无法消化的标准C + +所带来的一切设施;重新考虑使用C + +中,我们取得了一些重要的改进和实现这些理想。重要的编程风格,重点是要充分利用标准C + +的配套设施,而不是在这些设施。
主要的改进是关键,通过使用该库,减少的规模和复杂性,我们写的代码。简单的例子,我要证明和量化这些减少的程度。这样一个简单的实例中可能出现的任何C + +进口课程。
由于减少的规模和复杂性,我们也减少了开发时间,降低维护成本,并降低测试成本。另外很重要的一点是,通过利用图书馆,我们也可以简化学习C + +。对于一个小程序,只是为了取得好成绩,这样简化应该是充足的。然而,专业的程序员,非常效率的要求,我们可以预期增强的编程风格,以满足严格的要求,现代服务业,商业信息和即时响应,而且只有在不牺牲效率的情况下。为此,我显示了测量的结果,证明,降低的复杂性和效率,而不会丢失。最后,我还讨论了这种观点的学习和教育C + +的影响。
复杂性复杂性
尽量考虑一个主题,这是非常适合作为一种编程语言课程实践(翻译:第一个实践的过程,是“世界你好”):
输出的提示一句“请输入你的姓名
阅读的名称
输出的”Hello “
标准C + +,显而易见的答案是:
#包括,/ /获取标准的I / O设备
#包括字符串/ /获取标准字符串设施的
廉政的main()
{
/ /标准库的访问权限
使用命名空间std;
cout <字符串名称;
CIN >>
法院<<“Hello”的<<名<<'\ n';
}
一个真正的初学者我们要解释的整体结构。什么是main()#做吗?使用的是什么做的?此外,我们要了解法令规范琐碎的菊\ n意义,其中应该添加一个分号┅。 / a>
然而,就在这个计划的主要组成部分之间的概念是很简单的,叙述的主题,法律是不同的,我们必须学会这个方法,这是非常简单的:字符串的字符串(字符串)cout是一个输出端(输出设备)]是我们使用了一个写入到输出装置的操作员┅等。
为了进行比较,这里是传统的C风格的解决方案[注1]:
包括 / /获得标准的I / O设备>
:()
{
const int的最大值= 20; / /名称的最大长度的19
烧焦名称[MAX];
printf的(“请输入您的姓名:\ n”);
/ /读取的字符到
scanf的(“%s”的名称);
printf(“请您好%s \ n”,名称)的名称;
0;
}
显然,与未成年人 - 只是稍微 - 改变比C + +风格的版本更复杂,因为我们要解释的阵列和主逻辑奇怪的符号%?。主要的问题是没有价值的,这个简单的C-风格的答案。如果用户输入的长度超过19名(所谓的19日,20减去1是上面指定的,扣除使用将C风格字符串结束字符),这个程序就完成了。
有些人认为这个伪劣的事实不会造成伤害,如果在“适当的解决方案。不过,即便如此,有争议的线,这仅仅是“可以接受”达不到“良好”的境界。理想情况下,我们不应该让一个新手用户面对一个简单的程序崩溃。
C-风格的程序如何能够是相同的C + +风格的程序的行为权宜?首先,我们可以使用的scanf适中,以避免数组溢出位(数组溢出):
/ /获取标准的I / O设备
诠释分钟()
{
const int的最大值20;
>字符名称[MAX];
printf的(“请输入你的名字:\ n”);
scanf函数(“%19S”,名称); / /读取最多19个字符
printf的(“你好%S \ n”,名称);
返回0;
}
标准是什么方法,可以直接使用在scanf格式串缓冲区的大小符号的类型,所以我必须使用整数字面常量以上。这是一个糟糕的风格,也是未来维护的一个滴答作响的定时炸弹。下面是专家级的方法,但对于初学者是很难启口:
字符FMT [10];
/ /生成一个格式字符串,如使用简单的%s可能导致溢出(溢出)
的sprintf(FMT,“%%%DS”MAX-1);
/ /读取最多MAX-1字符的名称。
scanf函数(FMT);
糟糕进入,这一计划将超过大小的缓冲区“字符切,但我们希望该字符串的输入和增长。为此,我们必须抽象下降到一个较低的水平,单个字符的处理:
#包括
#
#
无效退出()
{
/ /写入错误消息,并留下
fprintf(stderr中,“内存耗尽\ n”); BR />退出(1);
}
廉政的main()
{
整数最大值= 20;
/ /配置缓冲区:
字符*名称=(char *)的最大的malloc();
(名称== 0)退出();
的printf(“请输入你的名字:\ n” );
/ /跳过领先的空白
而(真){
整数C = getchar函数();
(C = EOF)打破; / /结束文件
如果(isspace(C)){
ungetc函数(C状态,stdin);
突破;
}
}
INT I = 0;
而(真){
整数C = getchar函数();
(C =='\ n'| | C == EOF){
/ /加在年底结束的字符0
名称[I] = 0;
破;
}
名称[] = C
(== MAX-1){/ /缓冲区填满
最大= +最大;
名称=(CHAR *)的realloc(名称,最大值);
>如果(名称== 0)退出();
}
ITT;
}
printf的(“你好%S \ n”,名称); BR />免费(名称); / /释放内存
返回0;
}
以前的版本,这个版本要复杂得多。加上一个“跳过前导空白”治疗,我感到有些邪恶的,因为我不明白的题目叙述为提高这方面的需求,但跳过开头的空白是正常的行为,后来在其他版本中也将这样做。
有些人可能会认为,这情况并没有那么糟糕。最有经验的C程序员和C + +程序员在实际应用中(顺利的话)可能已经写了一些这方面的东西。我们甚至可以认为,如果你不能写这样的程序,你可以不是一个专业的程序员,但想想这些东西的概念是对初学者的负担。上面的程序使用了七种不同的标准的C函数,处理的字符输入一个很琐碎的水平上,利用指标,和处理自己的自由空间(自由存储区,译注:通常是堆)。使用realloc malloc的(不是新的),这把我们带入的大小和类型转换[注2]的问题,在这样的一个小程序,是什么的最佳做法的内存耗尽的问题可能会发生的呢?答案是显而易见的。我只是在这里做某些事情,来结束这个讨论退化成另一件无关紧要的话题。通常的C-风格的做法必须认真思考什么样的方式可以形成一个良好的基础,更深层次的教学和最后的实际使用。
总之,为了解决了原来简单的问题,但核心问题本身,我有回路测试,存储空间的大小,指标,转换,管理和明确的自由空间。这种编程风格是充满机会的错误。由于长期积累的经验,我能够避免任何重大的大小差别错误(关一)或内存配置错误。我的脸流的I / O,一个坚定的典型的初学者错误:读取一个字符(而不是int),而忘记检查EOF。在C + +标准库的时代,还没有出现,也就不足为奇了,许多教师无法摆脱这些不值钱的东西,,搁置他们教。不幸的是,许多学生只注意到这个低级的风格“不够好”,比写C + +风格的兄弟,他们开发了一个非常难以打破的习惯,并留下一个容易犯错的轨迹。
过去41行的C-风格的编程,功能等价的C + +风格的程序只有10行扣除的基本架构,比30:4。更重要的是不仅短,几行的C + +风格的其本身的性质是比较容易理解的。C + +的风格和C-风格两个版本的行数和复杂的概念是很难客观衡量,但我认为C + +风格的版本10的优势:1。
效率效率
>无害的,如上面的例子程序,效率是不是一个真正的主题。面对这种方案,简化和安全的重点是(类型)。然而,真正的系统往往是由一些非常注重效率的成分。对于这样的系统,问题就变成了:“我们可以给更高层次的抽象? “
重视程序的运行效率的考虑,下面是一个简单的例子:
未知的元素,每个元素做阅读一些行动
最简单,最明显的例子我能想到的做一些动作,在程序中涉及的所有元素的一系列双精度计算的平均浮点数从输入设备(平均)和中间值(中位数),这里是传统的C风格的解决方案:
/ / C风格的解决方案:
#包括的
包括中
/ /函数,以后的qsort()使用。
诠释比较(常量无效* P,常量无效* Q) BR /> {
录得双P0 = *(双)P;
录得双Q0 = *(双)Q;
(P0 Q0)返回1;
>(PO <QO)返回-1;
返回0;
}
无效退出(){
与fprintf(stderr中,“记忆用尽\ n“);
出口(1);
}
:(INT ARGC的char *的argv [])
{ RES = 1000; / /初始配置
字符*文件的argv [2];
双* BUF =(双)的malloc(sizeof(双)的* RES); <BR /如果(BUF == 0)退出();
双位数= 0;
双平均值= 0;
廉政n = 0;
> FILE * FIN =打开(文件中,“r”); / /打开文件作为输入(读)
双D;
而(fscanf(FIN,“LG”,&D)== 1){
(N = RES){
RES + =;
BUF =(双)的realloc(缓冲区大小(双)* RES);
如果(BUF == 0)退出();
}
缓冲区[N + +] = D;
/ /四舍五入的错误倾向
平均值=(N = 1) D:意思是+(D-均值)/ N;
}
的qsort(buf中,N,sizeof(双)的,比较);
( N){
诠释中旬= N / 2;
中位数=(N%2)?BUF [中]:(BUF-1中] + BUF [中])/ 2;
>}
printf的(“的元素个数=%d个,中位数=%G,平均=%g \ n”,
?,中位数,平均数);
/>免费(缓冲区);
}
这里是常见的C + +解决方案:
/ / C + +标准库的解决方案:
包括
】#包括的文件,
包括中
使用命名空间std;
主(INT ARGC,字符*的argv [])
{
字符*文件的argv [2];
矢量的缓冲区;
双位数= 0;
双平均值= 0;
fstream的鳍(文件,使用ios :: in);
双D;
而(FIN >> D){ BR /> buf.push_back(D);
buf.size平均=(()== 1)?
D:意思是+(D-均值)/ buf.size();
>}
排序(buf.begin(),buf.end());
如果(buf.size()){
诠释MID = buf.size() / 2;
中位数=
(buf.size()%2)?
缓冲区[中]:(BUF-1中] + BUF [中])/ 2; ...... />}
cout <<“请的元素个数=”<< buf.size()
“,中位数=”<<中位数< <<意味着<<'\ n';
}
这两个程序的大小不再像前面的例子中,贫富悬殊的差异(43:24,空白行不计算在内)。在共同的元素,如main()函数的声明,以及中间值中扣除不能被删除?计算(13号线),两个不同的行数的关键是20:11。 “输入和存储在C + +风格的程序循环和排序动作已大大缩短(”输入并保存的数目行环路差异:排序9的动作的数目的行中的差异:1),更重要的在C + +版本,每行包含的逻辑是简单得多,更 - 的正确性的机会,当然,也更加多了。
再次,内存管理在C + C-风格的显式的内存管理程序中的realloc出现在C + +风格的节目载体建构和push_back函数将取消的malloc +风格的方案实施的比喻,载体会自动增长,当元素被添加到push_back的。在C-风格的节目,realloc的行动,以及“配置的内存大小的轨道行动。在C + +风格的节目,我靠的异常处理(异常处理)记录的内存被耗尽。 C-风格的节目,据我了解,测试,以避免可能出现的内存耗尽问题。
也就不奇怪了,C + +版本更容易得到正确的。我剪切和粘贴的方式从C-风格的版本的C + +风格的版本。我忘了包括入口中的左我,?忘记使用buf.size;另外,编译器不支持局域网内的(本地)使用指令,迫使我将它移动到主要的。修正了4个错误,该程序可以被正确执行。
对于一个初学者,qsort的很奇怪。为什么你必须给元素的数量吗? (因为数组的元素不知道自己的号码)为什么你给的规模增加一倍吗? (不知道的qsort排序单位双打)。你为什么写那个丑陋的,用于比较值加倍的功能吗? (由于的qsort需要一个指向函数的指针,因为它不知道它的排序元素类型)为什么qsort的比较函数接受const void *的参数,而不是char *参数吗? (因为qsort的,可以责令非字符串值),void *的是什么意思呢?之前由const,这是什么意思呢? (好了,以后我们再回过头来谈论这个话题,并解释这些问题),对于初学者来说,这将是困难的,让他们看着。与此相反的解释的sort(v.begin(),v.end()),它是非常容易:简单排序(V)是相对简单的,但有时我们想要做的那种容器的一部分,因此,一般的方法是指定排序的操作范围。
为了比较的效率,我必须首先确定有多少笔式输入,使效率有意义的。由于50000文档(s),但也只有一半这个方案比第二少,所以我选择了50万笔的输入和500万笔的输入,进行比较。结果显示在表一
/读取,排序和输出优化前优化后的浮点数
C + + CC / C + +比C + + CC / C + +比
50万笔3.5 6.1 1.74 2.5 5.1 2.04
文件(S)5,000,000 38.4 172.6 4.49 27.4 126.6 4.62
BR />键在这个比例的数字。比值大于1,表示C + +风格的版本比较快。比较的语言,库,编程风格,大家都知道是非常棘手的,所以请不要让这些简单的试验基础上的激进的结论。这些比率的平均的结果,在不同的机器上执行几次。不同的执行环境中的相同的程序,在此期间,所不同的是小于一个百分比。我也运行我的C风格的编程ISO C严格兼容的版本,符合市场预期,在此期间,没有任何区别的效率。
我期待的C + +风格的节目会快一点点,一点点。测试不同的C + +编译器实现的作品,我发现执行结果有一个令人惊讶的变化。的时候,在一个小数据量的情况下进行的C-风格的版本比C + + - 风格的版本。然而,这种情况下,关注的焦点是,我们可以面对目前已知的技术,提供了一个更高层次的抽象和更好的保护错误。我使用的是C + +编译器既广泛又便宜 - 比研究实验室的玩具。 ,编译器可以提供更有效率,当然,也适用于结果。
找到一些人愿意支付的便利和更好的错误预防比3,10,甚至是50以上,这个想法也并不少见。然而,这些好处,再加上2倍,4倍的速度,这是非常壮观的和有吸引力的。这些数字应该是一个C + +库的供应商愿意接受的最低。为了了解所花的时间,我进行了一些额外的测试(见表2)。
B /读取一个浮点数和排序。要理解的输入动作的成本的成本,再加上一个“生成”,用于生成随机数的函数。
500000号文件(S):
优化前优化后
C + + CC / C + +比C + + CC / C + +比< /读取数据读取数据生成0.6 0.3 0.5 0.4 0.3 0.75
读入读排序3.5 6.1 1.75 2.5 5.1 2.04
生成和分类和排序生成和排序2.0 3.5 1.75 0.9 2.1 2.8 1.33 2.0 2.8 1.4
产生2.6 2.89
5,000,000号文件(S):
优化前优化后
C + + CC / C + +比C + + CC / C + +比
读取数据读取21.5 29.1 1.35 21.3 28.6 1.34
产生的数据产生7.2 4.1 0.57 5.2 3.6 0.69
读入和排序的排序38.4 172.6 4.49 27.4 126.6 4.62 BR />生产排序生成和排序24.4 147.1 6.03 11.3 100.6 8.9
当然,“读”刚刚读入的数据,“阅读与排序”只是读取数据,并排序,他们将不会产生任何输出。投入成本,以获得更好的感觉,“生成”是用来产生一个随机数,而不是从输入设备读取数据。
在其他的例子,其它的编译器,我想,C + + I / O流比stdio稍微慢一点。以前版本的程序,而不是使用cin文件流,确实是这样的。有些C + +编译器,文件I / O比CIN快速的太多理由至少部分原因是由于拙劣处理的CIN和COUT之间的绑定。然而,上述值,C + +风格的I / O像C-风格的I / O效率。
如果你改变这些程序,让他们阅读和排序的对象是一个整数而不是浮点数,并不会改变的相对效率 - 虽然我们可能会惊喜地发现,这种的变化对C + +风格的节目,这的确是非常简单(只有两个变化,C-风格的程序需要12个变化)。为了便于维护,是一个好兆头。在“生成”测试的差异表明,成本的配置。一个向量的push_back加应该就像一个数组中的malloc / free一样快,但其实不是这样。这样做的原因是很难在优化过程中,将“什么事也没有做初始值设置列(初始)”删除通话行动。幸运的是,由配置造成的成本,在面对输入成本(生成的配置要求)触发几乎总是如此之小。至于排序,如预期,比qsort的速度更快,主要是由于比较操作是在样线,扩大(内联),qsort函数必须调用功能。
这是很难选择的效率问题的一个例子很好的说明了。的意见,我从我的同事们阅读和比较的“价值”是不够的现实主义,应改为“字符串”和排序。所以我写了下面的程序:
#包括
包括文件
包括中
包括字符串
使用命名空间std;
:(INT ARGC,字符*的argv [])
{
字符*文件的argv [2] / /输入文件名的文件
的char * OFILE = ARGV [3]; / /文件名的输出文件
矢量的缓冲区;
fstream的鳍(文件,使用ios :: in);
串D;
(getline函数(翅,D))
buf.push_back(D);
的排序(buf.开始(),buf.end());
fstream的FOUT(OFILE,内部监督办公室出来),“”
副本(buf.begin(),buf.end的()
的ostream_iterator连用的(FOUT,“\ n”));
}
我把它改写为C型,并尽量让读取的字符优化。 C + +风格的版本执行 - 即使是在洗脸后手动调整和优化的C-风格的版本(消除字符串复制操作)。对于一个小的输出量,没有显着的差异,但大量的数据,排序再次击败qsort的,因为其更好的线路启动(内联),表三。
表/读取,排序和输出的字符串
C + + CC / C + +
比C,去除
字符串复制操作后,
C / C + +比
文件(500000)8.4 9.5 1.13 8.3 0.99
2,000,000号文件(S)37.4 81.3 2.17 76.1 2.03
我使用两百万的字符串,因为我没有足够的主内存来容纳500万字符串,而不会造成分页更换(分页)。
为了了解所花费的时间,我故意省略的程序执行(见表4)。的字符串,我准备一个相对较短的时期(平均七个字符)。
表IV /读取和输出的字符串 - 故意遗漏排序
C + +的风景/ C + +
比C,删除优化后
C / C + +比
文件(500000)2.5 3.0 1.2 2 0.8
文件(S)2,000,000 9.8 12.6 1.29 8.9 0.91
注意,字符串是一个完美的用户自定型别,它是唯一的标准库的一部分。如果我们可以串效率和完善,我们可以自我刻板印象,因为其他的用户没有得到有效的和精致的。
为什么我要讨论的编程风格和教学用品的效率?由于编程风格和技术,我们被教导要服务于现实世界中的问题。 C + +来创建用于在大型系统中,严格规范了系统的效率。因此,我认为,有些C + +会导致教育的编程风格和技术的人,只有在玩具方案不仅效率低,不能是相同的,失败,从而使人们停止学习。上述测量结果显示,如果你的C + +风格是非常依赖于泛型编程(泛型编程)和具体类型不,为了提供一个更简单,更达到“类型安全(安全型)”的代码,其效率与传统的C风格的长的短的。的风格的面向对象(面向对象),可以得到类似的结果。
标准库的实现效率的工作表现,有一个显着的差异,这是一个重要的问题。的决定在很大程度上依赖于标准库(或广为流传的非标准库)的程序员,是很重要的编程风格,你应该能够在不同的系统至少有公认的效率。我惊恐地发现,我的测试程序在一个系统中,C + +的风格和C风格的快一倍以上,在另一个系统上,但只有一半的速度。如果系统超过四程序员不接受的。至于我能理解的是,这种变异是由于经济基本面引起的,所以没有图书馆实施过于夸张的努力,我们应该能够实现效率的一致性。更优化的库,也许是最简单的方式来提高性能标准C + +的认知和实际效率。是的,编译器的实现非常努力工作,以消除不同的编译器之间的效率差异;在效率方面的措施,真正的作者的标准库的影响更大。
显然,上面的C + +风格的解决方案相比,简化了编程逻辑在C-风格的解决方案,可以实现C + +标准库。这样的比较是不足够的,对他们不公平?我不认为如此。 C + +的一个重要形态的能力,其支持的图书馆,精致,高效的。上述简单的程序,以显示任何应用程序的优点,可以保持 - 只要有保持精致和高效的图书馆。 C + +组的挑战,扩大领域,让程序员能够享受这些好处。换句话说,我们必须更多的应用,设计和实现一个精致,高效的图书馆,并让这些库被广泛使用。
学习C + +
即使是专业的程序员,这是不可能开始的整个语言学习完成后的第一个完整的画面,然后开始使用它。编程语言都应该分段学习,小规模的演习,以测试各种设施。因此,我们总是在熟悉的方式学习一门语言。真正的问题是,“我首先要学会的语言?”但是,“我应该先学习语言的一部分吗?”
在这个问题上,传统的答案是先学C + +与C兼容子集。但是,从的角度来看,我不认为这是一个很好的答案。这样的学习会导致过早专注于低级别的细节。这将迫使学生过早地面临着许多技术上的困难和模糊的编程风格和设计上的问题,又压抑了很多有趣的事情。在本文中,前面两个例子说明了这一点。 C + +库提供了更好的支持,更好的代表性类型的测试,当然,在“C优先投了反对票的做法。但是,请注意,我也不能说是”纯粹?
本回答被网友采纳
已赞过
已踩过<
评论
收起
你对这个回答的评价是?
推荐律师服务:
若未解决您的问题,请您详细描述您的问题,通过百度律临进行免费专业咨询