編程人的「對象」長啥樣?

2020-12-18 CSDN

作者 | 編程指北 責編 | 張文

頭圖 | CSDN 下載自視覺中國

別誤會,今天要寫的不是我對象,畢竟我還沒有......

這篇文章主要是聊聊我對於程式語言中「對象」的一些簡單認識,

面向過程 VS 面向對象

為什麼 C 叫面向過程(Procedure Oriented)的語言,而 Java、C++ 之類叫面向對象(Object Oriented)呢?

之前聽到一個有趣的說法:

在 C 語言中我們是這樣寫代碼的:

function_a(yyy);function_b(xxx);

從左往右看過去,最先看到的是函數,也就是 Procedure,故叫做 Procedure Oriented。

而在 Java 這類語言我們通常是這樣的:

Worker worker = new Woker("小北");worker.touchFish("5分鐘");worker.coding("1小時");

第一眼看到的就是一個個的對象,所以叫做面向對象 Object Oriented。

回到正題。在 C 語言中,數據和操作數據的函數是互相分開的,你並不知道數據和函數之間有什麼關聯,這在語言層面上是不支持的。

在 C 語言中,編程就是將一堆以功能為核心導向的函數進行組合,依次調用這些函數就可以了。

這就叫面向過程,其實和我們思考問題的方式是吻合的。比如要實現一個貪吃蛇遊戲,那面向過程的設計思路就是首先分析問題的步驟:

開始遊戲隨機生成食物繪製畫面接收輸入並改變方向判斷是否碰到牆壁和食物等...而用面向對象的思路則是:

首先,將整個遊戲拆解為一個個的實體:蛇、食物、障礙物、規則系統、動畫系統。

其次,分別去實現這些實體應該具有的功能(即成員函數),同時你還要考慮不同實體之間如何交互和傳遞消息。說白了就是調用關係和傳參。

比如規則系統接收蛇、食物、障礙物作為參數,可以判定是否吃到食物或者碰到牆壁。

動畫系統則可以接收蛇、食物、障礙物等作為參數,然後在屏幕上動態的顯示出來。

這樣做的好處是:可以利用面向對象有封裝、繼承、多態性的特性,設計出低耦合的系統,使系統更加靈活、更加易於維護。

好了,上面這段大概可以看做八股文。你分別用 C 和 Java/C++ 寫過程序自然知道二者區別。不然我說再多的高內聚、低耦合也沒啥用。

對象如何實現?

對象的就是由一堆的屬性(成員變量)和一系列的方法(成員函數)組成。在講這個之前,先補充說明一個函數指針。

我們都知道函數在 C/C++、Java 這類語言中都不是一等公民,一等公民的意思就是能夠像其它整數、字符串變量一樣,可以被賦值或者作為函數參數、返回值等。但是在 JS、Python 這類動態語言中,函數卻是一等公民,可以作為參數、返回值等等。

究其原因,這類語言底層實現中,一切東西皆是對象。函數、整數、字符串、浮點數都是對象,函數才因此具備同其它基本類型一樣的一等公民的身份。

在 C/C++ 中,函數雖然是二等公民, 但我們可以通過函數指針來變相的實現將函數用於變量賦值、函數參數、返回值場景。

函數指針是啥?

我們知道普通變量申明後,編譯器就會自動分配一塊適合的內存。函數也是同樣的,編譯的時候會將一個函數編譯好,然後放在一塊內存中。

(上面這段說法實際很不準確,因為編譯器不會分配內存,編譯好的代碼也是以二進位的形式放在磁碟上,只有程序開始運行時才會加載到內存)

如果我們把函數的首地址也存儲在某個指針變量裡,就可以通過這個指針變量來調用所指向的函數了,這個存儲函數首地址的特殊指針就叫做函數指針

比如有一個函數 int func(int a);

我們如何申明一個可以指向 func 的函數指針呢?

int (*func_p)(int);

看起來有點奇怪,其實函數指針變量的聲明格式和同函數 func 的聲明一樣,只不過把 func 換成(*func_p)罷了。

為什麼要括號呢?因為不要括號的話 int *func_p(int); 就是申明一個返回指針的函數了,括號就是為了避免這種歧義

