Hello,OOC World![Chap1sec3-X][DRAFT 0.1.0518]

Posted on Tue, 17 May 2011 18:49:05 -1100

 

项目地址 OOC-GCC : http://code.google.com/p/ooc-gcc/ 源码以LGPL发布,文档为GFDL,文档相关测试用例为GPL3
 转载请注明出处
1.3 为什么不用CPP?
    CPP过于冗杂,标准不够统一.编译器干了太多的活,想弄明白需要相当的时间去折
腾,而想弄精则更难,学习曲线既长又陡. 而且很多人因为入门时拜师不慎(比如“邪恶
的M”),把C和C++混为一谈,而忽略了C++的复杂性,另外这导致了相当一部人拿着C++来
写C的代码! 这本没错,只是你不觉得这很另类很诡异么?!!!
    个人罗列几个问题,有些并不难,有的甚至都不算严格的问题,只是一些概念.但我
相信只学了两三个月的C++新手,总有没听说过或是不会的.
 
1. 引用和指针有哪些区别
2. 通过哪种方式可以屏蔽C++中默认构造函数的使用
3. 大陆编程书籍中常出现的接口这个概念,C++中有借口么或是有相关对应么,怎么用?
4. 友元这一概念存在的意义以及如何使用
5. C++结构体和类相比有哪些限制,可以在C++结构体中定义函数,静态函数,虚函数么?
6. C++重载是否适用于使用同名同参数但不同返回值的情形.
7. 解释虚表虚函数的概念
8. C++中auto_ptr(只能指针)是怎么回事儿,有何作用,怎么用?!
9. C++中多重继承如何避免名称冲突
10.解释下隐式类型转换
11.C++可以用哪些类型转换措施,有静态动态之分么,向上向下都允许么,如果有限制硬是
使用会产生哪类问题.
12.我想定义个函数指针指向某类中的一个方法,怎么做?如果是指向类中的一个虚方法或
是静态方法,有却别么?
13.virtual和rtti有何关系
14.如何使用c++中的rtti
15.类中的函数,虚函数,静态函数是按类来分配还是按实例来分配.
16.类中的函数,虚函数,静态函数的具体内存分布大致是什么样的,先后顺序如何.
17.不同类的函数,虚函数,静态函数是在统一的一大块区域中分配,还是离散的毫无关联
的.
18.如何获得一个类的虚表指针.
19.如何重载或是能否重载+,++,<<,=,==这些运算符,重载时有限制么?
20.如何或是能否声明一个函数指针,其参数或返回值有模板参数.
21.rtti的使用有何副作用
22...bla..bla..
 
    这里面大部分问题我还是知道或是有印象的,有一小部分我也比较好奇.不过上面的
问题还不涉及C语法中相对复杂的东西(比如复杂指针的使用). 总之想真正掌握C已经不
容易了,再要引入这么多东西真是有点吃饱撑的感觉了,总之我表示我的脑容量有限,估计
从一点不知道开始学, 要花很长时间来学,而且如果用的不多或是一段时间不用,可能又
忘了,可能细节就往干了,可能要从头再来.人生苦短,如果你真想学的又快又好又高级的
语言个人建议你还是用python吧.如果你要编译后的高效,不妨试试go语言.或者仅仅学
习C语言,再学习下这份简单的OOC教程.
 
    开始下一节前说一下,本手册“热身运动”一节介绍了一种简单且对称的OO风格,
“伸展运动”介绍了一些宏来简化这一部分的代码. 当然这两节中所涉及的都比较简
单,主要是为了便于大家的理解和使用.后面的章节则会循序渐进的加强这些代码! 从下
节开始真正就有代码了,如果你是从头看到这里,相信你已经受够我的啰嗦了.
 
1.4 一个结构体+一个函数?!
#include <stdio.h>
struct A {
    int a;
};
void iniA(struct A *THIS){
    THIS->a=100;
}
int main(){
    struct A obj;
    iniA(&obj);
    printf("the value of obj's a is %d\n",obj.a);
    return 0;
}
    个代码?干啥的?
    个例子很是简单,只涉及一个结构体的定义和一个相关初始化函数,但在这份手册
中却有着非同一般的意义,而一切用C去模拟OO的东西也源于此.
    谓面向对象的编程,其本质目的是把数据一层一层封装起来从而使代码看上去更
加易懂,更易维护.单从封装数据的角度来看,C语言中的结构体足矣.而封装之后如何
使用它,自然要涉及所谓的构造函数,对应到上面的代码,就是那个名为iniA的函数啦.
 
1.5 不得不说的函数指针
    面的代码似乎过于简单了,肯定会有人说这不是面向对象的编程,从个人观点来看,
面向对象只是一种思想,而这种思想也主要用在数据的封装上,而上面的代码其实已经
体现了一点点.而下面将要做的就是一点点对其加强.
#include <stdio.h>
struct A {
    int a;
    void (*showA)(struct A *);
};
    static void A_showA(struct A *THIS){
    printf("the value of obj’s a is %d\n",THIS->a);
}
void iniA(struct A *THIS){
    THIS->a=100;
    THIS->showA=A_showA;
}
int main(){
    struct A obj;
    iniA(&obj);
    obj.showA(&obj);
    return 0;
}
    C语言中没有C++所谓的“方法”,其实也没有这个必要,因为本质上那就是一个函
数.还有些语言只有所谓的“过程”,他们本质上都是一样的(后面的文字不再区分这三个
概念,如遇到都理解成C语言中的函数就好,同样后面关于类和结构的称呼只要是在C语言
中本质也是一样的),只是细节上有些许差异. 另外要说的是C语言的结构体是可以包含
函数指针的,这一特性也使得用C去模拟对象变得可行且有意义.比如上面的代码就演示如
何使用“封装”在结构体重的函数指针.
 
1.6 栈内存vs堆内存
    针对于初学C的人来说是个难点,而更要命的是堆内存和栈内存的问题(如果你仍不
清楚这个问题,那么先回去补一补C语言的基础,我在这里不在赘述,因为这一块是会者不
难,但是如果不会讲起来可啰嗦了). 个人认为国内学生初学C语言时容易犯这样的错误
很大的一个原因就是入门教材一直用的是国内最流行的那本...
    于模拟OO的编程,分清堆内存和栈内存是很有必要的,特别是堆内存的使用会使你
的程序具备一定的“动态”特性. 但使用堆内存有利也有弊,特别对于C乃至C++这样的
语言来说,使用不当就杯具了,内存泄露这样的问题也源于此.下面再看一段代码
#include <stdio.h>
#include <stdlib.h>
struct A {
    int a;
    void (*showA)(struct A *);
};
static void A_showA(struct A *THIS){
    printf("the value of obj’s a is %d\n",THIS->a);
}
void iniA(struct A *THIS){
    THIS->a=100;
    THIS->showA=A_showA;
}
struct A * newA(){
    struct A * mem=(struct A*)malloc(sizeof(struct A));
    iniA(mem);
    return mem;
}
int main(){
    struct A * obj=newA();
    obj->showA(obj);
    free(obj);
    return 0;
}
    里要说明一下,iniA和newA这两个函数是为了模拟构造函数,本质上iniA进行真正
的初始化,newA实际上在外边又封装了一次,在堆内存上分配这个结构体. 这两个函数让
我们可以按需在堆上(newA)或栈上(iniA)声明一个结构实例,其实也就是本手册要模拟
的所谓的“类”.
    得注意的是上面的代码有一点很不爽的地方就是最后还要free一下堆内存,而与之
对应的malloc则放在了newA这个函数中, 这样的代码让我很是不爽,所以下面继续完善,
引入“析构”函数的模拟.
 
1.7 对称之美
    C++中有构造也有析构,只是编译器替我们做了太多的工作,以致有些初学者对此毫
不知情.这是我反感C++的原因之一,它掩饰了太多的东西,虽然有时看上去简单了, 但如
果你不了解底层,很多东西会觉得很不明晰,也因此这是门初学更容易犯错的语言.
    面要说的就是析构函数的模拟了.有了构造,再有析构,代码才会有对称性,至少看
上去才更加的OO.
#include <stdio.h>
#include <stdlib.h>
struct A {
    int a;
    void (*showA)(struct A *);
};
static void A_showA(struct A *THIS){
    printf("the value of obj’s a is %d\n",THIS->a);
}
void iniA(struct A *THIS){
    THIS->a=100;
    THIS->showA=A_showA;
}
struct A * newA(){
    struct A * mem=(struct A*)malloc(sizeof(struct A));
    iniA(mem);
    return mem;
}
void finA(struct A *THIS){
    THIS->a=0;
    THIS->showA=NULL;
}
void delA(struct A **THIS){
    finA(*THIS);
    free(*THIS);
    (*THIS)=NULL;
}
int main(){
    struct A * obj=newA();
    obj->showA(obj);
    delA(&obj);
    printf("is obj NULL ?\n%s\n",obj==NULL?"True":"False");
    return 0;
}
    finA函数比较简单,其对应构造用函数iniA,是真正的析构部分. 而delA则对
