vlambda博客
学习文章列表

面向对象的C语言编程-DynamicLinkageGenericFunctions--C语言中的偷梁换柱

DynamicLinkageGenericFunctionsConstructors and Destructors方法、信息、类和对象newdeleteclonediffersizeOfmain总结TIPS


DynamicLinkageGenericFunctions

[代码仓库]:https://github.com/zzu-andrew/linux-sys/blob/6c19498a436902120eec7e08c18e4a74d04dafa2 b+j6/ooc/test/c.02/


Constructors and Destructors

构造与析构函数

 struct type {
     size_t size; /* size of an object */
     void (* dtor) (void *); /* destructor */
 };
 struct String {
     char * text; /* dynamic string */
     const void * destroy; /* locate destructor */
 };
 struct Set {
    ... information ...
     const void * destroy; /* locate destructor */
 };

new数据初始化只是new函数的部分工作,需要将dtor函数指向新创建对象中的析构函数,并且new中传入不同的参数时需要实现不同的功能。

 new(Set); /* make a set */
 new(String, "text"); /* make a string */

需要进行初始化时,new将会根据不同的对象调用不同的构造函数对对象进行初始化,这里需要将对象中的函数指针ctor执行对应的构造函数,指向的构造函数如下,用于初始化除了对象之外的对象成员。

 /* 构造函数 */
 void * new (const void * _class, ...)
 {const struct Class * class = _class;
 void * p = calloc(1, class -> size);
 
 assert(p);
 /* 强制转换为Class,这样就能实现同样的函数只赋值初始化一次,但是所有定义的
 对象都能够调用 */
 * (const struct Class **) p = class;
 
 if (class -> ctor)
 {va_list ap;
 
 va_start(ap, _class);
 p = class -> ctor(p, & ap);
 va_end(ap);
 }
 return p;
 }
 /* 析构函数 */
 static void * String_ctor (void * _self, va_list * app)
 {struct String * self = _self;
 const char * text = va_arg(* app, const char *);
 
 self -> text = malloc(strlen(text) + 1);
 assert(self -> text);
 strcpy(self -> text, text);
 return self;
 }

从上述构造函数的实现可以看出,这个构造函数是没有负责对象的构建的,只是对对象中的成员进行了构造(初始化),因为对象的构造和析构是newdelete函数负责的。

并且delete函数也是只负责释放释放new函数申请的内存,所有构造函数申请的内存全部由析构函数负责进行释放。

 /* delete函数只负责释放new函数申请的内存 */
 void delete (void * self)
 { const struct Class ** cp = self;
 
  if (self && * cp && (* cp) -> dtor)
  self = (* cp) -> dtor(self);
  free(self);
 }
 /* 所有构造函数中申请的内存由,析构函数这里负责释放 */
 static void * String_dtor (void * _self)
 { struct String * self = _self;
 
  free(self -> text), self -> text = 0;
  return self;
 }

对于一个String,有一部分内存是通过构造函数进行申请的,但是String却是new函数申请的内存,String本身需要使用delete函数进行释放。

方法、信息、类和对象

delete函数中传入的对象指针,必须在传入前初始化好,将析构函数指针指向对应对象的析构函数

 struct Class {
  size_t size;
  void * (* ctor) (void * self, va_list * app);
  void * (* dtor) (void * self);
  void * (* clone) (const void * self);
  int (* differ) (const void * self, const void * b);
 };
 
 struct String {
  const void * class; /* must be first */
  char * text;
 };

每个对象的开头都有一个指向自类型的指针const void * class,通过这个指针我们能获取得到new对象时需要申请的内存大小 .size,构造函数.ctor,析构函数.dtor.clone函数对象复制函数,.differ函数对象对比函数。

仔细看来我们的每个对象开头都有一个指向自己类型的指针const void * class;,通过calss的类型中的提供的信息我们能知道如下信息:

 size_t size;  // 提供对象的大小,这里也就是String的大小
 void * (* ctor) (void * self, va_list * app); // 构造函数
 void * (* dtor) (void * self); // 析构函数
 void * (* clone) (const void * self); // 对象克隆函数
 int (* differ) (const void * self, const void * b); //对象对比函数
 


new

new函数中申请了对象的内存,并通过构造函数对对象中的内容进行了构造,其实这里也就是一个text字符串指针,申请一块内存,并将new中传入的字符串复制到申请的内存中去。

需要注意的是二级指针的使用方法,通过new中申请的是struct String那么p是指向String类型的一个对象,但是Stirng类型中class才是指向静态方法的结构体的指针,想要调用方法,就需要取出p指针中的class指针。实现方法就是* (const struct Class **) p = class;,要求就是,class必须是object中的首个指针。

拆分:

 *
 (const struct Class **)
 p
 
 在按照结合方式从左至右 *先与p结合,然后是(const struct Class **)
 上述等价于:
    p->class ==> (*p).class
    * (const struct Class **) p ==> (const struct Class *)p->class


 void * new (const void * _class, ...)
 { const struct Class * class = _class;
  void * p = calloc(1, class -> size);
 
  assert(p);
  /* 强制转换为Class,这样就能实现同样的函数只赋值初始化一次,但是所有定义的
  对象都能够调用 */
  * (const struct Class **) p = class;
 
  if (class -> ctor)
  { va_list ap;
  //ctor 指向的函数 String_ctor
  va_start(ap, _class);
  p = class -> ctor(p, & ap);
  va_end(ap);
 }
  return p;
 }


 static void * String_ctor (void * _self, va_list * app)
 { struct String * self = _self;
  const char * text = va_arg(* app, const char *);
 
  self -> text = malloc(strlen(text) + 1);
  assert(self -> text);
  strcpy(self -> text, text);
  return self;
 }

