字符串⽐较⼤⼩c语⾔指针详解经典⾮常详细,C语⾔指针详解
(经典,⾮常详细)
前⾔:复杂类型说明
要了解指针,多多少少会出现⼀些⽐较复杂的类型,所以我先介绍⼀下如何完全理解⼀个复杂类型,要理解复杂类型其实很简单,⼀个类型⾥会出现很多运算符,他们也像普通的表达式⼀样,有优先级,其优先级和运算优先级⼀样,所以我总结了⼀下其原则:从变量名处起,根据运算符优先级结合,⼀步⼀步分析.下⾯让我们先从简单的类型开始慢慢分析吧:int p; //这是⼀个普通的整型变量
int *p; //⾸先从P 处开始,先与*结合,所以说明P 是⼀个指针,然后再与int 结合,说明指针所指向的内容的类型为int 型.所以P是⼀个返回整型数据的指针
int p[3]; //⾸先从P 处开始,先与[]结合,说明P 是⼀个数组,然后与int 结合,说明数组⾥的元素是整型的,所以P 是⼀个由整型数据组成的数组
int *p[3]; //⾸先从P 处开始,先与[]结合,因为其优先级⽐*⾼,所以P 是⼀个数组,然后再与*结合,说明数组⾥的元素是指针类型,然后再与int 结合,说明指针所指向的内容的类型是整型的,所以P 是⼀个由返回整型数据的指针所组成的数组
int (*p)[3]; //⾸先从P 处开始,先与*结合,说明P 是⼀个指针然后再与[]结合(与"()"这步可以忽略,只是为了改变优先级),说明指针所指向的内容是⼀个数组,然后再与int 结合,说明数组⾥的元素是整型的.所以P 是⼀个指向由整型数据组成的数组的指针
int **p; //⾸先从P 开始,先与*结合,说是P 是⼀个指针,然后再与*结合,说明指针所指向的元素是指针,然后再与int 结合,说明该指针所指向的元素是整型数据.由于⼆级指针以及更⾼级的指针极少⽤在复杂的类型中,所以后⾯更复杂的类型我们就不考虑多级指针了,最多只考虑⼀级指针.
int p(int); //从P 处起,先与()结合,说明P 是⼀个函数,然后进⼊()⾥分析,说明该函数有⼀个整型变量的参数,然后再与外⾯的int 结合,说明函数的返回值是⼀个整型数据
Int (*p)(int); //从P 处开始,先与指针结合,说明P 是⼀个指针,然后与()结合,说明指针指向的是⼀个函数,然后再与()⾥的int 结合,说明函数有⼀个int 型的参数,再与最外层的int 结合,说明函数的返回类型是整型,所以P 是⼀个指向有⼀个整型参数且返回类型为整型的函数的指针
int *(*p(int))[3]; //可以先跳过,不看这个类型,过于复杂从P 开始,先与()结合,说明P 是⼀个函数,然后进⼊()⾥⾯,与int 结合,说明函数有⼀个整型变量参数,然后再与外⾯的*结合,说明函数返回的是⼀个指针,,然后到最外⾯⼀层,先与[]结合,说明返回的指针指向的是⼀个数组,然后再与*结合,说明数组⾥的元素是指针,然后再与int 结合,说明指针指向的内容是整型数据.所以P 是⼀个参数为⼀
个整数据且返回⼀个指向由整型指针变量组成的数组的指针变量的函数.
说到这⾥也就差不多了,我们的任务也就这么多,理解了这⼏个类型,其它的类型对我们来说也是⼩菜了,不过我们⼀般不会⽤太复杂的类型,那样会⼤⼤减⼩程序的可读性,请慎⽤,这上⾯的⼏种类型已经⾜够我们⽤了.
⼀、细说指针指针是⼀个特殊的变量,它⾥⾯存储的数值被解释成为内存⾥的⼀个地址。要搞清⼀个指针需要搞清指针的四⽅⾯的内容:指针的类型、指针所指向的类型、指针的值或者叫指针所指向的内存区、指针本⾝所占据的内存区。让我们分别说明。
先声明⼏个指针放着做例⼦:
例⼀:(1)int*ptr;
(2)char*ptr;
(3)int**ptr;
(4)int(*ptr)[3];
(5)int*(*ptr)[4];
1.指针的类型从语法的⾓度看,你只要把指针声明语句⾥的指针名字去掉,剩下的部分就是这个指针的类型。这是指针本⾝所具有的类型。让我们看看例⼀中各个指针的类型:
(1)int*ptr;//指针的类型是int*
(2)char*ptr;//指针的类型是char*
(3)int**ptr;//指针的类型是int**
(4)int(*ptr)[3];//指针的类型是int(*)[3]
(5)int*(*ptr)[4];//指针的类型是int*(*)[4]
怎么样?出指针的类型的⽅法是不是很简单?
2.指针所指向的类型当你通过指针来访问指针所指向的内存区时,指针所指向的类型决定了编译器将把那⽚内存区⾥的内容当做什么来看待。
从语法上看,你只须把指针声明语句中的指针名字和名字左边的指针声明符*去掉,剩下的就是指针所指向的类型。例如:
(1)int*ptr; //指针所指向的类型是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++;
}
这个例⼦将整型数组中各个单元的值加1。由于每次循环都将指针ptr加1 个单元,所以每次循环都能访问数组的下⼀个单元。再看例⼦:
例四:char a[20]="You_are_a_girl";
int *ptr=(int *)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 是被减去5,那么处理过程⼤同⼩异,只不过ptr 的值是被减去5 乘sizeof(int),新的ptr 指向的地址将⽐原来的ptr 所指向的地址向低地址⽅向移动了20 个字节。
下⾯请允许我再举⼀个例⼦:(⼀个误区)
例五:#include
int main()
{
char a[20]=" You_are_a_girl";
char *p=a;
char **ptr=&p;
//printf("p=%d\n",p);
//printf("ptr=%d\n",ptr);
//printf("*ptr=%d\n",*ptr);
printf("**ptr=%c\n",**ptr);
ptr++;
//printf("ptr=%d\n",ptr);
//printf("*ptr=%d\n",*ptr);
printf("**ptr=%c\n",**ptr);
}
误区⼀、输出答案为Y 和o
误解:ptr 是⼀个char 的⼆级指针,当执⾏ptr++;时,会使指针加⼀个sizeof(char),所以输出如上结果,这个可能只是少部分⼈的结果.
误区⼆、输出答案为Y 和a误解:ptr 指向的是⼀个char *类型,当执⾏ptr++;时,会使指针加⼀个sizeof(char *)(有可能会有⼈认为这个值为1,那就会得到误区⼀的答案,这个值应该是4,参考前⾯内容), 即&p+4; 那进⾏⼀次取值运算不就指向数组中的第五个元素了吗?那输出的结果不就是数组中第五个元素了吗?答案是否定的.
正解: ptr 的类型是char **,指向的类型是⼀个char *类型,该指向的地址就是p的地址(&p),当执⾏ptr++;时,会使指针加⼀个sizeof(char*),即&p+4;那*(&p+4)指向哪呢,这个你去问上帝吧,或者他会告诉你在哪?所以最后的输出会是⼀个随机的值,或许是⼀个⾮法操作.
总结⼀下:
⼀个指针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所指向的地址。
例六:int a=12; int b; int *p; int **ptr;
p=&a; //&a 的结果是⼀个指针,类型是int*,指向的类型是
//int,指向的地址是a 的地址。
*p=24; //*p 的结果,在这⾥它的类型是int,它所占⽤的地址是
//p 所指向的地址,显然,*p 就是变量a。
ptr=&p; //&p 的结果是个指针,该指针的类型是p 的类型加个*,
//在这⾥是int **。该指针所指向的类型是p 的类型,这
//⾥是int*。该指针所指向的地址就是指针p ⾃⼰的地址。
*ptr=&b; //*ptr 是个指针,&b 的结果也是个指针,且这两个指针
//的类型和所指向的类型是⼀样的,所以⽤&b 来给*ptr 赋
//值就是毫⽆问题的了。
**ptr=34; //*ptr 的结果是ptr 所指向的东西,在这⾥是⼀个指针,
//对这个指针再做⼀次*运算,结果是⼀个int 类型的变量。
四、指针表达式⼀个表达式的结果如果是⼀个指针,那么这个表达式就叫指针表式。
下⾯是⼀些指针表达式的例⼦:
例七:
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 当然也有了⾃⼰的位置。
sizeof 指针五、数组和指针的关系数组的数组名其实可以看作⼀个指针。看下例:
例九: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

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。