应newA,仅仅是又把finA再次封装了一下,但要注意的是这个例子中用到了指向指针的
指针(其实也可以普通的指针), 这样做有一个好处,比如上面的代码,在delA执行之
后,obj已经指向NULL了,如果我们再次使用已经执行过析构的obj对象,则错误会比较明
显. 最后的那个打印函数也是为了验证obj释放后置零这一特性.
    在趁热打铁,总结一下本手册以后常用的几个重要函数(假定我们有一个类名
曰Class),下面为了加强记忆放在一起总结一下.
 
1. void iniClass(Class *THIS); →→ 用于栈上对象的构造,
    ini作为一个prefix(前缀),表示初始的意思,相关词汇initialization,initiate,initial
 
2. void finClass(Class *THIS); →→ 用于栈上对象的析构,
    fin作为一个prefix(前缀),表示终止的意思,相关词汇finish,final
 
3. Class * newClass(void); →→ 用于堆上对象的构造,是iniClass的封装,
    new作为一个prefix(前缀),表示新建的意思,相关词汇new,neo-系部分词汇,
    在某些语言中直接  对应new这个KeyWord
 
4. void delClass(Class **THIS); →→ 用于堆上对象的析构,是finClass的封装,
    del作为一个prefix(前缀),表示删除的意思,相关词汇delete,de-系部分词汇,
    在某些语言中直接对应delete这个KeyWord
 
1.8 继承与多态
    C++的编程中关于“数据封装”有两种关系很是重要,一是继承关系,另一是包含关
系.本质上其实一样的,都是“包含”, 只不过C++的编译器再度不辞劳苦的帮我们做了
点工作.让所谓的继承关系使用起来似乎容易了些.
    C语言本身没有继承方面的语法糖,但是包含关系应该是所有的计算机语言都能描述
的,因为汇编都可以,其它更高级的语言自然也能描述. 在C语言中,一种常见的模拟继承
的做法是把父类放在子类的首部. 具体见下面的代码.
#include <stdio.h>
#include <stdlib.h>
struct A {
    int a;
    void (*show)(void *);
};
static void A_showA(struct A *THIS){
    printf("the value of obj’s a is %d\n",THIS->a);
}
void iniA(struct A *THIS){
    THIS->a=100;
    THIS->show=(void *)A_showA;
}
struct B {
    struct A A;
    int b;
};
static void B_showB(struct B *THIS){
    printf("the value of obj’s a is %d\n"
    "the value of obj’s b is %d\n",
    THIS->A.a,THIS->b);
}
void iniB(struct B *THIS){
    iniA((struct A*)THIS);
    THIS->b=200;
    ((struct A*)THIS)->show=(void *)B_showB;
}
int main(){
    struct B obj;
    iniB(&obj);
    struct A *s=&(obj.A);
    s->show(&obj);
    return 0;
}
    为上面这段代码只使用了栈内存,而对象本身也没有指向堆内存的指针,所以为了
简便只保留了“四大函数”中的ini系列. 同时也有一些细节上的修改,比如A中的函
数showA改为show,这是为了演示如何在子类中重写父类的函数.
    们假定子类B继承了父类A,自然用C的包含关系去模拟继承关系时应将父类放在首
部(当然也有些特殊应用要求统一放在尾部),这样做的一个好处是方便指针型的强制转
换. 而在使用的时候这种子类指针型强制转型成父类函数指针型的做法一般被称为“向
上转型”,在使用的时候我们用的其实是一个父类指针,这样的“数据抉择” 体现了“多
态”的思想.在父类中定义方法(接口) ,子类中具体实现.使用时则通过父类的形式来
调用.
 
1.9 贴心的匿名结构体
    一段代码相比会让大家觉得有些丑陋,因为用到了不少强制转换.其实现代的
主流C编译器都支持匿名结构体(anonymous struct或unamed struct)这一特性.这
样用C去模拟OO的继承关系时就更加舒服了.下面的代码和上一段代码功能完全相同,但得
益于匿名结构体这一特性,看上去更加悦目了.
#include <stdio.h>
#include <stdlib.h>
struct A {
    int a;
    void (*show)(void *);
};
static void A_showA(struct A *THIS){
    printf("the value of obj’s a is %d\n",THIS->a);
}
void iniA(struct A *THIS){
    THIS->a=100;
    THIS->show=(void *)A_showA;
}
struct B {
    struct A;
    int b;
};
static void B_showB(struct B *THIS){
    printf("the value of obj’s a is %d\n"
    "the value of obj’s b is %d\n",
    THIS->a,THIS->b);
}
void iniB(struct B *THIS){
    iniA((struct A*)THIS);
    THIS->b=200;
    THIS->show=(void *)B_showB;
}
int main(){
    struct B obj;
    iniB(&obj);
    obj.show(&obj);
    return 0;
}
    名结构体的使用可以让我们省去不少强制转换的麻烦.但是,有一个很大的问题就
是当有重名成员时到底如何处理,是直接报错, 还是不额外多分配重名成员所占的内存,
还是额外分配重名成员所占的内存.关于这个问题GCC4.5和GCC 4.6的处理方式多少有些
不同, 具体可以参见GCC testsuite中关于anonymous struct的部分.
这里多啰嗦几句,GCC原生支持的如下这种匿名结构体,暂用Type A简记
stuct B{
    struct A{
        int a;
    };
    int b;
};
注意A是在B中声明的
而据GCC官方文档所说,M$的编译器则原生支持如下形式的,暂用Type B简记
struct A{
    int a;
};
stuct B{
    struct A;
    int b;
};
     果想让GCC支持Type B这种类型的匿名结构体,4.5版本在编译时须加
上-fms-extensions, 而4.6版本则加上-fplan9-extensions,比如上面的代码如果
要用GCC编译,就应加上这些选项.
 
1.10 休息一下
    这一节,仅仅是回顾与总结
    个人认为前面已经大致说清了OO思想中关于“数据封装”部分最为重要一些东西.
而要用C来模拟OO,首先要解决则是如下的一些问题
 
1. 结构定义        2. 构造方法
3. 析构方法        4. 包含关系
 
 
    果你逐行的看完前面的代码,一定会觉得很累,虽然他们实现的功能很简单,仅仅是
打印一两个数值而已.由此看来上面的工作似乎时间扯淡无比的事情啊!!!
    错,上面的代码的确无比扯淡,很适合我们吃饱了撑着的时候去研究,也省的吃吗丁
啉了.但是,这种一层层的封装本身还是很有必要的, 只是我们不应重复的去敲这么多的
代码,也不应去记忆那么多繁琐的细节.
    得庆幸的是C语言是支持宏定义的,想想<<电子世界争霸战>>中那些华丽的nested
macros!但是程序毕竟不是时装秀,宏的滥用也绝不是件好事. 也因此很多更为高级的语
言抛弃了这一可能带来灾难错误的特性.但是不可否认,一旦某些宏称为一种约定,则是既
简洁有好用的.
    下面的章节中, 我会使用三个宏CLASS,CTOR( 对应CONSTRUCTOR) 和DTOR( 对
应DESTRUCTOR),通过它们来完成类本身的设计, 以及构造函数和析构函数的设计,进而
简化上面的OO风格所带来的冗余代码. 如果把这三个宏展开来看,和本章的代码并无差
异.

OOC与CPP生成代码大小对比

Posted on Sun, 15 May 2011 22:45:46 -1100

今天做了一个简单的对比测试,看一下gcc模拟oo与g++生成代码(strip无用符号后)大小的比较

下面附上测试demo,先来OOC的

 

/*
 * test_Animals.c
 *
 *  Created on: 2011-5-16
 *      Author: Jesse Meng [pingf0@gmail.com]
 */
#include "OOStd.h"

CLASS(Animal){
	char *name;
	STATIC(Animal)
	vFn talk;
};
int Animal_reload(Animal *THIS,char *name){
	THIS->name=ALLOC(strlen(name)+1);
	memcpy(THIS->name,name,strlen(name));
	return 0;
}
int Animal_unload(Animal *THIS,void *PARAM){
	FREE(THIS->name);
	return 0;
}
ASM(Animal,Animal_reload,Animal_unload,NULL,NULL)

CLASS_EX(Animal,Cat){
	STATIC_EX(Animal,Cat)
};
static void Meow(Animal *THIS){
	printf("Meow!My name is %s!\n",THIS->name);
}
int Cat_reloadSt(StAnimal *THIS,void *PARAM){
	THIS->talk=(void *)Meow;
	return 0;
}
ASM_EX(Animal,Cat,NULL,NULL,Cat_reloadSt,NULL)


