前言
代码分析是由我自己根据代码和结果分析所得,可以将代码直接复制运行,按你自己的理解。
目录
1.1一维数组
&是取地址符,用于取某个变量的地址,*是解引用,用于取出某一段地址的内容
代码如下
#include<stdio.h>
int main()
{
int b[10] = { 1,12,3,4,5,6,7,8,9,10 };
printf("b[0]的地址=%p\t", &b[0]);
printf("b[1]的地址=%p\t", &b[1]);
printf("b=%p\n", b);
printf("*b=%p\n", *b);
//printf("**b=%d\n", **b);//会报错:"*" 的操作数必须是指针,但它具有类型 "int"
printf("&b=%p\n", &b);
printf("*&b=%p\n", *&b);//整个数组的地址,解引用变为首元素的地址,+1,即第一行的第二个数的地址
printf("**&b=%p\n", **&b);
//printf("***&b=%p\n", ***&b);//会报错:"*" 的操作数必须是指针,但它具有类型 "int"
printf("b+1=%p\n", b + 1);
printf("*(b+1)=%p\n", *(b + 1));
printf("*b+1=%d\n", *b + 1);//数组首元素地址,*b即第一个元素
return 0;
}
运行结果
代码解释:
- 一维数组中,b表示数组首元素的地址,即b[0]的地址,而&b表示整个数组的地址,也用数组首元素地址来表示,也是b[0]的地址,所以b和&b的值是一样的,但意义不相同,*&b也表示首元素地址,即b,**&b第一个元素的那个整数值,这和*b是一样的,都是第一个元素的那个整数值,*和&相互抵消。
- *(b+1),b是数组首元素地址,(b+1),则是b[1]的地址,等价于&b[1],*(b+1)是对地址解引用,即b[1],注意%p是以16进制输出,c表示十进制里的12
- *b,对数组首元素地址解引用,取出地址内容,即b[0],*b+1,则是b[0]+1,输出2
1.2最普通的指针
代码如下
int x = 3;
int y = 4;
int* px;
int* py;
px = &x;
py = &y;
//上面两个等价于int *px=&x; int* py=&y;
指针变量的定义:数据类型*指针名,如int*px;表示指针其指向的类型是int(将变量旁边的*一起去掉则是指针指向的类型,指针自己的类型将变量名去除,这里是int*)
- &是取地址符,用于取某个变量的地址,*是解引用,用于取出某一段地址的内容
- px代表他命名了一个叫px的变量
- *代表他是一个一级指针
- int 代表这个指针指向的是一个int类型的变量
- 指针的使用:一开始就进行初始化:int*px=&x,或先声明再赋值:int *px; px=&x;
- px表示他自己的内容,这个内容需要是一个地址或指向空(int*px=NULL),要不然会成为野指针,x表示一段内存名字,通过&来获取这段内存的地址,即&x。
- &用于取出一段内存的地址,用&变量名表示,如&px代表:取出px命名的那段内存的地址,即px的地址(指针也有他自己的地址,px存放的是他指向内容的地址)。
- *取出内容,如:当px这个指针指向了x后(int*px=&x),px存放的就是x的地址,*px表示取x地址的内容,即3,同样的*(&x),也是3 ,当然*&x不用加()反正都会抵消。
1.3数组指针
代码如下:
#include<stdio.h>
int main()
{
int b[10] = { 1,12,3,4,5,6,7,8,9,10 };
int(*pb)[10] = &b;//规范写法,取整个数组
//int(*pb)[10] = b;//和上面得出结果是一样的
//int* pb = &b;//所有1处,"*" 的操作数必须是指针,但它具有类型 "int",无论是b还是&b
printf("&pb的地址%p\t", &pb);
printf("b[0]的地址%p\t", &b[0]);
printf("b[1]的地址%p\n", &b[1]);
printf("pb的大小=%d\t", sizeof(pb));
printf("*pb的大小=%d\t", sizeof(*pb));
printf("pb+1的大小=%d\t", sizeof(pb + 1));//pb跳的字节数取决于int(*pb)[10] = &b,里面的10或其他数
printf("*(pb+1)的大小=%d\n", sizeof(*(pb + 1)));
printf("**(pb+1)的大小=%d\t", sizeof(**(pb + 1)));
printf("*pb+1的大小=%d\n", sizeof(*pb + 1));
printf("b[0]的大小=%d\t", sizeof(b[0]));
printf("&b的大小=%d\t", sizeof(&b));
printf("&b[0]的大小=%d\t", sizeof(&b[0]));
printf("b的大小=%d\n", sizeof(b));
printf("pb=%p\t", pb);//指针的内容,整个数组地址
printf("*pb=%p\t", *pb);//整个数组地址的内容,首元素的地址
printf("**pb=%p\t", **pb);//
printf("**pb=%d ", **pb);//首元素地址的内容,首元素的整数值----1
printf("pb+1=%p\n", pb + 1);//整个数组的地址+1,类似于二维数组,地址是第二行的地址,跳跃40个字节
printf("&pb+1=%p\n", &pb + 1);//自己的地址跳跃,8个字节
printf("*(pb+1)=%p\n", *(pb + 1));//第二行地址,再解引用,是首元素地址
printf("**(pb + 1)其值?=%d\t", **(pb + 1));//首元素的整数值-------1
printf("**(pb + 1)地址?=%p\n", **(pb + 1));//首元素的整数值------1
//printf("***(pb + 1)=%d\t", ***(pb + 1));//元素再进行解引用,无法运行
printf("*pb+1=%p\t", *pb + 1);//整个数组地址解引用,首元素地址,加1,第二个元素地址
printf("*(*pb+1)=%d\n", *(*pb + 1));//-------1
return 0;
}
运行结果:
你注意到*(pb+1)为什么是40个字节,而pb+1是8个字节吗?等会告诉你。
结果分析
- 数组指针的表示:int(*pb)[10],[]的优先级高于*,加上()pb先于*结合表明他是一个指针,再于[10]结合表示指向10个元素,最后与int结合,也就代表他是指向装有10个整型元素的数组指针,是一个指针,指针4个或8个字节(看所在环境)。int(*pb)[10]=b,可以用,但int(*pb)[10]=&b是更为规范的写法,前者表示指向首元素地址,而后者表示指向整个数组。数组名(b)和&数组名(&b)尽管他们值一样,但在一些地方产生结果不同。
- 代码里的字节大小:所有代码都在64位下编译运行的,所以pb指针所占的字节数是8个字节,(若在32位下,则是4个字节)数组有10个元素且为整型,该环境下int型占4个字节,则b数组的大小是40个字节.
- 由图对b的大小进行计算结果也可以看出,b,&b,&b[0]的值是一样的,但是数组名是首元素地址,&数组名代表表整个数组,这里有个特别注意的点:sizeof(数组名),这里的数组名不再是首元素地址,而是表示整个数组,所以表示的是整个数组所占字节的大小,计算得40,且这个必须为数组名,不可以添加任何东西,这是一个特例,而sizeof(&数组名)是地址的大小,和指针一样8个字节由下可以验证:
代码如下
#include<stdio.h>
int main()
{
int a[] = { 1,2,3,4 };
printf("a=%d\n", sizeof(a));//16,数组整大小
printf("a + 0=%d\n", sizeof(a + 0));// 4/8,首元素地址
printf("*a=%d\n", sizeof(*a));// 4/8,a是首元素地址,*a首元素的字节大小
printf("a + 1=%d\n", sizeof(a + 1));// 4/8
printf("a[1]=%d\n", sizeof(a[1]));// 4/8 第二个元素大小
printf("&a=%d\n", sizeof(&a));// 4/8,地址的大小就是四个或八个字节
printf("*&a=%d\n", sizeof(*&a));// 16,&a是数组的地址,数组的地址解引用,即数组的大小,和a效果一样
printf("&a + 1=%d\n", sizeof(&a + 1));// 4,数组地址的大小即4/8
printf("&a[0]=%d\n", sizeof(&a[0]));// 4/8,第一个元素地址
printf("&a[0] + 1=%d\n", sizeof(&a[0] + 1)); //第二个元素地址
return 0;
}
结果如下
由结果图对pb的一系列解释:
- pb:其指向的应该是整个数组,以数组首元素地址的值表示,所以pb等价于&b,sizeof(pb)是个地址,8个字节(sizeof(&b))
- *pb:对于pb,加*是找这个指针的指向,因为这个指针应该有10个指向,加上*则是首元素地址(*pb),等价于b,所以sizeof(*pb),40个字节,再如同下方首元素地址+1,是第二个元素的地址。注意:*(pb+1)与其不同
- **pb:对首元素地址再解引用为数组元素值
- *pb+1:在首元素地址处再+1,为第二个元素地址,即b[1]的地址(图中有)
- *(*pb+1):由*pb可推得是b[1]
- 对于二维数组,可以将每一行当作整体看作一个元素,使二维变一维,若取b[0][0],我们需要先找出你要去那一行,以*pb为例,*pb表示找第一行,再要找第一行的地址,则用一维的方式找元素就行,如,将(*pb)看作一维数组,再解引用*(*pb),得出的就是1,后面会用一个二维数组继续进行验证
- pb+1:这里要将b看作二维数组,第一行是b这个数组里的10个数,第二行未初始化,pb没有解引用又进行加1,而pb指向含10个元素的数组,则要跳一个b数组的大小,即跳到数组的下一行,由上面代码结果得出,b[0]的地址与(pb+1)的地址刚好差40(在16进制中640-618==40),即10个int大小
- *(pb+1):如上述,在第二行解引用,是第二行的首元素地址,相当于b,所以sizeof(*(pb+1))大小是整个数组的大小,40字节,注意:内存是一维的而不是二维的,所以跳跃10个元素后,pb的地址与b[0]的地址刚好差40,因为刚好跳到了b[10]的下一个位置。(本文为了方便,而看作二维)
- **(pb+1):则是首元素的值,而b数组只定义了10个元素,没有对这个位置进行初始化,所以这个位置的值是一个垃圾值
- 由上文*(pb+1)是第二行的首元素地址,相当于sizeof(b[1])(要将b数组看作二维,b[1]是第二行的数组名,后面有代码与结果对二维的进行验证),表示那一行的大小,所以是40个字节,而pb+1是这个b的下一行整个数组的位置,相当于sizeof(&b[1]),地址是8个字节。
- 由此也可得int(*pb)[n],这里的n代表了,他要跨越几个字节,下面进行验证
代码如下
#include<stdio.h>
int main()
{
int b[10] = { 21,12,23,24,25,26,27,28,29,10 };
int(*pb)[3] = &b;
printf("&pb的地址%p\t", &pb);
printf("b[0]的地址%p\t", &b[0]);
printf("b[1]的地址%p\n", &b[1]);
printf("b[2]的地址%p\n", &b[2]);
printf("b[3]的地址%p\n", &b[3]);
printf("pb大小=%d\t",sizeof(pb));
printf("*pb大小=%d\n",sizeof(*pb));
printf("pb+1=%p\n", pb + 1);
printf("&pb+1=%p\n", &pb + 1);
printf("*(pb+1)=%p\n", *(pb + 1));
printf("**(pb + 1)=%d\n", **(pb + 1));
printf(" *(*(pb + 1)+1)=%d\n", *(*(pb + 1) + 1));
printf("*(*(pb + 1)+2)=%d\n", *(*(pb + 1) + 2));
printf("*pb+1=%p\t", *pb + 1);
printf("*(*pb+1)=%d\n", *(*pb + 1));
return 0;
}
代码结果
代码解释:
- **(pb+1):pb+1,因为int(*pb)[3] = &b,所以一次跳3个元素,将这个过程,看作对b数组的分割,pb+1存放地址以b[3]的地址作整个地址(pb+1代表指针的内容,即指向的地址),*(pb+1),取出首地址,即b[3]的地址,**(pb+1)为b[3]
- &pb+1是指针自己的地址去跳跃,跳8个字节
- 其他的结果也是一样的去推,不再做解释
-
总结:pb+i,i由指针决定,int(*pb)[n],n为多少就跳跃多少位置
1.4指针转为数组的写法
一维数组与指针
代码如下:
#include<stdio.h>
int main()
{
printf("一维数组与指针的转换\n");
int a[10] = { 5,9,7,8,1,23,12,4,45,12};
int* p= &a;
int(* pa)[1] = &a;
printf("指针写法\n\n");
for (int i = 0; i < 10; i++)
{
//两种方法均可
printf("p[%d]=%d ", i, p[i]);
printf("第%d个元素为=%d\n\n", i, *(p+i));
}
printf("数组指针的写法\n\n");
for (int i = 0; i < 10; i++)
{
printf("pa[%d]=%-4d ", i, *pa[i]);
printf("pa[%d]=%-4d ", i, (*pa)[i]);
printf("第%d个元素为=%d ", i, *(*pa+i));
printf("第%d个元素为=%d\n", i, **pa+i);//错误写法
}
return 0;
}
运行结果:
结果分析:
- 在普通指针中: *(p+i)等价于p[i],等于a[i]
- 在数组指针中:由于要进行一次解引用,所以 *pa[i]才是a[i]
- **pa+i是每次循环将b[0]进行+i,没有产生地址的跳跃
二维数组与指针
二维数组要先找到哪一行,即要* 。(前面说的很清楚,可以自己将代码复制,进行验证)
代码如下
#include<stdio.h>
int main()
{
int b[3][5] = { {3,2,5,117,9},
{12,16,11,22,112},
{66,23,56,21,18} };
int(*pb)[5] = &b;
//int* pb = &b;//错误写法
printf("b[0][0]=%p ", &b[0][0]);
printf("b[0][1]=%p ", &b[0][1]);
printf("b[1][0]=%p\n", &b[1][0]);
//printf("pb[0]=%p ", pb[0]);
//printf("pb=%p ", pb);
//printf("*pb=%p ", *pb);//
//printf("**pb=%d\n", **pb);
printf("pb[0][3]=%d ", *(pb[0] + 3));
printf("pb[2][3]=%d ", *(*(pb+2)+3));//注意下标
printf("\n\n");
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 5; j++)
{
printf("p[%d][%d]=%d\t",i,j,pb[i][j]);
//printf("pb[%d][%d] = %p\t", i, j, *(pb + i) + j);//错误写法,输出为各个b的地址
//正确写法
//printf("pb[%d][%d] = %d\t", i,j,*(pb[i]+j));
//printf("pb[%d][%d] = %d\t", i,j,*((*pb+i)+j));
}
printf("\n");
}
return 0;
}
结果如下
不过,你甚至可以这么写int(*pb)[3][5] = &b;,然后用***pb取出3和其他数,若你想让你的代码看起来更加复杂,大可一试。
二维数组的大小
代码如下
#include<stdio.h>
int main()
{
int a[3][4] = { 0 };
printf("%d\n", sizeof(a)); // 48
printf("%d\n", sizeof(a[0][0]));// 4
printf("%d\n", sizeof(a[0]));// 16 a[0]相当于第一行的数组名,数组名首元素地址,计算第一行的大小
printf("%d\n", sizeof(a[0] + 1));//4 没有&,即首元素地址加1,第一行第二个的地址
printf("%d\n", sizeof(*(a[0] + 1)));//4 是第一行第二个元素大小
printf("%d\n", sizeof(a + 1));//4 a是二维数组的数组名,没有sizeof(数组名),也没有&(数组名),所以是首元素地址
//即a是第一行地址,则本题是第二行地址
printf("%d\n", sizeof(*(a + 1)));// 16 即第二行的大小
printf("%d\n", sizeof(&a[0] + 1));//4 第二行地址 第一行地址加1
printf("%d\n", sizeof(*(&a[0] + 1)));//16 第二行大小
printf("%d\n", sizeof(*a));//a是首元素地址,即本题计算第一行大小
printf("%d\n", sizeof(a[3]));//16 不会去访问数组内的大小
return 0;
}
代码结果
- 注意:只有sizeof(数组名)才是整个数组的大小,如:sizeof(a)是3*4*4,48个字节,而sizeof(a[3]),虽然那个位置没有被初始化,但能够访问,代表取那一行整个数组的地址,4*4,16个字节(这时可以将a[3]单纯的看作一个一维数组名)
1.5指针数组
代码如下
#include<stdio.h>
int main()
{
int a1[5] = { 112,31,432,21,13 };
int a2[5] = { 21,13,1231,23,21 };
int a3[5] = { 123,42,345,7,1 };
//int* p[3] = { a1,a2,a3 };//定义了一个名称为p的指针数组,该数组有三个元素,
int* p[3] = { &a1,a2,a3 };//定义了一个名称为p的指针数组,该数组有三个元素,
//每个元素的类型为int*,即每个元素都是基本类型为int的指针
printf("a1地址%p\t", a1);
printf("a2地址%p\n", a2);
printf("\n\n");
printf("*p=%p\t", *p);
printf("**p=%d\n", **p);
printf("*p+1=%p\t", *p + 1);
printf("*(*p + 1)=%d\n", *(*p + 1));//a1的第二个元素
printf("*p + 1字节=%d\n", sizeof(*p + 1));//第一行地址,即首元素地址+1,即第二个元素地址,为8个字节
printf("p大小是%d\n",sizeof(p));
printf("p[0]大小是%d\n",sizeof(p[0]));
printf("&p大小是%d\n",sizeof(&p));
printf("*p大小是%d\n",sizeof(*p));
printf("*(p+1)=%p\t", *(p + 1));
printf("p+1=%p\n", p + 1);
printf("\n\n");
printf("*p[0]=%d\t\t", *p[0]);
printf("*p[1]=%d\t", *p[1]);
printf("*p[0]+1=%d\n", *p[0] + 1);
printf("p[0]=%p\t", p[0]);
printf("p[1]=%p ", p[1]);
printf("\n\n");
for (int i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 5; j++)
{
//都是对的写法
//printf("%-4d ", *(p[i] + j));
//printf("%-4d ", p[i][j]);
printf("%-4d ", *(*(p+i)+j));//注意与数组指针的差别:printf("pb[%d][%d] = %d\t", i,j,*((*pb+i)+j));
}printf("\n");
}
return 0;
}
运行结果
结果分析:
- int* p[3] = { a1,a2,a3 };,[]的优先级高于*,则该语句表示一个数组,数组有3个元素,数组元素类型是int*,所以int* p[3] = { &a1,a2,a3 };也是一样的。
- *(*(p+i)+j):由于这是一个数组,所以p是p这个数组的首元素地址,p+i则是这个数组的第i个元素的地址,*(p+i)是第i个元素的值,是指针数组存放的第i个数组的地址,用这个数组的首元素地址来表示,*(p+i)+j,则是在某个数组的首地址的基础下加j,是这个数组的第j个元素的地址,**(*(p+i)+j)对地址解引用,就是这个数组的第j个元素的值
1.6二维数组与指针
二维数组与指针的转换
代码如下
#include<stdio.h>
int main()
{
int b[3][5] = { {3,2,5,117,9},
{12,16,11,22,112},
{66,23,56,21,18} };
printf("b[0][0]=%p ", &b[0][0]);
printf("b[0][1]=%p ", &b[0][1]);
printf("b[1][0]=%p\n", &b[1][0]);
printf("b[1][1]=%p ", &b[1][1]);
printf("b=%p\t",b);
printf("&b=%p\n",&b);
printf("*b=%p\t", *b);
printf("**b=%d\n", **b);
printf("&b[0]=%p\t",&b[0]);
printf("b[0]=%p\t",b[0]);
printf("*b[0]=%d\n\n",*b[0]);
printf("&b[1]=%p\t", &b[1]);
printf("b[1]=%p\n", b[1]);
printf("b+1=%p\t", b + 1);
printf("&b+1=%p\n", &b + 1);//地址与b[0]相差60个字节,即整个数组+1
//printf("&(b+1)=%p\t", &(b + 1));//错误&必须作用于一个左值,而(b+1)是一个表达式,不是一个合法的左值
printf(" *(b + 1)=%p\t", *(b + 1));
printf(" **(b + 1)=%d\t", **(b + 1));
printf("*(b+1)+1=%p\t", *(b + 1) + 1);
printf("*(*(b + 1) + 1)=%d\n",*(*(b + 1) + 1));
printf("*b+1=%p\t", *b + 1);
printf("*((*b + 1)+2)=%d\t", *((*b + 1)+2));
return 0;
}
代码结果
结果分析
- 在二维数组中,第一行就类似于一维数组的第一个元素,b表示首元素地址,*b表示默认取第一行的地址,&b是整个数组的地址,**b表示首元素地址的内容b[0][0]
- b[0]相当于*b,取第一行地址,*b[0]相当于**b
- b+1,在第一行位置上再跳一行,是第二行的地址,*(b+1)默认取第二行的首地址,**(b+1)就是12
- *b+1,取第一行地址,即b[0][0]的地址,再+2是b[0][2]的地址,再用*,就是117
- *(b+1)+1,第二行首地址+1,是b[1][1]的地址,则*(*(b+1)+1)是b[1][1],值为16
- 第一行地址+1,则是第二行的地址,(*&b+1),即第二行地址,*(*&b+1)第二行首元素地址,**(*&b+1)第二行第一个元素的那个整数值。
- *&b功能抵消,其实就是b
总结:
- 在数组中&数组名,如&b,就是整个数组的地址以首元素地址表示,数组名就是首元素地址
- 一维数组中,&b表示整个数组的地址,*&b表示首元素地址,**&b第一个元素的那个整数值,等价于*b
- 二维数组中,&b表示整个数组的地址,也是第一行的地址,也是第一行第一个元素的地址,&b[1]是整个第二行的地址,b[1]+1是第二行第二个元素的地址,若*&b是默认找第一行地址,**&b是首元素地址,***&b才是第一行第一个元素的那个整数值
- 二维数组里我们首先需要明确自己要哪一行所以首先要b+i,找到第i行的地址,用第i行首元素地址来表示,*(b+i)得到第i行首元素地址,*(b+i)+j得到第i行第j个元素的地址,*(*(b+i)+j)得到第i行第j个元素。