C语言day14-指针-二维数组

一、数组与指针的基本概念

1. 数组的三大特点

  • 连续性:内存空间连续分配
  • 单一性:元素类型相同
  • 有序性:元素有固定顺序

2. 二维数组的本质

二维数组 = 一维数组的数组

int a[3][4];
// 可以理解为:有3个元素,每个元素都是int[4]类型

3. 核心类型辨析

// 整型二维数组
int a[3][4];
int (*a)[4];     // ❌ 错误理解
                 // ✅ 正确:a是数组名,不需要*

// 数组指针 vs 指针数组
char (*s)[4];    // 数组指针:指向char[4]类型数组的指针
char* s[4];      // 指针数组:有4个元素,每个元素都是char*类型

二、二维数组首元素的理解

1. 首元素的双重含义

int a[3][4];

首元素:a[0]
  // 1. 作为二维数组的首元素
  // 2. 相当于是内部一维数组的数组名
  //    a. 类型 --- int[4]类型
  //    b. 值   --- 内部一维数组的首元素地址 &a[0][0]

2. 地址的层次关系

&a[0]       // 1.行地址
a[0][0]     // 内部一维数组的首元素
&a[0][0]    // 内部一维数组的首元素地址
&a[0][0]    // <=地址值等价=> a[0]

