C语言指针详解(经典,非常详细)

C语言指针详解(经典,非常详细)前言 复杂类型说明要了解指针 多多少少会出现一些比较复杂的类型 所以我先介绍一下如何完全理解一个复杂类型 要理解复杂类型其实很简单 一个类型里会出现很多运算符 他们也像普通的表达式一样 有优先级 其优先级和运算优先级一样 所以我总结了一下其原则 从变量名处起 根据运算符优先级结合 一步一步分析 下面让我们先从简单的类型开始慢慢分析吧 intp 这是一个普通的整型变量 i

前言:复杂类型说明

    要了解指针,多多少少会出现一些比较复杂的类型,所以我先介绍一下如何完全理解一个复杂类型,要理解复杂类型其实很简单,一个类型里会出现很多运算符,他们也像普通的表达式一样,有优先级,其优先级和运算优先级一样,所以我总结了一下其原则:从变量名处起,根据运算符优先级结合,一步一步分析.下面让我们先从简单的类型开始慢慢分析吧:

  1. int p; //这是一个普通的整型变量 
  2. int *p; //首先从P 处开始,先与*结合,所以说明P 是一个指针,然后再与int 结合,说明指针所指向的内容的类型为int 型.所以P是一个返回整型数据的指针 
  3. int p[3]; //首先从P 处开始,先与[]结合,说明P 是一个数组,然后与int 结合,说明数组里的元素是整型的,所以P 是一个由整型数据组成的数组 
  4. int *p[3]; //首先从P 处开始,先与[]结合,因为其优先级比*高,所以P 是一个数组,然后再与*结合,说明数组里的元素是指针类型,然后再与int 结合,说明指针所指向的内容的类型是整型的,所以P 是一个由返回整型数据的指针所组成的数组 
  5. int (*p)[3]; //首先从P 处开始,先与*结合,说明P 是一个指针然后再与[]结合(与”()”这步可以忽略,只是为了改变优先级),说明指针所指向的内容是一个数组,然后再与int 结合,说明数组里的元素是整型的.所以P 是一个指向由整型数据组成的数组的指针 

 

  1. int p; //首先从P 开始,先与*结合,说是P 是一个指针,然后再与*结合,说明指针所指向的元素是指针,然后再与int 结合,说明该指针所指向的元素是整型数据.由于二级指针以及更高级的指针极少用在复杂的类型中,所以后面更复杂的类型我们就不考虑多级指针了,最多只考虑一级指针. 
  2. int p(int); //从P 处起,先与()结合,说明P 是一个函数,然后进入()里分析,说明该函数有一个整型变量的参数,然后再与外面的int 结合,说明函数的返回值是一个整型数据 
  3. Int (*p)(int); //从P 处开始,先与指针结合,说明P 是一个指针,然后与()结合,说明指针指向的是一个函数,然后再与()里的int 结合,说明函数有一个int 型的参数,再与最外层的int 结合,说明函数的返回类型是整型,所以P 是一个指向有一个整型参数且返回类型为整型的函数的指针 
  4. int *(*p(int))[3]; //可以先跳过,不看这个类型,过于复杂从P 开始,先与()结合,说明P 是一个函数,然后进入()里面,与int 结合,说明函数有一个整型变量参数,然后再与外面的*结合,说明函数返回的是一个指针,,然后到最外面一层,先与[]结合,说明返回的指针指向的是一个数组,然后再与*结合,说明数组里的元素是指针,然后再与int 结合,说明指针指向的内容是整型数据.所以P 是一个参数为一个整数据且返回一个指向由整型指针变量组成的数组的指针变量的函数.

说到这里也就差不多了,我们的任务也就这么多,理解了这几个类型,其它的类型对我们来说也是小菜了,不过我们一般不会用太复杂的类型,那样会大大减小程序的可读性,请慎用,这上面的几种类型已经足够我们用了.

一、细说指针

例一:

  1. (1)int*ptr; 
  2. (2)char*ptr; 
  3. (3)intptr; 
  4. (4)int(*ptr)[3]; 
  5. (5)int*(*ptr)[4];

1.指针的类型

2.指针所指向的类型

3.指针的值—-或者叫指针所指向的内存区或地址

4 指针本身所占据的内存区

指针本身占了多大的内存?你只要用函数sizeof(指针的类型)测一下就知道了。在32 位平台里,指针本身占据了4 个字节的长度。指针本身占据的内存这个概念在判断一个指针表达式(后面会解释)是否是左值时很有用。

 

二、指针的算术运算

例二:

  1.     char a[20]; 
  2.     int *ptr=(int *)a; //强制类型转换并不会改变a 的类型 
  3.     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 号单元开始的四个字节。我们可以用一个指针和一个循环来遍历一个数组,看例子:

例三:

  1.     int array[20]={
    0}; 
  2.     int *ptr=array
  3.     for(i=0;i<20;i++) 
  4.     { 
  5.         (*ptr)++; 
  6.         ptr++ 
  7.     } 

