初级程序员面试不靠谱指南(七)

六.c语言的"面向对象"

     我仍然能记得我大学C++课本的第一句话是,"c++是一种面向对象的编程语言",当时第一反应是试图从汉语字面上理解“面向对象”这四个字都不知道什么 意思,于是很自然的就忽视了。再到后来我们那个教材给我们造成了一种错觉,不过这也是中国很多教材的通病,让人觉得世界上只有一种语言是面向对象的,就是 c++,这个观念在我脑海里存在了许久许久,所以说,有时候一本烂书足以毁掉人的一生。不过还好,后来在接触了比较多的东西之后,终于正确的认识到了面向 对象是一种独立于编程语言概念,是一种编程的理念,不过我很庆幸在我后来慢慢编程入门的时候还是看了几本正确的书籍的,不然我肯定也是那种什么叫类,什么叫实例化一个对象都分不清楚的人。

      上一篇我不停的吹嘘函数指针的伟大,至少在C语言里面,我也不是一个高手,但是就我目前接触到的一些涉及到这个知识点的地方,我确实折服于熟练运用函数指针能创造出来的变成技巧。这里我说要用c语言实现“面向对象”的代码,只是简单的运用了一点点函数指针的技巧,而且是粗略的大致实现三个小的概念,如果你有兴趣去看这个方面的全部内容,我强烈推荐去看看这本书Object-Oriented Programming With ANSI-C,点击书名可以直接下载哦!哈哈。

1.new和malloc。严格意义上,我人生中面试的第一个问题就是,new和malloc之间的区别,当时我确实不知道,扯了一大堆无关紧要的,最后结果自然是桑心的,不过我敢保证,很多人还是不知道这两个有什么区别。先放一放这些,先来看一小段代码:

String c_sz = newString();
c_sz ->set(s1, "hello world");

     先单纯从这段代码上看一下,从代码上,可以看到这段代码的意思是,声明一个字符串,然后将其值设置为hello world。但是,要明确的是,c语言里面是没有string这个类型的,c语言里面用的是char*来声明字符串的,所以,如果用c语言能够运行上述代码,首要的一件事就是要能够搞出string这么一个结构,这个很容易想到,在c语言中,使用struct就能完成这一目标。但是struct里面是不能实现函数的,那么如何实现这段代码中的"->"?这时候如果凭空想,我觉得难度还是挺大的,不过大多数人都不是能够自己创造数学定理的,但是,一个能够熟练运用数学定理的人一样是很牛逼的,所以,我想绝大多数人不可能推出来这里可以采用函数指针作为结构的一个成员来完成这个目标的,当然我也不能。

     然后,我们再和c++语言对比一下(我想大多数到这里看文章的人肯定都有一定的c++知识),如果转移到c++中,类似的代码应该像下面这样:

String cpp_sz = new String();
cpp_sz ->set("hello world");

    这样差别就一目了然了,第一句,new和String()是分开的,这其中的一个原因就是new在c++里面是一个操作符,如何利用c语言实现一个操作符我这种能力的还是远远不能的,所以就稍微折中一点,只能实现一个“构造函数”了,模拟一下new了。第二个差别是set里面没有使用变量本身,这样看起来更加接近于“面向对象”的特点,但是很不幸的是,以我目前的能力,我也暂时还不能实现这样的一个函数。不过,这并不影响实现继承,多态等面向对象的概念。

    首先,定义出一个String的结构,因为c里面没有class,所以,class这个关键词是肯定不能用的,代码如下:

typedef struct String_Struct* String;

struct String_Struct
{
   char *value;

   char* (*get)(struct String_Struct* self);
   void (*set)(struct String_Struct* self, char* value);
};

    这里的还有一个很尴尬的问题就是c语言里面不含有访问操作符,就是public之类的,但是这个通过一定的技巧是可以实现的,唉,只有这个问题我的能力还能解决一点,不过为了更加简单一点,我们暂时忽略这个问题。这个结构里面包含三个元素,一个c字符串value,两个函数指针,前面的->符号就靠这两个函数指针实现了。下面的问题就是转到如何实现了。既然有函数指针,就需要有被指向的函数,也就是get,set等的具体实现,代码如下所示:

char* getString(struct String_Struct* self);
void setString(struct String_Struct* self, char* value);