CLASS_EX(Animal,Dog){
	STATIC_EX(Animal,Dog)
};
static void Woof(Animal *THIS){
	printf("Woof!My name is %s!\n",THIS->name);
}
int Dog_reloadSt(StAnimal *THIS,void *PARAM){
	THIS->talk=(void *)Woof;
	return 0;
}
ASM_EX(Animal,Dog,NULL,NULL,Dog_reloadSt,NULL)

int main(){

	Animal *a[]={
			///////////////////////////
			(void*)newDog("Jack"),
			(void *)newCat("Lily"),
			(void*)newDog("Mike"),
			(void *)newCat("Lucy"),

//       100个,这里懒得写了,因为测试时是cccv的 .....

	};

	int i=0;
	//StAnimal *f=ST((Dog*)(a[0]));
	StAnimal *f=ST((Dog*)(a[0]));
	for(i=0;i<100;++i){
		f->talk(a[i]);
	}

	for(i=0;i<100;++i){
		//delDog((Dog**)&(a[i]),NULL);
		delDog((Dog**)&(a[i]),NULL);
	}
	return 0;
}

下面来看下CPP的

 

#include <iostream>
#include <string>

using namespace std;

class Animal
{
        public:
        Animal(const string& name) : name(name) {}
        virtual void talk() = 0;
        const string name;
};

class Cat : public Animal
{
        public:
        Cat(const string& name) : Animal(name) {}
        virtual void talk() { cout<<"Meow!Myname is "+name<<endl; }
};

class Dog : public Animal
{
        public:
        Dog(const string& name) : Animal(name) {}
        virtual void talk() { cout<<"Woof!Myname is "+name<<endl; }
};
 
int main()
{
        Animal* animals[] =
        {
        		///////////////
        	new Dog("Jack"),
        	new Dog("Mike"),
                new Cat("Lily"),
                new Cat("Lucy"),
//     这里同样100个 .....

        };

        for(int i = 0; i < 100; i++){
            animals[i]->talk();
        }
        for(int i = 0; i < 100; i++){
        	delete animals[i];
        }
        return 0;
}

尽管我已经将OOC-GCC写的足够简单了,但还是比CPP写出来稍长一点(感觉还好一般20行左右)

不过编译出来的我还是比较满意的,

上面两段代码输出一致,风格一致,都是最基本的形式.

类设计为一个基类,两个近似的继承类,100个任意继承类的对象.

先生成100堆对象,执行100次各对象的基本方法,然后逐一销毁.

实际测试后cpp编译出来的体积为32246,通过OOC-GCC宏出来仅为21518(而这里面还包括内存分配日志,时间测试等函数的调用)

这个多少有点出乎我的意料,因为OOC-GCC通过宏引入了相当多的辅助函数,比如上面的实际用到的关于对象辅助生成的函数应该至少有24个,而且每个体积也不小的.

 

Hello,OOC World![Chap1Sec1-2][DRAFT]

Posted on Sun, 15 May 2011 18:17:32 -1100
项目地址 OOC-GCC : http://code.google.com/p/ooc-gcc/ 源码以LGPL发布,文档为GPL3
 转载请注明出处
1.1 什么是OOC
 
    OOC即Object Oritened C,当然也有些人称之为OOPC,多出来的P指Programming.
而OO指面向对象,当然有OB(Object Based)一说,就是基于对象,在这两个名词之间不多
啰嗦,一句话概括的化,OO比OB更“纯”一些,尽管这样概括并不够准确.
本手册所说的面向对象的C主要涉及两个方面,风格与简化!
 
OOC涉及的两个主要方面
 
1. 采用何种面向对象的编码风格
2. 如何简化因为使用某种编码风格而产生的额外代码
 
    一方面不同的人会有不同的见解,虽然千差万别,但却大同小异. 第二方面主要的
实现机制还有两种,一种是通过独立的预处理程序来生成额外的代码,另一种是用C语言本
身支持的宏特性来简化. 前者可以让代码获得更多的特性,后者可能无法很好的简化部
分因为OO风格而产生的冗余代码,但是使用起来却更加方便.
    然准确一些的话OOC还应该有一个较为完整的类型系统,比如GObject库这方面做
的比较全,但是个人认为其前两方面做得不怎么样,所以用纯C开发起来并不方便.
    手册主要是围绕一个名为OOC-GCC的小项目展开的,自然介绍的是其中采用的OO风
格,而这个项目本身针对更多是纯C的开发, 并且在使用上尽力做到简易,所以采用了宏来
完成“偷懒”的工作.
 
1.2 面向对象中常涉及的概念
 
1. 类,成员,接口与方法
2. 包,模块,命名空间,访问权限
3. 重载,重写,继承,多态,泛型,反射与自省
 
    面写的东西可能不全面,但即使这些展开说也多了去了,而且具体到某些细节我
的理解也不一定全都正确. 所以一来为了简洁,二来为了不去一不小心误人子弟.这些东
西我争取用一两句话来介绍.而因为本手册是针对C来使用的,也会给出一些在C语言里的
对应.
    及类成员是啥我就不多说了,就像C中的结构与结构成员.接口与方法到C里面都指
函数.tmd也不知哪个龟孙开始提出的OO,整出这么多概念来. C语言常用结构体来模拟
类,自然普通结构成员就对应类成员,而类的方法采用函数指针来模拟,个人的理解是这种
指针的模拟都对应接口或是虚方法. 当我们把某个函数指针指向具体的函数,就相当于
真正的实现了某个接口或是将虚方法化虚为实.
    C语言里没有什么包,命名空间等这类东西,不过模块化的C一般就是采用独立文件
的编写方式. 常把一个模块放在一个.c和一个.h文件中,不同的模块经过编译产生了
众多的.o文件,再通过链接将各个.o统一起来. 关于访问权限,一般的OO语言常涉
及public,private,protected这些,主要是限制是类中访问与类外访问以及继承时的权
限. C语言可没这么多条条框框,大体上除了局部括号内受限以及static关键字限定某些
文件内访问.其它的都很自由,你自己掌控一切.
    于继承,我见过两种描述,当然个人觉得这两种说法并不冲突,只是侧重点不太
相同. 一种感觉重在描述“数据抉择”这一块,就是如何去使用数据. 其说法是子类
使用父类的方法这是继承,而子类实现父类的接口在“向上转型”成父类再来使用以获得
不同的实现这是多态(的一种体现). 另一种关于继承的解释重在“数据封装”,就是继承
就是子类包含父类的东西,从本之上说是一种包含关系.
    人比较喜欢从“数据封装”方面来阐述继承,因为这更和字面意思,和现实生活也
更加对应.比如儿子继承母亲的某些性格特征, 继承遗产等等,侧重的也是“数据封装”.
当然现实中的继承远比程序中的复杂,而且很多现实继承是那种父类大,子类小,子类局部
继承的情况. 而程序中的继承基本都是由小到大的模式,这就要求我们进一步分析并
采用一些模式来完成数据的抽象.
    “数据封装”方面来看代码中的继承,大体也有两种.一种是一般成员继承, 这
种就是前面从“数据抉择”上的通过子类使用父类,另一种是接口等的继承,这种具体
实现是在子类中完成的, 但使用时以父类的形式使用,如前所述“数据抉择”上通过父类
使用子类的情形,一个父类定义接口,有很多子类的不同实现,这就体现了多态这一概念.
用C语言来完成“数据封装”上的继承,一种常规方式是子类结构体包含父类结构体
的形式.并且把父类结构体放在子类结构体的首部, 这样做的一个好处是当我们在堆上分
配一个对象,只需强制转换其指针到父类指针形态,就可以按照父类来使用了,当然这种
使用本身看上去是通过父类使用子类,也就是多态(的一种).
   接下来说一下重载重写等相关的定义,这一块我直接用英文了(因为我有些整体相当不
错的书的中译这一块翻译上总有差异), 就是overload,override,overwrite(还有人
有时还会用到一个词overlap).关于这一块我想多说几句,就是关于这几个词不同语言上
也可能有一些细微的差异. 而且很多所谓的OO语言对这一块虽然支持,但并不完美,尤其
体现在overload运算符以及返回值的处理的支持上. 简单说overload是允许某类中名
称相同但参数等不同的方法的存在,这需要编译器或解释器能够通过传入参数等进行判断
进而确定到底是用哪个方法. override和overwrite都是子类中有和父类重名的方法,
不同的是overwrite会抹杀掉父类的方法,而override只是隐藏了父类的方法. 从C语言
来说,因为方法都是用函数指针来模拟,所以这些都可以模拟,但是都不是自动的,需要相
当的额外代码来辅助,甚至是需要构件一个抽象类作为中间层来模拟.
    overXXXX系的对比着可以说一下的就是泛型,针对的是几个不同参数的方法,让编
