C++程式開發技巧

来源:https://www.cnblogs.com/BlickWinkel/archive/2023/06/04/17453337.html
-Advertisement-
Play Games

## 引言 > 類(class)的使用分為兩種——基於對象(object Based)和麵向對象(object oriented) > > 基於對象是指,程式設計中單一的類,和其他類沒有任何關係 > > 單一的類又分為:不帶指針的類(class without pointer members)和帶指 ...


引言

類(class)的使用分為兩種——基於對象(object Based)和麵向對象(object oriented)

基於對象是指,程式設計中單一的類,和其他類沒有任何關係

單一的類又分為:不帶指針的類(class without pointer members)和帶指針的類(class with pointer members)

面向對象則是類(class)中涉及了類之間的關係:複合(composition)、委托(delegation)、繼承(inheritance)

1.頭文件的防禦式聲明

#ifndef xxx
#define xxx
...
#endif

在編寫頭文件時應該有這樣的一種習慣

目的是避免多次重覆包含同一個頭文件,否則會引起變數及類的重覆定義

2.使用初始化列表的好處

  • 只有構造函數這類函數具有“初始化列表”這一特性
  • 從結果上來看,構造函數時使用初始化列表和在類內賦值是一樣的,但我們都知道一個變數必須先初始化然後才被賦值,而初始化列表顧名思義是只執行初始化這一步,在類內賦值時就要先初始化再被賦值,所以從執行效率上講,使用初始化列表會更快,也更簡潔

3.設計模式:singleton(單例類)

  • demo:
class A {
public:
    static A & getInstance();
    setup() {...}
private:
    A();
    A(const A & rhs);
    ...
}

A & A::getInstance()
{
    static A a;
    return a;
}

...
//外部介面
A::getInstance().setup();
  • 原理:將構造函數設置為私有屬性,同時設置一個靜態函數介面返回一個該類對象

  • 作用:保證每一個類僅有一個實例,併為它提供一個全局訪問點

  • 單例模式(Singleton)的主要特點不是根據用戶程式調用生成一個新的實例,而是控制某個類型的實例唯一性。它擁有一個私有構造函數,這確保用戶無法通過new直接實例它。除此之外,該模式中包含一個靜態私有成員變數instance與靜態公有方法Instance()。Instance()方法負責檢驗並實例化自己,然後存儲在靜態成員變數中,以確保只有一個實例被創建。

    這種模式主要有以下特征或條件:

    1. 有一個私有的無參構造函數,這可以防止其他類實例化它,而且單例類也不應該被繼承,如果單例類允許繼承那麼每個子類都可以創建實例,這就違背了Singleton模式“唯一實例”的初衷。
    2. 單例類被定義為sealed,就像前面提到的該類不應該被繼承,所以為了保險起見可以把該類定義成不允許派生,但沒有要求一定要這樣定義。
    3. 一個靜態的變數用來保存單實例的引用。
    4. 一個公有的靜態方法用來獲取單實例的引用,如果實例為null即創建一個。
  • 參考:

    設計模式詳解:Singleton(單例類)_singleton類_p_帽子戲法的博客-CSDN博客

    單例模式(Singleton)的6種實現 - JK_Rush - 博客園 (cnblogs.com)

4.常成員函數的重要性

  • 如果一個成員函數不改變類的數據成員時,就把它聲明為常函數,這是一個好的習慣

  • 當實例化一個常對象時,常對象要求不能改變數據成員,如果成員函數不加const,將無法調用此成員函數,編譯器不會通過,即使此函數確實沒有改變數據成員;同時,即使成員函數被聲明為了常函數,實例化一個普通對象時依然可以調用。

  • 簡單來說,不聲明為常成員函數可能不會有問題,但聲明為常成員函數能確保一定不出問題

5.如何解釋成員函數接收同類對象參數...

  • 問:如何解釋一個類的成員函數在接收同類對象的參數(比如拷貝構造函數)可以直接調用該對象的任何成員,明明既不是友元也不是嵌套?

  • 答:相同class的各個objects互為friends(友元)