我們來多看幾個函數指針的申明吧:

int (*f1)(int); // 傳入int,返回int void (*f2)(char*); //傳入char指針,沒有返回值 double* (*f3)(int, int); //傳遞兩個整數,返回 double指針

來看一個函數指針的具體用處吧:

# include<stdio.h>typedefvoid(*work)() Work; // typedef 定義一種函數指針類型voidxiaobei_work(){printf("小北工作就是寫代碼");}voidshuaibei_work(){printf("帥北工作就是摸魚")}voiddo_work(Work worker){ worker();}intmain(void){ Work x_work = xiaobei_work; Work s_work = shuaibei_work; do_work(x_work); do_work(s_work);return0;}

輸出:

小北工作就是寫代碼帥北工作就是摸魚

其實這裡有點為了用函數指針而用了,不過大家應該體會到了,函數指針最大的優點就是將函數變量化了。

我們可以將函數作為參數傳遞給其它函數,於是就有了多態的雛形。我們可以傳遞不同的函數來實現不同的行為。

voidqsort(void* base, size_t num, size_t width, int(*compare)(constvoid*,constvoid*))

這是 C 標準庫中 qsort 函數的申明,它最後一個參數就要求傳入一個函數指針,這個函數指針負責比較兩個 element。

因為兩個元素的比較方式只有調用者才知道,所以這裡需要以函數指針的形式告訴 qsort 如何去判定兩個元素的大小。

好了,函數指針就簡單介紹到這裡,接下來回到主題,對象。

對象

那麼在 C 語言中如何簡單模擬一個對象呢?

當然只能靠結構體啦,而成員函數就可以通過函數指針來實現,其它的比如訪問控制、繼承等我們暫時不考慮。

