2017年 1月 15日 指针 学习整理
有关指针的概念:
指针是一个特殊的变量,它里面存储的数值被解释为内存里的一个地址。
FIrst of all:我们需要明确目标
关于指针的学习以及使用我们需要搞清楚有关指针的四个内容:指针的类型,指针所指向的类型,指针的值(或者说叫指针所指向的内存区),还有指针本身所占用的内存区(指针也是一个特殊的变量吗,它肯定也是占据内存的)。接下来让我们分别进行学习。
我们先来申明几个指针的例子:
1 int *ptr; 2 char *ptr; 3 int **ptr; 4 int (*ptr)[3]; 5 int *(*ptr)[4];
The First:指针的类型
从语法的角度来看,你将指针声明的语句(如:int *ptr;)中的指针名去掉就是指针本身的类型。(指针的类型分很多种)
int *ptr; //指针类型是int * char *ptr; //指针类型是char * int **ptr; //指针类型是int ** int (*ptr)[3];// 指针类型是int(*)[3] int *(*ptr)[4];// 指针类型是int *(*)[4]
还可以吧!这部分应该是很好理解的。
The Second:指针所指向的类型
当你通过指针来访问指针所指向的内存区时,指针所指向的类型决定了编译器将把那片内存区里的内容当做什么来看待。
从语法上看,你只须把指针声明语句中的指针名字和名字左边的指针声明符*去掉,剩下的就是指针所指向的类型。例如:
int *ptr; //指针所指向的类型是int char *ptr; //指针所指向的的类型是char int **ptr; //指针所指向的的类型是 int * int (*ptr)[3]; //指针所指向的的类型是 int()[3] int *(*ptr)[4]; //指针所指向的的类型是 int *()[4]
在指针的算术运算中,指针所指向的类型有很大的作用。
指针的类型(即指针本身的类型)和指针所指向的类型是完全不同两个概念。当你对C越来越熟悉时,你会发现,把与指针搅和在一起的“类型”这个概念分成“指针的类型”和“指针所指向的类型”两个概念,是精通指针的关键点之一。我看了不少书,发现有些写得差的书中,就把指针的这两个概念搅在一起了,所以看起书来前后矛盾,越看越糊涂(想学好指针还是好好看我的博客吧-_-)。
The Third:指针的值
指针的值是指针本身存储的数值,这个值将被编译器当作一个地址,而不是一个一般的数值。在32位程序里,所有类型的指针的值都是一个32位整数,因为32位程序里内存地址全都是32位长。
指针所指向的内存区就是从指针的值所代表的那个内存地址开始,长度为sizeof(指针所指向的类型)的一片内存区。以后,我们说一个指针的值是XX,就相当于说该指针指向了以XX为首地址的一片内存区域;我们说一个指针指向了某块内存区域,就相当于说该指针的值是这块内存区域的首地址。
指针所指向的内存区和指针所指向的类型是两个完全不同的概念。在例一中,指针所指向的类型已经有了,但由于指针还未初始化,所以它所指向的内存区是不存在的,或者说是无意义的。
以后,每遇到一个指针,都应该问问:这个指针的类型是什么?指针指向的类型是什么?该指针指向了哪里? (尤其是在初学阶段,刚开始学习费点时间没关系,不然日后.....)
The Fourth:指针本身所占据的内存区
指针本身到底占据了多大内存呢?你只需要用sizeof(指针的类型)函数测一下就可以知道了。在32位的机器里,指针本身占据4个字节的长度。
64位机器应该是8个字节(具体忘了,有用64位机子的自己测一下,Re我。。)
指针本身占据的内存这个概念在判断一个指针表达式是否是左值(后面讲到,此处暂可忽略)时很有用。
好,到这里指针四大条已经讲完了,是不是感觉很简单呢!
指针的算数运算:
我们刚才已经知道指针其实是一个特殊的变量,所以它是可以的进行运算的。指针的运算和通常的算术运算不太相同。
eg:
char a[20]; int *ptr=a; ... ... ptr++;
我们定义了char数组a,指针ptr初始化为整形变量a,这时指针指向char数组a的内存开始的地方,也就是指向了a[0],然后ptr++,然而它不是+1这么简单。我们假设ptr指针指向的类型是T,那么ptr++就等同于ptr+=sizeof(T)。在上面的例子中由于char所占一个字节,ptr恰好+1,此时他指向了a[1]。若T是int类型,ptr++==ptr+sizeof(int){sizeof(int)==4},同样它由指向a[0]指向了a[1];
char a[20]; int *ptr = a; ... ... ptr += 5;
在这个例子中,ptr被加上了5,编译器是这样处理的:将指针ptr的值加上5乘sizeof(int),在32位程序中就是加上了5乘4=20。由于地址的单位是字节,故现在的ptr所指向的地址比起加5后的ptr所指向的地址来说,向高地址方向移动了20个字节。在这个例子中,没加5前的ptr指向数组a的第0号单元开始的四个字节,加5后,ptr已经指向了数组a的合法范围之外了(此时ptr指向啥玩意没人知道,也许是别的程序的内存,也许是空内存,还有可能系统不允许访问)。虽然这种情况在应用上会出问题,但在语法上却是可以的。这也体现出了指针的灵活性。
如果上例中,ptr是被减去5,那么处理过程大同小异,只不过ptr的值是被减去5乘sizeof(int),新的ptr指向的地址将比原来的ptr所指向的地址向低地址方向移动了20个字节。
总结一下,一个指针ptrold加上一个整数n后,结果是一个新的指针ptrnew,ptrnew的类型和ptrold的类型相同,ptrnew所指向的类型和ptrold所指向的类型也相同。ptrnew的值将比ptrold的值增加了n乘sizeof(ptrold所指向的类型)个字节。就是说,ptrnew所指向的内存区将比ptrold所指向的内存区向高地址方向移动了n乘sizeof(ptrold所指向的类型)个字节。一个指针ptrold减去一个整数n后,结果是一个新的指针ptrnew,ptrnew的类型和ptrold的类型相同,ptrnew所指向的类型和ptrold所指向的类型也相同。ptrnew的值将比ptrold的值减少了n乘sizeof(ptrold所指向的类型)个字节,就是说,ptrnew所指向的内存区将比ptrold所指向的内存区向低地址方向移动了n乘sizeof(ptrold所指向的类型)个字节。
运算符&和*
这里&是取地址运算符,*是...书上叫做“间接运算符”。&a的运算结果是一个指针,指针的类型是a的类型加个*,指针所指向的类型是a的类型,指针所指向的地址嘛,那就是a的地址。*p的运算结果就五花八门了。总之*p的结果是p所指向的东西,这个东西有这些特点:它的类型是p指向的类型,它所占用的地址是p所指向的地址。
eg:
1 int a=12; 2 int b; 3 int *p; 4 int **ptr; 5 p=&a;//&a的结果是一个指针,类型是int*,指向的类型是int,指向的地址是a的地址。 6 *p=24;//*p的结果,在这里它的类型是int,它所占用的地址是p所指向的地址,显然,*p就是变量a,即a被赋值成了24。 7 ptr=&p;//&p的结果是个指针,该指针的类型是p的类型加个*,在这里是int**。该指针所指向的类型是p的类型(int*)。该指针所指向的地址就是指针p自己的地址(循环相指,好好想想昂)。 8 *ptr=&b;//*ptr是个指针,&b的结果也是个指针,且这两个指针的类型和所指向的类型是一样的,所以?amp;b来给*ptr赋值就是毫无问题的了。 9 **ptr=34;//*ptr的结果是ptr所指向的东西,在这里是一个指针,对这个指针再做一次*运算,结果就是一个int类型的变量。
自己最好多写几个小程序练习一下,练几次就明白了。
指针表达式:
一个表达式的最后结果如果是一个指针,那么这个表达式就叫指针表达式。下面是一些指针表达式的例子:
1 int a,b; 2 int array[10]; 3 int *pa; 4 pa=&a;//&a是一个指针表达式。 5 int **ptr=&pa;//&pa也是一个指针表达式。 6 *ptr=&b;//*ptr和&b都是指针表达式。 7 pa=array; 8 pa++;//这也是指针表达式。
1 char *arr[20]; 2 char **parr=arr;//如果把arr看作指针的话,arr也是指针表达式 3 char *str; 4 str=*parr;//*parr是指针表达式 5 str=*(parr+1);//*(parr+1)是指针表达式 6 str=*(parr+2);//*(parr+2)是指针表达式
由于指针表达式的结果是一个指针,所以指针表达式也具有指针所具有的四个要素:指针的类型,指针所指向的类型,指针指向的内存区,指针自身占据的内存。
好了,当一个指针表达式的结果指针已经明确地具有了指针自身占据的内存的话,这个指针表达式就是一个左值,否则就不是一个左值。 在例七中,&a不是一个左值,因为它还没有占据明确的内存。*ptr是一个左值,因为*ptr这个指针已经占据了内存,其实*ptr就是指针pa,既然pa已经在内存中有了自己的位置,那么*ptr当然也有了自己的位置。
数组和指针的关系:
eg:
1 int array[10]={0,1,2,3,4,5,6,7,8,9},value; 2 ... 3 ... 4 value=array[0];//也可写成:value=*array; 5 value=array[3];//也可写成:value=*(array+3); 6 value=array[4];//也可写成:value=*(array+4);
上例中,一般而言数组名array代表数组本身,类型是int [10],但如果把array看做指针的话,它指向数组的第0个元素,类型是int *,所指向的类型是数组单元的类型即int。因此*array等于0就一点也不奇怪了。同理,array+3是一个指向数组第3个单元的指针,所以*(array+3)等于3。其它依此类推。
指针和结构类型的关系:
可以用于声明一个指向结构类型对象的指针。
eg:
1 struct MyStruct 2 { 3 int a; 4 int b; 5 int c; 6 } 7 8 MyStruct ss={20,30,40};//声明了结构对象ss,并把ss的三个成员初始化为20,30和40。 9 MyStruct *ptr=&ss;//声明了一个指向结构对象ss的指针。它的类型是 10 MyStruct*,它指向的类型是MyStruct。 11 int *pstr=(int*)&ss;//声明了一个指向结构对象ss的指针。但是它的类型和它指向的类型和ptr是不同的。
请问怎样通过指针ptr来访问ss的三个成员变量?
ptr->a; ptr->b; ptr->c;
指针和函数的关系:
可以把一个指针声明成为一个指向函数的指针。
int fun1(char*,int); int (*pfun1)(char*,int); pfun1=fun1; .... .... int a=(*pfun1)("abcdefg",7);//通过函数指针调用函数。
可以把指针作为函数的形参。在函数调用语句中,可以用指针表达式来作为实参。
指针到此宣告结束