《c++入門經典》筆記13

来源:https://www.cnblogs.com/adapter-chen/archive/2020/07/19/13340177.html
-Advertisement-
Play Games

第十三章 高級引用和指針 13.1按引用傳遞以提高效率 ​ 每次將值按對象傳入函數是,都將創建該對象的一個備份。每次按值從函數返回一個對象時,也將創建其備份。 ​ 對於用戶創建的大型對象,備份的代價很高。這將增加程式占用的記憶體量,而程式的運行速度將更慢。 ​ 在棧中,用戶創建的對象的大小為其成員變數 ...


第十三章 高級引用和指針

13.1按引用傳遞以提高效率

​ 每次將值按對象傳入函數是,都將創建該對象的一個備份。每次按值從函數返回一個對象時,也將創建其備份。

​ 對於用戶創建的大型對象,備份的代價很高。這將增加程式占用的記憶體量,而程式的運行速度將更慢。

​ 在棧中,用戶創建的對象的大小為其成員變數的大小之和,而每個成員變數本身也可能是用戶創建的對象。從性能和記憶體耗用方面說,將如此大的結構傳入棧中的代價非常高。

​ 當然,還有其它代價。對於您創建的類,每次創建備份時,編譯器都將調用一個特殊的構造函數:複製構造函數(拷貝構造函數)。

​ 對於大型對象,調用這些構造函數和析構函數在速度和記憶體耗用方面的代價非常高。

程式清單13.1 ObjectRef.cpp

#include <iostream>

class SimpleCat {
public:
  SimpleCat();            //預設構造函數
  SimpleCat(SimpleCat &); //拷貝構造函數
  ~SimpleCat();
};

SimpleCat::SimpleCat() {
  std::cout << "Simple Cat Constructor ..." << std::endl;
}

SimpleCat::SimpleCat(SimpleCat &) {
  std::cout << "Simple Cat Copy Constructor ..." << std::endl;
}

SimpleCat::~SimpleCat() {
  std::cout << "Simple Cat Destructor ..." << std::endl;
}

SimpleCat FunctionOne(SimpleCat theCat);
SimpleCat *FunctionTwo(SimpleCat *theCat);

int main() {
  std::cout << "Making a Cat ..." << std::endl;
  SimpleCat simpleCat;
  std::cout << "Calling FunctionOne..." << std::endl;
  FunctionOne(simpleCat);
  std::cout << "Calling FunctionTwo..." << std::endl;
  FunctionTwo(&simpleCat);
  return 0;
}

SimpleCat FunctionOne(SimpleCat theCat) {
  std::cout << "Function One. Returning ..." << std::endl;
  return theCat;
}
SimpleCat *FunctionTwo(SimpleCat *theCat) {
  std::cout << "Function Two. Returning ..." << std::endl;
  return theCat;
}

​ 可以看到程式中FunctionOne按值傳遞,FunctionTwo按址傳遞,FunctionOne的調用會產生一次拷貝構造,返回值是SimpleCat類型,所以也會產生一次拷貝構造,然後FunctionOne結束時,兩個拷貝出來的對象(一個是調用時產生,一個是返回時產生)就會被析構函數進行析構。

​ 調用FunctionTwo,因為參數是按引用傳遞,所以不會進行複製備份,也就不會調用拷貝構造函數,所以也就不會有輸出。所以結果中,FunctionTwo所觸發的輸出語句僅有Function Two. Returning ...一句,且並未調用拷貝構造和析構函數。

13.2傳遞const指針

這部分其實前面十一章的筆記有提到。

​ 雖然將指針傳遞給函數效率更高(比如上面的程式),但這也使得對象有被修改的風險。按值傳遞雖然效率較低,但是只是將複製品傳遞,所以並不會影響到原品,也就較按址傳遞多了一層保護。

​ 要同時獲得按值傳遞的安全性和按址傳遞的效率,解決的辦法是傳遞一個指向該類常量的指針(也可為常量的常量指針,即const 類型 *const 指針變數),這樣就不能修改所指向對象的值(如果是常量的常量指針,不僅不能修改所指向對象的值,也不能修改自身的值,即不能指向其它對象)

程式清單13.2 ConstPasser.cpp

#include <iostream>

class SimpleCat {
private:
  int itsAge;

public:
  SimpleCat();
  SimpleCat(SimpleCat &);
  ~SimpleCat();

  int getAge() const { return itsAge; }
  void setAge(int age) { itsAge = age; }
};

SimpleCat::SimpleCat() {
  std::cout << "Simple Cat Constructor ..." << std::endl;
  itsAge = 1;
}

SimpleCat::SimpleCat(SimpleCat &) {
  std::cout << "Simple Cat Copy Constructor ..." << std::endl;
}

