当前位置:首页 > 公众号精选 > 嵌入式微处理器
[导读]C和C++的最大区别便是,C++有类,C没有类的概念。单单这一个类使得C缺失很多的东西。好在C有结构体,勉强可以当0.1个类来使用。

微信公众号:二进制人生
专注于嵌入式linux开发。

目录

前言1、继承2、封装伪构造函数3、多态

前言

我们都知道C语言是一门过程性语言,所谓过程性就是在解决问题时,将问题按步骤分解。

例如,做菜的时候,先点火,再倒油,接着下菜翻炒,最后加盐和酱油。但有时候借鉴面向对象的思想来组织代码,逻辑层次会更加清晰。

C和C++的最大区别便是,C++有类,C没有类的概念。单单这一个类使得C缺失很多的东西。好在C有结构体,勉强可以当0.1个类来使用。

众所周知,类有三大特性:封装、继承、多态。我们来看看C语言如何借鉴类的三大特性来更好的组织代码。

1、继承

C语言没有严格意义上的继承,可以借助结构体嵌套实现类似于继承的形式,但始终不尽人意。

struct parent
{

    int a;
};
struct son
{

    struct parent p;//儿子继承父亲
    int b;
};

C++的类可以实现成员的访问控制,例如将变量b声明成private,那么外部就无法访问。但C的结构体做不到。

在C++里头,父亲的私有成员,儿子是无法访问的。结构体嵌套也做不到。因为结构体根本就没有访问控制的概念。

对于C++而言,访问控制实质上是在编译层做的,我们仍旧可以通过指针来间接访问。

例如:

class Base{
public:
   int a;
private:
   int b;
};

尽管b被声明成私有,但我们仍旧有办法访问它(借助指针绕过语法检查):

Base t;
int *p = &t.a;
cout << p[1] << endl;

2、封装

封装就是把数据和方法打包到一个类里面。C++的实现大致如下:

class 类名
{

public:
    公有方法1
    公有方法2
    ……
    公有数据1
    ……
private:
    私有方法1
    私有方法2
    ……
    私有数据1
    ……
};

这样做的好处是显而易见的。一个类实现了一个小模块,使得代码结构比较清晰。对外接口和数据定义成public,允许调用者直接访问。内部接口和数据定义成private,外部不可见。

在 QT 中,为了更好的隐藏一个类的具体实现,一般是一个公开头文件、一个私有头文件,私有头文件中定义实现的内部细节,公开头文件中定义开放给客户程序员的接口和公共数据。看看QObject (qobject.h),对应有一QObjectPrivate(qobject_p.h ) ,其他的也类似。

QObject{
public:
    xxx
    xxx
private:
    QObjectPrivate * priv;
};

我们可以借助C语言的指针和结构体来实现方法和数据的封装。基本框架如下:

struct 结构体名{
    数据1
    数据2;
    ……
    方法1:
    方法2;
    ……
}

在结构体里定义成员变量很容易,直接int a;
在结构体里定义成员函数要使用函数指针,比如:

int (*func)(void *)

所以,我们把上面的框架具体化就是:

struct manager{
    int data1;
    int data2;
    int (*operation1)(struct manager*);
    int (*operation2)(struct manager*);
};

实际上,C++的成员函数也是通过函数指针的形式来实现,本质上是一致的。我们都知道类的成员函数和类的成员变量是分开存储的,同一个类的所有对象,成员函数只需要占据一份地址空间。

在定义结构体之后,函数指针并没有赋值,一般我们会定义一个结构体初始化函数来初始化结构体成员,这有点类似于类的构造函数,但类的构造函数在创建对象时自动调用,而我们这个结构体初始化函数只能自己手动调用了。

同样的,对标C++的析构函数,我们在C语言里头有一个去初始化的函数来完成模块的去初始化,这种思想不就是一样的吗?

static int operation1(struct manager *manager_ptr)
{
    ……
}
static int operation2(struct manager *manager_ptr)
{
    ……
}

伪构造函数

注意,我们把两个operation函数定义成了static,这样子文件之外的函数就不能调用它,只能通过manager结构体来调用。是不是感觉有点封装的意味。

void init_manager(struct manager* manager_ptr)
{
    manager_ptr->operation1 = operation1;
    manager_ptr->operation2 = operation2;
    manager_ptr->data1 = 0;
    manager_ptr->data2 = 0;
}

