C 語言面向對象的封裝方式(示例)

      網友投稿 914 2025-03-31

      前一篇文章《C 語言面向對象的封裝方式》,我介紹了C語言編程常見的兩種代碼組織方式:


      1)函數和數據結構分離

      2)封裝

      并從原理上講述這兩種方式的根本區別。

      大型項目中,推薦采用封裝的方式,有利于團隊協作和每個模塊獨立演進。

      本文,給出一個代碼示例,具體展示這兩種方式在代碼實現上的差別。

      業務場景描述如下:

      對于數據庫、文件系統、存儲系統等,數據通常以頁(Page)為單位,在數據文件中進行組織。服務進程以頁為最小IO單位從磁盤上讀出,并在內存中緩存這個頁面。后續業務過程如果讀取的數據在這個頁中,則直接從內存頁獲取數據。如果要寫入新數據,或者要更改原來的數據,則需要找到剩余空間能滿足新寫入數據大小的Page,然后在目標頁中進行寫入或者數據修改的動作。由于頁上可能會有并發讀寫操作,因此需要注意加鎖。

      一個數據頁(Page)的內部結構,示例如下:(postgresql的數據頁)

      PageHeader 是頁頭部的控制信息,其結構如下:

      我們實現數據寫入(insert)和scan過濾兩種場景,看用兩種不同的代碼組織方式,在代碼實現上的差別(代碼進行了簡化,不考慮事務等復雜因素)。

      (方式一) 函數和數據結構分離

      調用者的邏輯:

      向Page中寫入一行數據:

      int insert_row(...) { // 計算要寫入的行所需的空間大小 int row_size = ....; // 找到剩余空間滿足這個要求的Page page_header_t* phpage = find_page(row_size); // 加鎖,避免并發寫 lock_write(phpage); // 寫入row header char* pbegin = (char*)phpage + pheader->pd_upper - row_size; row_header_t* phrow = (row_header_t*)pbegin; phrow->len = row_size - sizeof(row_header_t); phrow->xxx = ....; // 寫入row的各個列數據 char* pcol = (char*)phrow + sizeof(row_header_t); memcpy(pcol, ....); .... // 找到空閑的itemid槽位 item_id_t* pitemid = (item_id_t*)((char*)phpage + sizeof(page_header_t)); while (LP_UNUSED != pitemid->flags) pitemid++; // 寫入row對應的槽位信息 pitemid->offset = (char*)phrow - (char*)phpage; pitemid->LP_NORMAL; // 解鎖 unlock_write(phpage); return SUCCESS; }

      讀取page中的各行記錄,根據條件過濾(比如 where id>2):

      int scan_row(page_header_t* phpage, filter...) { // 加讀鎖 lock_read(phpage); // 遍歷itemid, 只查找有效行 item_id_t* pitemid = (item_id_t*)((char*)phpage + sizeof(page_header_t)); while (LP_UNUSED != pitemid->flags) { if (LP_NORMAL == pitemid->flags) { // 有效行 row_header_t* phrow = (row_header_t*)((char*)phpage + pitemid->offset); // 判斷此行是否滿足過濾條件 ...... } pitemid++; } // 解鎖 unlock_read(phpage); }

      從上面的數據讀寫兩個函數,我們可以看出,在函數和數據結構分離的模式下的特點:

      1)數據結構的內部成員,對調用者都是可見的,都是可讀寫的;

      上例中,page_header_t, item_id_t, row_header_t這些數據結構內部的成員,調用者都是知道的,并且可以直接讀寫的。

      2)數據結構之間的關系,調用者也是知道的;

      上例中,page_header_t的后面就是一系列row_id_t, 這些row_id_t的后面是空閑空間,每行數據從page后面逐個向前分配空間;

      3) 調用者根據1)和2)的信息,自己來編寫自己的業務邏輯,對這些數據結構的成員進行讀寫控制,最終形成各種業務函數。

      4)需要多少個業務函數,是調用者來決定的。調用者可以根據業務需求,隨時增減業務函數。

      這種模式下,需要數據結構保持長期穩定狀態。一旦數據結構發生變化,那么調用者設計的各種業務函數,基本都需要修改,哪怕只是把數據結構的一個成員名字修改了,也會導致大量的函數無法通過編譯。

      而工程實踐中,需求變更是常態,尤其是做有技術競爭力的產品,對底層數據結構、實現邏輯進行大幅度優化改進,也是經常發生的。

      因此,我們更希望用封裝模式,來組織代碼。

      這兩種方式的根本區別,用下圖來展示:

      在封裝的設計下,代碼上有了一些顯著的變化:

      1)數據結構的成員,對調用者不可見。調用者無法直接對數據結構的成員變量進行讀寫,只能通過特定的函數來操作這些數據結構。

      這些操作函數,是數據結構的設計者提供的,我們把這些操作函數叫接口函數。

      C 語言面向對象的封裝方式(示例)

      2)接口函數,不僅屏蔽掉數據結構的成員變量,還屏蔽掉數據結構之間的關系,甚至有些數據結構調用者根本就不知道其存在。

      3)接口函數,是面向使用者進行設計,而非面向底層數據結構進行設計。對業務場景進行分析,提取共性,進行接口抽象,最終形成接口函數。

      4)接口函數的函數名、參數類型和返回值,都要充分體現業務語義,屏蔽底層數據結構的具體實現細節。

      再回到對數據頁(Page)進行讀寫操作的例子上,我們用封裝的思想,重新設計一下代碼:

      服務層:

      1. 數據結構體 page_header_t,item_data_t, row_header_t 的成員結構無需調整,但我們需要把它們的定義放到.c文件中,這樣調用者就不能直接訪問他們的成員了。可以用指針,但不能用指針訪問其成員。

      2. 設計接口函數,封裝業務語義,主要有:

      寫入行:

      1) 為了寫入,要獲取滿足空間的頁面,返回已經鎖定的頁面

      page_header_t* find_page_for_write(int row_size);

      2) 寫入row數據

      int write_row_to_page(page_header_t* phpage, void* pdata, int row_size);

      3)釋放鎖定的頁面

      int release_page_for_write(page_header_t* phpage);

      讀取page,返回各個行數據:

      1)準備掃描page(內部加讀鎖)

      page_scan_t page_scan_begin(page_header_t* phpage);

      2)掃描下一行, 返回0 表示找到一個有效行

      int page_scan_next(page_scan_t* pscan, void** prow, int* row_size);

      3) 結束掃描(內部釋放鎖)

      int page_scan_end(page_scan_t* pscan);

      業務層:

      調用者的代碼,重新設計為:

      int insert_row(...) { // 計算要寫入的行所需的空間大小 int row_size = ....; // 找到剩余空間滿足這個要求的Page page_header_t* phpage = find_page_for_write(row_size); // 寫入row int iret = write_row_to_page(phpage, pdata, row_size); // 釋放頁面 iret = release_page_for_write(phpage); return iret; }

      讀取page中的各行記錄,根據條件過濾(比如 where id>2):

      int scan_row(page_header_t* phpage, filter...) { page_scan_t scan = page_scan_begin(phpage); void* prow = NULL; int row_size = 0; while (!page_scan_next(&scan, &prow, &row_size)) { // 應用filter,判斷此行是否滿足條件 ...... } page_scan_end(&scan); return SUCCESS; }

      我們看到,通過封裝,調用者的代碼邏輯更加清晰簡潔,并且與底層數據結構充分解耦,各自可以獨立演化,互相不影響。

      因此,大型項目,強烈推薦采用封裝的方式進行代碼組織設計。

      我的微信號 "實力程序員",歡迎大家關注我。

      C 語言 數據結構 面向對象編程

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:如何讓電腦文檔始終以wps打開
      下一篇:excel表格如何將多個工作簿窗口合并
      相關文章
      精品亚洲国产成人av| 亚洲精品无码中文久久字幕| 99亚洲男女激情在线观看| 亚洲乱码一二三四区麻豆| 亚洲精品美女久久久久| 久久亚洲精品国产精品| 亚洲美女视频一区| 亚洲毛片免费视频| 亚洲美女人黄网成人女| 亚洲综合无码一区二区三区| 亚洲高清中文字幕综合网| 亚洲毛片基地日韩毛片基地| 亚洲春色另类小说| 亚洲国产精品yw在线观看| 亚洲一级毛片免费看| 97se亚洲国产综合自在线| 亚洲依依成人亚洲社区| 亚洲精品乱码久久久久久蜜桃图片| 亚洲性无码AV中文字幕| 亚洲乱理伦片在线观看中字| 亚洲av第一网站久章草| 日韩亚洲精品福利| 亚洲精品乱码久久久久久不卡| 亚洲国产午夜福利在线播放| 久久久久国产成人精品亚洲午夜 | 亚洲精品天堂在线观看| 亚洲中文字幕无码av永久| 亚洲综合色婷婷在线观看| 亚洲av无码成人影院一区| 国产AV日韩A∨亚洲AV电影| 亚洲午夜无码片在线观看影院猛| 在线亚洲97se亚洲综合在线| 久久精品国产精品亚洲艾草网| 久久亚洲精品成人av无码网站| 亚洲国产午夜电影在线入口| 亚洲日韩国产二区无码| 国产精品亚洲一区二区无码| 亚洲人成无码网站久久99热国产| 中国亚洲女人69内射少妇| 亚洲AV无码AV男人的天堂| 亚洲熟妇无码久久精品|