SimpleCat::~SimpleCat() {
  std::cout << "Simple Cat Destructor ..." << std::endl;
}

const SimpleCat *const
FunctionTwo(const SimpleCat *const simpleCat); //指向常量的常量指針

int main() {
  std::cout << "Making a cat ..." << std::endl;
  SimpleCat simpleCat;
  std::cout << "simpleCat is now " << simpleCat.getAge() << " years old"
            << std::endl;
  int age = 5;
  simpleCat.setAge(age);
  std::cout << "simpleCat is now " << simpleCat.getAge() << " years old"
            << std::endl;
  std::cout << "Calling FunctionTwo..." << std::endl;
  FunctionTwo(&simpleCat);
  std::cout << "simpleCat is now " << simpleCat.getAge() << " years old"
            << std::endl;
  return 0;
}

const SimpleCat *const FunctionTwo(const SimpleCat *const simpleCat) {
  std::cout << "Function Two. Returning ..." << std::endl;
  std::cout << "simpleCat is now " << simpleCat->getAge() << " years old"
            << std::endl;
  // simpleCat->setAge(9);//報錯,無法對const類型對象進行修改
  return simpleCat;
}

​ 如果將註釋掉的代碼去掉註釋,程式則無法通過,所以可見將參數設置為常量的指針或者是常量的常量指針這種方式兼顧了址傳遞的高效與值傳遞的安全。

13.3作為指針替代品的引用

​ 將上一個程式重寫,使用引用而非指針。

程式清單13.3 RefPasser.cpp

#include <iostream>

class SimpleCat {
private:
  int itsAge;

public:
  SimpleCat();
  SimpleCat(SimpleCat &);
  ~SimpleCat();

  int getAge() const { return itsAge; }
  void setAge(int age) { itsAge = age; }
};

SimpleCat::SimpleCat() {
  std::cout << "Simple Cat Constructor ..." << std::endl;
  itsAge = 1;
}

SimpleCat::SimpleCat(SimpleCat &) {
  std::cout << "Simple Cat Copy Constructor ..." << std::endl;
}

SimpleCat::~SimpleCat() {
  std::cout << "Simple Cat Destructor ..." << std::endl;
}

const SimpleCat &FunctionTwo(const SimpleCat &simpleCat); //指向常量的常量指針

int main() {
  std::cout << "Making a cat ..." << std::endl;
  SimpleCat simpleCat;
  std::cout << "simpleCat is now " << simpleCat.getAge() << " years old"
            << std::endl;
  int age = 5;
  simpleCat.setAge(age);
  std::cout << "simpleCat is now " << simpleCat.getAge() << " years old"
            << std::endl;
  std::cout << "Calling FunctionTwo..." << std::endl;
  FunctionTwo(simpleCat);
  std::cout << "simpleCat is now " << simpleCat.getAge() << " years old"
            << std::endl;
  return 0;
}

const SimpleCat &FunctionTwo(const SimpleCat &simpleCat) {
  std::cout << "Function Two. Returning ..." << std::endl;
  std::cout << "simpleCat is now " << simpleCat.getAge() << " years old"
            << std::endl;
  // simpleCat.setAge(9);//報錯,無法對const類型對象進行修改
  return simpleCat;
}

​ 可見,改用引用效率和代價較指針沒變,但是使用起來較指針簡單。

13.4什麼情況下使用引用以及什麼情況下使用指針

​ 一般而言,c++程式員更喜歡使用引用而不是指針,因為他們更清晰,使用起來更容易。然而,引用不能重新賦值,如果需要依次指向不同的對象,就必須使用指針。引用不能為NULL,因此如果要指向的對象可能為NULL,就必須使用指針,而不能使用引用。如果要從堆中分配動態記憶體,就必須使用指針。

13.5指向對象的引用不在作用域內

​ c++程式員學會按引用傳遞後,常常會瘋狂地使用這種方式。然而,過猶不及,別忘了,引用始終是另一個對象的別名,如果將引用傳入或傳出函數,務必自問:它是哪個對象的別名?當我使用引用時,這個對象還存在嗎?

程式清單13.4 ReturnRef.cpp

#include <iostream>

class SimpleCat {
private:
  int itsAge;
  int itsWeight;

public:
  SimpleCat(int age, int weight);
  ~SimpleCat(){};
  int getAge() { return itsAge; }
  int getWeight() { return itsWeight; }
};

SimpleCat::SimpleCat(int age, int weight) : itsAge(age), itsWeight(weight) {}

SimpleCat &TheFunction();

int main() {
  SimpleCat &rCat = TheFunction();
  int age = rCat.getAge();
  std::cout << "rCat is " << age << " years old!" << std::endl;
  return 0;
}

SimpleCat &TheFunction() {
  SimpleCat simpleCat(5, 9);
  return simpleCat;
}

