第06章 数组
6.1 一维数组
6.1.1 数组的定义
语法:
数据类型 数组名[元素个数];
示例:
#include <stdio.h>
int main(void)
{
int arr[5]; // 定义一个包含5个整数的数组
// 数组大小
printf("数组大小: %lu 字节\n", sizeof(arr)); // 20字节
printf("元素个数: %lu\n", sizeof(arr) / sizeof(arr[0])); // 5
return 0;
}
⚠️ 注意:
- 数组下标从0开始,范围是[0, n-1]
- 数组大小必须是常量表达式
- 数组大小确定后不能改变
6.1.2 数组的初始化
完全初始化:
int arr[5] = {1, 2, 3, 4, 5};
部分初始化:
int arr[5] = {1, 2, 3}; // 剩余元素自动初始化为0
// arr = {1, 2, 3, 0, 0}
全部初始化为0:
int arr[5] = {0}; // 所有元素都是0
省略数组大小:
int arr[] = {1, 2, 3, 4, 5}; // 编译器自动计算大小为5
指定位置初始化(C99):
int arr[10] = {[0] = 1, [5] = 6, [9] = 10};
// arr = {1, 0, 0, 0, 0, 6, 0, 0, 0, 10}
6.1.3 数组的访问
#include <stdio.h>
int main(void)
{
int arr[5] = {10, 20, 30, 40, 50};
// 访问元素
printf("arr[0] = %d\n", arr[0]); // 10
printf("arr[2] = %d\n", arr[2]); // 30
// 修改元素
arr[1] = 25;
printf("arr[1] = %d\n", arr[1]); // 25
return 0;
}
⚠️ 数组越界: C语言不检查数组下标是否越界,越界访问会导致未定义行为!
int arr[5] = {1, 2, 3, 4, 5};
printf("%d\n", arr[10]); // 未定义行为!可能崩溃或输出垃圾值
6.1.4 遍历数组
使用for循环:
#include <stdio.h>
int main(void)
{
int arr[5] = {10, 20, 30, 40, 50};
int n = sizeof(arr) / sizeof(arr[0]);
for (int i = 0; i < n; i++)
{
printf("arr[%d] = %d\n", i, arr[i]);
}
return 0;
}
6.1.5 数组应用示例
示例1: 求数组最大值
#include <stdio.h>
int main(void)
{
int arr[] = {12, 45, 23, 78, 34, 90, 56};
int n = sizeof(arr) / sizeof(arr[0]);
int max = arr[0];
for (int i = 1; i < n; i++)
{
if (arr[i] > max)
{
max = arr[i];
}
}
printf("最大值: %d\n", max);
return 0;
}
示例2: 数组求和与平均值
#include <stdio.h>
int main(void)
{
int arr[] = {85, 90, 78, 92, 88};
int n = sizeof(arr) / sizeof(arr[0]);
int sum = 0;
for (int i = 0; i < n; i++)
{
sum += arr[i];
}
double avg = (double)sum / n;
printf("总分: %d\n", sum);
printf("平均分: %.2f\n", avg);
return 0;
}
示例3: 数组逆序
#include <stdio.h>
void printArray(int arr[], int n)
{
for (int i = 0; i < n; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
void reverseArray(int arr[], int n)
{
for (int i = 0; i < n / 2; i++)
{
int temp = arr[i];
arr[i] = arr[n - 1 - i];
arr[n - 1 - i] = temp;
}
}
int main(void)
{
int arr[] = {1, 2, 3, 4, 5};
int n = sizeof(arr) / sizeof(arr[0]);
printf("原数组: ");
printArray(arr, n);
reverseArray(arr, n);
printf("逆序后: ");
printArray(arr, n);
return 0;
}
输出:
原数组: 1 2 3 4 5
逆序后: 5 4 3 2 1
6.2 二维数组
6.2.1 二维数组的定义
语法:
数据类型 数组名[行数][列数];
示例:
int matrix[3][4]; // 3行4列的二维数组
内存布局: 二维数组在内存中按行存储(行主序)
matrix[0][0], matrix[0][1], matrix[0][2], matrix[0][3],
matrix[1][0], matrix[1][1], matrix[1][2], matrix[1][3],
matrix[2][0], matrix[2][1], matrix[2][2], matrix[2][3]
6.2.2 二维数组的初始化
完全初始化:
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
按行初始化:
int matrix[3][4] = {{1, 2}, {5, 6, 7}, {9}};
// {1, 2, 0, 0},
// {5, 6, 7, 0},
// {9, 0, 0, 0}
一维方式初始化:
int matrix[3][4] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
省略行数(列数不能省略):
int matrix[][4] = {1, 2, 3, 4, 5, 6, 7, 8}; // 自动推断为2行4列
全部初始化为0:
int matrix[3][4] = {0};
6.2.3 二维数组的访问
#include <stdio.h>
int main(void)
{
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
// 访问元素
printf("matrix[0][0] = %d\n", matrix[0][0]); // 1
printf("matrix[1][2] = %d\n", matrix[1][2]); // 7
printf("matrix[2][3] = %d\n", matrix[2][3]); // 12
// 修改元素
matrix[1][1] = 100;
printf("matrix[1][1] = %d\n", matrix[1][1]); // 100
return 0;
}
6.2.4 遍历二维数组
#include <stdio.h>
int main(void)
{
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
int rows = sizeof(matrix) / sizeof(matrix[0]); // 3
int cols = sizeof(matrix[0]) / sizeof(matrix[0][0]); // 4
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
printf("%3d ", matrix[i][j]);
}
printf("\n");
}
return 0;
}
输出:
1 2 3 4
5 6 7 8
9 10 11 12
6.2.5 二维数组应用示例
示例1: 矩阵转置
#include <stdio.h>
void printMatrix(int m[][4], int rows, int cols)
{
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
printf("%3d ", m[i][j]);
}
printf("\n");
}
}
void transpose(int m[][4], int result[][3], int rows, int cols)
{
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
result[j][i] = m[i][j];
}
}
}
int main(void)
{
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
int result[4][3];
printf("原矩阵:\n");
printMatrix(matrix, 3, 4);
transpose(matrix, result, 3, 4);
printf("\n转置后:\n");
printMatrix(result, 4, 3);
return 0;
}
示例2: 求每行的和
#include <stdio.h>
int main(void)
{
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
int rows = 3, cols = 4;
for (int i = 0; i < rows; i++)
{
int sum = 0;
for (int j = 0; j < cols; j++)
{
sum += matrix[i][j];
}
printf("第%d行的和: %d\n", i, sum);
}
return 0;
}
输出:
第0行的和: 10
第1行的和: 26
第2行的和: 42
6.3 字符数组
6.3.1 字符数组的定义
char str[10]; // 定义一个包含10个字符的数组
6.3.2 字符数组的初始化
逐个字符初始化:
char str[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
字符串字面量初始化:
char str[6] = "Hello"; // 自动添加'\0'
// 等价于 {'H', 'e', 'l', 'l', 'o', '\0'}
省略数组大小:
char str[] = "Hello"; // 自动计算大小为6(包含'\0')
⚠️ 重要: C语言字符串以'\0'结尾,'\0'是字符串结束标志。
6.3.3 字符串的输入输出
输出字符串:
#include <stdio.h>
int main(void)
{
char str[] = "Hello World";
printf("%s\n", str); // 输出整个字符串
puts(str); // 同上,自动换行
return 0;
}
输入字符串:
#include <stdio.h>
int main(void)
{
char name[50];
printf("请输入姓名: ");
scanf("%s", name); // 遇到空格停止
printf("您好, %s!\n", name);
return 0;
}
读取包含空格的字符串:
#include <stdio.h>
int main(void)
{
char str[100];
printf("请输入一行文本: ");
fgets(str, sizeof(str), stdin);
printf("您输入的是: %s", str);
return 0;
}
6.3.4 字符串处理函数
需要包含头文件<string.h>
strlen - 获取字符串长度:
#include <stdio.h>
#include <string.h>
int main(void)
{
char str[] = "Hello";
printf("长度: %lu\n", strlen(str)); // 5 (不包括'\0')
printf("数组大小: %lu\n", sizeof(str)); // 6 (包括'\0')
return 0;
}
strcpy - 字符串拷贝:
#include <stdio.h>
#include <string.h>
int main(void)
{
char src[] = "Hello";
char dest[20];
strcpy(dest, src);
printf("dest = %s\n", dest); // Hello
return 0;
}
⚠️ 危险: strcpy不检查目标数组大小,可能溢出!
strncpy - 安全的字符串拷贝:
char dest[20];
strncpy(dest, src, sizeof(dest) - 1);
dest[sizeof(dest) - 1] = '\0'; // 确保以'\0'结尾
strcat - 字符串连接:
#include <stdio.h>
#include <string.h>
int main(void)
{
char str1[50] = "Hello ";
char str2[] = "World";
strcat(str1, str2); // 将str2连接到str1后面
printf("%s\n", str1); // Hello World
return 0;
}
strcmp - 字符串比较:
#include <stdio.h>
#include <string.h>
int main(void)
{
char str1[] = "abc";
char str2[] = "abc";
char str3[] = "abd";
printf("%d\n", strcmp(str1, str2)); // 0 (相等)
printf("%d\n", strcmp(str1, str3)); // <0 (str1 < str3)
printf("%d\n", strcmp(str3, str1)); // >0 (str3 > str1)
return 0;
}
strchr - 查找字符:
#include <stdio.h>
#include <string.h>
int main(void)
{
char str[] = "Hello World";
char *p = strchr(str, 'W');
if (p != NULL)
{
printf("找到字符'W'在位置: %ld\n", p - str); // 6
printf("从该字符开始: %s\n", p); // World
}
return 0;
}
strstr - 查找子串:
#include <stdio.h>
#include <string.h>
int main(void)
{
char str[] = "Hello World";
char *p = strstr(str, "World");
if (p != NULL)
{
printf("找到子串在位置: %ld\n", p - str); // 6
printf("子串: %s\n", p); // World
}
return 0;
}
6.3.5 字符串处理示例
示例1: 统计字符个数
#include <stdio.h>
int main(void)
{
char str[] = "Hello World";
int letters = 0, spaces = 0, others = 0;
for (int i = 0; str[i] != '\0'; i++)
{
if ((str[i] >= 'a' && str[i] <= 'z') ||
(str[i] >= 'A' && str[i] <= 'Z'))
{
letters++;
}
else if (str[i] == ' ')
{
spaces++;
}
else
{
others++;
}
}
printf("字母: %d, 空格: %d, 其他: %d\n", letters, spaces, others);
return 0;
}
示例2: 字符串逆序
#include <stdio.h>
#include <string.h>
void reverseString(char str[])
{
int len = strlen(str);
for (int i = 0; i < len / 2; i++)
{
char temp = str[i];
str[i] = str[len - 1 - i];
str[len - 1 - i] = temp;
}
}
int main(void)
{
char str[] = "Hello";
printf("原字符串: %s\n", str);
reverseString(str);
printf("逆序后: %s\n", str);
return 0;
}
示例3: 回文字符串判断
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
bool isPalindrome(char str[])
{
int len = strlen(str);
for (int i = 0; i < len / 2; i++)
{
if (str[i] != str[len - 1 - i])
{
return false;
}
}
return true;
}
int main(void)
{
char str1[] = "abcba";
char str2[] = "hello";
printf("%s 是回文: %s\n", str1, isPalindrome(str1) ? "是" : "否");
printf("%s 是回文: %s\n", str2, isPalindrome(str2) ? "是" : "否");
return 0;
}
6.4 数组与内存
6.4.1 数组的存储
数组在内存中连续存储:
#include <stdio.h>
int main(void)
{
int arr[5] = {10, 20, 30, 40, 50};
printf("数组首地址: %p\n", (void*)arr);
for (int i = 0; i < 5; i++)
{
printf("arr[%d] 地址: %p, 值: %d\n",
i, (void*)&arr[i], arr[i]);
}
return 0;
}
输出示例:
数组首地址: 0x7ffeeb3c8a70
arr[0] 地址: 0x7ffeeb3c8a70, 值: 10
arr[1] 地址: 0x7ffeeb3c8a74, 值: 20
arr[2] 地址: 0x7ffeeb3c8a78, 值: 30
arr[3] 地址: 0x7ffeeb3c8a7c, 值: 40
arr[4] 地址: 0x7ffeeb3c8a80, 值: 50
⚠️ 注意: 相邻元素地址相差4字节(int的大小)
6.4.2 数组名
数组名是一个常量指针,指向数组首元素:
int arr[5] = {1, 2, 3, 4, 5};
arr // 数组首地址
&arr[0] // 第一个元素的地址(相同)
arr + 1 // 第二个元素的地址
&arr[1] // 第二个元素的地址(相同)
⚠️ 注意: 数组名是常量,不能修改:
int arr[5];
arr = arr + 1; // 错误!数组名不能被赋值
6.4.3 数组作为函数参数
传递数组时实际传递的是指针:
void func(int arr[], int n) // 等价于 void func(int *arr, int n)
{
// arr是指针,sizeof(arr)是指针的大小,不是数组大小!
printf("sizeof(arr) = %lu\n", sizeof(arr)); // 8 (64位系统)
}
int main(void)
{
int arr[5] = {1, 2, 3, 4, 5};
printf("sizeof(arr) = %lu\n", sizeof(arr)); // 20
func(arr, 5);
return 0;
}
⚠️ 重要: 数组作为函数参数时,必须同时传递数组大小!
正确的数组参数传递:
void printArray(int arr[], int n)
{
for (int i = 0; i < n; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
int main(void)
{
int arr[] = {1, 2, 3, 4, 5};
int n = sizeof(arr) / sizeof(arr[0]);
printArray(arr, n);
return 0;
}
6.5 实践练习
练习1: 冒泡排序
#include <stdio.h>
void bubbleSort(int arr[], int n)
{
for (int i = 0; i < n - 1; i++)
{
for (int j = 0; j < n - 1 - i; j++)
{
if (arr[j] > arr[j + 1])
{
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
void printArray(int arr[], int n)
{
for (int i = 0; i < n; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
int main(void)
{
int arr[] = {64, 34, 25, 12, 22, 11, 90};
int n = sizeof(arr) / sizeof(arr[0]);
printf("排序前: ");
printArray(arr, n);
bubbleSort(arr, n);
printf("排序后: ");
printArray(arr, n);
return 0;
}
练习2: 二分查找
#include <stdio.h>
int binarySearch(int arr[], int n, int target)
{
int left = 0, right = n - 1;
while (left <= right)
{
int mid = left + (right - left) / 2;
if (arr[mid] == target)
{
return mid;
}
else if (arr[mid] < target)
{
left = mid + 1;
}
else
{
right = mid - 1;
}
}
return -1; // 未找到
}
int main(void)
{
int arr[] = {2, 5, 8, 12, 16, 23, 38, 56, 72, 91};
int n = sizeof(arr) / sizeof(arr[0]);
int target = 23;
int index = binarySearch(arr, n, target);
if (index != -1)
{
printf("找到 %d 在索引 %d\n", target, index);
}
else
{
printf("未找到 %d\n", target);
}
return 0;
}
练习3: 杨辉三角
#include <stdio.h>
void printPascalTriangle(int n)
{
int triangle[n][n];
for (int i = 0; i < n; i++)
{
for (int j = 0; j <= i; j++)
{
if (j == 0 || j == i)
{
triangle[i][j] = 1;
}
else
{
triangle[i][j] = triangle[i-1][j-1] + triangle[i-1][j];
}
printf("%4d ", triangle[i][j]);
}
printf("\n");
}
}
int main(void)
{
int n = 10;
printf("杨辉三角(%d行):\n", n);
printPascalTriangle(n);
return 0;
}
6.6 常见错误
错误1: 数组越界
int arr[5];
arr[5] = 10; // 错误!索引范围是0-4
错误2: 数组大小使用变量(C89)
int n = 10;
int arr[n]; // C89错误,C99支持(变长数组VLA)
错误3: 数组赋值
int arr1[5] = {1, 2, 3, 4, 5};
int arr2[5];
arr2 = arr1; // 错误!数组不能直接赋值
// 正确方法:使用循环或memcpy
for (int i = 0; i < 5; i++)
{
arr2[i] = arr1[i];
}
错误4: 字符串缺少'\0'
char str[5] = {'H', 'e', 'l', 'l', 'o'}; // 没有'\0'
printf("%s\n", str); // 未定义行为!
错误5: 返回局部数组
int* func()
{
int arr[10];
return arr; // 错误!返回了局部变量的地址
}
6.7 小结
本章学习了:
一维数组的定义、初始化和访问 二维数组及其应用 字符数组与字符串处理 字符串处理函数(strlen, strcpy, strcat, strcmp等) 数组在内存中的存储方式 数组作为函数参数 常见数组算法(排序、查找等)
重点提示:
- 数组下标从0开始,注意越界问题
- C语言不检查数组越界,需要程序员自己保证
- 字符串必须以'\0'结尾
- 数组作为函数参数时退化为指针,需要传递大小
- 使用strncpy、strncat等安全函数,避免缓冲区溢出
- sizeof(数组名)在函数内外表现不同