HiHuo
首页
博客
手册
工具
关于
首页
博客
手册
工具
关于
  • C 语言入门教程

    • C语言系列教程
    • 第01章 基础概念与编译
    • 第02章 数据类型与变量
    • 第03章 运算符
    • 第04章 输入输出
    • 第05章 流程控制
    • 第06章 数组
    • 第07章 指针
    • 第08章 函数
    • 第09章 构造类型

第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等) 数组在内存中的存储方式 数组作为函数参数 常见数组算法(排序、查找等)

重点提示:

  1. 数组下标从0开始,注意越界问题
  2. C语言不检查数组越界,需要程序员自己保证
  3. 字符串必须以'\0'结尾
  4. 数组作为函数参数时退化为指针,需要传递大小
  5. 使用strncpy、strncat等安全函数,避免缓冲区溢出
  6. sizeof(数组名)在函数内外表现不同

上一章: 第05章 流程控制下一章: 第07章 指针

Prev
第05章 流程控制
Next
第07章 指针