在了解面向对象编程思想后,这一章将着重说明对面向对象编程的三要素如何在C语言中实现,同时与C++编程相对比。
1. 封装的实现
1.1 封装在C++中的实现
封装最重要的特性是隐藏内部信息和方法,使用者一般只能看到对外提供的接口和公开信息,在面向对象的编程语言C++中,一般通过类关键字class+访问修饰符(private/public/protected)实现,例如在类的public部分定义对外可用的接口函数,但是对于重要的,不希望被外部改变的数据,放在private部分。
#include <iostream>
using namespace std;
class Adder{
public:
// 构造函数
Adder(int i = 0)
{
total = i;
}
// 对外的接口
void addNum(int number)
{
total += number;
}
// 对外的接口
int getTotal()
{
return total;
};
private:
// 对外隐藏的数据
int total;
};
int main( )
{
Adder a;
a.addNum(10);
a.addNum(20);
a.addNum(30);
cout << "Total " << a.getTotal() <<endl;
return 0;
}
运行结果得到 Total 60
以上代码就是典型的封装,main函数可以实例化Adder类,生成对象a,并调用类中的公开接口函数addNum和getTotal,准确地得到执行结果60.但是却无从知晓private变量total,也无法改变。
1.2 封装在C语言中的实现
在C语言中没有class这样方便的数据结构,不过我们同样可以使用结构体struct加函数指针的方式来实现封装这一效果。具体可以进行如下操作
首先新建add.h文件并在里面写下
typedef struct _Adder Adder;
Adder *add_init(void);//初始化
void AddNum(Adder *adder, int number);//加函数
int getTotal(Adder *adder);//得总值的函数
int Adder_destory(Adder *adder);//结束,释放空间
再新建add.c文件,进行具体的定义
#include <stdio.h>
#include <stdlib.h>
#include "add.h"
struct _Adder
{
int total;
};
Adder *add_init(){
Adder *adder=malloc(sizeof(Adder));
adder->total=0;
return adder;
}
void AddNum(Adder *adder, int number){
(adder->total)+=number;
}
int getTotal(Adder *adder){
return adder->total;
}
int Adder_destory(Adder *adder){
free(adder);
return 0;
}
从中我们可以看到,由于结构体本身并没有将内部成员变为public/private/protected的功能,实现C语言封装的基本思想就是在调用的.h文件中延迟对目标类和相关函数的定义,在对应的.c文件中再进行具体定义,同时由于结构体内部不能存放函数,所以我们要把接口函数和结构体分开,接口函数可以通过指针指向结构体,并对里面的成员进行调用。这样,在主函数中使用函数接口时,用户无从知道结构体内部成员,也可以完成执行和使用。这种用法同样适用于继承和多态。
故在主函数中我们可以直接使用
#include <stdio.h>
#include <stdlib.h>
#include "add.h"
int main(void) {
Adder *k=add_init();
AddNum(k,10);
AddNum(k,20);
AddNum(k,30);
int a=getTotal(k);
printf("The conclusion is %d\n",a);
return 0;
}
输出结果为“The conclusion is 60”,C语言的封装便实现了。
2. 继承的实现
2.1 继承在C++中的实现
在C++的继承关系中,一般把被继承的旧类成为基类,继承的类叫做派生类。一般情况下,派生类会继承基类的所有方法,在C++中,常用以下语法表示 class 派生类:访问修饰符 基类
例如
#include <iostream>
class rectangle//长方形类
{
public:
void setwidth(int w){
width=w;
}
void setlength(int l){
length=l;
}
protected:
int width;
int length;
};
class cuboid:public rectangle//长方体继承了长方形的长宽属性
{
public:
void setheight(int h){
height=h;
}
int volume()
{
return width*length*height;
}
protected:
int height;
};
int main() {
cuboid rect;
rect.setheight(5);
rect.setlength(10);
rect.setwidth(7);
std::cout << "Hello World!\n"<<rect.volume();
}
在本实例中长方体派生类继承了长方形基类,在主函数中便可以直接调用长方形类的函数接口。执行结果最终得到长方体的体积350。
2.2 继承在C语言中的实现
在C语言中,实现继承的方式主要是将老结构体直接包含在新结构体的首位,例如新建rect.h头文件,写入以下代码
typedef void* HandleRect;//定义指针型句柄
typedef void* HandleCuboid;
typedef struct _rectangle//长方形
{
int length;
int width;
}rectangle,*prectangle;
typedef struct _cuboid//长方体
{
rectangle rect;
int height;
}cuboid,*pcuboid;
HandleCuboid cuboid_create();//初始化函数
void rect_set(HandleRect rect,int length,int width);//设定长方形长宽
void cuboid_set(HandleCuboid p,int length,int width,int height);//设定长方体长宽高
int cuboid_volume(HandleCuboid p);//计算体积
void cuboid_destory(HandleCuboid p);//结束
在rect.c中对函数进行具体定义
#include "rect.h"
#include <stdio.h>
#include <stdlib.h>
HandleCuboid cuboid_create()
{
pcuboid p=(pcuboid)malloc(sizeof(cuboid));
p->rect.length=0;
p->rect.width=0;
p->height=0;
return (HandleCuboid)p;
}
void rect_set(HandleRect rect,int length,int width){
((prectangle)rect)->length = length;
((prectangle)rect)->width = width;
}
void cuboid_set(HandleCuboid p, int length, int width, int height)
{
if(p){
rect_set(&((pcuboid)p)->rect,length,width);
((pcuboid)p)->height=height;
}
}
int cuboid_volume(HandleCuboid p){
return (((pcuboid)p)->rect.width*((pcuboid)p)->rect.length*((pcuboid)p)->height);
}
void cuboid_destory(HandleCuboid p){
free(p);
p=NULL;
}
最后在main函数中完成执行计算长方体属性的赋值和计算
#include <stdio.h>
#include <stdlib.h>
#include "rect.h"
int main(void) {
HandleCuboid a=cuboid_create();
cuboid_set(a, 10, 20, 30);
cuboid_volume(a);
int s=cuboid_volume(a);
printf("%d\n",s);
cuboid_destory(a);
printf("Hello World\n");
return 0;
}
打印结果为350,这样就用C语言模拟实现了继承。
注:其中,rect.h文件中的基类结构体要放到派生类结构体的首位是为了保障二者的内存首部分配一致
3. 多态的实现
3.1 多态在C++中的实现
为了达到“继承自同一基类的不同派生类对象,在调用同一函数时,产生不同的行为”这一目的,C++语言主要采取的办法是通过在基类中引入虚函数,实现在派生类中的函数重载,例如若有以下代码
#include<iostream>
using namespace std;
class A
{
public:
void foo()
{
printf("1\n");
}
virtual void fun()
{
printf("2\n");
}
};
class B : public A
{
public:
void foo()
{
printf("3\n");
}
void fun()
{
printf("4\n");
}
};
int main(void)
{
A a;
B b;
A *p = &a;
p->foo();
p->fun();
p = &b;
p->foo();
p->fun();
return 0;
}
则在执行结果中,第一个p->foo()和p->fun()的结果为1和2,即A类对象本身的成员结果。第二个p->foo()和p->fun()的结果为1和4,这是由于当p指针的地址变化成B类对象的地址后,B类对象成员函数中的foo函数继承自A类,编译器在编译时仍然遵循基类对象中的foo函数入口地值进行查找,故结果为1。而对于fun函数,它在基类中是一个虚函数,p->fun()指向一个虚函数,p指针在调用虚函数时需要根据虚函数列表找到相应函数地址,这里即找到的是子类对象的函数地址,因此结果为4。
3.2 多态在C语言中的实现
在C语言中,我们可以参考虚函数的特性,通过引入一个虚函数表,模拟多态这一特征,例如新建一个基类正多边形及接口函数图形面积,我们可以对它的派生类正三角型和正方形使用不同的面积计算公式
同样,我们首先创建一个A.h头文件
typedef struct _Rp Rp;//正多边形
typedef struct _rect rect;//正四边形
typedef struct _tri tri;//正三角形
typedef void (*Rp_area_c_t)(Rp*);/* 函数指针类型 */
typedef void (*rect_area_c_t)(rect*);
typedef void (*tri_area_c_t)(tri*);
typedef struct _Rp_vtable_t/* 虚函数表 */
{
Rp_area_c_t area_c;
}Rp_vtable_t;
typedef struct _rect_vtable_t
{
rect_area_c_t area_c;
}rect_vtable_t;
typedef struct _tri_vtable_t
{
tri_area_c_t area_c;
}tri_vtable_t;
struct _Rp
{
char* type_name;
unsigned int class_size;
Rp_vtable_t* vt;/* 虚函数表指针存放到类中 */
unsigned int sidelength;
};
struct _rect
{
Rp ancestor;
Rp* p_ancestor;
rect_vtable_t* vt;
};
struct _tri
{
Rp ancestor;
Rp* p_ancestor;
tri_vtable_t* vt;
};
Rp* Rp_born(void);//初始化,类似C++中的构造函数
rect* rect_born(void);
tri* tri_born(void);
void area_c(void* this);//计算面积,本例中的接口函数
void Rp_die(Rp* this);//结束,类似C++中的析构函数
void rect_die(rect* this);
void tri_die(tri* this);
创建A.c文件
#include <stdio.h>
#include "A.h"
#include <stdlib.h>
#include <string.h>
static void Rp_area_c(Rp* this);
static void rect_area_c(rect* this);
static void tri_area_c(tri* this);
static Rp_vtable_t __g_Rp_vtable =
{
.area_c = Rp_area_c,
};//各类虚函数表
static rect_vtable_t __g_rect_vtable =
{
.area_c = rect_area_c,
};
static tri_vtable_t __g_tri_vtable =
{
.area_c = tri_area_c,
};
Rp* Rp_born(void)
{
Rp* this = malloc(sizeof(Rp));
this->type_name = malloc(sizeof("Rp"));
memcpy(this->type_name, "Rp", sizeof(this->type_name));
this->class_size = sizeof(Rp);
this->vt = &__g_Rp_vtable;
this->sidelength = 10;
printf("Rp_born: sidelength = 10\n");
return this;
}
rect* rect_born(void)
{
rect* this = malloc(sizeof(rect));
this->p_ancestor = Rp_born();
memcpy(&(this->ancestor), this->p_ancestor, sizeof(Rp));
this->ancestor.class_size = sizeof(rect);
memcpy(&__g_Rp_vtable, this->ancestor.vt, sizeof(Rp_vtable_t));
this->ancestor.vt = &__g_Rp_vtable;
this->ancestor.vt->area_c = rect_area_c;
this->vt = &__g_rect_vtable;
printf("rect_born: sidelength = %d\n", this->ancestor.sidelength);
return this;
}
tri* tri_born(void)
{
tri* this = malloc(sizeof(tri));
this->p_ancestor = Rp_born();
memcpy(&(this->ancestor), this->p_ancestor, sizeof(Rp));
this->ancestor.class_size = sizeof(tri);
memcpy(&__g_Rp_vtable, this->ancestor.vt, sizeof(Rp_vtable_t));
this->ancestor.vt = &__g_Rp_vtable;
this->ancestor.vt->area_c = tri_area_c;
this->vt = &__g_tri_vtable;
printf("tri_born: sidelength = %d\n", this->ancestor.sidelength);
return this;
}
void area_c(void* this)
{
if(0 == strcmp(((Rp*)this)->type_name, "Rp"))
{
return ((Rp*)this)->vt->area_c(this);// 通过虚函数表的函数指针调用实现函数
}
printf("can't calculate\n");
}
static void Rp_area_c(Rp* this)
{
printf("The area can't be calculated because we do not know the number of side");
}
static void rect_area_c(rect* this)
{
if(this->ancestor.sidelength > 0)
{
float length=this->ancestor.sidelength;
float end=length*length;//正方形面积公式
printf("rect area calculate:\n");
printf("area is = %f\n", end);
}
else
{
printf("area is = 0\n");
}
}
static void tri_area_c(tri* this)
{
if(this->ancestor.sidelength > 0)
{
float length=this->ancestor.sidelength;
float end=0.433*length*length;//正三角形面积公式
printf("rect area calculate:\n");
printf("area is = %f\n", end);
}
else
{
printf("area is = 0\n");
}
}
void Rp_die(Rp* this)
{
free(this->type_name);
free(this);
printf("Rp_die\n");
}
void rect_die(rect* this)
{
printf("rect_die\n");
Rp_die(this->p_ancestor);
free(this);
}
void tri_die(tri* this)
{
printf("tri_die\n");
Rp_die(this->p_ancestor);
free(this);
}
最后在main.c中
#include <stdio.h>
#include <stdlib.h>
#include "A.h"
int main(void) {
Rp *a =rect_born();
area_c(a);
Rp_die(a);
rect *b=rect_born();
area_c(b);
rect_die(b);
tri *c=tri_born();
area_c(c);
tri_die(c);
return 0;
}
最终打印结果分别为0,100,43.3,这样对于相同的函数area_c,我们可以得到三个不同的执行结果,成功实现了C语言的多态。