去初始化函数我就不写了。

如果operation函数在外面的文件定义,则可以作为init_manager函数的参数传入,这种场景也非常常见。我实现了模块A,该模块的operation1函数处理数据并输出一些结果。但是我并不知道使用该模块的人想要什么格式的结果,比如有一些人想要json格式的结果,有些人想要xml格式的结果。我不能帮他们一一实现一个方法,我干脆叫你们统一按照我指定的函数模板,实现一个处理函数,完了你们调用结构体初始化函数注册下,我会在operation1函数处理完数据后,调用你们的处理函数,给你们一个满意的结果。

为了达到上面的目的,简单修改下,我们把函数operation2定义成一种类型,

typedef int  (*FUN_CBK)(struct manager *manager_ptr);

结构体定义稍作修改:

struct manager{
    int data1;
    int data2;
    int (*operation1)(struct manager*);
    FUN_CBK call_back;
};

结构体初始化函数也要做相应的修改,增加了一个函数指针形参:

void init_manager(struct manager* manager_ptr, \
FUN_CBK fun)
{
    manager_ptr->operation1 = operation1;
    manager_ptr->call_back = fun;//用外部传入的回调函数进行初始化
    manager_ptr->data1 = 0;
    manager_ptr->data2 = 0;
}

通过上面的操作,我们用结构体和函数指针完成了模块化封装。

我看了网上的博客,有些人为了特意模仿类,还用以下方式实现了类似于类的构造函数:

struct manager *manager_create(int m, int n)
{
    struct manager *m = (struct manager *)\
    malloc(sizeof(struct manager *));
    m->data1 = m;
    m->data2 = n;
    m->operation1 = operation1;
    m->operation2 = operation2;
}

以及类似于类的析构函数:

void manager_delete(struct manager *m_ptr)
{
    free(m_ptr);
    m_ptr = NULL;
}

使用示例:

struct manager *m_ptr = manager_create (1,2);
manager_delete(m_ptr);

个人不是很喜欢这种做法,万一忘记调用manager_delete还有内存泄露的风险。

结构体归根到底还是结构体,不能实现成员对外不可见。而C++中将成员声明成private之后,外部就无法访问了。C语言里想这么做,只能将该成员移出结构体,定义为static形式。因为C不支持在结构体内部定义static变量(不信,你可以自己去试下)。

为何不能在结构体内定义static变量,想想就知道了,static变量的地址在编译链接之后是唯一且确定的,而结构体只有在实例化时才能确定其地址,并且每个结构体实例都有自己的地址空间。

3、多态

多态在上面的例子也有体现。C语言实现的多态并非是严格意义上的多态,但是这种思想的应用很广泛,我们姑且叫它多态吧。你不解C++的多态也没关系,丝毫不影响你理解下文。

linux的VFS便借鉴了这种思想。VFS(Virtual File System)是内核提供的文件系统抽象层,其提供了文件系统的操作接口,可以隐藏底层不同文件系统的实现。

一个文件系统无非就是实现对文件、目录的管理。针对文件VFS定义了统一的结构体:

struct file {
    union {
        struct llist_nodefu_llist;
        struct rcu_head fu_rcuhead;
    } f_u;
    struct pathf_path;
    struct inode*f_inode;/* cached value */
    const struct file_operations*f_op;
    ……
}; 

strcut file代表一个文件,每种文件系统(比如ext3,vfat)实现读写等操作的方式都不一样,所以将这些方法封装成函数指针,统一定义在结构体struct file_operations内。

struct file_operations {
    struct module *owner;
    loff_t (*llseek) (struct file *, loff_tint);
    ssize_t (*read) (struct file *, char __user *, size_tloff_t *);
    ssize_t (*write) (struct file *, const char __user *, size_tloff_t *);
    ……
};

每个文件系统各自完成自己的实现。


再写一个实际的例子。
定义一个人的标准接口和数据如下:

strcut man{
    int head;
    int body;
    ……
    void (*say_hello)(void);//见面问候的方式
};

中国人见面时,说你好:

void china_say_hello(void)
{
    printf(“你好”);
}

英国人见面时,说hello:

void english_say_hello(void)
{
    printf(“hello”);
}

现在来初始化它们各自的问候方式:

