分享免费的编程资源和教程

网站首页 > 技术教程 正文

C++ 初学者教程,其实更应该说C++发展史

goqiw 2024-10-29 14:36:57 技术教程 10 ℃ 0 评论

初学者必备:C 经典入门详细教程

分享之前我还是要推荐下我自己的C/C++学习小组,不管你是小白还是大牛,小编我都挺欢迎,不定期分享干货,包括我自己整理的一份2017最新的C/C++资料和零基础入门教程,送给大家,欢迎初学和进阶中的小伙伴:343891365

公元前216年8月2日,意大利东部平原,一个叫做坎尼的地方,两支大军摆开阵势,准备决一死战。一方是由保罗斯和瓦罗两位执政官率领的罗马人,另一方则是伟大的军事天才汉尼拔*巴卡率领的迦太基军队及其同盟。罗马人超过8万,而迦太基仅有4万余人。然而到了傍晚,罗马人被彻底击败,7万人被杀,仅有少数得以逃脱。这就是著名的坎尼会战。经此一役,(外加先前进行的特利比亚和特拉西梅诺湖会战),罗马人元气大伤,成年公民损失达五分之一。部分城邦背叛罗马,西西里也发生起义。罗马已经到了摇摇欲坠的地步。

汉尼拔的这些胜利,完全得益于先前的一次异乎寻常的远征。公元前218年,汉尼拔率领军队,从新迦太基城(西班牙)出发,翻越比利牛斯山,进入南高卢地域。在他面前有两条路可走,翻越阿尔俾斯山,或者沿海岸进入意大利。但是,当时罗马人已在沿海地区部署了两支部队,准备拦截汉尼拔。而且,罗马人的海军优势,使得他们可以在任何时候将一支部队登陆在他的背后。而翻越阿尔俾斯山,则是一条及其艰险的道路,更何况是在冬天。

汉尼拔选择了阿尔俾斯山。他甩开了罗马人,从小圣贝纳德和日内瓦山之间越过阿尔俾斯山,进入意大利境内。此时,罗马人便失去了战略纵深,一把尖刀已经深深地插入他们的腹内...

C++的发展史上,也有着如同汉尼拔翻越阿尔俾斯山远征。一切还得从C with Class时代说起。

Bjarne曾经反复强调,他创建C++为的是将Simular的抽象能力同C的性能结合起来。于是,在C语言的基础上,诞生了一种拥有类、继承、重载等等面向对象机制的语言。在这个阶段,C++提供了两个方面的抽象能力。一种是数据抽象,也就是将数据所要表达的含义通过类型以及依附于类型上的成员表述。另一种则是多态,一种最原始的多态(重载)。 数据抽象,通过被称为“抽象数据类型(ADT)”的技术实现。

ADT的一种方案,就是类。类所提供的封装性将一个数据实体的外在特征,或者说语义的表述形式,同具体的实现,比如数据存储形式,分离。这样所增加的中间层将数据的使用者同数据的实现者隔离,使得他们使用共同的约定语义工作,不再相互了解彼此的细节,从而使得两者得以解耦。 多态则是更加基础更加重要的一种特性。多态使得我们得以用同一种符号实现某种确定的语义。多态的精髓在于:以一种形式表达一种语义。在此之前,我们往往被迫使用不同的符号来代表同一种抽象语义,为的是适应强类型系统所施加的约束。比如: //代码#1

int add_int(int lhs, int rhs);

