C语言也有封装,继承和多态你知道吗

目录
  • 封装
  • 继承
  • 多态
    • 函数的指针
    • 通过函数的指针实现多态
  • 总结

我们知道封装、继承、多态是面向对象的三大特性,我们也知道C语言是面向过程的语言,那么可不可以在面向过程的语言中用面向对象的思想编程呢。现在我们就一起看看用C语言如何实现封装、继承、多态。

封装

所谓封装就是把实现的细节隐藏起来,外部只能通过相关的函数对一个类进行操作,一呢是方便代码的复用,二也可以有效的保证代码的安全性。那么我们看看Redis源码中对于双向链表的一个设计和实现,是不是就是传说中的封装呢?

typedef struct listNode {
	struct listNode* prev;
	struct listNode* next;
	void* value;
} listNode;
 list* listCreate() {
	struct list* list;
	list = malloc(sizeof(struct list));
	if (list == NULL) return NULL;
 	list->head = list->tail = NULL;
	list->len = 0;
	return list;
}

继承

继承也是为了代码的重用设计的,比如多个子类都有一些共同的属性和方法,那么就可以将这些共同点抽象成一个父类,让子类去继承他,子类也就拥有了父类的特性,更好的实现了代码的重用性。但是继承也有很多缺点,比如:

1.java中不能多继承

2.如果继承了一个类,那么就继承了这个类的所有public的属性和方法,即使你不想继承

3.如果有一天父类的逻辑做了修改,那么子类的逻辑也被迫做了修改

基于这些原因呢,很多时候是不建议使用继承的,而是优先用组合的方式来达到代码的复用。在Go语言中也没有extends关键字,也是通过组合实现代码的复用。那么在C语言中,虽然没有继承,但是我们可以组合啊,实现的效果是大同小异的。例如:

struct Shape {
    int area;
    Shape* crateShape();
};
 struct Rectangle {
    struct Shape shape;
    int width;
    int height;
};
 struct Square {
    struct Shape shape;
    int side;
};

多态

函数的指针

这里要回顾一下C语言基础语法了,先来看看C语言中关于函数的指针。如果我们在C语言中定义了一个函数,那么在编译的时候会把函数的源代码编译成可执行的的指令,然后分配一块内存去存放。这段空间的的地址就是函数的入口地址,每次调用的时候会从该地址入口开始执行,函数名就代表了这个地址,因此函数名就是函数的指针

那么我们就可以定义一个用来指向函数的指针变量,用来存放某一函数的入口地址。例如:int (* add) (int, int)。这行代码中第一个int表示的是返回值类型;(* add)表示add是一个指针变量,括号不能省略;后面的(int, int)表示参数类型是两个int类型,括号不能省略,括号表示指针变量不是指向其他类型的,而是函数类型的。之前对函数的调用都是通过函数名,那么现在有了指针变量,我们也可以通过指针变量来对函数进行调用。

int main() {
    // 通过函数名调用
    max(9, 2);
     // 通过函数的指针变量调用
    int (*p)(int, int);
	// 将函数max的入口地址赋值给指针变量p
    p = max();
    (*p)(a, b)
}
 int add(int x, int y) {
    return x + y;
}

通过函数的指针实现多态

我们看下面代码,ShapeVtbl这个结构体中定义了一个计算面积的函数,没有实现,我们叫做虚函数表。Shape这个结构体中定义了两个属性,一个vtbl,一个area

struct Shape {
    struct ShapeVtbl *vtbl;
    int area;
};
 struct ShapeVtbl {
    float (*area)(struct Shape* self);
};
 struct Rectangle {
    struct Shape shape;
    int width;
    int height;
};
 struct Square {
    struct Shape shape;
    int side;
};

这里我们分别定义了计算矩形面积的方法rectangle_area和计算正方形面积的方法square_area。也初始化了两个ShapeVtbl,让他们的函数指针分别指向不同的函数入口。那么在实际运行的时候代码就会根据我们的选择调用不同的函数,呈现出多态的效果。

