江门市住房和城乡建设局门户网站,wordpress 购物 插件,基于 seajs 的高性能网站开发和优化实践_王保平(淘宝),摄影网站 源码这是道哥的第009篇原创一、前言在嵌入式开发中#xff0c;C/C语言是使用最普及的#xff0c;在C11版本之前#xff0c;它们的语法是比较相似的#xff0c;只不过C提供了面向对象的编程方式。虽然C语言是从C语言发展而来的#xff0c;但是今天的C已经不是当年的C语言的扩展… 这是道哥的第009篇原创一、前言在嵌入式开发中C/C语言是使用最普及的在C11版本之前它们的语法是比较相似的只不过C提供了面向对象的编程方式。虽然C语言是从C语言发展而来的但是今天的C已经不是当年的C语言的扩展了从2011版本开始更像是一门全新的语言。那么没有想过当初为什么要扩展出CC语言有什么样的缺点导致C的产生C在这几个问题上的解决的确很好但是随着语言标准的逐步扩充C语言的学习难度也逐渐加大。没有开发过几个项目都不好意思说自己学会了C那些左值、右值、模板、模板参数、可变模板参数等等一堆的概念真的不是使用23年就可以熟练掌握的。但是C语言也有很多的优点其实最后一个优点是最重要的使用的人越多生命力就越强。就像现在的社会一样不是优者生存而是适者生存。这篇文章我们就来聊聊如何在C语言中利用面向对象的思想来编程。也许你在项目中用不到但是也强烈建议你看一下因为我之前在跳槽的时候就两次被问到这个问题。二、什么是面向对象编程有这么一个公式程序数据结构算法。C语言中一般使用面向过程编程就是分析出解决问题所需要的步骤然后用函数把这些步骤一步一步调用在函数中对数据结构进行处理(执行算法)也就是说数据结构和算法是分开的。C语言把数据和算法封装在一起形成一个整体无论是对它的属性进行操作、还是对它的行为进行调用都是通过一个对象来执行这就是面向对象编程思想。如果用C语言来模拟这样的编程方式需要解决3个问题数据的封装继承多态第一个问题封装封装描述的是数据的组织形式就是把属于一个对象的所有属性(数据)组织在一起C语言中的结构体类型天生就支持这一点。第二个问题继承继承描述的是对象之间的关系子类通过继承父类自动拥有父类中的属性和行为(也就是方法)。这个问题只要理解了C语言的内存模型也不是问题只要在子类结构体中的第一个成员变量的位置放置一个父类结构体变量那么子类对象就继承了父类中的属性。另外补充一点学习任何一种语言一定要理解内存模型第三个问题多态按字面理解多态就是“多种状态”描述的是一种动态的行为。在C中只有通过基类引用或者指针去调用虚函数的时候才发生多态也就是说多态是发生在运行期间的C内部通过一个虚表来实现多态。那么在C语言中我们也可以按照这个思路来实现。如果一门语言只支持类而不支持多态只能说它是基于对象的而不是面向对象的。既然思路上没有问题那么我们就来简单的实现一个。三、先实现一个父类解决封装的问题Animal.h#ifndef _ANIMAL_H_#define _ANIMAL_H_
// 定义父类结构typedef struct { int age; int weight;} Animal;
// 构造函数声明void Animal_Ctor(Animal *this, int age, int weight);
// 获取父类属性声明int Animal_GetAge(Animal *this);int Animal_GetWeight(Animal *this);
#endif
Animal.c#include Animal.h
// 父类构造函数实现void Animal_Ctor(Animal *this, int age, int weight){ this-age age; this-weight weight;}
int Animal_GetAge(Animal *this){ return this-age;}
int Animal_GetWeight(Animal *this){ return this-weight;}测试一下#include stdio.h#include Animal.h#include Dog.h
int main(){ // 在栈上创建一个对象 Animal a; // 构造对象 Animal_Ctor(a, 1, 3); printf(age %d, weight %d \n, Animal_GetAge(a), Animal_GetWeight(a)); return 0;}可以简单的理解为在代码段有一块空间存储着可以处理Animal对象的函数在栈中有一块空间存储着a对象。与C对比在C的方法中隐含着第一个参数this指针。当调用一个对象的方法时编译器会自动把对象的地址传递给这个指针。所以在Animal.h中函数我们就模拟一下显示的定义这个this指针在调用时主动把对象的地址传递给它这样的话函数就可以对任意一个Animal对象进行处理了。四、 实现一个子类解决继承的问题Dog.h#ifndef _DOG_H_#define _DOG_H_
#include Animal.h
// 定义子类结构typedef struct { Animal parent; // 第一个位置放置父类结构 int legs; // 添加子类自己的属性}Dog;
// 子类构造函数声明void Dog_Ctor(Dog *this, int age, int weight, int legs);
// 子类属性声明int Dog_GetAge(Dog *this);int Dog_GetWeight(Dog *this);int Dog_GetLegs(Dog *this);
#endifDog.c#include Dog.h
// 子类构造函数实现void Dog_Ctor(Dog *this, int age, int weight, int legs){ // 首先调用父类构造函数来初始化从父类继承的数据 Animal_Ctor(this-parent, age, weight); // 然后初始化子类自己的数据 this-legs legs;}
int Dog_GetAge(Dog *this){ // age属性是继承而来转发给父类中的获取属性函数 return Animal_GetAge(this-parent);}
int Dog_GetWeight(Dog *this){ return Animal_GetWeight(this-parent);}
int Dog_GetLegs(Dog *this){ // 子类自己的属性直接返回 return this-legs;}测试一下int main(){ Dog d; Dog_Ctor(d, 1, 3, 4); printf(age %d, weight %d, legs %d \n, Dog_GetAge(d), Dog_GetWeight(d), Dog_GetLegs(d)); return 0;}在代码段有一块空间存储着可以处理Dog对象的函数在栈中有一块空间存储着d对象。由于Dog结构体中的第一个参数是Animal对象所以从内存模型上看子类就包含了父类中定义的属性。Dog的内存模型中开头部分就自动包括了Animal中的成员也即是说Dog继承了Animal的属性。五、利用虚函数解决多态问题在C中如果一个父类中定义了虚函数那么编译器就会在这个内存中开辟一块空间放置虚表这张表里的每一个item都是一个函数指针然后在父类的内存模型中放一个虚表指针指向上面这个虚表。上面这段描述不是十分准确主要看各家编译器的处理方式不过大部分C处理器都是这么干的我们可以想这么理解。子类在继承父类之后在内存中又会开辟一块空间来放置子类自己的虚表然后让继承而来的虚表指针指向子类自己的虚表。既然C是这么做的那我们就用C来手动模拟这个行为创建虚表和虚表指针。1. Animal.h为父类Animal中添加虚表和虚表指针#ifndef _ANIMAL_H_#define _ANIMAL_H_
struct AnimalVTable; // 父类虚表的前置声明
// 父类结构typedef struct { struct AnimalVTable *vptr; // 虚表指针 int age; int weight;} Animal;
// 父类中的虚表struct AnimalVTable{ void (*say)(Animal *this); // 虚函数指针};
// 父类中实现的虚函数void Animal_Say(Animal *this);
#endif2. Animal.c#include assert.h#include Animal.h
// 父类中虚函数的具体实现static void _Animal_Say(Animal *this){ // 因为父类Animal是一个抽象的东西不应该被实例化。 // 父类中的这个虚函数不应该被调用也就是说子类必须实现这个虚函数。 // 类似于C中的纯虚函数。 assert(0); }
// 父类构造函数void Animal_Ctor(Animal *this, int age, int weight){ // 首先定义一个虚表 static struct AnimalVTable animal_vtbl {_Animal_Say}; // 让虚表指针指向上面这个虚表 this-vptr animal_vtbl; this-age age; this-weight weight;}
// 测试多态传入的参数类型是父类指针void Animal_Say(Animal *this){ // 如果this实际指向一个子类Dog对象那么this-vptr这个虚表指针指向子类自己的虚表 // 因此this-vptr-say将会调用子类虚表中的函数。 this-vptr-say(this);}在栈空间定义了一个虚函数表animal_vtbl这个表中的每一项都是一个函数指针例如函数指针say就指向了代码段中的函数_Animal_Say()。 对象a的第一个成员vptr是一个指针指向了这个虚函数表animal_vtbl。3. Dog.h不变4. Dog.c中定义子类自己的虚表#include Dog.h
// 子类中虚函数的具体实现static void _Dog_Say(Dog *this){ printf(dag say \n);}
// 子类构造函数void Dog_Ctor(Dog *this, int age, int weight, int legs){ // 首先调用父类构造函数。 Animal_Ctor(this-parent, age, weight); // 定义子类自己的虚函数表 static struct AnimalVTable dog_vtbl {_Dog_Say}; // 把从父类中继承得到的虚表指针指向子类自己的虚表 this-parent.vptr dog_vtbl; // 初始化子类自己的属性 this-legs legs;}5. 测试一下int main(){ // 在栈中创建一个子类Dog对象 Dog d; Dog_Ctor(d, 1, 3, 4);// 把子类对象赋值给父类指针 Animal *pa d;// 传递父类指针将会调用子类中实现的虚函数。 Animal_Say(pa);}内存模型如下:对象d中从父类继承而来的虚表指针vptr所指向的虚表是dog_vtbl。在执行Animal_Say(pa)的时候虽然参数类型是指向父类Animal的指针但是实际传入的pa是一个指向子类Dog的对象这个对象中的虚表指针vptr指向的是子类中自己定义的虚表dog_vtbl这个虚表中的函数指针say指向的是子类中重新定义的虚函数_Dog_Say因此this-vptr-say(this)最终调用的函数就是_Dog_Say。基本上在C中面向对象的开发思想就是以上这样。这个代码很简单自己手敲一下就可以了。如果想偷懒请在后台留言我发给您。六、C面向对象思想在项目中的使用1. Linux内核看一下关于socket的几个结构体struct sock { ...}
struct inet_sock { struct sock sk; ...};
struct udp_sock { struct sock sk; ...};sock可以看作是父类inet_sock和udp_sock的第一个成员都是是sock类型从内存模型上看相当于是继承了sock中的所有属性。2. glib库以最简单的字符串处理函数来举例GString *g_string_truncate(GString *string, gint len)
GString *g_string_append(GString *string, gchar *val)
GString *g_string_prepend(GString *string, gchar *val)
API函数的第一个参数都是一个GString对象指针指向需要处理的那个字符串对象。GString *s1, *s2;s1 g_string_new(Hello);s2 g_string_new(Hello);
g_string_append(s1, World!);g_string_append(s2, World!);3. 其他项目还有一些项目虽然从函数的参数上来看似乎不是面向对象的但是在数据结构的设计上看来也是面向对象的思想比如1. Modbus协议的开源库libmodbus2. 用于家庭自动化的无线通讯协议ZWave3. 很久之前的高通手机开发平台BREW推荐阅读专辑|Linux文章汇总专辑|程序人生专辑|C语言我的知识小密圈