第一章 數組與指針概念剖析

 

數組與指針生來就是雙胞胎,多數人就是從數組的學習開始指針的旅程的。在學習的過程當中,很天然就會常常聽到或見到關於數組與指針的各類各樣的見解,下面我節選一些在各類論壇和文章裏常常見到的文字:express

「一維數組是一級指針」數組

「二維數組是二級指針」app

「數組名是一個常量指針」ide

「數組名是一個指針常量」函數

........................學習

這些文字看起來很是熟悉吧?相似的文字還有許多。不過很是遺憾,這些文字都是錯誤的,實際上數組名永遠都不是指針!這個結論也許會讓你震驚,但它的確是事實。可是,在論述這個問題以前,首先須要解決兩個問題:什麼是指針?什麼是數組?這是本章的主要內容,數組名是否指針這個問題留在第二章進行討論。看到這裏,也許有人內心就會嘀咕了,這麼簡單的問題還須要說嗎?int *p, a[10];不就是指針和數組嗎?可是,筆者在過往的討論過程當中,還真的發現有很多人對這兩個概念遠非清晰,這會妨礙對後面內容的理解,因此仍是有必要先討論一下。spa

什麼是指針?一種廣泛存在的理解是,把指針變量理解成就是指針,這種理解是片面的,指針變量只是指針的其中一種形態,但指針並不只僅只有指針變量。一個指針,包含了兩方面的涵義:實體(entity)和類型。標準是這樣描述指針類型的:翻譯

6.2.5 Types指針

A pointer type may be derived from a function type, an object type, or an incomplete type, called the referenced type. A pointer type describes an object whose value provides a reference to an entity of the referenced type. A pointer type derived from the referenced type T is sometimes called ‘‘pointer to T’’. The construction of a pointer type from a referenced type is called ‘‘pointer type derivation’’.code

請留意第二句所說的內容:指針類型描述了這樣一個對象,其值爲對某種類型實體的引用。標準在這裏所用的措詞是指針類型描述了一個對象。

再來看看標準關於取址運算符&的規定:

6.5.3.2 Address and indirection operators

Semantics

The unary & operator returns the address of its operand. If the operand has type 「type」, the result has type 「pointer to type」....... Otherwise, the result is a pointer to the object or function designated by its operand.

這個條款規定,&運算符的結果是一個指針。但問題是,&表達式的結果不是對象!標準自相矛盾了嗎?固然不是,這說明的是,指針的實體有對象與非對象兩種形態。

咱們常說的指針變量只是指針實體的對象形態,但對象與非對象兩種形態合起來,纔是指針的完整涵義,就是說,不管是否對象,只要是一個具備指針類型的實體,均可以稱之爲指針,換言之,指針不必定是對象,也不必定是變量。後一種狀況,指的是當須要產生一個指針類型的臨時對象時,例如函數的傳值返回或者表達式計算產生的中間結果,因爲是一個無名臨時對象,所以不是變量。

在C++中,因爲引入了OOP,增長了一種也稱爲「指針」的實體:類非靜態成員指針,雖然也叫指針,但它卻不是通常意義上的指針。C++標準是這樣說的:

3.9.2 Compound types

....... Except for pointers to static members, text referring to 「pointers」 does not apply to pointers to members..........

接下來,該談談數組了。數組是一種對象,其對象類型就叫數組類型。但筆者發現有個現象很奇怪,有些人根本沒有數組類型的意識,不過也的確有些書並無將數組做爲一個類型去闡述,也許緣由就在於此吧。數組類型跟指針類型都屬於派生類型,標準的條款:

6.2.5 Types

An array type describes a contiguously allocated nonempty set of objects with a particular member object type, called the element type. Array types are characterized by their element type and by the number of elements in the array. An array type is said to be derived from its element type, and if its element type is T, the array type is sometimes called 「array of T」. The construction of an array type from an element type is called 「array type derivation」.

數組類型描述了某種對象的非空集合,不容許0個元素,咱們有時候看見某個結構定義內部定義了一個大小爲0的數組成員,這是柔性數組成員的非標準形式,這個留在第八章講述。數組類型的語法(注意不是數組對象的聲明語法)是element type[interger constant],例如對於

int a[10];

a的數組類型描述就是int[10]。

數組名做爲數組對象的標識符,是一個通過「隱式特例化」處理的特殊標識符。整數對象的標識符、浮點數的標識符等等雖然也是標識符,但數組名與之相比卻有重大的區別。計算機語言存在的目的,是爲了將人類的天然語言翻譯爲計算機可以理解的機器語言,讓人類更加容易地利用和管理各類計算機資源,易用是思想,抽象是方法,語言將計算機資源抽象成各色各樣的語言符號和語言規則,數組、指針、整數、浮點數等等這些東西本質上就是對內存操做的不一樣抽象。做爲抽象的方法,能夠概括爲兩種實現,一是名字表明一段有限空間,其內容稱爲值;二是名字是一段有限空間的引用,同時規定空間的長度。第一種方法被各類計算機語言廣泛使用,在C/C++中稱爲從左值到右值的轉換。但數組不一樣於通常的整數、浮點數對象,它是一個彙集,沒法將一個彙集看做一個值,從一個彙集中取值,在C/C++的對象模型看來缺少合理性,是沒有意義的。在表達式計算的大多數狀況中,第一種方法並不適合數組,使用第二種方法將數組名轉換爲某段內存空間的引用更適合。

