面向对象的概念
面向对象(Object-Oriented)是一种软件开发的编程范式或思想方式。在面向对象编程(OOP)中,程序的设计和实现是围绕着对象的概念展开的。
对象是一个具体的实例,它具有唯一的标识、状态和行为。面向对象编程将数据和对数据的操作封装在一个对象中,通过定义类来创建这些对象。类是对象的模板或蓝图,它描述了对象具有的属性和方法。
面向对象编程的三个主要原则是封装(Encapsulation)、继承(Inheritance)和多态(Polymorphism)。
- 封装:封装将数据和基于数据的操作封装在一个对象中,隐藏了对象的内部细节,只暴露必要的接口供外部使用。这提高了代码的可维护性和安全性。
- 继承:继承允许一个类(子类)继承另一个类(父类)的属性和方法,使子类具有父类的特性。通过继承,可以实现代码的重用和层次化的组织。
- 多态:多态允许使用相同的接口来处理不同类型的对象,具有不同的实现。这使得可以通过统一的方式调用不同类的方法,增加了代码的灵活性和可扩展性。
面向对象编程的优点包括代码的模块化、可重用性、可维护性、灵活性和易于理解。它提供了一种更直观、自然的方法来组织和处理复杂的软件系统。许多编程语言,如Java、C++和Python,都支持面向对象编程。
C++ 中的对象
在C++中,对象是类的实例化。类是一种自定义的数据类型,描述了对象具有的属性和行为。通过创建类的对象,可以在内存中分配对应的存储空间,并使用这些对象来访问类中定义的成员。
在C++中,对象由类定义的一组属性和方法构成。属性(成员变量)表示对象的状态或数据,而方法(成员函数)表示对象的行为或操作。通过对象可以访问和操作这些属性和方法,以实现所需的功能。
以下是在C++中创建对象的基本步骤:
- 定义类:使用类关键字和类名定义一个类。在类中,可以声明和定义需要的成员变量和成员函数。
- 创建对象:使用类名和变量名声明一个对象。声明时会自动调用类的构造函数来初始化对象。
- 访问成员:通过使用对象名和成员运算符(点号 .)来访问对象的属性和方法。可以使用对象名访问成员变量并修改其值,也可以调用对象的成员函数执行相应的操作。
- 销毁对象:在不再需要对象时,可以使用 delete 关键字来销毁对象,并释放相关的内存空间。销毁对象时会自动调用类的析构函数。
这是一个简单的C++代码示例,演示了定义类、创建对象、访问成员属性和成员方法以及销毁对象的过程:
#include <iostream>
using namespace std;
// 定义一个类
class MyClass {
public:
// 成员变量
int myNumber;
// 构造函数
MyClass() {
cout << "对象已创建" << endl;
}
// 成员函数
void setNumber(int number) {
myNumber = number;
}
void printNumber() {
cout << "My number is: " << myNumber << endl;
}
// 析构函数 // 析构函数中包含了删除对象的方法,调用析构函数就是删除对象。
~MyClass() {
cout << "对象已销毁" << endl;
}
};
int main() {
// 创建对象
MyClass obj;
// 访问成员属性
obj.myNumber = 42;
// 访问成员方法
obj.printNumber();
// 设置成员属性并打印
obj.setNumber(88);
obj.printNumber();
return 0;
}
在上面的示例中,我们首先定义了一个名为MyClass
的类,该类具有一个整数类型的成员变量myNumber
和三个成员函数:一个构造函数用于对象的初始化,一个setNumber()
函数用于设置成员属性的值,一个printNumber()
函数用于打印成员属性的值。同时,我们还定义了一个析构函数,在对象被销毁时自动调用。
在主函数中,我们先创建一个MyClass
类的对象obj
,在对象创建时会调用构造函数。然后,通过对象名和成员运算符访问成员变量myNumber
并进行赋值。之后,我们调用成员方法printNumber()
来打印该数字。
接下来,我们使用成员方法setNumber()
设置成员属性的值为88,并再次调用printNumber()
打印更新后的值。
最后,在程序结束时,对象obj
会被自动销毁,析构函数会被调用。
C++ 面向对象:封装
下面是一个用C++演示封装的代码示例:
#include <iostream>
#include <string>
using namespace std;
// 定义一个类
class Person {
private:
string name;
int age;
public:
// 设置姓名
void setName(string personName) {
name = personName;
}
// 获取姓名
string getName() {
return name;
}
// 设置年龄
void setAge(int personAge) {
if (personAge >= 0) {
age = personAge;
}
}
// 获取年龄
int getAge() {
return age;
}
};
int main() {
// 创建对象
Person person;
// 使用公共接口设置和获取私有属性
person.setName("Alice");
person.setAge(25);
// 打印私有属性的值
cout << "Name: " << person.getName() << endl;
cout << "Age: " << person.getAge() << endl;
return 0;
}
在上述代码中,定义了一个Person
类,并使用private
访问修饰符将成员变量name
和age
声明为私有。这意味着只有类内部可以直接访问这些成员。
然后,在公共区域(public
)定义了用于设置和获取这些私有成员的公共成员函数。通过调用setName()
和setAge()
方法,可以设置私有成员变量的值。而使用getName()
和getAge()
方法,可以获取私有成员的值】】
在main()
函数中,创建一个Person
对象,并使用公共接口设置和获取私有属性。通过这种方式,实现了封装的概念,隐藏了类内部的实现细节,使外部代码只能通过公共接口来访问和操作私有属性。
最终输出结果为:
Name: Alice
Age: 25
C++ 面向对象:继承
下面是一个用C++演示继承的代码示例:
#include <iostream>
#include <string>
using namespace std;
// 基类
class Animal {
protected:
string name;
public:
// 构造函数
Animal(string animalName) : name(animalName) {}
// 成员函数
void eat() {
cout << name << " is eating." << endl;
}
};
// 派生类
class Cat : public Animal {
private:
string color;
public:
// 构造函数
Cat(string catName, string catColor) : Animal(catName), color(catColor) {}
// 成员函数
void meow() {
cout << name << " is meowing." << endl;
}
void displayColor() {
cout << "The color of " << name << " is " << color << "." << endl;
}
};
int main() {
// 创建派生类对象
Cat cat("Tom", "white");
// 调用基类成员函数
cat.eat();
// 调用派生类成员函数
cat.meow();
cat.displayColor();
return 0;
}
在上述代码中,定义了一个基类Animal
和一个派生类Cat
。基类有一个受保护的成员变量name
,并且包含了一个构造函数和一个eat()
成员函数。
派生类Cat
继承自基类Animal
,并添加了一个私有成员变量color
,还定义了两个成员函数meow()
和displayColor()
。
在main()
函数中,创建一个Cat
对象,并分别调用基类的成员函数和派生类的成员函数。由于继承关系,派生类可以直接使用基类的成员函数。此外,派生类还可以拥有自己新增的成员变量和成员函数。
最终的输出结果为:
Tom is eating.
Tom is meowing.
The color of Tom is white.
C++ 面向对象:多态
以下是一个使用C++演示多态的代码示例:
#include <iostream>
#include <string>
using namespace std;
// 基类
class Shape {
protected:
string name;
public:
// 构造函数
Shape(string shapeName) : name(shapeName) {}
// 纯虚函数
virtual void draw() = 0;
};
// 圆形派生类
class Circle : public Shape {
private:
double radius;
public:
// 构造函数
Circle(string circleName, double circleRadius) : Shape(circleName), radius(circleRadius) {}
// 实现纯虚函数
void draw() override {
cout << "Drawing a circle: " << name << ", radius: " << radius << endl;
}
};
// 三角形派生类
class Triangle : public Shape {
private:
double base;
double height;
public:
// 构造函数
Triangle(string triangleName, double triangleBase, double triangleHeight) : Shape(triangleName), base(triangleBase), height(triangleHeight) {}
// 实现纯虚函数
void draw() override {
cout << "Drawing a triangle: " << name << ", base: " << base << ", height: " << height << endl;
}
};
int main() {
// 创建基类指针数组
Shape* shapes[2];
// 创建圆形对象和三角形对象
Circle circle("Circle", 5.0);
Triangle triangle("Triangle", 4.0, 3.0);
// 将对象指针赋给数组中的元素
shapes[0] = &circle;
shapes[1] = ▵
// 循环调用虚函数
for (int i = 0; i < 2; i++) {
shapes[i]->draw();
}
return 0;
}
在上面的代码示例中,我们定义了一个基类Shape
,它包含一个纯虚函数draw()
。然后我们派生出两个具体的形状类:Circle
和Triangle
,并分别实现了draw()
函数。
在main()
函数中,我们首先创建了一个基类指针数组shapes
,然后通过创建不同的派生类对象给数组元素赋值。
接下来,我们使用循环遍历数组,并通过基类指针调用虚函数draw()
。由于虚函数是动态绑定的,它会根据对象的真实类型调用相应的成员函数,从而实现多态。
运行代码,输出结果为:
Drawing a circle: Circle, radius: 5
Drawing a triangle: Triangle, base: 4, height: 3
可以看到,虽然使用的是基类的指针数组,但通过调用虚函数draw()
时,会根据对象的类型来执行相应的成员函数,实现了多态的效果。
对象的属性是对象
在面向对象的程序设计中,有时候会将一个对象的属性设计为另一个对象,这种情况通常发生在以下两种情况下:
- 对象之间存在"has-a"关系:当一个对象在概念上包含或组成另一个对象时,适合将该对象的属性设计为另一个对象。例如,在汽车和轮胎之间存在"has-a"关系,因为汽车包含四个轮胎;在学生和课程之间存在"has-a"关系,因为学生选修多门课程。
class Tire {
// 轮胎类的定义
};
class Car {
private:
Tire tire; // 将轮胎作为汽车的属性
public:
// 汽车类的其他成员函数和成员变量
};
class Course {
// 课程类的定义
};
class Student {
private:
Course courses[5]; // 将课程数组作为学生的属性
public:
// 学生类的其他成员函数和成员变量
};
- 需要在不同对象之间共享数据:当多个对象需要共享一组数据时,适合将这组数据封装为一个对象,并将其作为其他对象的属性。这样可以确保对象之间共享的数据一致性。
class Location {
private:
double longitude;
double latitude;
public:
// Location类的其他成员函数和构造函数
};
class Person {
private:
string name;
Location location; // 将位置信息作为人的属性
public:
// Person类的其他成员函数和构造函数
};
通过将对象作为另一个对象的属性,可以提高代码的可读性和可维护性。同时,这也符合面向对象的封装和组合原则,使得程序更加模块化和灵活。
C++ 对象序列化和反序列化
对象序列化和反序列化是将对象转换为字节流(序列化)或将字节流转换回对象(反序列化)的过程。在C++中,可以使用标准库提供的序列化和反序列化功能来实现。
对象序列化
要将对象序列化为字节流,可以使用std::ofstream
来将对象数据写入文件。需要注意的是,被写入文件的对象的类必须实现序列化操作符<<
。
以下是一个示例:
#include <iostream>
#include <fstream>
class Foo {
private:
int data;
public:
// 序列化操作符
friend std::ostream& operator<<(std::ostream& os, const Foo& obj) {
return os.write(reinterpret_cast<const char*>(&obj.data), sizeof(obj.data));
}
// 构造函数
Foo(int d) : data(d) {}
};
int main() {
Foo foo(42);
std::ofstream file("data.bin", std::ios::binary);
if (file) {
file << foo; // 序列化对象到文件
file.close();
}
return 0;
}
在上述示例中,定义了一个类Foo
,其中包含一个data
成员变量。通过重载<<
操作符,将data
写入指定的输出流。在main()
函数中,创建了一个Foo
对象,并将其序列化到名为"data.bin"的二进制文件中。
对象反序列化
要从字节流中反序列化对象,可以使用std::ifstream
从文件中读取数据。被读取的对象的类必须实现反序列化操作符>>
。
以下是一个示例:
#include <iostream>
#include <fstream>
class Foo {
private:
int data;
public:
// 反序列化操作符
friend std::istream& operator>>(std::istream& is, Foo& obj) {
return is.read(reinterpret_cast<char*>(&obj.data), sizeof(obj.data));
}
// 构造函数
Foo() = default;
// 打印数据
void printData() {
std::cout << "Data: " << data << std::endl;
}
};
int main() {
Foo foo;
std::ifstream file("data.bin", std::ios::binary);
if (file) {
file >> foo; // 从文件反序列化对象
file.close();
}
foo.printData(); // 打印反序列化后的数据
return 0;
}
在上述示例中,定义了一个与序列化示例相同的类Foo
。通过重载>>
操作符,将数据从输入流中读取到data
成员变量中。在main()
函数中,创建了一个Foo
对象,并从名为"data.bin"的二进制文件中反序列化它。最后,调用printData()
函数打印反序列化后的数据。
注意 ,在序列化和反序列化过程中,使用的输入流和输出流都应以二进制模式打开。这可以通过将std::ios::binary
选项传递给std::ofstream
和std::ifstream
的构造函数来实现。
关注我,不迷路,共学习,同进步
关注我,不迷路,共学习,同进步