C语言笔记归纳4:数组

数组

目录

数组

一、先搞懂:数组到底是什么?(3 个核心特点)

数组的分类(新手重点掌握前两个)

二、一维数组:基础中的基础(必会!)

2.1 一维数组的创建:定义 “货架规格”

语法格式(直接套用)

示例 + 避坑

关键规则

2.2 一维数组的初始化:给 “货架装东西”

(1)不完全初始化(部分赋值,剩余默认 0)

(2)完全初始化(所有格子都赋值)

(3)省略格子数(仅初始化时可用)

2.3 一维数组的使用:访问 “货架格子”

(1)单个元素访问

(2)批量输入输出(循环遍历)

2.4 一维数组在内存中的存储(可视化)

代码验证(打印地址)

输出结果(示例)

可视化图示(货架模型)

三、二维数组:多行多列的 “货架”

3.1 二维数组的创建:定义 “多层货架规格”

语法格式

示例 + 避坑

3.2 二维数组的初始化:给 “多层货架装东西”

(1)不完全初始化(剩余格子默认 0)

(2)完全初始化(按行顺序赋值)

(3)按行初始化(逻辑清晰,推荐!)

(4)省略行数(仅初始化时可用)

3.3 二维数组的使用:双循环遍历 “多层货架”

输入 + 输出(按行 / 按列)

3.4 二维数组在内存中的存储(关键!)

代码验证(打印地址)

输出结果(示例)

可视化图示

四、实战练习:数组的两个经典应用(必练!)

4.1 练习 1:字符从两端向中间汇聚(动态效果)

需求:一个字符串(如 "welcome to bit")从两端逐步向中间显示,带动态效果。

完整代码(可直接复制运行)

核心思路:双指针法

4.2 练习 2:二分查找(有序数组高效查找)

需求:在有序数组中查找目标值(如 1~10 中的 7),返回其下标(效率远高于逐个遍历)。

完整代码(修正原笔记笔误)

核心思路:折半缩小范围

优势:效率极高!100 万个元素最多查找 20 次(时间复杂度 O (log₂n))。

五、补充知识点:数组的高频考点(新手必记)

5.1 数组名的特殊含义(面试常考!)

代码验证

5.2 strlen 与 sizeof 的区别(字符串数组常用)

5.3 数组作为函数参数(进阶必备)

六、新手必避的 6 个坑(红标警告!)

📝 总结


✨ 引言:

如果说变量是 C 语言的 “单个储物盒”,那数组就是 “一排同款储物格”—— 能整齐存放多个同类型数据(比如 10 个成绩、5 个字符),是处理批量数据的 “神器”。它不仅是指针、结构体的学习基础,更是实战中高频使用的核心知识点(比如排序、查找、数据统计)。

一、先搞懂:数组到底是什么?(3 个核心特点)

数组本质是 **“相同类型元素的有序集合”**,可以用 “超市货架” 来理解:

  • 📦 所有 “货架格子”(元素)类型一致(比如都是 int 型整数、char 型字符);
  • 📏 格子数量固定(创建时指定,普通数组不能改,C99 变长数组除外);
  • 📌 每个格子有唯一 “编号”(下标),从 0 开始,方便快速找到对应元素。

数组的分类(新手重点掌握前两个)

  • 一维数组:单行货架(int arr[10])→ 日常最常用;
  • 二维数组:多行多列货架(int arr[3][5])→ 处理表格类数据;
  • 多维数组:三维及以上(如int arr[2][3][4])→ 极少用,新手暂不深究。

二、一维数组:基础中的基础(必会!)

一维数组是数组的 “入门形态”,核心掌握 “创建 - 初始化 - 使用 - 存储” 四个环节,步骤清晰无压力~

2.1 一维数组的创建:定义 “货架规格”

