关于C语言模拟“类”的实现——面向对象编程
功能分析:计算一个图形的面积
实现分析:
- 要计算一个图形的面积,需要有该图形特定的参数,例如矩形需要宽度和高度,圆需要半径,所以需要将这些参数和对应的图形名称一一对应,并封装起来
- 一个封闭的常见图形都可以使用初等数学知识计算其面积,这个图形可以是圆形,也可以是矩形等,可计算面积算是封闭图形的共有的属性,所以可以将这些共性继承给各图形
- 一个图形可以是圆形,可以是矩形等,要为了针对不同的图形都能计算其面积,计算面积的函数应该针对不同的图形需要有多种运行状态,简称多态
封装、继承、多态的实现
封装是指将数据和方法绑定在一起,并隐藏内部实现细节,在 C 语言中,可以通过 结构体 和 函数 来模拟实现封装。通常结构体类型的定义放置于头文件(.h文件),函数的定义放置于源文件(.c文件),函数的声明放置于头文件(.h文件)。
可计算面积算是封闭图形的共有的方法(函数),根据共同属性,定义如下类似父类的结构体类型进行封装:
Shape.h文件
//父类
typedef struct
{
double (*Area)(const void* self); // 函数指针,模拟虚函数
} Shape;
对于不同属性,分别衍生出类似子类,例如圆形和矩形:
这里使用嵌套结构体进行继承父类的属性:
Shape.h文件
//子类 1:圆形
typedef struct
{
Shape base; // 继承父类
double radius; // 半径
} Circle;
Shape.h文件
//子类 2:矩形
typedef struct
{
Shape base; // 继承父类
double width, height; // 宽度和高度
} Rectangle;
上述定义中可以看到,子类继承了父类的共有方法,即计算图形的面积。
下面为了实现多态功能(不同的图形使用不同的计算面积公式),需要在源文件中根据图形的类型,定义不同的计算面积的函数。
Shape.c文件
double Circle_Area(const void* self)
{
const Circle* obj = (const Circle*)self;
return 3.14159 * obj->radius * obj->radius; // 面积公式:πr²
}
double Rectangle_Area(const void* self)
{
const Rectangle* obj = (const Rectangle*)self;
return obj->width * obj->height; // 面积公式:width * height
}
这里可以看到,形参self使用的是const void* 类型,是一种特殊的指针类型,它用于指向任何类型的数据,可以使用这个指针获取指向的地址中的数据,但是不能通过这个指针来修改数据的内容。
下面便是在主函数main中先定义两个形状,分别是半径为10的圆形和宽是3.14、高是2.71的矩形。定义完成后,为了实现多态功能,使用父类指针指向子类对象,并只需要调用父类中的共有方法——计算面积,便可实现两种图形的面积计算。
main.c文件
#include <stdio.h>
int main()
{
Circle shape_1 = { { Circle_Area }, 10 };//定义一个圆形的图形对象并初始化参数
Rectangle shape_2 = { { Rectangle_Area }, 3.14, 2.71 };//定义一个矩形的图形对象并初始化参数
Shape* shapes[] = { (Shape*)&shape_1, (Shape*)&shape_2 }; // 多态:父类指针指向子类对象
for (int i = 0; i < 2; i++)
{
printf("Shape_%d's Area: %.2f\n", i + 1, shapes[i]->Area(shapes[i]));//只需要调用一个方法,便可完成不同形状的面积计算
}
return 0;
}
输出结果为:
Shape_1's Area: 314.16
Shape_2's Area: 8.51
程序整合
为了方便验证,下面直接给出单文件程序:
#include <stdio.h>
//父类
typedef struct
{
double (*Area)(const void* self); // 函数指针,模拟虚函数
} Shape;
//子类 1:圆形
typedef struct
{
Shape base; // 继承父类
double radius; // 半径
} Circle;
//子类 2:矩形
typedef struct
{
Shape base; // 继承父类
double width, height; // 宽度和高度
} Rectangle;
double Circle_Area(const void* self)
{
const Circle* obj = (const Circle*)self;
return 3.14159 * obj->radius * obj->radius; // 面积公式:πr²
}
double Rectangle_Area(const void* self)
{
const Rectangle* obj = (const Rectangle*)self;
return obj->width * obj->height; // 面积公式:width * height
}
int main()
{
Circle shape_1 = { { Circle_Area }, 10 };//定义一个圆形的图形对象并初始化参数
Rectangle shape_2 = { { Rectangle_Area }, 3.14, 2.71 };//定义一个矩形的图形对象并初始化参数
Shape* shapes[] = { (Shape*)&shape_1, (Shape*)&shape_2 }; // 多态:父类指针指向子类对象
for (int i = 0; i < 2; i++)
{
printf("Shape_%d's Area: %.2f\n", i + 1, shapes[i]->Area(shapes[i]));//只需要调用一个方法,便可完成不同形状的面积计算
}
return 0;
}
使用C++实现
由于C++是支持面向对象编程的,故可以直接定义类如下:
#ifndef _SHAPE_H
#define _SHAPE_H
// 父类
class Shape {
public:
virtual double Area() const = 0; // virtual表示虚函数,=0表示纯虚函数,子类必须提供实现
virtual ~Shape() = default; // 虚析构函数,确保正确释放子类对象
};
#endif // SHAPE_H
#ifndef _SHAPE_H
#define _SHAPE_H
// 父类
class Shape {
public:
virtual double Area() const = 0; // 纯虚函数,子类必须实现
virtual ~Shape() = default; // 虚析构函数,确保正确释放子类对象
};
// 子类 1:圆形
class Circle : public Shape {
private:
double radius; // 半径
public:
Circle(double r); // 构造函数
double Area() const override; // 重写父类的虚函数
};
// 子类 2:矩形
class Rectangle : public Shape {
private:
double width, height; // 宽度和高度
public:
Rectangle(double w, double h); // 构造函数
double Area() const override; // 重写父类的虚函数
};
#endif
使用Matlab实现
由于matlab是支持面向对象编程的,故可以直接定义类如下:
Shape.m文件
% 父类
classdef Shape
methods (Abstract)
Area(self)
end
end
Circle.m文件
% 子类 1:圆形
classdef Circle < Shape
properties
radius
end
methods
function obj = Circle(radius)
obj.radius = radius;
end
function area = Area(obj)
area = pi * obj.radius^2;
end
end
end
Rectangle.m文件
% 子类 2:矩形
classdef Rectangle < Shape
properties
width
height
end
methods
function obj = Rectangle(width, height)
obj.width = width;
obj.height = height;
end
function area = Area(obj)
area = obj.width * obj.height;
end
end
end
注意每个类都单独放在一个m文件,下面的命令可以在脚本文件中运行,也可以直接在命令行窗口中输入运行:
% 主程序
shape_1 = Circle(10); % 定义一个圆形的图形对象并初始化参数
shape_2 = Rectangle(3.14, 2.71); % 定义一个矩形的图形对象并初始化参数
disp(['Shape_1''s Area:' , num2str(shape_1.Area)]);
disp(['Shape_2''s Area:' , num2str(shape_2.Area)]);
使用pyhton实现
可以使用ABC抽象基类实现多态:
from abc import ABC, abstractmethod
#ABC:抽象基类(Abstract Base Class),abstractmethod是标准库abc模块中定义的一个特定装饰器
# 抽象父类
class Shape(ABC): #继承自ABC
@abstractmethod #装饰下面的方法,要求方法必须在子类中实现
def Area(self):
pass
或者使用定义抽象方法,强制子类必须实现某个方法,否则会抛出异常
# 父类
class Shape:
def area(self):
raise NotImplementedError("Subclasses must implement this method")
#NotImplementedError 是 Python 中的一个内置异常类,通常用于表示某个方法或功能尚未实现。当定义一个父类,并希望子类必须实现某个方法时,可以在父类中使用 raise
下面便是各个子类:
# 子类 1:圆形
class Circle(Shape):
def __init__(self, radius):
self.radius = radius # 半径
def Area(self):
return math.pi * self.radius ** 2 # 面积公式:πr²
# 子类 2:矩形
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width # 宽度
self.height = height # 高度
def Area(self):
return self.width * self.height # 面积公式:width * height
整合后如下:
Shape.py文件
import math
from abc import ABC, abstractmethod
class Shape:
def area(self):
raise NotImplementedError("Subclasses must implement this method")
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return math.pi * self.radius ** 2
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
shape_1 = Circle(10)
shape_2 = Rectangle(3.14, 2.71)
print(f"Shape_1's Area: {shape_1.area()}")
print(f"Shape_2's Area: {shape_2.area()}")
输出结果为:
Shape_1's Area: 314.16
Shape_2's Area: 8.51
下面给出一个通过父类的方法统一调用子类的实现,这种方式可以隐藏具体的子类细节,使得代码更加通用和简洁,但是AI提示代码会有些冗余,Shape类的 area 方法并没有实际功能,只是将调用转发给子类的方法。
import math
class Shape:
def area(self, shape):
return shape.area(self)
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self, shape):
return math.pi * self.radius ** 2 # 面积公式:πr²
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width # 宽
self.height = height # 高度
def area(self, shape):
return self.width * self.height # 面积公式:width * height
shape = Shape()
shape_1 = Circle(10) # 定义一个圆形的图形对象并初始化参数
shape_2 = Rectangle(3.14, 2.71) # 定义一个矩形的图形对象并初始化参数
print(f"Shape_1's Area: {shape.area(shape_1)}")
print(f"Shape_2's Area: {shape.area(shape_2)}")
优点如下:
- 统一接口:所有子类的
area
方法都通过父类的area
方法调用,接口统一。 - 隐藏实现细节:调用者不需要知道具体的子类类型,只需要知道父类
Shape
即可。
总结
可以发现,相比于C语言模拟类的方法而言,直接面向对象编程的语言可以直接定义类,并且实现封装、继承、多态的特性,语法更加简洁,便于理解。