6.設計一個類要考慮什麼

  • 目的:高效、安全、簡潔、嚴密
  • 1.數據成員私有
  • 2.參數傳遞和返回值優先考慮用引用(傳遞的是地址值,這樣不管傳遞的數據記憶體占用多大,依然固定傳入四個位元組,即使當傳遞字元這樣小於四個位元組時用值傳遞確實比引用或指針傳遞更快一些,但不必考慮這些細枝末節)
  • 3.構造函數優先去使用初始化列表
  • 4.能聲明為常成員就聲明為常成員

7.返回值加不加引用

  • 取決於返回的值是否要改變、是否可以改變,前者由我們決定,後者由語法限制

兩個案例

class A
{
    int value;
    ...
};

...
    
A& fun1(A* x, const A& y)
{
    x.value += y.value; //第一參數會改變,第二參數不會改變
    return *x
}
class B
{
    int value;
    ...
};

...

B fun2(const B& x, const B& y)
{
	//第一參數和第二參數都不會改變
	return B(x.value + y.value);
}

8.運算符重載成員函數的思考

e.x.

inline complex&
_doapl(comlex* ths, const complex& r)
{
	...
	return *ths;
}

inline complex&
complex::operator += (const comlex& r)
{
	return _doapl(this, r);
}

...

comlex c1(2,1), c2(3), c3;
//c2 += c1;
//c3 += c2 += c1;

當重載一個二元運算符為成員函數時,我們知道重載函數除了右操作數是我們傳遞的,函數還會預設用一個this指針,來接收左操作數

那麼可能會有疑問,我們想要改變的是左操作數,而且由於傳遞的是指針,函數內也確實可以改變,那返回值又有什麼用呢,聲明為空不就行了。

當我們使用重載運算符時只是像被註釋的第一行代碼一樣,那麼返回值確實不重要,但是當我們使用的形式像被註釋的第二行代碼時,那麼返回值就很重要,因為c2.+=(c1) 這個函數的返回值就是 c3 += () 函數的參數

9.temp object(臨時對象)

  • 語法: typename ();
  • 生存期:僅聲明那一行
  • 返回值是臨時對象時不難return by reference

10.<<重載的一些註意點

  • 只能重載為非成員函數

  • 左操作數固定為系統定義的 ostream 類型,且為非常量引用

    • 非常量:向流寫入內容其實就是改變了它的狀態
    • 引用:無法複製一個 ostream 對象
    • 註:ostream 類與 istream 類一樣,它的的拷貝構造函數和賦值函數都是保護類型的,所以 ostream 是不允許拷貝或者賦值的,所以它也不能直接作為返回類型和參數傳遞,很多時候需要使用引用來進行傳遞。
  • 最好加返回值且為引用,原因前面已經說明,且我們對於連續調用<<的頻率要大得多

  • 連續調用時的調用順序

    complex c1, c2;
    cout << c1 << c2;
    //先執行 <<(cout, c1) 函數
    //返回的 ostream類型的cout的引用 又作為<<(ostream &, c2)的第一參數 
    
  • 在語法上我們當然也可以重載為成員函數,只要左操作數為自定義類型即可,但這樣並不符合我們通常的書寫習慣

11.Big Three(三位一體原則)

  • 三大件:拷貝構造、拷貝賦值、析構函數

  • 解釋:當一個類需要我們去主動設計析構函數時,那它很大概率也需要一個拷貝構造函數和賦值運算符重載成員函數

  • 應用:當一個類具有指針成員時(class with point member)或者說當我們設計了一個有動態記憶體管理的類時

  • 原因:

    • 析構函數角度:預設析構函數會僅刪除指向對象的指針,而刪除一個指針不會釋放指針指向對象占用的記憶體,最終會導致記憶體泄露

    • 拷貝構造角度:預設的構造函數是淺拷貝,複製的只是指針也就是地址值,這樣導致兩個對象共用一個記憶體空間,這是十分危險的,當其中一個對象被刪除後,析構函數將釋放那片共用的記憶體空間,接下來對這片已經釋放了記憶體的任何引用都將會導致不可遇見的後果。

    • 賦值運算角度:

      賦值相比於拷貝構造要考慮更多

      首先是自我賦值判斷,如果不判斷,當左右操作數指向的是同一個地址時,會造成將左操作數對象的元素刪除並釋放其占用的記憶體,同時由於左右操作數指向同一對象,導致右操作數同時被刪除,但接下來還要將右操作對象複製,這會造成不可預知的結果。這也被稱為證同測試。

      其次是進行三步必要操作:

      • 釋放已有記憶體
      • 開闢新的記憶體
      • 內容賦值
  • 代碼示例:

#ifndef __MYSTRING__
#define __MYSTRING__

class String
{
public:                                 
   String(const char* cstr=0);//構造函數
   String(const String& str);//拷貝構造函數           
   String& operator=(const String& str);//重載=運算符 
   ~String();//析構函數                               
   char* get_c_str() const { return m_data; }//成員函數,返回指向字元數組首地址的指針
private:
   char* m_data;//字元數組指針
};

#include <cstring>

//構造函數
inline
String::String(const char* cstr)
{
   //開闢記憶體、計算長度、內容拷貝
   if (cstr) {
      m_data = new char[strlen(cstr)+1];
      strcpy(m_data, cstr);
   }
   else {   
      m_data = new char[1];
      *m_data = '\0';
   }
}

//析構函數
inline
String::~String()
{
   delete[] m_data;//釋放指針指向空間
}

//重載=
inline
String& String::operator=(const String& str)
{
   //檢測自我賦值(self assignment)
   if (this == &str)
      return *this;

   delete[] m_data;
   m_data = new char[ strlen(str.m_data) + 1 ];
   strcpy(m_data, str.m_data);
   return *this;
}

//構造函數
inline
String::String(const String& str)
{
   m_data = new char[ strlen(str.m_data) + 1 ];
   strcpy(m_data, str.m_data);
}

#include <iostream>
using namespace std;

//重載<<
ostream& operator<<(ostream& os, const String& str)
{
   os << str.get_c_str();
   return os;
}

#endif

12.new和構造函數,delete和析構函數

  • 當我們使用new創建了一個指向類的對象的指針時

    這裡的new幹了三件事:

    • 調用 operator new 函數,這個函數內部又調用了malloc函數來分配記憶體
    • operator new 函數返回的是空指針,顯式轉換為類類型指針後賦值給我們創建的指針
    • 調用指針指向對象的構造函數

  • 當我們使用delete釋放一個指向對象的指針時
    • 首先調用對象的析構函數,這裡該類的析構函數又用delete釋放了類內動態分配的數組指針
    • 然後再釋放這個指向對象的指針
    • 結合本例來看,就是delete ps,先delete它指向的成員,再delete它自己

13.malloc()動態分配記憶體的結構

  • 紅色部分是 cookie ,記錄記憶體分配的總大小,就是圖中的41,其最低位用於表示是否已分配(1表示已分配,0表示已回收),之所以最低位可以變,是因為分配的記憶體總空間一定是16的倍數,其16進位表示時最低位一定為0,也就是說這個位置是空出來的,剛好用來表示記憶體狀態。每一個 new 的對象都會有上下兩個 cookie,來預先申請一塊記憶體池,然後供對象實例化。

  • 綠色部分是調用malloc()時向系統申請的記憶體,該函數返回時,也會返回這塊區域開頭的指針。

  • 綠色部分上下兩塊 gap 預先被填充為了0xfdfdfdfd,用來分隔客戶可以使用的記憶體區和不可使用的記憶體區,同時,當這塊記憶體被歸還時,編輯器也可以通過下gap的值區判斷當前記憶體塊是否被越界使用了

  • 從gap向上連續的7個記憶體空間共同組成了debug header,從上向下標號為1-7

    • 1、2兩塊空間保存了兩根指針,目的是使多個記憶體塊連接成鏈表。
    • 3空間保存了申請本記憶體塊的文件名
    • 4空間保存了申請本記憶體塊的代碼行數
    • 5空間記錄了本記憶體塊中實際可以被用戶使用的記憶體空間的大小
    • 6空間記錄了當前記憶體塊的流水號,即是鏈表中的第幾個,從1開始
    • 7空間記錄了當前記憶體塊被分配的形式
  • 填補區pad

