引言:实验4的重要性与学习目标
C语言程序设计A实验4通常涉及数组和字符串的综合应用,这是C语言学习中的关键转折点。通过本实验,学生将从简单的变量操作过渡到复杂数据结构的处理,掌握数据批量处理的核心技能。周信东教授的实验设计注重理论与实践结合,旨在培养学生解决实际问题的能力。
实验4的核心目标包括:
- 理解一维数组和二维数组的定义、初始化和引用方法
- 掌握字符串与字符数组的区别与联系
- 熟练运用数组作为函数参数进行数据传递
- 培养调试数组越界等常见错误的实战能力
实验内容详解
一、一维数组的定义与初始化
一维数组是最基础的数据结构,用于存储相同类型的数据序列。定义格式为:类型说明符 数组名[常量表达式];
关键规则:
- 数组下标从0开始,最大下标为
长度-1 - 数组名代表数组首元素的地址
- 定义时可以省略长度,但必须有初始化值
示例代码:
#include <stdio.h>
int main() {
// 正确定义数组的三种方式
int scores[5]; // 方式1:定义但不初始化
int grades[3] = {85, 90, 95}; // 方式2:定义时初始化
int values[] = {10, 20, 30, 40}; // 方式3:自动计算长度
// 错误示例:数组越界
// int arr[3] = {1,2,3,4}; // 错误:初始化值过多
// arr[3] = 100; // 错误:下标越界
// 正确使用数组
scores[0] = 98;
scores[1] = 87;
printf("第一个成绩:%d\n", scores[0]);
return 0;
}
二、二维数组与矩阵操作
二维数组常用于表示表格或矩阵数据,定义格式为:类型说明符 数组名[行数][列数];
内存布局理解: 二维数组在内存中按行连续存储,理解这一点对性能优化至关重要。
矩阵转置实战:
#include <stdio.h>
#define ROWS 3
#define COLS 3
void printMatrix(int matrix[ROWS][COLS]) {
for(int i = 0; i < ROWS; i++) {
for(int j = 0; j < COLS; j++) {
printf("%4d", matrix[i][j]);
}
printf("\n");
}
}
void transposeMatrix(int src[ROWS][COLS], int dest[COLS][ROWS]) {
for(int i = 0; i < ROWS; i++) {
for(int j = 0; j < COLS; j++) {
dest[j][i] = src[i][j];
}
}
}
int main() {
int matrix[ROWS][COLS] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
int transposed[COLS][ROWS];
printf("原始矩阵:\n");
printMatrix(matrix);
transposeMatrix(matrix, transposed);
printf("\n转置矩阵:\n");
printMatrix(transposed);
return 0;
}
三、字符串与字符数组
C语言中字符串以\0结尾的字符数组实现,这是理解C字符串的关键。
字符串定义的三种方式:
#include <stdio.h>
#include <string.h>
int main() {
// 方式1:字符数组(不自动添加\0)
char str1[6] = {'H', 'e', 'l', 'l', '0'};
// 方式2:字符串(自动添加\0)
char str2[] = "Hello"; // 长度为6,包含'\0'
// 方式3:指针方式(只读)
char *str3 = "World";
// 错误示例
// char str4[5] = "Hello"; // 错误:空间不足,需要6字节
// 正确操作
char buffer[20];
strcpy(buffer, "Hello");
strcat(buffer, " World");
printf("%s\n", buffer); // 输出:Hello World
return 0;
}
四、数组作为函数参数
数组作为函数参数时实际传递的是首地址,这是C语言高效处理大数据的关键。
实战:统计数组元素频次
#include <stdio.h>
#define MAX_SIZE 100
// 函数原型:数组作为参数
void countFrequencies(int arr[], int size, int freq[], int maxVal);
int main() {
int data[] = {2, 5, 3, 2, 5, 7, 3, 2, 8, 5};
int size = sizeof(data) / sizeof(data[0]);
int maxVal = 10; // 假设最大值不超过10
int frequencies[11] = {0}; // 0-10的频次统计
countFrequencies(data, size, frequencies, maxVal);
printf("元素频次统计:\n");
for(int i = 0; i <= maxVal; i++) {
if(frequencies[i] > 0) {
printf("%d 出现了 %d 次\n", i, frequencies[i]);
}
}
return 0;
}
void countFrequencies(int arr[], int size, int freq[], int maxVal) {
for(int i = 0; i < size; i++) {
if(arr[i] >= 0 && arr[i] <= maxVal) {
freq[arr[i]]++;
}
}
}
五、综合应用:学生成绩管理系统
这是一个完整的实验4综合案例,涵盖数组、字符串和函数的综合应用。
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#define MAX_STUDENTS 50
#define NAME_LEN 20
typedef struct {
char name[NAME_LEN];
int id;
int score;
} Student;
// 函数声明
void inputStudents(Student students[], int *count);
void printStudents(const Student students[], int count);
void sortStudents(Student students[], int count);
void searchStudent(const Student students[], int count, int id);
void calculateAverage(const Student students[], int count);
int main() {
Student students[MAX_STUDENTS];
int count = 0;
int choice;
while(1) {
printf("\n=== 学生成绩管理系统 ===\n");
printf("1. 输入学生信息\n");
printf("2. 显示所有学生\n");
printf("3. 按成绩排序\n");
printf("4. 按学号查询\n");
printf("5. 计算平均分\n");
printf("0. 退出\n");
printf("请选择:");
scanf("%d", &choice);
switch(choice) {
case 1:
inputStudents(students, &count);
break;
case 2:
printStudents(students, count);
break;
case 3:
sortStudents(students, count);
break;
case 4:
int id;
printf("输入学号:");
scanf("%d", &id);
searchStudent(students, count, id);
break;
case 5:
calculateAverage(students, count);
break;
case 0:
return 0;
default:
printf("无效选择!\n");
}
}
return 0;
}
void inputStudents(Student students[], int *count) {
if(*count >= MAX_STUDENTS) {
printf("学生数量已达上限!\n");
return;
}
printf("输入姓名(不超过%d字符):", NAME_LEN-1);
scanf("%s", students[*count].name);
printf("输入学号:");
scanf("%d", &students[*count].id);
printf("输入成绩:");
scanf("%d", &students[*count].score);
(*count)++;
printf("学生信息已保存!\n");
}
void printStudents(const Student students[], int count) {
if(count == 0) {
printf("没有学生信息!\n");
return;
}
printf("\n%-20s %-10s %-5s\n", "姓名", "学号", "成绩");
printf("=====================================\n");
for(int i = 0; i < count; i++) {
printf("%-20s %-10d %-5d\n",
students[i].name, students[i].id, students[i].score);
}
}
void sortStudents(Student students[], int count) {
// 使用冒泡排序按成绩降序
for(int i = 0; i < count - 1; i++) {
for(int j = 0; j < count - 1 - i; j++) {
if(students[j].score < students[j+1].score) {
// 交换整个结构体
Student temp = students[j];
students[j] = students[j+1];
students[j+1] = temp;
}
}
}
printf("排序完成!\n");
}
void searchStudent(const Student students[], int count, int id) {
for(int i = 0; i < count; i++) {
if(students[i].id == id) {
printf("\n找到学生:\n");
printf("姓名:%s\n", students[i].name);
printf("学号:%d\n", students[i].id);
printf("成绩:%d\n", students[i].score);
return;
}
}
printf("未找到学号为%d的学生!\n", id);
}
void calculateAverage(const Student students[], int count) {
if(count == 0) {
printf("没有学生信息!\n");
return;
}
int sum = 0;
for(int i = 0; i < count; i++) {
sum += students[i].score;
}
printf("学生总数:%d\n", count);
printf("总成绩:%d\n", sum);
printf("平均分:%.2f\n", (float)sum / count);
}
实战技巧分享
技巧1:数组越界检测
问题场景: 数组越界是C语言中最危险的错误,可能导致程序崩溃或安全漏洞。
解决方案:
// 安全的数组访问函数
int safeArrayAccess(int arr[], int size, int index) {
if(index < 0 || index >= size) {
fprintf(stderr, "错误:数组越界访问!索引%d,有效范围0-%d\n", index, size-1);
return -1; // 返回错误码
}
return arr[index];
}
// 使用示例
void testSafeAccess() {
int data[] = {10, 20, 30, 40, 50};
int size = sizeof(data) / sizeof(data[0]);
// 正确访问
printf("data[2] = %d\n", safeArrayAccess(data, size, 2));
// 错误访问(会被检测)
printf("data[10] = %d\n", safeArrayAccess(data, size, 10));
}
技巧2:字符串输入的安全处理
问题场景:
使用scanf("%s", str)可能导致缓冲区溢出。
解决方案:
#include <stdio.h>
#include <string.h>
// 安全的字符串输入函数
void safeStringInput(char *buffer, int maxSize) {
// 使用fgets读取一行,防止溢出
if(fgets(buffer, maxSize, stdin) != NULL) {
// 移除换行符
size_t len = strlen(buffer);
if(len > 0 && buffer[len-1] == '\n') {
buffer[len-1] = '\0';
}
}
}
// 使用示例
void testSafeStringInput() {
char name[10];
printf("输入姓名(最多9字符):");
safeStringInput(name, sizeof(name));
printf("您输入的是:%s\n", name);
}
技巧3:二维数组的动态内存分配
问题场景: 当数组大小在运行时才能确定时,需要使用动态内存分配。
解决方案:
#include <stdio.h>
#include <stdlib.h>
// 动态创建二维数组
int** createDynamicArray(int rows, int cols) {
// 分配行指针数组
int **arr = (int**)malloc(rows * sizeof(int*));
if(arr == NULL) {
fprintf(stderr, "内存分配失败!\n");
return NULL;
}
// 为每行分配列
for(int i = 0; i < rows; i++) {
arr[i] = (int*)malloc(cols * sizeof(int));
if(arr[i] == NULL) {
fprintf(stderr, "内存分配失败!\n");
// 释放已分配的内存
for(int j = 0; j < i; j++) {
free(arr[j]);
}
free(arr);
return NULL;
}
}
return arr;
}
// 释放动态数组
void freeDynamicArray(int **arr, int rows) {
if(arr == NULL) return;
for(int i = 0; i < rows; i++) {
free(arr[i]);
}
free(arr);
}
// 使用示例
void testDynamicArray() {
int rows = 3, cols = 4;
int **matrix = createDynamicArray(rows, cols);
if(matrix != NULL) {
// 初始化
for(int i = 0; i < rows; i++) {
for(int j = 0; C++ j < cols; j++) {
matrix[i][j] = i * cols + j;
}
}
// 打印
for(int i = 0; i < rows; i++) {
for(int j = 0; j < cols; j++) {
printf("%4d", matrix[i][j]);
}
printf("\n");
}
freeDynamicArray(matrix, rows);
}
}
技巧4:数组元素的批量操作
问题场景: 需要对数组进行查找、替换、过滤等批量操作。
解决方案:
#include <stdio.h>
// 查找数组中的最大值
int findMax(int arr[], int size) {
if(size <= 0) return 0;
int max = arr[0];
for(int i = 1; i < size; i++) {
if(arr[i] > max) {
max = arr[i];
}
}
return max;
}
// 查找数组中的最小值
int findMin(int arr[], int size) {
if(size <= 0) return 0;
int min = arr[0];
for(int i = 1; i < size; i++) {
if(arr[i] < min) {
min = arr[i];
}
}
return min;
}
// 查找特定元素的索引
int findIndex(int arr[], int size, int target) {
for(int i = 0; i < size; i++) {
if(arr[i] == target) {
return i;
}
}
return -1; // 未找到
}
// 统计满足条件的元素个数
int countIf(int arr[], int size, int threshold) {
int count = 0;
for(int i = 0; i < size; i++) {
if(arr[i] > threshold) {
count++;
}
}
return count;
}
// 使用示例
void testArrayOperations() {
int data[] = {3, 7, 2, 9, 5, 8, 1};
int size = sizeof(data) / sizeof(data[0]);
printf("最大值:%d\n", findMax(data, size));
printf("最小值:%d\n", findMin(data, size));
printf("9的索引:%d\n", findIndex(data, size, 9));
printf("大于5的元素个数:%d\n", countIf(data, size, 5));
}
技巧5:字符串处理的高级技巧
问题场景: 需要实现字符串分割、大小写转换、去除空格等复杂操作。
解决方案:
#include <stdio.h>
#include <string.h>
#include <ctype.h>
// 字符串大小写转换
void toUpperCase(char *str) {
for(int i = 0; str[i]; i++) {
str[i] = toupper(str[i]);
}
}
void toLowerCase(char *str) {
for(int i = 0; str[i]; i++) {
str[i] = tolower(str[i]);
}
}
// 去除字符串首尾空格
void trim(char *str) {
// 去除头部空格
int start = 0;
while(isspace(str[start])) start++;
// 去除尾部空格
int end = strlen(str) - 1;
while(end >= start && isspace(str[end])) end--;
// 移动并添加结束符
int i, j = 0;
for(i = start; i <= end; i++) {
str[j++] = str[i];
}
str[j] = '\0';
}
// 字符串分割(使用静态缓冲区)
char* myStrtok(char *str, const char *delim) {
static char *saved = NULL;
if(str != NULL) saved = str;
if(saved == NULL) return NULL;
char *token = saved;
while(*saved) {
for(const char *d = delim; *d; d++) {
if(*saved == *d) {
*saved = '\0';
saved++;
return token;
}
}
saved++;
}
if(*token == '\0') return NULL;
return token;
}
// 使用示例
void testStringOperations() {
char str1[] = " Hello World ";
char str2[] = "apple,banana,cherry";
printf("原始:'%s'\n", str1);
trim(str1);
printf("去除空格:'%s'\n", str1);
toUpperCase(str1);
printf("大写:'%s'\n", str1);
printf("\n分割字符串:\n");
char *token = myStrtok(str2, ",");
while(token != NULL) {
printf("'%s'\n", token);
token = myStrtok(NULL, ",");
}
}
常见错误与调试方法
错误1:数组长度计算错误
错误示例:
int arr[10];
int size = sizeof(arr); // 错误:得到的是字节数40,不是元素个数
正确方法:
int arr[10];
int size = sizeof(arr) / sizeof(arr[0]); // 正确:得到10
错误2:字符串缺少结束符
错误示例:
char str[5] = {'H','e','l','l','o'}; // 没有'\0',不是有效字符串
printf("%s", str); // 危险:可能继续读取内存直到遇到'\0'
正确方法:
char str[6] = {'H','e','l','l','o','\0'}; // 显式添加
// 或
char str[] = "Hello"; // 自动添加'\0'
错误3:二维数组作为函数参数
错误示例:
void func(int arr[][], int rows, int cols); // 错误:必须指定列数
正确方法:
void func(int arr[][10], int rows); // 正确:列数必须明确
// 或
void func(int (*arr)[10], int rows); // 正确:指针方式
// 或
void func(int **arr, int rows, int cols); // 动态数组
错误4:数组下标越界
错误示例:
int arr[5] = {1,2,3,4,5};
for(int i = 0; i <= 5; i++) { // 错误:i=5时越界
printf("%d ", arr[i]);
}
正确方法:
for(int i = 0; i < 5; i++) { // 正确:i<5
printf("%d ", arr[i]);
}
错误5:字符串函数使用不当
错误示例:
char str[5];
strcpy(str, "Hello"); // 错误:空间不足
正确方法:
char str[6];
strcpy(str, "Hello"); // 正确:足够空间
// 或
strncpy(str, "Hello", sizeof(str)-1); // 安全方式
str[sizeof(str)-1] = '\0'; // 确保结束符
实验4的扩展应用
扩展1:数组在游戏开发中的应用
井字棋游戏:
#include <stdio.h>
#include <stdbool.h>
#define SIZE 3
void initBoard(char board[SIZE][SIZE]) {
for(int i = 0; i < SIZE; i++) {
for(int j = 0; j < SIZE; j++) {
board[i][j] = ' ';
}
}
}
void printBoard(char board[SIZE][SIZE]) {
printf("\n 0 1 2\n");
for(int i = 0; i < SIZE; i++) {
printf("%d ", i);
for(int j = 0; j < SIZE; j++) {
printf("%c", board[i][j]);
if(j < SIZE-1) printf("|");
}
printf("\n");
if(i < SIZE-1) printf(" -+-+-\n");
}
}
bool checkWin(char board[SIZE][SIZE], char player) {
// 检查行
for(int i = 0; i < SIZE; i++) {
if(board[i][0] == player && board[i][1] == player && board[i][2] == player)
return true;
}
// 检查列
for(int j = 0; j < SIZE; j++) {
if(board[0][j] == player && board[1][j] == player && board[2][j] == player)
return true;
}
// 检查对角线
if(board[0][0] == player && board[1][1] == player && board[2][2] == player)
return true;
if(board[0][2] == player && board[1][1] == player && board[2][0] == player)
return true;
return false;
}
void ticTacToe() {
char board[SIZE][SIZE];
char currentPlayer = 'X';
int row, col;
int moves = 0;
initBoard(board);
while(moves < SIZE * SIZE) {
printBoard(board);
printf("\n玩家 %c 的回合(输入行和列,用空格分隔):", currentPlayer);
scanf("%d %d", &row, &col);
if(row < 0 || row >= SIZE || col < 0 || col >= SIZE || board[row][col] != ' ') {
printf("无效移动!请重试。\n");
continue;
}
board[row][col] = currentPlayer;
moves++;
if(checkWin(board, currentPlayer)) {
printBoard(board);
printf("\n玩家 %c 获胜!\n", currentPlayer);
return;
}
currentPlayer = (currentPlayer == 'X') ? 'O' : 'X';
}
printBoard(board);
printf("\n平局!\n");
}
扩展2:数组在数据处理中的应用
学生成绩统计分析:
#include <stdio.h>
#include <math.h>
#define MAX_STUDENTS 100
// 成绩统计结构
typedef struct {
int count;
int sum;
int max;
int min;
double mean;
double stdDev;
} Stats;
// 计算统计信息
void calculateStats(int scores[], int size, Stats *stats) {
if(size == 0) return;
stats->count = size;
stats->sum = 0;
stats->max = scores[0];
stats->min = scores[0];
// 计算总和、最大值、最小值
for(int i = 0; i < size; i++) {
stats->sum += scores[i];
if(scores[i] > stats->max) stats->max = scores[i];
if(scores[i] < stats->min) stats->min = scores[i];
}
// 计算平均值
stats->mean = (double)stats->sum / size;
// 计算标准差
double variance = 0;
for(int i = 0; i < size; i++) {
variance += pow(scores[i] - stats->mean, 2);
}
variance /= size;
stats->stdDev = sqrt(variance);
}
// 打印统计信息
void printStats(const Stats *stats) {
printf("\n=== 成绩统计分析 ===\n");
printf("学生人数:%d\n", stats->count);
printf("总分:%d\n", stats->sum);
printf("最高分:%d\n", stats->max);
printf("最低分:%d\n", stats->min);
printf("平均分:%.2f\n", stats->mean);
printf("标准差:%.2f\n", stats->stdDev);
// 成绩分布
printf("成绩分布:\n");
printf(" 优秀(90-100):%d人\n", stats->count);
printf(" 良好(80-89):%d人\n", stats->count);
printf(" 中等(70-79):%d人\n", stats->count);
printf(" 及格(60-69):%d人\n", stats->count);
printf(" 不及格(<60):%d人\n", stats->count);
}
void testStats() {
int scores[] = {85, 92, 78, 95, 88, 76, 90, 84, 91, 87};
int size = sizeof(scores) / sizeof(scores[0]);
Stats stats;
calculateStats(scores, size, &stats);
printStats(&stats);
}
总结与建议
实验4是C语言学习的重要里程碑,掌握数组和字符串操作将为后续学习数据结构和算法打下坚实基础。建议学生:
- 多动手实践:不要只看代码,要亲自编写、调试、运行
- 理解内存布局:数组在内存中的存储方式影响程序性能
- 培养安全意识:始终检查数组边界,防止越界访问
- 掌握调试技巧:使用调试器观察数组内容,理解程序执行流程
- 阅读优秀代码:学习开源项目中数组和字符串的使用方式
通过本实验的学习,你将具备处理批量数据和文本信息的能力,这是成为优秀程序员的必备技能。继续加油!