​ 這個程式一般編譯器是會報錯的,因為TheFunction()函數返回的對象引用是在TheFunction()函數內創建的,但是TheFunction()返回時,創建的這個對象是已經被銷毀了的,也就是返回的引用指向了一個不存在的對象(空引用被禁止),從而被編譯器禁止該程式運行。

13.6返回指向堆中對象的引用

​ 你是否認為:如果TheFunction()函數在堆中創建對象,這樣,返回的時候這個對象就依然存在,也就自然解決了上面的空引用問題。

​ 這種方法的問題在於,使用完該對象後,如何釋放為它分配的記憶體?

程式清單13.5 Leak.cpp

#include <iostream>

class SimpleCat {
private:
  int itsAge;
  int itsWeight;

public:
  SimpleCat(int age, int weight);
  ~SimpleCat(){};
  int getAge() { return itsAge; }
  int getWeight() { return itsWeight; }
};

SimpleCat::SimpleCat(int age, int weight) : itsAge(age), itsWeight(weight) {}

SimpleCat &TheFunction();

int main() {
  SimpleCat &rCat = TheFunction();
  int age = rCat.getAge();
  std::cout << "rCat is " << age << " years old!" << std::endl;
  std::cout << "&rCat is " << &rCat << std::endl;
  SimpleCat *pCat = &rCat;
  delete pCat; //原對象被釋放了,這時候rCat不就成了空引用了嗎?
  return 0;
}

SimpleCat &TheFunction() {
  SimpleCat *simpleCat = new SimpleCat(5, 9);
  std::cout << "SimpleCat: " << simpleCat << std::endl;
  return *simpleCat;
}

​ 不能對引用調用delete,一種聰明的解決方案是創建一個指針並將其指向該引用的對象的地址,這個時候調用delete也就能釋放分配的記憶體,但是往後引用名不就成了空引用嗎?雖然編譯器檢測不到且能正常運行,所以這就埋了一個雷,指不定什麼時候炸了。(正如之前指出,引用必須始終是一個實際存在的對象的別名。如果它指向的是空對象,那麼程式仍是非法的)

​ 對於這種問題,實際上有兩種解決方案。一種是返回一個指針,這樣可以在使用完該指針之後將其刪除。為此,需要將返回值類型聲明為指針而非引用,並返回該指針。

​ 另一種更好的解決方案是:在發出調用的函數中聲明對象,然後將其按引用傳遞給TheFunction()。這種方法的優點是,分配記憶體的函數(發出調用的函數)也負責釋放記憶體。

13.7誰擁有指針

​ 程式在堆中分配的記憶體時將返回一個指針。必須一直讓某個指針指向這塊記憶體,因為如果指針丟失(也就是沒有指針指向它了),便無法釋放該記憶體,進而導致記憶體泄露。

​ 在函數之間傳遞記憶體塊時,其中一個函數“擁有”指針。通常,使用引用傳遞記憶體塊中的值,而分配記憶體塊的函數將負責釋放它,但這是一個大致規則,並非不可打破。

​ 然而,讓一個函數分配記憶體,而另一個函數釋放很危險。在誰擁有指針方面不明確可能會導致兩個問題:忘記刪除指針或重覆刪除。無論哪種情況,都會給程式帶來嚴重的問題。編寫函數時,讓其負責釋放自己分配的記憶體是更安全的做法。


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

