C++面向对象(下)
# 三、重载运算符和重载函数
# 函数重载
允许多个函数名称一样,但是参数不同的情况存在(这在Object- C中是不允许的)
几个要点:
1、同一作用域
2、多个函数名称相同
3、函数参数个数,或顺序,或类型不一样
4、不会判断返回值类型
5、重载函数,不是覆盖函数,而是根据调用函数名称和参数进行对比,选择最合理的那个,没有一个完全匹配的,那么就会选择最相识的函数进行绑定,显然大部分场景错误匹配都可能报错(匹配的过程叫重载决策)
示例:
#include <iostream>
using namespace std;
class Log
{
public:
void log(int i) {
cout << "Log 调用了整型: " << i << endl;
}
void log(double d) {
cout << "Log 调用了双浮点型: " << d << endl;
}
void log(string c) {
cout << "Log 调用了字符串: " << c << endl;
}
};
int main(void)
{
Log log1;
log1.log(8);
log1.log(88.8);
log1.log("hello world!");
return 0;
}
结果:
Log 调用了整型: 8
Log 调用了双浮点型: 88.8
Log 调用了字符串: hello world!
# 运算符重载
要点:
1、运算符是带有特殊名称的函数(通常非字母的符号)
2、可以重定义或重载大部分 C++ 内置的运算符
3、重载运算符有一个返回类型和一个参数列表(同函数)
4、相同类型的对象进行相加,参数通常是this 同类型
5、重载定义中,当前对象的属性可以使用 this-> 运算符进行访问,也可以直接访问
示例:
#include <iostream>
using namespace std;
class Box
{
public:
// 构造函数定义:长,宽,高
Box(double l=1.0, double w=2.0, double h=4.0)
{
length = l;
width = w;
height = h;
}
// 重载 - 运算符,对象相减
Box operator-(const Box& b)
{
Box box;
box.length = this->length - b.length;
box.width = this->width - b.width;
box.height = this->height - b.height;
return box;
}
// 重载 ++ 运算符,Box 对象自相加
Box operator++(int)
{
Box box(length, width, height);
++length;
++width;
++height;
return box;
}
double Area()
{
return length * width;
}
double Volume()
{
//通过 this指针访问面积函数
return this->Area() * height;
}
private:
double length; //长
double width; //宽
double height; //高
};
int main(void)
{
Box box1(4.5, 6.7, 8.9); // 声明 box1
Box box2(3.5, 4.0, 8.0); // 声明 box2
Box box3 = box2 - box1;
cout << "box3 的体积 : " << box3.Volume() << "立方" <<endl;
cout << "box2 ++ 前的体积 : " << box2.Volume() << "立方" <<endl;
box2 ++;
cout << "box2 ++ 后的体积 : " << box2.Volume() << "立方" <<endl;
return 0;
}
运行:
box3 的体积 : -2.43立方
box2 ++ 前的体积 : 112立方
box2 ++ 后的体积 : 202.5立方
# 可重载运算符/不可重载运算符
下面是可重载的运算符列表:
+ | - | * | / | % | ^ |
---|---|---|---|---|---|
& | | | ~ | ! | , | = |
< | > | <= | >= | ++ | -- |
<< | >> | == | != | && | || |
+= | -= | /= | %= | ^= | &= |
|= | *= | <<= | >>= | [] | () |
-> | ->* | new | new [] | delete | delete [] |
下面是不可重载的运算符列表:
:: | .* | . | ?: |
---|---|---|---|
更多示例:
https://www.w3cschool.cn/cpp/cpp-overloading.html
https://blog.csdn.net/HelloZEX/article/details/89713061
# 四、多态
# 多态
要点:
1、继承发生时,子类实现父类相同函数名的方法,那么子类调用这个函数时,会优先决策子类是否存在满足这个条件的函数,没有才会去父类,甚至父类的父类
2、子类调用某个函数又多种选择时,叫多态(同一个函数多种实现形态可选择)
3、⚠️ virtual:虚函数 ,表示函数是动态确定的
4、子类要重写父类函数,那么父类函数要 virtual 修饰,表示动态执行,否则永远执行 基类的函数。
示例1:
#include <iostream>
using namespace std;
class Person
{
public:
string name;
int age;
void eat() {
cout << "老师【" << name << "】 吃东西" <<endl;
}
virtual void jump() {
cout << "老师【" << name << "】 跳来跳去" <<endl;
}
};
class Student: public Person
{
public:
string name;
int age;
void eat() {
cout << "学生【" << name << "】 吃东西" <<endl;
}
void jump() {
cout << "学生【" << name << "】 跳来跳去" <<endl;
}
};
int main( )
{
Student student;
student.name = "小明";
Person *person = &student;
person->eat();
person->jump();
return 0;
}
结果
老师【】 吃东西
学生【小明】 跳来跳去
问题:
1、虽然person 本质上指向的是student对象的地址,按道理 eat() 应该是调用student 方法,但是实际调用的还是老师对象,但是 jump() 确调用 了 子类的方法。区别就是 virtual 虚函数 修饰了基类成员函数。
2、编译时,编译器认为person声明使用的是Person,那么就实际分配一个老师类型对象,虽然引用地址了,但是没有实际生效。
原因是,默认成员和成员函数都是内联函数,编译时会进行代码copy。编译之后就当成是代码层面的类型了。
3、 virtual 虚函数 :如果要程序编译时知道,这个成员函数我后面可能要动态替换的,那么就需要一个标记了 ,就是虚函数。或者理解为动态的,
OC 中默认对象都是动态 runtime,所以不需要显式修饰,
Swift是静态编译的,所以和C++ 一样需要动态 dymic 修饰
# 虚函数
虚函数 是在基类中使用关键字 virtual 声明的函数。在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数。
我们想要的是在程序中任意点可以根据所调用的对象类型来选择调用的函数,这种操作被称为动态链接,或后期绑定。
动态链接在OC这门动态语言中很熟悉了,比如dyld动态链接器。
# 纯虚函数
1、基类中该函数只是简单声明,不实现。 有子类来实现,这样在C++中的虚函数可以叫纯虚函数
2、⚠️ 注意:基类声明时 = 0 告诉编译器,函数没有主体,上面的虚函数是纯虚函数。
#include <iostream>
using namespace std;
class Person
{
public:
string name;
int age;
// = 0 告诉编译器,函数没有主体,是纯虚函数。
virtual void doHomework() = 0;
};
class Student: public Person
{
public:
string name;
int age;
void doHomework() {
cout << "学生【" << name << "】 完成家庭作业!" <<endl;
}
};
int main( )
{
Student student;
student.name = "小明";
Person *person = &student;
person->doHomework();
return 0;
}
运行
学生【小明】 完成家庭作业!
# 五、数据、接口抽象
数据抽象:(数据处理逻辑黑盒)
1、是一种对外函数调用和函数内部实现相分离的编程(设计)技术。
2、同样调用eat函数,结果是根据对象有所区分的,内部逻辑我们不用管,只需要调用对应函数就行。
接口抽象:(接口通用、标准化)
1、抽象基类为所有的外部应用程序提供一个适当的、通用的、标准化的接口。然后,派生类通过继承抽象基类,就把所有类似的操作都继承下来。
2、外部应用程序提供的功能(即公有函数)在抽象基类中是以纯虚函数的形式存在的。这些纯虚函数在相应的派生类中被实现。
示例:
#include <iostream>
using namespace std;
class Person
{
public:
string name;
int age;
virtual void eat() = 0;
};
class Teacher: public Person
{
public:
void eat() {
cout << "老师【" << name << "】 吃东西" <<endl;
}
};
class Student: public Person
{
public:
void eat() {
cout << "学生【" << name << "】 吃东西" <<endl;
}
};
int main( )
{
Student student;
student.name = "小明";
student.eat();
Teacher teacher;
teacher.name = "Luce";
teacher.eat();
return 0;
}
结果
学生【小明】 吃东西
老师【Luce】 吃东西
数据抽象: eat函数内部实现外部不需要知道
接口抽象:不管是老师还是学生,都有相同的接口eat。大家表示吃的时候,都用这个接口实现eat功能,不能一个eat1(), 一个eat2()