一、指针
从根本上看,指针是一个值为内存地址的变量(或数据对象)。要搞清一个指针需要搞清指针的四方面的内容:指针的类型、指针所指向的类型、指针的值或者叫指针所指向的内存区、指针本身所占据的内存区。让我们分别说明。
先声明几个指针放着做例子:
例一:
(1)intptr;
(2)charptr;
(3)intptr;
(4)int(ptr)[3];
(5)int(ptr)[4];
1.指针的类型
从语法的角度看,你只要把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型。这是指针本身所具有的类型。看看下面例子中各个指针的类型:
(1)intptr;//指针的类型是int
(2)char*ptr;//指针的类型是char
(3)int**ptr;/指针的类型是int
(4)int(ptr)[3]; //指针所指向的的类型是int()[3]
(5)int(ptr)[4]; //指针所指向的的类型是int()[4]
在指针的算术运算中,指针所指向的类型有很大的作用。
指针的类型(即指针本身的类型)和指针所指向的类型是两个概念。当你对C 越来越熟悉时,你会发现,把与指针搅和在一起的"类型"这个概念分成"指针的类型"和"指针所指向的类型"两个概念,是精通指针的关键点之一。我看了不少书,发现有些写得差的书中,就把指针的这两个概念搅在一起了,所以看起书来前后矛盾,越看越糊涂。
3.指针的值----或者叫指针所指向的内存区或地址
指针的值是指针本身存储的数值,这个值将被编译器当作一个地址,而不是一个一般的数值。在32 位程序里,所有类型的指针的值都是一个32 位整数,因为32 位程序里内存地址全都是32 位长。指针所指向的内存区就是从指针的值所代表的那个内存地址开始,长度为si zeof(指针所指向的类型)的一片内存区。以后,我们说一个指针的值是XX,就相当于说该指针指向了以XX 为首地址的一片内存区域;我们说一个指针指向了某块内存区域,就相当于说该指针的值是这块内存区域的首地址。指针所指向的内存区和指针所指向的类型是两个完全不同的概念。在例一中,指针所指向的类型已经有了,但由于指针还未初始化,所以它所指向的内存区是不存在的,或者说是无意义的。
以后,每遇到一个指针,都应该问问:这个指针的类型是什么?指针指的类型是什么?该指针指向了哪里?(重点注意)
4.指针本身所占据的内存区
指针本身占了多大的内存?你只要用函数sizeof(指针的类型)测一下就知道了。在32 位平台里,指针本身占据了4 个字节的长度。指针本身占据的内存这个概念在判断一个指针表达式(后面会解释)是否是左值时很有用。
二、指针的算术运算
指针可以加上或减去一个整数。指针的这种运算的意义和通常的数值的加减运算的意义是不一样的,以单元为单位。例如:
char a[20];
int ptr=(int )a; /制类型转换并不会改变a 的类型
ptr++;
在上例中,指针ptr 的类型是int,它指向的类型是int,它被初始化为指向整型变量a。接下来的第3句中,指针ptr被加了1,编译器是这样处理的:它把指针ptr 的值加上了sizeof(int),在32 位程序中,是被加上了4,因为在32 位程序中,int 占4 个字节。由于地址是用字节做单位的,故ptr 所指向的地址由原来的变量a 的地址向高地址方向增加了4 个字节。由于char 类型的长度是一个字节,所以,原来ptr 是指向数组a 的第0 号单元开始的四个字节,此时指向了数组a 中从第4 号单元开始的四个字节。我们可以用一个指针和一个循环来遍历一个数组,看例子:
int array[20]={0};
int ptr=array;
for(i=0;i<20;i++)
{
(ptr)++;
ptr++;
}
三、运算符&和
这里&是取地址运算符,是间接运算符。
&a 的运算结果是一个指针,指针的类型是a 的类型加个,指针所指向的类型是a 的类型,指针所指向的地址嘛,那就是a 的地址。
p 的运算结果就五花八门了。总之p 的结果是p 所指向的东西,这个东西有这些特点:它的类型是p 指向的类型,它所占用的地址是p所指向的地址。
四、指针表达式
一个表达式的结果如果是一个指针,那么这个表达式就叫指针表式。
下面是一些指针表达式的例子:
int a,b;
int array[10];
int pa;
pa=&a; //&a 是一个指针表达式。
Int ptr=&pa; //&pa 也是一个指针表达式。
ptr=&b; //ptr 和&b 都是指针表达式。
pa=array;
pa++; //这也是指针表达式。
char arr[20];
char parr=arr; //如果把arr 看作指针的话,arr 也是指针表达式
char str;
str=parr; //parr 是指针表达式
str=(parr+1); //(parr+1)是指针表达式
str=(parr+2); //(parr+2)是指针表达式
由于指针表达式的结果是一个指针,所以指针表达式也具有指针所具有的四个要素:指针的类型,指针所指向的类型,指针指向的内存区,指针自身占据的内存。
好了,当一个指针表达式的结果指针已经明确地具有了指针自身占据的内存的话,这个指针表达式就是一个左值,否则就不是一个左值。在例子中,&a 不是一个左值,因为它还没有占据明确的内存。ptr 是一个左值,因为ptr 这个指针已经占据了内存,其实ptr 就是指针pa,既然pa 已经在内存中有了自己的位置,那么ptr 当然也有了自己的位置。
五、数组和指针的关系
数组的数组名其实可以看作一个指针。看下例:
int array[10]={0,1,2,3,4,5,6,7,8,9},value;
value=array[0]; //也可写成:value=array;
value=array[3]; //也可写成value=(array+3);
value=array[4]; //也可写成:value=(array+4);
上例中,一般而言数组名array 代表数组本身,类型是int[10],但如果把array 看做指针的话,它指向数组的第0 个单元,类型是int 所指向的类型是数组单元的类型即int。因此array 等于0 就一点也不奇怪了。同理,array+3 是一个指向数组第3 个单元的指针,所以(array+3)等于3。其它依此类推。
char str[3]={
"Hello,thisisasample!",
"Hi,goodmorning.",
"Helloworld"
};
char s[80];
strcpy(s,str[0]); //也可写成strcpy(s,str);
strcpy(s,str[1]); //也可写成strcpy(s,(str+1));
strcpy(s,str[2]); //也可写成strcpy(s,(str+2));*
上例中,str 是一个三单元的数组,该数组的每个单元都是一个指针,这些指针各指向一个字符串。把指针数组名str 当作一个指针的话,它指向数组的第0 号单元,它的类型是char ,它指向的类型是char 。
str 也是一个指针,它的类型是char ,它所指向的类型是char,它指向的地址是字符串"Hello,thisisasample!"的第一个字符的地址,即'H'的地址。注意:字符串相当于是一个数组,在内存中以数组的形式储存,只不过字符串是一个数组常量,内容不可改变,且只能是右值.如果看成指针的话,他即是常量指针,也是指针常量.
str+1 也是一个指针,它指向数组的第1 号单元,它的类型是char,它指向的类型是char。
(str+1)也是一个指针,它的类型是char,它所指向的类型是char,它指向"Hi,goodmorning."的第一个字符'H'
下面总结一下数组的数组名(数组中储存的也是数组)的问题:
声明了一个数组TYPE array[n],则数组名称array 就有了两重含义:
第一,它代表整个数组,它的类型是TYPE[n];
第二,它是一个常量指针,该指针的类型是TYPE,该指针指向的类型是TYPE,也就是数组单元的类型,该指针指向的内存区就是数组第0 号单元,该指针自己占有单独的内存区,注意它和数组第0 号单元占据的内存区是不同的。该指针的值是不能修改的,即类似array++的表达式是错误的。在不同的表达式中数组名array 可以扮演不同的角色。在表达式sizeof(array)中,数组名array 代表数组本身,故这时sizeof 函数测出的是整个数组的大小。
在表达式array 中,array 扮演的是指针,因此这个表达式的结果就是数组第0 号单元的值。sizeof(array)测出的是数组单元的大小。
表达式array+n(其中n=0,1,2,.....)中,array 扮演的是指针,故array+n 的结果是一个指针,它的类型是TYPE ,它指向的类型是TYPE,它指向数组第n号单元。故sizeof(array+n)测出的是指针类型的大小。在32 位程序中结果是4
int array[10];
int (ptr)[10];
ptr=&array;
上例中ptr 是一个指针,它的类型是int()[10],他指向的类型是int[10] ,我们用整个数组的首地址来初始化它。在语句ptr=&array中,array 代表数组本身。
本节中提到了函数sizeof(),那么我来问一问,sizeof(指针名称)测出的究竟是指针自身类型的大小呢还是指针所指向的类型的大小?
答案是前者。例如:
int(ptr)[10];
则在32 位程序中,有:
sizeof(int()[10])4
sizeof(int[10])40
sizeof(ptr)==4
实际上,sizeof(对象)测出的都是对象自身的类型的大小,而不是别的什么类型的大小。七、指向数组的指针的自增:
int a[5];
int p = a;
可以 ++p 递增指针p指向下一个数组元素,然后用p取得元素的值。*
能不能用a++或者++a把指针指向下一个数组元素? 不能!!! 开头就说过,数组名是指向数组首元素的指针常量。指针a是不可以指向其他元素的,只能指向首元素的起始地址。
八、 数组指针做形参:
把数组从主调函数传到被调函数时,我们可以传数组的首地址和数组长度就可以,为什么不是传递数组呢?还是为了效率,下面用代码说明:
void Output(char *p, int len);
int main()
{undefined
char str[] = "HelloWorld HelloWorld HelloWorld HelloWorld HelloWorld HelloWorld ";
Output(str, strlen(str));
system("pause");
return 0;
}
void Output(char *p, int len)
{undefined
for (int i = 0; i < len; ++i)
{undefined
printf("%c", *(p + i));
}
}
如上,如果我们Output函数不是用指针的方式char *p,而是用数组的方式char p[]的话,有什么不一样?
我们定义的数组str有65字节,调用函数的话会传递这65字节的数组。
char *p指针大小是4字节,只需要传递4字节的数据就可以了,不止少传递数据,执行起来也更快了。