參考:

https://zhuanlan.zhihu.com/p/492161361

https://www.cnblogs.com/zyb993963526/p/15682014.html#_label2

https://blog.csdn.net/qq_61500888/article/details/122170203

14.array new 搭配 array delete

動態分配數組時要註意的:

  • 其記憶體區域相比上面所提到的多了一個記憶體塊用來記錄數組長度(分配對象數量)
  • 當申請記憶體後,返回的指針指向數據開始處,而使用 delete[] 釋放時,指針會指向它的上一塊,也就是記錄數組長度的那一塊,從而可以根據對象的數量調用相應次數的析構函數。如果使用 delete 釋放的話,它不會去獲取對象的長度,而是只調用指針指向的那一個對象的析構函數。
  • 如果對象的類型是內置類型或者是無自定義的析構函數的類類型,是可以使用 delete 來釋放 new[] 對象的。但是,如若不然,使用 delete 來釋放對象,對象所分配的記憶體空間雖然會照樣全部釋放,但是只會調用第一個對象的析構函數,這就導致記憶體泄漏。所以,養成良好的習慣,new [] 必 delete []

15.this指針

類的每一個非靜態成員函數(包括構造函數、拷貝構造等)都隱含著一個指針形參名為this,當對象調用成員函數時就會隱含傳遞該對象的地址給它,這也是為什麼一個類的成員函數雖然只有一份但也會根據接收的消息不同產生不同的行為,而靜態成員函數不隱含this指針,所以即使調用它的對象不同維護的依然是同一段代碼

16.namespace

三個案例:

  • 使用using引入一個命名空間的全部
  • 使用using引入一個命名空間的個體
  • 不用using,使用時手動引入


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在一些複雜的業務表中間查詢數據,有時候操作會比較複雜一些,不過基於SqlSugar的相關操作,處理的代碼會比較簡單一些,以前我在隨筆《基於SqlSugar的開發框架循序漸進介紹(2)-- 基於中間表的查詢處理》介紹過基於主表和中間表的聯合查詢,而往往實際會比這個會複雜一些。本篇隨筆介紹聯合多個表進行... ...
  • 一、功能變數名稱解析過程 DNS功能變數名稱完整解析過程 1、查詢本地 hosts文件 解析記錄 2、查詢客戶端本地DNS緩存記錄 3、訪問DNS轉發(緩存)伺服器本地緩存記錄 4、轉發到權威伺服器查詢本地緩存記錄 5、訪問權威伺服器解析記錄 6、權威伺服器迭代查詢 6.1、訪問子域權威伺服器查詢本地緩存記錄 6. ...
  • # ulimit Linux ulimit命令用於控制shell程式的資源。 ```shell [email protected] 10:41:17 [pwd:~]# ulimit --help ulimit: ulimit [-SHabcdefiklmnpqrstuvxPT ...
  • ## 01|修改Win用戶名 打開運行,輸入cmd,回車; 輸入control userpasswords2,回車; 點擊屬性,修改用戶名,點擊確定; 打開運行,輸入regedit,回車; 定位到HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\Cur ...
  • [TOC](快速上手kettle(三)壺中可以放些啥?) ### 序言 快速上手kettle開篇中,我們將kettle比作壺,並對這個壺做了簡單介紹。 而上一期中我們實現了①將csv文件通過kettle轉換成excel文件; ②將excel文件通過kettle寫入到MySQL資料庫表中 這兩個案例。 ...
  • # mysql編碼錯誤 ![image-20230604115322661](https://img2023.cnblogs.com/blog/2862884/202306/2862884-20230604115422584-1121547495.png) 問題出現在用django的admin組件向 ...
  • # **鎖** ## **概述** - **介紹** 鎖是電腦協調多個進程或線程併發訪問某一資源的機制。在資料庫中,除傳統的計算資源(CPU、RAM、I/O)的爭用以外,數據也是一種供許多用戶共用的資源。如何保證數據併發訪問的一致性、有效性是所有資料庫必須解決的一個問題,鎖衝突也是影響資料庫併發訪 ...
  • # 基於 Web 和 Deep Zoom 的高解析度大圖查看器的實踐 高解析度大圖像在 Web 中查看可以使用 Deep Zoom 技術,這是一種用於查看和瀏覽大型高解析度圖像的技術,它可以讓用戶以交互方式瀏覽高解析度大圖像,並且能夠在不影響圖像質量的情況下進行縮放和平移操作。 ## 技術點 ### ...
