C语言实现面向对象编程 | 干货

news2024/9/23 19:25:32

前言

        GOF的《设计模式》一书的副标题叫做“可复用面向对象软件的基础”,从标题就能看出面向对象是设计模式基本思想。

由于C语言并不是面向对象的语言,C语言没有直接提供封装、继承、组合、多态等面向对象的功能,但C语言有struct和函数指针。我们可以用struct中的数据和函数指针,以此来模拟对象和类的行为。

所以在正式开始设计模式前,先看看如何用C语言实现面向对象编程。

本章针对面向对象的封装、继承、组合、多态给出C语言的实现方法。

封装

        封装是指对象仅暴露必要的对外接口(这里指public方法)来和其它对象进行交互,其它的属性和行为都无需暴露,这使得对象的内部实现可以自由修改。

这也要求对象包含它能进行操作所需要的所有信息,不必依赖其它对象来完成自己的操作。

以下以电力公司的例子演示封装。

电力公司生产并提供电力。为了汇聚各种发电厂的电力并让用户获得电力,电力公司提供了两个统一接口:

1、电力公司汇聚各种发电厂的电力,无论是火力发电厂、水力发电厂、原子能发电厂等都使用一个接口。如果什么时候一家火力发电厂改造成了风力发电厂,发电厂的实现完全不一样了,但接口不变,所以电力公司感觉不到发电厂变了,不需要为了发电厂实现升级而改造电力公司的系统。

2、电力公司向用户提供电力,无论用户用电的设备是烤面包机还是洗衣机,电力公司和用户之间都使用一个接口(电源插座)。用户的用电设备可以千变万化,但接口(电源插座)不变。所以电力公司不用关系用户的什么设备在用电。

代码:

#include <stdio.h>

struct PowerCompany {
    int powerReserve;
    void (*PowerPlant)(struct PowerCompany *this, int power);
    void (*PowerUser)(struct PowerCompany *this, int power);
};


void PowerPlant(struct PowerCompany *this, int power)
{ 
    this->powerReserve += power;
    printf("默认发电厂,发电%d瓦\n", power); 
    return;  
}

void PowerUser(struct PowerCompany *this, int power)
{
    if (this->powerReserve >= power) {
        printf("用电%d瓦\n", power);
        this->powerReserve -= power;
    } else {
        printf("电力不足,用电失败\n");
    }
    return;
}

/* struct PowerCompany 的构造函数 */
void PowerCompany(struct PowerCompany *this)
{
    this->powerReserve = 0;
    this->PowerPlant = PowerPlant;
    this->PowerUser = PowerUser;
    return;
}

/* struct PowerCompany 的析构函数 */
void _PowerCompany(struct PowerCompany *this)
{

}

int main(void)
{
    struct PowerCompany myPowerCompany;
    PowerCompany(&myPowerCompany);

    /* 发电 */
    myPowerCompany.PowerPlant(&myPowerCompany, 1000);

    /* 用电 */
    myPowerCompany.PowerUser(&myPowerCompany, 800);
    myPowerCompany.PowerUser(&myPowerCompany, 800);
    
    _PowerCompany(&myPowerCompany);
    return 0;
}

从电力公司的例子中可以看出,良好的封装可以有效减少耦合性,封装内部实现可以自由修改,对系统的其它部分没有影响。

继承

        面向对象编程最强大的功能之一就是代码重用,而继承就是实现代码重用的主要手段之一。继承允许一个类继承另一个类的属性和方法。

我们可以通过识别事物之间的共性,通过抽象公共属性和行为来构造父类,而通过继承父类来构造各子类。父类,即公共属性和行为,就得到了复用。

以下哺乳动物的例子演示继承。

猫和狗都是哺乳动物,它们具有公共的属性和行为。比如,猫和狗都有眼睛,且它们都会叫。

我们把眼睛的颜色、会叫抽象出来,作为哺乳动物父类的属性,让猫类、狗类都继承哺乳动物父类,可实现对”眼睛的颜色“、”会叫“实现的复用。

