日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

c++ class struct同名_C/C++面向对象编程之封装

發布時間:2024/7/5 c/c++ 39 豆豆
生活随笔 收集整理的這篇文章主要介紹了 c++ class struct同名_C/C++面向对象编程之封装 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
點擊“藍字”關注我們吧前言:

何為面向過程:

面向過程,本質是“順序,循環,分支”

面向過程開發,就像是總有人問你要后續的計劃一樣,下一步做什么,再下一步做什么,意外、事物中斷、突發事件怎么做。理論上來說,任何一個過程都可以通過“順序,循環,分支”來描述出來,但是實際上,很多項目的復雜度,都不是“順序循環分支”幾句話能說清楚的。稍微大一點的項目,多線程,幾十件事情并發, 如果用這種最簡單的描述方式,要么幾乎無法使用,缺失細節太多,要么事無巨細,用最簡單的描述,都會讓后期復雜度提升到一個爆炸的狀態。

何為面向對象:

面向對象,本質是“繼承,封裝,多態”

面向對象的核心是把數據和處理數據的方法封裝在一起。面向對象可以簡單的理解為將一切事物模塊化 ,面向對象的代碼結構,有效做到了層層分級、層層封裝,每一層只理解需要對接的部分,其他被封裝的細節不去考慮,有效控制了小范圍內信息量的爆炸。然而當項目的復雜度超過一定程度的時候,模塊間對接的代價遠遠高于實體業務干活的代價, 因為面向對象概念的層級劃分,要實現的業務需要封裝,封裝好跟父類對接。多繼承是萬惡之源,讓整個系統結構變成了網狀、環狀,最后變成一坨亂麻。

Erlang 的創建者 JoeArmstrong 有句名言:

面向對象語言的問題在于,它們依賴于特定的環境。你想要個香蕉,但拿到的卻是拿著香蕉的猩猩,乃至最后你擁有了整片叢林。

能解決問題的就是最好的:

程序設計要專注于“應用邏輯的實現”本身,應該盡量避免被“某種技術”分心 。《UNIX編程藝術》,第一原則就是KISS原則,整本書都貫徹了KISS(keep it simple, stupid!) 原則。寫項目、寫代碼,目的都是為了解決問題。而不是花費或者說浪費過多的時間在考慮與要解決的問題完全無關的事情上。不管是面向過程,還是面向對象,都是為了解決某一類問題的技術。各有各的用武之地:

在驅動開發、嵌入式底層開發這些地方,面向過程開發模式,干凈,利索,直觀,資源掌控度高。在這些環境,面向過程開發幾乎是無可替代的。

在工作量大,難度較低、細節過多、用簡單的規范規則無法面面俱到的環境下,用面向對象開發模式,用低質量人力砸出來產業化項目。

1、面向對象編程

面向對象只是一種設計思路,是一種概念,并沒有說什么C++是面向對象的語言,java是面向對象的語言。C語言一樣可以是面向對象的語言,Linux內核就是面向對象的原生GNU C89編寫的,但是為了支持面向對象的開發模式,Linux內核編寫了大量概念維護modules,維護struct的函數指針,內核驅動裝載等等機制。而C++和java為了增加面向對象的寫法,直接給編譯器加了一堆語法糖。

2、什么是類和對象

在C語言中,結構體是一種構造類型,可以包含若干成員變量,每個成員變量的類型可以不同;可以通過結構體來定義結構體變量,每個變量擁有相同的性質。
在C++語言中,類也是一種構造類型,但是進行了一些擴展,可以將類看做是結構體的升級版,類的成員不但可以是變量,還可以是函數;不同的是,通過結構體定義出來的變量還是叫變量,而通過類定義出來的變量有了新的名稱,叫做對象(Object)在 C++ 中,通過類名就可以創建對象,這個過程叫做類的實例化,因此也稱對象是類的一個實例(Instance)類的成員變量稱為屬性(Property),將類的成員函數稱為方法(Method)。在C語言中的使用struct這個關鍵字定義結構體,在C++ 中使用的class這個關鍵字定義類。

結構體封裝的變量都是 public 屬性,類相比與結構體的封裝,多了 private 屬性和 protected ?屬性, private 和protected ?關鍵字的作用在于更好地隱藏了類的內部實現 ,只有類源代碼才能訪問私有成員,只有派生類的類源代碼才能訪問基類的受保護成員,每個人都可以訪問公共成員。這樣可以有效的防止可能被不知道誰訪問的全局變量。

