本博客主要内容为 “小甲鱼” 视频课程《带你学C带你飞》【第一季】 学习笔记,文章的主题内容均来自该课程,在这里仅作学习交流。在文章中可能出现一些错误或者不准确的地方,如发现请积极指出,十分感谢。
也欢迎大家一起讨论交流,如果你觉得这篇文章对你有所帮助,记得评论、点赞哦 ~(。・∀・)ノ゙
1. 指针和数组的关系
指针和数组之间的关系虽然十分微妙,但是不可以认为指针就是数组,因为数组名是数组第一个元素的地址,也是数组的首地址。
比如说下面的这段程序
#include <stdio.h>
int main()
{
int a;
int *p = &a;
printf("请输入一个整数:");
scanf("%d", &a);
printf("a = %d\n", a);
printf("请重新输入一个整数:");
scanf("%d", p);
printf("a = %d\n", a);
return 0;
}
我们用整型变量 a 的地址初始化指针变量 p,其中 scanf("%d", &a);
的含义是将输入存放在 &a 这个地址所指向的变量中,所以我们打印输出 a 的值,就是通过 scanf 得到的输入的值。之后我们需要重新输入一个整数,因为 scanf 是将输入的数字保存到某一个地址中,如果不使用 &a ,而是使用 p 来表示地址也是可以的。执行上面这段代码,可以获得如下的结果
请输入一个整数:13
a = 13
请重新输入一个整数:23
a = 23
这也强化了上节课所讲的一个概念,就像整型变量存放整型数字一样,指针变量用来存放地址;就像可以将整型变量当作是整型数字来使用一样 (比如说一个整形变量 a =3, b= 4,那么 a+b 实际上就可以看作是 3+4),指针变量也可以当作是地址来使用。
比如说现在我们想要输入的是一个字符串,可以使用下面的这段程序
#include <stdio.h>
int main()
{
char str[128];
printf("请输入鱼C的域名:");
scanf("%s", str);
printf("鱼C工作室的域名是:%s\n", str);
printf("str 的地址是:%p\n", str);
printf("str 的地址是:%p\n", &str[0]);
return 0;
}
在这里我们可以看到,在 scranf 中使用的是 str 而且他的前面并没有 & 符号,这是为什么呢?原因很简单,因为数组名是数组第一个元素的地址,也是数组的首地址。执行上面的代码可以得到如下的结果
请输入鱼C的域名:fishc.com
鱼C工作室的域名是:fishc.com
str 的地址是:0x7ffd75ce3930
str 的地址是:0x7ffd75ce3930
在程序中,最后两个打印输出的占位符都是 %p
,也就是最后两个输出的都是指针,并且从实验结果可以看出的确数组名是数组第一个元素的地址,也是数组的首地址。
2. 数组中每个元素的地址
由于数组是一堆类型一致的变量挨个排放在一起的,所以第二个元素的地址就应该是第一个元素地址加上第一个元素所占的空间。
在下面的这个例子中,我们初始化了多种不同类型的数组,并打印出数组中每一个元素的地址
#include <stdio.h>
int main()
{
char a[] = "FishC";
int b[5] = {1, 2, 3};
float c[5] = {1.1, 2.2, 3.3};
double d[5] = {1.1, 2.2, 3.3};
printf("a[0] -> %p, a[1] -> %p, a[2] -> %p\n", &a[0], &a[1], &a[2]);
printf("b[0] -> %p, b[1] -> %p, b[2] -> %p\n", &b[0], &b[1], &b[2]);
printf("c[0] -> %p, c[1] -> %p, c[2] -> %p\n", &c[0], &c[1], &c[2]);
printf("d[0] -> %p, d[1] -> %p, d[2] -> %p\n", &d[0], &d[1], &d[2]);
return 0;
}
执行上述代码可以得到如下的结果
a[0] -> 0x7fff94f04e80, a[1] -> 0x7fff94f04e81, a[2] -> 0x7fff94f04e82
b[0] -> 0x7fff94f04e10, b[1] -> 0x7fff94f04e14, b[2] -> 0x7fff94f04e18
c[0] -> 0x7fff94f04e30, c[1] -> 0x7fff94f04e34, c[2] -> 0x7fff94f04e38
d[0] -> 0x7fff94f04e50, d[1] -> 0x7fff94f04e58, d[2] -> 0x7fff94f04e60
对于字符串数组 a,相邻的两个元素之间的间隔是 1,int 型的间隔是 4,float 的间隔是 4,double 的间隔是 8,通过计算发现确实与上面所讲的是相同的。
3. 指针的运算
如果用一个指针遍历数组,应该怎么做呢?假如我们已经定义并初始化了一个数组,并且定义了一个指针。因为数组的名字就是数组第一个元素的地址,所以语句 1 和语句 2 是等价的,都是将数组 a 的首地址存放到指针变量 p 中。
int a[] = {1, 2, 3, 4, 5};
int *p;
p = a; // 语句1
p = &a[0]; // 语句2
如果想用一个指针遍历一个数组,只需将数组的指针指向数组的第一个元素的地址即可。数组中接下来的每一个元素只需要对他进行指针的运算就可以了。
当指针指向数组元素的时候,我们可以对指针变量进行加减运算,这样做的意义相当于指向距离指针所在位置向前或向后第 n 个元素。比如 p+1 表示 p 指针指向的元素的下一个元素;p-1 则表示指向上一个元素。需要郑重强调的是:p+1 并不是简单地将地址加 1,而是指向数组的下一个元素。
比如说下面这段代码,我们通过指针的运算来获取数组中元素的值,其中需要注意的是指针的运算要写成 *(p+1) 的形式,而不是 *p+1 的形式。
#include <stdio.h>
int main()
{
int b[5] = {1, 2, 3, 4, 5};
int *p = b;
printf("*p = %d, *(p+1) = %d, *(p+2) = %d\n", *p, *(p+1), *(p+2));
return 0;
}
运行上面这段代码可以获得如下的结果
*b = 1, *(b+1) = 2, *(b+2) = 3
通过结果可以看到,指针加1,并不是指向下一个地址,而是直接指向下一个元素。比如说上面的这个例子,程序是如何判断他应该走多少个地址来找到下一个元素呢?实际上是通过数据类型,因为在定义数组的时候,已经给出了数组中元素的数据类型,比如整型就需要再走四个地址。
实际上通过指针访问数组,并不一定要定义一个指针,因为数组名本身就是数组中第一个元素的指针,所以上面的程序还可以简化为如下的形式
#include <stdio.h>
int main()
{
int b[5] = {1, 2, 3, 4, 5};
printf("*b = %d, *(b+1) = %d, *(b+2) = %d\n", *b, *(b+1), *(b+2));
return 0;
}
下面的这个例子就比较special了。使用指针定义一个字符串,但是用数组的方式访问
#include <stdio.h>
#include <string.h>
int main()
{
char *str = "I love FishC.com!";
int i, length;
length = strlen(str);
for (i = 0; i < length; i++)
{
printf("%c", str[i]);
}
printf("\n");
return 0;
}
之所以可以用指针来定义一个字符串,是因为在 c 语言中一个字符串就是一个字符数组,所以它本质上还是一个数组,这个时候 str 代表数组中第一个元素的地址,也就是数组名,这也是他可以通过数组形式访问的原因。有的同学可能会问,为什么是数组第一个元素的地址,而不是整个数组的地址,这个将会在下节课的内容中介绍。执行代码可以得到如下的结果
I love FishC.com!
很明显代码是成功的。在上面的代码中将 printf("%c", str[i]);
改为 printf("%c", *str++);
实验发现也是也是成功的。
所以数组和指针之间的关系就是,数组名是数组第一个元素的地址,也是数组的首地址。
参考
[1] “小甲鱼” 视频课程《带你学C带你飞》【第一季】P22
欢迎大家关注我的知乎号(左侧)和经常投稿的微信公众号(右侧)