UML:

代码:

#include <stdio.h>

struct Mammal {
    int eyeColor;
    void (*ShowEyeColor)(struct Mammal *this);
    int callNum;
    void (*Call)(struct Mammal *this);
};

void ShowEyeColor(struct Mammal *this)
{
    if (this->eyeColor == 1) {
        printf("眼睛是绿色\n");
    } else {    
        printf("眼睛是蓝色\n");
    }
    return;
}

void Call(struct Mammal *this)
{
    printf("叫%d声\n", this->callNum);
    return;
}

// struct Mammal 的构造函数
void Mammal(struct Mammal *this, int eyeColor, int callNum)
{
    this->eyeColor = eyeColor;    
    this->ShowEyeColor = ShowEyeColor;  
    this->callNum = callNum;
    this->Call = Call;
    return;  
}

struct Dog {
    struct Mammal mammal;
};

// struct Dog 的构造函数
void Dog(struct Dog *this, int eyeColor, int callNum)
{
    Mammal(this, eyeColor, callNum);
    // 狗类的其它属性,略
    return;
}

// struct Dog 的析构函数
void  _Dog(struct Dog *this)
{

}

struct Cat {
    struct Mammal mammal;
    // 猫类的其它属性,略
};

// struct Cat 的构造函数
void Cat(struct Cat *this, int eyeColor, int callNum)
{
    Mammal(this, eyeColor, callNum);
    return;
}

// struct Cat 的析构函数
void  _Cat(struct Cat *this)
{

}

int main(void)
{
    struct Dog myDog;
    Dog(&myDog, 1, 3);
    myDog.mammal.ShowEyeColor(&myDog);
    myDog.mammal.Call(&myDog);
    _Dog(&myDog);
 
    struct Cat myCat;
    Cat(&myCat, 2, 5);
    myCat.mammal.ShowEyeColor(&myCat);
    myCat.mammal.Call(&myCat);
    _Cat(&myCat); 
    
    return 0;
}

多态

        多态与继承是紧耦合的关系,但它通常作为面向对象技术中最强大的优点之一。

子类从继承父类的接口,每个子类是单独的实体,每个子类需要对同一消息有单独的应答。

每个子类对同一消息的应答采用继承的相同接口,但每个子类可以有不同的实现,这就是多态。

在猫和狗的例子中,猫类、狗类都继承了哺乳动物父类的“叫”的方法,但猫类、狗类的叫声并不一样,所以猫类、狗类可以采用不同的“叫”的实现。

以下代码演示了多态。

代码:

#include <stdio.h>

struct Mammal {
    int eyeColor;
    void (*ShowEyeColor)(struct Mammal *this);
    int callNum;
    void (*Call)(struct Mammal *this);
};

void ShowEyeColor(struct Mammal *this)
{
    if (this->eyeColor == 1) {
        printf("眼睛是绿色\n");
    } else {    
        printf("眼睛是蓝色\n");
    }
    return;
}

void Call(struct Mammal *this)
{
    printf("叫%d声\n", this->callNum);
    return;
}

/* struct Mammal 的构造函数 */
void Mammal(struct Mammal *this, int eyeColor, int callNum)
{
    this->eyeColor = eyeColor;    
    this->ShowEyeColor = ShowEyeColor;  
    this->callNum = callNum;
    this->Call = Call;
    return;  
}

struct Dog {
    struct Mammal mammal;
};

void Bark(struct Dog *this)
{
    int i;
    for (i = 0; i < this->mammal.callNum; i++) {
        printf("汪 ");
    }
    printf("\n");
    return;
}

/* struct Dog 的构造函数 */
void Dog(struct Dog *this, int eyeColor, int callNum)
{
    Mammal(this, eyeColor, callNum);
    this->mammal.Call = Bark;
    return;
}

// struct Dog 的析构函数
void  _Dog(struct Dog *this)
{

}

struct Cat {
    struct Mammal mammal;
};