语法格式(直接套用)
数据类型 数组名[常量值]; // 常量值=货架格子数(元素个数)
示例 + 避坑
#include <stdio.h>
int main()
{
    int score[10];    // 正确:10个int型格子(存10个成绩,40字节)
    char name[5];     // 正确:5个char型格子(存4个字符+1个'\0',5字节)
    double height[8]; // 正确:8个double型格子(存8个身高,64字节)
    
    // 以下都是错误写法,新手直接避开!
    // int err1[];    // 错误:未初始化时,必须指定格子数
    // int err2[-3];  // 错误:格子数不能为负数
    // int err3[3.5]; // 错误:格子数必须是整数常量(如10、3+5)
    return 0;
}
关键规则
  • 数组名命名:和变量一致(字母、数字、下划线,不重复关键字,区分大小写);
  • 格子数要求:C99 之前必须是 “常量”(如 10、5*2),不能用变量(如int n=5; int arr[n];)。

2.2 一维数组的初始化:给 “货架装东西”

初始化是 “创建时给格子赋值”,避免格子里存 “垃圾数据”,分 3 种场景,新手优先掌握前两种:

(1)不完全初始化(部分赋值,剩余默认 0)
int main()
{
    int arr1[10] = {0};    // 推荐!第一个格子=0,其余9个默认=0
    int arr2[10] = {1,2,3};// 前3个格子=1、2、3,后7个默认=0
    char ch[5] = {'a','b'};// 前2个格子='a'、'b',后3个默认=0('\0')
    return 0;
}

✅ 实用场景:初始化所有元素为 0(直接写= {0}),不用逐个赋值。

(2)完全初始化(所有格子都赋值)
int main()
{
    int arr3[5] = {1,2,3,4,5};// 5个格子依次赋值1~5
    // int arr4[5] = {1,2,3,4,5,6};// 错误:赋值个数>格子数,超出范围
    return 0;
}
(3)省略格子数(仅初始化时可用)
int main()
{
    // 编译器自动根据赋值个数算格子数(赋值个数=格子数)
    int arr5[] = {1,2,3,4};// 正确:格子数=4(等价于int arr5[4])
    char ch2[] = "abc";    // 特殊:字符串初始化,自动加'\0',格子数=4(a、b、c、'\0')
    return 0;
}

💡 小技巧:不确定数组大小时,用这种方式最方便(比如存已知的几个固定值)。

2.3 一维数组的使用:访问 “货架格子”

数组通过 “下标” 访问元素,下标从 0 开始(第一个格子下标 = 0,最后一个 = 格子数 - 1),核心是[](下标引用操作符)。

(1)单个元素访问
int main()
{
    int arr[] = {10,20,30,40,50};// 下标0~4
    printf("%d\n", arr[0]);// 输出10(第一个格子)
    printf("%d\n", arr[2]);// 输出30(第三个格子)
    printf("%d\n", arr[4]);// 输出50(最后一个格子)
    // printf("%d\n", arr[5]);// 错误:下标越界(最大下标4),结果乱码/程序崩溃
    return 0;
}

⚠️ 敲黑板:下标越界是新手高频错!编译器不报错,但会访问非法内存,导致程序异常。

(2)批量输入输出(循环遍历)

批量处理数据时,用for循环遍历所有格子,搭配sizeof计算格子数(通用写法,推荐!):

int main()
{
    int arr[10] = {0};// 初始化所有格子为0
    int i = 0;
    // 计算格子数:数组总大小 / 单个元素大小(通用公式,改数组大小不用改这里)
    int sz = sizeof(arr) / sizeof(arr[0]);
    
    // 输入:给每个格子赋值
    printf("请输入10个整数:\n");
    for (i = 0; i < sz; i++)
    {
        scanf("%d", &arr[i]);// &取地址:arr[i]是单个元素,需传地址
    }
    
    // 输出:打印所有格子内容
    printf("你输入的数组是:\n");
    for (i = 0; i < sz; i++)
    {
        printf("%d ", arr[i]);
    }
    return 0;
}

2.4 一维数组在内存中的存储(可视化)

数组元素在内存中是连续存放的,下标增长时,地址从低到高递增(每个格子占的字节数 = 数据类型长度)。