char* getString(struct String_Struct*  self_obj)
{
    return self_obj->value;
    
}

void setString(struct String_Struct* self, char* value)
{
     self->value=(char *) malloc(sizeof(char) * (strlen(value) + 1));
     strncpy(self->value,value,strlen(value));
}

    在这些都完成之后之后,就需要专心于如何写出"构造函数"了,如果想达到在声明一个函数的时候结构的所有字段都被初始化的话,至少应该分成两步,第一步是分配内存,第二步是初始化所有结构,这也是new和malloc的差别之一,可以理解成new里面包含malloc,在new之后不仅要分配内存,还要进行构造函数的初始化,而malloc仅仅只是分配内存。按照这个思路,"构造函数"应该类似如下这样的结构:

String newString()
{
    String self = (String)malloc(sizeof(struct String_Struct));

    InitialStruct(struct String_Struct* self);

    return self;
}

     接着就应该思考初始化的部分了,在string_struct有三个部分,所以初始化一定是对value和两个函数指针赋予默认值,代码如下:

InitialStruct(struct String_Struct* self)
{
    self->get = &getString;
    self->set = &setString;

    self->set(self, "");
}

     至此,你就可以用上面的代码set和get字符串了。再回头看一下这个的实现步骤,主要的一个方法就是利用函数指针,同时这也是后面实现继承和多态的方法。

 2.继承的魔法。如何能够实现“继承”,首先要想想继承能干嘛,继承出来的子类应该具有基类的所有功能,并且还有自己的功能(在不考虑访问控制符号的情况下),我们把问题尽量简单化,实现其最基本的一个功能点,如何让"继承"的结构可以包含基类的函数呢?按照上面的思路,很容易的就可以想到使用函数指针的指向进行控制就能达到这一目的,不过在此之前,先得定义一个继承的结构,我打算从String里面继承出一个LittleString:

typedef struct LittleString_Struct* LittleString;

struct LittleString_Struct
{
	String base;

	char* (*get)(const String_Struct* self);
	int (*length)(const LittleString_Struct* self);
};

       除了基类的方法,我打算为了彰显一下继承的不同,所以多实现一个length,和上面一样,你可以对比相应的c++代码来加深一下认识,在结构声明完成之后,接着,需要的是定义一个length函数,因为基类没有这个方法,这个很简单:

int Length(const LittleString_Struct* self)
{
    return strlen(self->base->value);
}

     下面一个需要做的就是子类的构造函数了,这里需要初始化的有基本类和派生类的所有元素,这个有点复杂,不过按照上面的思路,最重要的是函数指针指向正确的位置,其构造函数应该大致如下:

LittleString newLittleString();

LittleString newLittleStringString()
{
    LittleString self = (LittleString)malloc(sizeof(struct LittleString_Struct));

    self->base = newString();

    self->get = self->base->get;
    self->length = self->length;

    self->base->set(self->base, "");

    return self;
}

      注意里面的函数指针的指向,首先初始化基类的对象,这一点也是无数c++书本里面所描写的,然后在派生类中实现基类的函数,具体关于面向对象的东西在紧接着我要写的c++部分我要详细阐述,因为这是很多面试的重点,这里只能假设基本的概念全明白了,这个就是将函数指针指向基类已经实现的函数,最后就是实现派生类本身的内容,这一点和前面的一致。我想这样的代码,对理解c++的面向对象是有很大帮助的,至少对我确实是这样。

3.多态的指向。有了前面的概念,如何实现多态应该很简单了,只要将指针函数指向派生类的函数而不是基类的函数就可以做到这一点,比如,在上面,我们想在派生类中实现自己的get(当然,这有点不合常理),只需要重新定义一个函数指针,char* (*get)(const LittleString_Struct* self);一个新的get函数,并且在派生类的构造函数中指向新的get函数就行了,这一点我就不实现了,有兴趣的可以自己试试,事实上,还可以按照一样的思路实现析构函数等等,不过这些全凭你自己的兴趣了。

  最后,我有一点要说明一下,上面关于面向对象的代码只是一个很粗略的梗概,c++中如何实现的要远远复杂于这些内容,所以,这些仅仅是一个参考,纯是为了帮助理解,别当真。

posted on 2013-06-24 13:25  一心一怿  阅读(1970)  评论(1编辑  收藏  举报

导航