C 指针讲解.docx
- 文档编号:16028568
- 上传时间:2023-07-10
- 格式:DOCX
- 页数:24
- 大小:313.31KB
C 指针讲解.docx
《C 指针讲解.docx》由会员分享,可在线阅读,更多相关《C 指针讲解.docx(24页珍藏版)》请在冰点文库上搜索。
C指针讲解
一维指针指针数组二维指针 二维数组
char*a,char**a,char*a[],chara[][], char*a[][][],andsooninmemory
X86-64bit架构的服务器
CentOSx645.x
gccversion4.1.220080704
指针和数组是C的比较难搞懂的知识点,需要结合内存来学习,非常感谢各位兄弟为我指点迷津.
下面总结一下:
首先说明一下C程序在运行时,不同的内容或变量分别存储在什么地方?
分了几块区域分别是,code,constants,global,heap,stack;(内存地址从低到高)
其中constants存储常量(常量值不允许修改),global存储在所有函数以外定义的全局变量(全局变量允许修改),heap是一块动态内存区域(可存放持久化内容,不会自动释放内存),stack存放函数内的本地变量(函数执行完后本地变量占用的内存将自动释放);
如图:
接下来要介绍两个符号*和&.
1.*用在定义变量类型,或者强制类型转换时,表示要定义一个指针或者把这个变量类型转换成指针(并告知这个指针指向的是什么类型的数据).
2.*用在一个指针变量的前面,表示这个指针指向地址存储的内容(内容是什么类型的就取决于这个指针定义时:
告知这个指针指向的是什么类型的数据,指针的加减运算得到的内存地址也和指针指向的是什么类型的数据相关,int*a,a+1得到的地址则是在a指向的地址基础上加4字节.).
3.&用在变量名的前面,表示这个变量所在的地址.
例1:
[root@db-172-16-3-33zzz]#catb.c
#include
intmain(){
char*a="hello";
fprintf(stdout,"&a:
%p,a:
%p,a:
%s\n",&a,a,a);
return0;
}
结果:
[root@db-172-16-3-33zzz]#gcc-O3-Wall-Wextra-Werror-g./b.c-ob&&./b
&a:
0x7fff71b22230,a:
0x400618,a:
hello
&a表示a的地址.
a表示a存储的内容,因为a是指针,所以它的内容读取出来就是一个地址.
*a表示a指针存储的这个地址存储的内容.
在内存中的图示:
这个图包含了几块内容:
1.在64位的系统中,指针占用了8个字节.因为指针中存储的是地址,而且地址是64位的.所以需要8个字节.
2.在x86架构的机器中,内存填充是从低位到高位的.所以hello在内存中是这样存储的:
地址:
内容
0x400618:
0x68 (ascii:
h)
0x400619:
0x65 (ascii:
e)
0x40061a:
0x6c (ascii:
l)
0x40061b:
0x6c (ascii:
l)
0x40061c:
0x6f (ascii:
o)
0x40061d:
0x68 (ascii:
NULL)
3.那怎么来证明以上是正确的呢?
很简单,按照每个字节来打印就知道了.
这里需要注意的是指针的加减法得到的地址和指针指向的地址存储的内容的类型是有关的,反过来说,要让指针加1刚好得到的是下一个字节那就告诉编译器,这个指针指向的地址的内容是char类型就好了,因为sizeof(char)=1字节.
先来打印一下hello是不是按照上面说的这样存储的?
[root@db-172-16-3-33zzz]#catb.c
#include
intmain(){
char*a="hello";
fprintf(stdout,"&a:
%p,a:
%p,a:
%s\n",&a,a,a);
fprintf(stdout,"sizeof(char*):
%lu,sizeof(char):
%lu\n",sizeof(char*),sizeof(char));
fprintf(stdout,"a+0:
%p,*(a+0):
%x,*(a+0):
%c\n",a+0,*(a+0),*(a+0));
fprintf(stdout,"a+1:
%p,*(a+1):
%x,*(a+1):
%c\n",a+1,*(a+1),*(a+1));
fprintf(stdout,"a+2:
%p,*(a+2):
%x,*(a+2):
%c\n",a+2,*(a+2),*(a+2));
fprintf(stdout,"a+3:
%p,*(a+3):
%x,*(a+3):
%c\n",a+3,*(a+3),*(a+3));
fprintf(stdout,"a+4:
%p,*(a+4):
%x,*(a+4):
%c\n",a+4,*(a+4),*(a+4));
fprintf(stdout,"a+5:
%p,*(a+5):
%x,*(a+5):
%c\n",a+5,*(a+5),*(a+5));
return0;
}
结果:
[root@db-172-16-3-33zzz]#gcc-O3-Wall-Wextra-Werror-g./b.c-ob&&./b
&a:
0x7ffffe249680,a:
0x4006f8,a:
hello
sizeof(char*):
8,sizeof(char):
1
a+0:
0x4006f8,*(a+0):
68,*(a+0):
h
a+1:
0x4006f9,*(a+1):
65,*(a+1):
e
a+2:
0x4006fa,*(a+2):
6c,*(a+2):
l
a+3:
0x4006fb,*(a+3):
6c,*(a+3):
l
a+4:
0x4006fc,*(a+4):
6f,*(a+4):
o
a+5:
0x4006fd,*(a+5):
0,*(a+5):
解说:
a是一个指针,这个指针指向的内容是char类型的,所以这个指针的加减运算a+1表示地址加1字节.
*a把你带到它指向的0x4006f8,而fprintf以16进制和char输出的正是0x4006f8这个地址的这个字节上的内容(1字节刚好用两个16进制数表示,刚好可以转成char)
a+0这里只是为了说明指针的加减运算,可以去掉+0.
sizeof(char*):
8表示指针占用了8字节,sizeof(char):
1表示char占用了1字节.
4.接下来打印&a这个地址是不是也是按照从低到高存储的?
为了一个字节一个字节打出, 我们需要再定义一个指针b,用指针b来做加减运算,但是这样能如愿吗?
[root@db-172-16-3-33zzz]#catb.c
#include
intmain(){
char*a="hello";
char**b=&a;
fprintf(stdout,"sizeof(a):
%lu,b:
%p,&a:
%p,b+1:
%p\n",sizeof(a),b,&a,b+1);
return0;
}
结果:
[root@db-172-16-3-33zzz]#gcc-O3-Wall-Wextra-Werror-g./b.c-ob&&./b
sizeof(a):
8,b:
0x7fff3692b520,&a:
0x7fff3692b520,b+1:
0x7fff3692b528
解说:
sizeof(a)=8 结果告诉我们,a这个变量占用了8字节,因为它存储的是个内存地址,在64位的操作系统中这个是可以理解的.
b这个指针指向a,所以b和&a打印出来的值转成内存地址当然是一样的.
b+1运算得到的地址当然应该是加8个字节.因为地址做加减运算得到的当然还是地址,至于得到的结果和什么有关,当然和这个做加减运算的指针定义时所告知的它指向什么类型的数据有关.char**b,(*b表示b是一个指针,然后char*则是告诉你b指向的是一个指针,甭管是什么类型的指针,反正b指向的是一个指针,一个指针占用8字节,所以b+1就是加8个字节)
那怎么样能让b+1结果是加1个字节呢?
这里要用到强制类型转换.(char*)b就把b强制转成char*了. 所以((char*)b)+1就是加1字节.*(((char*)b)+1)就是取这个地址的内容.
%x表示取一个字节并按照16进制打印出来.
[root@db-172-16-3-33zzz]#catb.c
#include
intmain(){
char*a="hello";
char**b=&a;
unsignedshorti;
unsignedshortx=(unsignedshort)sizeof(a);
fprintf(stdout,"&a:
%p,a:
%p\n",&a,a);
for(i=0;i fprintf(stdout,"i: %u,((char*)b)+i: %p,*(((char*)b)+i): %x\n",i,((char*)b)+i,*(((char*)b)+i)); } return0; } 结果: [root@db-172-16-3-33zzz]#gcc-O3-Wall-Wextra-Werror-g./b.c-ob&&./b &a: 0x7fff1ffff608,a: 0x400728 i: 0,((char*)b)+i: 0x7fff1ffff608,*(((char*)b)+i): 28 i: 1,((char*)b)+i: 0x7fff1ffff609,*(((char*)b)+i): 7 i: 2,((char*)b)+i: 0x7fff1ffff60a,*(((char*)b)+i): 40 i: 3,((char*)b)+i: 0x7fff1ffff60b,*(((char*)b)+i): 0 i: 4,((char*)b)+i: 0x7fff1ffff60c,*(((char*)b)+i): 0 i: 5,((char*)b)+i: 0x7fff1ffff60d,*(((char*)b)+i): 0 i: 6,((char*)b)+i: 0x7fff1ffff60e,*(((char*)b)+i): 0 i: 7,((char*)b)+i: 0x7fff1ffff60f,*(((char*)b)+i): 0 解说: a变量存放在内存地址0x7fff1ffff608这里的连续8个字节中. a变量的8个字节中存储了什么内容呢? 0x400728 它是从低位到高位存储的,如上面的结果,28存在0x7fff1ffff608,07存在0x7fff1ffff609,40存在0x7fff1ffff60a,另外5个字节存的都是0x00. 5.如果是多字节的数据又是怎么存储的呢? 比如int占据了4个字节,它是怎么存储的? 答案也是从低位到高位. [root@db-172-16-3-33zzz]#catc.c #include intmain(){ inta=-987654; int*b=&a; unsignedshorti; unsignedshortx=(unsignedshort)sizeof(a); fprintf(stdout,"&a: %p,a: %i,a: %x\n",&a,a,a); for(i=0;i fprintf(stdout,"i: %u,((char*)b)+i: %p,(unsignedchar)(*(((char*)b)+i)): %x\n",i,((char*)b)+i,(unsignedchar)(*(((char*)b)+i))); } return0; } 结果: [root@db-172-16-3-33zzz]#gcc-O3-Wall-Wextra-Werror-g./c.c-oc&&./c &a: 0x7fffe57051cc,a: -987654,a: fff0edfa //负数是正数的反码加1,所以得到的是这个结果. i: 0,((char*)b)+i: 0x7fffe57051cc,(unsignedchar)(*(((char*)b)+i)): fa i: 1,((char*)b)+i: 0x7fffe57051cd,(unsignedchar)(*(((char*)b)+i)): ed i: 2,((char*)b)+i: 0x7fffe57051ce,(unsignedchar)(*(((char*)b)+i)): f0 i: 3,((char*)b)+i: 0x7fffe57051cf,(unsignedchar)(*(((char*)b)+i)): ff 注意,这里一定要把内容再强制转换成unsignedchar再输出,就是这个(unsignedchar)(*(((char*)b)+i)): %x. 否则编译器会输出4字节的16进制数,不太好看.如下: #include intmain(){ inta=-987654; int*b=&a; unsignedshorti; unsignedshortx=(unsignedshort)sizeof(a); fprintf(stdout,"&a: %p,a: %i,a: %x\n",&a,a,a); for(i=0;i fprintf(stdout,"i: %u,((char*)b)+i: %p,(*(((char*)b)+i)): %x\n",i,((char*)b)+i,(*(((char*)b)+i))); } return0; } 结果: [root@db-172-16-3-33zzz]#gcc-O3-Wall-Wextra-Werror-g./c.c-oc&&./c &a: 0x7ffff6fada3c,a: -987654,a: fff0edfa i: 0,((char*)b)+i: 0x7ffff6fada3c,(*(((char*)b)+i)): fffffffa i: 1,((char*)b)+i: 0x7ffff6fada3d,(*(((char*)b)+i)): ffffffed i: 2,((char*)b)+i: 0x7ffff6fada3e,(*(((char*)b)+i)): fffffff0 i: 3,((char*)b)+i: 0x7ffff6fada3f,(*(((char*)b)+i)): ffffffff 6.那么bit-field又是怎么存储的呢? 答案当然也是从低位到高位存储的,这里要用到struct来验证. [root@db-172-16-3-33zzz]#catc.c #include intmain(){ typedefstructtest{ unsignedcharf1: 1; unsignedcharf2: 2; unsignedcharf3: 3; unsignedcharf4: 2; }test; testt1={0,1,4,3}; test*pt1=&t1; fprintf(stdout,"(unsignedchar)(*((unsignedchar*)pt1)): %x\n",(unsignedchar)(*((unsignedchar*)pt1))); return0; } 结果: [root@db-172-16-3-33zzz]#gcc-O3-Wall-Wextra-Werror-g./c.c-oc&&./c (unsignedchar)(*((unsignedchar*)pt1)): e2 解说: testt1={0,1,4,3}; 转成二进制分别如下: f1(0): 0 f2 (1): 01 f3(4): 100 f4(3): 11 如果是按低位到高位存储的,那么它存储的应该是: 11100010.这个与0xe2刚好相符. 这里同样用到了强制类型转换, (unsignedchar*)pt1是把指向structtest的指针转成了指向unsignedchar的指针. (unsignedchar)(*((unsignedchar*)pt1))是把 *((unsignedchar*)pt1)转成了unsignedchar类型. 如果不使用指针,直接把struct变量转成unsigned是不能编译通过的.错误如下: [root@db-172-16-3-33zzz]#catc.c #include intmain(){ typedefstructtest{ unsignedcharf1: 1; unsignedcharf2: 2; unsignedcharf3: 3; unsignedcharf4: 2; }test; testt1={0,1,4,3}; unsignedchara=(unsignedchar)t1; fprintf(stdout,"a: %x\n",a); return0; } 结果: [root@db-172-16-3-33zzz]#gcc-O3-Wall-Wextra-Werror-g./c.c-oc&&./c ./c.c: Infunction‘main’: ./c.c: 11: error: aggregatevalueusedwhereanintegerwasexpected 所以指针在C里面运用真的太灵活了. 进入正题,讲讲char**a,char*a[],chara[][],char*a[][],char**a[][],char*a[][][] 看起来很复杂,其实理解了就不复杂了. 1. char**a: 表示a是一个指针,这个指针指向的地址存储的是char*类型的数据. 指针的加减运算在这里的体现: a+1表示地址加8字节. char*也是一个指针, 用(*a)表示, 指向的地址存储的是char类型的数据。 指针的加减运算在这里的体现: (*a)+1表示地址加1字节. [root@db-172-16-3-33zzz]#catb.c #include intmain(){ char*a="hello"; char**b=&a; fprintf(stdout,"&b: %p,b: %p,&a: %p,a: %p,*a: %c,a: %s\n",&b,b,&a,a,*a,a); return0; } 结果: [root@db-172-16-3-33zzz]#gcc-O3-Wall-Wextra-Werror-g./b.c-ob&&./b &b: 0x7fff5319c1d8,b: 0x7fff5319c1e0,&a: 0x7fff5319c1e0,a: 0x400628,*a: h,a: hello 图示: 2. char*a[] 表示a是数组,数组中的元素是指针,指向char类型.(数组里面所有的元素是连续的内存存放的). 需要特别注意: 数组名在C里面做了特殊处理,数组名用数组所占用内存区域的第一个字节的内存地址替代了。 并且数组名a也表示指针. 如数组占用的内存区域是0x7fff5da3f550到0x7fff5da3f5a0,那么a就被替换成0x7fff5da3f550. 所以a并不表示a地址存储的内容,而是a地址本身(这个从a=&a就能够体现出来).这个一定要理解,否则会无法进行下去. a+1表示a的第二个元素的内存地址,所以是加8字节.(因为a的元素是char指针,所需要的空间为8字节(64位内存地址).) *(a+1)则表示a这个数组的第二个元素的内容(是个char类型的指针.本例表示为world字符串的地址). *(*(a+1))则表示a这个数组的第二个元素的内容(char指针)所指向的内容(w字符). char*a[10]表示限定这个数组最多可存放10个元素(char指针),也就是说这个数组占用10*8=80字节. 如果存储超出数组的限额编译警告如下: [root@db-172-16-3-33zzz]#catb.c #include intmain(){ char*a[1]={"abc","def"}; fprintf(stdout,"a[1]: %s\n",a[1]); return0; } 结果: [root@db-172-16-3-33zzz]#gcc-O3-Wall-Wextra-Werror-g./b.c-ob&&./b cc1: warningsbeingtreatedaserrors ./b.c: Infunction‘main’: ./b.c: 4: warning: excesselementsinarrayinitializer //超出数组长度.因为赋值时给了2个元素,而限定只有1个元素. ./b.c: 4: warning: (nearinitializationfor‘a’) 例子: [root@db-172-16-3-33zzz]#catb.c #
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 指针讲解 指针 讲解