代码验证(打印地址)
int main()
{
    int arr[] = {1,2,3,4,5};// int占4字节
    int i = 0;
    int sz = sizeof(arr) / sizeof(arr[0]);
    for (i = 0; i < sz; i++)
    {
        printf("arr[%d] = %d, 地址 = %p\n", i, arr[i], &arr[i]);
    }
    return 0;
}
输出结果(示例)
arr[0] = 1, 地址 = 00BFFD70
arr[1] = 2, 地址 = 00BFFD74  // 比前一个地址+4(int占4字节)
arr[2] = 3, 地址 = 00BFFD78  // 连续递增,没有空隙
arr[3] = 4, 地址 = 00BFFD7C
arr[4] = 5, 地址 = 00BFFD80
可视化图示(货架模型)
地址:低 → 高
货架格子:[1] [2] [3] [4] [5]
下标:   0   1   2   3   4

✅ 核心结论:连续存储是数组的关键特性,也是后续排序、查找的基础。

三、二维数组:多行多列的 “货架”

二维数组可以理解为 “把多个一维数组叠成多层货架”,比如int arr[3][5]就是 “3 层货架,每层 5 个格子”,适合处理表格类数据(如 3 个班级,每个班级 5 个学生成绩)。

3.1 二维数组的创建:定义 “多层货架规格”

语法格式
数据类型 数组名[行数(常量)][列数(常量)];
示例 + 避坑
int main()
{
    int class_score[3][5];// 正确:3行5列,15个int型格子(存3个班级成绩)
    char name[2][6];      // 正确:2行6列,12个char型格子(存2个名字)
    
    // 以下是错误写法,新手避开!
    // int err1[][];    // 错误:行数和列数不能都省略
    // int err2[3][];   // 错误:列数不能省略(编译器需知道每层有多少格子)
    // int err3[3.5][2];// 错误:行数/列数必须是整数常量
    return 0;
}

3.2 二维数组的初始化:给 “多层货架装东西”

分 3 种方式,按行初始化最清晰,新手优先掌握:

(1)不完全初始化(剩余格子默认 0)
int main()
{
    int arr1[3][5] = {1,2,3};// 按行顺序赋值,前3个=1、2、3,其余12个=0
    int arr2[3][5] = {0};    // 推荐!所有格子默认=0
    return 0;
}
(2)完全初始化(按行顺序赋值)
int main()
{
    // 3行5列,按“第一行→第二行→第三行”顺序赋值
    int arr3[3][5] = {1,2,3,4,5, 2,3,4,5,6, 3,4,5,6,7};
    // 第一行:1,2,3,4,5;第二行:2,3,4,5,6;第三行:3,4,5,6,7
    return 0;
}
(3)按行初始化(逻辑清晰,推荐!)

用大括号分组,每组对应一行,一眼能看出每行的内容:

int main()
{
    int arr4[3][5] = { {1,2}, {3,4}, {5,6} };
    // 第一行:1,2,0,0,0;第二行:3,4,0,0,0;第三行:5,6,0,0,0
    return 0;
}
(4)省略行数(仅初始化时可用)

编译器会根据 “列数” 和 “赋值个数” 自动算行数:

int main()
{
    int arr5[][5] = {1,2,3,4,5,6};// 列数=5,赋值6个元素→行数=2(5+1)
    // 第一行:1,2,3,4,5;第二行:6,0,0,0,0
    
    int arr6[][5] = { {1,2}, {3,4}, {5,6} };// 3组→行数=3
    return 0;
}

3.3 二维数组的使用:双循环遍历 “多层货架”

二维数组需要 “外层循环控制行,内层循环控制列”,才能访问到每个格子:

输入 + 输出(按行 / 按列)
int main()
{
    int arr[3][5] = {0};
    int i = 0;// 行下标(0~2)
    int j = 0;// 列下标(0~4)
    
    // 输入:按行赋值
    printf("请输入3行5列的整数:\n");
    for (i = 0; i < 3; i++)
    {
        for (j = 0; j < 5; j++)
        {
            scanf("%d", &arr[i][j]);// &取地址:单个元素需传地址
        }
    }
    
    // 输出:按行打印(常用)
    printf("按行打印:\n");
    for (i = 0; i < 3; i++)
    {
        for (j = 0; j < 5; j++)
        {
            printf("%d ", arr[i][j]);
        }
        printf("\n");// 每行结束换行,排版整齐
    }
    
    // 输出:按列打印(特殊场景用)
    printf("按列打印:\n");
    for (j = 0; j < 5; j++)// 列下标先循环
    {
        for (i = 0; i < 3; i++)// 行下标后循环
        {
            printf("%d ", arr[i][j]);
        }
        printf("\n");
    }
    return 0;
}