C語言中的結構體:

1//通過struct?關鍵字定義結構體
2struct?object3{
4????char?name[8];?????????????
5????char?type;????????????????
6????char?flag;???????????????
7????//指向函數的指針類型
8????void??(*display)(void);???????????
9};

C++語言中的類:

1//通過class關鍵字類定義類
2class?object{
3public:
4????char?name[8];????????????
5????char?type;??????????????
6????char?flag;???????????????
7????//類包含的函數體
8????void?display(){
9????????printf("123456789");
10????}
11};

3、內存分布的對比

不管是C語言中的結構體或者C++中的類,都只是相當于一個模板,起到說明的作用,不占用內存空間;結構體定義的變量和類創建的對象才是實實在在的數據,要有地方來存放,才會占用內存空間。

結構體變量的內存模型:
結構體的內存分配是按照聲明的順序依次排列,涉及到內存對齊問題。
為什么會存在內存對齊問題,引用傻孩子公眾號裸機思維的文章《漫談C變量——對齊》加以解釋:

在ARM Compiler里面,結構體內的成員并不是簡單的對齊到字(Word)或者半字(Half Word),更別提字節了(Byte),結構體的對齊使用以下規則:

  • 整個結構體,根據結構體內最大的那個元素來對齊。比如,整個結構體內部最大的元素是WORD,那么整個結構體就默認對齊到4字節。

  • 結構體內部,成員變量的排列順序嚴格按照定義的順序進行。

  • 結構體內部,成員變量自動對齊到自己的大小——這就會導致空隙的產生。

  • 結構體內部,成員變量可以通過 attribute ((packed))單獨指定對齊方式為byte。

strut對象的內存模型:

1//通過struct?關鍵字定義結構體
2struct?{
3????uint8_t????a;
4????uint16_t???b;
5????uint8_t????c;
6????uint32_t?? d;
7};

memory layout:

class對象的內存模型:
假如創建了 10 個對象,編譯器會將成員變量和成員函數分開存儲:分別為每個對象的成員變量分配內存,但是所有對象都共享同一段函數代碼,放在code區。如下圖所示:


成員變量在堆區或棧區分配內存,成員函數放在代碼區。對象的大小只受成員變量的影響,和成員函數沒有關系。對象的內存分布按照聲明的順序依次排列,和結構體非常類似,也會有內存對齊的問題。

可以看到結構體和對象的內存模型都是非常干凈的,C語言里訪問成員函數實際上是通過指向函數的指針變量來訪問(相當于回調),那么C++編譯器究竟是根據什么找到了成員函數呢?
實際上C++的編譯代碼的過程中,把成員函數最終編譯成與對象無關的全局函數,如果函數體中沒有成員變量,那問題就很簡單,不用對函數做任何處理,直接調用即可。
如果成員函數中使用到了成員變量該怎么辦呢?成員變量的作用域不是全局,不經任何處理就無法在函數內部訪問。
C++規定,編譯成員函數時要額外添加一個this指針參數,把當前對象的指針傳遞進去,通過this指針來訪問成員變量。

this 實際上是成員函數的一個形參,在調用成員函數時將對象的地址作為實參傳遞給 this。不過 this 這個形參是隱式的,它并不出現在代碼中,而是在編譯階段由編譯器默默地將它添加到參數列表中。

這樣通過傳遞對象指針完成了成員函數和成員變量的關聯。這與我們從表明上看到的剛好相反,通過對象調用成員函數時,不是通過對象找函數,而是通過函數找對象。
這在C++中一切都是隱式完成的,對程序員來說完全透明,就好像這個額外的參數不存在一樣。

無論是C還是C++,其函數第一個參數都是一個指向其目標對象的指針,也就是this指針,只不過C++由編譯器自動生成——所以方法的函數原型中不用專門寫出來而C語言模擬的方法函數則必須直接明確的寫出來

4 掩碼結構體

在C語言的編譯環境下,不支持結構體內放函數體,除了函數外,就和C++語言里定義類和對象的思路完全一樣了。還有一個區別是結構體封裝的對象沒有好用的private 和protected屬性,不過C語言也可以通過掩碼結構體這個騷操作來實現private 和protected的特性。

注:此等操作并不是面向對象必須的,這個屬于錦上添花的行為,不用也不影響面向對象。

先通過一個例子直觀體會一下什么是掩碼結構體,以下例子來源為:傻孩子的PLOOC的readme,作者倉庫地址:https://github.com/GorgonMeducer/PLOOC