void Meow(struct Cat *this)
{
    int i;
    for (i = 0; i < this->mammal.callNum; i++) {
        printf("喵 ");
    }
    printf("\n");
    return;
}

/* struct Cat 的构造函数 */
void Cat(struct Cat *this, int eyeColor, int callNum)
{
    Mammal(this, eyeColor, callNum);
    this->mammal.Call = Meow;
    return;
}

// struct Cat 的析构函数
void  _Cat(struct Cat *this)
{

}

int main(void)
{
    struct Dog myDog;
    Dog(&myDog, 1, 3);
    
    struct Cat myCat;
    Cat(&myCat, 2, 5);

    struct Mammal *myMammal;
    myMammal = &myDog;
    myMammal->Call(myMammal);
    myMammal = &myCat;
    myMammal->Call(myMammal);

    _Dog(&myDog);
    _Cat(&myCat);

    return 0;
}

组合

        组合与继承都是面向对象中代码复用的方式,也只有通过组合和继承两种方式能够实现使用其他类构建新类。

在前面讲的继承关系中,我们把通用属性和行为抽象出来作为父类。

例如:猫、狗都是哺乳动物,它们具有哺乳动物通用的属性和行为。猫、狗与哺乳动物的关系是“is-a”,即猫、狗(is-a)哺乳动物。而组合关系体现的是“has-a”。以房子和窗户的关系举例。

我们可以单独构建窗户类,然后把窗户类应用到各种房子类上。此时房子(has-a)窗户,但绝不是窗户(is-a)房子。

以下UML和代码演示了组合。

UML:

代码

#include <stdio.h>

struct Window {
    int length;
    int width;
    void (*ShowWindow)(struct Window *this);
};

void ShowWindow(struct Window *this)
{
    printf("这是长%d厘米、宽%d厘米的窗户\n", this->length, this->width);
    return;
}

void Window(struct Window *this, int length, int width)
{
    this->length = length;
    this->width = width;
    this->ShowWindow = ShowWindow;
    return;
}

void _Window(struct Window *this)
{

}

struct House {
    struct Window *window;
    int livingRoomNum;
    int bedRoomNum;
    int bathRoomNum;
    void (*ShowHouse)(struct House *this);
};

void ShowHouse(struct House *this)
{
    printf("这是%d室%d厅%d卫的房子\n", this->bedRoomNum, this->livingRoomNum, this->bathRoomNum);
    return;
}


void House(struct House *this, int livingRoomNum, int bedRoomNum, int bathRoomNum)
{
    this->livingRoomNum = livingRoomNum;
    this->bedRoomNum = bedRoomNum;
    this->bathRoomNum = bathRoomNum;
    this->ShowHouse = ShowHouse;
    return;
}

void _House(struct House *this)
{

}

void main()
{
    struct House myHouse;
    House(&myHouse, 2, 3, 2);
    
    /* 组合是一种低耦合,如果不初始化,子类只是存放了一个空指针来占位关联。
       此处是与继承关系的区别。继承是一种强耦合,在继承关系中,无论如何子类拥有父类全部的信息。*/
    struct Window myWindow1;
    myHouse.window = &myWindow1;
    Window(myHouse.window, 100, 50);

    /* 通过获得其它对象的引用而在“运行时”动态定义 */
    myHouse.ShowHouse(&myHouse);
    myHouse.window->ShowWindow(myHouse.window);

    _Window();
    _House();

    return;
}

组合和继承的区别有以下几点:

组合关系体现的是“has-a”。继承关系体现的是“is-a”。

温馨提示

        由于微信公众号近期改变了推送规则,如果您想经常看到我们的文章,可以在每次阅读后,在页面下方点一个「赞」或「在看」,这样每次推送的文章才会第一时间出现在您的订阅列表里。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1110930.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2023年中国精准护肤发展现状及趋势分析:未来皮肤实现定制化诊断成趋势[图]

