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

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

《c++入門經典》筆記10 第十章 創建指針 10.1理解指針及其用途 變數是可存儲一個值的對象:整型變數存儲一個數字,字元變數存儲一個字母,而指針是存儲記憶體地址的變數。 電腦記憶體是存儲變數值的地方。根據約定,電腦記憶體被劃分成按順序編號的記憶體單元,每個記憶體單元都有對應的地址。記憶體中,不管其類型是 ...


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

第十章 創建指針

10.1理解指針及其用途

變數是可存儲一個值的對象:整型變數存儲一個數字,字元變數存儲一個字母,而指針是存儲記憶體地址的變數

電腦記憶體是存儲變數值的地方。根據約定,電腦記憶體被劃分成按順序編號的記憶體單元,每個記憶體單元都有對應的地址。記憶體中,不管其類型是什麼,每個變數都位於特定的地址處。

記憶體編址方案隨電腦而異。通常,程式員無須知道變數地址,如果想要獲取地址信息,可使用地址運算符&

程式清單10.1 Address.cpp

#include<iostream>

int main()
{
    unsigned short shortVar = 5;
    unsigned long longVar = 65535;
    long sVar = -65535;

    std::cout<<"shortVar:\t"<<shortVar;
    std::cout<<"\tAddress of shortVar:\t"<<&shortVar<<"\n";
    std::cout<<"longVar:\t"<<longVar;
    std::cout<<"\tAddress of longVar:\t"<<&longVar<<"\n";
    std::cout<<"sVar:\t"<<sVar;
    std::cout<<"\tAddress of sVar:\t"<<&sVar<<"\n";

}

(地址預設以16進位表示法輸出的)

您運行該程式時,變數的地址將不同,因為這取決於記憶體中存儲的其他內容以及可用的記憶體有多少。

在指針中存儲地址

每個變數都有地址,即使不知道變數的具體地址,也可將該地址存儲在指針變數中。

int howOld = 50;
int* pAge = nullptr;//初始化一個int型空指針變數,這樣能更明顯看出來pAge類型是int*,但c/c++的標準寫法是int *pAge
pAge = &howOld;//將howOld的地址取出來放入指針變數pAge中

間接運算符

間接運算符(*)又被稱為解引用運算符。對指針解除引用時,將獲取指針存儲的地址處的值。

int howOld = 50;
int* pAge = &howOld;
int yourAge;
yourAge = *pAge;//yourAge的值變成了50
*pAge = 10;//howOld的值變成了10,而yourAge的值還是50

指針pAge前面的間接運算符(*)表示“存儲在......處的值”。這條賦值語句的意思是,從pAge指向的地址處獲取值,並將其賦給yourAge。看待這條語句的另一種方式是,不影響指針,而是影響指針指向的內容(比如上面最後一條語句)。

使用指針操作數據(其實上面那個例子就是)

程式清單10.2 Pointer.cpp

#include <iostream>

int main()
{
    int myAge;
    int *pAge = nullptr;

    myAge = 5;
    pAge = &myAge;
    std::cout << "myAge: " << myAge << "\n";
    std::cout << "*pAge: " << *pAge << "\n\n";

    std::cout << "*pAge = 7\n";
    *pAge = 7;
    std::cout << "myAge: " << myAge << "\n";
    std::cout << "*pAge: " << *pAge << "\n\n";

    std::cout << "myAge = 9\n";
    myAge = 9;
    std::cout << "myAge: " << myAge << "\n";
    std::cout << "*pAge: " << *pAge << "\n";
}

查看存儲在指針中的地址:

程式清單10.3 PointerCheck.cpp

#include <iostream>

int main()
{
    unsigned short int myAge = 5, yourAge = 10;
    unsigned short int *pAge = &myAge;

    std::cout << "pAge: " << pAge << "\n";
    std::cout << "*pAge: " << *pAge << "\n";

    pAge = &yourAge;
    std::cout << "after reassign the pAge point to yourAge : "
              << "\n";
    std::cout << "pAge: " << pAge << "\n";
    std::cout << "*pAge: " << *pAge << "\n";

    return 0;
}

為何使用指針

