C语言实现多态机制

今天,看到一篇面经,问的是如何通过C语言实现多态。特意记录一下答案。

题外话

面向对象的三大特征

封装
对于封装来说就是数据与具体操作实现的代码都放在某个对象的内部,使这些代码的具体细节不被外界发现,只留下一些接口供外部来使用,而不能一任何的形式来对象内部的实现。使用封装能够隐藏具体的实现的细节,使代码更加易于维护并且保证了系统的安全性。
继承
继承机制是面向对象程序设计使代码进行复用的最重要的手段,他允许程序员在保证类原有类特性的基础上进行扩展来增加功能。这样新产生的类就被称为派生类,继承就可以表现面向对象机制的的层次结构。
多态
多态简单点说就是“一个接口,多种实现”,就是同一种事物表现出的多种形态。多态在面向对象语言中是指:接口多种的不同实现方式。也就是复用相同接口,实现不同操作。

总的来说:封装可以隐藏实现细节包括包含私有成员,使得代码模块增加安全指数;继承可以扩展已存在的模块,为了增加代码的复用性;多态则是为了保证类在继承和派生的时候,类的实例被正确调用,实现了接口的重用

静态多态&动态多态

C++多态支持两种多态性,编译时多态和运行时多态。编译时多态是通过重载函数来实现的,运行时多态是通过虚函数来实现的。

静态多态:编译器在编译期间完成,编译器根据函数实参的类型(可能会隐式类型转换),可推断出要调用哪个函数,如果有对应的函数就调用相应的函数,否则就报一个编译错误。

动态多态:在函数执行期间(非编译期)判断所引用对象的实际类型,根据实际类型的调用相应的方法。使用virtual关键字修饰类的成员函数,指明该函数为虚函数,派生类需要重新实现,编译器将实现动态绑定。

动态多态实现的条件:

  1. 使用场景:父类的指针或者引用指向父类或者子类的对象(由赋值兼容规则决定);
  2. 实现多态的两个条件:虚函数的重写;父类的指针或者引用调用重写的虚函数。
  3. 若父类中的成员函数加上virtual关键字,则子类中重写的该函数默认virtual,可以不指定,但是一般加上;
    重写:子类重写父类的虚函数,要求函数名称,函数参数,返回值完全一样(协变除外);

C++中的多态

我们知道的是在C++中会维护一张虚函数表,根据赋值兼容规则,我们知道父类的指针或者引用是可以指向子类对象的。如果一个父类的指针或者引用调用父类的虚函数则该父类的指针会在自己的虚函数表中查找自己的函数地址,如果该父类对象的指针或者引用指向的是子类的对象,而且该子类已经重写了父类的虚函数,则该指针会调用子类的已经重写的虚函数。

//c++中的多态
#include <iostream>
using namespace std;
class A
{
public:
    virtual void fun()//虚函数实现
    {
        cout << "Base A::fun() " << endl;
    }
};

class B:public A
{
public:
    virtual void fun()//虚函数实现,子类中virtual关键字可以没有
    {
        cout << "Derived B::fun() " << endl;
    }
};

void Test1()
{
    A a;//基类对象
    B b;//派生类对象

    A* pa = &a;//父类指针指向父类对象
    pa->fun();//调用父类的函数

    pa = &b; //父类指针指向子类对象,多态实现
    pa->fun();//调用派生类同名函数
}
输出:
Base A::fun()
Derived B::fun()

C语言实现多态

C语言中是没有class类这个概念的,但是有struct结构体,我们可以考虑使用struct来模拟;但是在C语言的结构体内部是没有成员函数的,如果实现这个父结构体和子结构体共有的函数呢?我们可以考虑使用函数指针来模拟。但是这样处理存在一个缺陷就是:父子各自的函数指针之间指向的不是类似C++中维护的虚函数表而是一块物理内存,如果模拟的函数过多的话就会不容易维护了。

//C实现动态,用到函数指针

typedef void(*FUN)();//重定义一个函数指针类型

//父类
struct Base 
{
    FUN _f;
};

//子类
struct Derived
{
    Base _b;//在子类中定义一个基类的对象即可实现对父类的继承
};


void FunB()
{
    printf("%s\n", "Base::fun()");
}
void FunD()
{
    printf("%s\n", "Derived::fun()");
}

void Test2()
{
    Base b;//父类对象
    Derived d;//子类对象

    b._f = FunB;//父类对象调用父类同名函数
    d._b._f = FunD;//子类调用子类的同名函数

    Base *pb = &b;//父类指针指向父类对象
    pb->_f();

    pb = (Base *)&d;//让父类指针指向子类的对象,由于类型不匹配所以要进行强转
    pb->_f();

}