精准护肤是深度融合光电科技与精准医学在皮肤上的实践&#xff0c;以皮肤问题为导向通过研究挖掘各类皮肤问题发生发展的生理机制、环境因素&#xff0c;找寻相应的靶点并选择活性成分与光电技术&#xff0c;利用现代医疗技术实现能量/成分靶向传递&#xff0c;并通过不同人群的…

选择低代码开发,我轻松了不少!

一、传统软件开发现状 随着对定制应用程序的需求激增&#xff0c;很明显传统的开发方法无法跟上。 传统的瀑布式应用程序开发过程需要许多具有高度专业化角色的人员参与。例如&#xff0c;该过程需要&#xff1a; 业务分析师创建功能需求技术分析师将这些需求转化为技术规范创建…

mysql宋红康第一篇

mysql宋红康第一篇 索引的数据结构 为什么使用索引&#xff1f; 索引是存储引擎用于快速找到数据记录的一种数据结构&#xff0c;就好比一本教科书的目录部分&#xff0c;通过目录中找到对应文章的页码&#xff0c;便可快速定位到需要的文章。MySQL中也是一样的道理&#xf…

前端Sortable拖拽实现排序

下载地址: https://download.csdn.net/download/dongyan3595/85111182 <script type"text/javascript" src"moduleSet.js"></script> <script type"text/javascript" src"Sortable.min.js"></script> 前端…

读《Gaitset: Regarding gait as a set for cross-view gait recognition》

2019在AAAI&#xff08;还有一版叫GaitSet: Regarding Gait as a Set for Cross-View Gait Recognition&#xff0c;大体上一样&#xff09; 摘要 现有的步态识别方法要么利用步态模板&#xff0c;难以保存时间信息&#xff0c;要么利用保持不必要的顺序约束的步态序列&#x…

面试题解答:Spring Lifecycle 和 SmartLifecycle 有何区别?

当我们想在 Spring 容器启动或者关闭的时候&#xff0c;做一些初始化操作或者对象销毁操作&#xff0c;我们可以怎么做&#xff1f; 注意我这里说的是容器启动或者关闭的时候&#xff0c;不是某一个 Bean 初始化或者销毁的时候&#xff5e; 1. Lifecycle 对于上面提到的问题…

加密行业焦点:本周五,关注灰度GBTC转型是否有解?

密切关注比特币交易所交易基金&#xff08;ETF&#xff09;进展的投资者&#xff0c;正在将目光聚集到本周五。由于众多比特币现货ETF都被推迟到明年的一月中&#xff0c;市场现在最关注的就是灰度GBTC转型是否有解。 据报道&#xff0c;华盛顿特区的法院将在本周五发布一项命令…

10月19日星期四今日早报简报微语报早读

10月19日星期四&#xff0c;农历九月初五&#xff0c;早报微语早读分享。 1、浙江发现3000年前的夏商宫殿级遗址&#xff1b; 2、江苏省消保委&#xff1a;“萝卜刀”玩具广告应去除暴力等不良暗示&#xff1b; 3、广东个体工商户数量突破1000万户&#xff1b; 4、国家统计…

MT3520B 丝印AS20B 2A电流 2.3V-6V输入、1.5MHz同步降压转换器

MT3520B是一个1.5MH的恒定频率电流模式降压转换器。它非常适合需要单节离子电池提供高达2A电流的便携式设备&#xff0c;同时在峰值负载条件下仍能实现90%以上的效率。该MT3520B也可以运行在100%的低压差操作占空比&#xff0c;延长便携式系统的电池寿命&#xff0c;而轻载操作…

【高危安全通告】Oracle 10月月度安全漏洞预警

近日&#xff0c;安全狗应急响应中心关注到Oracle官方发布安全公告&#xff0c;共披露出在Oracle Weblogic中存在的6个高危漏洞。 漏洞描述 CVE-2023-22069&#xff1a;Oracle Weblogic 远程代码执行漏洞 Oracle WebLogic Server存在远程代码执行漏洞&#xff0c;该漏洞的CVS…

审批流程设计

