当前位置:   article > 正文

C 语言面向对象编程 – 继承_struct coordinate

struct coordinate

上一篇文章主要讲述了 C 语言面向对象编程 – 封装的简单概念和实现。

上一篇文章的具体内容,可以查看以下链接: C 语言面向对象编程 - 封装

本篇文章继续来讨论一下,如何使用 C 语言实现面向对象编程的一个重要特性:继承。

继承就是基于一个已有的类(一般称作父类或基类),再去重新声明或创建一个新的类,这个类可以称为子类或派生类。子类或派生类可以访问父类的数据和函数,然后子类里面又添加了自己的属性和数据。在 C 语言里面,可以通过结构体嵌套的方式去实现类的单继承(暂不考虑多重继承),但有一点注意事项,就是在结构体嵌套时,父类对象需要放在结构体成员的第一个位置。

现在,我们基于已有的 coordinate 类作为父类,再重新定义一个 rectangle 派生类。在上一篇文章代码的基础上,我们修改一下父类 coordinate,把操作函数通过函数指针的方式封装在结构体内,让对象的封装程度进一步提高。修改后的父类coordinate代码,如下所示:

头文件 coordinate.h 

  1. #ifndef __COORDINATE_H_
  2. #define __COORDINATE_H_
  3. //声明一个位置类,属性为坐标x,y,提供属性操作函数
  4. typedef struct coordinate {
  5. short int x;
  6. short int y;
  7. void (*moveby)(struct coordinate *p_coordinate,short int dx,short int dy);
  8. short int (*get_x)(struct coordinate *p_coordinate);
  9. short int (*get_y)(struct coordinate *p_coordinate);
  10. }COORDINATE_T,*P_COORDINATE_T;
  11. extern void coordinate_init(P_COORDINATE_T p_coordinate,short int x,short int y);
  12. extern void coordinate_uninit(P_COORDINATE_T p_coordinate);
  13. #endif // !__COORDINATE_H_

在头文件 coordinate.h 里,声明一个位置类,类里面提供了坐标属性 x 和 y,还提供了属性的操作函数指针。头文件对外提供 coordinate_init 和 coordinate_uninit 两个函数,用来初始化对象和解除初始化。

源文件 coordinate.c 

  1. #include "stdio.h"
  2. #include "stdlib.h"
  3. #include "string.h"
  4. #include "inc/coordinate.h"
  5. //修改 coordinate 的属性值
  6. static void coordinate_moveby(struct coordinate *p_coordiante,short int dx,short int dy)
  7. {
  8. if(NULL != p_coordiante){
  9. p_coordiante->x += dx;
  10. p_coordiante->y += dy;
  11. }
  12. }
  13. //获取coordinate的属性值x
  14. static short int coordinate_get_x(struct coordinate *p_coordiante)
  15. {
  16. return (NULL != p_coordiante) ? p_coordiante->x : -1;
  17. }
  18. //获取coordinate的属性值y
  19. static short int coordinate_get_y(struct coordinate *p_coordiante)
  20. {
  21. return (NULL != p_coordiante) ? p_coordiante->y : -1;
  22. }
  23. //创建一个coordinate对象
  24. void coordinate_init(P_COORDINATE_T p_coordinate,short int x,short int y)
  25. {
  26. if((x < 0) || (y < 0) || (NULL == p_coordinate)){
  27. printf("coordinate create error! x or y can not be less than zero \n");
  28. return;
  29. }
  30. p_coordinate->x = x;
  31. p_coordinate->y = y;
  32. p_coordinate->moveby = coordinate_moveby;
  33. p_coordinate->get_x = coordinate_get_x;
  34. p_coordinate->get_y = coordinate_get_y;
  35. }
  36. //销毁一个coordinate对象
  37. void coordinate_uninit(P_COORDINATE_T p_coordinate)
  38. {
  39. if(NULL != p_coordinate){
  40. p_coordinate->x = -1;
  41. p_coordinate->y = -1;
  42. p_coordinate->moveby = NULL;
  43. p_coordinate->get_x = NULL;
  44. p_coordinate->get_y = NULL;
  45. }
  46. }

在源文件 coordinate.c 里,属性的操作函数都使用 static 进行声明,不允许外部调用。在函数 coordinate_init 中,主要进行了属性赋值,并注册操作函数指针,后面可以直接通过函数指针对操作函数进行调用。在函数 coordinate_uninit 中,主要是清除各个属性的赋值。

至此,整个父类 coordinate 修改完成,父类把属性和属性的操作函数都封装在结构体内,其封装程度已经比较高,外部不能直接调用父类的属性操作函数,必须通过函数指针的方式进行调用。

接下来,我们基于父类 coordinate ,重新声明一个子类 rectangle ,子类在头文件中的声明,如下所示:

头文件 rectangle.h 

  1. #ifndef __RECTANGLE_H_
  2. #define __RECTANGLE_H_
  3. #include "coordinate.h" //包含基类的接口
  4. //声明一个rectangle类,继承coordinate类
  5. typedef struct rectangle {
  6. COORDINATE_T coordinate; //父类,必须放在首位
  7. unsigned short width;
  8. unsigned short height;
  9. }RECTANGLE_T,*P_RECTANGLE_T;
  10. extern P_RECTANGLE_T rectangle_create(short int x,short int y,unsigned short width,unsigned short height);
  11. extern void rectangle_destroy(P_RECTANGLE_T p_rectangle);
  12. extern void rectangle_test_function(void);
  13. #endif // !__RECTANGLE_H_

