1. C语言中的面向对象
我在工作之前,学习的是C++和python这样的语言,工作以后虽然也还用这两种语言开发一些程序,但是嵌入式开发中主要使用的是C语言,我常常在想C语言和C++的区别,C++可以分为四大部分,C语言、面向对象、泛型编程、STL,这四个主要模块,再加上大量的细小的特性构成了如今的C++,在这里C++的函数重载特性导致了和C语言的兼容出现问题,给C和C++的混合编程带来了一些麻烦。
本文想讨论C和C++在面向对象上的使用,从软件工程的发展历史上来看,面向对象就是将一些数据给隐藏起来,使用对外函数来访问这些数据,C语言不是为面向对象设计的语言,但是面向对象作为一种编程思想和模式,并不为某种语言所特有。也是说C语言同样可以通过一些手段实现面向对象,只是想比于C++这种为面向对象设计的语言,多少有些繁琐。在实际开发和研究过程种,我对于C语言的面向对象有了一些认识和感悟。本文将重点讲讲这一部分。
面向对象作为一种编程思想,为软件开发带来根本性的指导,所谓的设计模式和设计原则都是围绕着面向对象展开的,那么面向对象的好处是什么呢,在软件规模越来越大的时候,面向过程的编程方式不再适合整体的软件架构,唯有对软件的行为的约束和限制,才能使得软件更加健康的发展,所以面向对象其实是对软件行为的约束,并未扩展软件行为的能力,这就是面向对象的封装特性,而继承,则是代码的复用,在C语言中,代码的复用通过函数即可实现,多态的意义则是延迟变化,将行为在软件中动态调用,这也就是函数指针的功能,其实所谓的多态,本质的实现逻辑就是函数指针,只是C++直接使用函数指针过于危险,将这一部分在编译器中实现,编码时并不感知指针的存在。
C语言面向对象的问题在于并没有统一的格式,由于C语言的设计中,并没有考虑面向对象,C语言的封装和多态都需要靠指针来实现。而继承是使用父类的结构体,而且,封装的程度也随着各个开发者的习惯而定,这就使得C语言面向对象的形式表现较为,并无统一格式。
C语言面向对象其实也有一定的优势,就是函数指针,原本在C++中被限制的行为,在C语言中可以被突破,比如virtual修饰的函数才能被覆盖,在C语言中函数都是通过指针存放在结构体中的,所以就出现没有不可替换的函数。当然这是优点,也是缺点,优点是有更多的可能性,缺点是打破原则,对于大的框架而言带来一些不必要的麻烦。
C语言为什么要面向对象,面向对象费时费力,往往我们要实现一个功能,直接写个函数,然后找个地方调用就好了,在代码量较小时还好,软件功能越来越多,就出现了一些问题,有些没有统一的接口,导致函数的访问在软件内跳来跳去,软件耦合性增加,修改维护时,牵一发而动全身。所以面向对象是为了架构整洁,当然并不是说不用面向对象就实现不了整洁的架构,只是面向对象对访问做出了限制,使得程序的健壮性增加。在拥有面向对象的特性后,设计模式和设计原则就排上了用场,。
下面就说说我所认为的C语言面向对象应该的样子:
- 首先就是简洁,C语言实现有限的面向对象即可,我并不希望C语言能够完全实现C++那样的特性,当然麻烦一些,也是可以做到大部分的,如果说软件整体架构对面向对象非常依赖,那么我建议使用其他编程语言,C语言在实现面向对象时,本身就有这个负担,相比于其他语言开发上会有更大的压力,当然非做不可,也不是不行,小心谨慎,多写测试用例。所以我认为C语言实现有限的面向对象即可,强多态,中封装,弱继承,或者封装和继承可以调换一下优先级,毕竟实现每个特性是有代价的,在C语言中就是代码变得复杂臃肿。
- 构造函数在struct之外,析构函数在struct之内,在调用构造函数的时候,对象还没有创建,所以构造函数放在结构体内部的意义不大,但是析构函数是可以在结构体内的,所以放在内部是没什么问题。整体类的对外呈现就是构造函数+类本身。
- 在继承和接口上,建议使用,单继承或者单接口,如果需要多接口,较为繁琐,调用非第一接口时需要将指针的偏移,不太推荐。
- 若是注重多态,可以在子类构造函数中,直接返回父类指针。
2. Demo
// Printer.h
#ifndef __PRINTER_H
#define __PRINTER_H
typedef struct Printer_t Printer;
struct Printer_t {
char name[20];
void (*Print)(Printer *this, char *buf);
void (*Delete)(Printer *this);
};
typedef struct AutoPrinter_t AutoPrinter;
struct AutoPrinter_t {
Printer base;
};
Printer *AutoPrinterCreator(char *devName);
typedef struct PaperPrinter_t PaperPrinter;
struct PaperPrinter_t {
Printer base;
int num;
};
Printer *PaperPrinterCreator(char *devName, int n);
#endif
// printer.c
#include <memory.h>
#include <stdio.h>
#include <stdlib.h>
#include "Printer.h"
#include "string.h"
void AutoPrint(Printer *this, char *buf)
{
printf("type:auto [%s]: %s\n", this->name, buf);
}
void AutoPrinterDestructor(Printer *this)
{
AutoPrinter *me = (AutoPrinter *)this;
free(me);
printf("AutoPrinter Del\n");
}
Printer *AutoPrinterCreator(char *devName)
{
AutoPrinter *me = (AutoPrinter *)malloc(sizeof(AutoPrinter));
memcpy(me->base.name, devName, 20);
me->base.Print = AutoPrint;
me->base.Delete = AutoPrinterDestructor;
return (Printer *)me;
}
void PaperPrint(Printer *this, char *buf)
{
PaperPrinter *me = (PaperPrinter *)this;
printf("type:paper paperNum:%d [%s]: %s\n", me->num, this->name, buf);
}
void PaperPrinterDestructor(Printer *this)
{
PaperPrinter *me = (PaperPrinter *)this;
free(me);
printf("PaperPrinter Del\n");
}
Printer *PaperPrinterCreator(char *devName, int n)
{
PaperPrinter *me = (PaperPrinter *)malloc(sizeof(PaperPrinter));
memcpy(me->base.name, devName, 20);
me->base.Print = PaperPrint;
me->base.Delete = PaperPrinterDestructor;
me->num = n;
return (Printer *)me;
}
// main.c
#include <stdio.h>
#include <stdint.h>
#include "Printer.h"
int main()
{
Printer *pAuto1 = AutoPrinterCreator("auto1");
pAuto1->Print(pAuto1, "i am auto1");
pAuto1->Delete(pAuto1);
Printer *pAuto2 = AutoPrinterCreator("auto2");
pAuto2->Print(pAuto2, "i am auto2");
pAuto2->Delete(pAuto2);
Printer *pPaper1 = PaperPrinterCreator("paper1", 15);
pPaper1->Print(pPaper1, "i am PaperPrinter p1");
pPaper1->Delete(pPaper1);
Printer *pPaper2 = PaperPrinterCreator("paper2", 20);
pPaper2->Print(pPaper2, "i am PaperPrinter p2");
pPaper2->Delete(pPaper2);
return 0;
}
3. 总结
C语言在程序开发过程中,在大型程序开发过程中,往往会考虑使用面向对象,面向对象的应用,规范软件结构程序,得益于设计原则的应用,使得软件的各个模块能够做到一定程度上的结构,结构体的封装也使得程序,在一定程度上的内聚,在软件的扩展和可维护性上都相当程度的提升,最为关键的是,我们有了驾驭大型软件架构的能力。