3.4 二维数组在内存中的存储(关键!)

很多新手以为二维数组是 “按行分开存储”,其实它和一维数组一样 ——所有元素连续存放,顺序是 “第一行所有元素→第二行所有元素→第三行所有元素”。

代码验证(打印地址)
int main()
{
    int arr[3][5] = {0};
    int i = 0, j = 0;
    for (i = 0; i < 3; i++)
    {
        for (j = 0; j < 5; j++)
        {
            printf("arr[%d][%d] = %p\n", i, j, &arr[i][j]);
        }
        printf("\n");// 换行方便观察
    }
    return 0;
}
输出结果(示例)
arr[0][0] = 00BFFD50
arr[0][1] = 00BFFD54  // +4字节(int)
arr[0][2] = 00BFFD58  // 连续递增
arr[0][3] = 00BFFD5C
arr[0][4] = 00BFFD60

arr[1][0] = 00BFFD64  // 紧跟第一行最后一个元素(无空隙)
arr[1][1] = 00BFFD68
...
arr[2][0] = 00BFFD78  // 紧跟第二行最后一个元素
可视化图示
地址:低 → 高
元素:[0][0] [0][1] [0][2] [0][3] [0][4] [1][0] [1][1] ... [2][4]

✅ 核心结论:二维数组本质是 “伪装成多行的一维数组”,连续存储的特性是后续指针操作的基础。

四、实战练习:数组的两个经典应用(必练!)

光看语法不够,动手练两个实战案例,才能真正掌握数组的用法~

4.1 练习 1:字符从两端向中间汇聚(动态效果)

需求:一个字符串(如 "welcome to bit")从两端逐步向中间显示,带动态效果。
完整代码(可直接复制运行)
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
#include <windows.h>
#include <stdlib.h>

int main()
{
    char target[] = "welcome to bit!!!!!!";// 目标字符串(含'\0')
    char mask[] = "********************";// 占位符字符串(长度和target一致)
    
    int left = 0;                          // 左指针(从最左边开始)
    int right = strlen(target) - 1;         // 右指针(从最右边开始,减1是因为strlen不含'\0')
    
    while (left <= right)
    {
        mask[left] = target[left];  // 左指针位置替换为目标字符
        mask[right] = target[right];// 右指针位置替换为目标字符
        
        printf("%s\n", mask);          // 打印当前效果
        Sleep(300);                   // 暂停300毫秒(让效果可见,头文件windows.h)
        system("cls");                // 清空屏幕(头文件stdlib.h)
        
        left++;  // 左指针右移
        right--; // 右指针左移
    }
    printf("%s\n", mask); // 最后显示完整字符串(避免清空后看不到)
    return 0;
}
核心思路:双指针法
  • 左指针left从 0 开始,右指针right从字符串末尾开始;
  • 每次循环替换两个指针位置的字符,然后指针向中间移动;
  • Sleepsystem("cls")实现动态效果。

4.2 练习 2:二分查找(有序数组高效查找)

需求:在有序数组中查找目标值(如 1~10 中的 7),返回其下标(效率远高于逐个遍历)。
完整代码(修正原笔记笔误)
#include <stdio.h>
int main()
{
    int arr[] = {1,2,3,4,5,6,7,8,9,10};// 前提:数组必须有序(升序/降序)
    int target = 7;                      // 要查找的目标值
    int left = 0;                       // 左边界下标
    int right = sizeof(arr)/sizeof(arr[0]) - 1; // 右边界下标
    int find_flag = 0;                   // 标记是否找到(0=未找到,1=找到)
    
    while (left <= right)
    {
        // 计算中间下标(避免left+right溢出,比(left+right)/2更安全)
        int mid = left + (right - left) / 2; 
        
        if (arr[mid] < target)
        {
            left = mid + 1; // 目标值在右半部分,左边界右移
        }
        else if (arr[mid] > target)
        {
            right = mid - 1; // 目标值在左半部分,右边界左移
        }
        else
        {
            find_flag = 1;
            printf("找到了!目标值%d的下标是:%d\n", target, mid);
            break; // 找到后退出循环
        }
    }
    
    if (find_flag == 0)
    {
        printf("未找到目标值%d\n", target);
    }
    return 0;
}
核心思路:折半缩小范围
  1. 每次取数组中间元素mid,与目标值比较;
  2. 目标值大→缩小到右半部分,目标值小→缩小到左半部分;
  3. 重复步骤,直到找到目标值或边界交叉(未找到)。