-Advertisement-
Play Games
更多相關文章
  • 網上關於Spring迴圈依賴的博客太多了,有很多都分析的很深入,寫的很用心,甚至還畫了時序圖、流程圖幫助讀者理解,我看了後,感覺自己是懂了,但是閉上眼睛,總覺得還沒有完全理解,總覺得還有一兩個坎過不去,對我這種有點笨的人來說,真的好難。當時,我就在想,如果哪一天,我理解了Spring迴圈依賴,一定要 ...
  • 關於編程語言中的註釋,其重要性基本上已為大家所共識。 然而關於註釋的規範,這個話題就像我們之前聊過的縮進、終止符和命名方式一樣,眾口難調。 註釋符通常可分為兩種,即行註釋與塊註釋(inline/block),它們在不同的編程語言中的符號可謂讓人眼花繚亂。 比如行註釋符,它至少有以下的 17 種之多( ...
  • 第十四章 高級函數 14.1重載成員函數 ​ 函數可以進行重載,成員函數(成員方法)實質上也是一種函數,所以成員函數也可以進行重載。 程式清單14.1 Rectangle.cpp #include <iostream> class Rectangle { private: int width; in ...
  • 在寫這篇文章時,我是滿懷感激與賞識之情的。 來誇一個人,講一個道理,寫給大家,也是寫給自己。 來自讀者的反饋 先說說事情的經過。 新書出版之後,昨天第一次看到(抱歉看到的比較晚)讀者的反饋。所謂反饋就是在書中留了GitHub的地址,如果書中有錯誤的地方,讀者可以通過該鏈接提交Issues(問題),來 ...
  • 之前只是很模糊的知道其意思:在request scope中,每個request創建一個新的bean;在session scope中,同一session中的bean都是一樣的。但是不知道怎麼用代碼去驗證它,今天可找到驗證它的代碼了。 ...
  • 隊列實質就是一種存儲數據的結構 通常用鏈表或者數組實現 一般而言隊列具備FIFO先進先出的特性,當然也有雙端隊列(Deque)優先順序隊列 主要操作:入隊(EnQueue)與出隊(Dequeue) BlockingQueue 隊列實質就是一種存儲數據的結構 通常用鏈表或者數組實現 一般而言隊列具備FI ...
  • 1 以前在github發開源項目,都因為懶,從來不構建到中央倉庫。最近因為其他人要用,聯繫我。希望可以發到中央倉庫。我想,不就是mvn deploy嘛,開搞。一圈弄下來,發現真沒那麼簡單。當中遇到了無數的坑,讓我每一次都心裡默默念到,發個項目,為何如此痛苦。 現將痛苦的過程詳細記錄下來。希望可以幫助 ...
  • JDBC中 execute與executeUpdate的區別 execute與executeUpdate的區別 步驟 1 : 相同點 execute與executeUpdate的相同點:都可以執行增加,刪除,修改 package jdbc; import java.sql.Connection; i ...
一周排行
    -Advertisement-
    Play Games
  • .Net8.0 Blazor Hybird 桌面端 (WPF/Winform) 實測可以完整運行在 win7sp1/win10/win11. 如果用其他工具打包,還可以運行在mac/linux下, 傳送門BlazorHybrid 發佈為無依賴包方式 安裝 WebView2Runtime 1.57 M ...
  • 目錄前言PostgreSql安裝測試額外Nuget安裝Person.cs模擬運行Navicate連postgresql解決方案Garnet為什麼要選擇Garnet而不是RedisRedis不再開源Windows版的Redis是由微軟維護的Windows Redis版本老舊,後續可能不再更新Garne ...
  • C#TMS系統代碼-聯表報表學習 領導被裁了之後很快就有人上任了,幾乎是無縫銜接,很難讓我不想到這早就決定好了。我的職責沒有任何變化。感受下來這個系統封裝程度很高,我只要會調用方法就行。這個系統交付之後不會有太多問題,更多應該是做小需求,有大的開發任務應該也是第二期的事,嗯?怎麼感覺我變成運維了?而 ...
  • 我在隨筆《EAV模型(實體-屬性-值)的設計和低代碼的處理方案(1)》中介紹了一些基本的EAV模型設計知識和基於Winform場景下低代碼(或者說無代碼)的一些實現思路,在本篇隨筆中,我們來分析一下這種針對通用業務,且只需定義就能構建業務模塊存儲和界面的解決方案,其中的數據查詢處理的操作。 ...
  • 對某個遠程伺服器啟用和設置NTP服務(Windows系統) 打開註冊表 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\NtpServer 將 Enabled 的值設置為 1,這將啟用NTP伺服器功 ...
  • title: Django信號與擴展:深入理解與實踐 date: 2024/5/15 22:40:52 updated: 2024/5/15 22:40:52 categories: 後端開發 tags: Django 信號 松耦合 觀察者 擴展 安全 性能 第一部分:Django信號基礎 Djan ...
  • 使用xadmin2遇到的問題&解決 環境配置: 使用的模塊版本: 關聯的包 Django 3.2.15 mysqlclient 2.2.4 xadmin 2.0.1 django-crispy-forms >= 1.6.0 django-import-export >= 0.5.1 django-r ...
  • 今天我打算整點兒不一樣的內容,通過之前學習的TransformerMap和LazyMap鏈,想搞點不一樣的,所以我關註了另外一條鏈DefaultedMap鏈,主要調用鏈為: 調用鏈詳細描述: ObjectInputStream.readObject() DefaultedMap.readObject ...
  • 後端應用級開發者該如何擁抱 AI GC?就是在這樣的一個大的浪潮下,我們的傳統的應用級開發者。我們該如何選擇職業或者是如何去快速轉型,跟上這樣的一個行業的一個浪潮? 0 AI金字塔模型 越往上它的整個難度就是職業機會也好,或者說是整個的這個運作也好,它的難度會越大,然後越往下機會就會越多,所以這是一 ...
  • @Autowired是Spring框架提供的註解,@Resource是Java EE 5規範提供的註解。 @Autowired預設按照類型自動裝配,而@Resource預設按照名稱自動裝配。 @Autowired支持@Qualifier註解來指定裝配哪一個具有相同類型的bean,而@Resourc... ...