熟悉指針的語法後,便可將其用於其他用途了,指針最長用於完成如下三項任務:

  • 管理堆中的數據;
  • 訪問類的成員數據和成員函數;
  • 按引用將變數傳遞給函數

10.2堆和棧

(這部分其實如果有一點數據結構或者操作系統基礎更好)

程式員通常需要處理下述五個記憶體區域

  • 全局名稱空間
  • 寄存器
  • 代碼空間

局部變數和函數參數存儲在棧中,代碼當然在代碼空間中,而全局變數在全局名稱空間中。寄存器用於記憶體管理,如跟蹤棧頂和指令指針,幾乎餘下的所有記憶體都分配給了堆,堆有時也被稱為自由存儲區。

局部變數的局限性是不會持久化,函數返回時,局部變數將被丟棄。全局變數解決了這種問題,但代價是在整個程式中都能訪問它,這導致代碼容易出現bug,難以理解與維護。將數據放在堆中可解決這兩個問題。

每當函數返回時,都會清理棧(實際上,開始調用函數時,棧空間進行壓棧操作;函數調用完時,棧空間進行出棧操作)。此時,所有的局部變數都不在作用域內,從而從棧中刪除。只有到程式結束後才會清理堆,因此使用完預留的記憶體後,您需要負責將其釋放(手動GC)。讓不再需要的信息留在堆中稱為記憶體泄露(垃圾滯留)

堆的優點在於,在顯示釋放前,您預留的記憶體始終可用。如果在函數中預留堆中的記憶體,在函數返回後,該記憶體仍可用。

以這種方式(而不是全局變數)訪問記憶體的優點是,只有有權訪問指針的函數才能訪問它指向的數據。這提供了控制嚴密的數據介面,消除了函數意外修改數據的問題。

關鍵字new

在c++中,使用new關鍵字分配堆中的記憶體,併在其後指定要為之分配記憶體的對象的類型,讓編譯器知道需要多少記憶體。比如new int分配4位元組記憶體。

關鍵字new返回一個記憶體地址,必須將其賦給指針。

int *pPointer = new int;//指針pPointer將指向堆中的一個int變數
*pPointer = 72;//將72賦值給pPointer指向的堆記憶體變數

關鍵字delete

使用好了分配的記憶體區域後,必須對指針調用delete,將記憶體歸還給堆空間。

delete pPointer;

對指針調用delete時,將釋放它指向的記憶體。如果再次對該指針調用delete,就會導致程式崩潰(delete野指針)。刪除指針時,應將其設置為nullptr,對空指針調用delete是安全的。

Animal *pDog = new Animal;
delete pDog;//釋放記憶體
pDog = nullptr;//設置空指針
delete pDog;//安全行為

程式清單10.4 Heap.cpp

#include <iostream>

int main()
{
    int localVariable = 5;
    int *pLocal = &localVariable;
    int *pHeap = new int;
    if (pHeap == nullptr)
    {
        std::cout << "Error! No memory for pHeap!!";
        return 1;
    }
    *pHeap = 7;
    std::cout << "localVariable: " << localVariable << std::endl;
    std::cout << "*pLocal: " << *pLocal << std::endl;
    std::cout << "*pHeap: " << *pHeap << std::endl;
    delete pHeap; //此處只是釋放了堆中new分配的記憶體,並沒有刪除指針,所以下麵可以接著用。
    pHeap = new int;
    if (pHeap == nullptr)
    {
        std::cout << "Error! No memory for pHeap!!";
        return 1;
    }
    *pHeap = 9;
    std::cout << "*pHeap: " << *pHeap << std::endl;
    delete pHeap; //再次釋放new出來的記憶體
    return 0;
}

另一種可能無意間導致記憶體泄露的情形是,沒有釋放指針指向的記憶體就給它重新賦值。

int *pPointer = new int;
*pPointer = 72;
pPointer = new int;
*pPointer = 50;//指針變數指向了一個新new出來的存有50的int型變數,但是之前那個存有72的堆記憶體變數還沒被釋放,也就造成了記憶體泄露

上述代碼應該修改成這樣:

