本页使用了标题或全文手工转换

指標 (電腦科學)

维基百科,自由的百科全书
跳转至: 导航搜索

计算机科学中,指標英语Pointer),又譯指針,是程式語言中的一個物件,用來儲存某一個位址。指標的內容只有一個記憶體位址,這個位址的值直接指向(points to)存在電腦記憶體中另一塊記憶體空間。在指標指向的記憶體中,可能代表另一個變數、結構、物件或函數。但是從指標本身無法得知它所參照的記憶體中儲存了什麼資料型別的資訊。指標參考(reference)了記憶體中某個位址,通過被稱為反參考(dereferencing)指標的動作,可以取出在那個位址中儲存的值。作個比喻,假設將電腦記憶體當成一本書,一張內容記錄了某個頁碼加上行號的便利貼,可以被當成是一個指向特定頁面的指標;根據便利貼上面的頁碼與行號,翻到那個頁面,把那個頁面的那一行文字讀出來,就相當於是對這個指標進行反參考的動作。

高級語言中,指標有效的取代了在低階語言,如匯編語言機器碼,直接使用通用暫存器的地方,但它可能只適用於合法位址之中。因為指標更貼近硬體,編譯器能夠很容易的將指標翻譯為機械碼,這使指標操作時的負擔較少,因此能夠提高程式的運作速度。

使用指標能夠簡化許多資料結構的實作,例如在遍歷字串,查取表格,控制表格及樹狀結構上。對指標進行複製,之後再解參照指標以取出資料,無論在時間或空間上,都比直接複製及存取資料本身來的經濟快速。指標表示法較為直覺,使程式的表達更為簡潔,同時也能夠提供動態機制來建立新的節點。

程序編程(procedural programming)中,指標也被用來保存系統呼叫流程,以及動態連結資料庫(DLL)的進入點位址。在物件導向編程中,使用函數指標(Function pointer)來綁定方法(method),常見於虛擬方法表(Virtual method table)中。

但是指標本身也存在一些功能上的缺點,在存取某個資料結構時,可能會超出可用範圍,使軟體或作業系統出現異常,嚴重時可造成當機。利用指標去存取或修改非合法可取用的資料,也可能造成安全性問題。

許多程式語言中都支援某種形式的指標,最著名的是C語言,但是有些程式語言對指標的運用採取比較嚴格的限制。因為指標的機制比較簡單,其功能可以被集中重新實作成更抽象化的參照(reference)資料形別,如Java一般避免用指针,改為使用參照

歷史[编辑]

在1964年,哈羅德·勞森發明了最早的指標。

概論[编辑]

有两种含义,一是作为数据类型,二是作为实体。

指针作为实体,是一个用来保存一个内存地址的计算机语言中的变量。指针一般出现在比较底层的程序设计语言中,如C语言。高层的语言Java一般避免用指针,而是引用。[1]

指针作为数据类型,可以从一个函数类型、一个对象类型或者一个不完备类型中导出。从中导出的数据类型称之为被引用类型(referenced type)。指针类型描述了一种对象,其值为对被引用类型的实体的引用。[2]

C++标准中规定,“指针”概念不适用于成员指针(不包含指向静态成员的指针)。[3]C++标准规定,指针分为两类:[4]

  • object pointer type:指向void或对象类型,表示对象在内存中的字节地址或空指针。
  • function pointer type:指代一个函数

指针的实质[编辑]

C99与C++11标准分别明确规定了把一个指针值转换自(from)/成(to)整形是允许的,但整型的大小至少不低于std::intptr_t[5]

在C语言的多数实现中,指针值是一个以当前系统寻址范围为取值范围的整数。

32位系统的寻址能力(地址空间)是4GB[6](0~232-1),以二进制表示时长度为32比特,也就是4B。不难验证,在32位系统的大多数实现里,int类型也正好是4B(32-bit)长度,可以取遍上述范围。同理,64位系统取值范围为0~264-1,int类型长度为8B。

使用指针的目的[编辑]

简化代码[编辑]

如果没有指针,很难用一个统一的模式去A的定位并修改一棵树的结点。例如:不用指针要修改A的左子树的左子树的右子结点,只有“A.LC.LC.RC=…”一种表达方式,不能通过赋值而简化。

参数传递[编辑]

C中函数调用是按值传递的,传入参数在子函数中只是一个初值相等的副本,无法对传入参数作任何改动。但实际编程中,经常要改动传入参数的值。在C语言中一般通过传入参数的地址而不是原参数本身来实现。当对传入参数(地址)取“*”运算时,就可以直接在内存中修改,从而改动原想作为传入参数的参数值。

传指针
#include <stdio.h>
void inc(int *val)
{
    (*val)++;
}
int main()
{
    int a=3;
    inc(&a);
    printf("%d", a);
    return 0;
}

在执行inc(&a);时,操作*val,即是在操作a了。

傳值

以下例子中,main()內的變數從來沒有改變,改變的只是sw()內的變數。

#include <iostream>
using namespace std;
 
void sw(int x, int y) {
	int Temp;
	Temp = x;
	x = y;
	y = Temp;
}
 
int main() {
	int a=1;
	int b=2;
	cout <<  a << b << endl;
	sw(a,b);
	cout <<  a << b << endl;
	return 0;
}
 
// Output:
// 12
// 12


