C语言实现面向对象

众所周知,面向对象大行其道,其中以C++、JAVA等这样的高级语言为代表,而这样的高级OO语言其实也是以C为蓝本的,以下本文就着重分析C语言如何实现面向对象。
面向对象具备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的这种设计思路,我们对面向对象思想理解又可以上升一个层次了。

代码下载链接

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值