<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
物件導向是一種思維方式,基本上用什麼語言都是可以實現的。C語言的程式設計方式一般是程式導向的,但是也是可以實現物件導向的。物件是什麼?什麼又是物件導向?物件導向的三大特性又怎麼實現,且聽我細細道來。
此物件非彼物件,雖然有時候此物件又可以是你腦袋中的物件,那讓我們從我們誤解的物件開始瞭解吧,雖然我沒有,但是用一下自己的直男思維,想想一個物件也是可以滴。那我就進入一下我這個直男腦袋中的物件吧!我有一個物件,這個物件呢,膚白貌美大長腿。用詩中的話就是“皓腕凝霜雪,壚邊人似月”,美麗的江南女子,誰不喜歡呢。既然是想象,物件不只是僅僅是膚白貌美大長腿,還得愛你,在你寂寞的時候能陪你,在你難過時能在你身邊,在你打遊戲的時候不會無理取鬧。這樣的物件多好哇,簡直就是夢中情人,可惜只存在想想中(嘆氣)。
通過上面的例子,想象中的物件,它具有了物件的特徵,是不算非常符合人類的特性,易懂。那讓我們從上面的例子提取出來物件的普遍特徵。
特徵一:屬性
讓我們回到我想象中的物件,物件是怎樣的,皓腕凝霜雪,壚邊人似月,這個是物件的屬性,也就是樣子。當然屬性不只是樣子啦,你可以新增更多的屬性,比如聲音好聽,年齡20歲等等。
特徵二:行為
物件具有的動作就是行為。在上面的例子就是,物件非常愛你,難過的時候能陪你,寂寞的時候也能陪你等等,就是這個物件具有的動作,物件能幹什麼。
我們知道了物件是什麼,但是你有沒有發現這個物件是很廣泛的,也就是我想象中的物件不知有一個,符合我想象中物件的特徵可以多個,也就是我可以想象又很多個物件。我可以想象有”後宮佳麗三千人“,這三個都符合我物件的特徵。這些特徵就是類,也就是符合我想象的人不只是只有一個,可以有多個,只要這個人符合我想象的特徵,她就是在這個類下面的。
那麼類與物件的關係又是怎樣的?物件就是符合這個類的特例,怎麼理解呢?
在我的想象中,符合大美女的屬性有很多,但是我不知道具體是誰,但是有一天我在動漫中看見了這個人,她叫小A。小A就是在大美女類下面的一個物件。又有一天,我又碰到一個人,也符合我定義的大美女,這個物件,她叫小B。小A和小B都是符合定義的,也就是在這個類下面的,而小A和小B是一個特例,也就是這個類下面的人,是獨一無二的。
老生常談,先簡單看看程式導向的程式設計方式是怎樣的?設想一個場景,刺激點的。有一天傑哥想你了,打算和你回家一起打電動,那他該怎麼做才能邀請到你回家一起打電動?
程式導向的解決方式:簡單點的方式簡化一下
1:他首先西裝革履,打扮的人模狗樣,看起來十分帥氣,小姐姐看了表示很贊
2:打車到你家
3:盛情邀約
物件導向的解決方式又是怎樣的呢:我們看他邀請你涉及幾個物件,打車物件,邀請物件,打電動物件,回家物件。那物件是怎麼做的呢?那讓我們看一下執行順序:傑哥首先呼叫了打扮的物件進行了打扮,然後呼叫打車物件去了你家。到了你家後呼叫了邀請物件的行為發出了邀請,然後你呼叫了邀請物件的行為拒絕了傑哥,傑哥呼叫了情緒的物件的行為,發出了很難過的感覺。
打扮物件:
打車物件:
邀請物件:
回家物件:
情緒:
通過上面的例子,大概瞭解到了與程式導向的區別了,物件導向的程式設計方式的單元是物件,做了什麼事情也是以物件執行動作。物件可以被很多物件呼叫,傑哥可以呼叫邀請物件中的邀請行為,你也可以呼叫邀請物件發出拒絕邀請的動作。物件的屬性是怎樣的,怎樣定義是靈活的。
看到上面的例子:物件導向的程式設計方式=程式導向+物件導向。物件將某一些行為高度封裝,然後由指揮官也就是我們自己按照自己的想法按照某個順序呼叫(程式導向),在過程中,物件之間會進行一定的資料互動與一定的物件之間的行為呼叫。
再舉個例子:實現一個循跡小車
構建物件:感測器 控制器 小車
小車物件:
控制器:
感測器:
//虛擬碼 void Follow_mark(void) { 呼叫感測器物件進行測量,將測量值儲存到器測量值 呼叫控制器物件,將感測器測量值作為輸入引數,計算得到結果進行儲存 呼叫小車物件,根據控制傳入的控制值,計算當前偏移量,然後根據偏移量呼叫左轉/右轉行為 }
瞭解了物件導向的思想,思想是最重要的,特徵是次要的。物件導向具有三大特徵,我們或多或少都可以實現,java,python,C++都有,但是C也是可以實現的,只是會比較麻煩,三大特徵分別是封裝,繼承,多型。這三大特徵能夠幫助實現物件導向的程式設計,使得物件導向變得更優雅。我們先了解三大特徵之三大特徵之封裝。
封裝就是將物件的特徵進行封裝,使之物件的屬性和行為只能通過物件進行存取。在上面的例子中,邀請的物件,它的屬性與行為是被封裝好的,我們只能呼叫邀請這個物件才能呼叫邀請物件的行為。
優勢:
1、隱藏內部細節,類似函數,只需要呼叫這個邀請物件的行為發出邀請,而不需要知道里面的底層實現
2、更安全,複用性更好。物件的值都是被封裝好的,隱藏掉的,一般是程式設計師只會提供相應的介面來存取,不能直接修改。複用性,從上面的例子,誰都可以呼叫物件的行為。
基礎版不涉及函數指標與函數表,先學習這個基礎版的,理解好物件導向的最簡單的封裝的實現。
在實現前我們先想一想C到底有什麼結構可以實現封裝屬性,各種屬性。這個很簡單,結構體嘛,能放各種型別的屬性。
行為又怎麼體現呢,可以實現各種行為,函數嘛。後面的多型會涉及函數指標,使用函數指標可以實現多型,這都是後面的事情,後面的文章會有簡介。
那讓我們做一個PID控制器的物件吧,如果不懂的小夥伴也沒關係,這個只是控制器,有輸入,輸出,偵錯引數,瞭解這些就行了。具體實現過程,內部細節不懂也沒關係,這個不重要,我程式碼會標出來的。
那我們直接閱讀程式碼,進入困難模式:程式碼會有比較詳細的註釋,很容易看懂!
//開始構造物件,既然是控制器,物件必須具有輸入,輸出,偵錯引數 //屬性就是:引數值,輸入值,輸出值 //行為就是:設定引數,檢視引數,根據輸入計算輸出,構造物件,刪除物件 //屬性:用結構體實現 #include "stdio.h" #include <string.h> #include <stdlib.h> //控制器物件 //控制器物件屬性 typedef struct { int input;/*控制器輸入*/ int ouput;/*控制器輸出*/ int P_parameter,I_parameter,D_parameter;/*控制偵錯引數*/ int Sum_error;/*總偏差,位置式PID積分相關的引數*/ int Last_error;/*上次偏差,位置式PID積分相關的引數*/ }controller; //構造物件,初始化 controller *Ctor_controller(void) { controller *temp; temp=(controller *)malloc(sizeof(controller)); //清零 memset(temp,0,sizeof(controller)); return temp; } //刪除物件 void Del_ontroller(controller * const Me) { if(Me!=NULL) free(Me); } //設定控制器引數 void Write_controller(controller * const Me,int P,int I,int D) { Me->P_parameter=P; Me->I_parameter=I; Me->D_parameter=D; } //讀取控制器引數的值 controller Read_controller(const controller * const Me,int P,int I,int D) { return (*Me); } //計算控制器輸出,細節看不懂沒關係,只需要知道傳入的是偏差,就會有輸出一個計算結果就行,這個結果能夠幫助控制 //至於偏差怎麼定義什麼時候需要用到PID控制器就知道了 int Out_controller(controller * const Me,int input) { float iIncpid=0; int now_error=input;//當前偏差 Me->Sum_error+=input; //積分量限幅,方式積分飽和過深 if(Me->Sum_error >500) { Me->Sum_error = 500 ; } if(Me->Sum_error < -500) { Me->Sum_error = -500 ; } Me->ouput=Me->P_parameter * input // P +Me->I_parameter * Me->Sum_error // I +Me->D_parameter * (now_error-Me->Last_error); // D Me->Last_error=now_error; // 儲存誤差,用於下次計算 return(Me->ouput); // 返回計算值 } int main() { controller *test; controller read_val; //構造,建立一個物件 test=Ctor_controller(); //設定物件的值 Write_controller(test,1,1,1); //檢視物件的值 read_val=Read_controller(test,1,1,1); printf("物件 P= %d I=%d D=%d rn",read_val.P_parameter,read_val.I_parameter,read_val.D_parameter); //呼叫控制器一次: printf("控制器輸出=%d rn",Out_controller(test,100)); //刪除/銷燬一個物件 Del_ontroller(test); }
輸出結果:
物件 P= 1 I=1 D=1
控制器輸出=300
從上面的例子可以看出來,我直接呼叫物件,就可以實現封裝,設定,檢視等,注意使用了需要手動呼叫刪除,不然容易出現記憶體漏失,物件的生存時間就是我們程式設計師自己釋放前的時間。
這裡是使用堆的方式,容易出現記憶體溢位的情況,如果是微控制器等其他資源較小的單元,可以使用其他方式構造物件,比如下面:物件的生存時間就是主函數的結束時間,編譯器替我們釋放了物件的資源,不需要我們主動進行釋放。
#include "stdio.h" #include <string.h> #include <stdlib.h> //控制器物件 //控制器物件屬性 typedef struct { int input;/*控制器輸入*/ int ouput;/*控制器輸出*/ int P_parameter,I_parameter,D_parameter;/*控制偵錯引數*/ int Sum_error;/*總偏差,位置式PID積分相關的引數*/ int Last_error;/*上次偏差,位置式PID積分相關的引數*/ }controller; //構造物件,初始化 void Ctor_controller(controller * const Me) { //清零 memset(Me,0,sizeof(controller)); } //刪除物件 void Del_ontroller(controller * const Me) { ; } //設定控制器引數 void Write_controller(controller * const Me,int P,int I,int D) { Me->P_parameter=P; Me->I_parameter=I; Me->D_parameter=D; } //讀取控制器引數的值 controller Read_controller(const controller * const Me,int P,int I,int D) { return (*Me); } //計算控制器輸出,細節看不懂沒關係,只需要知道傳入的是偏差,就會有輸出一個計算結果就行,這個結果能夠幫助控制 //至於偏差怎麼定義什麼時候需要用到PID控制器就知道了 int Out_controller(controller * const Me,int input) { float iIncpid=0; int now_error=input;//當前偏差 Me->Sum_error+=input; //積分量限幅,方式積分飽和過深 if(Me->Sum_error >500) { Me->Sum_error = 500 ; } if(Me->Sum_error < -500) { Me->Sum_error = -500 ; } Me->ouput=Me->P_parameter * input // P +Me->I_parameter * Me->Sum_error // I +Me->D_parameter * (now_error-Me->Last_error); // D Me->Last_error=now_error; // 儲存誤差,用於下次計算 return(Me->ouput); // 返回計算值 } int main() { controller test; controller read_val; //構造,建立一個物件 Ctor_controller(&test); //設定物件的值 Write_controller(&test,1,1,1); //檢視物件的值 read_val=Read_controller(&test,1,1,1); printf("物件 P= %d I=%d D=%d rn",read_val.P_parameter,read_val.I_parameter,read_val.D_parameter); //呼叫控制器一次: printf("控制器輸出=%d rn",Out_controller(&test,100)); }
到進階版,才能夠完整的看到封裝的實現,封裝裡面就具有了物件的屬性與行為。這裡我們通過函數指標存取物件的行為,我們可以通過函數指標存取物件的行為。
那具體行為是怎麼實現的呢?實現是通過函數表中的函數指標來存取函數,以此來實現不同函數的呼叫,從而實現物件的行為。
那讓我們看一下程式碼實現,然後分析指標指向就知道函數是怎麼實現的。
標頭檔案 :定義了物件的屬性與行為
#ifndef __OOP_H #define __OOP_H //控制器物件 struct controller_vtbl; typedef struct { //物件屬性 int input;/*控制器輸入*/ int ouput;/*控制器輸出*/ int P_parameter,I_parameter,D_parameter;/*控制偵錯引數*/ int Sum_error;/*總偏差,位置式PID積分相關的引數*/ int Last_error;/*上次偏差,位置式PID積分相關的引數*/ //物件行為指標,通過指標存取函數 struct controller_vtbl *vptr; }controller; //物件的行為所在表,定義物件的行為在這裡,通過定義函數指標指向需要實現物件行為的指標 struct controller_vtbl { controller * (*Ctor_controller)(void); void (*Del_controller)(controller * const Me); controller (*Read_controller)(const controller * const Me); void (*Write_controller)(controller * const Me,int P,int I,int D); int (*Out_controller)(controller * const Me,int input); }; //物件行為函數 controller * Ctor_controller(void); void Del_controller(controller * const Me); controller Read_controller(const controller * const Me); void Write_controller(controller * const Me,int P,int I,int D); int Out_controller(controller * const Me,int input); #endif
原始檔::具體函數的行為屬性的實現就在這裡
//開始構造物件,既然是控制器,物件必須具有輸入,輸出,偵錯引數 //屬性就是:引數值,輸入值,輸出值 //行為就是:設定引數,檢視引數,根據輸入計算輸出,構造物件,刪除物件 //屬性:用結構體實現 #include "stdio.h" #include <string.h> #include <stdlib.h> #include "temp.h" //構造物件,初始化 controller * Ctor_controller(void) { controller *ptr; struct controller_vtbl *table; ptr=(controller *)malloc(sizeof(controller)); table=(struct controller_vtbl *)malloc(sizeof(struct controller_vtbl)); //清零 memset(ptr,0,sizeof(controller)); table->Ctor_controller=&Ctor_controller; table->Del_controller=&Del_controller; table->Out_controller=&Out_controller; table->Write_controller=&Write_controller; table->Read_controller=&Read_controller; ptr->vptr=table; return ptr; } //刪除物件/解構物件 void Del_controller(controller * const Me) { if(Me!=NULL) { free(Me->vptr); free(Me); } } //設定控制器引數 void Write_controller(controller * const Me,int P,int I,int D) { Me->P_parameter=P; Me->I_parameter=I; Me->D_parameter=D; } //讀取控制器引數的值 controller Read_controller(const controller * const Me) { return (*Me); } //計算控制器輸出,細節看不懂沒關係,只需要知道傳入的是偏差,就會有輸出一個計算結果就行,這個結果能夠幫助控制 //至於偏差怎麼定義什麼時候需要用到PID控制器就知道了 int Out_controller(controller * const Me,int input) { float iIncpid=0; int now_error=input;//當前偏差 Me->Sum_error+=input; //積分量限幅,方式積分飽和過深 if(Me->Sum_error >500) { Me->Sum_error = 500 ; } if(Me->Sum_error < -500) { Me->Sum_error = -500 ; } Me->ouput=Me->P_parameter * input // P +Me->I_parameter * Me->Sum_error // I +Me->D_parameter * (now_error-Me->Last_error); // D Me->Last_error=now_error; // 儲存誤差,用於下次計算 return(Me->ouput); // 返回計算值 } int main() { controller *test; controller read_val; //構造,建立一個物件,返回物件指標 test=Ctor_controller(); //設定物件的值 test->vptr->Write_controller(test,1,1,1); //檢視物件的值 read_val=test->vptr->Read_controller(test); printf("物件 P= %d I=%d D=%d rn",read_val.P_parameter,read_val.I_parameter,read_val.D_parameter); //呼叫控制器一次: printf("控制器輸出=%d rn",test->vptr->Out_controller(test,100)); //刪除/銷燬一個物件 test->vptr->Del_controller(test); }
執行結果:
物件 P= 1 I=1 D=1
控制器輸出=300
可以看到,我們這次操作物件並不是直接呼叫函數,而是通過指標的方式來存取具體的哪個函數,而指標是在建立的物件裡面的,這樣就可以直接通過物件來存取它的行為。
後面實現多型也是使用了函數指標的方式,在多型裡面這裡的指標與行為表有了它自己的名字,就是虛指標與虛表。
知道了什麼是物件,物件與類的關係,以及如何實現物件導向的封裝,下一篇將瞭解到物件導向的繼承,如何用C語言來實現,例子依然是PID控制器,不過會對控制器進行繼承,更高階的實現。
本篇文章就到這裡了,希望能夠給你帶來幫助,也希望您能夠多多關注it145.com的更多內容!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45