上一篇文章主要講述了 C 語言面向?qū)ο缶幊台C 封裝的簡單概念和實現(xiàn),本篇文章繼續(xù)來討論一下,如何使用 C 語言實現(xiàn)面向?qū)ο缶幊痰牧硪粋€重要特性:繼承。
繼承就是基于一個已有的類(一般稱作父類或基類),再去重新聲明或創(chuàng)建一個新的類,這個類可以稱為子類或派生類。
子類或派生類可以訪問父類的數(shù)據(jù)和函數(shù),然后子類里面又添加了自己的屬性和數(shù)據(jù)。
簡單打個比喻就是,眼睛耳朵嘴巴鼻子這幾項都是動物的特征,鄰居老王家的小狗旺財繼承了動物的特征,并且還有自身的一些特征,比如:會擺動的尾巴。也就是,旺財這個dog類,繼承了其父類animal類的特征,并且還新增加了屬于自身的一些特征。
一句話高度概括什么是面向?qū)ο螅?strong>到底是旺財自己會搖尾巴,還是外部的因素驅(qū)使旺財會搖尾巴?
仔細(xì)斟酌這句話,當(dāng)把這句話理解透了,就會對“面向?qū)ο蟆钡母拍钣懈由羁痰睦斫狻?/p>
在 C 語言里面,可以通過結(jié)構(gòu)體嵌套的方式去實現(xiàn)類的單繼承(暫不考慮多重繼承),但有一點注意事項,就是在結(jié)構(gòu)體嵌套時,父類對象需要放在結(jié)構(gòu)體成員的第一個位置。
現(xiàn)在,我們基于已有的 coordinate 類作為父類,再重新定義一個 rectangle 派生類。
在上一篇文章代碼的基礎(chǔ)上,我們修改一下父類 coordinate,把操作函數(shù)通過函數(shù)指針的方式封裝在結(jié)構(gòu)體內(nèi),讓對象的封裝程度進(jìn)一步提高。
修改后的父類coordinate代碼,如下所示:
#ifndef __COORDINATE_H_
#define __COORDINATE_H_
//聲明一個位置類,屬性為坐標(biāo)x,y,提供屬性操作函數(shù)
typedef struct coordinate {
short int x;
short int y;
void (*moveby)(struct coordinate *p_coordinate,short int dx,short int dy);
short int (*get_x)(struct coordinate *p_coordinate);
short int (*get_y)(struct coordinate *p_coordinate);
}COORDINATE_T,*P_COORDINATE_T;
extern void coordinate_init(P_COORDINATE_T p_coordinate,short int x,short int y);
extern void coordinate_uninit(P_COORDINATE_T p_coordinate);
#endif // !__COORDINATE_H_
在頭文件 coordinate.h 里,聲明一個位置類,類里面提供了坐標(biāo)屬性 x 和 y,還提供了屬性的操作函數(shù)指針。頭文件對外提供 coordinate_init 和 coordinate_uninit 兩個函數(shù),用來初始化對象和解除初始化。
源文件 coordinate.c
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "inc/coordinate.h"
//修改coordinate的屬性值
static void coordinate_moveby(struct coordinate *p_coordiante,short int dx,short int dy)
{
if(NULL != p_coordiante){
p_coordiante->x += dx;
p_coordiante->y += dy;
}
}
//獲取coordinate的屬性值x
static short int coordinate_get_x(struct coordinate *p_coordiante)
{
return (NULL != p_coordiante) ? p_coordiante->x : -1;
}
//獲取coordinate的屬性值y
static short int coordinate_get_y(struct coordinate *p_coordiante)
{
return (NULL != p_coordiante) ? p_coordiante->y : -1;
}
//創(chuàng)建一個coordinate對象
void coordinate_init(P_COORDINATE_T p_coordinate,short int x,short int y)
{
if((x < 0) || (y < 0) || (NULL == p_coordinate)){
printf("coordinate create error! x or y can not be less than zero n");
return;
}
p_coordinate->x = x;
p_coordinate->y = y;
p_coordinate->moveby = coordinate_moveby;
p_coordinate->get_x = coordinate_get_x;
p_coordinate->get_y = coordinate_get_y;
}
//銷毀一個coordinate對象
void coordinate_uninit(P_COORDINATE_T p_coordinate)
{
if(NULL != p_coordinate){
p_coordinate->x = -1;
p_coordinate->y = -1;
p_coordinate->moveby = NULL;
p_coordinate->get_x = NULL;
p_coordinate->get_y = NULL;
}
}
在源文件 coordinate.c 里,屬性的操作函數(shù)都使用 static 進(jìn)行聲明,只能在該源文件調(diào)用函數(shù),不允許外部調(diào)用。在函數(shù) coordinate_init 中,主要進(jìn)行了屬性賦值,并注冊操作函數(shù)指針,后面可以直接通過函數(shù)指針對操作函數(shù)進(jìn)行調(diào)用。在函數(shù) coordinate_uninit 中,主要是清除各個屬性的賦值。
至此,整個父類 coordinate 修改完成,父類把屬性和屬性的操作函數(shù)都封裝在結(jié)構(gòu)體內(nèi),其封裝程度已經(jīng)比較高,外部不能直接調(diào)用父類的屬性操作函數(shù),必須通過函數(shù)指針的方式進(jìn)行調(diào)用。
接下來,我們基于父類 coordinate ,重新聲明一個子類 rectangle ,子類在頭文件中的聲明,如下所示:
頭文件 rectangle.h
#ifndef __RECTANGLE_H_
#define __RECTANGLE_H_
#include "coordinate.h" //包含基類的接口
//聲明一個rectangle類,繼承coordinate類
typedef struct rectangle {
COORDINATE_T coordinate; //父類,必須放在首位
unsigned short width;
unsigned short height;
}RECTANGLE_T,*P_RECTANGLE_T;
extern P_RECTANGLE_T rectangle_create(short int x,short int y,unsigned short width,unsigned short height);
extern void rectangle_destroy(P_RECTANGLE_T p_rectangle);
extern void rectangle_test_function(void);
#endif // !__RECTANGLE_H_
源文件 rectangle.c
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "inc/rectangle.h"
//創(chuàng)建一個rectangle類對象
P_RECTANGLE_T rectangle_create(short int x,short int y,unsigned short width,unsigned short height)
{
P_RECTANGLE_T p_rectangle = NULL;
p_rectangle = (P_RECTANGLE_T)malloc(sizeof(RECTANGLE_T));
if(NULL != p_rectangle){
p_rectangle->width = width;
p_rectangle->height = height;
coordinate_init(&(p_rectangle->coordinate),x,y);
}
else printf("rectangle create error! n");
return p_rectangle;
}
//銷毀一個rectangle類對象
void rectangle_destroy(P_RECTANGLE_T p_rectangle)
{
coordinate_uninit(&(p_rectangle->coordinate));
if(NULL != p_rectangle){
free(p_rectangle);
p_rectangle = NULL;
}
}
在頭文件 rectangle.h 里面,通過include包含了父類coordinate的接口,并創(chuàng)建了一個新的結(jié)構(gòu)體,用于聲明一個 rectangle 類,這個結(jié)構(gòu)體把父類 coordinate 放在了第一個成員的位置,同時新增了自己的兩個屬性,寬度width和高度height。
rectangle_create 函數(shù)用于創(chuàng)建一個 P_RECTANGLE_T 類型的對象,并為其分配內(nèi)存空間。分配成功后,對調(diào)用父類 coordinate_init函數(shù),對父類的各種屬性進(jìn)行初始化,并同時對自身的屬性 width 和 height 進(jìn)行初始化,最后返回創(chuàng)建成功后的對象指針。
rectangle_destroy 用于父類對象屬性的解除初始化,并為對象屬性重新分配默認(rèn)值,釋放之前申請的內(nèi)存空間,銷毀 rectangle 對象。
從頭文件 rectangle.h 和源文件 rectangle.c 可以看出,子類 rectangle 是基于其父類 coordinate 進(jìn)行聲明和構(gòu)建的,因為矩形rectangle除了 width 和 height 屬性外,還包含了坐標(biāo) x 和 y 屬性。
把父類放在結(jié)構(gòu)體成員的第一個位置,是由于結(jié)構(gòu)體內(nèi)存的連續(xù)性,可以很安全地進(jìn)行強(qiáng)制類型轉(zhuǎn)換。舉個例子:假如一個函數(shù)要求傳入的參數(shù)是 COORDINATE_T 類型,但可以通過強(qiáng)制類型轉(zhuǎn)換,傳入 RECTANGLE_T 類型的參數(shù),具體的使用方法,可以查看以下測試函數(shù)。
void rectangle_test_function(void)
{
P_RECTANGLE_T p_rectangle_1 = NULL;
P_RECTANGLE_T p_rectangle_2 = NULL;
//創(chuàng)建兩個 P_RECTANGLE_T 類型的類對象
p_rectangle_1 = (P_RECTANGLE_T)rectangle_create(0,0,150,150);
p_rectangle_2 = (P_RECTANGLE_T)rectangle_create(200,200,500,500);
if((NULL != p_rectangle_1) && (NULL != p_rectangle_2)){
//打印出類對象的初始化屬性,通過函數(shù)指針的方式來調(diào)用屬性操作函數(shù)
printf("p_rectangle_1,x = %d,y = %d,width = %d,height = %d n",
p_rectangle_1->coordinate.get_x(&(p_rectangle_1->coordinate)),
p_rectangle_1->coordinate.get_y(&(p_rectangle_1->coordinate)),
p_rectangle_1->width,p_rectangle_1->height);
printf("p_rectangle_2,x = %d,y = %d,width = %d,height = %d n",
p_rectangle_2->coordinate.get_x(&(p_rectangle_2->coordinate)),
p_rectangle_2->coordinate.get_y(&(p_rectangle_2->coordinate)),
p_rectangle_2->width,p_rectangle_2->height);
//修改類對象的屬性,注意這里有兩種方式,1、通過強(qiáng)制類型轉(zhuǎn)換修改。2、通過正常方式修改
p_rectangle_1->coordinate.moveby((P_COORDINATE_T)p_rectangle_1, 50, 50);
p_rectangle_2->coordinate.moveby(&(p_rectangle_2->coordinate), 50, 50);
//再次打印出類對象的修改后的屬性
printf("after moveby, p_rectangle_1,x = %d,y = %d,width = %d,height = %d n",
p_rectangle_1->coordinate.get_x(&(p_rectangle_1->coordinate)),
p_rectangle_1->coordinate.get_y(&(p_rectangle_1->coordinate)),
p_rectangle_1->width,p_rectangle_1->height);
printf("after moveby, p_rectangle_2,x = %d,y = %d,width = %d,height = %d n",
p_rectangle_2->coordinate.get_x(&(p_rectangle_2->coordinate)),
p_rectangle_2->coordinate.get_y(&(p_rectangle_2->coordinate)),
p_rectangle_2->width,p_rectangle_2->height);
}
//銷毀類對象
rectangle_destroy(p_rectangle_1);
rectangle_destroy(p_rectangle_2);
}
測試函數(shù)的運(yùn)行效果,如下圖所示:
p_rectangle_1,x = 0,y = 0,width = 150,height = 150
p_rectangle_2,x = 200,y = 200,width = 500,height = 500
after moveby, p_rectangle_1,x = 50,y = 50,width = 150,height = 150
after moveby, p_rectangle_2,x = 250,y = 250,width = 500,height = 500
通過上述代碼的測試,可以總結(jié)出以下幾點內(nèi)容:
1、外部函數(shù)可以通過子類直接使用父類的各個成員,但只能通過子類結(jié)構(gòu)體的第一個成員來訪問。
2、父類放在子類結(jié)構(gòu)體的第一個位置,由于結(jié)構(gòu)體內(nèi)存的連續(xù)性,因此可以通過強(qiáng)制類型轉(zhuǎn)換來直接訪問。
3、由于C語言結(jié)構(gòu)體的特性,即使子類存在與父類同名的函數(shù),父類的函數(shù)不會被子類的函數(shù)覆蓋和重寫,因此,子類與父類之間不存在函數(shù)重載。
源碼下載地址:https://github.com/embediot/my_program_test
感謝閱讀!