前幾篇文章的連結:
淺談指標(一)https://www.cnblogs.com/jisuanjizhishizatan/p/15365167.html
淺談指標(二)https://www.cnblogs.com/jisuanjizhishizatan/p/15365823.html
淺談指標(三)https://www.cnblogs.com/jisuanjizhishizatan/p/15367297.html
前言
“淺談指標”系列的大概是最後一篇了吧。如果以後我再發現一些其他用法,也會開幾篇新文章,不過短時間內應該就不會更新了吧。
從何說起呢?上次我們把free和delete講完,delete中如果是delete一個陣列,那麼需要這樣寫:
delete []p;
這裡使用了空的方括號。那麼,C++裡,還有什麼地方使用空方括號呢?我們一起探究一下。
空方括號[]的使用
初始化陣列
假設我們要開發一個日曆軟體,其中必定要儲存每個月的天數。這些數值最好以常量陣列的形式儲存,我們可以這樣寫:
const int Days[]={0,31,28,31,30,31,30,31,31,30,31,30,31};
確實,我們可以往那個方括號中寫上13,但是我們也可以不寫,這時候,編譯器會自動確定元素個數。
函式引數
我們這次編寫一個函式,形式為print(array,size),用於輸出array陣列的內容。
void print(int array[],int size){
for(int i=0;i<size;i++){
cout<<array[i];
}
}
在函式的引數之中,我們使用空的方括號來說明這是一個陣列。如果你在方括號中手動定義元素個數,例如void print(int array[10],int size)
,元素個數10也會被編譯器無視。
這和本文的主題——指標有何關係呢?我們來看看。
實際上,C++不支援把陣列當作引數傳遞。看似我們print函式傳遞的是陣列,實際上,傳遞的是“指向陣列首元素的指標”。也就是說,我們的引數int array[]
和引數int *array
是等價的。當往引數傳遞陣列時,陣列會被退化為指標。
有人會想,我們可以直接使用sizeof輸出,就不需要呼叫方指定size引數了。很可惜,這種寫法是錯誤的。
#include<iostream>
using namespace std;
void print(int array[]){
for(int i=0;i<sizeof(array);i++){
cout<<array[i];
}
}
int main(){
int a[5]={1,2,3,4,5};
print(a);
}
輸出結果一定不是12345。因為,array在函式中,已經不再是陣列了,而是指標,因此array中儲存的是地址。在32位系統上,sizeof(array)是4。在64位系統上,sizeof(array)是8。不管怎樣,輸出的一定不是我們期望的結果。
因此,當我們開發此類函式的時候,一定要注意sizeof的問題。當然,我們可以把陣列作為結構體的成員進行傳遞,但是這樣做速度將會減慢(因為要複製整個陣列元素而不是一個地址)。
類和指標
建構函式和解構函式
在閱讀文章之前,想必大家都對C++的類,建構函式和解構函式有所瞭解。
關於建構函式和解構函式,可以看我之前的一篇文章:https://www.cnblogs.com/jisuanjizhishizatan/p/15313713.html
建構函式和解構函式的定義:
建構函式就是在一個類被建立的時候自動執行的函式。
解構函式就是在一個類被銷燬的時候自動執行的函式。
new和delete
當執行new的時候,分配一個類的記憶體,會自動執行建構函式。
當執行delete的時候,釋放類的記憶體,就會自動執行解構函式。
我們看如下程式碼:
#include<bits/stdc++.h>
using namespace std;
class A{
public:
int a;
A(){
cout<<"created"<<endl;
}
};
A *test;
int main(){
cout<<"main"<<endl;
test=new A;
}
一般來說,在類宣告的時候就呼叫建構函式了,test是全域性變數,因此一定在main之前執行,輸出created。
但是,由於test是指標,直到new的時候,test才被分配記憶體,因此在main之後輸出。
解構函式的原理類似。
malloc
如果把上面程式碼的new換為malloc,程式會輸出什麼?答案是隻輸出main。
有人可能會很奇怪,不就是把new換成malloc嗎?其實,只有new可以自動呼叫建構函式,malloc不會呼叫建構函式,因此,C++中一般更常用new而不是malloc。
強制轉型問題
上文提到了malloc。那麼我就順便說點關於malloc強制轉型的問題吧。在C語言中,由於沒有new,只能用malloc分配記憶體。例如下面的語句:
p=malloc(100*sizeof(int));
malloc的返回值是void*。在C語言裡,任何數都可以賦值給void*,void*也可以賦值給任何指標型別。因此,上面的程式碼理所應當這樣寫。如果使用強制轉換,就顯得沒有必要:
p=(int*)malloc(100*sizeof(int));
並且《征服C指標》這樣寫:
C語言預設把沒有定義的函式的返回值解釋為int。假設忘記寫了#include<stdlib.h>,又對malloc的返回值作了強制轉型,C編譯器可能不會發出警告。那些運氣好,現在還能跑起來的程式,如果遷移到了指標的大小完全不同的機器上去,應該就跑不起來了。
C語言中,一般不對返回值作強制轉型。然而C++就不一樣了。我們嘗試執行不帶強制轉型的malloc,結果是:
error: invalid conversion from 'void*' to 'int*' [-fpermissive]
C++不允許把void*的指標賦值給其他型別。因此,如果要使用malloc,必須使用強制轉型。相比較而言,還是new來的方便。
順便提一句,NULL在stdio.h有些時候定義為(void*)0(當然更常見的是直接定義為0,沒有void*),因此這種情況下就不能把NULL賦值給其他指標型別。對此C++規定把NULL直接定義為常量0,並專門擴充了nullptr。
nullptr
nullptr和C語言的NULL完全等價。
NULL的問題
假設有兩個過載函式
void f(int i);
void f(int* i);
f(NULL);
執行最後一行的時候,由於0可以解釋為指標也可以解釋為整數,兩個過載都可以匹配,導致出錯。
nullptr
nullptr完全可以代替NULL賦值給指標,表示空地址。但是,不能把nullptr賦值給普通整數,
int n=nullptr;
是非法的。
如果上面過載例子中把nullptr傳入f的引數,結果會呼叫指標版本的f,這樣不容易造成二義性。
完。