2014年8月25日,收藏家和殺手——面向對象的C++和C(一)
收藏家和殺手——面向對象的C++和C(一)
序 ========= 用了至少12年的C++,前些年Linux之父Linus在批評C++的時候(詳細可看CSDN的《C++一無是處》所提到的這起事件:http://www.csdn.net/article/a/2010-06-12/218785),引起了全世界非常多資深程序猿發起聲討C++的群體騷動。我盡管沒有騷動,但還是跟在後面看了他們的大部分描寫敘述。我得承認。他們說的大部分是事實(事實上每一個語言都有非常多能夠批判的事實)。
作為一名有強迫癥的程序猿(我堅信有強迫癥的程序猿才是好程序猿),長期的C++經驗讓我在項目伊始就一直就糾纏在Code Style,Design Pattern、Framework等因素的形式是否優美、擴展性是否周到等問題,這些問題事實上就是內心重復自我懷疑式的噩夢,更糟糕的是這個噩夢的時間占用的項目周期還不少,盡管我個人也非常清楚,這些噩夢事實上和項目的最後目的沒有直接關系(不會被老板、客戶、用戶看到)。並且也清楚噩夢的時間越長,就越和敏捷(Agile)的原型叠代流程以及精益(Lean Startup)思想背道而馳,但還似乎忍不住……這甚至類似於一種心理疾病。
相信非常多C++老程序猿都有和我同樣的感覺,或許這個噩夢的起因能夠解釋為是由於我們都太想在編程中尋找一種完美和純粹,但商業項目並不看重內裏的完美和純粹。換句話說就是我們想把藝術性的行為表如今代碼中,讓編程藝術化,但商業項目說“不是必需,能用就是好的”。
對於這個噩夢另一種更客觀的解釋,就是C++給了我們太多並且相互矛盾的選擇(Java給我們的選擇也非常多,但矛盾少了,組織的好一點)。而讓我們這些強迫癥患者無所適從,而商業項目僅僅要實現,才無論你怎麽選擇。
更通俗來說。就像打架,C++給我們了非常多武器。太多的選擇和矛盾組合讓我們無所適從,可以使用好這麽多武器須要成年累月的經驗,而商業項目就是最快最準的打倒對方,才無論你用啥。
想想看,當我擁有匕首、手槍、機關槍、火箭炮……時,我非常可能會忘了我的目的是打到對方,而當起了武器收藏家;但當我僅僅有匕首在手的時候呢?或許對方真正要倒黴了——我非常可能是個抱著誓死決心的殺手。
——所以有時候,少就是多,多就是少:)
從看到Linus對C++的批判開始。我就一直想能否夠僅僅用C來寫某個新項目,體驗一下和C++敲代碼的不同,可惜興許好幾個Linux的後臺並發項目都還是用C++寫的。直到近期一個項目,是涉及232和485接口的硬件傳感器的Linux項目,才讓我下決心所實用C來寫。以下就來說說我的一些感想和總結。
殺手也可能成為收藏家——C也能夠寫基礎的面向對象
=========
或許在看到C能夠寫面向對象這句話後。非常多初、中級程序猿驚呆了,覺得我胡說八道。假設我換個說法,C能夠以面向對象的方式敲代碼。但C語言本身並不直接具有面向對象的特性,這些程序猿或許就會好受一些,會從覺得我胡說八道轉為覺得我有機會自圓其說。以下就讓我自圓其說一下。
就語言本身而言,C語言的確不是一個面向對象的語言,但我們的問題是,那麽C語言能夠實現面向對象編程嗎?我的答案是能夠。面向對象就基本本質而言,沒有那麽復雜,事實上就是對數據和操作這些數據的方法的統一封裝。
C++中有class(或者struct)這麽一個keyword能夠把數據和關聯的方法封裝成一個類。那麽C語言呢?C語言的struct事實上也能夠把數據和關聯的方法封裝在一起。
或許非常多人說。你騙人。C語言的struct中僅僅能放成員變量,不能放成員方法。或許你忘了還有函數指針這麽一個東西,這個東西的存在,能夠把方法像變量一樣的放在C語言的struct中。例如以下(下面三個代碼文件在Mac
OS X的XCode上完畢):
//
// person.h
// cthinking
//
// Created by Rafael Gu on 14-8-25.
// Copyright (c) 2014年 Rafael Gu. All rights reserved.
//
#ifndef _PERSON_H_
#define _PERSON_H_
struct person;
typedef unsigned char (*GET_AGE)(struct person *this);
typedef void (*SET_AGE)(struct person *this, unsigned char age);
// all public members
struct person {
char name[32];
GET_AGE get_age;
SET_AGE set_age;
};
struct person *person_create();
void person_destroy(struct person *p);
#endif // _PERSON_H_
//
// person.c
// cthinking
//
// Created by Rafael Gu on 14-8-25.
// Copyright (c) 2014年 Rafael Gu. All rights reserved.
//
#include "person.h"
#include <stdlib.h>
// all private members
struct _person {
unsigned char age;
};
static unsigned char _get_age(struct person *this) {
struct _person *p = (struct _person *)(this + 1);
return p->age;
}
static void _set_age(struct person *this, unsigned char age) {
struct _person *p = (struct _person *)(this + 1);
p->age = age;
}
struct person *person_create() {
struct person *p = (struct person *)malloc(sizeof(struct person) + sizeof(struct _person));
p->get_age = _get_age;
p->set_age = _set_age;
return p;
}
void person_destroy(struct person *p) {
free(p);
}
//
// main.c
// cthinking
//
// Created by Rafael Gu on 14-8-25.
// Copyright (c) 2014年 Rafael Gu. All rights reserved.
//
#include <stdio.h>
#include <string.h>
#include "person.h"
int main(int argc, const char * argv[]) {
struct person *this = person_create();
memset(this->name, 0, 32);
strncpy(this->name, "rafael", 6);
this->set_age(this, 35);
printf("%s‘s age is: %u\n", this->name, this->get_age(this));
person_destroy(this);
return 0;
}
假設已經看懂上面代碼的程序猿,應該會會心一笑,上面主要用了函數指針、struct的變體偏移、編譯單元的static函數(變量也相同)等C的技術。假設是寫過objective-c的程序猿,立即就會認為和不同private和publickeyword的objective-c的類結構非常像。
在上面的類中,name是public變量。age是私有變量。通過相應的getter和setter訪問。person_create能夠看做是new和構造函數的結合體,而person_destroy能夠看做是delete和析構函數的結合體。
當然這裏僅僅實現了private和public,沒有protected。
假設有人問。怎樣實現class的static函數和變量。事實上他們就是C的編譯單元的static函數和變量。僅僅要看懂上面的代碼,而且知道編譯單元是啥以及和static什麽關系就明確了。
今天就寫到這裏。下次假設再寫,就像寫高級一點的面向對象知識了,比方多態。當然為了防止沒有下文的遺憾,我這裏把實現多態的技術原理說一下:
- 繼承——你看上面的person和_person的struct變體偏移就知道怎樣組合不同結構體實現繼承了。
- 重載、虛函數——你看上面的函數指針在“構造”函數才被指定。那麽你應該明確怎樣實現重載和虛函數了。
最後。這裏要說一下,我僅僅是在用C寫面向對象,並非要把C++或者Java的每一個語言級的面向對象特性都展示出來。當然要展示。C也全然能夠做得到。
我的項目是基於上面代碼的對象方式寫得。假設全然實現C++或者Java的全部面向對象特性。也不太有用。
2014年8月25日,收藏家和殺手——面向對象的C++和C(一)