1//!?the?original?structure?in?class?source?code
2struct?byte_queue_t?{
3????uint8_t???*pchBuffer;
4????uint16_t??hwBufferSize;
5????uint16_t??hwHead;
6????uint16_t??hwTail;
7????uint16_t??hwCount;
8};
9
10//!?the?masked?structure:?the?class?byte_queue_t?in?header?file
11typedef?struct?byte_queue_t?{
12????uint8_t?chMask?[sizeof(struct?{
13????????uint8_t???*pchBuffer;
14????????uint16_t??hwBufferSize;
15????????uint16_t??hwHead;
16????????uint16_t??hwTail;
17????????uint16_t??hwCount;
18????})];
19}?byte_queue_t;

為了使其工作,我們必須確保類源代碼不包括其自己的接口頭文件。您甚至可以這樣做…如果您對內容很認真

1//!?the?masked?structure:?the?class?byte_queue_t?in?header?file
2typedef?struct?byte_queue_t?{
3????uint8_t?chMask?[sizeof(struct?{
4????????uint32_t????????:?32;
5????????uint16_t????????:?16;
6????????uint16_t????????:?16;
7????????uint16_t????????:?16;
8????????uint16_t????????:?16;
9????})];
10}?byte_queue_t;

通過這個例子,我們可以發現給用戶提供的頭文件,其實是一個固態存儲器,即使用字節數組創建的掩碼,用戶通過掩碼結構體創建的變量無法訪問內部的成員,這就是實現屬性私有化的方法。至于如何實現只有類源代碼才能訪問私有成員,只有派生類的類源代碼才能訪問基類的受保護成員的特性,這里先埋個伏筆,關注本公眾號,后續文章再深入探討。

還回到掩碼結構體本身的特性上,可以發現一個問題,掩碼結構體丟失了結構體的對齊信息,因為掩碼的本質是創建了一個chMask數組,我們知道數組是按照元素對齊的,而原本結構體是按照Word對齊的。所以當你用掩碼結構體聲名結構體變量的時候,這個變量多半不是對齊到word的,當你在模塊內訪問這個對象的時候…編譯器默認你整個結構體是對齊到word,這就會導致錯位的產生,可能會直接導致hardfault了!

為了解決這個問題,可以利用_ attribute_ ((align))以及 _ alignof_的操作,對它進行如下改進:

1//!?the?original?structure?in?class?source?code
2struct?byte_queue_t?{
3????struct??{???????????????????????????????????????????????????????????????\
4????????????uint8_t???*pchBuffer;
5????????????uint16_t??hwBufferSize;
6????????????uint16_t??hwHead;
7????????????uint16_t??hwTail;
8????????????uint16_t??hwCount;?????????????????????????????????????????????????????????\
9???????????????}__attribute__((aligned(__alignof__(struct?{uint8_t???*pchBuffer;
10????????????uint16_t??hwBufferSize;
11????????????uint16_t??hwHead;
12????????????uint16_t??hwTail;
13????????????uint16_t??hwCount;}))));???
14};
15
16//!?the?masked?structure:?the?class?byte_queue_t?in?header?file
17typedef?struct?byte_queue_t?{
18????????????uint8_t?chMask??????????????????\
19????????????????[sizeof(struct?{uint8_t???*pchBuffer;
20????????????uint16_t??hwBufferSize;
21????????????uint16_t??hwHead;
22????????????uint16_t??hwTail;
23????????????uint16_t??hwCount;})]??????????????????????????????\
24????????????????__attribute__((aligned(__alignof__(struct?{uint8_t???*pchBuffer;
25????????????uint16_t??hwBufferSize;
26????????????uint16_t??hwHead;
27????????????uint16_t??hwTail;
28????????????uint16_t??hwCount;}))));???????????????????????????????????????\
29}?byte_queue_t;

這部分理解起來可能稍微有點復雜,但是不理解也沒關系,現在先知道有這個東西,后續文章還會有更騷的操作來更直觀的實現封裝、繼承和多態!

5 C語言實現類的封裝

如果你趟過了掩碼結構體那條河,那么恭喜你,你已經成功上岸了。我們繼續回到面向對象的問題上,面向對象的核心是把數據和處理數據的方法封裝在一起。封裝并不是只有放在同一個結構體里這一種形式,放在同一個接口頭文件里(也就是.h)里,也是一種形式——即,一個接口頭文件提供了數據的結構體,以及處理這些數據的函數原型聲明,這已經完成了面向對象所需的基本要求。下邊將通過C語言的具體實例加以說明。