译器根据参数等来判断.泛型通常是只写一个方法,但是参数采用抽象的形式, 编译器通
过类型推衍等来判断具体是什么数据(当然这么概括泛型是为了简便,可能不太全面).
    后说一下反射(reflection)和自省(introspection),这是更加动态的机制,可能
需要类型系统等的支持.这两个概念有些人分不清,也有些人干脆把他们揉在一起来理解,
不过这也不影响,只要知道其动态特性怎么用就好. 简单的说反射式通过一个动态的字
符串,获得相应的类或方法等.自省则是通过一个已有的但未知类型的实例直接获得其相
关信息或是直接使用.C99好像支持了__TYPEOF__(gnu套件一般用小写的typeof),有点
自省的意思了. 但是去模拟反射却不容易,需要额外的数据结构以及约定好的类型系统
库才行(个人觉得).还有要说的是反射和自省看似美丽但是用起来代价也是不小的,比如
额外引入的数据处理对性能影响比较大, 很多时候使用这些机制会将代码变得冗长难懂,
类型不安全等.
    广义上讲多态就是只从多种数据中选择需要的适合的来用,从这个角度来说它与前面
说的“两大部分”中“数据抉择”部分直接对应了.这样理解的话,它就包含了前面说
的overload,泛型,反射,自省等等. 而广义上的继承则也占据了“数据封装”中的相当
一部分.因此某种意义上说广义的继承广义的多态就是OO的核心(不要动不动就下定
义,所以我加了个限定词“某种意义上”)
    C去模拟OO,其实无论“数据封装”或是“数据抉择”哪一部分都能模拟,只是模拟
的时候额外的代码量也是相当可观的. 而单从用宏来简化这些额外代码上,主要能简化
的就是“数据封装”的部分,“数据抉择”部分如果想要简化最好还是通过间接代码生成
的方式. 也因此OOC-GCC中的宏主要是在解决“数据封装”方面的问题,对于“数据抉
择”方面的OO模拟还是要写不少额外的代码(最近测试了一些和完全对应的C++代码
编译出来大小相差不大,相对还小些,特别使用到堆内存的时候).
 
注意
 
上面的概念可能有不少地方不够准确或是存在错误, 如果有所发现, 还请联系
我pingf0@gmail.com. 另外后面的章节尽量不再解释和OO相关的抽象概念,如有需要,
请参阅此节.
 

恶心的GObject[Part I][v0.1]

Posted on Sat, 18 Sep 2010 05:43:43 -1100

虽然GObject最初是为了简化C语言的OO开发而设立的,但毕竟C不是天生的OO语言,而其中有太多抽象的东西,反而让其难以入门.....

虽然以前花了很长时间去研究这东西,但限于当时的水平,一些东西并没有弄透彻,甚至有不少错误....

因为前段自己尝试用C语言来模拟OO,积累了不少经验,最近偶然又回顾了下GObject,一些似懂非懂的概念明朗了许多.....

Part I. GObject中最恶心的概念

下面罗列几个概念,都是难以弄明白的....

GCClosure,GClosure,GClosureMarshal,

GClosureNotifyData,GClosureNotify,SignalAccumulator,GSignalAccumulator...

晕了吧,估计不少学了一段Gtk的人对上面的概念还是一知半解...

在具体展开前,我们需要知道GObject是怎么描述类,以及如何进行类型的判定和转换的....

我们知道C语言进行OO编程的时候要大量用到结构体函数指针,GObject也不例外,

而GObject中每一个类也是一到多个结构体[多个结构体是干嘛的后面会解释],GObject本身用多个int型变量来表示各个类型,类型的检测就是一个int型变量的检查,而类型的转换也是根据这个int型的变量[需要判断是否可以转换],进行结构体或指针的强制转换.

GCClosure,GClosure就是两个结构体!

前面的GCClosure多了个C,自然是C语言用的,不难想到GCClosure是GClosure一个封装,提供针对C语言的一种绑定,自然,GObject可以绑定到多种语言,比如Python,Ruby等等,目前绑定的比较好的是C[原生]/CPP/VALA/PYTHON

仔细看下

struct _GCClosure
{
  GClosure	closure;
  gpointer	callback;
};

 

注意,_GCClosure会用typedef在定义成GCClosure,这一点后面都类似,以后不再特殊说明,

通过GCClosure定义,不难发现其封装了一个GClosure的同时,还有一个callback,猜到了吧!GCClosure的主要功能就是回调!

估计不够淡定的朋友会觉得这也太扯淡了,回调的本质就是一个函数指针,包装这么多层干嘛?GObject这样设计自然有它的道理,

我们知道不同语言有不同的类型,比如C语言里就没有原生的String,

而即便是C语言我们仅在C语言,我们在定义回调函数时也可能要用到不同的形式,比如有的返回int,有的返回void,我们怎么区别这些呢?

不急,待我一步一步分析,

GOBject中GClosureMarshal是一个函数指针,但是要注意它是用来定义回调函数类型的而不是直接调用的!

typedef void  (*GClosureMarshal)	(GClosure	*closure,
					 GValue         *return_value,
					 guint           n_param_values,
					 const GValue   *param_values,
					 gpointer        invocation_hint,
					 gpointer	 marshal_data);

 

看到它的参数没有,指定了回调返回值类型,回调函数参数的个数等等

而每一个GClosure都要有一个绑定的GClosureMarshal,

具体来看看

struct _GClosure
{
  /*< private >*/
  volatile      	guint	 ref_count : 15;
  volatile       	guint	 meta_marshal : 1;
  volatile       	guint	 n_guards : 1;
  volatile       	guint	 n_fnotifiers : 2;	/* finalization notifiers */
  volatile       	guint	 n_inotifiers : 8;	/* invalidation notifiers */
  volatile       	guint	 in_inotify : 1;
  volatile       	guint	 floating : 1;
  /*< protected >*/
  volatile         	guint	 derivative_flag : 1;
  /*< public >*/
  volatile       	guint	 in_marshal : 1;
  volatile       	guint	 is_invalid : 1;

  /*< private >*/	void   (*marshal)  (GClosure       *closure,
					    GValue /*out*/ *return_value,
					    guint           n_param_values,
					    const GValue   *param_values,
					    gpointer        invocation_hint,
					    gpointer	    marshal_data);
  /*< protected >*/	gpointer data;

  /*< private >*/	GClosureNotifyData *notifiers;

};

 

注意,官方网上的Manual可能描述上过老,容易误导初学者,上面的源自GOjbect源码

罗列上面代码并不是大家一个一个分析的,只是要大家知道前面我说的一个Closure绑定一个Marshaller.....

我前面说过GClosureMarshal是用来定义回调函数类型的,不是用来调用的,GObject中真正的回调是marshal_data[够抽象的,这个是一个void *指针] ,关于这个我不多说什么[因为自己也没时间研究],因为一般不常用,主要用于其它语言间的绑定.

对于C语言,GObject本身已经提供了很多现成的C语言专用的Marshaller,下面给出一个最简单的C专用的Marshaller

/* VOID:VOID (./gmarshal.list:26) */
void
g_cclosure_marshal_VOID__VOID (GClosure     *closure,
                               GValue       *return_value G_GNUC_UNUSED,
                               guint         n_param_values,
                               const GValue *param_values,
                               gpointer      invocation_hint G_GNUC_UNUSED,
                               gpointer      marshal_data)
{
  typedef void (*GMarshalFunc_VOID__VOID) (gpointer     data1,
                                           gpointer     data2);
  register GMarshalFunc_VOID__VOID callback;
  register GCClosure *cc = (GCClosure*) closure;
  register gpointer data1, data2;

  g_return_if_fail (n_param_values == 1);

  if (G_CCLOSURE_SWAP_DATA (closure))
    {
      data1 = closure->data;
      data2 = g_value_peek_pointer (param_values + 0);
    }
  else
    {
      data1 = g_value_peek_pointer (param_values + 0);
      data2 = closure->data;
    }
  callback = (GMarshalFunc_VOID__VOID) (marshal_data ? marshal_data : cc->callback);

  callback (data1,
            data2);
}

看到了吧那个callback可以指向marshal_data,而marshal_data也是一个函数指针,一般来说它应指向GCClosure中的callback,只是真正调用时是从GClosure中来调用而已.

而原始的GClosureMarshal本质上也是这样的......

注意VOID_VOID表示回调返回VOID,额外的参数为VOID[就是没有额外的参数]

 

好了,分析的差不多了,有了上面的基础我们就不难理解signal的链接与回调

对于C语言,

一个SIGNAL在CONNECT时,实际创建了一个C语言的Closure,并绑定了一个C语言的Marshaller,而C语言Marshaller的类型,要在SIGNAL 创建时来定义[注意常见的Gtk中g_signal_connect的常见SIGNAL都是已经定义好回调的类型的,所以你在定义已知信号的回调是要按类型定义],回调函数是在GCClosure中定义的,但本质上是通过GClosure来调用....

