C语言第五讲:数组与指针详解
1. 数组基本概念
数组是相同类型数据元素的有序集合,这些元素在内存中连续存储,通过统一的数组名和下标来访问各个元素。
1.1 一维数组
定义与声明
// 语法:类型说明符 数组名[常量表达式];
int numbers[10]; // 声明包含10个整数的数组
float scores[5]; // 声明包含5个浮点数的数组
char letters[26]; // 声明包含26个字符的数组
重要规则:
-
数组长度必须是常量表达式,不能是变量
-
下标从0开始,最大下标为数组长度-1
-
数组名表示数组的首地址(第一个元素的地址)
初始化方式
// 1. 完全初始化
int arr1[5] = {1, 2, 3, 4, 5};
// 2. 部分初始化(未初始化元素自动为0)
int arr2[5] = {1, 2}; // arr2[0]=1, arr2[1]=2, 其余为0
// 3. 省略长度(编译器自动计算)
int arr3[] = {1, 2, 3, 4, 5}; // 自动确定为长度5
// 4. 全部初始化为0
int arr4[10] = {0}; // 所有元素为0
数组元素引用
int arr[5] = {10, 20, 30, 40, 50};
// 三种访问方式:
// 1. 下标法(最常用)
printf("%d", arr[0]); // 输出第一个元素:10
// 2. 数组名+偏移量
printf("%d", *(arr + 1)); // 输出第二个元素:20
// 3. 指针变量法
int *p = arr;
printf("%d", p[2]); // 输出第三个元素:30
1.2 冒泡排序算法详解
冒泡排序是一种简单的排序算法,通过重复比较相邻元素并交换位置来实现排序。
算法原理
#include <stdio.h>
int main() {
int nums[10];
int i, j, temp;
// 输入10个数
printf("请输入10个整数:\n");
for(i = 0; i < 10; i++) {
scanf("%d", &nums[i]);
}
// 冒泡排序核心算法
for(i = 0; i < 9; i++) { // 外层循环:控制排序轮数(n-1轮)
for(j = 0; j < 9 - i; j++) { // 内层循环:每轮比较次数递减
if(nums[j] > nums[j + 1]) { // 比较相邻元素
// 交换位置
temp = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = temp;
}
}
}
// 输出排序结果
printf("从小到大排序结果:\n");
for(i = 0; i < 10; i++) {
printf("%d ", nums[i]);
}
return 0;
}
排序过程分析
以数组 [5, 3, 8, 1, 2]为例:
第一轮:比较4次
-
5>3? 是 → 交换 →
[3, 5, 8, 1, 2] -
5>8? 否 → 不变
-
8>1? 是 → 交换 →
[3, 5, 1, 8, 2] -
8>2? 是 → 交换 →
[3, 5, 1, 2, 8]
第二轮:比较3次 → [3, 1, 2, 5, 8]
第三轮:比较2次 → [1, 2, 3, 5, 8](已有序)
1.3 多维数组
二维数组声明与初始化
// 声明一个2行3列的二维数组
int matrix[2][3];
// 初始化方式:
// 1. 按行分组初始化
int arr1[2][3] = {{1, 2, 3}, {4, 5, 6}};
// 2. 线性初始化(按内存顺序)
int arr2[2][3] = {1, 2, 3, 4, 5, 6};
// 3. 部分初始化
int arr3[2][3] = {{1, 2}, {4}}; // 未初始化的自动为0
// 4. 省略第一维长度
int arr4[][3] = {1, 2, 3, 4, 5, 6}; // 自动推断为2行
内存存储方式
二维数组按行主序存储:先存储第一行所有元素,再存储第二行,依此类推。
对于 int arr[2][3] = {{1,2,3}, {4,5,6}}:
内存布局:[1, 2, 3, 4, 5, 6]
索引对应:arr[0][0]=1, arr[0][1]=2, arr[0][2]=3,
arr[1][0]=4, arr[1][1]=5, arr[1][2]=6
元素访问
int arr[2][3] = {{1,2,3}, {4,5,6}};
printf("%d", arr[0][0]); // 输出1
printf("%d", arr[1][2]); // 输出6
// 遍历二维数组
for(int i = 0; i < 2; i++) {
for(int j = 0; j < 3; j++) {
printf("arr[%d][%d] = %d\n", i, j, arr[i][j]);
}
}
1.4 字符数组与字符串
字符数组初始化
// 1. 单个字符初始化
char str1[5] = {'H', 'e', 'l', 'l', 'o'};
// 2. 字符串常量初始化(自动添加'\0')
char str2[6] = "Hello"; // 实际存储:'H','e','l','l','o','\0'
// 3. 省略长度
char str3[] = "Hello"; // 自动计算长度为6
字符串特殊性
-
字符串以
'\0'(空字符)结尾 -
printf使用%s格式输出字符串 -
字符数组可以修改,字符串常量不可修改
char arr[] = "Hello"; // 字符数组,可修改
arr[0] = 'h'; // 合法
char *ptr = "Hello"; // 指向字符串常量,不可修改
// ptr[0] = 'h'; // 错误!试图修改常量
2. 指针基础
2.1 指针概念与声明
指针是存储内存地址的变量,通过指针可以间接访问和操作数据。
基本语法
int a = 100; // 普通整型变量
int *p = &a; // 指针变量,存储a的地址
// 三步骤理解:
// 1. 创建普通变量:int a = 10;
// 2. 创建指针变量:int *p;
// 3. 建立指向关系:p = &a;
相关运算符
| 运算符 | 含义 | 示例 | 说明 |
|---|---|---|---|
& |
取地址 | &a |
获取变量a的内存地址 |
* |
解引用 | *p |
获取指针p指向的值 |
int score = 95;
int *p = &score;
printf("score的值:%d\n", score); // 直接访问:95
printf("score的地址:%p\n", &score); // 输出地址
printf("通过p访问:%d\n", *p); // 间接访问:95
2.2 指针与数组的关系
数组名作为指针
数组名是指向数组首元素的指针常量。
int arr[5] = {10, 20, 30, 40, 50};
// 以下表达式等价:
printf("%d", arr[2]); // 下标法:30
printf("%d", *(arr + 2)); // 指针法:30
// 但数组名是指针常量,不能修改
// arr = arr + 1; // 错误!数组名不可修改
指针遍历数组
int arr[5] = {1, 2, 3, 4, 5};
int *p = arr; // p指向数组首元素
for(int i = 0; i < 5; i++) {
printf("%d ", *(p + i)); // 输出:1 2 3 4 5
}
// 或者使用指针自增
p = arr;
for(int i = 0; i < 5; i++) {
printf("%d ", *p);
p++; // 指针移动到下一个元素
}
2.3 指针数组 vs 数组指针
这是两个容易混淆的重要概念。
指针数组(数组元素是指针)
// 语法:类型 *数组名[长度];
int a = 10, b = 20, c = 30;
int *ptr_arr[3] = {&a, &b, &c}; // 包含3个int指针的数组
// 访问方式:
printf("%d", *ptr_arr[0]); // 输出10(a的值)
特点:
-
本质是数组
-
每个元素都是指针
-
内存中指针连续存储,但指向的数据可能不连续
数组指针(指向数组的指针)
// 语法:类型 (*指针名)[数组长度];
int arr[5] = {1, 2, 3, 4, 5};
int (*arr_ptr)[5] = &arr; // 指向整个数组的指针
// 访问方式:
printf("%d", (*arr_ptr)[2]); // 输出3
特点:
-
本质是指针
-
指向整个数组
-
指针运算时以整个数组为单位移动
记忆口诀
-
有括号先指针,无括号先数组
-
int *p[5]→ 无括号,先[5]→ 指针数组 -
int (*p)[5]→ 有括号,先*p→ 数组指针
2.4 指针运算与类型匹配
指针运算规则
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr;
printf("%p\n", p); // 地址:0x1000
printf("%p\n", p + 1); // 地址:0x1004(int占4字节)
// 指针运算根据指向类型的大小进行调整
char *char_p = (char*)arr;
printf("%p\n", char_p); // 地址:0x1000
printf("%p\n", char_p + 1); // 地址:0x1001(char占1字节)
类型匹配重要性
int a = 10;
float b = 3.14;
int *int_p = &a; // 正确:int指针指向int变量
// int_p = &b; // 错误!类型不匹配
float *float_p = &b; // 正确:float指针指向float变量
3. 关键概念总结
-
数组是相同类型数据的连续存储集合,通过下标访问元素
-
数组名是指向首元素的指针常量,不能修改其值
-
冒泡排序通过相邻元素比较交换实现排序,需要n-1轮比较
-
二维数组按行主序存储,可视为"数组的数组"
-
指针存储内存地址,通过
&取地址、*解引用 -
指针数组是存储指针的数组,数组指针是指向数组的指针
-
类型匹配是指针操作的基础,不同类型的指针不能混用
理解数组和指针的关系是掌握C语言核心编程能力的关键,这些概念在后续的数据结构、内存管理等高级话题中会频繁使用。