源文件 rectangle.c 

  1. #include "stdio.h"
  2. #include "stdlib.h"
  3. #include "string.h"
  4. #include "inc/rectangle.h"
  5. //创建一个rectangle类对象
  6. P_RECTANGLE_T rectangle_create(short int x,short int y,unsigned short width,unsigned short height)
  7. {
  8. P_RECTANGLE_T p_rectangle = NULL;
  9. p_rectangle = (P_RECTANGLE_T)malloc(sizeof(RECTANGLE_T));
  10. if(NULL != p_rectangle){
  11. p_rectangle->width = width;
  12. p_rectangle->height = height;
  13. coordinate_init(&(p_rectangle->coordinate),x,y);
  14. }
  15. else{
  16. printf("rectangle create error! \n");
  17. }
  18. return p_rectangle;
  19. }
  20. //销毁一个rectangle类对象
  21. void rectangle_destroy(P_RECTANGLE_T p_rectangle)
  22. {
  23. coordinate_uninit(&(p_rectangle->coordinate));
  24. if(NULL != p_rectangle){
  25. free(p_rectangle);
  26. p_rectangle = NULL;
  27. }
  28. }

在头文件 rectangle.h 里面,通过include包含了父类coordinate的接口,并创建了一个新的结构体,用于声明一个 rectangle 类,这个结构体把父类 coordinate 放在了第一个成员的位置,同时新增了自己的两个属性,宽度width和高度height。

在源文件rectangle.c 里面,rectangle_create 函数用于创建一个 P_RECTANGLE_T 类型的对象,并为其分配内存空间。分配成功后,对调用父类 coordinate_init函数,对父类的各种属性进行初始化,并同时对自身的属性 width 和 height 进行初始化,最后返回创建成功后的对象指针。

rectangle_destroy 用于父类对象属性的解除初始化,并为对象属性重新分配默认值,释放之前申请的内存空间,销毁 rectangle 对象。

从头文件 rectangle.h 和源文件 rectangle.c 可以看出,子类 rectangle 是基于其父类 coordinate 进行声明和构建的,因为矩形rectangle除了 width 和 height 属性外,还包含了坐标 x 和 y 属性。

把父类放在结构体成员的第一个位置,是由于结构体内存的连续性,可以很安全地进行强制类型转换。举个例子:假如一个函数要求传入的参数是 COORDINATE_T 类型,但可以通过强制类型转换,传入 RECTANGLE_T 类型的参数,具体的使用方法,可以查看以下测试函数。

  1. void rectangle_test_function(void)
  2. {
  3. P_RECTANGLE_T p_rectangle_1 = NULL;
  4. P_RECTANGLE_T p_rectangle_2 = NULL;
  5. //创建两个 P_RECTANGLE_T 类型的类对象
  6. p_rectangle_1 = (P_RECTANGLE_T)rectangle_create(0,0,150,150);
  7. p_rectangle_2 = (P_RECTANGLE_T)rectangle_create(200,200,500,500);
  8. if((NULL != p_rectangle_1) && (NULL != p_rectangle_2)){
  9. //打印出类对象的初始化属性,通过函数指针的方式来调用属性操作函数
  10. printf("p_rectangle_1,x = %d,y = %d,width = %d,height = %d \n",\
  11. p_rectangle_1->coordinate.get_x(&(p_rectangle_1->coordinate)), \
  12. p_rectangle_1->coordinate.get_y(&(p_rectangle_1->coordinate)), \
  13. p_rectangle_1->width,p_rectangle_1->height);
  14. printf("p_rectangle_2,x = %d,y = %d,width = %d,height = %d \n", \
  15. p_rectangle_2->coordinate.get_x(&(p_rectangle_2->coordinate)), \
  16. p_rectangle_2->coordinate.get_y(&(p_rectangle_2->coordinate)), \
  17. p_rectangle_2->width,p_rectangle_2->height);
  18. //修改类对象的属性,注意这里有两种方式,1、通过强制类型转换修改。2、通过正常方式修改
  19. p_rectangle_1->coordinate.moveby((P_COORDINATE_T)p_rectangle_1, 50, 50);
  20. p_rectangle_2->coordinate.moveby(&(p_rectangle_2->coordinate), 50, 50);
  21. //再次打印出类对象的修改后的属性
  22. printf("after moveby, p_rectangle_1,x = %d,y = %d,width = %d,height = %d \n",\
  23. p_rectangle_1->coordinate.get_x(&(p_rectangle_1->coordinate)), \
  24. p_rectangle_1->coordinate.get_y(&(p_rectangle_1->coordinate)), \
  25. p_rectangle_1->width,p_rectangle_1->height);
  26. printf("after moveby, p_rectangle_2,x = %d,y = %d,width = %d,height = %d \n", \
  27. p_rectangle_2->coordinate.get_x(&(p_rectangle_2->coordinate)), \
  28. p_rectangle_2->coordinate.get_y(&(p_rectangle_2->coordinate)), \
  29. p_rectangle_2->width,p_rectangle_2->height);
  30. }
  31. //销毁类对象
  32. rectangle_destroy(p_rectangle_1);
  33. rectangle_destroy(p_rectangle_2);
  34. }

测试函数的运行效果,如下图所示:

通过上述代码的测试,可以总结出以下几点内容:

1、外部函数可以通过子类直接使用父类的各个成员,但只能通过子类结构体的第一个成员来访问。

2、父类放在子类结构体的第一个位置,由于结构体内存的连续性,因此可以通过强制类型转换来直接访问。

3、由于C语言结构体的特性,即使子类存在与父类同名的函数,父类的函数不会被子类的函数覆盖和重写,因此,子类与父类之间不存在函数重载。

 

源码下载地址:https://github.com/embediot/my_program_test

 

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/blog/article/detail/43411
推荐阅读
相关标签
  

闽ICP备14008679号