数组与指针
-
数组和指针的关系:在 C 语言中,数组名其实是一个指针,指向数组的第一个元素。例如,如果你有一个数组
int arr[10];
,那么arr
实际上是指向arr[0]
的指针。这意味着你可以用指针来遍历数组,也可以通过指针操作数组元素。 -
通过指针访问数组:当你理解了数组名是一个指针后,你就能明白为什么
*(arr + 1)
等价于arr[1]
。这个表达式的意思是“通过指针arr
,向后移动一个位置,然后取该位置的值”。 -
指针的灵活性:数组在定义时大小是固定的,而指针则更为灵活,可以指向任何内存位置。这使得你在处理数据时,能够更灵活地进行操作,例如动态分配内存或处理不同类型的数据结构。
-
内存理解:通过理解指针如何操作数组,你会对内存管理有更深入的认识。这在 C 语言中尤为重要,因为你需要手动管理内存,避免错误操作。
把数组和指针放在一起学习,有助于更深入地理解两者的工作原理,可以更好地理解内存和数据访问的底层机制,让编程更加高效和可靠。
数组
- 具有相同的数据类型
- 使用过程中需要保留原始数据
一维数组
数组初始化中的常量表达式:数组名 [常量表达式](通常为常量,或宏定义的符号常量)表示数组的大小,这是因为一个函数的函数栈空间大小是固定的。
数组元素的引用方式是 数组名 [下标](从 0 开始)访问数组中的数据
数组在内存中的存放
数组存放在一块连续的内存中,元素间的地址根据其数据类型所占字节数决定。
访问越界 (Out-of-Bounds Access)
C 语言的内存管理:先定义的变量在高地址,后定义的变量在低地址。
#include <stdio.h>
int main() {
int i = 10; // 定义一个整数变量 i,并赋值为 10
int arr[3] = {1, 2, 3}; // 定义一个大小为 3 的整数数组
printf("Before out-of-bounds access:\n");
printf("i = %d\n", i);
printf("arr[0] = %d, arr[1] = %d, arr[2] = %d\n", arr[0], arr[1], arr[2]);
// 访问数组的第 4 个元素(越界访问)
arr[3] = 20;
printf("\nAfter out-of-bounds access:\n");
printf("i = %d\n", i); // 查看 i 的值是否被改变
printf("arr[0] = %d, arr[1] = %d, arr[2] = %d\n", arr[0], arr[1], arr[2]);
return 0;
}
当访问越界发生时,新定义的数组元素可能覆盖了先定义的变量i
的地址(高地址),i
的值(该地址中的值)发生改变。
可能的输出:
Before out-of-bounds access:
i = 10
arr[0] = 1, arr[1] = 2, arr[2] = 3
After out-of-bounds access:
i = 20 // i的值可能被改变
arr[0] = 1, arr[1] = 2, arr[2] = 3
在不同的环境中,越界行为是未定义的,结果可能会有所不同,甚至可能导致程序崩溃。为避免这种错误,始终要确保在使用数组时不越界访问。
二维数组
二维数组可以看作是一种特殊的一维数组,按行连续存放在内存中。
注意:二维数组的数组名 —— 指向一个一维数组的指针
函数的值传递:二维数组打印
#include <stdio.h>
void print(int a[][4], int row)
{
int i,j;
for (i = 0; i < row; i++)
{
for(j=0; j<sizeof(a[0])/sizeof(int); j++)
{
printf("%3d", a[i][j]);
}
printf("\n");
}
}
int main()
{
int arr[3][4]={1,3,5,7,9,11,13,15,17,19,21,23};
print(arr, 3);
system("pause");
}
像一维数组的大小一样,二维数组的“行”不能直接传递到子函数中,二维数组在传递时是弱化成的,因此需要把“列”写入到形参中才能通过编译。
指针
编译时,系统给变量分配内存地址。
指针用于指向内存地址,并可以通过直接或间接的方式访问和修改地址内的变量。
指针也是一个变量,称为指针变量,
指针的使用场景:传递和偏移。
一级指针
一级指针的传递
例:使用子函数改变主函数中变量的值。
void change(int *j)
{
*j = 5;
}
int main()
{
int i = 10;
change(&i);
//把变量i的地址传递给子函数中的指针变量j
printf("%d\n, i);
}
若使用简单的值传递,由于子函数与主函数的变量所在的栈空间不同,当 change
函数执行结束时,子函数占用的栈空间得到释放。对子函数中局部变量的操作不影响主函数中的变量。
指针的偏移与自增
指针的偏移是为连续的数组元素服务的。
#include <stdio.h>
int main()
{
int a[5] = {1, 3, 5, 7, 9}; // 定义一个包含5个元素的数组a
int *p; // 定义一个指向整数的指针p
int i;
p = a; // p指向数组a的第一个元素,即p = &a[0],a[0]的地址
i = *p++;
// *p 取当前指针p指向的值,也就是从地址&a[0]找到a[0]的值1;
// 然后p++,指针p自增,指向数组的下一个元素a[1];
// 最后将a[0]的值1赋给变量i,所以此时i = 1。
i = p[0]++;
// p[0]表示当前p指向的元素的值,指针p在上一步之后自增指向数组a的第二个元素,即a[1]的值3;
// 先将p[0]的当前值3赋给i,所以i = 3;
// 然后执行自增操作p[0]++,也就是p[0]=p[0]+1,将p[0]的值3增加1,即a[1]的值变为4。
}
指针偏移:p++
将指针 p
从指向 a[0]
变为指向 a[1]
。这种操作使得你可以遍历数组元素,而不需要直接使用数组下标。
p++
和 p[0]++
是两种不同的自增操作。
p++
是指针自增,使指针指向下一个元素。
p[0]++
是元素自增,先取当前元素的值,再将该元素值增加 1。
数组指针与二维数组
数组指针是一个指向行数组的指针变量,服务于二维数组的传递与偏移。
数组指针本质上仍是一级指针。
int main()
{
int a[3][4]={1,3,5,7,9,11,13,15,17,19,21,23};
int b[4]={1,2,3,4};
int (*p)[4];
//指向长度为4的行数组
int i,j;
p = a;
for(i = 0; i < 3; i++)
{
for(j = 0; j < 4; j++)
{
printf("%3d", *(*(p+i)+j));
}
}
}
二维数组的偏移
表示形式 | 含义 | 地址值 |
---|---|---|
a |
二维数组名,指向行数组a[0] | 0x2000 |
a[0] , *(a+0) , *a |
0行0列元素地址 | 0x2000 |
a+1 , &a[1] |
1行的首地址 | 0x2010 |
a[1] , *(a+1) |
1行0列元素a[1][0]的地址 | 0x2010 |
a[1]+2 , *(a+1)+2 , &a[1][2] |
1行2列元素a[1][0]的地址 | 0x2018 |
*(a[1]+2) , *( *(a+1)+2) , a[1][2] |
1行2列元素a[1][0]的值 | 元素的值为13 |
&a+1
对数组名取地址后 +1,指针偏移到数组末尾
二级指针
二级指针服务于一级指针的传递与偏移。
使用场景:在子函数(定义形参为二级指针)中改变主函数的一级指针指向(值)。
void change(int** p1, int* p2)
{
*p1 = p2;
}
int main()
{
int i=10;
int j=5;
int *pi = &i;
int *pj = &j;
printf("i=%d,j=%d,*pi=%d,*pj=%d\n",i,j,*pi,*pj);
change(&pi,pj);
//把变量j的值赋给变量i
printf("i=%d,j=%d,*pi=%d,*pj=%d\n",i,j,*pi,*pj);
}
指针数组
二级指针的偏移服务于指针数组。
例,冒泡排序(索引式排序)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define N 5
int main()
{
char b[N][10]={"Google","Facebook","Microsoft","HUAWEI","Apple"};
char *p[N];
char **p2;
int i,j;
char *temp;
p2 = p;
for (i = 0; i < N; i++)
{
p2[i]=b[i];
}
for (i = 0; i < N; i++)
{
printf("%s\n",p2[i]);
}
printf("After sort:\n");
for (i = N; i > 0; i--)
{
for (j = 0; j < i - 1; j++)
{
if (strcmp(p[j], p[j + 1])>0)
{
temp = p[j+1];
p[j+1] = p[j];
p[j] = temp;
}
}
}
for (i = 0; i < N; i++)
{
printf("%s\n",p[i]);
}
printf("-----------------\n");
for (i = 0; i < N; i++)
{
printf("%s\n", b[i]);
}
system("pause");
}
通过交换指针(指向的地址值)的顺序,对来数据量较大的结构体进行排序操作,以减小交换成本。
指针数组与其他数组一样,无法将长读传递给子函数。
实际工作中,排序数据较多时,需要为指针数组申请动态内存空间。
p = (char **)malloc(sizeof(char *) *5);