审批流程界面的设计有多种多样。本文介绍其中一种形式。如下图所示&#xff1a; 做好审批流程需注意2个点&#xff0c;定制审批流程与审批环节介绍。定制审批流程&#xff0c;可以根据单据种类&#xff08;或其他因素&#xff09;定制不同的审批流环节&#xff0c;从而适应多种…

Kali Linux 安装搭建 hadoop 平台 详细教程

1&#xff09;前期环境准备&#xff1a;&#xff08;虚拟机、jdk、ssh&#xff09; 2&#xff09;SSH相关配置 安装SSH Server服务器&#xff1a;apt-get install openssh-server 更改默认的SSH密钥 cd /etc/ssh mkdir ssh_key_backup mv ssh_host_* ssh_key_backup 创建新…

idgen导入Android11源码

文章目录 配置下载AS编译源码依赖导入玩一下andorid.iml 注意&#xff1a; 有些时候发现为啥自己编译就这么难呢&#xff1f;不是卡死就无数次重启虚拟机&#xff0c;一切的原罪在配置过低&#xff0c;换句话说就是穷。关于导入源码的下载参考 Android Studio for Platform (AS…

day09_面向对象_多态_static

今日内容 1.作业 2.访问修饰符 3.static 4.多态 零、复习 私有化的单词: private 继承的关键词: extends 属性的封装 将属性私有,private提供一对儿set,get 继承的特性:(自己的话说明) 继承目的是减少重复代码,父类代码子类自己使用A extends B关于属性: 子类可以使用父类非私有…

软考(高级)是否需要报班,大家有什么建议?

对于报考高级专业领域还不确定。根据我的个人经验&#xff0c;我强烈建议不要犹豫&#xff0c;直接报班。时间非常紧张&#xff0c;务必抓紧学习重点&#xff0c;不要漫无目的地自学。免费的学习方式往往会付出更多的时间成本。请考虑自身经济情况。 尽管自学考试自由度高&…

vue之使用箭头函数导致表格无法刷新数据

vue之使用箭头函数导致表格无法刷新数据 1.产生背景2. 产生原因3. 解决措施 1.产生背景 在使用初始化方法查询默认表单后&#xff0c;有使用选择器进行条件查询的需求。 但在使用监听器监听选择器绑定的value值时使用了箭头函数&#xff0c;请求响应后发现数据更新了但表格为…

C语言指针用法大全

1. 指针的基础概念&#xff1a; 什么是指针&#xff1f; 如何声明和初始化指针&#xff1f; 指针和变量的关系。 2. 内存和地址&#xff1a; 计算机内存的基本概念。 如何获取变量的地址&#xff1f; 如何通过指针访问变量的地址&#xff1f; 3. 指针运算符&#xff1a; * 运算…

蓝蜂物联网水肥一体化MQTT应用案例

水肥一体化MQTT应用案例 一、客户介绍 目前我国农业用水面临资源短缺的问题&#xff0c;同时农业用水浪费现象非常严重&#xff0c;造成我们不可能通过单纯增加新水源来缓解农业用水的紧缺状况。 通过水肥一体化物联网远程精准控制技术&#xff0c;可以根据检测土壤水分、作物…

Stable Diffusion之novel Ai教程,小白必经之路

一、介绍是Stable Diffusion(简称SD) 1.SD是什么 Stable diffusion是一个基于Latent Diffusion Models&#xff08;潜在扩散模型&#xff0c;LDMs&#xff09;的文图生成&#xff08;text-to-image&#xff09;模型。简单的来说SD是可以通过提示词生成图片的应用。目前已经发…

LeetCode算法栈—验证图书取出顺序

验证图书取出顺序 目录 验证图书取出顺序 题解&#xff1a; 代码&#xff1a; 运行结果&#xff1a; 验证图书取出顺序 现在图书馆有一堆图书需要放入书架&#xff0c;并且图书馆的书架是一种特殊的数据结构&#xff0c;只能按照 一定 的顺序 放入 和 拿取 书籍。 给定一个…