struct man china{
    .say_hello = china_say_hello;
}
struct man english{
    .say_hello = English_say_hello;
}

英国人和中国人对外呈现都是struct man,其见面问候的接口都是man.say_hello,但其底层实现却可以不一样。

并且我们可以在程序运行时,随意的更改中国人的问候方式。比如婴儿时期,只会“哇哇”叫,长大了才会说“你好”,我们可以改变成员say_hello的值,让其在不同时期指向不同的函数,从而达到运行时多态的目的。

其实呢,C++的多态,也是通过函数指针来实现的,学习过C++的同学就会知道,含有虚函数的类,会维护一个虚函数表,里面存放了虚函数的地址。所以说啊,C语言是C++的母语,万变不离指针,指针是C语言的一大法宝。


-END-


本文授权转载自二进制人生,作者:二进制人生




推荐阅读



【01】长见识:你真的知道C语言里extern "C" 的作用吗?
【02】硬件工程师必知的10个C语言技巧
【03】C语言如何实现拷贝图片?几行代码即可搞定
【04】C语言常用的一些转换工具函数!
【05】2020年8月程序员工资最新统计


免责声明:整理文章为传播相关技术,版权归原作者所有,如有侵权,请联系删除

免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

嵌入式ARM

扫描二维码,关注更多精彩内容

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

嵌入式开发作为信息技术领域的重要分支,其涉及的语言种类繁多,各具特色。这些语言的选择取决于目标平台的特性、性能需求、开发者的熟练程度以及项目的具体要求。本文将详细介绍几种常见的嵌入式开发语言,包括C语言、C++、汇编语言...

关键字: 嵌入式开发 C语言

Java语言和C语言是两种不同的编程语言,它们在语法、特性和应用领域上有许多差别。下面将详细介绍Java语言和C语言之间的差异以及它们各自的技术特点。

关键字: Java语言 C语言 编程

嵌入式系统是一种专门设计用于特定应用领域的计算机系统,它通常由硬件和软件组成,并且被嵌入到其他设备或系统中,以实现特定的功能。在嵌入式系统的开发过程中,选择适合的编程语言是至关重要的。C语言是一种被广泛应用于嵌入式系统开...

关键字: 嵌入式 计算机 C语言

C语言是一种广泛应用于软件开发领域的编程语言。它是由贝尔实验室的Dennis Ritchie在20世纪70年代初创建的,旨在为UNIX操作系统的开发提供一种高级编程语言。C语言具有简洁、高效、可移植性强等特点,因此成为了...

关键字: C语言 操作系统 应用程序

嵌入式系统是现代生活中无处不在的一部分。它们包括了我们的家电、汽车、智能手机、医疗设备等等。这些系统的工作必须高效、可靠,因为它们往往控制着生活中的关键方面。而C语言作为一种广泛用于嵌入式系统开发的编程语言,其质量和稳定...

关键字: 嵌入式系统 C语言 编程

在嵌入式系统开发领域中,C语言是使用最广泛的编程语言之一。它具有高效、灵活和可移植的特点,成为嵌入式系统设计师的首选语言。本文将介绍C语言编程的基本概念、特点以及在嵌入式系统开发中的应用。

关键字: 嵌入式系统 C语言 编程

C语言编译器是一种用于将C语言源代码转换为可执行程序的软件工具。它的主要功能是将C语言代码翻译成机器语言,以便计算机能够理解和执行。C语言编译器通常包括预处理器、编译器、汇编器和链接器等多个组件,它们协同工作以完成编译过...

关键字: C语言 编译器 Microsoft Visual C++

python语言不止三特点,其全部特点为简单易学、高级语言、解释型语言、可移植性、面向对象、强大的功能、开源、可扩展性、丰富的库、规范代码。

关键字: python语言 开源 面向对象

Matlab和C语言的区别是:1、用途不同;2、语法不同;3、运行速度不同;4、可移植性不同;5、代码管理不同。Matlab是一种数值计算和科学计算工具

关键字: matlab语言 C语言 系统编程

单片机是一种集成电路,它包含了中央处理器、存储器、输入输出接口和时钟等基本部件。单片机广泛应用于各种电子设备中,如家用电器、汽车电子、医疗设备等。单片机的使用领域已十分广泛,如智能仪表、实时工控、通讯设备、导航系统、家用...

关键字: 单片机编程 单片机 C语言
关闭
关闭