struct Animal {char name[20];void (*eat)(struct Animal* this, char *food); // 成員方法 eatint (*work)(struct Animal* this); // 成員方法 工作};

但是 eat 和 work 都還沒有任何具體實現,所以我們可以在一個初始化函數中構造 Animal 對象。

voideat(struct Animal* this, char *food) { printf("%s 在吃 %s\n", this->name, food);};voidwork(struct Animal* this) { printf("%s 在工作\n", this->name);}struct Animal* Init(constchar *name) {struct Animal *animal = (struct Animal *)malloc(sizeof(struct Animal)); strcpy(animal->name, name); animal->eat = eat; animal->work = work;return animal;}

在 Init 函數內部我們就完成了「成員函數」的賦值和一些初始化工作,並且給 eat 和 work 兩個函數指針都綁定了具體的實現。

接下來我們可以使用一下這個對象:

intmain() {struct Animal *animal = Init("小狗"); animal->eat(animal, "牛肉"); animal->work(animal);return0;}

輸出:

小狗在吃牛肉小狗在工作

為什麼明明 animal 調用的 eat 方法,卻還要把 animal 當參數傳遞給 eat 方法呢,難道 eat 不知道是哪一個 Animal 調用的它嗎?

答案是確實不知道。對象其實就是在內存中一段有意義的區域,每一個不同的對象都有各自的內存位置

而他們的成員函數卻存放在代碼段,並且只會存在一份副本。

所以 animal->eat(...)調用方式和直接調用 eat(...),效果完全等同,那個animal 存在的意義就是讓你從面向過程轉變為面向對象思考,將方法調用轉變為對象間消息傳遞。

所以當調用成員函數的時候,我們還需要傳入一個參數 this,用來指代當前是哪個對象在調用。

由於 C 語言不支持面向對象,所以我們需要手動將 animal 作為參數傳遞給 eat、work 函數。

如果是在 C++ 這種面向對象的語言中,我們直接不用手動傳遞這個參數,就像下面這樣:

animal->eat(「牛肉」);animal->work();

實際上這是編譯器幫我們去做這個事,上面這兩行代碼,經過編譯器之後會變成下面這個樣子:

eat(animal, "牛肉");work(animal);

然後,編譯器還會在編譯階段默默地將 this 作為成員函數的一個形參添加到參數列表。

並且哪個對象調用的方法,那個對象就會被當做參數賦值給 this。

學習 Java 的的同學也一定對這個this非常熟悉吧,Java 中和 C++ 中的 this 基本都是一樣的作用。

或者說,幾乎所有的面向對象語言,都會存在一個類似的機制,來將調用對象隱式的傳遞給成員函數,比如 Python 中的對象定義:

classStu:def__init__(self, name, age):self.name = nameself.age = agedefdisplayStu(self): print "Name : ", self.name, ", Age: ", self.age

可以看到每個成員函數第一個參數都必須叫 self,這個 self 實際上就是和 this是一樣的作用。

只有這樣,當你在成員函數內訪問成員變量的時候,編譯器才知道你訪問的是哪一個對象。

誒,別忙,按照這樣說,那豈不是,如果我在成員函數內不訪問任何成員變量,就不需要傳遞這個 this 指針?

或者說可以傳遞一個空指針?

理論上確實成立,並且在 C++ 中也是可行的,比如下面這段代碼:

classStu{public:voidHello(){cout << "hello world" << endl; }private:char *name;int age;float score;};

由於,在 Hello 函數中沒有用到任何成員變量,所以我們甚至可以這樣玩:

Stu *stu = new Stu;stu->Hello(); // 正常對象,正常調用stu = NULL;stu->Hello() // 雖然 stu 為 NULL,但是依然不會發送運行時錯誤

這裡實際上可以這樣看:

stu->Hello(); 等價於Hello(NULL);

由於在 Hello 函數內部,沒有使用任何的成員變量,所以就不需要用 this 指針去定位成員變量的內存位置,在這種情況下,調用對象為不為 NULL 其實是不重要的。

但是如果 Hello 函數訪問了成員變量,比如:

voidHello(){cout << "Hello " << this->name << endl;}

這裡需要用到 this 去訪問 name 成員變量,那麼就會導致運行時程序發生 coredump,因為我們訪問了一個 NULL 地址,或者說是基於 NULL 偏移一定位置的地址,這段空間絕對是沒有訪問權限的。

恰好之前也有位同學在群裡問了這個問題:

這個問題的解釋就和上面的一樣,但是這個結論不能推廣到其它語言,比如 Java、Python。這些語言的虛擬機一般會做一些額外的檢查,比如判斷調用對象是否是空指針等,是的話就會觸發空指針異常。

而 C++ 就真的是很純粹的編譯成彙編,只要從彙編層面能跑通,那就沒問題,所以才能利用這個「奇技淫巧」。

那寫這篇文章的目的,就是想讓大家對「對象」有一個具體的認識,最好是明白對象在內存中或者 JVM 中是如何布局的。

我以前就會覺得對象挺神奇的,一堆的功能,後來才後知後覺,這不就是一個結構體再加上編譯器的語法糖嗎

相關焦點