(function() { var impMonitorUrls = []; var clickMonitorUrls = []; function visitUrl(url) { var img = new Image(); img.src = url; return img; } function visitAllUrls(urls) { for (var i = 0; i < urls.length; i++) { visitUrl(urls[i]); } } function addEventListener(node, event, func, useCapture) { node = node || document; useCapture = useCapture || false; if (node.addEventListener) { node.addEventListener(event, func, useCapture); } else { node.attachEvent('on' + event, func); } } function init() { var imgLink = document.getElementById('img_link'); if (imgLink) { addEventListener(imgLink, 'click', function() { visitAllUrls(clickMonitorUrls); }, false); } } function req_imp12() { visitUrl("https://eduad.baidu.com/impression/wenku_post_json?; visitAllUrls(impMonitorUrls); init(); }

if (window.attachEvent) { window.attachEvent('onload', req_imp12); }

else if (window.addEventListener) { window.addEventListener('load', req_imp12, false); } }) (); .product { position: relative; } .adv-logo { position: absolute; bottom: 1px; z-index: 100; } .ad-label { left: 1px; } .dsp-logo { right: 1px; }

float add_float(float lhs, float rhs);

很显然,这两个函数表达的语义分别是“把两个int类型值加在一起”和“把两个float类型值加在一起”。这两个语义抽象起来都表达了一个意思:加。

我们在做算术题的时候是不会管被计算的数字是整数还是实数。同样,如果能够在编程的时候,不考虑算术操作对象的类型,只需关心谁和谁进行什么操作,那么会方便得多。当C++引入重载后,这种愿望便得以实现: //代码#2

int add(int lhs, int rhs); float add(float lhs, float rhs);

重载使得我们只需关心“加”这个语义,至于什么类型和什么类型相加,则由编译器根据操作数的类型自动解析。

从某种意义上说,重载是被长期忽视,但却极为重要的一个语言特性。在多数介绍OOP的书籍中,重载往往被作为OOP的附属品,放在一些不起眼的地方。它的多态本质也被动多态的人造光环所设遮蔽。然而,重载的重要作用却在实践中潜移默化地体现出来。重载差不多可以看作语言迈入现代抽象体系的第一步。它的实际效用甚至要超过被广为关注的OOP,而不会像OOP那样在获得抽象的同时,伴随着不小的副作用。

随着虚函数的引入,C++开始具备了颇具争议的动多态技术。虚函数是一种依附于类(OOP的类型基础)的多态技术。其技术基础是后期绑定(late-binding)。当一个类D继承自类B时,它有两种方法覆盖(override)B上的某个函数: //代码#3 class B { public:

void fun1(); virtual void fun2(); };

class D:public B {

(window.cproArray = window.cproArray || []).push({ id: "u3054369" });

public:

void fun1(); void fun2(); };

当继承类D中声明了同基类B中成员函数相同函数名、相同签名的成员函数,那么基类的成员函数将被覆盖。对于基类的非虚成员函数,继承类会直接将其遮蔽。对于类型D的使用者,fun1代表了D中所赋予的语义。而类型D的实例,可以隐式地转换成类型B的引用b,此时调用b的fun1,则执行的是类B的fun1 定义,而非类D的fun1,尽管此时b实际指向一个D的实例。

但是,如果继承类覆盖了基类的虚函数,那么将得到相反的结果:当调用引用b的fun2,实际上却是调用了D的fun2定义。这表明,覆盖一个虚函数,将会在继承类和基类之间的所有层次上执行覆盖。这种彻底的、全方位的覆盖行为,使得我们可以在继承类上修饰或扩展基类的功能或行为。这便是OOP扩展机制的基础。而这种技术被称为动多态,意思是基类引用所表达的语义并非取决于基类本身,而是来源于它所指向的实际对象,因此它是“多态”的。因为一个引用所指向的对象可以在运行时变换,所以它是“动”的。

随着动多态而来的一个“副产品”,却事实上成为了OOP的核心和支柱。虚函数的“动多态”特性将我们引向一个极端的情况:一个都是虚函数的类。更重要的,这个类上的虚函数都没有实现,每个虚函数都未曾指向一个实实在在的函数体。当然,这样的类是无法直接使用的。有趣的是,这种被称为“抽象基类”的类,迫使我们继承它,并“替它”实现那些没有实现的虚函数。这样,对于一个抽象基类的引用,多态地拥有了继承类的行为。而反过来,抽象基类实际上起到了强迫继承类实现某些特定功能的作用。因此,抽象基类扮演了接口的角色。接口具有两重作用:一、约束继承类(实现者)迫使其实现预定的成员函数(功能和行为);二、描述了继承类必定拥有的成员函数(功能和行为)。这两种作用促使接口成为了OOP设计体系的支柱。

C++在这方面的进步,使其成为一个真正意义上具备现代抽象能力的语言。然而,这种进步并非“翻越阿尔俾斯山”。充其量也只能算作“翻越比利牛斯山”。对于C++而言,真正艰苦的远征才刚开始,那令人生畏的“阿尔俾斯山”仍在遥远的前方。

同汉尼拔一样,当C++一脚迈入“现代抽象语言俱乐部”后,便面临两种选择。或者在原有基础上修修补补,成为一种OOP语言;或者继续前进,翻越那座险峻的山峰。C++的汉尼拔——Bjarne Stroustrup——选择了后者。

从D&E的描述中我们可以看到,在C++的原始设计中就已经考虑“类型参数”的问题。但直到90年代初,才真正意义上地实现了模板。然而,模板只是第一步。诸如Ada等语言中都有类似的机制(泛型,generic),但并未对当时的编程技术产生根本性的影响。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表