C语言“面向对象设计”--不仅是看起来有点像

最近帮个同学debug C程序,闲暇之余也没有闲着,查过很多资料后,对C语言的理解深了一点。C语言不愧为世界IT的基石,即使历经几十


年,依然经久不衰。
我也只能这样跟风称赞几句,至于其中的深奥,实在不得领会,或者,根本不用领会。


进入正题,怎样用C语言来实现面向对象模式。为何有这个想法,是在看《设计模式初学者指南》中,序言中提到过,面向对象从来不是语言


,而是方法,在C语言时期,C++尚在酝酿当中,那时便有面向对象的“设计模式”,比如用一个虚函数指针表来维护虚函数等等。语言在发展


,模式就成了语法,条款就成了法律,情人就变成了老婆,世间万物无不如此。


面向对象的设计方法,首先得知道重点是什么,面向对象最重要的地方,我认为在于多态和数据隐藏,类,对象,继承重载等概念同样非常关


键,但是都基于前两位。
当然,在什么时候用合适的方法,函数式的设计从来没有消失过,反而愈发壮大。


为了满足好奇心,我百度花了不少时间,心里大概有个底,如何用C语言“实现”面向对象。至少要看起来像,这个也是最终的目的之一。


好了,要看起来像,用起来像java,c#,首先得有 类,这个可以交给C语言中的结构体,可惜的是结构体只能封装数据,不能封装函数,要说


例外,就是在结构体中声明函数指针,来达到看起来像“成员函数”的样子。


现在,我要实现一个“泛型”容器类 List,  让它用起来就像C#中的List<T>一样:
         /**************** C# code:*****************************/
         List<double> myList=new Lis<double>();
         double data1;
         myList.Add(data1);


为什么这里的泛型要打上引号,因为在C语言中无法主动检测类型,也就不能比较"Type1==Type2",无法区分两者的真正类型。
我所知道的方法实现部分泛型就是用void* 指针,void* 指针可以转换为任意的指针,这个作用和C#中Object的装箱拆箱别无二致。


这个容器采用什么样的数据结构呢,要能变长,随意增减大小,最好就是双链表了,否则两遍数据结构这课不是白学了吗.。


所以首先定义双链表的节点:


//List.h


typedef struct _Linklist{


         Linklist* pPrvNode;    //指向前一个节点
         Linklist* pNextNode;  //指向后一个节点
         void *dataObj;             //注意这里,每个NODE储存的是数据的引用--它的指针。由于我们不可能知道数据是什么样的,所以用void*
                                            //代替,这是就是泛型
}Linklist;




然后我们的List容器,需要知道自己链表的首尾指针即可。
我们想想看,容器还要有哪些对外的特性?这决定了它的对外接口


通俗的说
1 要能添加数据  Add()
2要能按索引删除数据  Remove()
3要能通过索引得到数据 GetObjByIndexOf()
4要能返回一个迭代器 Iterator() 




//迭代器是什么,javacode:
iter=List.Iterator();
while(iter.hasNext())
{
       data=  iter.next();
}


最后是所有类的两个函数 构造函数和析构函数。


通过上面的分析,我们能得到List类的大致的设计图,用代码描述出来




//List.h


typedef struct _List{


//"成员变量"
       int length;    //  数据的总个数                           
       int datasize;                     //注意这里,这里保存一个数据的大小,是用来初步“类型检查”,如果加入的数据和之前的数据大小不一样,   


                                              //则说明类型不同,不让加入 ,仅此而已。                     
      


       Linklist* _head;      //链表的头指针                                        
       Linklist* _tail;         //链表的尾指针
                               
       Iterator* _iter;        //迭代器的指针
 
   
// “成员”函数,这里声明一些上函数指针。由于C语言函数不能获得之外的局部变量,所以我们给它参数中放上this指针,以便访问到对象


的//"数据"
                           
       Iterator* (*GetIterator)(List* thisList);                          //获得迭代器        
       Bool (*AddObj)(List* thisList,void* obj,int datasize);  
       Bool (*Remove)(List* thisList,void* obj);               
       void* (*GetObjByIndex)(List* thisList,int index);       




       const char*(*GetType)();  //重点是这两个虚函数,待会我们在子类中重写他们
       void (*Delete)(void* thislist);


}List;




List* new_List(int datasize);           //构造函数单独使用,不放在成员函数里面,相当于类的静态函数


