程序员Feri一名12年+的程序员,做过开发带过团队创过业,擅长Java、鸿蒙、嵌入式、人工智能等开发,专注于程序员成长的那点儿事,希望在成长的路上有你相伴!君志所向,一往无前!
指针与数组
-
"
*
",也称为解引用符号,其作用与&相反
。 -
"
*
",后面只能跟指针(即地址)或指针变量,"&
"后面跟的是普通变量(包括指针变量)。
1.指向一维数组的指针变量
所谓数组元素的指针就是数组元素的地址
。可以用一个指针变量指向一个数组元素。
int a[10]={2,4,6,8,10,12,14,16,18,20};
int *p; //定义p为指向整型变量的指针变量
p = &a[0]; //把a[0]元素的地址赋给指针变量p
如下几个写法是等价的:
int *p;
p = &a[0]; //千万不要写成*p = &a[0];,那就错了
int *p = &a[0];
int *p = a; //a不代表整个数组,所以这里不是将数组a赋给p。而代表数组元素a[0]的首地址。
注意:
因为数组名a保存的是数组首元素a[0]的地址
,所以在scanf函数中的输入项如果是数组名,不要再加地址符&
。
int main() {
char arr[10];
scanf("%s", arr); //arr前不应加 &
puts(arr);
return 0;
}
2.指针访问数组的元素
如果指针变量p的初值为&a[0]
,则:
-
p+i
和a+i
就是数组元素a[i]
的地址。或者说,它们指向a数组序号为i的元素。 -
*(p+i)
或*(a+i)
是p+i
或a+i
所指向的数组元素的值,即a[i]的值。
举例1:数组元素赋值、遍历
方式1:下标法
#include <stdio.h>
#define N 5
int main() {
int a[N];
printf("请输入%d个整数:\n",N);
for (int i = 0; i < N; i++)
scanf("%d", &a[i]); //数组元素用数组名和下标表示
for (int i = 0; i < N; i++)
printf("%d ", a[i]);
printf("\n");
return 0;
}
方式2:
#include <stdio.h>
#define N 5
int main() {
int a[N];
printf("请输入%d个整数:\n",N);
for (int i = 0; i < N; i++)
scanf("%d", &a[i]); //数组元素用数组名和下标表示
for (int i = 0; i < N; i++)
printf("%d ", *(a + i));
printf("\n");
return 0;
}
方式3:使用指针变量
#include <stdio.h>
#define N 5
int main() {
int a[N];
int *p = a;
printf("请输入%d个整数:\n", N);
for (int i = 0; i < N; i++)
scanf("%d", p + i);
for (int i = 0; i < N; i++)
printf("%d ", *(p + i));
printf("\n");
return 0;
}
或者
#include <stdio.h>
#define N 5
int main() {
int a[N];
int *p = a;
printf("请输入%d个整数:\n", N);
for (int i = 0; i < N; i++)
scanf("%d", p + i);
for (p = a; p < (a + N); p++)
printf("%d ", *p);
printf("\n");
return 0;
}
第(1)和第(2)种方法执行效率是相同的。C编译系统是将a[i]转换为*(a+i)处理的,即先计算元素地址。因此用第(1)和第(2)种方法找数组元素
费时较多
。第(3)种方法比第(1)、第(2)种方法快,用指针变量直接指向元素,不必每次都重新计算地址,像p++这样的自加操作是比较快的。这种有规律地改变地址值(p++)能大大
提高执行效率
。但第(1)方法比较直观,适合初学者。
思 考:
可以通过改变指针变量p的值指向不同的元素。如果不用p变化的方法而用数组名a变化的方法(例如,用a++)行不行呢? (不行)
for(p = a;a < (p + N);a++)
printf("%d",*a);
因为数组名a代表数组的首地址(或数组首元素的地址),它是一个指针型常量
,它的值在程序运行期间是固定不变的。所以a++是无法实现的。必须将 a 的地址赋值给指针变量 p ,然后对 p 进行自增。
指针带下标的使用
指向数组元素的指针变量也可以带下标,如p[i]
。p[i]被处理成*(p+i)
,如果p是指向一个整型数组元素a[0],则p[i]代表a[i]。但是必须弄清楚p的当前值是什么?如果当前p指向a[3],则p[2]并不代表a[2],而是a[3+2],即a[5]。
举例:
int main() {
int a[5] = {10,20,30,40,50};
int *p = a;
//遍历数组元素
for(int i = 0;i < 5;i++){
printf("%d ",p[i]);
}
printf("\n");
//注意:
p++;
printf("%d ",p[0]); //20
return 0;
}
&数组名
举例1:
//复习
int main() {
int arr[5] = {0};
int *p = arr;
printf("%p\n",p); //000000000034fa50
printf("%p\n",&p); //000000000037fbd8
return 0;
}
进一步思考:
printf("%p\n", arr); //000000000034fa50
printf("%p\n", &arr); //000000000034fa50
发现,数组名
和 &数组名
打印的地址是一样的。
二维数组与指针
使用数组名访问
设有一个二维数组 a 定义为:
int a[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}};
二维数组 a,可视为三个一维数组:a[0]、a[1]、a[2];而每个一维数组又是一维数组,分别由 4 个元素组成。首先,理解如下的操作:
printf("%d\n",a[0][0]); //二维数组中元素a[0][0]的值
printf("%p\n",&a[0][0]); //二维数组中元素a[0][0]的值对应的地址
printf("%p\n",a[0]); //二维数组中a[0][0]的地址
printf("%p\n",a); //二维数组中a[0]的地址
printf("%p\n",&a); //二维数组a的地址
对应图示
举例:
表示形式 | 含义 | 地址 |
---|---|---|
a | 二维数组名,指向一维数组a[0], 即0行起始地址 | 2000 |
&a[0][0],a[0],*a | 0行0列元素地址 | 2000 |
a[0][0],*(a[0]),**a | 0行0列元素a[0][0] 的值 | 1 |
&a[1],a+1 | 指向索引为1行的起始地址 | 2016 |
&a[1][0],a[1],*(a+1) | 1行0列元素a[1][0] 的地址 | 2016 |
a[1][0],*(a[1]),*(*(a+1)) | 1行0列元素a[1][0] 的值 | 5 |
&a[1][2],a[1]+2,*(a+1)+2 | 1行2列元素a[1][2] 的地址 | 2024 |
a[1][2],*(a[1]+2),*(*(a+1)+2) | 1行2列元素a[1][2] 的值 | 是元素值,7 |
总结:
&a:二维数组a的地址 a: 二维数组中a[0]的地址 a[0]:二维数组中a[0][0]的地址 讨论:a[0][0]相关的 a[0][0]的地址:&a[0][0],a[0],*a, a[0][0]的值: a[0][0],*(a[0]),**a, 讨论:a[1]相关的 a[1]的地址:&a[1],a + 1 讨论:a[1][0]相关的 a[1][0]的地址:&a[1][0],a[1],*(a+1) a[1][0]的值:a[1][0],*a[1],*(*(a+1)) 讨论:a[1][2]相关的 a[1][2]的地址:&a[1][2],a[1]+2,*(a+1)+2 a[1][2]的值:a[1][2],*(a[1]+2),*(*(a+1)+2)
注意:
如果 a 是二维数组,则 a[i]代表一个数组名, a[i]并不占内存单元,也不能存放a 数组元素值。它只是一个地址。所以:a、a+i、a[i]、*(a+i)、*(a+i)+j、a[i]+j 都是地址。
获取数组元素值的三种表示形式:
1) a[i][j]
下标法
2) *(a[i]+j)
用一维数组名
3) *(*(a+i)+j)
用二维数组名
使用指针变量访问
设 p 是指针变量,若p 指向数组首元素,即p = a[0];
,那a[i][j]
的指针如何表示?
先看一个代码:
int main() {
int a[3][2] = {
{10, 20},
{30, 40},
{50, 60}};
int *p;
p = &a[0][0];
printf("%p\n", p); //000000f2f49ff7b0
printf("%p\n", p + 1); //000000f2f49ff7b4
printf("%p\n", p + 2); //000000f2f49ff7b8
int *q;
q = a[0];
printf("%p\n", q); //000000f2f49ff7b0
printf("%p\n", q + 1); //000000f2f49ff7b4
printf("%p\n", q + 2); //000000f2f49ff7b8
int *r;
r = a; //代码片段1
printf("%p\n", r); //000000f2f49ff7b0
printf("%p\n", r + 1); //000000f2f49ff7b4
printf("%p\n", r + 2); //000000f2f49ff7b8
return 0;
}
进而:
-
p+j 将指向 a[0] 数组中的元素
a[0][j]
。 - 对于二维数组
a[M][N]
来讲,由于 a[0]、a[1]、... 、a[M-1]等各行数组在内存中是依次连续存储,则对于 a 数组中的任一元素a[i][j]
:-
地址表示:
p+i*N+j
-
值表示:
*(p+i*N+j)
、p[i*N+j]
-
注意:上述代码中,代码片段1中的赋值操作会存在类型不匹配的情况,我们在5.6节中展开说明。
举例1:
int b[4][3] = {
{10, 20, 30},
{40, 50, 60},
{70, 80, 90},
{100, 110, 120}};
int *p = b[0];
则:元素 b[1][2]
对应的地址/指针、元素值为:
printf("b[1][2]对应的地址/指针为:%p\n",p+1*3+2);
printf("b[1][2]对应的值为:%d\n",*(p+1*3+2));
printf("b[1][2]对应的值为:%d\n",p[1*3+2]);
举例2:用指针访问二维数组,求二维数组元素的最大值。
#include <stdio.h>
#define ROWS 3
#define COLS 4
int main() {
int a[ROWS][COLS] = {
{10, 20, 30, 40},
{50, 60, 70, 80},
{120, 110, 100, 90}};
int *q, max1;
for (q = a[0], max1 = *q; q < a[0] + ROWS * COLS; q++)
if (max1 < *q)
max1 = *q;
printf("Max=%d\n", max1);
return 0;
}
指针数组
数组指针 vs 指针数组
数组指针:
当指针变量里存放一个数组的首地址时,此指针变量称为指向数组的指针变量,简称数组指针
。
数组指针是指针?还是数组?
答案是:指针。
整型指针: int * pint; 能够指向整型数据的指针。
浮点型指针: float * pf; 能够指向浮点型数据的指针。
数组指针:能够指向数组的指针。
指针数组:
数组是用来存放一系列相同类型的数据,当然数组也可以用来存放指针,这种用来存放指针的数组
被称为指针数组,它要求存放在数组中指针的数据类型必须一致
。
问题:指针数组是指针还是数组?
答案:是数组。是存放指针的数组。
指针数组的使用
格式:
数据类型 *指针数组名[大小];
举例1:
int *arr[5];
arr是一个数组,有5个元素,每个元素是一个整型指针,需要使用下标来区分。
今天就到这吧,指针也就说到这里啦,加油哈!