简单表示下

GCClosure{
  GClosure {
      ...
     其它很多和类型相关的东西; 
      ...
      GClosureMarshal marshal; //这是个函数指针,
      //这个的参数中有一个要指向下面定义的callback
     ...
  }
 void *callback;
}

而我们在ui使用回调是将SIGNAL发送[EMIT]出去,若在signal队列中有绑定的HANDLER[就是我们的callback],就由其处理...

signal的发送非常复杂,这里不再赘述[如果要研究这个,可以从SignalNode结构入手].....

我们仅需要知道[对于C语言]一旦找到了相应的SIGNAL的ID,就会找到指定GClosure[不是GCClosure],然后通过其找到GClosure并调用其中的marshall函数[传入的参数有何callback相关的],进而调用回调函数.

如果是C语言,可以调用类似g_cclosure_marshal_VOID__VOID的一个定义好的marshal,其内部会调用对应的指向callback的函数[具体参考上面那个C语言VOID_VOID型marshal,调用了传入marshal_data函数指针,也就是我们绑定到GCClosure的callback指针]

好了最扯淡的部分结束了,还有四个[GClosureNotifyData , GClosureNotify , SignalAccumulator , GSignalAccumulator]没说,不过这四个比较简单,理解起来也容易些,下面为了节约时间,一并说了.

GClosureNotifyData 是一个结构体,它包含了一个函数指针GClosureNotify

SignalAccumulator 也是一个结构体,他也包含了一个函数指针GSignalAccumulator

具体代码

struct _GClosureNotifyData
{
  gpointer       data;
  GClosureNotify notify;
};

typedef void  (*GClosureNotify)     (gpointer    data,
                     GClosure   *closure);

typedef struct
{
  GSignalAccumulator func;
  gpointer           data;
} SignalAccumulator;

typedef gboolean (*GSignalAccumulator)  (GSignalInvocationHint *ihint,
                     GValue            *return_accu,
                     const GValue          *handler_return,
                     gpointer               data);

上面的data即回调函数传入的参数

如果我们回调的参数中有定义在堆上的,并且调用完要释放,那么应该通过绑定一个GClosureNotify型的指针,而GSignalAccumulator指针则是用来控制某个信号响应后是否继续向下传递用的.

注意: GClosure中有指向GClosureNotifyData的指针,而SignalNode结构体中有个指向SignalAccumulator结构体的指针.

 

好了,本篇就到此为止,基本分析了GObject中最难理解的概念,因为写的仓促和个人水平有限希望发现问题的朋友能够给予指正.

我的邮件地址 pingf0@gmail.com

OOC-Embeded_C语言模拟对象的应用

Posted on Sun, 05 Sep 2010 01:21:10 -1100

其实还是在折腾如何用C语言模拟对象的机制,

现在这个版本个人该觉还是不错的,可以用GCC以及KEIL,IAR等附带的编译器来编译

另外还在此基础上实现了常用的数据结构,包括下面这些,

链表[包括单链表,双链表,循环链表],堆栈,队列,集合,

哈希表[包括链式和开放地址式],二差树[普通二叉树,平衡二叉树],

堆/优先队列,图......

项目地址:

http://code.google.com/p/ooc-embeded/

提供win下编译好的DLL,源代码可通过SVN获取

核心代码如下,

/**
 * OOC-Embeded 
 * version : see the log file
 * status  : beta 
 * author  : Jesse Meng 
 * Blog    : http://www.pingf.me 
 * E-mail  : pingf0@gmail.com
 */
