一、首先是设计思想上:
C++是面向对象的语言,而C是面向过程的结构化编程语言
什么是面向过程:“自顶向下,逐步求精”的面向过程程序设计
面向过程程序设计的思想即这样的一种解决思路 - 提出问题,分析问题的处理流程,将大问题分解为小问题,如果小问题比较复杂,就继续划分小问题为更小的问题,然后通过小模块一一解决小问题,最后再根据整个业务流程将这些小问题串在一起(调用函数),这样就达到了解决所有问题的目的
优点1:程序结构简单 - 仅由三种基本结构组成
面向过程编程的思想在程序实现上只需要三种控制结构,即顺序、选择、循环。通过这三种基本结构,我们就可以解决任何问题,所以我们可以专注于程序逻辑的实现,不需要学习、记忆更多的控制结构。
优点2:分而治之,各个击破
当我们在解决复杂问题时,通常采用的就是“分而治之”的策略,即把大问题分解为小问题,然后再各个击破这些小问题,这样整个大问题就得到了解决。所以,面向过程程序设计思想也是采用这种“分而治之”的策略,把较大的程序按照业务逻辑划分为多个子模块,然后在分工逐个完成这些子模块。最后按照业务流程把他们组织起来,最终使得整个问题得到解决。按照一定的原则,把大问题细分为小的问题然后“各个击破”,符合人们思考问题的一般规律,其设计结构更易于理解,同时这种方法也易于人们掌握,通过分解问题,降低了问题的复杂度,使得程序易于实现和维护,另外,部分分解后的小问题(子模块)可以重复使用,从而避免了重复开发,而多个子模块也可以由多人分工协作完成,提高开发效率。
优点3:自顶向下,逐步求精
面向过程程序设计思想倡导的方法是“自顶向下,逐步求精”,即从宏观角度考虑,按照功能或者业务逻辑划分程序的子模块,定义程序的整体结构,然后再对各个子模块逐步细化,最终分解到程序语句为止。这种方法可以使程序员全面考虑问题,使程序的逻辑关系清晰明了。它让整个开发过程从原来的考虑 “怎么做” 变成考虑 “先做什么,再做什么”,流程就更加清晰了。
但是,一件事物必定有优点也有缺点,面向过程的程序设计当然也躲不过这种规律,并且缺点的凸显往往是在程序的复杂性越来越高时才会出现的,那么这种设计思想的缺点是什么呢?
缺点:
在面向过程程序设计时,数据和操作往往是分离的,这就导致如果数据的结构发生了变化,那么操作数据的函数不得不重新改写,这个代价是非常高的。并且,数据往往不具有封装性,很多变量都会暴露在全局,加大了被任意修改的风险。另外,一旦涉及到模块的重新划分,往往需要修改原来写好的功能模块。且这种数据和操作相互分离的特点使得一些模块跟具体的应用环境结合紧密,旧有的程序模块就很难在新的程序中得到复用。所以,这些固有的缺点使它越来越难以适应大型的软件项目的开发。正是在这样的历史背景下,一些新的程序设计思想开始不断涌现,而面向对象的程序设计思想就是最重要的一环。
什么是对象——万般皆对象:面向对象程序设计
面向对象的程序设计(OOP)是面向过程程序设计的继承和发展,它不仅汲取了后者的精华,而且以一种更加接近人类思维的方式来分析和解决问题:程序是对现实世界的抽象和描述,现实世界的基本单元是物体,与之对应的,程序中的基本单元是对象。
面向对象思想认为:现实世界是由对象组成的,无论大到一个国家还是小到一个原子,都是如此。并且对象都由两部分组成 - 描述对象状态或属性的数据(变量)以及描述对象行为或者功能的方法(函数)。并且与面向过程不同,面向对象是将数据和操作数据的函数紧密结合,共同构成对象来更加精确地描述现实世界,这是面向过程和面向对象两者最本质的区别。
之前提到面型过程的缺点,即面向过程中数据和操作是分离的,当问题规模比较小时,需求变化不大的时候,面向过程工作都做的很好。 但是,当问题的规模越来越大、越来越复杂时,面向过程就显得力不从心了,即修改了某个结构体,就不得不修改与之相关的所有过程函数,而一个过程函数的修改,往往又会设计到其他数据结构,在规模比较小的时候容易解决,但是当系统规模越来越大时,特别是涉及到了多人协作开发,这就非常困难,这就是著名的软件危机。 正是如此,面向对象的程序设计应运而生,它的主要特点是封装、继承和多态。封装即将数据和操作封装在一起,并避免了局部变量的暴露,而只提供接口;继承可以在原来的基础上很快的产生新的对象;多态是同一个方法调用,针对不同的对象有不同的反应,这方便了程序的设计。
我们可以将现实世界中的数据和对数据进行操作的动作捆绑在一起形成类,然后再通过类定义对象,很好地实现了对现实世界事物的抽象和描述;通过继承,可以在旧类型的基础上快速派生得到新的类型,很好地实现了设计和代码的复用;同时多态机制保证了在继承的同时,还有机会对已有行为进行重新定义,满足了不断出现的新需求的需要。
优点:
(1)容易设计和实现
面向对象思想强调的是从客观存在的事物(对象)出发来认识问题和分析解决问题,因为这种方式更加符合我们认识事物的规律,所以大大降低了问题的理解难度,而面向对象思想所应用的封装、继承、多态都是符合人类日常的思维习惯的,所以使用面向对象思想设计的程序结构清晰、更容易设计和实现。
(2)复用设计和代码,开发效率和系统质量都得到了提高
面向对象思想的继承和多态,强调了程序设计和代码的重用,这在设计新系统的时候,可以最大限度地重用已有的、经过大量实践检验的设计和代码,使得系统能够满足新的业务需求并具有较高的质量。同时,因为可以服用以前的设计和代码,所以大大提高了开发效率。
(3)容易扩展
开发大型系统的时候,最担心的即使需求的变更以及对系统进行扩展,利用面向对象思想的封装、继承和多态,可以设计出“高内聚、低耦合”的系统结构,可以让系统更加灵活、更容易扩展,从而轻松应对系统的扩展需求,降低维护成本。
最佳实践:高内聚,低耦合。 这是软件工程中的一个概念,通常用来判断一个软件设计的好坏。所谓的高内聚是指一个软件模块是由相关性很强的代码组成,只负责某单一任务,也就是常说的“单一责任原则”。而低耦合指的是在一个完整的系统中,模块与系统之间,尽可能保持相互独立,即每个模块尽可能独立完成特定的子功能,模块与模块之间的接口,尽可能的少而简单。
二、语法上:
1、C++具有封装、继承和多态三种特性
2、C++相比C,增加多许多类型安全的功能,比如在类型转换上,多了类型检查
什么是类型安全?
类型安全很大程度上可以等价于内存安全,类型安全的代码不会试图访问自己没被授权的内存区域。“类型安全”常被用来形容编程语言,其根据在于该门编程语言是否提供保障类型安全的机制;有的时候也用“类型安全”形容某个程序,判别的标准在于该程序是否隐含类型错误。类型安全的编程语言与类型安全的程序之间,没有必然联系。好的程序员可以使用类型不那么安全的语言写出类型相当安全的程序,相反的,差一点儿的程序员可能使用类型相当安全的语言写出类型不太安全的程序。绝对类型安全的编程语言暂时还没有。
C语言的类型安全
C只在局部上下文中表现出类型安全,比如试图从一种结构体的指针转换成另一种结构体的指针时,编译器将会报告错误,除非使用显式类型转换。然而,C中相当多的操作是不安全的。以下是两个十分常见的例子:
(1)printf格式输出
/* - print.cpp
* version:1.1
*/
int main()
{
printf("%d\n",10);
system("pause");
return 0;
}
上面的代码很简单,printf函数中,%d与10匹配,结果正确。
稍作修改:
/* - print.cpp
* version:1.2
*/
int main()
{
printf("%f\n",10);
system("pause");
return 0;
}
%f浮点数与10并不匹配,但是编译通过,执行也没报错,但是结果却是:
0.000000
请按任意键继续. . .
更进一步,把%f修改为%s,编译通过,执行将报错Access Violation。
(2)malloc函数的返回值
malloc是C中进行内存分配的函数,它的返回类型是void*即空类型指针,常常有这样的用法char* pStr=(char*)malloc(100*sizeof(char)),这里明显做了显式的类型转换。类型匹配尚且没有问题,但是一旦出现int* pInt=(int*)malloc(100*sizeof(char))就很可能带来一些问题,而这样的转换C并不会提示错误。
C++的类型安全
如果C++使用得当,它将远比C更有类型安全性。相比于C,C++提供了一些新的机制保障类型安全:
(1)操作符new返回的指针类型严格与对象匹配,而不是void*;
(2)C中很多以void*为参数的函数可以改写为C++模板函数,而模板是支持类型检查的;
(3)引入const关键字代替#define constants,它是有类型、有作用域的,而#define constants只是简单的文本替换;
(4)一些#define宏可被改写为inline函数,结合函数的重载,可在类型安全的前提下支持多种类型,当然改写为模板也能保证类型安全;
(5)C++提供了dynamic_cast关键字,使得转换过程更加安全,因为dynamic_cast比static_cast涉及更多具体的类型检查。
即便如此,C++也不是绝对类型安全的编程语言。如果使用不得当,同样无法保证类型安全。比如下面两个例子:
int i=5;
void* pInt=&i;
double d=(*(double*)pInt);
cout<<d<<endl;
输入结果不是5,而意想不到的结果:-9.25596e+061。又比如:
#include<iostream>
using namespace std;
class Parent
{
};
class Child1:public Parent
{
public:
int i;
Child1(int e):i(e)
{
}
};
class Child2:public Parent
{
public:
double d;
Child2(double e):d(e)
{
}
};
int main()
{
Child1 c1(5);
Child2 c2(4.1);
Parent* pp;
Child1* pc1;
pp=&c1;
pc1=(Child1*)pp; //#1 强制转换,由于类型仍然为Child1*,不造成错误
cout<<pc1->i<<endl;
pp=&c2;
pc1=(Child1*)pp; //#2 强制转换,且类型发生变化,将造成错误
cout<<pc1->i<<endl;
system("pause");
return 0;
}
结果如下:
5
1717986918
请按任意键继续. . .
上面两个例子之所以引起类型不安全的问题,是因为程序员使用不得当。第一个例子用到了空类型指针void*,第二个例子则是在两个类型指针之间进行强制转换。因此,想保证程序的类型安全性,应尽量避免使用空类型指针void*,尽量不对两种类型指针做强制转换。
3、C++支持范式编程,比如模板类、函数模板等