delete

delete函数是对new申请的内存进行释放的函数,在确保传入的值不是NULL并且构造函数存在的情况下先调用析构函数对self中构造函数申请的内存进行释放,在调用free释放new函数申请的内存。

 void delete (void * self)
 { const struct Class ** cp = self;
 
  if (self && * cp && (* cp) -> dtor)
  self = (* cp) -> dtor(self);
  free(self);
 }

析构函数

 static void * String_dtor (void * _self)
 { struct String * self = _self;
 
  free(self -> text), self -> text = 0;
  return self;
 }


clone

对象的克隆

 void * clone (const void * self)
 { const struct Class * const * cp = self;
 
  assert(self && * cp && (* cp) -> clone);
  return (* cp) -> clone(self);
 }
 
 

对象的克隆,就是调用new方法新生成一个和传入对象一样的对象

 static void * String_clone (const void * _self)
 { const struct String * self = _self;
 
  return new(String, self -> text);
 }

differ

差异对比函数,实现了对对象本身以及对象字符串是否相同的对比,如果对象相等那就返回0,如果是不同的对象,对象描述text一样也放回0,表示相同;

 int differ (const void * self, const void * b)
 { const struct Class * const * cp = self;
 
  assert(self && * cp && (* cp) -> differ);
  return (* cp) -> differ(self, b);
 }
 static int String_differ (const void * _self, const void * _b)
 { const struct String * self = _self;
  const struct String * b = _b;
 
  if (self == b)
  return 0;
  if (! b || b -> class != String)
  return 1;
  return strcmp(self -> text, b -> text);
 }

sizeOf

sizeOf函数比较简单,内部只是返回对象中size字段中存储的数值

 size_t sizeOf (const void * self)
 { const struct Class * const * cp = self;
 
  assert(self && * cp);
  return (* cp) -> size;
 }


main

 #include <stdio.h>
 #include "String.h"
 #include "new.h"
 
 int main ()
 { void * a = new(String, "a"), * aa = clone(a);
  void * b = new(String, "b");
 
  printf("sizeOf(a) == %zu\n", sizeOf(a));
  if (differ(a, b))
  puts("ok");
 
  if (differ(a, aa))
  puts("differ?");
 
  if (a == aa)
  puts("clone?");
 
  delete(a), delete(aa), delete(b);
  return 0;
 }



总结

这小节想说的其实就是动态的指针,同样的应用程序将函数指针指向不同的函数,就能实现不同的效果.

在主函数中创建了a、aa、b对象,三个对象都通过calss指针指向了共同的Class结构体, Class结构中有指向静态函数的指针,在编译的时候通过编译不同的c文件实现对函数指针装载不同的函数。

实现的效果也就是通过动态指针指向不同的函数,从而实现同样的主函数,实际执行的时候,执行不同的函数,达到不同的效果,实现对数据的封装。


TIPS


 #include <stdio.h>
 #include <stdlib.h>
 #include <assert.h>
 #include <string.h>
 #include <stdarg.h>
 
 void test(void);
 
 struct String {
  const void * class; /* must be first */
  char * text;
 };
 
 struct Class {
  size_t size;
  void (* test) (void);
 };
 void test(void)
 {
     printf("test for point.\n");
 }
 
 static const struct Class _String = {
  sizeof(struct String),
  test
 };
 
 const void * String = & _String;
 
 /**
  *  
  */
 int main(int argc, char const *argv[])
 {
     size_t stringSize = sizeof(struct String);
     
     const struct Class * class = String;
  void * p = calloc(1, class -> size);
 
  assert(p);
     
  * (const struct Class **) p = class;
 
     struct String {
         const void * class; /* must be first */
         char * text;
    };
     struct String * p = (struct String *)calloc(1, sizeof(struct String));
     
     return 0;
 }


我们有如下定义:

 struct String {
  const void * class; /* must be first */
  char * text;
 };
 struct Class {
  size_t size;
  void (* test) (void);
 };

首先我们申请一个内存void * p = calloc(1, sizeof(struct String));,这里很好理解,这个时候我们想让p变成一个指向struct String类型的一块内存,只需要申请的时候加上强制转换就可以了struct String * p = (struct String *)calloc(1, sizeof(struct String));,这个时候p就是一个指向struct String类型元素的一个结构体指针。

接下来看下这个使用方法*(const struct Class **) p = class;很多人看到第一眼的时候,第一眼就本能的将后面两个取址符与前面一个解引用相互抵消最终得出下面这样的结论,实际效果相当于:

(const struct Class *) p = class;,你要是真的这样想就大错特错了。

这里的意思其实是如下图:

在经过上述操作之后,p指针指向了String结构体,而* (const struct Class **) p = class;的作用就是讲String中的void *类型的指针class指向String结构体,注意是结构体不是类型,解如图中那样,String结构体其实是一个struct Class类型的结构体指针。

 static const struct Class _String = {
  sizeof(struct String),
  test
 };
 
 const void * String = & _String;


 所以下面这个绝对不能使用取址和解引用相互抵消的方式进行代码走读,也不要新百度问答上那些讲的这不这样使用不能达到能将String结构体中的void  *指针指向新申请内存的目的
     const struct Class ** -- 告诉编译器这个指针是个二维指针,第一个指针是指向String结构体,结构体中又有两个指针,第二个指针说明的是取结构体指针中的指针,具体取得哪个指针就由前面给出的类型来决定了,这里取得是一个`struct Class`结构体指针。
     因为这个时候取得是双重指针,有因为p本身又是指针,要想和class这个指针对应上,需要对指针进行一次解引用,这也就形成了下面这种方式,在C语言中实现取结构体指针中的指针的方式
 * (const struct Class **) p = class;