#ifndef __JC_OOP__
#define __JC_OOP__
#include <stdio.h>
#include <stdlib.h>
//
#ifdef BUILD_DLL
/* DLL export */
#define EXPORT __declspec(dllexport)
#elif   defined BUILD_WITH_DLL  
/* EXE import */
#define EXPORT __declspec(dllimport)
#else
#define EXPORT
#endif
//
#define USE_HEAP_FOR_CLASS
//
#ifdef USE_CALLOC_FOR_CLASS
#define _CLASS_ALLOC(this,Type) \
this=(struct _##Type *)malloc(sizeof(struct _##Type));  
#else
#define _CLASS_ALLOC(this,Type) \
this=(struct _##Type *)calloc(1,sizeof(struct _##Type));  
#endif 
/////////////////////////////////////////////////////////////
#ifdef USE_HEAP_FOR_CLASS
#define _CLASS(Type) \
typedef struct _##Type Type; \
EXPORT int init##Type(struct _##Type * this,void *param); \
EXPORT int fin##Type(struct _##Type *this,void *param); \
EXPORT struct _##Type * new##Type(void *param); \
EXPORT int del##Type(struct _##Type **this,void *param); \
struct _##Type \
{
//////////////////////////////////////
#define _CTOR(Type) \
EXPORT struct _##Type * new##Type(void *param) \
{ \
	struct _##Type *this; \
	int initRet=0; \
	_CLASS_ALLOC(this,Type); \
	if( NULL==this ) { \
        return NULL; \
    } \
	initRet=init##Type(this,param); \
	if(-1==initRet) { \
        free(this); \
        return NULL; \
    } \
	return this; \
} \
EXPORT int init##Type(struct _##Type * this,void *param) \
{  
//
#define _DTOR(Type) \
EXPORT int del##Type(struct _##Type **this,void *param) \
{ \
	int finRet=0; \
	if(NULL==(this)) { \
        return -1; \
    } \
	finRet=fin##Type(*(this),param); \
	if(-1==finRet) {\
        return -1; \
    } \
	free(*(this)); \
	(*(this))=NULL; \
	return 0; \
} \
EXPORT int fin##Type(struct _##Type *this,void *param) \
{ 
//
#else
#define _CLASS(Type) \
typedef struct _##Type Type; \
EXPORT int init##Type(struct _##Type * this,void *param); \
EXPORT int fin##Type(struct _##Type *this,void *param); \
struct _##Type \
{
//////////////////////////////////////
#define _CTOR(Type) \
EXPORT int init##Type(struct _##Type * this,void *param) \
{  
//
#define _DTOR(Type) \
EXPORT int fin##Type(struct _##Type *this,void *param) \
{ 
//  
#endif 
/////////////////////
#define _END_CLASS };
//
#define _END_CTOR \
	return  0; \
} 
// 
#define _END_DTOR \
return 0; \
}
//
#define _HAVES(Type,name) \
static struct _##Type##S s; \
//
#define _AUTOS(Type,name,sparam) \
do{ \
    init##Type##S(&s,sparam); \
    this->name=&s; \
}while(0);
//
#define _HAVEP(Type) \
static struct _##Type##P *p=NULL; 
//
#define _CASTP(Type) \
do{ \
    p=(struct _##Type##P *)param; \
}while(0);
/////////////////////////////////////////////////////////////
#define CLASS(Type) _CLASS(Type)
#define END_CLASS _END_CLASS 
//
#define CTOR(Type) _CTOR(Type)
#define END_CTOR _END_CTOR
//
#define DTOR(Type) _DTOR(Type)
#define END_DTOR _END_DTOR
//
#define HAVES(Type,name) _HAVES(Type,name)  
//
#define AUTOS(Type,name,sparam) _AUTOS(Type,name,sparam)  
//
#define HAVEP(Type) _HAVEP(Type)  
//
#define CASTP(Type) _CASTP(Type)  
//
#endif


C语言模拟OO机制的再研究

Posted on Fri, 27 Aug 2010 06:00:38 -1100

因为过一段日子可能又要写些单片机上的代码,所以难免还是离不开C.

以前研究过GObject,也自己用宏写过一些模拟OO的东西,不过这些用在单片机上就不那么顺了.....

于是就想把OO的思想进一步简化,

最和新的东西无非就是类的声明(设计),构造与析构,

所以浓缩了下面的代码,并保证在Keil这类工具上0警告编译通过....

/**
 * OOC-Embeded [Jcoop elite version]
 * version : 0.2.0827elite
 * status  : beta 
 * author  : Jesse Meng 
 * Blog    : http://www.pingf.me 
 * E-mail  : pingf0@gmail.com 
 */


#ifndef __JC_OOP__
#define __JC_OOP__
#include <stdio.h>
#include <stdlib.h>
 
#ifdef BUILD_DLL
/* DLL export */
#define EXPORT __declspec(dllexport)
#elif   defined BUILD_WITH_DLL  
/* EXE import */
#define EXPORT __declspec(dllimport)
#else
#define EXPORT
#endif

#define USE_HEAP_FOR_CLASS
 
#ifdef USE_CALLOC_FOR_CLASS
#define _CLASS_ALLOC(this,Type) \
this=(struct _##Type *)malloc(sizeof(struct _##Type));  
#else
#define _CLASS_ALLOC(this,Type) \
this=(struct _##Type *)calloc(1,sizeof(struct _##Type));  
#endif 

/////////////////////////////////////////////////////////////
#ifdef USE_HEAP_FOR_CLASS
#define _CLASS(Type) \
typedef struct _##Type Type; \
EXPORT int init##Type(struct _##Type * z); \
EXPORT int fin##Type(struct _##Type *z); \
EXPORT struct _##Type * new##Type(void); \
EXPORT int del##Type(struct _##Type **z); \
struct _##Type \
{
//
#define _END_CLASS };
//////////////////////////////////////
#define _CTOR(Type) \
EXPORT struct _##Type * new##Type(void) \
{ \
	struct _##Type *this; \
	int initRet=0; \
	_CLASS_ALLOC(this,Type); \
	if( NULL==this ) { \
        return NULL; \
    } \
	initRet=init##Type(this); \
	if(initRet<0) { \
        return NULL; \
    } \
	return this; \
} \
EXPORT int init##Type(struct _##Type * z) \
{  \
	struct _##Type * this=NULL;\
	this=(struct _##Type *)z; \
	if( NULL==this ) { \
        return -1; \
    }  
        
//
#define _END_CTOR \
	return  1; \
} 
//
#define _DTOR(Type) \
EXPORT int del##Type(struct _##Type **z) \
{ \
	struct _##Type **this = NULL; \
	int finRet=0; \
	this=(struct _##Type **)z;  \
	if(NULL==this) { \
        return -1; \
    } \
	finRet=fin##Type(*this); \
	if(finRet<0) {\
        return -1; \
    } \
	free(*this); \
	(*this)=NULL; \
	return 1; \
} \
EXPORT int fin##Type(struct _##Type *z) \
{ \
	struct _##Type * this=NULL; \
	this=(struct _##Type *)z; \
	if( NULL==this ) { \
        return -1; \
    } 
//  
#define _END_DTOR \
return 1; \
}
#else
#define _CLASS(Type) \
typedef struct _##Type Type; \
EXPORT int init##Type(struct _##Type * z); \
EXPORT int fin##Type(struct _##Type *z); \
struct _##Type \
{
//
#define _END_CLASS };
//////////////////////////////////////
#define _CTOR(Type) \
EXPORT int init##Type(struct _##Type * z) \
{  \
	struct _##Type * this=NULL;\
	this=(struct _##Type *)z; \
	if( NULL==this ) { \
        return -1; \
    }  
        
//
#define _END_CTOR \
	return  1; \
} 
//
#define _DTOR(Type) \
EXPORT int fin##Type(struct _##Type *z) \
{ \
	struct _##Type * this=NULL; \
	this=(struct _##Type *)z; \
	if( NULL==this ) { \
        return -1; \
    } 
//  
#define _END_DTOR \
return 1; \
}
#endif 
 
/////////////////////////////////////////////////////////////
#define CLASS(Type) _CLASS(Type)
#define END_CLASS _END_CLASS 
//
#define CTOR(Type) _CTOR(Type)
#define END_CTOR _END_CTOR
//
#define DTOR(Type) _DTOR(Type)
#define END_DTOR _END_DTOR
//
#endif

因为定义的东西少[也就100多行吧],所以使用起来就容易,省去了很多要记忆的东西,

下面是用例测试

#include <stdio.h>
#include "jc_oop.h"


CLASS(Test)
    int a;
    int b;
END_CLASS

CTOR(Test)
    printf("Test constructor");
    //this->a=1;
    //this->b=2;
END_CTOR
//
DTOR(Test)
    printf("Test destructor");
    this->a=this->b=0;
END_DTOR

int main (int argc, char *argv[])
{
    Test stk;
    Test * heap=NULL;
    heap=newTest();
    delTest(&heap);
    initTest(&stk);
    finTest(&stk);
    return(0);
}

别看这么简单,但是像继承和组合的关系还是可以表示清楚的

比如下面测试了组合关系

 

#include <stdio.h>
#include "jc_oop.h"


CLASS(Test)
    int a;
    int b;
END_CLASS

CTOR(Test)
    printf("Test constructor\n");
    //this->a=1;
    //this->b=2;
END_CTOR
//
DTOR(Test)
    printf("Test destructor\n");
    this->a=this->b=0;
END_DTOR

CLASS(Test2)
    Test *test;
END_CLASS

CTOR(Test2)
    printf("Test2 constructor\n");
    this->test=newTest();
END_CTOR
//
DTOR(Test2)
    printf("Test2 destructor,actually this is after Test\n");
    delTest(&(this->test));
END_DTOR

int main (int argc, char *argv[])
{
    Test stk;
    Test2 * heap=NULL;
    heap=newTest2();
    delTest2(&heap);
    return(0);
}

至于像方法之类的东西,自然还是用函数指针来模拟,

为了在嵌入式系统上使用方便,不再用宏来定义

JCOOP入门指南[04]

Posted on Sun, 18 Jul 2010 06:32:18 -1100

//修改 : 2010.7.25 修正自JCOOP 0.12之后[不包括0.12]SUB_CTOR宏接受三个参数,最后一个参数显示的指明共享部分的指针名称

 

//这两天对JCOOP核心的宏做了一些修改[本文编写时这个版本还未上传,不过也快了],主要是增加了些返回的状态,

//便于出错后的检测,不过因为用到的printf,所以某些超低端嵌入式平台如果使用最好做一些修改,以减小代码体积.

继前文介绍了JCOOP[sourceforge.net/projects/jcoop]类的设计方法[简化类,标准类,以及含有继承的类],

本篇介绍使用JCOOP中的"包含"

假设某个机车的核心是一个发动机,

我们可以这样设计这些类,

先设计一个发动机类,再从此类派生一个汽车类.

但也可采用下面的方法,

设计一个汽车类,在此类中包含一个指向原先设计好的发动机类的指针.

上面的例子中显然后者更和常理,绝大多数情况下,两种设计可以互相转化.

在良好的设计中应该尽可能的使用第二种设计方式,尽管这样会舍弃了一些语言本身的简易特性,但会使得代码更易修改,耦合度低,程序更加紧凑.....

下面的叙述还是采用上一篇中的代码,稍作修改,以演示如何在JCOOP中方便的使用"包含"的方式去设计一个类

先设计一个非常简单的Toy类

CLASS(Toy)
	unsigned char *name;
	SHARE(Toy)
		METHOD(void,playWithToy)(void *);
END_CLASS

然后我们假设每个小孩都有一个玩具,并且可以玩!

这样Baby类就变成了下面这样

CLASS(Baby)
	HAVE(Toy,toy);
	unsigned int age; //表示年龄的成员
	SHARE(Baby)
		HAVES(Toy,toy);
		METHOD(unsigned int,getAge)(void *);
		METHOD(void,setAge)(void *,unsigned int);
		METHOD(void,cry)(); //两个void分别对应返回值和参数
END_CLASS

而Baby的构造函数就变成了

CTOR(Baby) //构造函式
	SUB_CTOR(Toy,toy);   //注意,0.12后的版本应使用SUB_CTOR(Toy,toy,toy),这样可以指定对应共享部分指针的名称
	printf("Baby constructor called ... \n");
	ASSIGN(age,1); //赋值
	CTORS(Baby)
		SUB_CTORS(Toy,toy);
		printf("Baby share part constructor called ... \n");
		CONNECT(cry,Jcry); //链接到指定的函式
		CONNECT(getAge,JgetAge);
		CONNECT(setAge,JsetAge);
END_CTOR

其析构函数也相应的变成了

DTOR(Baby) //析构函式
	printf("Baby destructor called ... \n");
	UNASSIGN(age); //置零
	SUB_DTOR(Toy,toy);
	DTORS(Baby)
		printf("Baby share part destructor called ... \n");
		DISCONNECT(getAge);
		DISCONNECT(setAge);
		DISCONNECT(cry); //断开链接
		SUB_DTORS(Toy,toy);
END_DTOR

注意到

在CLASS中使用的是HAVE...HAVES的组合[要注意,共享和非共享部分的指针名称应保证一致,否则会报错]

在CTOR中使用的是SUB_CTOR...SUB_CTORS的组合
在DTOR中使用的是SUB_DTOR...SUB_DTORS的组合

下面列出完整的代码

#include <stdio.h>                                                                                                     
#include "jc_oop.h"


CLASS(Toy)
	unsigned char *name;
	SHARE(Toy)
		METHOD(void,playWithToy)(void *);
END_CLASS


CLASS(Baby)
	HAVE(Toy,toy);
	unsigned int age; //表示年龄的成员
	SHARE(Baby)
		HAVES(Toy,toy);
		METHOD(unsigned int,getAge)(void *);
		METHOD(void,setAge)(void *,unsigned int);
		METHOD(void,cry)(); //两个void分别对应返回值和参数
END_CLASS

CLASS(Boy)
	EXTD(Baby);
	unsigned int sex;
	unsigned char * favor;
	SHARE(Boy)
		EXTDS(Baby);
		METHOD(int,setFavor)(void *,const unsigned char *);
		METHOD(unsigned char *,getFavor)(void *);
		METHOD(void,cry)();  
END_CLASS

static void JplayWithToy(void *z){
	ZS(Toy);
	printf("\n>>>>>                           \n");
	printf("playing with %s\n",this->name);
	printf("                           <<<<<\n\n");
}
static void Jcry(){
	printf("baby is crying...\n");
}
static void JboyCry(){
	printf("boy is crying...\n");
}
static unsigned int JgetAge(void *z){
	ZS(Baby);
	return this->age;
}

static void JsetAge(void *z,unsigned int age){
	ZS(Baby);
	this->age=age;
}

static int JsetFavor(void *z,const unsigned char *str){
	ZS(Boy);
	unsigned int len=strlen(str)+1;
	if((this->favor=(unsigned char *)malloc(sizeof(unsigned char)*len))!=NULL){
		strncpy(this->favor,str,len);
		return 1;
	}
	return 0;
}

static unsigned char * JgetFavor(void *z){
	ZS(Boy);
	return this->favor;
}
 


CTOR(Toy) //构造函式
	printf("Toy constructor called ... \n");
	ASSIGN(name,"audi car mini-model"); //赋值
	CTORS(Toy)
		printf("Toy share constructor called ... \n");
		CONNECT(playWithToy,JplayWithToy);
END_CTOR

DTOR(Toy) //构造函式
	printf("Toy destructor called ... \n");
	UNASSIGN(name);
	DTORS(Toy)
		printf("Toy share destructor called ... \n");
		DISCONNECT(playWithToy);
END_DTOR

CTOR(Baby) //构造函式
	SUB_CTOR(Toy,toy);  //注意,0.12后的版本应使用SUB_CTOR(Toy,toy,toy),这样可以指定对应共享部分指针的名称	
        printf("Baby constructor called ... \n");
	ASSIGN(age,1); //赋值
	CTORS(Baby)
		SUB_CTORS(Toy,toy);
		printf("Baby share part constructor called ... \n");
		CONNECT(cry,Jcry); //链接到指定的函式
		CONNECT(getAge,JgetAge);
		CONNECT(setAge,JsetAge);
END_CTOR

DTOR(Baby) //析构函式
	printf("Baby destructor called ... \n");
	UNASSIGN(age); //置零
	SUB_DTOR(Toy,toy);
	DTORS(Baby)
		printf("Baby share part destructor called ... \n");
		DISCONNECT(getAge);
		DISCONNECT(setAge);
		DISCONNECT(cry); //断开链接
		SUB_DTORS(Toy,toy);
END_DTOR


CTOR(Boy)
	SU_CTOR(Baby);
	printf("Boy constructor called ... \n");
	ASSIGN(Baby.age,10);
	ASSIGN(sex,1);
	unsigned int len=strlen("play football")+1;
	this->favor=(unsigned char *)malloc(sizeof(unsigned char)*len);
	strncpy(this->favor,"play football\0",len);
	CTORS(Boy)
		SU_CTORS(Baby);
		printf("Boy share part constructor called ... \n");
		CONNECT(cry,JboyCry); //链接到指定的函式
		CONNECT(getFavor,JgetFavor);
		CONNECT(setFavor,JsetFavor);
END_CTOR



DTOR(Boy)
	printf("Boy destructor called ... \n");
	UNASSIGN(sex);
	free(this->favor);
	this->favor=NULL;
	SU_DTOR(Baby);
	DTORS(Boy)
		printf("Boy share part destructor called ... \n");
		DISCONNECT(cry); //断开链接
		DISCONNECT(getFavor);
		DISCONNECT(setFavor);
		SU_DTORS(Baby);
END_CTOR

	


int main(){
	NEW_BUNDLE(Boy,boy,bs);
	printf("the boy's age is %d\n",bs->Baby.getAge(boy));    
	bs->Baby.setAge(boy,15);
	printf("the boy's age is %d\n",bs->Baby.getAge(boy)); 
	bs->cry();	
	printf("the boy's favor is %s\n",bs->getFavor(boy)); 
	bs->setFavor(boy,"play basketball");
	printf("the boy's favor is %s\n",bs->getFavor(boy)); 
	
	bs->Baby.toy->playWithToy(boy->Baby.toy);
	 
	printf("\n***** now test the conversion *****\n\n"); 
	
	BabyShare *babys=NULL;
	babys=CAST_PTR(BabyShare,bs);
	babys->cry();
	Baby *baby=NULL;
	baby=CAST_PTR(Baby,boy);
	printf("the baby's age is %d\n",babys->getAge(baby)); 
	   
	DEL(Boy,boy);
	DELS(Boy,bs);
	return 0;
}

正确的运行结果如下,

Toy share constructor called ... 
Baby share part constructor called ... 
Boy share part constructor called ... 
Toy constructor called ... 
Baby constructor called ... 
Boy constructor called ... 
the boy's age is 10
the boy's age is 15
boy is crying...
the boy's favor is play football
the boy's favor is play basketball

>>>>>                           
playing with audi car mini-model
                           <<<<<


***** now test the conversion *****

baby is crying...
the baby's age is 15
Boy destructor called ... 
Baby destructor called ... 
Toy destructor called ... 
Boy share part destructor called ... 
Baby share part destructor called ... 
Toy share destructor called ... 

JCOOP入门指南[03]

Posted on Fri, 09 Jul 2010 07:35:21 -1100

通过前面的介绍,相信本文的读者(具备一定编程基础)对JCOOP[sourceforge.net/projects/jcoop]有了大致的了解

本节依旧是以实例为主来说明如何使用JCOOP来模拟"继承"这一OO中重要的元素.

 

C语言中本没有类的概念,但其实有了结构体和函数指针,一个类的最基本功能是都可以实现的.前文中这些都已介绍了.

不过学过CPP和JAVA都知道,OO中必然有继承,这是C语言中所没有的,那么如何用C来实现呢?

准确说来C只能说是来模拟,而不能实现所有继承的功能,而模拟的方法自然还是用结构体[C中就那么点东西啊....]

具体见下面的示例

struct A{

        int a;

};

struct B{

        struct  A   stA;

        int b;

};

后面的结构体b包含了a结构体的一个实例.而类型转换时遵循位置靠前的原则.

在JCOOP中定义了如下一些宏来方便的实现继承这一功能,

类的声明中使用-->EXTD(共享部分使用EXTDS)

类的构造中使用-->SU_CTOR(共享部分使用SU_CTORS)

类的析构中使用-->SU_DTOR(共享部分使用SU_DTORS)

具体可以看下面的代码[如果感觉下面的代码仍有些怪异,请阅读前面相关JCOOP的文章]

 

#include <stdio.h>                                                                                                     
#include "./include/jc_oop.h"


CLASS(Baby)
	unsigned int age; //表示年龄的成员
	SHARE(Baby)
		METHOD(unsigned int,getAge)(void *);
		METHOD(void,setAge)(void *,unsigned int);
		METHOD(void,cry)(); //两个void分别对应返回值和参数
END_CLASS

CLASS(Boy)
	EXTD(Baby);
	unsigned int sex;
	unsigned char * favor;
	SHARE(Boy)
		EXTDS(Baby);
		METHOD(int,setFavor)(void *,const unsigned char *);
		METHOD(unsigned char *,getFavor)(void *);
		METHOD(void,cry)();  
END_CLASS


static void Jcry(){
	printf("baby is crying...\n");
}
static void JboyCry(){
	printf("boy is crying...\n");
}
static unsigned int JgetAge(void *z){
	ZS(Baby);
	return this->age;
}

static void JsetAge(void *z,unsigned int age){
	ZS(Baby);
	this->age=age;
}

static int JsetFavor(void *z,const unsigned char *str){
	ZS(Boy);
	unsigned int len=strlen(str)+1;
	if((this->favor=(unsigned char *)malloc(sizeof(unsigned char)*len))!=NULL){
		strncpy(this->favor,str,len);
		return 1;
	}
	return 0;
}

static unsigned char * JgetFavor(void *z){
	ZS(Boy);
	return this->favor;
}
 




CTOR(Baby) //构造函式
	printf("Baby constructor called ... \n");
	ASSIGN(age,1); //赋值
	CTORS(Baby)
		printf("Baby share part constructor called ... \n");
		CONNECT(cry,Jcry); //链接到指定的函式
		CONNECT(getAge,JgetAge);
		CONNECT(setAge,JsetAge);
END_CTOR

DTOR(Baby) //析构函式
	printf("Baby destructor called ... \n");
	UNASSIGN(age); //置零
	DTORS(Baby)
		printf("Baby share part destructor called ... \n");
		DISCONNECT(getAge);
		DISCONNECT(setAge);
		DISCONNECT(cry); //断开链接
END_DTOR


CTOR(Boy)
	SU_CTOR(Baby);
	printf("Boy constructor called ... \n");
	ASSIGN(Baby.age,10);
	ASSIGN(sex,1);
	unsigned int len=strlen("play football")+1;
	this->favor=(unsigned char *)malloc(sizeof(unsigned char)*len);
	strncpy(this->favor,"play football\0",len);
	CTORS(Boy)
		SU_CTORS(Baby);
		printf("Boy share part constructor called ... \n");
		CONNECT(cry,JboyCry); //链接到指定的函式
		CONNECT(getFavor,JgetFavor);
		CONNECT(setFavor,JsetFavor);
END_CTOR



DTOR(Boy)
	printf("Boy destructor called ... \n");
	UNASSIGN(sex);
	free(this->favor);
	this->favor=NULL;
	SU_DTOR(Baby);
	DTORS(Boy)
		printf("Boy share part destructor called ... \n");
		DISCONNECT(cry); //断开链接
		DISCONNECT(getFavor);
		DISCONNECT(setFavor);
		SU_DTORS(Baby);
END_CTOR

	


int main(){
	NEW_BUNDLE(Boy,boy,bs);
	printf("the boy's age is %d\n",bs->Baby.getAge(boy));    
	bs->Baby.setAge(boy,15);
	printf("the boy's age is %d\n",bs->Baby.getAge(boy)); 
	bs->cry();	
	printf("the boy's favor is %s\n",bs->getFavor(boy)); 
	bs->setFavor(boy,"play basketball");
	printf("the boy's favor is %s\n",bs->getFavor(boy)); 


	printf("\n***** now test the conversion *****\n\n"); 
	
	BabyShare *babys=NULL;
	babys=CAST_PTR(BabyShare,bs);
	babys->cry();
	Baby *baby=NULL;
	baby=CAST_PTR(Baby,boy);
	printf("the baby's age is %d\n",babys->getAge(baby)); 
	   
	DEL(Boy,boy);
	DELS(Boy,bs);
	return 0;
}

运行时,会显示下面的字符,

Baby share part constructor called ... 
Boy share part constructor called ... 
Baby constructor called ... 
Boy constructor called ... 
the boy's age is 10
the boy's age is 15
boy is crying...
the boy's favor is play football
the boy's favor is play basketball

***** now test the conversion *****

baby is crying...
the baby's age is 15
Boy destructor called ... 
Baby destructor called ... 
Boy share part destructor called ... 
Baby share part destructor called ... 

上面这个实例实现了一个Boy类,该类继承了前面的Baby类[见前文],

这里仅简单说明以下几点,

    1.Boy中继承了Baby类,共享部分也是如此.

    2.Boy中类也实现了单独的cry方法,你可以将这种方式理解成函数的重写.

    3.类型转换时,因为上面程序中都是使用CAST_PTR宏,因为类都建立在堆上,我们只是用指向其的指针.

    4.如果最后调用babys->cry();那么还是打印baby is crying ......

    5.注意上面constructor和destructor调用的辅助文字仅仅在说明调用的次序,当然如果你改变它的位置,打印的位置也会有所改变.

    6.另外JboyCry()仅仅是因为两个类都放在一个文件中,为了避免重名才这样做.

 

 

 

JCOOP入门指南[02]

Posted on Thu, 08 Jul 2010 12:54:01 -1100

上一篇中用JCOOP[sourceforge.net/projects/jcoop]设计了一个最最简单的Baby类,但是如果都像这样做的话一旦类的规模增大,

每声明一个实例,有些时候就要额外的占用一定的空间[比如类方法使用的指针],

为了节约资源,在JCOOP中一个类其实包含了两部分,一部分存储每个实例的属性成员,

另一部分存储公共的变量,可以是共享变量也可以是公用的方法[这类似与C++中的虚表]

而其声明方式类似下面这样,

CLASS(A)

        ......//普通成员

        SHARE(A)

                ......//方法或共享成员

END_CLASS

而每个类的私有部分和公有部分通过一个名为share的指针(如果声明了公有部分则存在于私有部分,否则不存在)

指向公有部分.

类似的,类的构造和析构也有相应的宏CTOR...CTORS...END_CTORDTOR...DTORS...END_DTOR来声明.

具体见下面的代码,

 

#include <stdio.h>                                                                                                     
#include "./include/jc_oop.h"


CLASS(Baby)
	unsigned int age; //表示年龄的成员
	SHARE(Baby)
		METHOD(unsigned int,getAge)(void *);
		METHOD(void,setAge)(void *,unsigned int);
		METHOD(void,cry)(void *); //两个void分别对应返回值和参数
END_CLASS

static void Jcry(void *z){
	printf("baby is crying...\n");
}

static unsigned int JgetAge(void *z){
	ZS(Baby);
	return this->age;
}

static void JsetAge(void *z,unsigned int age){
	ZS(Baby);
	this->age=age;
}


CTOR(Baby) //构造函式
	printf("Baby constructor called ... \n");
	ASSIGN(age,1); //赋值
	CTORS(Baby)
		printf("Baby share part constructor called ... \n");
		CONNECT(cry,Jcry); //链接到指定的函式
		CONNECT(getAge,JgetAge);
		CONNECT(setAge,JsetAge);
END_CTOR

DTOR(Baby) //析构函式
	printf("Baby destructor called ... \n");
	UNASSIGN(age); //置零
	DTORS(Baby)
		printf("Baby share part destructor called ... \n");
		DISCONNECT(getAge);
		DISCONNECT(setAge);
		DISCONNECT(cry); //断开链接
END_DTOR

int main(){
	NEW_BUNDLE(Baby,baby,bs);
	printf("the baby's age is %d\n",bs->getAge(baby));    
	bs->cry(baby);
	bs->setAge(baby,3);
	printf("the baby's age is %d\n",bs->getAge(baby));    
	DEL(Baby,baby);
	DELS(Baby,bs);
	return 0;
}

这里要特别声明的是如果用ZS宏,第一个参数的声明应为void *z,这样才会正确的转换成相应的"this指针"

编译正确后,会输出如下,

Baby share part constructor called ... 
Baby constructor called ... 
the baby's age is 1
baby is crying...
the baby's age is 3
Baby destructor called ... 
Baby share part destructor called ... 

另外顺带要说一下NEW_BUNDLE这个宏,其相当于NEWS和NEW这两个宏的组合,第一次声明某个实例时使用该宏会更加简洁.

和前面讲到的CTORS和DTORS一样,那个末尾的S表示SHARE,也就是公有部分.

而程序中我们实际使用的是两个类型分别BabyShareBaby指针,其分别指向了两个在堆上创建的实例.

当然如果你正确的使用NEW和DEL等宏,那么一般不用过多关注堆上类所用内存的分配.

JCOOP入门指南[01]

Posted on Thu, 08 Jul 2010 07:39:44 -1100

写一点关于JCOOP的入门指南,JCOOP最核心的部分是由一堆宏构成的,

能够简化C语言描述对象的过程,具体代码见sourceforge.net/projects/jcoop

下面是最简单的一个例子,

涉及到用JCOOP构造类的方法之一以及构造函数与析构函数的使用

因为这个例子非常简单,多的就不写了,直接看注释就好

[那个jc_oop.h要到上面的网址下载,另外最好用GCC来编译,MS那套东西我没测试过,Mingw肯定欧科的]

 
#include <stdio.h>                                                                                                     
#include "./include/jc_oop.h"

static void cry(){
	printf("baby is crying...\n");
}


CLASS(Baby)
	int age; //表示年龄的成员
	METHOD(void,cry)(void); //两个void分别对应返回值和参数
END_CLASS

CTOR(Baby) //构造函式
	printf("Baby constructor called ... \n");
	ASSIGN(age,1); //赋值
	CONNECT(cry,cry); //链接到指定的函式
END_CTOR

DTOR(Baby) //析构函式
	printf("Baby destructor called ... \n");
	UNASSIGN(age); //置零
	DISCONNECT(cry); //断开链接
END_DTOR

int main(){
	NEW(Baby,baby,NULL);
	printf("the baby's age is %d\n",baby->age);    
	baby->cry();
	DEL(Baby,baby);
	return 0;
}

运行时会打印如下的字符串

Baby constructor called ... 
the baby's age is 1
baby is crying...
Baby destructor called ... 

需要注意的是上面的Baby类是构造在堆上的,所以baby自然是一个指针

其在NEW时分配相应的内存,并初始化相应的值(构造函数),

在DEL时调用析构函数释放对应的内存.

从上面的代码不难发现,使用JCOOP不但可以形象的描述一个类,

而且简化了代码的编写,特别是涉及内存分配相关的部分.