经常听说C语言面向过程,C++面向对象。为什么说c语言是面向过程的,因为c语言的两个编程原则,(1)是结构化编程,c语言提供了诸如(do…while、for循环、while、if…else等语句);(2)是自顶向下的设计原则,主要理念是将大型程序分解为小型,易于管理的任务,如果其中一项过大则继续分解为小型任务,直到将程序分解为小型、易于编写的模块单元(函数)。所以说,C语言结构化的编程技术反应了过程性的编程思路,强调的是算法,根据执行的操作来构思一个程序。
其实,面向对象的程序设计OOP(Object Oriented Programming)与其说是一种方法,不如说是式一种编程思路,C语言也可以通过typedefine(主要通过结构体、枚举)和#define等命令,也可以实现一定程度的OOP的编程思路。
与==结构化编程==强调的是==算法实现==不同,OOP强调的是数据。OOP不像过程编译那样试图使问题满足语言的过程性方法,而是让语言来满足问题的要求。其理念是设计与问题的本质相对应的数据格式。
C++提出类的概念,类是一种规范,描述了这种新数据格式,对象使用这种规范构造的特定数据结构。例如,一个描述学生的基本特征(姓名,学号,性别,特长,成绩等),而对象就是特定的某个学生(李平安,123,男,打游戏,99)。类中还规定了使用哪些数据可以进行的某项特定操作,如可以根据算法得到学生的偏科情况,平均分,身体素质等等。设计类是一项艰巨的任务,C++提供了STL这样的类模板,C++真正的优点之一是:可以方便地重读使用和修稿现有的、经过大量测试验证的代码。
@TOC
1.1 面向对象编程OOP
面向对象程序设计的基本特点:抽象、封装、继承&派生、多态。
(1)抽象:对同一类对象的共同属性和行为进行概括,形成共同性质的类。通过类来实现。
比如将天下所有的表都可以抽象为一个clock类(class clock):其中包含①数据抽象:小时(int hour)、分(int minute)、秒(int second);②代码抽象:设置时间setTime()、显示时间showTime();
(2)封装:将抽象出的数据,代码封装在一起,形成一个类,封装可以提高安全性,简化编程,使用者只需要看说明了解接口如何使用就行了。如果一定要说封装是如何实现的,那就是通过类声明用的大括号封装 =={ }==。
(3)继承:在已有的类的基础上,进行扩展形成新的类。有利于代码重用。
比如对上例的clock类,将其继承并且派生出一个新类:电子手表eClock,这个eClock不仅继承了clock的数据hour、minute、second和函数setTime、showTime,还在此基础上添加了设定闹钟时间alarmHour、alarmMinute、alarmSecond和闹钟提示bellClock()的功能。
(4)多态:同一名称,不同的功能实现方式。目的是,达到行为标识符统一,减少程序中的标识符的个数。
1.1.1 类和对象
首先,类是同一类对象的抽象,对象是类的实例。 比如 学生student就是一个类,而 学生.李平安 就是学生这个抽象类的一个现实存在的实例。
联系实际中的类,可以想象到在设计一个类的时候,我们需要关心:
(1) 此类型的合法值是什么?
(2) 此类型应有什么样的函数和操作符?
(3) 类的对象高如何创建、销毁?
(4) 如何进行对象的初始化和赋值?
(5) 对象作为函数的参数如何进行值传递?
(6) 谁将使用此类型的对象成员?
1.1.2 类定义的语法形式
1 | // 类定义的语法 |
为数据成员设置类内的初始值,在构造对象的时候,用于初始化对象的数据成员用的。
1 | // 类内的初始值 |
1.1.3 对象定义的语法
1 | // 对象定义的语法 |
类中的成员互相访问:直接用成员名就行了;
类外访问类的成员:通过对象来访问public成员 语法:”对象名.成员名”。
1.1.4 对象定义的语法
类的成员函数,再类中该如何定义类的成员函数
(1) 首先,在类中声明函数原型
(2) 对于简单的成员函数,可以内联在类的内部;
(3) 内联函数主要在类外给出函数体实现,并在函数名前使用类名加以限定(允许声明重载函数和带默认参数的函数):
1 |
|
1.1.5 构造函数和默认构造函数
如何对定义的对象初始化,按什么规律初始化,这都需要程序员写程序规定,故此,C++提供了构造函数。
==构造函数==:在==对象被创造出来的时候,将对象初始化为一个特定值==,是类中的一个特殊函数,用于描述初始化算法:
(1). 构造函数与类名相同;
(2). 构造函数没有函数类型,当然也无返回值;
(3). 可以是内联函数,允许重载,可以带默认参数;
(4). 构造函数在对象被创建的时候调用;
1 |
|
此时的执行结果为:
1 | 8 : 30 : 30 |
1.1.6 委托构造函数
当在一个类中,重载多个构造函数的时候,往往构造函数的初始化方式和函数体都是相同的,如果要写多个函数体重载,就显得重复了,这时候,为了简化代码的编程,可以采用==委托机制==:
如,将上例中的:
1 | clock::clock(int newH, int newM, int newS):hour(newH),minute(newM),second(newS){} |
可以用委托的方法:
1 | clock::clock(int newH, int newM, int newS):hour(newH),minute(newM),second(newS){} |
委托构造函数的最大好处就是保持了代码的一致性,方便修改。
1.1.7 复制构造函数
定义基本类型的变量的时候,经常会用到一个已知的变量初始化这个变量。在定义对象的时候也是相同的,==用一个已知存在的对象初始化新对象==,是一种==特殊的构造函数==。
编译器会生成一个默认的复制构造函数,可以实现两个对象之间的一一对应复制,在大多数情况下就已经够好用了。我这里只记了用已知对象初始化新对象的语法:
1 | clock myClock2(myClock); |
执行结果为:
1 | 8 : 30 : 30 |
1.1.8 析构函数
当一个对象在存在期间可能会占用很多系统资源。在对象消亡的时候就需要做一些善后工作,就由析构函数完成,释放占用的资源。
当一个对象被创建的时候,自动调用构造函数;
当一个对象要消亡的时候,自动调用析构函数;
(1). 析构函数原型:~类名();
(2). 析构函数没有参数,也无返回类型,无返回值;
(3). 和构造函数一样,在编程时不写析构函数/构造函数时,系统会自动生成一个默认的构造函数,但其函数体是空的,不进行任何操作,只是为了满足编译的语法需要;
简单综合实例:
1 |
|
运行结果
1 | complex::complex(int r ,int i) called! |
1.2 类的组合
1.3 继承
1.3.1 类的继承
继承可以使一个类可以从现有类中派生,而不必重新定义一个新类。继承和派生其实是对同一过程的不同角度的描述。①继承的目的:实现代码重用;②派生的目的:原有程序无法解决问题,需要进行改造;
继承的语法形式:
1 | // 单继承: |
1.3.2 派生类的构成
派生类的构成:
(1). 吸收的基类成员:
◇ 默认情况下派生类包含了全部基类除构造函数和析构函数之外的所有成员函数;
◇ 可以用using语句继承基类构造函数。
(2). 改造的基类成员:
◇ 在派生类中声明一个与基类同名的新成员,将基类的成员覆盖,因为会现在派生类中寻找成员函数;
(3). 添加的新的成员 ☆☆☆
◇ 完全新增新功能,新数据
1.3.3 继承方式
继承方式:
不同的继承方式影响主要体现在:
◇派生类成员对基类成员的访问权限;
◇通过派生类对象对基类成员的访问权限。
◇ public(公有继承): 基类的public和protected成员:访问属性在派生类中保持不变;基类的private成员:不可直接访问;派生类的成员不能访问基类中的private成员;通过派生类对象,可以访问基类的public和protected成员;
◇ protected(保护继承):基类的public和protected成员,都以protected的身份出现在派生类中;基类的private成员:不可直接访问;派生类的成员不能访问基类中的private成员;通过派生类对象,不能访问基类的任何成员;
◇ private(私有继承):基类的所有成员,都以private的身份出现在派生类中;派生类中的成员可以访问基类的public和protected成员;通过派生类产生的对象,不能访问基类的任何成员。
1.3.4 派生类的构造函数
在默认情况下:
◇ 基类的构造函数不被继承;
◇ 派生类需要定义自己的构造函数,且派生类的构造函数还需要负责向基类的初始化功能传递参数,由基类的构造函数完成基类的初始化,由派生类的函数完成本类新增成员的函数。
单继承时构造函数的定义语法
1 | 派生类名::派生类名(基类所需形参,本类所需形参):基类名(参数表),本类名(本类成员初始化列表) |
eg:
1 |
|
1.3.5
还差一部分内容,以后可能补上
1.3.6 虚基类与虚继承
是不是说继承多个基类的时候,有重名的成员也没有关系,访问的时候用类名限定就可以了?如果某个派生类从多个基类派生,而这些基类又有共同的基类,则在访问此共同基类的成员时。将产生冗余,并有可能因冗余带来不一致性。
虚基类的声明:关键字virtual
1 | // 以virtual说明基类的继承方式为虚继承 |
作用:
◇主要用来解决多继承时可能发生的对同一基类继承多次而产生的冗余/二义性问题;
◇为最远派生类提供唯一成员,而不重复产生多次复制。
注意:
◇在第一级继承的时,就要将共同基类设计为虚基类。
==虚基类及其派生类构造函数==:
◇ 虚基类及其派生类构造函数;
◇ 虚基类的成员是由最远派生类的构造函数通过调用虚基类的构造函数进行初始化的;
◇ 在整个继承结构中,直接或间接继承虚基类的所有派生类,都必须在构造函数的成员初始化表中为虚基类的构造函数列出参数。如果未列出, 则表示调用该虚基类的默认构造函数;
◇ 在建立对象时,只有最远派生类的构造函数调用虚基类的构造函数,其他类对虚基类构造函数的调用被忽略。
1.4 多态
多态性是面向对象程序设计的一个重要特征,利用多态性可以设计和实现一个易于扩展的系统。==多态性是指具有不同功能的函数可以用同一个函数名,这样就可以用一个函数名调用不同内容的函数,发出同样的消息被不同类型的对象接收时,导致完全不同的行为==。这里说的消息主要指类的成员函数的调用,而不同的行为是指不同的实现。
多态性通过联编实现。联编是指一个计算机程序自身彼此关联过程。按照联编所进行的阶段不同,可以分为两种不同的联编方法:静态联编和动态联编。在C++中,根据联编的时刻不同,存在两种类型的多态,即函数重载和虚函数。
1.4.1 运算符重载规则
对于自定义的类型,如何使用系统预先定义好的运算?比如自定义一个复数类,该如何实现复数的运算?将预定义好的运算符,针对新的类型赋予新的含义。
◇ 运算符本质也是一种函数,对运算符的重载也就是对函数的重载。
◇ 只能重载预定义好的运算符;
◇ 重载后的优先级和结和性保持不变。
◇ 运算符重载时针对新类型数据的实际需要,对原有运算符进行适当的改造。
实现方式:
◇ 重载为类的非静态成员函数;
◇ 重载为非成员函数;
(1)重载为类成员的运算符函数定义形式:
1 | 函数类型 operator 运算符(形参) |
双目运算符重载规则:
◇ 如果要重载B为类成员函数,使之能实现表达式 oprd1 B oprd2 ,其中oprd1 为 A 类对象,==则 B 应被重载为 A 类的成员函数==,==形参类型应该是 oprd2 所属的类型==。
◇经重载后,表达式 oprd1 B oprd2 相当于 oprd1.operator B(oprd2);
(2) 运算符重载为非成员函数
下面两种情况,需要将运算符重载为类外的函数:
◇ 将操作符重载为类的成员函数的时候,左操作符必须是类的对象;
◇ 如果做操作数是类的对象,但不是我们自己设计的类;
运算符重载为非成员函数的规则:
◇ 函数的形参表:依自左至右次序依次排列各操作数;
◇ 如果在运算符的重载中需要操作某类对象的私有成员,可以将此函数声明为类的友元。
1.4.2 虚函数
在类的继承层次结构中,在不同的层次中可以出现名字,参数个数和类型都相同而功能不同的函数。编译器按照先自己后基类的顺序进行查找覆盖,如果子类有父类相同的原型的成员函数时,要想调用父类的成员函数。需要对父类重新引用调用。==虚函数可以解决子类和基类相同原型成员函数的函数调用问题==。
虚函数允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数。
虚函数:
◇ 用virtua关键字说明的函数,(在声明的时候加关键字);
◇ 虚函数是实现运行时多态的基础
◇ C++中的虚函数是动态绑定的函数
◇ 虚函数必须是非静态的成员函数,虚函数经过派生之后,就可以实现运行过程中的多态。
在基类中用virtual声明的成员函数为虚函数,在派生类中重新定义此函数,改变该函数的功能。在C++语言中虚函数可以继承,当一个成员函数被定义为虚函数后,其派生类的同名函数都自动成为虚函数,但如果派生类中没有覆盖基类的虚函数,则调用时调用基类的虚函数。