【原創】淺談指標(三)

計算機知識雜談發表於2021-10-04

上期連結

https://www.cnblogs.com/jisuanjizhishizatan/p/15365823.html

前言

我寫本文的初衷是為了讓更多的人瞭解指標。最近一直有人在我的博文點反對,我也不想點名,只是想說:不要對指標懷有偏見。你可以認為本文的東西都在胡扯,但是,不管如何,請你自己在自己的電腦上執行一下,檢驗我的言論是否正確。我撰寫這些文章之前,一定閱讀了很多參考資料,不會隨便亂寫一通。如果真有什麼錯誤之處,也請大家在評論區指出。

引用和指標

言歸正傳,上期我們講了swap函式,花了很大的篇幅。但是,仔細觀察algorithm庫中的swap,在呼叫的時候不需要寫&符號取地址?這是由於它使用了C++的引用機制。
什麼是引用?它和指標有什麼異同之處?我們一起觀察。首先,我們看一個簡短的例子。

#include<bits/stdc++.h>
using namespace std;
int main(){
  int a=10;
  int &ref=a;
  printf("a..%d,ref..%d\n",a,ref);
  ++a;
  printf("a..%d,ref..%d\n",a,ref);
  return 0;
}

把第7行的++a換為++ref,結果相同。
int &ref=a,表示建立一個變數為a的引用,名為f。很多書上這樣寫:

ref相當於a的別名,使用ref就是使用a。

我可以負責任的告訴你,上述描述是錯誤的。
事實上,ref和a,不是“別名”的關係,而是兩個使用相同地址的變數。一般定義兩個變數,它們地址一定不相同,但是引用變數和被引用變數的地址相同,才能導致“修改ref就是修改a”的效果。
在swap中,如果我們指定引數中的ab和main中的ab地址相同,就可以“不使用指標進行交換”了。(但是其實C++編譯過程中,引用就是用指標實現的)我們可以這樣寫程式碼:

void Swap(int &a,int &b){
    int t=a;a=b;b=t;
}

這樣一來,swap中的ab和main中的ab地址相同,就可以直接交換,省去了取地址的操作。

引用的內部實現

我們嘗試把這一篇中,使用引用的swap和上一篇使用指標的swap進行反彙編,看看生成的彙編程式碼有何不同。我這邊使用線上工具:https://godbolt.org/
實驗證明:兩個swap函式內部完全相同。這也表明,C++在編譯的時候,會把引用當作指標處理。
事實上,不可能存在地址完全相同的變數,其中一個變數必定是指標才能實現這樣的效果(即讓其中一個變數指向另一個)。只不過,我們在引用的書寫中,不需要多寫一個&符號和*符號而已。

可變長結構體

注:這一段是參考《征服C指標》寫的,由於該書出版第一版時C99未發行,因此可能內容與最新的C或C++有一定出入,見諒。
假設我們要開發一個畫圖軟體,其中有一個折線的功能。折線是由多條直線構成的,因此,結構體應該這樣定義:

struct Point{
    int x,y;
};
struct Line{
    Point p1,p2;
};
struct Polyline{
    int n;
    Line *l;
};

如果要對Polyline分配記憶體,那麼必須使用new分配兩次記憶體。一次給polyline整體分配記憶體,第二次需要給成員l分配記憶體。(原書由於是C語言,寫的是malloc,本文給了一些修改)

Polyline *p;
p=new Polyline;
p.l=new Line [n];

但是我們可以這樣寫結構體,這樣只需分配一次記憶體了。

struct Polyline{
  int n;
  Line l[1];
};

然後使用如下的方法分配記憶體:

p=(Polyline*)malloc(sizeof(Polyline)+n*sizeof(Line));

雖然陣列只定義了一個,但是在分配p空間的時候,我們發現,多分配了n個空間,給到後面的Line。這樣一來,即使Line的後續元素(第2個,第3個)不在結構體中,也能使用。圖示如下:(自己畫的)

這種寫法稱作可變長結構體。在C99中,專門擴充套件了這個語法(之前的這種寫法都屬於“違反標準的技巧”),並且可以在定義最後元素l的時候直接使用空方括號[]。

free/delete

free和delete用於釋放記憶體。free一般用於C語言,釋放malloc的記憶體。delete用於C++,釋放new的記憶體。程式例項:

#include<bits/stdc++.h>
using namespace std;
int main(){
    int *p=new int;
    *p=100;
    cout<<*p;
    delete p;
}

如果把第4行的new換為malloc,那麼最後一行也要換為free(p)。
注意事項:
1.以前有個函式叫做cfree,和calloc配對使用。(calloc也用於記憶體分配)但是現在建議大家不要使用cfree。
2.delete陣列的時候,要加上[]符號,例如 delete []p
3.小心多個指標同時指向同一記憶體時,如果free掉其中一個指標,其他指標仍在使用將會非常危險。

今天內容到此為止。

相關文章