时有的时候听到旁人说面向对象的主次设计,以前也会有上过面向对象程序设计那门课。不过不幸的是,那一个皆以以C++,以至VC++为根基的。而更为不幸的是,多年的话作者平素是二个C的使用者。在这个学院的时候,笔者根本做的是硬件上的驱动层,和尾巴部分效能层。


在办事以后,又做的是手提式有线电话机上的软件开采,全体那一个都以和C离不开的。即使本身必须要说,C++是一门很好的言语,可是它的编写翻译速度,代码效用,编写翻译后的代码大小都限定了它在嵌入式上的行使。(尽管现行反革命的嵌入式CPU越来越快,内部存款和储蓄器体积变大,笔者觉着用C++也应有未有何难题。那使本人觉得就好像是嵌入式编写翻译器的限量。尽管Philip和TI好像都有C++的编写翻译器,可是好似没人用那么些。难道是太贵了?但不管怎么说,嵌入式应用中,C语言的广泛利用是明显的卡塔 尔(阿拉伯语:قطر‎

layout: “post”
title: “30/70 C语言回调函数”
date: “2017-03-29 21:39”

那正是说在面向进度的时期发生的C语言能不可能接受面向对象的思量吗?作者感到是早晚能够的,C++不过是在语言品级上走入了对指标的支撑,同有时候提供了丰盛的靶子库。而在C语言下,大家只可以通宵达旦了。


风流倜傥、 面向对象理念的目标是框架化,花招是空虚

运用 C
语言编制程序时,经常能够将函数调用扩大风华正茂层封装,那样做可以让函数的行使更为灵敏多变。

深信广大人都精通面向对象讲了什么:类,抽象类,世袭,多态。不过是什么样来头促使那一个概念的爆发呢?

宏定义封装

#define MacroFunction() Afunction(a, b, c) 将Afunction(a, b,
c)的常用用法作为一个完璧归赵举办包装,这种封装方式,在后续使用时,借使急需更正值的话,能够直接校勘#define MacroFunction() Bfunction(a, b, c)
而无需再举行任何操作。但还要也设不通常,倘使大家要求将八分之四的
Afunction(a, b, c) 改为 Bfunction(a, b, c)
就相比较麻烦,若是使用这种办法的话就只可以贰个个翻看替换了。境遇这种情状时,下后生可畏种艺术是更加好的采纳。

打个例如说:你去买显示屏,但是显示屏的品牌样式是五花八门的,你在买的长河中爆发的业务也是不行预测的。对于如此的事务,大家在程序语言中如何去描述呢。面向对象的观念便是为着消除那样的难题。编写一个顺序(以致正是五个工程卡塔 尔(阿拉伯语:قطر‎,从无到用是艰辛的,从有到丰硕是越来越困难的。面向对象将前后相继的逐少年老成行为化为目的,而又用抽象的措施将那个指标分类(抽象卡塔尔,进而将复杂的事体简化为多少个基本点的有机结合(框架化卡塔尔。

静态入口函数

静态的入口函数,有限支撑函数名雷同,利用标记位调用子函数。那样的出色应用比超级多,例如说网卡驱动里面有二个入口函数Nilan(int
FunctionCode,Para*卡塔尔国。具体的参数是怎么记不理解了。可是NiLan的重头戏是那般的:

Long Nila(int FunctionCode,Para*)
{
   Switch(FunctionCode)
   {
       Case SendPacket: send(….)
       Case ReceivePacket: receive(…)
       ...
   }
}

写到这里大家精晓哪些看头了吗。保险同意气风发的函数名正是说:网卡驱动是和pNA+左券栈互连的,那么哪些保险pNA+合同栈和见仁见智的驱动都非常呢,贰个简易的法子正是仅仅使用四个入口函数。

别的正是是一些初读书人在写程序时,也会影响地动用这种方法,如:

int Fun(int flag)
{
    if (flag == 0)
    {
        Afunction();
    }
    else if (flag > 0)
    {
        Bfunction();
    }
    else
    {
        Cfunction();
    }
}

透过更正假若函数的参数值,来调用内部的相继函数。那样的做法是能够升高的:如若之后想调用新的函数,扩张对应的函数参数值就好了。

层与层之间的互连接口是极小的(这里是三个入口函数卡塔尔,平日是应用名字分析的方法并非切实的函数调用(利用FunctionCode调用函数,Nilan仅仅完毕名字深入解析的效率卡塔 尔(阿拉伯语:قطر‎。

实际上大家的身边超多东西都以如此组合的:比方说Computer:Computer是由主板,CPU加上各样卡整合的。那便是贰个框架化。而忽视分歧的CPU,分裂的主板,分化的声卡,网卡,显卡的差距,这正是架空。再举例未来的教育网:是由主大旨节点:北大,南开,北京邮政和邮电通讯大学等多少个,然后是逐条子节点,依次组成了全体教育网网络。

回调(CALLBACK)函数

所以自身认为面向对象的编制程序观念正是:叁个巨型工程是分档期的顺序结构的,每层又由抽象的协会连接为全部(框架化卡塔 尔(阿拉伯语:قطر‎,各种抽象结构之间是相互独立的,能够独自发展(世袭,多态卡塔尔。档期的顺序之间,结构之间各有联合的通讯格局(日常是消息,事件机制卡塔尔国。

简述

回调函数才是本文的关键,它比地点的二种办法非常灵敏,定制性越来越强。它使得四个函数能够在不重复编译的景况下促功能益的丰富。日常选拔回调函数大概存在有以下两点难题:

  • 频率裁减:回调函数使用了函数指针,平时会先拜望函数指针找到函数存放的实际上地址,然后才是真正的函数调用,同直接调用函数比,多了一个寻址函数存款和储蓄地点的进度(普通函数在编写翻译时已经转变到了地点常亮,而函数指正依旧作为四个变量使用卡塔尔。质量难题今后早已不做过多的构思,指正调用本身就超快,且以后机械品质都过剩。
  • 会促成程序的“支离破碎”:在前后相继中,你读到一个函数指针的时候,并不知道那几个函数指针指向的是哪个函数,调用食会让程序碎片化严重。那一点笔者权且还不是太明了。

回调函数正是七个通过函数指针调用的函数。就算您把函数的指针(地址卡塔尔国作为参数字传送递给另二个函数,当以此指针被用为调用它所指向的函数时,大家就说这是回调函数。回调函数不是由该函数的兑现方一贯调用,而是在特定的平地风波或规范发出时由此外的一方调用的,用于对该事件或规范举办响应。

应用回调函数能够把调用者与被调用者分开。调用者不爱护谁是被调用者,只是存在一个装有某种特定原型、有些限定规范(如再次回到值为int卡塔 尔(阿拉伯语:قطر‎的被调用函数。

二、从前C语言编制程序中常用的“面向对象”方法

有关函数指针

回调函数并不归属 C 语言最基本的用法,应该算是 C
语言使用的二个技艺,能够让 C 操作起来越来越灵活,但灵活的代价便是您要对 C
语言比价熟习,函数指针,指针函数等能够区分解除。

函数指针是指向函数的指针变量,即本质是三个指针变量。指向函数的指针包涵了函数的地点,能够因此它来调用函数。注解格式如下:类型说明符
(*函数名)(参数)。

只顾:函数名自身就是意味着指针/地址,因而并没有必要再去取地址,使用 &/*
是未有太大的含义的。

void (*fptr)();
void Function(void)
{
    ...
}

/* 以下两句等价 */
fptr=&Function;
fptr=Function;

/* 以下两句等价 */
x=(*fptr)();
x=fptr();

虽然
fptr=&Function; 和 x=(*fptr)();写起来都进一层复杂,不过有些技术员趋向于选拔这种格式,因为它分明提议是经过指针而非函数名来调用函数的,那边小编也推荐应用这种写法。

事实上C语言诞生以来,大家就想了累累主意来浮现“面向对象”的考虑。上面就来讲说自个儿所知道的法子。

符合规律步骤

日常编写那意气风发类别回调及主调函数时,能够分成以下多少个步骤:

  • 概念指向回调函数的项目,使用
    typedef,如:typedef int (*typefun)(int); 表示 typefun
    类型指向了相仿于 int Test(int num) 那样的二个函数。
  • 调用回调函数的函数,有个形参类型为
    typefun,代表我们选用那几个函数时,会将回调函数作为参数字传送递给那些函数。
  • 发端调用,将回调函数的地址(即函数名卡塔尔作为实参直接传送给调用回调函数的那些主调函数。

当真深入分析,大家可以发掘,回调函数的应用便是传递了函数的地点,与传递普通变量之处并不曾精气神的界别。

1.宏定义:

案例具体解析

下边举个回调函数的简要实例:

#include <stdlib.h>
#include <stdio.h>

typedef int (*typefun)(int);

int Test(int num)
{
    int i;
    for (i=0; i<num; i++)
    {
        printf("The %d th charactor is: %c/n", i, (char)('a' + i%26));
    }
    return 0;
}

void Caller(int n, typefun ptr)
{
    // 也能写成这样: void Caller(int n, int (*ptr)(int)) ,typedef 定义更加方便使用
    (*ptr)(n);
    return;
}

int main()
{
    Caller(30, Test); //相当于调用Test2(30);
    return 0;
}

如上的例子过于轻巧,并不曾多大的实际意义,但我们经过如此的事例能够看来回调函数和方面叙述的静态入口函数都以在原先的函数方面多加了意气风发层封装,为了让函数调用起来更为灵敏多变。

更进一层,利用回调函数,C
语言能够完结广大技艺性的操作,上面扩大二个事例,用回调函数来贯彻的叁个简短泛型程序:

/* 以下的泛型只是举例,如果需要比较浮点型,字符型则需要补充完善回调函数 */

#include <stdio.h>

typedef int (*cmp)(void *, void *);

typedef struct
{
    char *name;
    float score;
} stru_member, *pstru_member;

void *Max(void *p[], int len, cmp fun)
{
    void *res = p[0];
    for (int i = 0; i < len; i++)
    {
        if ((*fun)(p[i], res))      // 这边等价于 if (fun(p[i], res))
        {
            res = p[i];
        }
    }
    return res;
}

int comp(int *a, int *b)
{
    if (*a > *b)
        return 1;
    else
        return 0;
}

int mcomp(pstru_member a, pstru_member b)
{
    if (a->score > b->score)
        return 1;
    else
        return 0;
}

int main(void)
{
    int *pres;
    int data[5] = {1, 12, 30, 4, 5};
    int *pdata[5] = {&data[0], &data[1], &data[2], &data[3], &data[4]};
    pres = Max((void **)pdata, 5, (cmp)&comp);      // 这边等价于 pres = Max((void **)pdata, 5, (cmp)comp);  
    printf("%dn", *pres);

    stru_member mem[3] = {{"no.1", 99.6}, {"no.2", 91.4}, {"no.3", 95.5}};
    pstru_member pmem[3] = {&mem[0], &mem[1], &mem[2]};
    pstru_member pres2 = Max((void **)pmem, 3, (cmp)&mcomp);
    printf("%s:%fn", pres2->name, pres2->score);
}

回调函数在 C
语言中太过常用,往往一些高等的操作都急需动用那几个才具,后续还有恐怕会更新一些有关回调函数技艺运用的部分小说。


参照链接:
http://c.biancheng.net/cpp/html/1202.html
http://blog.codingnow.com/2010/03/object_oriented_programming_in_c.html
https://www.zhihu.com/question/19801131
http://blog.csdn.net/tzshlyt/article/details/52993282
http://www.cnblogs.com/kunhu/p/3713370.html
http://www.cnblogs.com/kunhu/p/3700610.html


编著时间:21:39-22:56

有的人冷俊不禁要问,宏定义怎么扯到那边来了,大家得以先看多少个简单易行的例子:

#define MacroFunction Afunction

然后在程序里面你调用了大批量的AFunction,不过有一天,你忽然开采你要用BFunction了,(可是AFunction又必须要要,很有异常的大可能率你未来还要调用卡塔尔,此时,你就足以#define
MacroFunction Bfunction来完毕如此的目标。

2.静态的入口函数,保障函数名雷同,利用标识位调用子函数:

如此那般的独立应用超级多,比方说网卡驱动里面有贰个入口函数Nilan(int
FunctionCode,Para*卡塔尔。具体的参数是什么样记不掌握了。可是NiLan的本位是这么的:

Long Nilan(int FunctionCode,Para*)

{

Switch(FunctionCode)

{

Case SendPacket:

send(….)

Case ReceivePacket:

receive(…)

 …..

  }

写到这里大家领略怎样意思了吧。保障平等的函数名正是说:网卡驱动是和pNA+左券栈互连的,那么如何确认保证pNA+公约栈和莫衷一是的驱动都同盟呢,二个简短的章程就是独自使用四个入口函数。通过改换假使函数的参数值,来调用内部的生机勃勃风度翩翩函数。

像这种类型的做法是能够提升的:要是之后想调用新的函数,扩充对应的函数参数值就好了。假设大家将网卡驱动和pNA+左券栈看作四个层的话,我们能够开采:层与层之间的互连接口是不大的(这里是叁个入口函数卡塔尔,平时是采取名字深入分析的法子实际不是宛在近期的函数调用(利用FunctionCode调用函数,Nilan仅仅达成名字深入剖析的成效卡塔尔国――!接口限定和名字拆解深入分析

接口约束:层与层之间仅仅精晓有限的函数

名字解析:层与层之间确立联合的名字与函数的附和关系,之间利用名字调用成效。

3.CALLBACK函数

本人感到这是C语言的一个创举,固然它很简短,就象如何把鸡蛋竖起来同样,不过你要是没悟出的话,仍然为三个难点。若是说静态入口函数完毕了七个可治本的宏观的话,CallBack便是落到实处了三个可发展的微观:它使得叁个函数能够在不重复编译的图景下促功能益的丰盛!但是在最最开始时代的时候,也是有蛮三个人持批驳态度,因为它用了函数指针。

函数指针纵然灵活,可是出于它要拜谒内部存款和储蓄器两遍才方可调用到函数,第一次访谈函数指针,第一回才是真的的函数调用。它的功效是比不上普通函数的。可是在七个不太苛刻的条件下,函数调用本人就多少耗费时间,函数指针的质量又不是专程不佳,使用函数指针其实是一个最棒的精选。

只是函数指针除了品质,最麻烦的地点正是会诱致程序的“破烂不堪”。试想:在程序中,你读到八个函数指针的时候,如若您愣是不知道这么些函数指针指向的是哪些函数,这几个感到确实很倒霉。(能够看后面包车型客车篇章,要动用先进的前后相继框架,防止那样的情况卡塔尔

三、Event和Message

看了上边的陈述,相信大家不怎么某个掌握为什么要接受Event和Message了。具体的函数调用会拉动众多的主题素材(固然从效用上讲,那样做是很好的卡塔尔。为了升高程序的灵活性,Event和Message的措施发生了。用名字解析的艺术替代经常的函数调用,那样,如若两岸对这么的剖析是同生机勃勃的话,就能够直达三个合併。然则伊芙nt和Message的机能还不只是这样。

Event和Message还应该有组建进度间通讯的功力。进程将本人的音信发给“控制宗旨”(轻便的正是三个音信队列,和贰个while循环不断的吊销息队列的从头到尾的经过并施行卡塔尔,调整造进程序获取音讯,分发给相应的历程,那样任何进度就足以获取那一个消息并开展响应。

Event和Message是很灵巧的,因为您可以每一日加多可能关闭多个经过,(仅仅供给加多分发音讯的列表就足以了卡塔 尔(英语:State of Qatar)Event和Message从程序完结上校本身感觉是意气风发律的,只不过概念分歧。伊芙nt多用来指二个动作,比方硬件发生了何等专业,须要调用贰个什么函数等等。Message多用于指三个指令,举例如何程序爆发了哪些操作命令等等。

四、小结

实际编制程序序和写小说相像,都是先有二个大纲,然后稳步的充裕。先抽象化获得程序的骨架,然后再构思种种方面包车型地铁其余剧情:譬如说程序极端的时候会发生怎样难点?程序的那么些地点的效果以往还不周到,现在再完善会有哪些难点?程序是否足以扩张的?

相关文章