  • 21 張思維導圖,檸檬哥肝了半個月的「後端技術學習路線」長啥樣?
    那如果你不是計算機相關專業的想轉行,也不要被嚇到了,畢竟這是人家四年時間的學習內容,本科的培養目標不僅僅是培養出一個軟體工程師,本科學習還是面向碩士博士的基礎培養,注意是計算機科學專業,名字裡有個詞叫「科學」,我這篇文章要說的 BAT 公司後臺軟體開發,可以認為是「工學」方向,更多的是服務於工程開發。
  • 鄉村小學第一堂電腦課,從「編程」開始
    或許是因為我們來的次數多了,所以只要看見穿著乾淨白T,衣服背後印著「夢想藝術課堂」字樣的人,孩子們都用奶音親切地稱他們為「老師」。4個小時的車程,保利粵東投資發展有限公司×一時半刻一行9人,時隔一個月,再次來到揭陽橋觀村小學。
  • 「食住玩」我們記憶裡的「財神到」還記得嗎?
    「食住玩」記憶裡「財神到」是啥樣的:八仙進寶、仙女送財。「食住玩」記憶裡「財神到」是啥樣的:手繪財神派送紅包「食住玩」記憶裡「財神到」是啥樣的:手繪財神派送紅包「食住玩」記憶裡「財神到」是啥樣的:手繪財神派送紅包「食住玩」記憶裡「財神到」是啥樣的:財神Cosplay合影「食住玩」記憶裡「財神到」是啥樣的:財神爺獻寶,散財童子。
  • 少兒編程賽道「樂碼王國」獲數百萬天使輪融資,創始人系氫創學員
    近日,氫創孵化輔導項目「樂碼王國」正式對外宣布獲得數百萬天使輪融資。「樂碼王國」是一站式少兒編程教育服務商,創始人席坤系氫創第三期學員。投資方為浙江成功之道教育集團。「樂碼王國」創始人席坤向氫創創業導師夏美帖發來天使輪融資成功的喜訊,並感謝氫創的培訓和投資人精準對接服務,席總在氫創第三期孵化班最後路演階段獲得第一名,是氫創資本重點輔導陪跑對象。「樂碼王國」AI少兒編程專注於推動中國AI少兒編程教育事業發展。
  • 成年人也可接受編程培訓,編程速成訓練營「Le Wagon」獲 1900 萬美元
    2013 年,「Le Wagon」在法國起步,創業的起點為成人編程教育市場,是一家專注於產品研發的編程速成訓練營。目前,「Le Wagon」編程訓練營已擴張到亞洲、中東、美洲和非洲地區。2019 年,隨著深圳分校的即將開業,「Le Wagon」的分校達到了 30 家。
  • 東北話做程式語言,好使、招人稀罕
    簡而言之,它是一門東北方言詞彙為基本關鍵字的程式語言。作者在項目開篇便強調了此編程需要「以人為本」:這玩意兒可是填補了世界方言編程地圖上的一大片兒空地啊!這麼說吧,誰要是看了 dongbei 程序能忍住了不笑,我敬他是純爺們兒!那它有啥特點咧?多了去了:簡單啊!小學文化程度就行。
  • 「比特農場」裡的極客巫師
    我是眼看著他從打籃球的瘦高個,到體重長了 30 斤,頭髮少了一半——你懂的,做程式設計師真是不容易。我是幹銷售的,不懂編程,但總是聽他叨叨這些年技術變化太快,AI 又帶來了新變數什麼的。其實他危機感很強,一邊幹活兒一邊學習,這兩年籃球反正是徹底荒廢了。他經常說的口頭語,就是「網際網路正在越來越難幹了」。
  • 現場|就在剛才,萌翻人的東航「達菲·聯萌」彩繪機正式亮相!長啥樣...
    今天下午2點,東方航空機隊中最新一架迪士尼彩繪在昆明長水機場正式亮相。這架被命名為「達菲·聯萌」號的東航 x 迪士尼聯名彩繪飛機,以目前在亞洲極受歡迎的迪士尼樂園人物達菲熊和他的朋友們為主題,這也是東航首次在波音飛機上噴塗迪士尼彩繪。
  • 到底該給孩子「補」點啥?
    是不是套路咱不去掰扯了,可媽媽們想把天底下一切最好的東西都給孩子的這份心讓人感動,不過感動歸感動,咱孩子真的需要「補」點啥嗎?我對她說:「對於6個月以下的寶寶來說,母乳可以滿足孩子的全部營養需求,不需要額外的營養補充。」那位媽媽半信半疑地看著我,我又補充道:「連水都不需要額外餵呢,而且這個口服液裡面糖分含量比較高,對孩子沒什麼好處。」 我曉之以理動之以情地說了半天,也不知道最後她聽沒聽進去。後來老媽跟我說:「你知道麼,你那勁頭特像推銷員!」
  • 有內味兒了,東北話做程式語言,好使、招人稀罕
    簡而言之,它是一門東北方言詞彙為基本關鍵字的程式語言。作者在項目開篇便強調了此編程需要「以人為本」: 這玩意兒可是填補了世界方言編程地圖上的一大片兒空地啊!這麼說吧,誰要是看了 dongbei 程序能忍住了不笑,我敬他是純爺們兒! 那它有啥特點咧?
  • 「CNC編程」2020年為什麼有的人經常吐槽機加工,卻還在幹?
    | (資深CNC工程師,聯繫作者本人可以長按上面圖片加微信 )在過去的2020年,朋友圈為什麼有的人經常吐槽機加工,卻還在幹? 因為這個世界上,20%的人佔據80%的財富,80%的草根只能搶奪剩下20%的財富。因為佔領頂端20%的人,在他們背後有學習,只是不過不為人知而已。如果你認為自已歸屬於80%人之內,做事當然是事倍功半了,所以感到做什麼都不順,這是必然的結果。
  • 真的是啥樣?
    「掃碼支付」與微信支付和支付寶等常用支付平臺類似。除此之外,數字人民幣還支持「碰一碰」支付,這令人們使用數字人民幣時,不需要網絡、不需要銀行帳號,只要兩個手機都裝有DC/EP數字錢包,就可以通過「碰一碰」實現轉帳功能,被稱為收支雙方「雙離線支付」,該功能基於智慧型手機的NFC(近場通信)模塊,以及銀聯的標籤支付技術。
  • 瓊恩·雪諾和龍母的孩子會長啥樣?讓StyleGAN告訴你
    但利用 GAN 測一測他們的孩子會長啥樣還是可以做到的。來看看機器學習的「相面之術」如何?你有沒有好奇過自己喜歡的電影或電視劇裡的人物性別變換後是長啥樣的?比如說,下面這位?權遊裡「弒君者」轉換性別的效果圖咦……長得真不咋樣,還是男性的詹姆斯好看。
  • 未來教室長啥樣?智慧機器人離我們多遠? 學交會暨教育展今日開幕
    未來教室長啥樣?智慧機器人離我們多遠? 來源:澎湃新聞 政務 未來教室長啥樣
  • 「學編程不為當碼農」AI 教育實踐從一場中小學機器人比賽說起
    比如,有的戰隊會採取「先下手為強」的辦法,將對方區域內的積木拋出來,讓對方「失分」。「操控機器人手指要特別靈活。」第二次參加這類機器人比賽的毛曉宇分享了他的訣竅。從賽前準備工作、賽中謀略布局,整個比賽都在考驗兩隊選手的邏輯編程、操控技術、策略執行和團隊配合能力。
  • 95後眼中的「國潮」長啥樣?
    6月22日19點,由第一財經商業數據中心(CBNData)旗下全新潮流態度IP「潮MOODS」發起的「漲潮計劃」線下啟動會在上海啟動。李寧在2018紐約時裝周秋冬秀場展示的「悟道」系列潮品 | 圖源:網絡95後眼中的「國潮」究竟有啥與眾不同?
  • 渾水摸「YY」、「俠盜」蘋果和辛巴的「麥乳精」|極客一周
    被做空一天後,歡聚時代發布聲明指責渾水對直播生態「無知」,做空報告邏輯不清,數據混亂,包含大量錯誤。但不得不說,相對於做空報告翔實的數據,歡聚時代的反駁聲明顯得有點無力了。不少業內人士看完了渾水報告之後表示,誰能想到一份這麼長的做空報告,「文筆竟然還挺好!
  • 直男斬、直女斬分別長啥樣?看看秦牛正威和孟美岐就知道了!
    比如在一眾仙女中,最早奪得「直男斬」稱號的蔡卓宜▼ 所以,今天就來聊聊「直男斬」「直女斬」型分別具有哪些特點?你更適合朝哪個方向發展?如何調節自己的異性緣和同性緣?
  • 編程進階之路:用簡單的面向對象編程提升深度學習原型
    將面向對象編程中那些簡單的概念(如函數化和類繼承),應用到深度學習原型代碼中,可以獲得巨大的收益。簡介本文的目標讀者是像我這樣沒有軟體工程師背景的數據科學家和機器學習(ML)從業者,而非經驗豐富的軟體工程師。
  • 曾將馬斯克「踢」出局的人工智慧公司 Opan AI 都做啥了?
    就在前天,特斯拉 CEO 埃隆·馬斯克連發數條推特,主要是想告訴網友,「我和這個叫 Open AI 的公司早就沒關係了」。面對這些消息,網友們的反應大致可以分為兩類。一種是「嗯?不是說好了要一起保護人類嗎?你為啥要離開?」