这个例子将整型数组中各个单元的值加1。由于每次循环都将指针ptr加1 个单元,所以每次循环都能访问数组的下一个单元。

 

例四:

  1. char a[20]=“You_are_a_girl”
  2. int *ptr=(int *)a; 
  3. 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 个字节。
下面请允许我再举一个例子:(一个误区)

例五:

  1.     #include

     
  2.     int main() 
  3.     { 
  4.         char a[20]=” You_are_a_girl”
  5.         char *p=a; 
  6.         char ptr=&p; 
  7.         //printf(“p=%d\n”,p); 
  8.         //printf(“ptr=%d\n”,ptr); 
  9.         //printf(“*ptr=%d\n”,*ptr); 
  10.         printf(“ptr=%c\n”,ptr); 
  11.         ptr++; 
  12.         //printf(“ptr=%d\n”,ptr); 
  13.         //printf(“*ptr=%d\n”,*ptr); 
  14.         printf(“ptr=%c\n”,ptr); 
  15.     } 

误区一、输出答案为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 所指向的类型)个字节。指针和指针进行加减:两个指针不能进行加法运算,这是非法操作,因为进行加法后,得到的结果指向一个不知所向的地方,而且毫无意义。两个指针可以进行减法操作,但必须类型相同,一般用在数组方面,不多说了。
 


三、运算符&和*

例六:

  1. int a=12; int b; int *p; int ptr; 
  2. p=&a; //&a 的结果是一个指针,类型是int*,指向的类型是 
  3. //int,指向的地址是a 的地址。 
  4. *p=24; //*p 的结果,在这里它的类型是int,它所占用的地址是 
  5. //p 所指向的地址,显然,*p 就是变量a 
  6. ptr=&p; //&p 的结果是个指针,该指针的类型是p 的类型加个* 
  7. //在这里是int 。该指针所指向的类型是p 的类型,这 
  8. //里是int*。该指针所指向的地址就是指针p 自己的地址。 
  9. *ptr=&b; //*ptr 是个指针,&b 的结果也是个指针,且这两个指针 
  10. //的类型和所指向的类型是一样的,所以用&b 来给*ptr  
  11. //值就是毫无问题的了。 
  12. ptr=34; //*ptr 的结果是ptr 所指向的东西,在这里是一个指针, 
  13. //对这个指针再做一次*运算,结果是一个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)是指针表达式

五、数组和指针的关系

例九:

  1. int array[10]={
    0,1,2,3,4,5,6,7,8,9},value; 
  2. value=array[0]; //也可写成:value=*array; 
  3. value=array[3]; //也可写成:value=*(array+3); 
  4. value=array[4]; //也可写成:value=*(array+4);

例十:

  1. char *str[3]={ 
  2.     “Hello,thisisasample!”
  3.     “Hi,goodmorning.”
  4.     “Helloworld” 
  5. }; 
  6. char s[80] 
  7. strcpy(s,str[0]); //也可写成strcpy(s,*str); 
  8. strcpy(s,str[1]); //也可写成strcpy(s,*(str+1)); 
  9. strcpy(s,str[2]); //也可写成strcpy(s,*(str+2));

例十一:

  1. int array[10]; 
  2. int (*ptr)[10]; 
  3. ptr=&array;

六、指针和结构类型的关系

例十二:

  1.     struct MyStruct 
  2.     { 
  3.         int a; 
  4.         int b; 
  5.         int c; 
  6.     }; 
  7.     struct MyStruct ss={
    20,30,40}; 
  8.     //声明了结构对象ss,并把ss 的成员初始化为2030 40 
  9.     struct MyStruct *ptr=&ss
  10.     //声明了一个指向结构对象ss 的指针。它的类型是 
  11.     //MyStruct *,它指向的类型是MyStruct 
  12.     int *pstr=(int*)&ss; 
  13.     //声明了一个指向结构对象ss 的指针。但是pstr  
  14.     //它被指向的类型ptr 是不同的。 

请问怎样通过指针ptr 来访问ss 的三个成员变量?
答案:
ptr->a; //指向运算符,或者可以这们(*ptr).a,建议使用前者
ptr->b;
 



ptr->c;

 

又请问怎样通过指针pstr 来访问ss 的三个成员变量?
答案:
*pstr; //访问了ss 的成员a。
*(pstr+1); //访问了ss 的成员b。
*(pstr+2) //访问了ss 的成员c。
 




虽然我在我的MSVC++6.0 上调式过上述代码,但是要知道,这样使用pstr 来访问结构成员是不正规的,为了说明为什么不正规,让我们看看怎样通过指针来访问数组的各个单元: (将结构体换成数组)

 

例十三:

  1. int array[3]={
    35,56,37}; 
  2. int *pa=array
  3. //通过指针pa 访问数组array 的三个单元的方法是: 
  4. *pa; //访问了第0 号单元 
  5. *(pa+1); //访问了第1 号单元 
  6. *(pa+2); //访问了第2 号单元

七、指针和函数的关系

