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相关的抽象概念,如有需要,
请参阅此节.