一周排行
    -Advertisement-
    Play Games
  • 前言 插件化的需求主要源於對軟體架構靈活性的追求,特別是在開發大型、複雜或需要不斷更新的軟體系統時,插件化可以提高軟體系統的可擴展性、可定製性、隔離性、安全性、可維護性、模塊化、易於升級和更新以及支持第三方開發等方面的能力,從而滿足不斷變化的業務需求和技術挑戰。 一、插件化探索 在WPF中我們想要開 ...
  • 歡迎ReaLTaiizor是一個用戶友好的、以設計為中心的.NET WinForms項目控制項庫,包含廣泛的組件。您可以使用不同的主題選項對項目進行個性化設置,並自定義用戶控制項,以使您的應用程式更加專業。 項目地址:https://github.com/Taiizor/ReaLTaiizor 步驟1: ...
  • EDP是一套集組織架構,許可權框架【功能許可權,操作許可權,數據訪問許可權,WebApi許可權】,自動化日誌,動態Interface,WebApi管理等基礎功能於一體的,基於.net的企業應用開發框架。通過友好的編碼方式實現數據行、列許可權的管控。 ...
  • Channel 是乾什麼的 The System.Threading.Channels namespace provides a set of synchronization data structures for passing data between producers and consume ...
  • efcore如何優雅的實現按年分庫按月分表 介紹 本文ShardinfCore版本 本期主角: ShardingCore 一款ef-core下高性能、輕量級針對分表分庫讀寫分離的解決方案,具有零依賴、零學習成本、零業務代碼入侵適配 距離上次發文.net相關的已經有很久了,期間一直在從事java相關的 ...
  • 前言 Spacesniffer 是一個免費的文件掃描工具,通過使用樹狀圖可視化佈局,可以立即瞭解大文件夾的位置,幫助用戶處理找到這些文件夾 當前系統C盤空間 清理後系統C盤空間 下載 Spacesniffer 下載地址:https://spacesniffer.en.softonic.com/dow ...
  • EDP是一套集組織架構,許可權框架【功能許可權,操作許可權,數據訪問許可權,WebApi許可權】,自動化日誌,動態Interface,WebApi管理等基礎功能於一體的,基於.net的企業應用開發框架。通過友好的編碼方式實現數據行、列許可權的管控。 ...
  • 一、ReZero簡介 ReZero是一款.NET中間件 : 全網唯一開源界面操作就能生成API , 可以集成到任何.NET6+ API項目,無破壞性,也可讓非.NET用戶使用exe文件 免費開源:MIT最寬鬆協議 , 一直從事開源事業十年,一直堅持開源 1.1 純ReZero開發 適合.Net Co ...
  • 一:背景 1. 講故事 停了一個月沒有更新文章了,主要是忙於寫 C#內功修煉系列的PPT,現在基本上接近尾聲,可以回頭繼續更新這段時間分析dump的一些事故報告,有朋友微信上找到我,說他們的系統出現了大量的http超時,程式不響應處理了,讓我幫忙看下怎麼回事,dump也抓到了。 二:WinDbg分析 ...
  • 開始做項目管理了(本人3年java,來到這邊之後真沒想到...),天天開會溝通整理需求,他們講話的時候忙裡偷閑整理一下常用的方法,其實語言還是有共通性的,基本上看到方法名就大概能猜出來用法。出去打水的時候看到外面太陽好好,真想在外面坐著曬太陽,回來的時候好兄弟三年前送給我的鍵盤D鍵不靈了,在打"等待 ...