C++基础第七弹
2.C++继承
-
继承允许我们依据另一个类来定义一个类
-
继承代表了 is a关系
//基类 class Animal{ }; //子类 class Dog:public Animal{ };
-
语法:
class 子类名:访问修饰符 父类名{ };
访问控制和继承
- 子类可以访问父类中所有的非私有成员,因此父类成员如果不想被子类的成员函数访问,则就在父类中声明为private
访问 | public | protected | private |
---|---|---|---|
同一个类 | yes | yes | yes |
派生类 | yes | yes | no |
外部的类 | yes | no | no |
- 子类继承了所有的父类方法,但有一些情况除外:
- 父类的构造函数、析构函数、拷贝构造函数
- 父类的重载运算符
- 父类的友元函数
继承类型
- 公有继承(public):当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但可以通过调用基类的公有和保护成员来访问。
- 保护继承(protected): 当一个类派生自保护基类时,基类的公有和保护成员将成为派生类的保护成员。
- 私有继承(private):当一个类派生自私有基类时,基类的公有和保护成员将成为派生类的私有成员。
多继承
-
多继承就是一个子类可以有多个父类,继承了多个父类的特性
class 子类名:访问修饰符 父类名1,访问修饰符 父类名2, ......{ 子类代码块; };
//
// Created by 16690 on 2024/4/22.
//
#include <iostream>
using namespace std;
class Shape{
public:
void setWidth(int wid){
width = wid;
}
void setHeight(int hei){
height = hei;
}
protected:
int width;
int height;
};
class PaintCost{
public:
int getCost(int area){
return area * 70;
}
};
class Rectangle:public Shape,public PaintCost{
public:
int getArea(){
return (width * height);
}
};
int main(void){
Rectangle rectangle;
int area;
rectangle.setWidth(10);
rectangle.setHeight(20);
area = rectangle.getArea();
cout << "Total area: " << area << endl;
int cost;
cost = rectangle.getCost(area);
cout << "cost: " << cost << endl;
return 0;
}
- 构造函数调用顺序:基类 > 成员类 > 派生类;
- 多继承派生类: 基类构造顺序 依照 基类继承顺序调用
- 类成员:依照 类成员对象 定义顺序 调用成员类构造函数
3.C++重载运算符和重载函数
- C++允许在同一作用域中的某个函数和运算符指定多个定义,分别称为函数重载和运算符重载
- 重载是指一个与之前已经在该作用域内声明过的函数或方法具有相同的名称,但参数列表和定义不相同
- 当调用一个重载函数和重载运算符时,编译器通过将所使用的参数类型与定义中的参数类型进行比较,决定选用最合适的定义,选择最合适的重载函数或重载运算符的过程,称为重载决策
C++中的函数重载
-
在同一个作用域内,可以声明几个功能类似的同名函数,但这些同名函数的形式参数(参数的个数、类型、顺序)必须不同,不能仅通过返回类型的不同来重载函数
// // Created by 16690 on 2024/4/22. // #include <iostream> using namespace std; class PrintData{ public: void print(int i){ cout << "整数 : " << i << endl; } void print(double f){ cout << "浮点数 : " << f << endl; } void print(char c[]){ cout << "字符 : " << c << endl; } }; int main(void){ PrintData pd; pd.print(5); pd.print(500.263); pd.print("Hello C++"); return 0; }
C++中的运算符重载
-
可以重定义或重载大部分C++内置的运算符,然后就能使用自定义类型的运算符
-
重载的运算符是带有特殊名称的函数,函数名是由关键字operator和其后重载的运算符符号构成的,与其他函数一样,重载运算符有一个返回类型和一个参数列表
-
语法:
类名 operator 运算符(const 类名& 参数名){ }
-
声明加法运算符用于把两个Box对象相加,返回最终的Box对象,大多数的重载运算符可被定义为普通的非成员函数或被定义为类成员函数
Box operator+(const Box&, const Box&);
-
通过对运算符按照自己逻辑进行运算,同时将这个运算规则放到代码需要用到该运算符的内容中
//
// Created by 16690 on 2024/4/22.
//
#include <iostream>
using namespace std;
class Box{
private:
double length;
double width;
double height;
public:
double getVolume(void){
return length * width * height;
}
void setLength(double len){
length = len;
}
void setWidth(double wid){
width = wid;
}
void setHeight(double hei){
height = hei;
}
//重载 + 运算符
Box operator+(const Box& box){
Box boxTemp;
boxTemp.length = this -> length + box.length;
boxTemp.width = this -> width + box.width;
boxTemp.height = this -> height + box.height;
return boxTemp;
}
};
int main(void){
Box box1, box2, box3;
double volume = 0.0;
box1.setWidth(5.0);
box1.setHeight(10.0);
box1.setLength(15.0);
box2.setWidth(3.0);
box2.setHeight(6.0);
box2.setLength(7.0);
//box1的体积
volume = box1.getVolume();
cout << "Volume of Box1 : " << volume << endl;
//box2的体积
volume = box2.getVolume();
cout << "Volume of Box2 : " << volume << endl;
//box3的体积
box3 = box1 + box2;
volume = box3.getVolume();
cout << "Volume of Box3 : " << volume << endl;
return 0;
}
可重载运算符 | 运算符类型 |
---|---|
双目算术运算符 | + (加),-(减),*(乘),/(除),% (取模) |
关系运算符 | ==(等于),!= (不等于),< (小于),> (大于),<=(小于等于),>=(大于等于) |
逻辑运算符 | ||(逻辑或),&&(逻辑与),!(逻辑非) |
单目运算符 | + (正),-(负),*(指针),&(取地址) |
自增自减运算符 | ++(自增),–(自减) |
位运算符 | | (按位或),& (按位与),~(按位取反),^(按位异或),,<< (左移),>>(右移) |
赋值运算符 | =, +=, -=, *=, /= , % = , &=, |=, ^=, <<=, >>= |
空间申请与释放 | new, delete, new[ ] , delete[] |
其他运算符 | ()(函数调用),->(成员访问),,(逗号),[](下标) |
不可重载运算符 | 运算符类型 |
---|---|
成员访问运算符 | . |
成员指针访问运算符 | .* ,->* |
域运算符 | :: |
长度运算符 | sizeof |
条件运算符 | ?: |
预处理符号 | # |
4.C++多态
-
多态就是多种形态,当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态
-
C++多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数
-
形成多态必须具备三个条件:
-
必须存在继承关系;
-
继承关系必须有同名虚函数(其中虚函数是在基类中使用关键字Virtual声明的函数,在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数);
-
存在基类类型的指针或者引用,通过该指针或引用调用虚函数;
-
-
父类的虚函数或纯虚函数在子类中依然是虚函数。当不希望父类的某个函数在子类中被重写,用关键字 final 来避免该函数再次被重写
//
// Created by 16690 on 2024/4/22.
//
#include <iostream>
using namespace std;
class Shape {
protected:
int width, height;
public:
Shape(int a = 0, int b = 0) {
width = a;
height = b;
}
//使用virtural定义虚函数,可以供子类重写
virtual int area() {
cout << "Parent class area :" << endl;
return 0;
}
};
class Rectangle : public Shape {
public:
//使用成员初始化列表来调用父类的构造函数
Rectangle(int a = 0, int b = 0) : Shape(a, b) {
}
int area() {
cout << "Rectangle class area :" << (width * height) << endl;
return (width * height);
}
};
class Triangle : public Shape {
public:
Triangle(int a = 0, int b = 0) : Shape(a, b) {
}
int area() {
cout << "Triangle class area :" << (width * height / 2) << endl;
return (width * height / 2);
}
};
int main(void) {
Shape *shape;
Rectangle rectangle(10, 7);
Triangle triangle(10, 5);
//存储矩形的地址
shape = &rectangle;
shape->area();
//存储三角形的地址
shape = ▵
shape-> area();
return 0;
}
虚函数
-
虚函数 是在基类中使用关键字 virtual 声明的函数。(可以在子类进行重写该函数)
在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数。
-
在程序中任意点可以根据所调用的对象类型来选择调用的函数,这种操作被称为动态链接,或后期绑定
纯虚函数
-
在基类中定义虚函数,以便在派生类中重新定义该函数更好地适用于对象,但是在基类中又不能对虚函数给出有意义的实现,这个时候就会用到纯虚函数。
-
= 0 告诉编译器,函数没有主体,代码中的虚函数是纯虚函数。
class Shape { protected: int width, height; public: Shape( int a=0, int b=0) { width = a; height = b; } // pure virtual function virtual int area() = 0; };
总结
- 有virtual修饰的函数,有实现,就是虚函数
- 有virtual修饰的函数,没有实现,就是纯虚函数
5.C++数据抽象
- 只向外界提供关键信息,并隐藏其后台的实现细节,即只表现必要的信息而不呈现细节
访问标签强制抽象
- 使用访问标签来定义类的抽象接口,一个类可以包含零个或多个访问标签
- 使用公共标签定义的成员都可以访问该程序的所有部分,一个类型的数据抽象视图由它的公共成员来定义
- 使用私有标签定义的成员无法访问到使用类的代码,私有部分对使用类型的代码隐藏了实现细节
数据抽象的好处
- 类的内部受到保护,不会因为无意的用户级错误导致对象状态受损
- 类实现可能随着时间的推移而发生变化,以便应对不断变化的需求,或应对哪些要求不改变用户级代码的错误报告
数据抽象的实例
- 任何带有公有和私有成员的类都可以作为数据抽象的实例
//
// Created by 16690 on 2024/4/22.
//
#include <iostream>
using namespace std;
class Adder{
private:
//需要隐藏的数据
int total;
public:
// 构造函数
Adder(int i =0 ){
total = i;
}
//对外的接口
void addNum(int number){
total += number;
}
int getTotal(){
return total;
}
};
int main(void){
Adder adder;
adder.addNum(10);
cout << "adder.getTotal = " << adder.getTotal() << endl;
return 0;
}
6.数据封装
- **程序语句(代码):**这是程序中执行动作的部分,它们被称为函数。
- **程序数据:**数据是程序的信息,会受到程序函数的影响。
封装是面向对象编程中的把数据和操作数据的函数绑定在一起的一个概念,这样能避免受到外界的干扰和误用,从而确保了安全。数据封装引申出了另一个重要的 OOP 概念,即数据隐藏。
数据封装是一种把数据和操作数据的函数捆绑在一起的机制,数据抽象是一种仅向用户暴露接口而把具体的实现细节隐藏起来的机制。
C++ 通过创建类来支持封装和数据隐藏(public、protected、private)。我们已经知道,类包含私有成员(private)、保护成员(protected)和公有成员(public)成员。默认情况下,在类中定义的所有项目都是私有的。
class Box
{
public:
double getVolume(void)
{
return length * breadth * height;
}
private:
double length; // 长度
double breadth; // 宽度
double height; // 高度
};
7.C++接口(抽象类)
-
接口描述了类的行为和功能,而不需要完成类的特定实现。
-
C++ 接口是使用抽象类来实现的,抽象类与数据抽象互不混淆,数据抽象是一个把实现细节与相关的数据分离开的概念。
如果类中至少有一个函数被声明为纯虚函数,则这个类就是抽象类。纯虚函数是通过在声明中使用 “= 0” 来指定的
class Box
{
public:
// 纯虚函数
virtual double getVolume() = 0;
private:
double length; // 长度
double breadth; // 宽度
double height; // 高度
};
- 设计抽象类(通常称为ABC)的目的,是为了给其他类提供一个可以继承的适当的基类,抽象类不能被用于实例化对象,它只能作为接口使用,若试图实例化一个抽象类的对象,会导致编译错误
- 若一个ABC的子类需要被实例化,则必须实现每个纯虚函数,这意味着C++支持使用ABC声明接口,若没有在派生类中重写纯虚函数,就尝试实例化该类的对象,会导致编译错误,可用于实例化对象的类被称为具体类
#include <iostream>
using namespace std;
// 基类
class Shape
{
public:
// 提供接口框架的纯虚函数
virtual int getArea() = 0;
void setWidth(int w)
{
width = w;
}
void setHeight(int h)
{
height = h;
}
protected:
int width;
int height;
};
// 派生类
class Rectangle: public Shape
{
public:
int getArea()
{
return (width * height);
}
};
class Triangle: public Shape
{
public:
int getArea()
{
return (width * height)/2;
}
};
int main(void)
{
Rectangle Rect;
Triangle Tri;
Rect.setWidth(5);
Rect.setHeight(7);
// 输出对象的面积
cout << "Total Rectangle area: " << Rect.getArea() << endl;
Tri.setWidth(5);
Tri.setHeight(7);
// 输出对象的面积
cout << "Total Triangle area: " << Tri.getArea() << endl;
return 0;
}