oop编程和c语言这种面向过程的编程各有利弊。c语言其执行效率高,在一些非大型的项目中效果很好,但是在大型项目中要维护、更新如果还按照一般的思想去完成的话,工作量太大,且不太现实。oop的思想可以说是一场编程思想的扩展。其oop机制具有三大特性:封装、继承和多态,包括如信息隐藏、封装函数、抽象数据类型、继承、多态、函数重载、运算符重载、乏型编程(模板)等。这次来讲述其中的一些特点。
前言
为了文章的结构清晰和方便记忆,本文以三大特性分别展开叙述,为了解释方便,会给出一段代码,对代码做出解析,并引出各个概念。
代码如下:
#include<iostream>
using namespace std;
//基类People
class People{
public:
//一般定义的类的方法
void setname(char *name);
void setage(int age);
int getage();
//构造与析构函数
People(char *name,int age);
People();
~People();
//虚函数
virtual void v(){cout<<"People::v"<<endl;}
protected:
char *m_name;
private:
int m_age;
};
//构造函数定义
People::People(char *name,int age){
m_name=name;
m_age=age;
}
People::People(){
m_name="张三";
m_age=20;
}
People::~People(){}
//其他函数定义
void People::setname(char *name){ m_name = name; }
void People::setage(int age){ m_age = age; }
int People::getage(){ return m_age; }
//派生类Pupil***************
class Pupil: public People{
public:
//类的方法声明
void sethobby(char *hobby);
char *gethobby();
void display();
//构造函数
Pupil(char*name,int age,char* hobby);
Pupil(){};
//重写基类中的v函数
void v(){cout<<"Pupil::v"<<endl;}
private:
char *m_hobby;
};
//构造函数定义
Pupil::Pupil(char* name,int age,char* hobby){
m_name=name;
setage(age);
sethobby(hobby);
}
//其他函数定义
void Pupil::sethobby(char *hobby){ m_hobby = hobby; }
char *Pupil::gethobby(){ return m_hobby; }
void Pupil::display(){
cout<<m_name<<"的年龄是"<<getage()<<",TA喜欢"<<gethobby()<<"。"<<endl;
}
int main(){
//三种调用方法
//对象vivo
Pupil vivo("vivo",12,"篮球");//创建对象时向构造函数传参
vivo.display();
vivo.v();
//对象oppo
Pupil opp;
opp.setname("opp");
opp.setage(13);
opp.sethobby("乒乓球");
opp.display();
//对象hua
Pupil *phua = new Pupil("hua", 14, "足球");//创建对象时向构造函数传参
phua->display();
return 0;
}
首先来分析一下代码,给出思维导图(People类和其派生类pupil类的成员关系)。
cpp构造与析构
在C++中,有一种特殊的成员函数,它的名字和类名相同,没有返回值,不需要用户显式调用(用户也不能调用),而是在创建对象时自动执行。这种特殊的成员函数就是构造函数(Constructor)。如果用户自己没有定义构造函数,那么编译器会自动生成一个默认的构造函数,只是这个构造函数的函数体是空的,也没有形参,也不执行任何操作。例如上述的People();
构造函数也是允许重载
的,例如上述代码中的:
People(char *name,int age);
People();
创建对象时系统会自动调用构造函数进行初始化工作,同样,销毁对象时系统也会自动调用一个函数来进行清理工作,例如释放分配的内存、关闭打开的文件等,这个函数就是析构函数。
~People();
析构函数(Destructor)也是一种特殊的成员函数,没有返回值,不需要程序员显式调用(程序员也没法显式调用),而是在销毁对象时自动执行。构造函数的名字和类名相同,而析构函数的名字是在类名前面加一个~符号。
注意:析构函数没有参数,不能被重载,因此一个类只能有一个析构函数。如果用户没有定义,编译器会自动生成一个默认的析构函数。
再提一嘴
,显式调用和隐式调用,显示调用即People(xx),隐式调用就是People=xx。
一、封装特性
在oop中,封装是指将数据(属性)和实现操作(方法)的代码集中起来放在对象内部,并尽可能隐蔽对象的内部细节。对象好像是一个不透明的黑盒子,从外面是看不见的,更不能从外面直接访问或修改这些数据以及代码。我们将对象的特性称为“成员变量”(属性),将对象的行为成为“成员函数”(方法),被封装的特性只能通过特定的行为去访问。
封装有两方面的含义:
(1)是将有关的数据和操作代码封装在一个对象中,各个对象相互独立,互不干扰.
(2)是将对象中的某些数据与操作代码对外隐蔽,即隐蔽其内部细节,只留下少量接口,以便于外界联系,接收外界消息。
封装的好处是:将对象的使用者和设计者分开,大大降低了人们操作对象的复杂程度,使用者不必知道对象行为实现的具体细节,只需要使用者提供的接口即可自如的操作对象。封装的结果实际上隐蔽了复杂性,并提供了代码重用性,并提供了代码重用性,从而减轻了开发一个软件系统的难度。以上是 link的说法。
再说下我的理解:就是隐藏对象的属性和实现细节,仅对外公开接口和对象进行交互,将数据和操作数据的方法进行有机结合。例1:函数就是封装的一种,只需要调用就可以得到返回值。例2:上述代码中,定义好类之后,调用的时候只需要pup.setname()
。而其中的属性m_name
和setname()
、getname()
这些就是属性和方法的有机结合。
还有一点,在本类中的
属性
及其方法
,只能这个类的对象(实例化后)能操作这些 属性、方法。其他类的对象是不能操作本类的属性、方法。这也是oop编程的特性。
二、继承特性
继承是cpp的第二大特性,继承允许我们依据另一个类来定义一个类,这使得创建和维护一个应用程序变得更容易。这样做,也达到了重用代码功能和提高执行效率的效果。
上述代码中,通过利用People类只需要增加一部分属性和方法就可以得到一个新的Pupil类。既提高了People类的复用,又很方便的得到了新类。
2.1基类的派生
上述的People类其实就是基类,Pupil类是其派生类,Pupil类也可以继续得到它的派生类。其定义方式如class People: public Pupil
所示。其中piblic是继承方式,类似于类中有三种类型的成员,继承方式也有三种。分别是 public继承、protected继承和private继承。下表汇总了不同继承方式对不同属性的成员的影响结果
继承方式/基类成员 | public成员 | protected成员 | private成员 |
---|---|---|---|
public继承 | public | protected | 不可见 |
protected继承 | protected | protected | 不可见 |
private继承 | private | private | 不可见 |
访问属性设置的原则:
- 需要被外界访问的成员, 设置为: public。
- 只能在当前类中访问的成员, 设置为: private。
- 只能在当前类和派生类中访问的成员, 设置为: protected。
其中的不可见意思是不能直接对该成员进行操作,例如上述代码中的pup.age=18
这条语句就是非法的,但是也不代表不能设置该属性了,可以通过public属性的setage()
和getage()
来设置和得到age
属性的值。这样的好处是什么?答:保护属性,以防被其他途径所篡改,造成损失 😦
三、多态特性
多态按字面的意思就是多种形态。当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态。
C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。
这里有两种多态类型,静态多态和动态多态。下面通过一个例题来分析
#include <iostream>
class Base{
public:
int Bar(char x){
return (int)(x);
}
virtual int Bar(int x){
return (2 * x);
}
};
class Derived : public Base{
public:
int Bar(char x){
return (int)(-x);
}
int Bar(int x){
return (x / 2);
}
};
int main(void){
Derived Obj;
Base *pObj = &Obj;
printf("%d,", pObj->Bar((char)(100)));
printf("%d,", pObj->Bar(100));
}
结果:100,50
其中,基类指针和基类指针的区别借鉴于 基类指针和派生类指针。
这里讲pObj对象声明为Base类型,其实际类型是Derived类型
运行时,虚函数动态绑定,调用的是Derived类中的Bar函数
非虚函数静态绑定,调用Base类的Bar。
虚函数 是在基类中使用关键字 virtual 声明的函数。在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数。
我们想要的是在程序中任意点可以根据所调用的对象类型来选择调用的函数,这种操作被称为动态链接,或后期绑定。
您可能想要在基类中定义虚函数,以便在派生类中重新定义该函数更好地适用于对象,但是您在基类中又不能对虚函数给出有意义的实现,这个时候就会用到纯虚函数。virtual int area() = 0;