优势:效率极高!100 万个元素最多查找 20 次(时间复杂度 O (log₂n))。

五、补充知识点:数组的高频考点(新手必记)

5.1 数组名的特殊含义(面试常考!)

数组名大部分时候是 “数组首元素的地址”,但有两个例外:

  1. sizeof(数组名):数组名代表整个数组,计算的是数组总大小(如int arr[10]; sizeof(arr)=40);
  2. &数组名:数组名代表整个数组,取的是整个数组的地址(类型是int(*)[10],不是普通指针)。
代码验证
int main()
{
    int arr[5] = {1,2,3,4,5};
    printf("arr = %p\n", arr);      // 首元素地址(00BFFD70)
    printf("&arr[0] = %p\n", &arr[0]);// 首元素地址(00BFFD70)
    printf("&arr = %p\n", &arr);    // 整个数组地址(00BFFD70,值相同但类型不同)
    
    printf("sizeof(arr) = %zd\n", sizeof(arr));// 20(5×4字节)
    printf("sizeof(&arr[0]) = %zd\n", sizeof(&arr[0]));// 4/8(指针大小)
    return 0;
}

5.2 strlen 与 sizeof 的区别(字符串数组常用)

函数 / 操作符功能适用场景示例(char arr[] = "abc"
strlen统计字符串长度('\0' 前的字符数)字符串数组strlen(arr) = 3
sizeof计算总内存字节数(含 '\0')所有数据类型 / 数组sizeof(arr) = 4

5.3 数组作为函数参数(进阶必备)

数组作为函数参数时,会退化为首元素地址,函数无法直接获取数组大小,必须手动传入:

// 错误写法:函数内sizeof(arr)算的是指针大小(4/8字节)
void printArray(int arr[])
{
    int sz = sizeof(arr)/sizeof(arr[0]);// 错误:sz=1(4/4)
    for (int i=0; i<sz; i++)
    {
        printf("%d ", arr[i]);
    }
}

// 正确写法:手动传入数组大小
void printArray(int arr[], int sz)
{
    for (int i=0; i<sz; i++)
    {
        printf("%d ", arr[i]);
    }
}

int main()
{
    int arr[] = {1,2,3,4,5};
    int sz = sizeof(arr)/sizeof(arr[0]);
    printArray(arr, sz); // 传入数组名(首元素地址)和大小
    return 0;
}

六、新手必避的 6 个坑(红标警告!)

  1. 下标越界:下标从 0 开始,最大下标 = 元素个数 - 1(如int arr[5]的最大下标是 4);
  2. 二维数组省略列数:int arr[3][](错误,列数不能省略);
  3. 数组初始化时赋值过多:int arr[3] = {1,2,3,4}(错误,超出格子数);
  4. 二分查找用于无序数组:必须先排序,否则查找结果错误;
  5. 数组作为函数参数未传大小:函数内无法获取正确的元素个数;
  6. 字符串数组未加 '\0':char arr[] = {'a','b','c'}(打印时乱码,无结束标志)。

📝 总结

数组是 C 语言处理批量数据的 “核心工具”,核心是 “相同类型、连续存储、下标访问”。掌握一维数组的创建、初始化、遍历,二维数组的行名列名逻辑,再通过两个实战练习巩固,就能为后续指针、结构体学习打下坚实基础。

如果这篇博客帮你理清了数组的逻辑,欢迎点赞收藏!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值