天天看點

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

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

數組與指針生來就是雙胞胎,多數人就是從數組的學習開始指針的旅程的。在學習的過程中,很自然就會經常聽到或見到關于數組與指針的各種各樣的看法,下面我節選一些在各種論壇和文章裡經常見到的文字:

“一維數組是一級指針”

“二維數組是二級指針”

“數組名是一個常量指針”

“數組名是一個指針常量”

........................

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

什麼是指針?一種普遍存在的了解是,把指針變量了解成就是指針,這種了解是片面的,指針變量隻是指針的其中一種形态,但指針并不僅僅隻有指針變量。一個指針,包含了兩方面的涵義:實體(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’’.

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

再來看看标準關于取址運算符&的規定:

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的指針,并且從一個左值轉換成一個右值。經過轉換後,數組名不再代表數組對象,而是一個代表數組首位址的符号位址,并且不是對象。特别指出的是,數組到指針的轉換規則隻适用于表達式,隻在這種條件下數組名才作為轉換的結果代表數組的首位址,而當數組名作為數組對象定義的辨別符、初始化器及作為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;                        

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

    printf( "%d", sizeof( fun( &T ).a ) );      

    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由于無此需要,隻提出了規則。符号是語言對計算機的進階抽象,但計算機并不認識符号,它隻認識數值,是以一個符号要參加表達式計算必須先對其進行數值化,三條轉換規則就是為了這個目的而存在的。

看到這裡,大概有些初學者已經被上述那些左值右值、對象非對象搞得稀裡糊塗了。的确,數組與指針的複雜性讓人望而生畏,不是一朝一夕就能完全掌握的,需要一段較長的時間慢慢消化。是以筆者才将數組與指針稱為一門藝術,是的,它就是藝術

本文來自CSDN部落格,轉載請标明出處:http://blog.csdn.net/supermegaboy/archive/2009/11/23/4855027.aspx

繼續閱讀