面向过程的结构化程序设计方法
自顶向下、逐步求精。采用模块分解与功能抽象,自顶向下、分而治之。
程序的模块是由函数构成的。
程序=数据结构+算法
可重用性差
面向对象的方法
将数据及对数据的操作方法封装在一起,作为一个相互依存、不可分离的整体——对象。对同类型对象抽象出其共性,形成类。对象与对象之间通过消息进行通讯。
程序的模块是由类构成的。
程序=对象+消息
对象=算法+数据结构
特点:封装性、继承性、多态性
1本章主要内容
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4YnexSPk-1638521462511)(assets/image-20211127202318-870sri9.png)]
1、OOP的基本特点
1 抽象
抽象是对具体对象(问题)进行概括,抽出这一类对象的公共性质并加以描述的过程。
- 先注意问题的本质及描述,其次是实现过程或细节。
- 数据抽象:描述某类对象的属性或状态(此类对象区别于彼类对象的特征)。
- 行为抽象:描述某类对象的共有的行为特征或具有的功能。
- 抽象的实现:通过类的声明。
1.1 钟表
- 数据抽象:
int Hour,int Minute,int Second - 行为抽象:
SetTime(),ShowTime()
1**.1.1 封装实例——钟表类**
class Clock
{
public: void SetTime(int NewH,int NewM,int NewS);
void ShowTime();
private: int Hour,Minute,Second;
};
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mbUT8Deq-1638521462512)(assets/image-20211127203830-1r6q6ki.png)]
1.2抽象实例——人
- 数据抽象:
char *name, char *gender, int age, int id - 行为抽象:
生物属性角度:GetCloth(), Eat(), Walk(),…
社会属性角度:Work(), Study() ,…
1.3抽象实例–案例中的类
2 封装
封装就是将抽象得到的数据和行为相结合,形成一个有机的整体,也就是将数据与操作数据的函数代码进行有机地结合,形成“类”。其中的数据和函数都是类的成员。
目的是增强安全性和简化编程,使用者不必了解具体的实现细节,而只需要通过外部接口,以特定的访问权限,来使用类的成员。
实现封装:类声明中的{ }限定了类的边界
2.1 封装实例——钟表类
class Clock
{
public: void SetTime(int NewH,int NewM, int NewS); void ShowTime();
private: int Hour,Minute,Second;
};
3 继承与派生
继承与派生是C++中支持层次分类的一种机制,允许程序员在保持原有类特性的基础上,进行更具体、更详细的说明。
实现:声明派生类
3.1 案例中的继承
4 多态性
多态:同一名称,不同的功能实现方式。
目的:达到行为标识统一,减少程序中标识符的个数。
实现:重载函数和虚函数——第八章
2、类和对象
2.1 类
2.1.1 c++中的类
类是具有相同属性和行为的一组对象的集合,它为属于该类的全部对象提供了统一的抽象描述,其内部包括属性和行为两个主要部分。
利用类可以实现数据的封装、隐藏、继承与派生。
利用类易于编写大型复杂程序,其模块化程度比C中采用函数更高。
2.1.2 类的定义
类是一种用户自定义类型,定义形式:
class 类名称
{
public:
公有成员(外部接口)
private:
私有成员
protected:
保护型成员
}; //最后的分号不可少,这是一条说明语句
2.1.3 类的成员
class Clock
{
public: void SetTime(int NewH, int NewM, int NewS); void ShowTime();
private:
int Hour, Minute, Second; //与一般的变量声明相同
};
2.1.3.1 类成员的访问控制
-
类的访问控制机制
在C++中,类的成员是“有所见有所不见”的,这种可见性是由访问控制符决定的。 -
C++中,有三种访问控制符:
private—仅本类能访问
protected—家族访问(后)
public—谁都能访问
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NaREzDC7-1638521462513)(assets/image-20211127211238-4wiubmw.png)]
2.1.3.2 使用说明
- 类的三个访问控制符可以任意顺序出现任意次,一般相同的在一起,公有类型放在最前面。
- 不能在类声明中给数据成员赋初值。
- 数据成员不能用auto、register、extern修饰。
2.1.3.3 类中缺省的访问属性是私有(private)
class complex{
double real;
double imag;
public:
void init(double r,double i)
{ real=r; imag=i; }
double realcomplex()
{ return real;}
};
2.1.4 成员函数定义
成员函数定义有两种方式:
类中声明原型,类外定义;
-
类外定义的具体形式如下:
返回值类型 类名::函数成员名(参数表)
{
函数体
}void Clock :: SetTime(int NewH=0, int NewM=0, int NewS=0) { Hour=NewH; Minute=NewM; Second=NewS; } void Clock :: ShowTime() { cout<<Hour<<":"<<Minute<<":"<<Second; } //允许声明重载函数和带默认形参值的函数
-
类内直接定义;
class Clock
{
public:
void SetTime(int NewH, int NewM, int NewS); void ShowTime()
{cout<<Hour<<“:”<<Minute<<“:”<<Second<<endl;}
private:
int Hour,Minute,Second;
};
这样定义的成员函数是一种内联函数(在编译的时候插入到每一个调用它的地方),亦可在类外定义内联函数,不过要在函数前加上inline。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tQWVTMCi-1638521462513)(assets/image-20211127213313-oas36ok.png)]
2.1.5 内联成员函数
- 为了提高运行时的效率,对于较简单的函数可以声明为内联形式。
- 内联成员函数体中不要有复杂结构(如循环语句和switch语句)。
- 在类中声明内联成员函数的方式:
将函数体放在类的声明中。
在类外定义内联函数,不过要在函数前加上inline。
2.2 对象
2.2.1 对象的定义
对象:对象是类的变量,是现实世界的实体;人们认识世界的基本单位。
对象与类之间的关系:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uvOL8xw7-1638521462514)(assets/image-20211127220345-ojfh0tj.png)]
对象的定义:和C中的结构类型定义一样,即用类型声明变量。
2.2.2 类与对象的关系
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VB8gfiHZ-1638521462514)(assets/image-20211127220650-9429i2k.png)]
2.2.3 类中成员的访问方式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-St5qoSmr-1638521462515)(assets/image-20211127220752-e0c2j6q.png)]
3、构造函数
3.1 定义
- 构造函数的作用是在对象被创建时使用特定的值构造对象,或者说将对象初始化为一个特定的状态。
- 在对象创建时由系统自动调用。
- 如果程序中未声明,则系统自动产生出一个默认形式的构造函数。
- 允许为内联函数、重载函数、带默认形参值的函数。
3.2 构造函数举例
class Clock
{
public:
Clock (int NewH, int NewM, int NewS);//构造函数
void SetTime(int NewH, int NewM, int NewS);
void ShowTime();
private:
int Hour,Minute,Second;
};
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jGhyxOhL-1638521462516)(assets/image-20211127221034-3uc1t76.png)]
3.3 构造函数的实现
Clock::Clock(int NewH, int NewM, int NewS)
{
Hour= NewH;
Minute= NewM;
Second= NewS;
}
//建立对象时构造函数的作用:
int main()
{
Clock c (0,0,0); //隐含调用构造函数,将初始值作为实参。
c.ShowTime();
}
3.4 拷贝构造函数
拷贝构造函数是一种特殊的构造函数,其形参为本类的对象引用。
class 类名
{ public :
类名(形参);//构造函数
类名(类名 &对象名);//拷贝构造函数
...
};
类名:: 类名(类名 &对象名)//拷贝构造函数的实现
{ 函数体 }
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8HPCHFTy-1638521462516)(assets/image-20211127221331-xg9twi0.png)]
如果程序员没有为类声明拷贝初始化构造函数,则编译器自己生成一个默认的拷贝构造函数。
这个默认的构造函数执行的功能是:把初始值对象的每个数据成员的值,都复制到将要建立的对象的对应数据成员。
3.5 引用拷贝构造函数的三种情况
1、当用类的一个对象去初始化该类的另一个对象时系统自动调用拷贝构造函数实现拷贝赋值。
void main(void)
{ Point A(1,2);
Point B(A); //拷贝构造函数被调用
cout<<B.GetX()<<endl;
}
2、若函数的形参为类对象,调用函数时,实参赋值给形参,系统自动调用拷贝构造函数。例如:
void fun1(Point p)
{ cout<<p.GetX()<<endl;
}
void main()
{ Point A(1,2);
fun1(A); //调用拷贝构造函数
}
3、当函数的返回值是类对象时,系统自动调用拷贝构造函数。例如:
Point fun2()
{ Point A(1,2);
return A; //调用拷贝构造函数将A的值烤到临时对象
}
void main()
{
Point B;
B=fun2();
}
3.5.1 拷贝构造函数举例
#include <iostream>
using namespace std;
class Point
{
public:
Point(int xx=0,int yy=0){X=xx; Y=yy;}
Point(Point& p);
int GetX() {return X;}
int GetY() {return Y;}
private:
int X,Y;
};
Point::Point (Point& p)
{
X=p.X;
Y=p.Y;
cout<<"拷贝构造函数被调用"<<endl;
}
//形参为Point类的函数
void fun1(Point p)
{ cout<<p.GetX()<<endl;
}
//返回值为Point类对象的函数
Point fun2()
{
Point A(1,2);
return A;
}
//主程序
int main()
{
Point A(4,5);//第一个对象A
Point B(A);//A初始化B第一次调用拷贝函数
cout<<B.GetX()<<endl;
fun1(B);//对象B为fun1实参,第二次调用
B=fun2();//函数返回值是类对象,第三次调用
cout<<B.GetX()<<endl;
}
4、析构函数
- 完成对象被删除前的一些清理工作。
- 在对象的生存期即将结束的时刻系统自动调用它,然后再释放此对象所属的空间。
- 如果程序中未声明析构函数,编译器将自动产生一个默认的析构函数。
4.1 构造函数和析构函数
#include<iostream>
using namespace std;
class Point
{
public:
Point(int xx,int yy);
~Point();
//...其它函数原形
private:
int X,int Y;
};
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MXnZiXtq-1638521462517)(assets/image-20211127223832-m9ccyli.png)]
Point::Point(int xx,int yy)
{ X=xx;
Y=yy;
}
Point::~Point()
{
}
#include <iostream>
#include <cmath>
using namespace std;
class Point
{
public:
Point(int xx=0, int yy=0)
{
X=xx; Y=yy; //countP++;
}
Point(Point &p) //拷贝构造函数
{
X = p.X;
Y = p.Y;
}
int GetX() {
return X;
}
int GetY() {
return Y;
}
static void GetC() { //非静态方法引用了静态成员变量,需要在类的外部初始化静态成员
//cout << x << endl; //错误
cout<<" Object id=" << countP << endl;
}
static void GetC(Point &p) { //非静态方法引用了静态成员变量,需要在类的外部初始化静态成员
cout << p.X << endl; //错误
cout<<" Object id=" << countP << endl;
}
friend double computeDistance(Point &a, Point &b); //友元函数,可以访问Point私有成员
friend class Line; //友元类,可以访问Point私有成员
private:
int X, Y;
static int countP;
};
class Line{
double computeDistance()
{
return sqrt((a.X - b.X) * (a.X- b.X) + (a.Y - b.Y) * (a.Y - b.Y)); //因为Line是Point的友元类,所以可以直接访问Point里的私有属性
}
private:
Point a, b;
};
int Point::countP = 0;
double computeDistance(Point &a, Point &b)
{
//cout << a.GetX() ;//访问对象里共有的方法,没有问题
return sqrt((a.X - b.X) * (a.X- b.X) + (a.Y - b.Y) * (a.Y - b.Y)); //因为computeDistance是Point的友元函数,所以可以直接访问Point里的私有属性
}
int main()
{
Point::GetC(); //没有任何对象的时候就可以调用静态方法和静态属性
Point A(4,5);
cout<<"Point A,"<<A.GetX()<<","<<A.GetY();
A.GetC();
Point B(A);
cout<<"Point B,"<<B.GetX()<<","<<B.GetY();
B.GetC();
return 0;
}