众所周知,面向对象大行其道,其中以C++、JAVA等这样的高级语言为代表,而这样的高级OO语言其实也是以C为蓝本的,以下本文就着重分析C语言如何实现面向对象。
面向对象具备3大特性:封装、继承和多态。大多数语言都具有封装特性,只不过面向对象在封装上进一步增强,并且将数据和方法进行糅合。在面向对象中顶级存在是类和对象,而在过程式语言中函数则是顶级存在。
1.定义子类和父类表现形式
C语言中没有class这样的关键字,但是可以使用struct来代替,以下为一个简单的父子类(动物--狗)定义:
父类-animal:
2.父、子类构造函数的实现
在C++、JAVA中我们通常使用new constructor()来构造对象,其本质就是将class实例化(为成员分配内存空间),而在C语言中我们直接使用malloc分配即可。
父类构造函数animal_init:
其中最后一个方法属于析构函数,是为了保证内存的正确释放,避免内存泄露。
同理,子类构造函数dog_init如下:
具体log输出入下:
4.C++和JAVA的面向对象实现对比
4.1C++的面向对象实现如下:
程序的运行结果如下:
理论上说Java面向对象会更加简洁,也更容易理解,因为java本来就是C/C++延伸出来的产物。以下为Java的OO实现:
面向对象具备3大特性:封装、继承和多态。大多数语言都具有封装特性,只不过面向对象在封装上进一步增强,并且将数据和方法进行糅合。在面向对象中顶级存在是类和对象,而在过程式语言中函数则是顶级存在。
1.定义子类和父类表现形式
C语言中没有class这样的关键字,但是可以使用struct来代替,以下为一个简单的父子类(动物--狗)定义:
父类-animal:
/* 动物类,是所有动物类的基类,也是抽象类 */
struct animal_s_ {
char *name; /*< 动物的名称 */
animal_ops_t *animal_ops; /* 动物的基本行为 */
};
/* 动物的基本行为 */
struct animal_ops_s_ {
/* 动物吃了什么食物 */
void (*eat)(char *food);
/* 动物走了多少步 */
void (*walk)(int steps);
/* 动物在说什么 */
void (*talk)(char *msg);
/*父类的析构函数*/
void (*release)( animal_t *animal);
};
上述为父类animal属性和方法的定义,二者封装在struct animal_s_中。
子类--dog:
typedef struct dog_s_ dog_t;
typedef struct dog_ops_s_ dog_ops_t;
struct dog_s_ {
animal_t base; /* 继承自 animal 基类 */
char *name; /*< 动物的名称 */
dog_ops_t *dog_ops; /* 动物的基本行为 */
/* 以下还可以添加与 dog 相关的属性和方法(函数指针), 如: */
/* char *owner; // dog 的主人 */
/* void (*hunt)(const char *rabbit); // 猎兔犬 */
};
struct dog_ops_s_ {
/* 动物吃了什么食物 */
void (*eat)(char *food);
/* 动物走了多少步 */
void (*walk)(int steps);
/* 动物在说什么 */
void (*talk)(char *msg);
/*子类的析构函数*/
void (*release)(dog_t *dog);
};
子类dog和父类类似,只不过子类struct dog_s_中还包含一个父类成员base,因此子类和父类建立了联系,我们可以通过子类访问父类,从而实现继承。
2.父、子类构造函数的实现
在C++、JAVA中我们通常使用new constructor()来构造对象,其本质就是将class实例化(为成员分配内存空间),而在C语言中我们直接使用malloc分配即可。
父类构造函数animal_init:
animal_t * animal_init(char *name)
{
printf("call base class constructor animal_init ...\n");
assert(name != NULL);
size_t name_len = strlen(name);
animal_t *animal = (animal_t *)malloc(sizeof(animal_t));
memset(animal, 0, sizeof(animal_t));
animal->name=(char *)malloc(name_len+1);
animal->name[name_len]='\0';
memcpy(animal->name, name, name_len);
animal->animal_ops=(animal_ops_t *)malloc(sizeof(animal_ops_t));
animal->animal_ops->eat=animal_eat;
animal->animal_ops->walk=animal_walk;
animal->animal_ops->talk=animal_talk;
animal->animal_ops->release = animal_release;
return animal;
}
父类构造函数中主要实现对成员变量的分配内存,并且初始化成员函数指针的指向为具体实现,具体的成员函数实现如下:
/* 基类的有关操作,如吃,走,说等等 */
static void animal_eat(char *food)
{
printf("I'm a animal, I eat %s\n", food);
return;
}
static void animal_walk( int steps)
{
printf("I'm a animal, I can jump %d steps one time\n", steps);
return;
}
static void animal_talk( char *msg)
{
printf("I'm a animal, I talk my language %s\n", msg);
return;
}
/* 基类的析构函数,不需要显示调用 */
static void animal_release(animal_t *animal)
{
assert(animal != NULL);
//注意内存泄露
free(animal->name);
free(animal->animal_ops);
free(animal);
return;
}
从上我们看到父类操作方法都是加static修饰,这个符合OO编程中的封装和隐藏的特性,意味着外界不能够直接调用该方法,该方法属于对象,如要使用必须先获得对象。
其中最后一个方法属于析构函数,是为了保证内存的正确释放,避免内存泄露。
同理,子类构造函数dog_init如下:
dog_t * dog_init(const char *name)
{
printf("call sub class constructor dog_init ...\n");
assert(name != NULL);
size_t name_len = strlen(name);
dog_t *dog = (dog_t *)malloc(sizeof(dog_t));
//创建父类animal对象
animal_t *animal = (animal_t *)animal_init("hello-dog");
memcpy(&(dog->base), animal, sizeof(animal_t));
//创建子类dog对象
dog->name=(char *)malloc(name_len+1);
dog->name[name_len]='\0';
memcpy(dog->name, name, name_len);
dog->dog_ops=(dog_ops_t *)malloc(sizeof(dog_t));
dog->dog_ops->eat=eat;
dog->dog_ops->walk=walk;
dog->dog_ops->talk=talk;
dog->dog_ops->release=dog_release;
return dog;
}
子类方法实现如下:
static void eat(char *food)
{
printf("I'm a dog, I eat %s\n", food);
}
static void walk(int steps)
{
printf("I'm a dog, I can jump %d steps one time\n", steps);
}
static void talk(char *msg)
{
printf("I'm a dog, I talk my language %s\n", msg);
}
static void dog_release(dog_t *dog)
{
assert(dog != NULL);
//析构父类对象
char *name = dog->base.name;
animal_ops_t *animal_ops = dog->base.animal_ops;
free(name);
free(animal_ops);
//析构子类对象
name = dog->name;
dog_ops_t *dog_ops = dog->dog_ops;
free(name);
free(dog_ops);
free(dog);
}
3.主方法调用
在C的main中,要创建对象只需要调用上面的xx_init函即可,main的实现如下:
int main(int argc, const char *argv[])
{
//cat_t *cat = cat_init();
dog_t *dog = dog_init("Tom");
//子类对象调用自己的方法
dog->dog_ops->eat("bones");
dog->dog_ops->walk(5);
dog->dog_ops->talk("wuang wuang wuang...");
printf("-----------------------------------\n");
//子类对象调用父类的方法,实现继承
dog->base.animal_ops->eat("bones");
dog->base.animal_ops->walk(6);
dog->base.animal_ops->talk("wuang wuang wuang...");
printf("-----------------------------------\n");
//父类对象调用子类方法,实现多态
animal_t *animal = (animal_t *)((char *)dog + sizeof(animal_t));
animal->animal_ops->eat("shit");
//析构整个对象
dog->dog_ops->release(dog);
return 0;
}
在main中调用dog_init创建对象后,就可以直接访问dog类的成员属性和方法,并且还可以直接访问父类的资源,原因是在子类的构造函数中也构造了父类,这就是继承的完整实现;另外我们还可以通过将子类对象赋值给父类指针(引用)实现多态,让父类访问子类资源;最后析构整个对象,在子类对象的析构同时,也完成了对父类的析构。
具体log输出入下:
call sub class constructor dog_init ...
call base class constructor animal_init ...
I'm a dog, I eat bones
I'm a dog, I can jump 5 steps one time
I'm a dog, I talk my language wuang wuang wuang...
-----------------------------------
I'm a animal, I eat bones
I'm a animal, I can jump 6 steps one time
I'm a animal, I talk my language wuang wuang wuang...
-----------------------------------
I'm a dog, I eat shit
release dog and animal object
从log中我们看到,子类构造函数先行构造这个和C++、JAVA是不同的,原因在于父类animal是定义子类dog中的,在子类未分配内存时,是无法直接给父类分配内存的。
4.C++和JAVA的面向对象实现对比
4.1C++的面向对象实现如下:
#include <iostream>
#include <stdio.h>
using namespace std ;
class animal{
private:
char *name; /*< 动物的名称 */
public:
/* 基类的构造函数,需要显示调用 */
animal(){
cout << "construct :animal()... " << endl;
}
/* 基类的有关操作,如吃,走,说等等 */
void eat(const char *food){
cout <<"I'm a animal, I eat " << food <<endl;
}
/* 动物走了多少步 */
virtual void walk(int steps){
cout << "I'm a animal, I can jump " <<steps <<" steps one time" <<endl;
}
/* 动物在说什么 */
void talk(const char *msg){
cout <<"I'm a animal, I talk my language "<< msg <<endl;
}
/*父类的析构函数*/
~animal(){
cout << "deconstruct :~animal()... " << endl;
}
};
class dog : public animal {
private:
char *name; /*< 动物的名称 */
public:
/* 基类的构造函数,需要显示调用 */
dog(){
cout << "construct :dog()... " << endl;
}
/* 基类的有关操作,如吃,走,说等等 */
void eat(const char *food){
cout <<"I'm a dog, I eat " << food <<endl;
}
/* 动物走了多少步 */
void walk(int steps){
cout << "I'm a dog, I can jump " <<steps <<" steps one time" <<endl;
}
/* 动物在说什么 */
/* void talk(const char *msg){
cout <<"I'm a dog, I talk my language "<< msg <<endl;
} */
/*父类的析构函数*/
~dog(){
cout << "deconstruct :~dog()... " << endl;
}
};
int main(){
//构造子类对象
dog d;
d.eat("meat");//调用子类的自己的方法
d.talk("wuang...");//调用父类的方法,实现继承
animal *am = &d;//子类对象赋值给父类,实现多态
am->walk(6);
return 0;
}
从上可以看出,C++中因为有自己的关键字class,可以直接用来封装类的属性和方法,继承时候通过子类:父类即可,相对C来说较为简洁,构造对象时候也不用手动申请内存,释放对象时候回自动调用各自的析构函数。
程序的运行结果如下:
construct :animal()...
construct :dog()...
I'm a dog, I eat meat
I'm a animal, I talk my language wuang...
I'm a dog, I can jump 6 steps one time
deconstruct :~dog()...
4.2 Java的面向对象实现
理论上说Java面向对象会更加简洁,也更容易理解,因为java本来就是C/C++延伸出来的产物。以下为Java的OO实现:
class Animal{
private String name="animal";
public void eat(String food)
{
System.out.println("I'm a animal, I eat "+food);
return;
}
public void walk( int steps)
{
System.out.println("I'm a animal, I can jump "+ steps+ " steps one time " );
return;
}
public void talk( String msg)
{
System.out.println("I'm a animal, I talk my language "+msg);
return;
}
Animal(){
System.out.println("construct Animal object...");
}
}
class Dog extends Animal{
private String name="dog";
public void eat(String food)
{
System.out.println("I'm a dog, I eat "+food);
return;
}
public void walk( int steps)
{
System.out.println("I'm a dog, I can jump "+ steps+ " steps one time " );
return;
}
/* public void talk( String msg)
{
System.out.println("I'm a dog, I talk my language "+msg);
return;
} */
Dog(){
System.out.println("construct dog object...");
}
}
public class DogTest{
public static void main(String [] args){
Dog d = new Dog();
d.eat("meat");
d.talk("ao ao ao ..."); //调用父类的方法实现继承
Animal an = d;
an.eat("grass"); //父类调用子类的方法实现多态
}
}
运行结果如下:
construct Animal object...
construct dog object...
I'm a dog, I eat meat
I'm a animal, I talk my language ao ao ao ...
I'm a dog, I eat grass
从上可以看出,Java的面向对象比C++更为简洁,它舍弃了析构函数,设计思想进一步与C脱离,语法也更精炼 ,从C++/Java这样的运行结果来看,整个面向对象的逻辑和设计非常清晰(相对C来说),不过仔细回味C的这种设计思路,我们对面向对象思想理解又可以上升一个层次了。