【原創】淺談指針(四)
前幾篇文章的鏈接:
淺談指針(一)//www.cnblogs.com/jisuanjizhishizatan/p/15365167.html
淺談指針(二)//www.cnblogs.com/jisuanjizhishizatan/p/15365823.html
淺談指針(三)//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++的類,構造函數和析構函數有所了解。
關於構造函數和析構函數,可以看我之前的一篇文章://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,這樣不容易造成二義性。
完。