// 计算矩形面积的方法
float rectangle_area(struct Shape *self) {
    struct Rectangle *r = (struct Rectangle *)self;
    self->area = r->width * r->height;
    return self->area;
}
 // 计算正方形面积的方法
float square_area(struct Shape *self) {
    struct Square *r = (struct Square *)self;
    self->area = r->side * r->side;
    return self->area;
}
 // 初始化了两个ShapeVtbl,让area函数分别指向了rectangle_area、square_area
struct ShapeVtbl vtbl1 = {
    rectangle_area,
};
 struct ShapeVtbl vtbl2 = {
    square_area,
};
 // 计算面积的方法,这里的area函数的逻辑是在运行时期动态绑定的,也就是有self中函数指针指向的实际函数决定的
float shape_area(struct Shape *self) {
    struct ShapeVtbl *v = self->vtbl;
    return v->area(self);
}
 struct Square* createSquare() {
    struct Square *s = malloc(sizeof(struct Square));
    s->side = 5;
    s->shape.vtbl = &vtbl2;
}
 int main() {
    struct Square* s = createSquare();
    printf("area => %f\n", shape_area((struct Shape *)s));
}

更多可以参考图例:

这样的设计在redis源码中有很多应用,比如redis中的字典,dict结构体定义了字典的基本属性以及属于dict的一些特定函数,代码在dict.h中。

/*
 * 字典类型特定函数,定义了计算哈希值、复制键、比较键等函数
 */
typedef struct dictType {
    unsigned int (*hashFunction)(const void *key);
    void *(*keyDup)(void *privdata, const void *key);
    void *(*valDup)(void *privdata, const void *obj);
    int (*keyCompare)(void *privdata, const void *key1, const void *key2);
    void (*keyDestructor)(void *privdata, void *key);
    void (*valDestructor)(void *privdata, void *obj);
 } dictType;
 /*
 * 字典
 */
typedef struct dict {
     // 类型特定函数
    dictType *type;
     void *privdata;
     dictht ht[2];
     int rehashidx;
     int iterators;
 } dict;

而在redis中,dict的应用比较多,键的类型也可能有sdsredisObject等多种类型,他们的键比较函数,hash函数等都是不同的,因此有了下面的代码,分别定义了适应于各种键的对比、hash等函数,并封装在了不同的dictType,代码在redis.c中。那么在实际应用中,只需要为不同的类型选择不同的dictType即可。

dictType clusterNodesBlackListDictType = {
    dictSdsCaseHash,            /* hash function */
    NULL,                       /* key dup */
    NULL,                       /* val dup */
    dictSdsKeyCaseCompare,      /* key compare */
    dictSdsDestructor,          /* key destructor */
    NULL                        /* val destructor */
};
 /* Migrate cache dict type. */
dictType migrateCacheDictType = {
    dictSdsHash,                /* hash function */
    NULL,                       /* key dup */
    NULL,                       /* val dup */
    dictSdsKeyCompare,          /* key compare */
    dictSdsDestructor,          /* key destructor */
    NULL                        /* val destructor */
};
 /* Replication cached script dict (server.repl_scriptcache_dict).
 * Keys are sds SHA1 strings, while values are not used at all in the current
 * implementation. */
dictType replScriptCacheDictType = {
    dictSdsCaseHash,            /* hash function */
    NULL,                       /* key dup */
    NULL,                       /* val dup */
    dictSdsKeyCaseCompare,      /* key compare */
    dictSdsDestructor,          /* key destructor */
    NULL                        /* val destructor */
};
 int dictSdsKeyCompare(void *privdata, const void *key1,
        const void *key2)
{
    int l1,l2;
    DICT_NOTUSED(privdata);
     l1 = sdslen((sds)key1);
    l2 = sdslen((sds)key2);
    if (l1 != l2) return 0;
    return memcmp(key1, key2, l1) == 0;
}

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注我们的更多内容!

(0)

