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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...