例十四:

  1.     int fun(char *); 
  2.     inta; 
  3.     char str[]=“abcdefghijklmn”
  4.     a=fun(str); 
  5.     int fun(char *s) 
  6.     { 
  7.         int num=0
  8.         for(int i=0;;) 
  9.         { 
  10.             num+=*s;s++; 
  11.         } 
  12.         return num; 
  13.     } 

八、指针类型转换

例十五:

  1. float f=12.3
  2. float *fptr=&f; 
  3. int *p;

例十六:

  1.     void fun(char*); 
  2.     int a=125,b; 
  3.     fun((char*)&a); 
  4.     void fun(char*s) 
  5.     { 
  6.         charc; 
  7.         c=*(s+3);*(s+3)=*(s+0);*(s+0)=c; 
  8.         c=*(s+2);*(s+2)=*(s+1);*(s+1)=c; 
  9.     } 

那可不可以把一个整数当作指针的值直接赋给指针呢?就象下面的语句:

  1.     unsigned int a; 
  2.     TYPE *ptr; //TYPE intchar 或结构类型等等类型。 
  3.     a=
  4.     ptr=; //我们的目的是要使指针ptr 指向地址 
  5.       
  6.     ptr=a; //我们的目的是要使指针ptr 指向地址 
  7.     //编译一下吧。结果发现后面两条语句全是错的。那么我们的目的就不能达到了吗?不,还有办法: 
  8.     unsigned int a; 
  9.     TYPE *ptr; //TYPE intchar 或结构类型等等类型。 
  10.     a=N //N 必须代表一个合法的地址; 
  11.     ptr=(TYPE*)a //呵呵,这就可以了。 

例十七:

  1. int a=123,b; 
  2. int *ptr=&a; 
  3. char *str; 
  4. b=(int)ptr; //把指针ptr 的值当作一个整数取出来。 
  5. str=(char*)b; //把这个整数的值当作一个地址赋给指针str

九、指针的安全问题

例十八:

  1. char s=‘a’
  2. int *ptr; 
  3. ptr=(int *)&s; 
  4. *ptr=1298

例十九:

  1. char a; 
  2. int *ptr=&a; 
  3. ptr++; 
  4. *ptr=115;

 

 

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/198743.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月26日 下午2:34
下一篇 2026年3月26日 下午2:34


相关推荐

  • sqlserver数据库同步工具_sql server数据库安装

    sqlserver数据库同步工具_sql server数据库安装 一、确认数据库运行环境是否配置正确打开SQLServerManagementStudio,新建查询: select*fromsys.servers GO //这里可得到原来的计算机名称。然后将其记录下来(复制即可)  看这里的name是否和你的服务器的计算机名称一样,如果一样可以跳到文档(二),否则请按如下操作更改 新建查询:

    2022年10月10日
    8
  • random函数的用法

    random函数的用法用法:1、random.random()随机生成(0,1)之间的浮点数2、random.randint(上限,下限)随机生成在范围之内的整数,两个参数分别表示上限和下限3、random.randrange(,,)在指定范围内,按指定基数递增的集合中获得一个随机数,有三个参数,前两个参数代表范围上限和下限,第三个参数是递增增量,不包括下限,包括上限使用方式如下:random.r…

    2022年6月12日
    58
  • pycharm2021最新激活码(最新序列号破解)

    pycharm2021最新激活码(最新序列号破解),https://javaforall.net/100143.html。详细ieda激活码不妨到全栈程序员必看教程网一起来了解一下吧!

    2022年3月18日
    202
  • matlab中doc是什么意思_求和符号在matlab中怎么表示

    matlab中doc是什么意思_求和符号在matlab中怎么表示苹果OSX系统在界面与使用上相比我们熟悉的Windows系统有很大的区别,很多刚接触苹果电脑的朋友会觉得Mac电脑桌面下的Dock栏很酷,使用也很方便。但大多数用户都不知道Dock栏是什么,该如何用好,今天我们将详细为大家介绍下Dock栏使用技巧。Dock栏是什么?Dock栏是苹果Mac电脑OSX系统桌面下方的那那一排快捷操作键,类似于Windows电脑的任务栏,我们可以将一些经常需要用到的应用放…

    2025年10月30日
    6
  • 实测字节版Manus扣子空间6大实用场景,居然还能这么玩!(附扣子空间保姆级教程)

    实测字节版Manus扣子空间6大实用场景,居然还能这么玩!(附扣子空间保姆级教程)

    2026年3月12日
    2
  • mysql截取字符串并更新_mysql 截取字符串并 update select

    mysql截取字符串并更新_mysql 截取字符串并 update select亲测有效格式为update需要修改的表b1innerjoin(查询到的临时表)b2onb1.id=b2.idsetb1.要修改的字段=b2.查询到的值因为想要把表中的一个字段的一部分取出来,另放一个新的字段里面,所以想到了mysql的字符串截取功能。需要更新的数据:selectparams,substring_index(params,’=’,-1),paramI…

    2022年6月11日
    120

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号