【原創】淺談指針(四)

前幾篇文章的鏈接:
淺談指針(一)//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,這樣不容易造成二義性。

完。