一、C++的三大特性
封装、继承、多态
封装:即将一个对象的属性和行为封装成类,使其更符合人们对于一件事件的认知,
将属于这个对象的所有东西打包在一起。
继承:是面向对象编程使代码可以复用的最重要的手段,它可以让程序员在原有类的特性上进行扩展,
增加功能,这样产生的类叫做派生类,呈现出面向对象设计的层次结构,
由简单的基类到复杂的派生类的这么一个过程。
多态:字面意思就是多种形态。指同一个函数作用在不同的对象时,可以产生不同的行为,
多态通常通过虚函数和动态绑定来实现的。通过多态可以实现接口的重用,代码的可扩展性。
1、封装
即C++的独特语法类中成员属性,实例代码如下(演示了如何使用封装特性):
#include <iostream>
#include <string>
using namespace std;
//创建一个员工的类,其属性有姓名,年龄,薪资,行为:获取和设置属性的函数
class Employee
{
private:
string name;
int age;
double salary; //薪资,这三个个人信息是私有的
public:
// 构造函数初始化成员对象
Employee(string n, int a, double s) : name(n), age(a), salary(s) {}
// 公有成员函数用于设置私有成员变量的值
void setName(string n)
{
name = n;
}
void setAge(int a)
{
if (a >= 18)
{ // 对年龄进行简单的验证
age = a;
} else
{
cout << "Invalid age!" << endl;
}
}
void setSalary(double s)
{
if (s >= 0)
{ // 对薪水进行简单的验证
salary = s;
} else
{
cout << "Invalid salary!" << endl;
}
}
// 公有成员函数用于获取私有成员变量的值
string getName()
{
return name;
}
int getAge() {
return age;
}
double getSalary() {
return salary;
}
};
int main()
{
// 创建一个Employee对象
Employee emp("John Doe", 30, 50000);
// 输出员工信息
cout << "Name: " << emp.getName() << endl;
cout << "Age: " << emp.getAge() << endl;
cout << "Salary: $" << emp.getSalary() << endl;
// 修改员工信息
emp.setAge(35);
emp.setSalary(60000);
// 再次输出员工信息
cout << "\nUpdated Information:" << endl;
cout << "Name: " << emp.getName() << endl;
cout << "Age: " << emp.getAge() << endl;
cout << "Salary: $" << emp.getSalary() << endl;
return 0;
}
在这个示例中,Employee 类封装了员工的姓名、年龄和薪水信息。这些信息被声明为私有成员变量,只能通过公有成员函数来访问和修改。通过这种方式,可以隐藏对象的内部细节,并提供对外部的安全接口,以便在不暴露实现细节的情况下使用对象。
2、继承
继承就不用多说了,有共有继承、保护继承、私有继承。例子也比较简单
1)重载
除了函数重载、运算符重载和虚函数之外,C++中还有一些其他语法机制可以实现多态性,其中包括:
① 纯虚函数与抽象类、
纯虚函数是一个在基类中📌声明但没有定义的虚函数。通过将虚函数声明为纯虚函数,
可以使得基类成为抽象类,从而无法实例化对象,只能作为其他类的基类使用。
抽象类是包含纯虚函数的类,它的存在是为了提供一个接口,让派生类继承并实现这些虚函数。
② 虚析构函数
虚析构函数是一个在基类中声明为📌虚函数的析构函数。当基类指针指向派生类对象并且使用
delete 删除该指针时,只有通过将析构函数声明为虚函数,才能确保调用正确的析构函数,
从而正确释放内存。
因为顺序为基构派构派析基析,所以只有在基类中声明虚析函数才能在释放delete时先调用的
派生类的析构函数先
③ 函数模板与类模(了解)
函数模板和类模板允许在不同的上下文中以相同的代码实现对不同类型的操作。
通过模板的特性,可以在编译时确定要调用的具体函数或类,从而实现多态性。
3、多态
多态性的主要形式包括:
编译时多态性(静态绑定):也称为静态多态性,通过函数重载和运算符重载实现。
在编译的汇编阶段就确定要调用的函数或运算符版本,不涉及基类和派生类的关系。⭐
运行时多态性(动态绑定):也称为动态多态性,通过虚函数和继承实现。
在运行时确定要调用的函数版本,具体取决于对象的实际类型。⭐
体现多态性的主要细节语法包括以下几个方面:
① 虚函数(Virtual Functions) 动态多态性(动态绑定)
② 纯虚函数(Pure Virtual Functions)动态多态性(动态绑定)
② 函数重写(Function Overriding) 动态多态性(动态绑定)
函数重写:派生类 重写和基类 的同名虚(纯虚)函数⭐
③ 函数重载(Dynamic Binding) 静态多态性(静态绑定)
④ 运算符重载(Dynamic Binding) 静态多态性(静态绑定)
记住一个是编译的汇编阶段--静态、一个是运行阶段--多态
动态绑定的例子:
1.虚函数(Virtual Functions)
在基类中声明虚函数,允许派生类覆盖(override)该函数。👍👍👍
基类中的虚函数声明以 virtual 关键字开头。
class Base
{
public:
virtual void display()
{
cout << "这是基类的函数" << endl;
}
};
2.函数重写(Function Overriding)
重写或者覆盖的关键字:override
派生类可以重新实现基类中的虚函数,称为重写或覆盖。
派生类中的重写函数应该具有相同的原型(函数签名)和返回类型。
class Derived : public Base
{
public:
void display() override //注意有override关键字,而且是在函数后面的
{
cout << "这是派生类的重写(覆盖基类的)函数" << endl;
}
};
纯虚函数是在基类中声明但没有实现的虚函数,📌📌📌没有函数体。
📌📌📌包含纯虚函数的类称为抽象类,不能被实例化,但可以作为基类使用。
//抽象类 有纯虚函数的类(一般作为基类)
class AbstractBase
{
public:
virtual void display(); // 纯虚函数 没有函数体,意味着 派生类要重写它
};
class Derived : public AbstractBase
{
public:
void display() override
{
cout << "Derived class display function" << endl;
}
};
class AbstractBase xxx; //报错,不能实例化📌📌📌
虚函数:有函数体,意味着可以被重写, 📌但是也能调用它
纯虚函数: 没有函数体,意味着只能被重写,也意味着不能被调用
4.动态绑定
通过基类指针或引用调用虚函数时,📌📌📌实际调用的是派生类中的函数。
这种行为发生在📌运行时,称为动态绑定或后期绑定。
int main() {
Base *ptr; //基类指针
Derived obj;
// 指向派生类对象的基类指针,为什么是基类的指针指向派生类?因为防止内存泄漏
ptr = &obj;
ptr->display(); // 调用派生类的同名重写的方法,实现多态性
return 0;
}
二、动态绑定和静态绑定完整例子
1)动态绑定
动态绑定是指在📌运行时根据对象的实际类型来确定调用哪个函数或方法,
通常与虚函数结合使用,实例代码如下(演示了什么是动态绑定):
#include <iostream>
using namespace std;
// 基类
class Shape
{
public:
// 虚函数
virtual void draw()
{
cout << "这是基类的函数" << endl;
}
};
// 派生类 Circle
class Circle : public Shape
{
public:
// 覆盖基类的虚函数
void draw() override
{
cout << "圆形派生类的函数" << endl;
}
};
// 派生类 Rectangle
class Rectangle : public Shape
{
public:
// 覆盖基类的虚函数
void draw() override
{
cout << "长方形的派生类函数" << endl;
}
};
int main()
{
Shape* shapePtr;
Circle circle;
Rectangle rectangle;
// 基类指针指向派生类对象
shapePtr = &circle;
// 调用虚函数,根据对象的实际类型确定调用哪个函数实现
shapePtr->draw(); // 输出 圆形派生类的函数
// 基类指针指向另一个派生类对象
shapePtr = &rectangle;
// 同样调用虚函数,根据对象的实际类型确定调用哪个函数实现
shapePtr->draw(); // 输出 长方形的派生类函数
return 0;
}
2)静态绑定
静态绑定是在““编译时确定””调用哪个函数或方法,通常与非虚函数或全局函数重载相关。
实例代码如下(演示了什么是静态绑定):
例子1(非虚函数):📌📌📌因为基类不是虚函数,所以不能用override,
这里重写更像是派生类的函数覆盖基类的函数
📌如果基类是虚函数就不是静态多态了。
#include <iostream>
using namespace std;
// 基类
class Shape
{
public:
// 非虚函数
void draw()
{
cout << "Drawing a shape" << endl;
}
};
// 派生类 Circle
class Circle : public Shape
{
public:
// 重写基类的函数
void draw()
{
cout << "Drawing a circle" << endl;
}
};
// 派生类 Square
class Square : public Shape
{
public:
// 重写基类的函数
void draw()
{
cout << "Drawing a square" << endl;
}
};
int main() {
Shape shape;
Circle circle;
Square square;
// 使用对象的静态类型调用函数
shape.draw(); // 输出 "Drawing a shape"
circle.draw(); // 输出 "Drawing a circle"
square.draw(); // 输出 "Drawing a square"
return 0;
}
例子2:(全局函数重载)
#include <iostream>
using namespace std;
// 函数重载
void print(int num)
{
cout << "Printing an integer: " << num << endl;
}
void print(double num)
{
cout << "Printing a double: " << num << endl;
}
int main()
{
int integer = 5;
double floating = 3.14;
// 根据参数类型的不同,调用相应的重载函数
print(integer); // 输出 "Printing an integer: 5"
print(floating); // 输出 "Printing a double: 3.14"
return 0;
}
这些语法机制提供了更灵活的代码结构,使得C++程序可以根据需要在运行时动态地适应不同的对象类型和行为。
3)重载(overload)和重写(override)的区别
① 重载(overload) 不是语法关键字,只是一个叫法
在 C++ 中,并没有专门用于声明函数重载的关键字或语法。实现函数重载时,并不需要使用特定的关键字如 "overload" 来声明函数。
重载指的是在同一个作用域内,可以定义📌多个同名函数或操作符,
但它们的参数列表必须不同(包括参数类型、数量或顺序)。
在调用重载函数时,编译器根据提供的参数类型和数量来确定调用哪个函数版本。
📌重载可以应用于成员函数、全局函数以及操作符重载。
示例代码:
#include <iostream>
using namespace std;
// 重载的函数声明
void print(int num);
void print(double num);
void print(string str);
int main()
{
int integer = 5;
double floating = 3.14;
string text = "Hello, world!";
// 调用重载的函数
print(integer); // 输出 "Printing an integer: 5"
print(floating); // 输出 "Printing a double: 3.14"
print(text); // 输出 "Printing a string: Hello, world!"
return 0;
}
// 重载的函数定义
void print(int num)
{
cout << "Printing an integer: " << num << endl;
}
void print(double num)
{
cout << "Printing a double: " << num << endl;
}
void print(string str)
{
cout << "Printing a string: " << str << endl;
}
② 重写(Override) 语法关键字
重写是指派生类重新定义(覆盖)了基类中的虚函数,使得派生类的对象在运行时调用该函数时,
执行的是派生类中的版本而不是基类中的版本。
““📌重写只适用于虚函数””,而且只有在派生类中对基类中的虚函数进行重新定义时才会发生。
#include <iostream>
// 基类
class Base
{
public:
// 虚函数
virtual void display()
{ //也可以写成存虚函数 virtual void display() = 0;
std::cout << "Base class display function" << std::endl;
}
};
// 派生类
class Derived : public Base
{
public:
// 重写基类的虚函数
void display() override
{ //派生类中写override,只能推理的基类的display是虚函数 或者纯虚函数
std::cout << "Derived class display function" << std::endl;
}
};
int main()
{
Base* basePtr;
Derived derivedObj;
// 指向派生类对象的基类指针
basePtr = &derivedObj;
// 调用虚函数,将调用派生类的实现
basePtr->display();
return 0;
}
总的来说,重载是针对相同名称的函数,而重写是针对继承关系中的虚函数。重载是静态绑定,而重写是动态绑定。
4)final(用于限制类的继承和虚函数的覆盖)
在 C++ 中,final 关键字用于限制类的继承和虚函数的覆盖。当类或虚函数声明为 final 时,它们将不能被其他类继承或者派生类重写。
① 在类声明中使用 final
class Base final
{ //该类已经非常丰富了,不需要派生类 核心类, 不需要进行继承 类的最终版本
// ...
};
在这个示例中,Base 类声明为 final,意味着不能有任何类继承自 Base 类。
字符串类: find string
② 在虚函数声明中使用 final
class Base
{
public:
virtual void func() final; //完全体函数 或 核心函数 不能对他 重写
};
class Derived : public Base
{
public:
// 错误:无法覆盖被声明为 final 的虚函数
// virtual void func();
};
在这个示例中,Base 类中的 func 虚函数声明为 final,意味着它不能被任何派生类覆盖。
使用 final 可以帮助确保代码的安全性和稳定性,因为它可以防止其他开发人员对关键类或函数进行不合适的修改或派生。