中文名 | 指针 |
外文名 | pointer |
类别 | 指示测量的数据的装置 |
适用范围 | 计算机语言 |
作用 | 通过它找到以它为地址的内存单元 |
在计算机科学中,指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向(points to)存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址的内存单元。
在高级语言中,指针有效地取代了在低级语言,如汇编语言与机器码,直接使用通用暂存器的地方,但它可能只适用于合法地址之中。指针参考了存储器中某个地址,通过被称为反参考指针的动作,可以取出在那个地址中存储的值。作个比喻,假设将电脑存储器当成一本书,一张内容记录了某个页码加上行号的便利贴,可以被当成是一个指向特定页面的指针;根据便利粘贴面的页码与行号,翻到那个页面,把那个页面的那一行文字读出来,就相当于是对这个指针进行反参考的动作。
在信息工程中,指针是一个用来指示一个内存地址的计算机语言的变量或中央处理器(CPU)中寄存器(Register)【用来指向该内存地址所对应的变量或数组】。指针一般出现在比较接近机器语言的语言,如汇编语言或C语言。面向对象的语言如Java一般避免用指针。指针一般指向一个函数或一个变量。在使用一个指针时,一个程序既可以直接使用这个指针所储存的内存地址,又可以使用这个地址里储存的函数的值。
另外,指针也指钟表中用来指示对应时间的部件。
简介
使用指针来读取数据,在重复性操作的状况下,可以明显改善程序性能,例如在遍历字符串,查取表格,控制表格及树状结构上。对指针进行复制,之后再解引用指针以取出数据,无论在时间或空间上,都比直接复制及访问数据本身来的经济快速。
指针的机制比较简单,其功能可以被集中重新实现成更抽象化的引用(reference)数据形别。许多编程语言中都支持某种形式的指针,最著名的是C语言,但是有些编程语言对指针的运用采取比较严格的限制,如Java一般避免用指针,改为使用引用。
有两种含义,一是作为数据类型,二是作为实体。
指针作为实体,是一个用来保存一个内存地址的计算机语言中的变量。指针一般出现在比较底层的程序设计语言中,如C语言。高层的语言如Java一般避免用指针,而是引用。
指针作为数据类型,可以从一个函数类型、一个对象类型或者一个不完备类型中导出。从中导出的数据类型称之为被引用类型(referenced type)。指针类型描述了一种对象,其值为对被引用类型的实体的引用。
C++标准中规定,“指针”概念不适用于成员指针(不包含指向静态成员的指针)。
C++标准规定,指针分为两类:
1、object pointer type:指向void或对象类型,表示对象在内存中的字节地址或空指针;
2、function pointer type:指代一个函数 ;
指针与C语言
大家都认为,c语言之所以强大,以及其自由性,很大部分体现在其灵活的指针运用上。因此,说指针是c语言的灵魂,一点都不为过。同时,这种说法也让很多人产生误解,似乎只有C语言的指针才能算指针。basic不支持指针,在此不论。其实,pascal语言本身也是支持指针的。从最初的pascal发展至今的object pascal,可以说在指针运用上,丝毫不会逊色于c语言的指针。
内存分配表
计算机中的内存都是编址的,就像你家的地址一样。在程序编译或者运行的时候,系统(可以不关心具体是什么,可能是编译器,也可能是操作系统)开辟了一张表。每遇到一次声明语句(包括函数的传入参数的声明)都会开辟一个内存空间,并在表中增加一行纪录,记载着一些对应关系。
int nP;
char myChar;
int *myPointer;
char *myPointer2;
指针,是一个无符号整数(unsigned int),它是一个以当前系统寻址范围为取值范围的整数。32位系统下寻址能力(地址空间)是4G Bytes(0~2^32-1)二进制表示长度为32bits(也就是4Bytes), unsigned int类型也正好如此取值。
按值传递
C中函数调用是按值传递的,传入参数在子函数中只是一个初值相等的副本,无法对传入参数作任何改动。但实际编程中,经常要改动传入参数的值。这一点我们可以用传入参数的地址而不是原参数本身,当对传入参数(地址)取(*)运算时,就可以直接在内存中修改,从而改动原想作为传入参数的参数值。
#include <stdio.h>
void inc(int *val)
{
(*val)++;
}
int main()
{
int a = 3;
inc(&a);
printf("%d",a);
}
/*
在执行 inc(&a); 时,系统在内存分配表里增加了一行“ inc 中的val”,其地址为新地址,值为&a。
操作 *val ,即是在操作a了。
*/
*和&运算
(*p)操作是这样一种运算,返回 p 的值作为地址的那个空间的取值。
(&p)则是这样一种运算,返回当时声明p 时开辟的地址。显然可以用赋值语句对内存地址赋值。
另类*和&
两个地方要注意:
1、在程序声明变量的时候的 ‘ * ’,只是表明“它是一个无符号整数,这个整数指向某个内存地址,一次访问sizeof(type)长度”。这点不要和 (*) 操作符 混淆;
2、在C++程序声明变量的时候的 ‘ & ’,只是表明“它是一个引用,这个引用声明时不开辟新空间,它在内存分配表加入新的一行,该行内存地址等于和调用时传入的对应参数内存地址”。
这点不要和(*)声明符,(&)操作符混淆。
双级指针
对于一棵树,我们通常用它的根节点地址来表示这棵树。这就是“擒贼先擒王”。找到了树的根,其每个节点都可以找到。但是有时候我们需要对树进行删除节点,增加节点操作,往往考虑到删除根节点,增加的节点取代原来的根节点作为新根节点的情况。为了修改根节点这个“整数”,我们需要退一步,使用这个“整数”的内存地址,也就是指向这个“整数”的指针。在声明时,我们用2 个 ‘ * ’ 号,声明指向指针的指针。它的意思是“它是一个整数,这个整数指向某个内存地址,一次访问sizeof(int)长度,其值是一个整数,那个整数值指向某个内存地址,一次访问sizeof(BTree)长度。”。由于存放的指针变量的地址,因此是指向指针变量的指针变量,或称二级指针变量。
指针的初始化
对指针进行初始化或赋值只能使用以下四种类型的值 [3] :
1、0 值常量表达式,例如,在编译时可获得 0 值的整型 const对象或字面值常量 0。
2、类型匹配的对象的地址。
3、另一对象末的下一地址。
4、同类型的另一个有效指针。
把 int 型变量赋给指针是非法的,尽管此 int 型变量的值可能为 0。但允许把数值 0 或在编译时可获得 0 值的 const 量赋给指针:
int ival;
int zero = 0;
const int c_ival = 0;
int *pi = ival;
pi = zero; //error: pi initialized from int value of ival
pi = zero; //error: pi assigned int value of zero
pi = c_vial; //ok: c_ival is a const with compile-time value of 0
pi = 0; //ok: directly initialize to literal constant 0
除了使用数值 0 或在编译时值为 0 的 const 量外,还可以使用 C++ 语言从 C 语言中继承下来的预处理器变量 NULL,其值为 0。如果在代码中使用了这个预处理器变量,则编译时会自动被数值 0 替换。因此,把指针初始化为 NULL 等效于初始化为 0 值.
// #defines NULL to 0
int *pi = NULL; //ok: equivalent to int *pi = 0;
与数组关系
指针数组:就是一个由指针组成的数组,那个数组的各个元素都是指针,指向某个内存地址。
char *p[10]; //p是一个指针数组
数组指针:数组名本身就是一个指针,指向数组的首地址。注意这是一个常数。
char (*p)[10]; //p是一个数组指针
函数指针:本身是一个指针,指向一个函数入口地址,通过该指针可调用其指向的函数,使用函数指针可实现回调函数。
#include <stdio.h>
void inc(int *val)
{
(*val)++;
}
int main()
{
void (*fun)(int *);
int a = 3;
fun = inc; //fun是一个函数指针
(*fun)(&a);
printf("%d",a);
}
指针函数:本身是一个函数,其返回值是一个指针。
void * fun(void); // fun是一个指针函数
与“引用”的区别
一、指针和引用的区别
(1)、引用总是指向一个对象,没有所谓的 null reference 。所有当有可能指向一个对象也有可能不指向对象则必须使用指针。由于C++ 要求 reference 总是指向一个对象所以 reference要求有初值。
String & rs = string1;
由于没有所谓的 null reference ,所以在使用前不需要进行测试其是否有值,而使用指针则需要测试其的有效性。
(2)、指针可以被重新赋值而reference则总是指向最初或地的对象。
(3)、必须使用reference的场合.。Operator[ ] 操作符由于该操作符很特别地必须返回【能够被当做assignment 赋值对象 ]】的东西,所以需要给他返回一个 reference。
(4)、其实引用在函数的参数中使用很经常。
void Get***(const int& a) //这样使用了引用又可以保证不修改被引用的值
{
}
相同点:
1、都是地址的概念: 指针指向一块内存,它的内容是所指内存的地址;引用是某块内存的别名。
不同点:
1、指针是一个实体,而引用仅是个别名;
2、引用使用时无需解引用(*),指针需要解引用;
3、引用只能在定义时被初始化一次,之后不可变;指针可变;引用“从一而终”;
4、引用没有 const,指针有 const,const 的指针不可变;
5、引用不能为空,指针可以为空;
6、“sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身(所指向的变量或对象的地址) 的大小;但是当引用作为成员时,其占用空间与指针相同(没找到标准的规定)。
7、指针和引用的自增(++)运算意义不一样;
C++中指针传递与引用传递
从概念上讲,指针从本质上讲就是存放变量地址的一个变量,在逻辑上是独立的,它可以被改变,包括其所指向的地址的改变、其指向的地址中所存放的数据的改变。
而引用是一个别名,它在逻辑上不是独立的,它的存在具有依附性,所以引用必须在一开始就被初始化,而且其引用的对象在其整个生命周期中是不能被改变的(自始至终只能依附于同一个变量)。
在C++中,指针和引用经常用于函数的参数传递,然而,指针传递参数和引用传递参数是有本质上的不同的:
指针传递参数本质上是值传递的方式,它所传递的是一个地址值。值传递过程中,被调函数的形式参数作为被调函数的局部变量处理,即在栈中开辟了内存空间以存放由主调函数放进来的实参的值,从而成为了实参的一个副本。值传递的特点是被调函数对形式参数的任何操作都是作为局部变量进行,不会影响主调函数的实参变量的值。(这里是在说实参指针本身的地址值不会变)
而在引用传递过程中,被调函数的形式参数虽然也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都影响了主调函数中的实参变量。
引用传递和指针传递是不同的,虽然它们都是在被调函数栈空间上的一个局部变量,但是任何对于引用参数的处理都会通过一个间接寻址的方式操作到主调函数中的相关变量。而对于指针传递的参数,如果改变被调函数中的指针地址,它将影响不到主调函数的相关变量。如果想通过指针参数传递来改变主调函数中的相关变量,那就得使用指向指针的指针,或者指针引用。
程序在编译时分别将指针和引用添加到符号表上,符号表上记录的是变量名及变量所对应地址。指针变量在符号表上对应的地址值为指针变量的地址值,而引用在符号表上对应的地址值为引用对象的地址值。符号表生成后就不会再改,因此指针可以改变其指向的对象(指针变量中的值可以改),而引用对象则不能修改。