所以,與通常標識符相比,數組名既有通常性,也有特殊性。通常性表如今其對象性質與通常標識符是同樣的,這種狀況下的數組名,表明數組對象,同時因爲符合C/C++的左值模型,它是一個左值,只不過是不可修改的,不可修改的緣由與上一段中敘述的內容相同,經過一個名字試圖修改整個彙集是沒有意義的;而特殊性則反映在表達式的計算中,也就是C/C++標準中所描述的數組與指針轉換條款,在這個條款中,數組名不被轉換爲對象的值,而是一個符號地址。

如今來看看標準是如何規定數組與指針的轉換的:

C89/90的內容:

6.2.2.1 Lvalues and function designators

Except when it is the operand of the sizeof operator or the unary & operator, or is a character string literal used to initialize an array of character type. or is a wide string literal used to initialize an array with element type compatible with wchar-t, an lvalue that has type 「array of type」 is converted to an expression that has type 「pointer to type」 that points to the initial element of the array object and is not an lvalue.

C99的內容:

6.3.2.1 Lvalues, arrays, and function designators

Except when it is the operand of the sizeof operator or the unary & operator, or is a string literal used to initialize an array, an expression that has type 「array of type」 is converted to an expression with type 「pointer to type」 that points to the initial element of the array object and is not an lvalue. If the array object has register storage class, the behavior is undefined.

數組類型到指針類型轉換的結果,是一個指向數組首元素的類型爲pointer to type的指針,而且從一個左值轉換成一個右值。通過轉換後,數組名再也不表明數組對象,而是一個表明數組首地址的符號地址(這一句應爲數組首元素地址的符號地址,2011年4月),而且不是對象。特別指出的是,數組到指針的轉換規則只適用於表達式,只在這種條件下數組名才做爲轉換的結果表明數組的首地址(應爲數組首元素的地址,2011年4月),而當數組名做爲數組對象定義的標識符、初始化器及做爲sizeof、&的操做數時,它才表明數組對象自己,並非地址。

這種轉換帶來一個好處,對於數組內部的指針運算很是有利。咱們能夠用a + 1這種精煉的形式表示a[1]的地址,無須用&a[1]這種醜陋的代碼,實際上,&a[1]是一種代碼冗餘,是對代碼的浪費,由於&a[1]等價於&*( a + 1 ),&與*因爲做用相反被抵消,實際上就是a + 1,既然這樣咱們何不直接使用a + 1呢?撇開爲了照顧人類閱讀習慣而產生的可讀性而言,&a[1]就是垃圾。

可是,另外一方面,這種異於通常標識符左值轉換的特例化大大增長了數組與指針的複雜性,困擾初學者無數個日日夜夜的思惟風暴今後拉開了帷幕!

在兩個版本的轉換條款中,有一點須要留意的是,兩個版本關於具備數組類型的表達式有不一樣的描述。

C89/90規定:

 

an lvalue that has type 「array of type」 is......

但C99卻規定:

an expression that has type 「array of tye」 is.......

C99中去掉了lvalue的詞藻,爲何?咱們知道,數組名是一個不可修改的左值,但實際上,也存在右值數組。在C中,一個左值是具備對象類型或非void不完整類型的表達式,C的左值表達式排除了函數和函數調用,而C++由於增長了引用類型,所以返回引用的函數調用也屬於左值表達式,就是說,非引用返回的函數調用都是右值,若是函數非引用返回中包含數組,狀況會怎樣?考慮下面的代碼:

#include <stdio.h>
struct Test
{
    int a[10];
};
struct Test fun( struct Test* );

int main( void )
{
    struct Test T;
    int *p = fun( &T ).a;                   /* A */

    int (*q)[10] = &fun( &T ).a;            /* B */

    printf( "%d", sizeof( fun( &T ).a ) );  /* C*/
    return 0;
}

struct Test fun( struct Test *T )
{
    return *T;
}

在這個例子裏,fun( &T )的返回值是一個右值,fun( &T ).a就是一個右值數組,是一個右值表達式,但a自己是一個左值表達式,要注意這個區別。在C89/90中,因爲規定左值數組才能進行數組到指針的轉換,所以A中的fun( &T ).a不能在表達式中進行從數組類型到指針類型的轉換,A中的fun( &T ).a是非法的,但C99在上述條款中再也不限定左值表達式,即對這個轉換再也不區分左值仍是右值數組,所以都是合法的;C中的fun( &T ).a是sizeof運算符的操做數,這種狀況下fun( &T ).a並不進行數組到指針的轉換,所以C在全部C/C++標準中都是合法的;B初看上去仍然有點詭異,&運算符不是已經做爲例外排除了數組與指針的轉換嗎?爲何仍是非法?其實B違反了另外一條規定,&的操做數要求是左值,而fun( &T ).a是右值。C++繼承了C99的觀點,也容許右值數組的轉換,其條款很是簡單:

An lvalue or rvalue of type 「array of N T」 or 「array of unknown bound of T」 can be converted to an rvalue of type 「pointer to T.」 The result is a pointer to the first element of the array.

數組類型到指針類型的轉換與左值到右值的轉換、函數類型到指針類型的轉換一塊兒是C/C++三條很是重要的轉換規則。C++因爲重載解析的須要,把這三條規則概念化了,統稱爲左值轉換,但C因爲無此須要,只提出了規則。符號是語言對計算機的高級抽象,但計算機並不認識符號,它只認識數值,所以一個符號要參加表達式計算必須先對其進行數值化,三條轉換規則就是爲了這個目的而存在的。

看到這裏,大概有些初學者已經被上述那些左值右值、對象非對象搞得稀裏糊塗了。的確,數組與指針的複雜性讓人望而生畏,不是一朝一夕就能徹底掌握的,須要一段較長的時間慢慢消化。所以筆者纔將數組與指針稱爲一門藝術,是的,它就是藝術!

相關文章
相關標籤/搜索