sw()執行完畢後,其內容會自動刪除。

a b x y
1 2 - -
1 2 1 2
1 2 2 1
1 2 - -

指针的运算和声明[编辑]

取地址和取值运算[编辑]

*p操作是这样一种运算,其返回内存中地址为p的值的内存空间中的值。&p则是这样一种运算,其返回操作数p的内存地址。[7]显然可以用赋值语句对内存地址赋值。

假设有这么一段内存地址空间,取值如下:(十六进制)

地址 0000 2000 2001 2002 2003 2004 3000 3001 3002 3003
取值 ???? 01 30 00 30 00 00 20 03 9A

然后,执行这么一段代码“int *p;”,假设初始化时p被分配3001H、3002H两个地址。则p为2003H,*p为3000H。[8]

**p的值为多少?

**p=*(*(p))=*(*(2003H))=*(3000H)=0020H。

那么&&p*(&p)&(*p)又等于多少?

&&p=&(&(p))=&(3001H),此时出错了,3001H是个常数怎么可能有地址呢?

*&p=*(&(p))=*(3001H)=2003H,也就是*&p=p

&*p=&(*(p))=&(3000H),此时出错了,3000H是个常数怎么可能有地址呢?

指针的复杂形式[编辑]

双重指针(指向指针的指针)[编辑]

双重指针是指向指针的指针,它是一个指针,这个指针指向某个内存地址,该地址的值是一个指针,指向给另一个内存地址(通常异于前者,但不排除二者相等)。

指针数组[编辑]

指针数组:就是一个数组,数组的各个元素都是指针值。

数组指针[编辑]

数组名出现在表达式中时,很多情况下(除了数组名作为sizeof的操作数或者作为取地址&元素符的操作数)会被隐式转换为指向数组的首个元素的指针右值表达式。

当数组名作为取地址&元素符的操作数,则表达式的值为指向整个数组的指针右值表达式。

例子:

char s[]="hello";
 
int main() {
  char (*p1)[6]=&s; //OK!
  char (*p2)[6]=s; //compile error: cannot convert 'char*' to 'char (*)[6]'   
  char *p3=&s;//compile error: cannot convert 'char (*)[6]' to 'char*' 
}

根据上述C语言标准中的规定,表达式 &s 的值的类型是char (*)[6],即指向整个数组的指针;而表达式 s 则被隐式转换为指向数组首元素的指针值,即 char* 类型。同理,表达式 s[4] ,等效于表达式 *(s+4)。

指向函数的指针[编辑]

指向函数的指针:不同于指向数据类型的指针,函数指针指向一段可执行的代码。很多人都说C语言是一种面向过程的语言[來源請求],因为它最多只有结构体的定义,而没有的概念。根据本段所述,可以认为C语言能成为面向对象的语言,只是表述比较麻烦而已。事实上很多开源程序都使用这种方式组织他们的代码。

#include <stdio.h>
 
void inc(int *val)
{
    (*val)++;
}
 
int main(void)
{
    void (*fun)(int *);
    int a=3;
    fun=inc;
    (*fun)(&a);
    printf("%d", a);
    return 0;
}

指標的進化與取代[编辑]

由於指標太活躍,因此導致它幾乎能不受限制的在各種記憶體位址間活動,所以一旦有任何重複、重疊、溢位的情形發生時,電腦便直接當機,這成為指標功能上的最大缺憾。因此在新的網路程式語言的開發上,新的語言如javac#等語種已經取消了指標的無限制使用形式。 C#允許指標有限功能的使用,指標和運算指標在一個操作的環境中是存在潛在的非安全性的,因為他們的使用可以避開對象的一些嚴格訪問規則。C#中使用指標的代碼段或者方法的地址要用unsafe關鍵字進行標記,這樣,這些代碼的使用者就會知道這個代碼相比其他的代碼而言是不具有安全性的。編譯器需要unsafe關鍵字時將使用此代碼的程序轉換成是允許被編譯的。一般來說,不安全代碼的使用可能是為了非託管的API(應用程序編程介面)的更好互用,或者是為了(存在內在不安全性的)系統調用,也有可能是出於提高性能等方面的原因。而Java中不允許指標或者算術指標的使用。

参考[编辑]

  1. ^ 实质上Java在传递对象的时候用的是按指针(这里认为指针和引用没有本质区别)传递,在传递基本类型(如int)时用的是按值(副本)传递。
  2. ^ C99语言标准的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’’.
  3. ^ C++11标准3.9.2 Compound types中的规定:Except for pointers to static members, text referring to “pointers” does not apply to pointers to members
  4. ^ C++11标准3.9.2第3段
  5. ^ C++11标准3.7.4.3
  6. ^ 这和32位操作系统最大支持内存大小没有关系,所谓32位操作系统只支持4GB的说法是不对的,Windows 2003数据中心版的32位版本就支持最大64GB的内存。操作系统支持的内存数还取决于其存储访问的组织形式,以及操作系统的使用许可。
  7. ^ C99标准6.5.3.2.3中规定:The unary & operator returns the address of its operand.
  8. ^ 这里的字节序为小尾序,Little-Endian,低位在低地址,Intelx86系列CPU适用。MotorolaPowerPC系列则采用大尾序,Big-Endian,高位在低地址,则上述p为0320H,*p需进一步查内存0320H处的存储值。