引言:实验4的重要性与学习目标

C语言程序设计A实验4通常涉及数组和字符串的综合应用,这是C语言学习中的关键转折点。通过本实验,学生将从简单的变量操作过渡到复杂数据结构的处理,掌握数据批量处理的核心技能。周信东教授的实验设计注重理论与实践结合,旨在培养学生解决实际问题的能力。

实验4的核心目标包括:

  • 理解一维数组和二维数组的定义、初始化和引用方法
  • 掌握字符串与字符数组的区别与联系
  • 熟练运用数组作为函数参数进行数据传递
  • 培养调试数组越界等常见错误的实战能力

实验内容详解

一、一维数组的定义与初始化

一维数组是最基础的数据结构,用于存储相同类型的数据序列。定义格式为:类型说明符 数组名[常量表达式];

关键规则:

  1. 数组下标从0开始,最大下标为长度-1
  2. 数组名代表数组首元素的地址
  3. 定义时可以省略长度,但必须有初始化值

示例代码:

#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语言学习的重要里程碑,掌握数组和字符串操作将为后续学习数据结构和算法打下坚实基础。建议学生:

  1. 多动手实践:不要只看代码,要亲自编写、调试、运行
  2. 理解内存布局:数组在内存中的存储方式影响程序性能
  3. 培养安全意识:始终检查数组边界,防止越界访问
  4. 掌握调试技巧:使用调试器观察数组内容,理解程序执行流程
  5. 阅读优秀代码:学习开源项目中数组和字符串的使用方式

通过本实验的学习,你将具备处理批量数据和文本信息的能力,这是成为优秀程序员的必备技能。继续加油!