假設我們要封裝一個基于字節的隊列類,不妨叫做Queue,因此我們建立了一個類文件queue.c和對應的接口頭文件queue.h。假設我們約定queue.c將不包含queue.h(這么做的好處很多,在以后的內容里再講解,當然對掩碼結構體技術來說,模塊的實現是否包含模塊的接口頭文件并不是關鍵)。

queue.h

1...
2//!?the?masked?structure:?the?class?byte_queue_t?in?header?file
3typedef?struct?queue_t?{
4????????????uint8_t?chMask??????????????????\
5????????????????[sizeof(struct?{uint8_t???*pchBuffer;
6????????????uint16_t??hwBufferSize;
7????????????uint16_t??hwHead;
8????????????uint16_t??hwTail;
9????????????uint16_t??hwCount;})]??????????????????????????????\
10????????????????__attribute__((aligned(__alignof__(struct?{uint8_t???*pchBuffer;
11????????????uint16_t??hwBufferSize;
12????????????uint16_t??hwHead;
13????????????uint16_t??hwTail;
14????????????uint16_t??hwCount;}))));???????????????????????????????????????\
15}?queue_t;
16
17...
18extern?bool?queue_init(queue_t?*ptQueue,?uint8_t?*pchBuffer,?uint16_t?hwSize);
19extern?bool?enqueue(queue_t?*ptQueue,?uint8_t?chByte);
20extern?bool?dequeue(queue_t?*ptQueue,?uint8_t?*pchByte);
21extern?bool?is_queue_empty(queue_t?*ptQueue);
22...

queue.c

1...
2//!?the?original?structure?in?class?source?code
3typedef?struct?__queue_t?{
4????struct??{???????????????????????????????????????????????????????????????\
5????????????uint8_t???*pchBuffer;
6????????????uint16_t??hwBufferSize;
7????????????uint16_t??hwHead;
8????????????uint16_t??hwTail;
9????????????uint16_t??hwCount;?????????????????????????????????????????????????????????\
10???????????????}__attribute__((aligned(__alignof__(struct?{uint8_t???*pchBuffer;
11????????????uint16_t??hwBufferSize;
12????????????uint16_t??hwHead;
13????????????uint16_t??hwTail;
14????????????uint16_t??hwCount;}))));???
15}__queue_t;
16...

可以看到,實際上類型queue_t是一個掩碼結構體,里面只有一個起到掩碼作用的數組chMask,其大小和真正后臺的的類型__queue_t相同——這就是掩碼結構體實現私有成員保護的秘密。解決了私有成員保護的問題,剩下還有一個問題,對于queue.c的函數來說queue_t只是一個數組,那么正常的功能要如何實現呢?下面的代碼片將斷為你解釋一切:

1...
2#define?__class(__NAME)??????????????????__##__NAME
3#define?class(__NAME)???????????????????__class(__NAME)???
4bool?is_queue_empty(queue_t?*ptQueue) 5{
6????CLASS(queue_t)?*ptQ?=?(CLASS(queue_t)?*)ptQueue;
7????if?(NULL?==?ptQueue)?{
8????????return?true;
9????}
10????return?((ptQ->hwHead?==?ptQ->hwTail)?&&?(0?==?ptQ->hwCount));
11}
12...

可以從這里看出來,只有類的源文件才能看到內部使用的結構體,而掩碼結構體是模塊內外都可以看到的,簡單來說,如果實際內部的定義為外部的模塊所能直接看見,那自然就沒有辦法起到保護作用。

從編譯器的角度來說,這種從queue_t到__queue_t類型指針的轉義是邏輯上的,并不會因此產生額外的代碼,簡而言之,使用掩碼結構體幾乎是沒有代價的。

再次強調:實現面向對象,掩碼結構體并不是必須的,只是錦上添花,所以不理解的話,也不要糾結!

想要更深入了解C語言面向對象的思想,建議參考的書籍:《UML+OOPC嵌入式C語言開發精講》

點擊下方“藍字”,發現更多精彩。

STM32通用Bootloader——FOTA

STM32通用FLASH管理軟件包——SFUD/FAL

STM32通用低功耗組件——PM

長按關注我們CSDN博客:Aladdin Wang微信號:17630350805“在看”的小可愛永遠十八歲!

總結

以上是生活随笔為你收集整理的c++ class struct同名_C/C++面向对象编程之封装的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。