相关推荐

  • C语言模拟实现C++的继承与多态示例

    一.面向过程编程与面向对象编程的区别 众所周知,C语言是一种典型的面向过程编程语言,而C++确实在它的基础上改进的一款面向对象编程语言,那么,面向过程与面向对象到底有什么样的区别呢? [从设计方法角度看] 面向过程程序设计方法采用函数(或过程)来描述对数据的操作,但又将函数与其操作的数据分离开来. 面向对象程序设计方法是将数据和对象的操作封装在一起,作为一个整体来处理. [从维护角度看] 面向过程程序设计以过程为中心,难于维护. 面向对象程序设计以数据为中心,数据相对功能而言,有较强的稳定性,因

  • C语言实现C++继承和多态的代码分享

    这个问题主要考察的是C和C++的区别,以及C++中继承和多态的概念. C和C++的区别 C语言是面向过程的语言,而C++是面向对象的过程. 什么是面向对象和面向过程? 面向过程就是分析解决问题的步骤,然后用函数把这些步骤一步一步的进行实现,在使用的时候进行一一调用就行了,注重的是对于过程的分析.面向对象则是把构成问题的事进行分成各个对象,建立对象的目的也不仅仅是完成这一个个步骤,而是描述各个问题在解决的过程中所发生的行为. 面向对象和面向过程的区别? 面向过程的设计方法采用函数来描述数据的操作,

  • C/C++使用C语言实现多态

    目录 1.多态的概念 1.1什么是多态? 1.2为什么要用多态呢? 1.3多态有什么好处? 2.多态的定义及实现 2.1继承中构成多态的条件 2.2虚函数 2.3虚函数的重写 2.4C++11 override 和 final 2.5 重载.覆盖(重写).隐藏(重定义)的对比 3.抽象类 3.1概念 3.2实现继承和接口继承 4.多态的原理 4.1虚函数表 4.2多态的原理 4.3 动态绑定与静态绑定 5.单继承和多继承关系的虚函数表 5.1 单继承中的虚函数表 5.2 多继承中的虚函数表 总结

  • C语言封装函数字符串练习汇总分享

    目录 封装函数: 判断闰年和平年 封装函数: 字符串查找(参考strstr) 封装函数: 字符串比较(参考strcmp) 封装函数: 字符串拼接(参考strcat) 封装函数: 字符串转整数(参考atoi) 封装函数: 字符串转浮点数(参考atof) 封装函数: 整数转字符串 123 “123” 封装函数: 浮点数转字符串 123.456 – “123.456” 封装函数: 判断回文字符串 封装函数: 字符串替换 封装函数: 清除二维数组的指定行 封装函数:字符串里所有大写字母互相替换 封装GP

  • 详解C语言面向对象编程中的封装

    目录 前言 一.面向对象基本概念 什么是对象? 对象与类 面向对象的编程方式 二.C语言实现面向对象 面向对象的三大特征 面向对象之封装 简介 代码实现–基础版 代码实现-进阶版 总结 前言 面向对象是一种思维方式,基本上用什么语言都是可以实现的.C语言的编程方式一般是面向过程的,但是也是可以实现面向对象的.对象是什么?什么又是面向对象?面向对象的三大特性又怎么实现,且听我细细道来. 一.面向对象基本概念 什么是对象? 此对象非彼对象,虽然有时候此对象又可以是你脑袋中的对象,那让我们从我们误解的

  • C语言模式实现C++继承和多态的实例代码

    这个问题主要考察的是C和C++的区别,以及C++中继承和多态的概念. C和C++的区别 C语言是面向过程的语言,而C++是面向对象的过程. 什么是面向对象和面向过程? 面向过程就是分析解决问题的步骤,然后用函数把这些步骤一步一步的进行实现,在使用的时候进行一一调用就行了,注重的是对于过程的分析.面向对象则是把构成问题的事进行分成各个对象,建立对象的目的也不仅仅是完成这一个个步骤,而是描述各个问题在解决的过程中所发生的行为. 面向对象和面向过程的区别? 面向过程的设计方法采用函数来描述数据的操作,

  • C语言也有封装,继承和多态你知道吗

    目录 封装 继承 多态 函数的指针 通过函数的指针实现多态 总结 我们知道封装.继承.多态是面向对象的三大特性,我们也知道C语言是面向过程的语言,那么可不可以在面向过程的语言中用面向对象的思想编程呢.现在我们就一起看看用C语言如何实现封装.继承.多态. 封装 所谓封装就是把实现的细节隐藏起来,外部只能通过相关的函数对一个类进行操作,一呢是方便代码的复用,二也可以有效的保证代码的安全性.那么我们看看Redis源码中对于双向链表的一个设计和实现,是不是就是传说中的封装呢? typedef struc

  • Java OOP三大特征之封装继承与多态详解

    目录 封装 继承 多态 OOP语言的三大特征即:面向对象的三个比较重要的思想 封装 官话:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口进行交互 通俗讲,不让类外看到实现的细节,通过技术手段对这些细节包装一个外壳,同时提供几个公开的接口,让你进行交互即可(例如:手机,内部的具体零件,不会让你观察到,使用者只能看到外壳,通过外壳的显示屏,充电口进行交互)简而言之——套壳屏蔽细节 实际上通过private来实现 例如: 继承 面向对象的思想中提出了继承的概念,专门用来进

  • Python编程-封装,继承与多态

    目录 一.类的封装 1.案例演示 2.案例分析 3.解决方案 1.封装的定义 2.封装的实现 3.案例演示 一.类的封装 为什么需要封装: Python是面向对象的编程语言,因为面向对象中所有的实体都是以对象为基本单位的,以宏观世界的实体来映射到计算机世界中,每个对象有它自己的属性和自己的行为,我们用类来将这些属性和行为封装到一起,当它实例化出来一个对象的时候,我们可以说这个对象具有这些属性性和行为. 对象的数据封装特性彻底消除了传统结构方法中数据与操作分离所带来的种种问题,提高了程序的可复用性

  • Python面向对象封装继承和多态示例讲解

    面向对象的三大特征:封装,继承,多态 1.封装: 提高程序的安全性 将数据(属性)和行为(方法)包装到类对象中,在方法内部对属性进行对象的外部调用方法. 这样无需关心内部的具体实现. 在python中没有专门的修饰符用于属性的私有,如果属性不希望被访问,前面使用两个下划线 2.继承: 提高代码的复用性 3.提高程序的可拓展性和可 维护性. 1. 封装 我们来看看私有方式的使用: # 作者:互联网老辛 # 开发时间:2021/4/4/0004 22:11 class Student: def __

  • Java面向对象编程(封装/继承/多态)实例解析

    本文主要介绍了面向对象的三大特征实例解析,下面看看具体内容. 封装 封装一个Teacher和Student类 package com.hz.test; public class Teacher { private String name; private String majorDirection; private String teachCourse; private int teachAge; public Teacher() { super(); } public Teacher(Stri

  • Python面向对象的三大特性封装、继承、多态

    Python是一门面向对象的语言.面向对象都有三大特性:封装.继承.多态. 下面分别来说说这三大特性: 1.封装 隐藏对象的属性和实现细节,仅对外提供公共访问方式.在python中用双下划线开头的方式将属性设置成私有的 . 好处: 1. 将变化隔离: 2. 便于使用: 3. 提高复用性: 4. 提高安全性. 2.继承 继承是一种创建新类的方式,在python中,新建的类可以继承一个或多个父类,父类又可称为基类或超类,新建的类称为派生类或子类. 即一个派生类继承基类的字段和方法.继承也允许把一个派

  • C# 面向对象三大特性:封装、继承、多态

    面向对象有封装.继承.多态这三个特性,面向对象编程按照现实世界的特点来管理复杂的事物,把它们抽象为对象,具有自己的状态和行为,通过对消息的反应来完成任务.这种编程方法提供了非常强大的多样性,大大增加了代码的重用机会,增加了程序开发的速度,将具备独立性特制的程序代码包装起来,修改部分程序代码时不至于会影响到程序的其他部分. 1.封装 每个对象都包含它进行操作所需要的所有信息,封装只公开代码单元的对外接口,而隐藏其具体实现,尽量不对外公开代码.使用封装有很多好处,从设计角度来讲,封装可以对外屏蔽一些

随机推荐