持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第5天
今天继续来看面向对象的内容
继承允许我们依据一个类来定义另一个类,使创建和维护一个应用程序变得更加容易,也达到了重用代码和提高执行效率的结果
例:创建animal类,再创建dog类,继承animal类
class animal{
};
class dog :public animal{
};
基类&派生类
已有的animal类称为基类,新建的dog类成为你派生类
#include <iostream>
using namespace std;
//基类
class shape{
public:
void setwidth(int);
void setheight(int);
protected:
int width;
int height;
};
void shape::setwidth(int w) {
width=w;
}
void shape::setheight(int h) {
height = h;
}
//派生类
class Rectangle :public shape{
public:
int getarea(void);
};
int Rectangle::getarea() {
return width * height;
}
int main(){
Rectangle r1;
r1.setwidth(10);
r1.setheight(2);
cout << r1.getarea() << endl;
return 0;//20
}
访问控制和继承
| 访问 | public | protected | private |
|---|---|---|---|
| 同一个类 | √ | √ | √ |
| 派生类 | √ | √ | × |
| 外部的类 | √ | × | × |
派生类会继承所有的基类方法,除了:
- 基类的构造函数,析构函数,拷贝构造函数
- 基类的重载运算符
- 基类的友元函数
继承类型
当一个派生类继承基类,该基类可以被继承为public,protected,private。通过访问修饰符access-specifier来指定
通常使用public继承,一般不使用protected和private继承
- 公有继承:一个类派生自公有基类:基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但可以调用基类的公有成员和保护成员访问。
- 保护继承:一个类派生自保护基类,基类的公有和保护成员是派生类的保护成员
- 私有继承:一个类派生自私有基类,基类的公有和保护成员是派生类的私有成员
多继承
含义:一个子类可以有多个父类,继承多个父类的特性
//继承语法
class <派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>,…
{
<派生类类体>
};
#include <iostream>
using namespace std;
//基类1
class Shape{
public:
void setwidth(int);
void setheight(int);
protected:
int width;
int height;
};
void Shape::setheight(int h) {
height = h;
}
void Shape::setwidth(int w) {
width = w;
}
//基类2
class PaintCost{
public:
int cost(int);
};
int PaintCost::cost(int area) {
return area * 70;
}
//派生类
class Rentangle :public Shape, public PaintCost{//继承两个父类
public:
int getarea(void );
};
int Rentangle::getarea() {
return width * height;
}
int main(){
Rentangle r1;
int area;
r1.setheight(2);
r1.setwidth(3);
area = r1.getarea();
cout << "要花费的价格是:" << r1.cost(area) << endl;//420
}
重载运算符和重载函数
重载声明:与 之前在这个作用域内已声明的 函数或方法 具有相同的名称,但他们的参数列表和定义不同
函数重载和运算符重载:C++允许在同一作用域中的某个函数和运算符指定多个定义
重载决策:调用一个重载函数或重载运算符时,编译器会根据你的参数类型去和定义的参数类型比较,选择出最合适的定义。
- 重载函数实例:
#include <iostream>
using namespace std;
class Printdata{
public:
void Print(int);
void Print(double);
void Print(char);
};
void Printdata::Print(int a) {
cout << a <<endl;
}
void Printdata::Print(char c) {
cout << c << endl;
}
void Printdata::Print(double b) {
cout << b << endl;
}
int main(){
Printdata p;
p.Print(10);
p.Print('a');
p.Print(9.9);
return 0;
}
-
运算符重载
- 可以重定义大多C++的内置运算符
- 重载运算符是带有特殊名称的函数,由关键字operator和后面要重载的运算符构成,和其他函数一样,有一个返回值和参数列表
Box operator+(const Box&);
实例:
#include <iostream>
using namespace std;
class Box{
public:
void setlength(int);
void setbreadth(int);
void setheight(int);
int getVolume(void);
Box operator+(const Box& b){
Box box;
box.length = this->length + b.length;
box.breadth = this->breadth + b.breadth;
box.height = this->height + b.height;
return box;//就是返回已经加完之后的box
}
private:
int length;
int breadth;
int height;
};
void Box::setheight(int h) {
height = h;
}
void Box::setlength(int l) {
length = l;
}
void Box::setbreadth(int b) {
breadth = b;
}
int Box::getVolume() {
return length * breadth * height;
}
int main (){
Box box1;
Box box2;
int volume = 0;
box1.setlength(1);
box1.setbreadth(2);
box1.setheight(3);
box2.setlength(4);
box2.setbreadth(5);
box2.setheight(6);
volume = box1.getVolume();
cout << "box1:" << volume << endl;
volume = box2.getVolume();
cout << "box2:" << volume << endl;
Box box3;
box3 = box1 + box2;
volume = box3.getVolume();
cout << "box3:" << volume <<endl;
return 0;
}
多态
即多种形态,当类之间存在层次结构,且类之间通过继承关联,就会用到多态。多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。
- 这个例子就是Shape被派生为两个类,调用函数时会根据调用函数的对象来执行不同的函数
#include <iostream>
using namespace std;
class Shape{
protected:
int width;
int height;
public:
Shape(int,int);
virtual int getarea(void);
};
Shape::Shape(int w, int h) {
width = w;
height = h;
}
int Shape::getarea() {
cout << "Parent class area :" <<endl;
return 0;
}
class Rectangle :public Shape {
public:
Rectangle( int a=0, int b=0):Shape(a, b) { }//调用父类的构造方法
int getarea();
} ;
int Rectangle::getarea() {
cout << "Rectangle class area :" <<endl;
return width * height;
}
class Triangle:public Shape{
public:
int getarea();
Triangle( int a=0, int b=0):Shape(a, b) { }
};
int Triangle::getarea() {
cout << "Triangle class area :" <<endl;
return width * height/2;
}
int main (){
Shape *shape;
Rectangle rec(10,7);
Triangle tri(10,5);
// 存储矩形的地址
shape = &rec;
// 调用矩形的求面积函数 area
shape->getarea();
// 存储三角形的地址
shape = &tri;
// 调用三角形的求面积函数 area
shape->getarea();
return 0;
}
关于子类继承和调用父类的构造函数
构造函数和其他的成员变量不同,它不能被子类继承,所以子类为了初始化从父类继承来的成员变量,需要调用其父类的构造函数
数据抽象
什么是数据抽象:只像外界提供关键信息,隐藏后台细节(只表现必要信息但不呈现细节)。就像电视机一样,你可以换频道,调音量,但你不知道它怎么接收信号(即不知道他内部是怎么实现的)。放到C++编程也是一样,C++类为数据抽象提供可能,它向外界提供了很多操作对象数据的公共方法,外界实际上并不清楚内部的实现。就例如我们经常调用的sort()函数,经常使用类iostream的cout对象来输出数据。
访问标签强制抽象
一个类可以包含零个或多个访问标签
- 使用public定义的成员可以访问程序的all
- 使用private定义的成员无法访问到使用类的代码
数据抽象的好处
- 类的内部受到保护,防止无意的用户错误导致类的内部受损
- 类实现可能随着时间的推移发生变化
实例
#include <iostream>
using namespace std;
class Adder{
public:
Adder(int i = 0){
total = i;
}
//对外接口
void addNum(int number){
total=total+number;
}
//对外接口
int gettotal(){
return total;
}
private:
int total;
};
int main(){
Adder a;
a.addNum(10);
a.addNum(20);
cout << a.gettotal() << endl;
return 0;
}
公有成员addNum和gettotal是对外接口,用户需要知道他们来使用类
数据封装
C++程序有两个基本要素,即程序语句(函数)和程序数据。封装就是把数据和函数绑在一起,避免外界的干扰或误用。
上面提到的数据抽象是向用户暴露接口而把具体的实现细节隐藏起来。
C++创建类成员有public,protected以及private。这就是一种封装方式。实际上,任何带有公有成员和私有成员的类都可以作为数据封装和数据抽象的实例
接口(抽象类)
首先还是先搞懂什么是接口类,接口类是只提供方法声明,自身不提供方法定义的抽象类。接口类的方法定义/实现只能由接口类的子类完成 。
#include <iostream>
using namespace std;
class Shape{
public:
void setwidth(int);
void setheight(int);
//提供接口框架的纯虚函数
virtual int getarea() = 0;
protected:
int width;
int height;
};
void Shape::setheight(int h) {
height = h;
}
void Shape::setwidth(int w) {
width = w;
}
class Rectangle :public Shape{
public:
int getarea();
};
int Rectangle::getarea() {//接口类的方法定义/实现只能由接口类的子类完成 。
return width * height;
}
int main(){
Rectangle r1;
r1.setheight(10);
r1.setwidth(3);
cout << r1.getarea() <<endl;
return 0;
}
结语
过完接口这一节后C++面向对象的基本内容就已经结束了,对于我来说目前C++的学习就告一段落了,因为再学一些更高级的东西自己也会因为用不上然后很快忘记。在过面向对象的内容的时候明显感觉比基本语法要吃力很多,虽然是学完一遍了,但如果不复习的话应该也是忘得非常快。目前的计划就是明天再照着写的博客复习一遍面向对象,然后后天直接开始数据结构了。