int *pPointer = new int;
*pPointer = 72;
delete pPointer;
pPointer = new int;
*pPointer = 50;

也就是說一個不存在記憶體泄露的c++程式至少其new與delete是成對的,或者說是數量相等的。(這可苦了c艹程式員咯!)

空指針常量:

在較早的c++版本中,使用0或者NULL來設置指針為空值。

int *pBuffer = 0;
int *pBuffer = NULL;

但是其實因為定義NULL的語句是一個預處理巨集:

#define NULL 0

所以其實NULL和0一個意思,上面兩句也是一個意思。

但是當某個函數進行了重載,參數有指針類型也有int型的時候,傳了一個值為0的空值指針進去,這個時候會出現二義性:

void displayBuffer(char*);//這個函數的參數是char型指針變數
void displayBuffer(int);

如果將空指針作為參數去調用,那麼會調用displayBuffer(int),這個時候就會造成運行與預期不同。

所以才應該使用關鍵字nullptr

int *pBuffer = nullptr;

程式清單10.5 Swapper.cpp

#include <iostream>

int main()
{
    int value1 = 12500;
    int value2 = 1700;
    int *pointer2 = nullptr;
    pointer2 = &value2;
    value1 = *pointer2;
    pointer2 = 0;
    std::cout << "value = " << value1 << "\n";

    return 0;
}


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

-Advertisement-
Play Games
更多相關文章
  • 一、基本概念 程式(program): 是為完成特定任務、用某種語言編寫的一組指令的集合。即指一 段靜態的代碼,靜態對象。 進程(process):是程式的一次執行過程,或是正在運行的一個程式。是一個動態 的過程:有它自身的產生、存在和消亡的過程。——生命周期 運行中的QQ,運行中的MP3播放器 程 ...
  • 簡介 道可道,非常道。這裡常道指的永恆不變的道理,常有不變的意思。顧名思義和變數相比,常量在聲明之後就不可改變,它的值是在編譯期間就確定的。 下麵簡單的聲明一個常量: const p int = 1 聲明常量的時候可以指定類型也可以類似:=簡單聲明一樣,不指定類型如下: const p = 1 也可 ...
  • from typing import Listclass Solution: # 第一種是我想的辦法 def singleNumber(self, nums: List[int]) -> int: # 首先進行排序 nums.sort() # 然後判斷重覆的數字,數組中的數字必定為奇數個, # 如果 ...
  • Pydantic 是一個使用Python類型提示來進行數據驗證和設置管理的庫。Pydantic定義數據應該如何使用純Python規範用併進行驗證。PEP 484 從Python3.5開始引入了類型提示的功能,PEP 526 使用Python3.6中的變數註釋語法對其進行了拓展。Pydantic使用這 ...
  • # 二叉搜索樹的特點是左子樹小於根節點,右子樹大於根節點。# 因此當根節點為i的時候,左子樹的值為1:i-1,右子樹為i+1:n# 當節點為n的時候所有的能夠組成的樹為左子樹個數乘以右子樹個數。class Solution: def numTrees(self, n: int) -> int: dp ...
  • update 2020/7/15 優化了一下 \(markdown\) 的用法,增加了前面的題目描述。 題目: 題目描述 從加里敦大學城市規劃專業畢業的小明來到了一個地區城市規劃局工作。這個地區一共有 \(n\) 座城市,\(n-1\) 條高速公路,保證了任意兩運城市之間都可以通過高速公路相互可達, ...
  • 引言 現在各大技術社區 Spring Boot 的文章越來越多,Spring Boot 相關的圖文、視頻教程越來越多,使用 Spring Boot 的互聯網公司也越來越多; Java 程式員現在出去面試, Spring Boot 已經成了必問的內容。 一切都在證明,Spring Boot 已經成為了 ...
  • 《c++入門經典》筆記11 第十一章 開發高級指針 11.1在堆中創建對象 實際上,類就是對象的類型,對象也是一種變數,所以你可以在堆中創建int型變數,自然也就能創建自定義型變數。 Cat *pCat = new Cat; 這將調用預設構造函數(無參構造函數),每當在堆或棧中創建對象時,都將調用構 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...