1.封装(Encapsulation)
C++封装就是将数据和操作包装成一个单独的实体(类)中,并通过控制访问权限隐藏内部实现细节。
封装就像把一个物品放进一个箱子里面,箱子外部的人只能通过箱子上的接口来与物品进行交互,而无法直接触及物品的内部细节。
1.创建一个简单的类
#include <iostream>
#include <string>
using namespace std;
class Student {
public:
string name;
int age;
string major;
void displayInfo() const {
cout << "Name: " << name << ", Age: " << age << ", Major: " << major << endl;
}
};
int main() {
Student student;
student.name = "John";
student.age = 20;
student.major = "Computer Science";
student.displayInfo(); // 输出:Name: John, Age: 20, Major: Computer Science
return 0;
}
上面代码就是将属性和行为封装到了一个类中,可以通过对类的调用进行使用。
2.C++ 类访问修饰符
访问修饰符是一种用于控制类成员(变量和函数)可访问性的关键字。在C++中,有三种访问修饰符
public:
- 公有成员可以在类的内部和外部访问。
- 可以通过对象或类的实例访问公有成员。
- 公有成员对于类的用户是可见的。
- 默认情况下,类的成员是公有的。
class MyClass {
public:
int publicVar; // 公有变量
void publicFunc() { // 公有函数
// 函数实现
}
};
int main() {
MyClass obj;
obj.publicVar = 10; // 访问公有变量
obj.publicFunc(); // 调用公有函数
return 0;
}
protected:
- 受保护的成员可以在类的内部和派生类中访问。
- 可以通过派生类的对象或指针访问受保护的成员,无法通过类的实例访问。
- 受保护的成员对于类的用户是不可见的。
class MyBaseClass {
protected:
int protectedVar; // 受保护变量
void protectedFunc() { // 受保护函数
// 函数实现
}
};
class MyDerivedClass : public MyBaseClass {
public:
void accessProtected() {
protectedVar = 10; // 访问受保护变量
protectedFunc(); // 调用受保护函数
}
};
int main() {
MyDerivedClass obj;
obj.accessProtected(); // 调用派生类的函数来访问受保护成员
return 0;
}
private:
- 私有成员只能在类的内部访问。
- 私有成员对于类的派生类和类的用户都是不可见的。
class MyClass {
private:
int privateVar; // 私有变量
void privateFunc() { // 私有函数
// 函数实现
}
public:
void accessPrivate() {
privateVar = 10; // 访问私有变量
privateFunc(); // 调用私有函数
}
};
int main() {
MyClass obj;
obj.accessPrivate(); // 调用公有函数来访问私有成员
return 0;
}
注意:访问修饰符只对类的成员起作用,不影响类的对象的访问权限。
3.继承(Inheritance):
当我们谈到继承时,可以将其比喻为家族关系。想象一下,有一个大家族,由祖父母、父母和子女组成。祖父母是这个家族的根源,他们有一些特定的特征和行为。父母是祖父母的孩子,他们继承了一些祖父母的特征,但也有一些自己的特征。子女是父母的孩子,他们继承了祖父母和父母的特征,并可以有自己独特的特征。
类的继承也是类似的概念。一个类可以继承另一个类的属性和行为,并可以添加自己的属性和行为。这个被继承的类称为父类或基类,而继承这个父类的类称为子类或派生类。
继承的好处在于代码的重用和扩展。通过继承,子类可以继承父类的特性,不需要从头开始编写相同的代码。子类可以重用父类的方法和属性,并且可以添加自己特有的方法和属性。这使得代码更加模块化和易于维护。
假设我们有一个基类叫做"车辆",它有一些通用的特征和行为,比如品牌、颜色和驾驶方法。现在,我们可以派生出不同类型的车辆,比如"轿车"和"卡车"作为子类,它们继承了"车辆"类的特征和行为,并可以具有自己特定的特征和行为。
"轿车"子类可以有特定的属性,比如座位容量和车型(如轿跑车或家庭轿车),同时还可以继承"车辆"类的共同属性,比如品牌和颜色。此外,"轿车"类可以定义自己的行为,比如加速、制动和打开车门。
继承的主要概念:
- 公有继承(public inheritance):子类继承父类的公有成员和保护成员,但不继承私有成员。继承后,父类的公有成员在子类中仍然是公有的,保护成员在子类中仍然是保护的。
- 保护继承(protected inheritance):子类继承父类的保护成员和私有成员,但不继承公有成员。继承后,父类的保护成员在子类中变为保护的,私有成员在子类中仍然是私有的。
- 私有继承(private inheritance):子类继承父类的所有成员,但都变为私有成员。在私有继承中,父类的公有和保护成员在子类中都变为私有的。
class Animal {
public:
void eat() {
cout << "Animal is eating." << endl;
}
};
class Dog : public Animal {
public:
void bark() {
cout << "Dog is barking." << endl;
}
};
int main() {
Dog dog;
dog.eat(); // 调用从Animal继承的成员函数
dog.bark(); // 调用Dog自己的成员函数
return 0;
}
4.多态(Polymorphism)
多态就是多种形态,指的是同一个方法或函数可以在不同的对象上产生不同的行为。
多态性的好处在于增强了代码的灵活性和可扩展性。通过多态,我们可以编写通用的代码,处理不同类型的对象,而无需为每个对象编写特定的代码。这提高了代码的可重用性和可维护性,并使程序更容易扩展和修改。
想象你有一个车的基类(Car),这个基类有一个共同的方法叫作"drive"(驾驶)。现在,你有两个具体的车类,一个是小轿车类(Sedan),另一个是卡车类(Truck),它们都继承自车的基类。
当你调用小轿车的"drive"方法时,它会执行小轿车的特定驾驶行为,例如加速并平稳地行驶在道路上。
而当你调用卡车的"drive"方法时,它会执行卡车的特定驾驶行为,例如启动大型引擎、慢慢启动并承载重物。
尽管你使用了相同的方法名"drive",但在不同的车对象上调用时,会产生不同的行为。这就是多态的体现,同一个方法根据对象的实际类型,产生不同的行为。
多态的实现方式主要有两种:静态多态和动态多态。
1.静态多态(Static Polymorphism):
也称为编译时多态或早期绑定。在编译时期,根据函数的参数类型或重载函数的签名,确定要调用的具体函数。这种多态是通过函数重载(function overloading)和运算符重载(operator overloading)实现的。
#include <iostream>
using namespace std;
class Shape {
public:
virtual void draw() {
cout << "绘制形状" << endl;
}
};
class Circle : public Shape {
public:
void draw() override {
cout << "绘制圆形" << endl;
}
};
class Rectangle : public Shape {
public:
void draw() override {
cout << "绘制矩形" << endl;
}
};
int main() {
Shape* shape1 = new Circle();
Shape* shape2 = new Rectangle();
shape1->draw(); // 输出:绘制圆形
shape2->draw(); // 输出:绘制矩形
delete shape1;
delete shape2;
return 0;
}
无论是调用shape1->draw()还是shape2->draw(),都会根据静态类型调用基类Shape的draw方法。这就是静态多态性的特点,方法的调用在编译时确定,不会根据对象的实际类型在运行时动态确定。
2.动态多态(Dynamic Polymorphism):
也称为运行时多态或晚期绑定。在运行时期,根据对象的实际类型,确定要调用的具体方法。这种多态是通过继承和虚函数(virtual function)实现的。在基类中声明虚函数,并在派生类中进行重写(override),从而实现多态性。
#include <iostream>
using namespace std;
class Car {
public:
virtual void drive() {
cout << "车辆正在行驶..." << endl;
}
};
class Sedan : public Car {
public:
void drive() override {
cout << "小轿车在平稳地行驶。" << endl;
}
};
class Truck : public Car {
public:
void drive() override {
cout << "卡车正在承载重物行驶。" << endl;
}
};
int main() {
Car* car1 = new Sedan();
Car* car2 = new Truck();
car1->drive(); // 输出:小轿车在平稳地行驶。
car2->drive(); // 输出:卡车正在承载重物行驶。
delete car1;
delete car2;
return 0;
}
当调用car1->drive()时,根据car1指针所指向的对象是Sedan,将调用Sedan类中的drive方法,输出"小轿车在平稳地行驶。"。
同样地,当调用car2->drive()时,根据car2指针所指向的对象是Truck,将调用Truck类中的drive方法,输出"卡车正在承载重物行驶。"。
这就是动态多态性的特点,方法的调用在运行时根据对象的实际类型确定,而不是在编译时确定。