C语言Array数组 - 完整指南与练习题

数组(Array)是C语言编程中的一个基本概念,它能够有效地存储和操作大量数据集合。本指南涵盖了你需要了解的所有关于C语言数组的知识。

1. 同类对象的线性集合

C语言中的数组是同类型变量的集合,在内存中连续存储。元素使用从零开始的索引访问(例如,大小为N的数组使用A[0]A[N-1])。

使用数组的关键点:

  • 使用#define来符号化设置数组大小,以提高可维护性
  • 过度声明数组大小(例如,分配10,000而不是100)通常是安全的,但会浪费内存
  • 访问数组边界之外是一个常见错误,在运行时通常无法检测到,会导致未定义行为

数组初始化

数组可以在声明时通过多种方式初始化:

c 复制代码
int arr[5] = {1, 2, 3, 4, 5}; // 完全初始化
int arr[5] = {1, 2};          // 部分初始化,其余设置为0
int arr[5] = {0};             // 所有元素设置为0
int arr[] = {1, 2, 3};        // 大小由初始化器决定

当数组未显式初始化时,它们包含不确定的值。在赋值前访问未初始化的数组可能导致未定义行为。

2. 读取数据到数组中

当向数组读取数据时,边界检查至关重要,以避免缓冲区溢出。使用"伙伴变量"跟踪已读取的元素数量,确保后续处理遵守数组容量。过多的输入应该明确处理,而不是静默丢弃。

代码示例:

c 复制代码
#include <stdio.h>
#define MAXVALS 100
int main() {
    int A[MAXVALS], next, n = 0, excess = 0;
    printf("最多输入 %d 个值,按^D结束\n", MAXVALS);
    while (scanf("%d", &next) == 1) {
        if (n == MAXVALS) {
            excess++;
        } else {
            A[n++] = next;
        }
    }
    printf("数组中读入了 %d 个值", n);
    if (excess) {
        printf(",丢弃了 %d 个多余的值", excess);
    }
    printf("\n");
    return 0;
}

附加概念:动态内存分配

为了灵活性,C语言支持使用mallocfree进行动态分配:

c 复制代码
int *arr = malloc(sizeof(int) * size);
if (arr == NULL) {
    // 处理分配失败
}
free(arr); // 释放内存

这允许数组在运行时增长或缩小,不像静态数组。

3. 数组排序

排序是数组的一个常见操作。这里是一个简单的冒泡排序示例:

c 复制代码
void bubbleSort(int arr[], int n) {
    for (int i = 0; i < n - 1; i++) {
        for (int j = 0; j < n - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
}

标准库排序

C语言标准库提供了qsort函数进行高效排序,它实现了快速排序算法。快速排序是一个高效的原地排序算法,使用分治策略,平均时间复杂度为O(n log n)。

c 复制代码
#include <stdlib.h>
int compare(const void *a, const void *b) {
    return (*(int *)a - *(int *)b);
}
qsort(arr, n, sizeof(int), compare);

qsort函数接受四个参数:

  1. 指向要排序数组的指针
  2. 数组中的元素数量
  3. 每个元素的字节大小
  4. 定义排序顺序的比较函数

比较函数必须返回:

  • 如果第一个元素应该在第二个元素之前,返回负值
  • 如果元素相等,返回零
  • 如果第一个元素应该在第二个元素之后,返回正值

qsort对大型数据集更有效,并且通过提供适当的比较函数,可以支持任何数据类型的排序。算法选择一个"轴心"元素,并根据其他元素是小于还是大于轴心将它们划分为两个子数组,然后递归地对子数组进行排序。

4. 函数与数组

在函数中,数组作为指针传递,允许修改原始数组。这种行为是C语言的基础,通过避免数组复制来实现内存使用效率。

向函数传递数组

向函数传递数组时,始终包含数组大小参数以确保边界安全:

c 复制代码
void processArray(int arr[], int size) {
    for (int i = 0; i < size; i++) {
        arr[i] *= 2; // 示例操作
    }
}

5. 二维数组

二维数组是数组的数组,通常用于表示矩阵、网格和表格:

c 复制代码
int matrix[3][3] = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

使用matrix[i][j]访问元素。内存仍然是连续的,采用行主序排列。

附加概念:动态二维数组

对于动态大小,分配二维数组:

c 复制代码
int **matrix = malloc(rows * sizeof(int *));
for (int i = 0; i < rows; i++) {
    matrix[i] = malloc(cols * sizeof(int));
}
// 释放内存
for (int i = 0; i < rows; i++) {
    free(matrix[i]);
}
free(matrix);

6. 字符串和字符串数组

C语言中的字符串是以null结尾的char数组。标准库提供了string.h函数如strcpystrcmp用于字符串操作。字符串可以存储在数组中,既可以是矩形矩阵(固定长度字符串)也可以是不规则数组(可变长度字符串)。

附加概念:字符串分词

用于解析,使用strtok分割字符串:

c 复制代码
#include <string.h>
char str[] = "苹果,香蕉,橘子";
char *token = strtok(str, ",");
while (token != NULL) {
    printf("%s\n", token);
    token = strtok(NULL, ",");
}

7. 指针和数组

在C语言中,数组和指针密切相关。数组名实际上是指向其第一个元素的常量指针。理解指针算术对于高效的数组操作至关重要。

附加概念:指针算术

通过指针访问数组元素:

c 复制代码
int arr[] = {1, 2, 3};
int *p = arr;
printf("%d\n", *(p + 1)); // 打印2

8. 程序参数

argc(参数计数)和argv(参数向量),其中argv[0]是程序名。使用atoi将字符串参数转换为整数。

附加概念:错误处理

验证参数以避免错误:

c 复制代码
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char *argv[]) {
    if (argc < 2) {
        fprintf(stderr, "用法: %s <数字>\n", argv[0]);
        return 1;
    }
    int n = atoi(argv[1]);
    printf("数字: %d\n", n);
    return 0;
}

9. 案例研究:不同单词

本案例研究演示了读取文本并列出不同单词,约束条件是每个单词最多10个字符,总共最多1,000个单词。实现使用ctype.h函数如isalpha和来自string.h的字符串操作函数,采用线性搜索算法。

使用哈希表优化性能

对于大型单词集,哈希表实现提供更快的查找:

c 复制代码
// 使用开放寻址的简化哈希表
#define TABLE_SIZE 1000
struct WordEntry {
    char word[11];
    int count;
};
struct WordEntry table[TABLE_SIZE];
// 实现哈希和插入函数

10. 练习和解答

练习1:阶乘循环

循环设置A[0] = 1,然后每个后续A[i] = i * A[i-1],计算阶乘:

  • A[0] = 1
  • A[1] = 1 * 1 = 1
  • A[2] = 2 * 1 = 2
  • A[3] = 3 * 2 = 6
  • 等等

练习2:质数

循环实现埃拉托斯特尼筛法来存储质数:

  • A[0] = 2开始,然后检查从3开始的数字
  • 如果一个数n不能被任何之前的质数整除,它就存储在A

练习5:字符串长度

c 复制代码
int strlen(char *s) {
    int len = 0;
    while (s[len] != '\0') {
        len++;
    }
    return len;
}

练习6:修改的getword

转换大写为小写并允许数字字符串:

c 复制代码
#include <ctype.h>
int getword(char *word, int max) {
    int c, i = 0;
    while ((c = getchar()) != EOF && i < max - 1) {
        if (isalpha(c)) {
            word[i++] = tolower(c);
        } else if (isdigit(c)) {
            word[i++] = c;
        } else {
            break;
        }
    }
    word[i] = '\0';
    return i > 0 ? i : EOF;
}

练习7:单词频率

跟踪并按频率排序:

c 复制代码
struct WordEntry {
    char word[11];
    int count;
};
struct WordEntry words[1000];
int n = 0;
// 在main中,在getword之后:
for (int i = 0; i < n; i++) {
    printf("%s: %d\n", words[i].word, words[i].count);
}
// 按频率排序
qsort(words, n, sizeof(struct WordEntry), compareByCount);

11. 附加概念:内存对齐

C语言中的数组在内存中对齐以实现高效访问。未对齐的访问可能在某些架构上导致性能问题或错误。使用alignas(C11)进行显式对齐:

c 复制代码
#include <stdalign.h>
alignas(16) int arr[100];

12. 结论

C语言中的数组是管理数据集合的强大工具,从简单的线性数组到复杂的字符串数组和程序参数。理解边界检查、指针算术和动态分配可以增强它们的实用性。结合数组与高级技术如qsort、动态内存和哈希表,开发者可以构建健壮和高效的C程序。如有问题、欢迎留言讨论。

相关推荐
dd向上3 小时前
聊聊GPIO 工作模式
c语言·c++·单片机·嵌入式硬件
威桑4 小时前
在 Windows 和 Linux 下使用 C/C++ 连接 MySQL 的详细指南
linux·c语言·windows
XINVRY-FPGA11 小时前
XCVU47P-2FSVH2892E Xilinx Virtex UltraScale+ FPGA AMD
c语言·c++·人工智能·嵌入式硬件·阿里云·fpga开发·fpga
W说编程14 小时前
算法导论第二十四章 深度学习前沿:从序列建模到创造式AI
c语言·人工智能·python·深度学习·算法·性能优化
A.A呐17 小时前
【Linux第四章】gcc、makefile、git、GDB
linux·c语言·开发语言·c++·git
C羊驼18 小时前
C语言:排序算法
c语言·算法·排序算法
凌肖战20 小时前
力扣网C语言编程题:多数元素
c语言·算法·leetcode
jz_ddk20 小时前
[学习] 哈希码:原理、用途与实现详解(C代码示例)
c语言·学习·算法·哈希算法
coderhuo21 小时前
浅析elf中的.bss和.data
c语言