关键理解

  • a[0]等价于 &a[0][0]
  • a[0]类型int[4]
  • &a[0]类型int[4]*(C语言写作 int(*)[4]

三、数组指针的定义与使用

1. 定义方式

int a[3][4];

// 首元素是 a[0]
// &a[0] --- 行地址
//      --- 确定地址类型
//      --- a[0]的类型是 int[4]
//      --- &a[0]的数据类型 --- int[4]*

int (*p)[4] = &a[0];  // 正确定义
int (*p)[4] = a;      // 也正确,a会退化为&a[0]

2. 指针运算详解

int a[3][4];
int (*p)[4] = a;

访问元素:
  p        // 代表一行地址
  p+i      // 第i行 行地址
  *(p+i)   // 第i行 第0列(首元素)的地址 ---列地址
  *(p+i)+j // 第i行 第j列 元素的地址 ---列地址
  *(*(p+i)+j) // 第i行 第j列 元素
表达式含义类型
p代表一行地址int(*)[4] 行指针
p+i第i行的行地址int(*)[4] 行指针
*(p+i)第i行首元素地址(降维)int* 列指针
*(p+i)+j第i行第j列元素地址int* 列指针
*(*(p+i)+j)第i行第j列元素值int

3. 等价表达式

(1). a[i][j]
(2). *(*(a+i)+j)
(3). *(a+i)[j]        // a[i] <=> *(a+i)
(4). *(a[i] + j)

四、字符型二维数组

1. 基本使用

char s[3][10] = {"hello", "world", "china"};
char (*p)[10] = s;

// 访问方式
*(p+i)        // 第i行首字符地址
*(p+i)+j      // 第i行第j列字符地址 (i=0, j=4)

2. 内存布局

char s[3][10] = {"hello", "world", "china"};

内存示意:
p    -> |'h'|'e'|'l'|'l'|'o'|'\0'|...|  (10字节)
p+1  -> |'w'|'o'|'r'|'l'|'d'|'\0'|...|  (10字节)
p+2  -> |'c'|'h'|'i'|'n'|'a'|'\0'|...|  (10字节)

3. 关注点---字符串

printf("%s\n", p+i);    // ❌ 不行
                        // p+i是行地址 char(*)[10]
                        // %s需要 char* 类型

printf("%s\n", *(p+i)); // ✅ 正确
                        // *(p+i)是列地址 char*
                        // %s --- char* 整个字符串的首地址

关键理解

  • 不行:p+i行地址 char(*)[10]
  • 正确:*(p+i)首字符地址 char*
  • %s 需要的是 char* 类型

五、访问二维数组的两种方式

方式1:使用数组指针(推荐)

int a[3][4];
int (*p)[4] = &a[0];  // 或 = a
int (*p)[4] = a;

方式2:使用二维数组名直接访问

int a[3][4];
// 直接用 a 访问,a 等价于 &a[0]

六、指针数组(一维指针数组)

1. 定义与特点

char *s1 = "hello";
char *s2 = "world";
char *s3 = "china";

char *s[3] = {"hello", "world", "china"};
// s 是指针数组:数组中存放的都是地址

2. 二级指针操作

char *s[3] = {"hello", "world", "china"};
char **p = s;  // p指向指针数组的首元素

// 类型分析
s[0]    // 首元素,类型:char*
&s[0]   // 首元素地址,类型:char**

// 使用
printf("%s\n", p+i);    // ❌ 错误,p+i是char**
printf("%s\n", *(p+i)); // ✅ 正确,*(p+i)是char*

七、完整对比表

1. 类型对比

声明方式名称存储内容指针类型适用场景
int a[3][4]二维数组12个intint(*)[4]固定大小的矩阵
char s[3][10]字符二维数组3×10个字符char(*)[10]固定长度的字符串集合
char *s[3]指针数组3个char*char**不同长度的字符串集合

2. 内存布局对比

// 二维数组
char s[3][10] = {"hello", "world", "china"};
[h][e][l][l][o][\0][ ][ ][ ][ ]  // 连续的30字节
[w][o][r][l][d][\0][ ][ ][ ][ ]
[c][h][i][n][a][\0][ ][ ][ ][ ]

// 指针数组
char *s[3] = {"hello", "world", "china"};
s[0] -> "hello"  (在只读数据区)
s[1] -> "world"  (在只读数据区)
s[2] -> "china"  (在只读数据区)
数组本身只存储3个指针

3. 操作对比

操作二维数组指针数组
定义指针char (*p)[10] = s;char **p = s;
输出字符串printf("%s", *(p+i));printf("%s", *(p+i));
修改字符✅ 可以:s[0][0] = 'H';❌ 不可以(字符串常量)
改变指向❌ 数组名不可改变✅ 可以:s[0] = "new";

八、重点难点解析

1. 降维操作的理解

int (*p)[4];  // 行指针

p      // 行指针(int(*)[4])
*(p)   // 降维 -> 列指针(int*)
*(p)+j // 仍是列指针
*(*(p)+j) // 降维 -> 元素值(int)

记忆口诀

  • 每次 * 操作就是一次降维
  • 行指针 → 列指针 → 元素值

2. 类型判断技巧

// 看到 [] 在变量名右边 → 数组
char s[10];     // 数组
char *s[10];    // 指针数组(数组优先级高)

// 看到 () 包裹变量名 → 指针
char (*s)[10];  // 数组指针(指针优先级被()提高)
char (*s);      // 普通指针

3. 字符串输出的注意事项

char s[3][10] = {"hello", "world", "china"};
char (*p)[10] = s;

// ❌ 错误写法
printf("%s", p);      // p是行指针,不是字符指针
printf("%s", p+1);    // p+1还是行指针

// ✅ 正确写法
printf("%s", *p);     // *p是字符指针 char*
printf("%s", *(p+1)); // *(p+1)是字符指针 char*
printf("%s", p[1]);   // p[1]等价于*(p+1)

九、课堂练习汇总

练习1:找出二维整型数组最大值

int findMax(int (*p)[4], int row, int col);

练习2:字符串输入函数

void inputStr(char (*p)[10], int row);

练习3:找出最大字符串

int findMaxStr(char (*p)[10], int row);

练习4:字符串逆序

void reverseStrings(char (*p)[10], int row);

练习5:字符串排序

// 选择排序
void selectSort(char (*p)[10], int row);

// 冒泡排序
void bubbleSort(char (*p)[10], int row);

十、记忆要点总结

1. 核心概念

  • 二维数组的首元素是一个一维数组 a[0]
  • 数组名的是地址,类型是数组类型
  • &a[0] 地址值等于 a[0],但类型不同

2. 指针类型

  • 数组指针int (*p)[4] ← 指向数组
  • 指针数组int *p[4] ← 数组存指针

3. 访问元素

// 二维数组
*(*(p+i)+j)  <==>  p[i][j]  <==>  a[i][j]

// 记忆:两次*降维,两次[]索引

4. 字符串操作

  • 二维数组:printf("%s", *(p+i));
  • 指针数组:printf("%s", *(p+i));
  • 关键:确保传给 %s 的是 char* 类型

5. 类型判断口诀

  • 优先级() > [] > *
  • 从变量名开始,向右看 [] 是数组,向左看 * 是指针
  • 遇到 () 就先处理括号内的

终极理解:指针的本质是类型系统,不同的指针类型决定了:

  1. 指针运算时的步长(+1移动多少字节)
  2. 解引用时得到什么类型
  3. 可以执行什么操作

掌握了类型,就掌握了指针!

 1 #include <stdio.h>
  2 int main(void)
  3 {
  4    int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
  5    int (*p)[4] = a;
  6    int i = 0;
  7    int j = 0;
  8    for(i = 0;i <3;++i)
  9    {
 10        for(j = 0;j<4;++j)
 11        {
 12            printf("%d ",*(*(p+i)+j));
 13        }
 14        putchar('\n');
 15    
 16    }
 17        return 0;                                                           
 18 }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值