结构体的强制类型转换

我们看到C语言通过强制类型转换完成多态的实现。

pb = (Base *)&d;//让父类指针指向子类的对象,由于类型不匹配所以要进行强转

因为Derived中只有一个Base类,在强制转换的过程中会把结构体d中的变量赋值给pb。如果说两个结构体中的前n个变量无法一一对应,则会出现段错误

例如,定义Derived为

struct Derived
{
    int b;
    Base _b;//在子类中定义一个基类的对象即可实现对父类的继承
};

则输出

Base::fun()
Segmentation fault (core dumped)

但如果定义为

struct Derived
{
    Base _b;//在子类中定义一个基类的对象即可实现对父类的继承
    int b;
};

则正常输出。

参考:
C语言模式实现C++继承和多态.
纯C语言实现简单封装继承机制 .

  • 5
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
学习C语言的好书. OOPC是指OOP(Object-Oriented Programming)与C语言的结合,它是一个面向对象C语言编程框架。它是一套C语言的宏,定义了OOP概念的关键字,借助于这一套宏,实现面向对象的特性,如类、对象、继承、接口、多态、消息等。   C++对于大型软件架构的良好可控性,和对以后程序员维护代码时良好的可读性;然而就目前来说,在嵌入式领域广泛的使用C++显然是不现实的事情。一般的嵌入式系统开发中只用到了其中的一小部分功能,而不需要全部的机制,比如多重继承、运算符重载等。因此,许多嵌入式系统的开发者就舍弃了C++的庞大身躯而回归到精简的C环境中。 一般情况下,一个更容易扩展、维护的软件通常采用的是OOP的思想,添加一个原本不存在的相对无关单独的个体,总比在一个已经存在的过程内硬塞进去一个对象要简单;而且面向过程更容易导致混乱的维护。然而舍弃C++的同时也舍弃了珍贵的OOP能力,实在太可惜了。 C语言良好的可移植性,对内存等良好的操作性以及执行之速度均是一般嵌入式产品的不二首选。我们要应此放弃C++吗?当然不,幸好已经有很多优秀的设计师为我们指明了C语言OOP化的道路。 虽然OOPC语法不如C++那么简洁,但是OOPC也有亮丽的特色,就是编译后的程序所占的内存空间比C++小的多,执行效率高,适用于Embedded System。
多态是面向对象编程中的一个概念,它允许不同的对象对同一消息做出不同的响应。简单来说,就是同一种操作作用于不同的对象上面,可以产生不同的执行结果。在C语言中,由于其不是面向对象编程语言,没有内置的多态机制,但是可以通过函数指针实现类似多态的效果。 举个例子,假设有一个图形类,包括圆形和矩形两种子类,它们都有计算面积的方法。我们可以定义一个函数指针,指向这个方法,然后让不同的对象调用这个函数指针,就可以实现对不同对象的多态操作。 ```c #include <stdio.h> // 定义图形类 typedef struct { int type; // 1表示圆形,2表示矩形 union { struct { int r; // 圆半径 } circle; struct { int w; // 矩形宽度 int h; // 矩形高度 } rectangle; } data; } Shape; // 计算圆形面积的函数 int calcCircleArea(Shape* shape) { int r = shape->data.circle.r; return 3.14 * r * r; } // 计算矩形面积的函数 int calcRectangleArea(Shape* shape) { int w = shape->data.rectangle.w; int h = shape->data.rectangle.h; return w * h; } int main() { Shape s1 = {1, {{5}}}; // 圆形,半径为5 Shape s2 = {2, {{3, 4}}}; // 矩形,宽度为3,高度为4 int (*calcArea)(Shape*) = NULL; // 定义函数指针 calcArea = calcCircleArea; // 指向计算圆形面积的函数 printf("圆形面积为:%d\n", calcArea(&s1)); // 调用函数指针,输出圆形面积 calcArea = calcRectangleArea; // 指向计算矩形面积的函数 printf("矩形面积为:%d\n", calcArea(&s2)); // 调用函数指针,输出矩形面积 return 0; } ``` 上面的代码中,我们通过定义一个函数指针`calcArea`来实现不同对象的多态操作,它可以指向不同的计算面积的方法。当它指向`calcCircleArea`时,调用`s1`的`calcArea`方法就相当于调用了`calcCircleArea`方法,计算出圆形的面积;当它指向`calcRectangleArea`时,调用`s2`的`calcArea`方法就相当于调用了`calcRectangleArea`方法,计算出矩形的面积。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值