Bool List_Add(List* thisList,void* Obj,int datasize);
Bool List_Remove(List* thisList,void* Obj);
Iterator* List_GetIterator(List* thisList);
void* List_GetObjByIndex(List* thislist,int index);
const char* List_GetType();
void List_Delete(void* thislist);


头文件定义完毕后,在源文件中实现部分函数:
我们只看其中两个函数的实现,一个构造函数,一个待会会重写的函数


//List.cpp
//构造函数,分别一层一层往下申请空间,并给每个变量一个有意义的值。
List* new_List(int datasize) //ctor
{
List* list;

list=(List *)malloc(sizeof(List)); //申请空间
if(list==NULL){return NULL;}
Linklist* L;


L=(Linklist*)malloc(sizeof(Linklist));
L->pPrvNode=L->pNextNode=NULL;
list->_head=L;
list->_tail=L;
list->length=0;
list->datasize=datasize;


    list->_iter=new_Iterator(L);  


              //这里实现了函数指针的指向,将结构体里面的函数指针指向实际实现的函数,就能使用了。


    list->AddObj=List_Add;
    list->Remove=List_Remove;
    list->GetIterator=List_GetIterator;
    list->GetObjByIndex=List_GetObjByIndex;
    list->GetType=List_GetType;
    list->Delete=List_Delete;




   return list;  
}
const char* List_GetType()
{
     return "Type is List";
}




看完函数指针后大家应该想到了,如果要重写函数,不改变函数名,改变函数的指向即可。


接下来把所有的函数都实现完后,我们在main里面使用就是这样了:
//假如有个Data结构体
typedef struct _Data Data; 
//main函数里面:


   List* list=new_List(sizeof(Data));
   Data data[10];


   int i=0;
   for(i=0;i<10;i++)
{
     list->AddObj(list,&data[i],sizeof(Data)); //加入数据
}


   printf("%s\n",list->GetType());


这时候控制台里面就会输出" Type is List"的字样


这里是不是有点像面向对象的语言了,唯一维和的地方就是不管什么函数都要传自己的指针,毕竟它有自己的缺陷。


下面开始实现继承和多态。


有了List类,我们能进一步在这个类上面做些什么呢,比如说,在List上面实现 栈 这种数据结构Stack.


我们想让Stack继承List, 让它有List的所有成员,并且决定是否重写函数,怎么样做到呢,很简单:


//Stack.h:


#include"List.h"


typedef struct _Stack{


List  baseObj;  //这个包含的“父类”一定要放在第一个


                 void*(*Pop)(Stack* thisStack);
                 Bool (*Push)(Stack* thisStack,void* obj,int datasize);
                 void (*Delete)( Stack* thisStack);
}Stack;。


通过在结构体中声明一个父类结构变量就能达到我们的期望,这个同样基于C语言指针的灵活性等下我们就能看到。
好了,不是一直在扯怎样实现多态吗,构造函数给你看,你就明白了:
Stack* new_Stack(int datasize)//ctor
{
Stack*stack=(Stack*)malloc(sizeof(Stack));
List* list=new_List(datasize);




    list->GetType=Stack_GetType;                  //重点!!!将父类的函数指针指向新的函数,代表重写


stack->baseObj=*list;

//这里不能list->Delete(list)因为会删掉iter,而stack中的List中的*iter正指向它
                   free(list);
  
    
stack->Push=Stack_Push;       
stack->Pop=Stack_Pop;
stack->Delete=Stack_Delete;
return stack;
}
而重写后函数为:
const char* Stack_GetType()
{
    return "Type is Stack";
}


接下来就是模仿类型转换的时刻了,多态不是梦:


main函数中:


    List* list=new_List(sizeof(Data));


    List* slist= (List*)new_Stack(sizeof(Data));    //将stack强制转换为父类,注意这个技巧,结构体指针的强制转换。


    printf("%s\n",list->GetType());                 //用list类的gettype函数
    printf"%s\n",slist->GetType());               //slist使用父类的接口.




    可以看到,控制台中输出了两句不同的话:
  
  Type is List
  Type is Stack


 明明都是 List 类型的指针,用同一个函数输出的结果却不一样,这就是让人着迷的多态。乍一看让人惊讶,C语言也能实现多态,但是上面


的讲解会让你明白,多态就是将函数指针指来指去,用同一个名字,身后却是不同的风景。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值