网站定制北京,wordpress二次元极简主题,免费个人网站建站源码,wordpress网站微信公众号推送1.一维数组 目标:通过思维导图了解学习一维数组的核心知识点: 1.1定义 使用 类型名 数组名[数组长度]; 定义数组。
// 示例#xff1a;
int arr[5];
1.2一维数组初始化 数组的初始化可以分为静态初始化和动态初始化两种方式。 它们的主要区别在于初始化的时机和内存分配的方…1.一维数组 目标:通过思维导图了解学习一维数组的核心知识点: 1.1定义 使用 类型名 数组名[数组长度]; 定义数组。
// 示例
int arr[5];
1.2一维数组初始化 数组的初始化可以分为静态初始化和动态初始化两种方式。 它们的主要区别在于初始化的时机和内存分配的方式。
1.2.1一维数组静态初始化 定义: 在声明数组的同时为数组元素赋初值。这种方式在编译时就确定了数组的大小和元素的值因此也称为编译时初始化。
特点: 编译时确定数组的大小和元素的值在编译时就已经确定编译器会为数组分配足够的内存空间。 自动计算大小如果在初始化时省略数组的大小编译器会根据初始化列表中的元素个数自动计算数组的大小。 默认值如果初始化列表中的元素个数少于数组的大小未初始化的元素会被自动初始化为0对于数值类型或空字符对于字符类型。
示例:
//完全初始化
int arr[5] {1, 2, 3, 4, 5};。//部分初始化
int arr[5] {1, 2};//自动计算长度
int arr[] {1, 2, 3};
1.2.2一维数组动态初始化 定义 : 在程序运行时为数组分配内存并初始化数组元素。 动态分配可以通过 malloc、calloc 和 realloc 函数来实现因此也称为运行时初始化。 运行时确定数组的大小和元素的值在程序运行时确定需要手动调用动态内存分配函数来分配内存。 手动管理内存使用动态内存分配函数分配的内存需要手动释放否则会导致内存泄漏。 灵活性可以根据程序的需要动态地调整数组的大小。 动态内存分配函数定义: (1)malloc
//函数原型
void* malloc(size_t size);
功能:
分配指定大小的内存块并返回指向该内存块的指针。分配的内存块未初始化内容是未定义的即可能包含垃圾值。
参数:
size要分配的内存大小以字节为单位。
返回值:
成功返回指向分配内存的指针。失败返回 NULL。
示例:
int *ptr;ptr (int *)malloc(5 * sizeof(int)); // 分配5个整数的内存空间if (ptr NULL)
{printf(内存分配失败\n);return 1;
}(2) calloc
//函数原型
void* calloc(size_t num, size_t size);
功能:
分配 num 个大小为 size 的内存块并将所有字节初始化为0。返回指向分配内存的指针。
参数:
num要分配的内存块数量。size每个内存块的大小以字节为单位。
返回值:
成功返回指向分配内存的指针。失败返回 NULL。
示例:
int *ptr;ptr (int *)calloc(5, sizeof(int)); // 分配5个整数的内存空间并初始化为零if (ptr NULL) {printf(内存分配失败\n);return 1;
}(3) realloc
//函数原型
void* realloc(void* ptr, size_t size);
功能:
调整已分配内存块的大小。如果成功返回指向新内存块的指针如果失败返回 NULL原内存块不变。
参数:
ptr指向要调整大小的内存块的指针。size新的内存大小以字节为单位。
返回值:
成功返回指向新内存块的指针。失败返回 NULL原内存块不变。
示例:
int *ptr;ptr (int *)malloc(5 * sizeof(int)); // 分配5个整数的内存空间if (ptr NULL)
{printf(内存分配失败\n);return 1;
}// 重新分配内存扩大到10个整数的空间ptr (int *)realloc(ptr, 10 * sizeof(int));if (ptr NULL)
{printf(内存重新分配失败\n);return 1;
}1.2.3注意事项
1)内存释放 使用完动态分配的内存后必须调用 free 函数释放内存以避免内存泄漏。
//示例free(arr);
(2)空指针检查 在分配内存后检查返回的指针是否为 NULL以确保内存分配成功。
//示例if (arr NULL)
{printf(内存分配失败\n);return 1;}
(3)内存对齐 malloc 和 calloc 返回的内存地址是按系统要求对齐的可以安全地存储任何类型的对象。
(4)内存调整 使用 realloc 时如果调整大小失败返回 NULL原内存块不变需要手动处理这种情况。
//示例int *new_arr (int *)realloc(arr, new_n * sizeof(int));if (new_arr NULL)
{printf(内存调整失败\n);free(arr); // 释放原内存return 1;}
1.3一维访问元素 使用下标访问数组名[下标]
// 示例int value arr[0];
1.4求一维数组长度 使用 sizeof 操作符sizeof(数组名) / sizeof(数组名[0])
// 示例int length sizeof(arr) / sizeof(arr[0]);
1.5一维数组边界检查 1.5.1边界检查的作用 防止数组越界访问从而避免未定义行为和潜在的安全漏洞。数组越界访问可能导致程序崩溃、数据损坏或其他不可预测的行为。
1.5.2 什么是数组越界
数组越界是指访问数组中不存在的元素。例如对于一个长度为5的数组 arr[5]访问 arr[5] 或 arr[-1] 都是越界访问。
1.5.3边界检查的重要性 避免未定义行为访问越界的内存可能导致程序崩溃或产生不可预测的结果。 提高程序安全性防止潜在的安全漏洞如缓冲区溢出攻击。 调试方便边界检查有助于快速定位和修复数组访问错误。
1.5.4如何进行边界检查 在访问数组元素时进行检查。 在访问数组元素之前确保下标在合法范围内。
(2)使用循环时进行检查 在使用循环遍历数组时确保循环变量在合法范围内。
1.5.5边界检查代码示例
1)单个元素访问时的边界检查
#include stdio.hint main()
{int arr[5] {1, 2, 3, 4, 5};int index;printf(请输入要访问的数组索引 (0-4): );scanf(%d, index);// 边界检查if (index 0 index 5)
{printf(arr[%d] %d\n, index, arr[index]);} else {printf(错误: 索引 %d 越界\n, index);}return 0;
}
2)使用循环时的边界检查
#include stdio.hint main()
{int arr[5] {1, 2, 3, 4, 5};int n sizeof(arr) / sizeof(arr[0]);printf(数组元素: );for (int i 0; i n; i)
{printf(%d , arr[i]);}printf(\n);// 边界检查在循环中已经隐含int index;printf(请输入要访问的数组索引 (0-4): );scanf(%d, index);if (index 0 index n)
{printf(arr[%d] %d\n, index, arr[index]);} else {printf(错误: 索引 %d 越界\n, index);}return 0;
}
(3)动态数组的边界检查
#include stdio.h
#include stdlib.hint main()
{int n;printf(请输入数组的大小: );scanf(%d, n);// 动态分配内存int *arr (int *)malloc(n * sizeof(int));if (arr NULL) {printf(内存分配失败\n);return 1;}// 初始化数组元素for (int i 0; i n; i) {arr[i] i 1;}// 打印数组元素printf(数组元素: );for (int i 0; i n; i) {printf(%d , arr[i]);}printf(\n);// 边界检查int index;printf(请输入要访问的数组索引 (0-%d): , n-1);scanf(%d, index);if (index 0 index n) {printf(arr[%d] %d\n, index, arr[index]);} else {printf(错误: 索引 %d 越界\n, index);}// 释放内存free(arr);return 0;
}
4)使用宏进行边界检查
#include stdio.h#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))int main() {int arr[5] {1, 2, 3, 4, 5};int index;printf(请输入要访问的数组索引 (0-%d): , ARRAY_SIZE(arr) - 1);scanf(%d, index);if (index 0 index ARRAY_SIZE(arr)) {printf(arr[%d] %d\n, index, arr[index]);} else {printf(错误: 索引 %d 越界\n, index);}return 0;
}
5)边界点的注意事项 边界检查在访问数组元素之前确保下标在合法范围内。 循环边界确保循环变量在合法范围内避免越界访问。 动态数组动态数组的大小在运行时确定边界检查尤为重要。 宏定义使用宏定义简化边界检查。
1.6一维数组常规的遍历 一维数组遍历以下分析一维数组遍历的7种方法:
1.6.1 for 循环遍历 最常用的方法适用于大多数情况。
#include stdio.hint main()
{//定义一个名为arr的整型数组并初始化数组的元素为1、2、3、4、5。int arr[] {1, 2, 3, 4, 5}; //sizeof(arr)返回整个数组占用的字节数sizeof(arr[0])返回数组中一个元素占用的字节数两者相除得到数组的元素个数。int n sizeof(arr) / sizeof(arr[0]); //计算数组arr的元素个数//p打印提示信息表示接下来将使用for循环遍历数组printf(使用 for 循环遍历数组:\n);//i从0开始每次循环增加1直到i小于数组的元素个数n。for (int i 0; i n; i) //遍历,n是指sizeof()求出的元素个数
{//在每次循环中打印数组中当前索引i对应的元素值。arr[i]表示数组arr中索引为i的元素printf(arr[%d] %d\n, i, arr[i]);}return 0;
}/*使用 for 循环遍历数组输出的结果:
arr[0] 1
arr[1] 2
arr[2] 3
arr[3] 4
arr[4] 5
*/
1.6.2 while 循环遍历 适用于需要手动管理循环变量的情况。
#include stdio.hint main()
{//定义一个名为arr的整型数组并初始化数组的元素为1、2、3、4、5int arr[] {1, 2, 3, 4, 5};//sizeof(arr)返回整个数组占用的字节数//sizeof(arr[0])返回数组中一个元素占用的字节数两者相除得到数组的元素个数int n sizeof(arr) / sizeof(arr[0]); //计算数组arr的元素个数//定义一个整型变量i并初始化为0用于作为数组的索引int i 0;//打印提示信息表示接下来将使用while循环遍历数组printf(使用 while 循环遍历数组:\n);//这是一个while循环只要i小于数组的元素个数n循环就会继续执行while (i n) {//在每次循环中打印数组中当前索引i对应的元素值//arr[i]表示数组arr中索引为i的元素printf(arr[%d] %d\n, i, arr[i]);//每次循环结束后将索引i的值增加1以便访问数组中的下一个元素i;}return 0;
}/*使用 while 循环遍历数组输出的结果:
arr[0] 1
arr[1] 2
arr[2] 3
arr[3] 4
arr[4] 5
*/
1.6.3 do-while 循环遍历 适用于需要至少访问一次数组元素的情况。
#include stdio.hint main()
{//定义一个名为arr的整型数组并初始化数组的元素为1、2、3、4、5int arr[] {1, 2, 3, 4, 5};//sizeof(arr)返回整个数组占用的字节数//sizeof(arr[0])返回数组中一个元素占用的字节数两者相除得到数组的元素个数int n sizeof(arr) / sizeof(arr[0]); //计算数组arr的元素个数//定义一个整型变量i并初始化为0用于作为数组的索引int i 0;//打印提示信息表示接下来将使用do-while循环遍历数组printf(使用 do-while 循环遍历数组:\n);//这是一个do-while循环它会先执行一次循环体中的代码//然后再检查循环条件i n。只要条件为真循环就会继续执行do {//在每次循环中打印数组中当前索引i对应的元素值printf(arr[%d] %d\n, i, arr[i]); //arr[i]表示数组arr中索引为i的元素//每次循环结束后将索引i的值增加1以便访问数组中的下一个元素i;} while (i n);return 0;
}/*使用 do-while 循环遍历数组输出结果:
arr[0] 1
arr[1] 2
arr[2] 3
arr[3] 4
arr[4] 5
*/
1.6.4指针遍历 通过指针操作可以更灵活地遍历数组。
#include stdio.hint main()
{//定义一个名为arr的整型数组并初始化数组的元素为1、2、3、4、5int arr[] {1, 2, 3, 4, 5};//sizeof(arr)返回整个数组占用的字节数//sizeof(arr[0])返回数组中一个元素占用的字节数两者相除得到数组的元素个数int n sizeof(arr) / sizeof(arr[0]); //计算数组arr的元素个数//定义一个指向整型的指针ptr并将其初始化为数组arr的首地址int *ptr arr;//打印提示信息表示接下来将使用指针遍历数组printf(使用指针遍历数组:\n);//这是一个for循环i从0开始每次循环增加1直到i小于数组的元素个数nfor (int i 0; i n; i) {//在每次循环中打印数组中当前索引i对应的元素值//*(ptr i)表示指针ptr指向的地址加上偏移量i后所指向的元素值printf(arr[%d] %d\n, i, *(ptr i));}return 0;
}/*使用指针遍历数组输出结果:
arr[0] 1
arr[1] 2
arr[2] 3
arr[3] 4
arr[4] 5
*/
1.6.5指针和 while 循环遍历 结合指针和 while 循环可以更灵活地遍历数组。
#include stdio.hint main()
{//定义一个名为arr的整型数组并初始化数组的元素为1、2、3、4、5int arr[] {1, 2, 3, 4, 5};//sizeof(arr)返回整个数组占用的字节数//sizeof(arr[0])返回数组中一个元素占用的字节数两者相除得到数组的元素个数int n sizeof(arr) / sizeof(arr[0]); //计算数组arr的元素个数//定义一个指向整型的指针ptr并将其初始化为数组arr的首地址int *ptr arr;//定义一个指向整型的指针end并将其初始化为数组arr的末尾地址即arr nint *end arr n;//打印提示信息表示接下来将使用指针和while循环遍历数组printf(使用指针和 while 循环遍历数组:\n);//这是一个while循环只要指针ptr小于指针end循环就会继续执行while (ptr end) {//在每次循环中打印数组中当前指针ptr所指向的元素值//ptr - arr计算当前指针相对于数组首地址的偏移量*ptr表示指针ptr所指向的元素值printf(arr[%ld] %d\n, ptr - arr, *ptr);//每次循环结束后将指针ptr向后移动一个元素的位置ptr;}return 0;
}/*使用指针和 while 循环遍历数组输出结果:
arr[0] 1
arr[1] 2
arr[2] 3
arr[3] 4
arr[4] 5
*/1.6.6指针和 for 循环遍历 结合指针和 for 循环可以更灵活地遍历数组。
#include stdio.hint main()
{//定义一个名为arr的整型数组并初始化数组的元素为1、2、3、4、5int arr[] {1, 2, 3, 4, 5};//sizeof(arr)返回整个数组占用的字节数//sizeof(arr[0])返回数组中一个元素占用的字节数两者相除得到数组的元素个数int n sizeof(arr) / sizeof(arr[0]); //计算数组arr的元素个数//定义一个指向整型的指针ptr并将其初始化为数组arr的首地址int *ptr arr;//定义一个指向整型的指针end并将其初始化为数组arr的末尾地址即arr nint *end arr n;//打印提示信息表示接下来将使用指针和for循环遍历数组printf(使用指针和 for 循环遍历数组:\n);//这是一个for循环i从0开始每次循环增加1直到指针ptr小于指针end//在每次循环中i用于表示当前元素的索引ptr用于指向当前元素for (int i 0; ptr end; i, ptr) {//在每次循环中打印数组中当前指针ptr所指向的元素值//i表示当前元素的索引*ptr表示指针ptr所指向的元素值printf(arr[%d] %d\n, i, *ptr);}return 0;
}/*使用指针和 for 循环遍历数组输出结果:
arr[0] 1
arr[1] 2
arr[2] 3
arr[3] 4
arr[4] 5
*/
1.6.7 foreach 循环遍历 C11 引入模拟 foreach 风格不常用。
#include stdio.h//定义一个宏 foreach用于简化数组遍历
#define foreach(type, var, arr, len) \//宏展开后的 for 循环用于遍历数组 //type *var arr 定义了一个指向数组首元素的指针 var//*end arr len 定义了一个指向数组末尾的指针 end//var ! end 是循环条件var 用于移动指针到下一个元素for (type *var arr, *end arr len; var ! end; var) int main() {//定义并初始化一个整型数组 arrint arr[] {1, 2, 3, 4, 5};//sizeof(arr[0]) 计算数组 arr 的长度 nint n sizeof(arr) / sizeof(arr[0]);//打印提示信息printf(使用 foreach 循环遍历数组:\n);//int 是元素类型ptr 是循环变量名arr 是数组名n 是数组长度foreach(int, ptr, arr, n) //使用 foreach 宏遍历数组 arr{//在每次循环中打印当前元素的值及其在数组中的索引//ptr - arr 计算当前元素的索引*ptr 获取当前元素的值printf(arr[%ld] %d\n, ptr - arr, *ptr);}return 0;
}/*使用 foreach循环遍历数组输出结果:
arr[0] 1
arr[1] 2
arr[2] 3
arr[3] 4
arr[4] 5
*/
1.7一维数组动态遍历 动态遍历数组通常涉及动态分配内存来创建数组并使用循环来访问和操作这些元素。 动态分配内存可以通过 malloc、calloc 或 realloc 函数来实现。 遍历方法可通过6中方法实现,以下代码我会用for循环来详细解析代码,其他用于汇总方法实现。 方法 1使用 for 循环遍历 2 使用 while 循环遍历 3使用 do-while 循环遍历 4使用指针遍历 5使用指针和 while 循环遍历 6使用指针和 for 循环遍历
1.7.1一维数组动态遍历函数原型介绍 之前在一维数组动态初始化内有介绍过这三个函数,在这里再拆分解析下,用于强化记忆。
1.7.1.1 malloc函数原型介绍
//1.malloc函数原型 void* malloc(size_t size);//2.使用 malloc 函数动态分配内存//3.malloc 函数接受一个参数表示需要分配的字节数//4.在这里n * sizeof(int) 计算出需要分配的字节数以存储 n 个整数//5.malloc 函数返回一个指向分配内存的指针如果分配失败则返回 NULL//6.(int *) 是类型转换,将malloc返回的void *指针转换为 int *指针以便可以正确地访问分配的内存//7.如果内存分配失败返回 1表示程序异常结束int *arr (int *)malloc(n * sizeof(int));if (arr NULL) {printf(内存分配失败\n);return 1;
}
1.7.1.2 calloc函数原型介绍
//1. calloc函数原型 void* calloc(size_t num, size_t size);//2. 使用 calloc 函数动态分配内存//3. calloc 函数接受两个参数第一个参数 n 表示要分配的元素数量第二个参数 sizeof(int) 表示每个元素的大小以字节为单位//4. calloc 函数会在分配内存后将所有字节初始化为零。(int *) 是类型转换将 calloc 返回的 void * 指针转换为 int * 指针以便可以正确地访问分配的内存。//5.检查 calloc 是否成功分配了内存。如果 arr 为 NULL表示内存分配失败。//6.如果内存分配失败打印一条错误信息//7. 如果内存分配失败返回 1表示程序异常结束int *arr (int *)calloc(n, sizeof(int));if (arr NULL) {printf(内存分配失败\n);return 1;}1.7.1.3 realloc 函数原型介绍
//1. 函数原型 void *realloc(void *ptr, size_t size);//2. 使用 realloc 函数尝试重新分配内存//3.realloc 函数接受两个参数第一个参数 ptr 是一个指向之前已经分配的内存块的指针第二个参数 10 * sizeof(int) 表示新的内存块大小即 10 个整数的大小//4. realloc 函数会尝试将 ptr 指向的内存块调整为新的大小//5.如果调整成功realloc 会返回一个指向新内存块的指针如果调整失败realloc 会返回 NULL//6.(int *) 是类型转换将 realloc 返回的 void * 指针转换为 int * 指针以便可以正确地访问分配的内存//7. if检查 realloc 是否成功重新分配了内存。如果 new_ptr 为 NULL表示重新分配失败//8. perror 作用是打印错误流的,如果重新分配失败使用 perror 函数打印一条错误信息该信息会包含系统错误信息//9. return 1;如果重新分配失败返回 1表示程序异常结束int *new_ptr (int *)realloc(ptr, 10 * sizeof(int));if (new_ptr NULL) {perror(realloc failed);free(ptr); // 释放原始内存块return 1;}
1.7.2使用malloc函数实现一维数组动态遍历
#include stdio.h
#include stdlib.hint main() { //定义一个整型变量n用于存储用户输入的数组大小int n;//提示用户输入数组的大小printf(请输入数组的大小: );//从标准输入读取用户输入的数组大小并存储在变量n中scanf(%d, n);//malloc函数返回一个指向分配内存的指针如果分配失败则返回NULL.//(int *)是类型转换将malloc返回的void *指针转换为int *指针,//以便可以正确地访问分配的内存int *arr (int *)malloc(n * sizeof(int)); //动态分配一个大小为n的整型数组if (arr NULL) //检查内存分配是否成功{// 如果arr为NULL表示内存分配失败printf(内存分配失败\n); return 1; //打印错误信息并返回1表示程序异常结束}//循环变量i从0开始每次循环增加1直到i小于nfor (int i 0; i n; i) //使用for循环初始化数组元素{//在每次循环中将数组元素arr[i]初始化为i 1arr[i] i 1;}//提示用户,打印提示语句使用 for 循环遍历数组printf(使用 for 循环遍历数组:\n);//循环变量i从0开始每次循环增加1直到i小于nfor (int i 0; i n; i) //使用for循环遍历数组并打印每个元素的值{// //在每次循环中打印数组元素arr[i]的值printf(arr[%d] %d\n, i, arr[i]);}// 释放内存free(arr); //free函数用于释放之前通过malloc分配的内存return 0;
} 使用malloc函数实现6种遍历方法
#include stdio.h
#include stdlib.hint main()
{int n;printf(请输入数组的大小: );scanf(%d, n);// 动态分配内存int *arr (int *)malloc(n * sizeof(int));if (arr NULL) {printf(内存分配失败\n);return 1;}// 初始化数组元素for (int i 0; i n; i) {arr[i] i 1;}// 使用 for 循环遍历数组printf(使用 for 循环遍历数组:\n);for (int i 0; i n; i) {printf(arr[%d] %d\n, i, arr[i]);}// 使用 while 循环遍历数组printf(\n使用 while 循环遍历数组:\n);int i 0;while (i n) {printf(arr[%d] %d\n, i, arr[i]);i;}// 使用 do-while 循环遍历数组printf(\n使用 do-while 循环遍历数组:\n);i 0;do {printf(arr[%d] %d\n, i, arr[i]);i;} while (i n);// 使用指针遍历数组printf(\n使用指针遍历数组:\n);int *ptr arr;for (int i 0; i n; i) {printf(arr[%d] %d\n, i, *(ptr i));}// 使用指针和 while 循环遍历数组printf(\n使用指针和 while 循环遍历数组:\n);ptr arr;int *end arr n;while (ptr end) {printf(arr[%ld] %d\n, ptr - arr, *ptr);ptr;}// 使用指针和 for 循环遍历数组printf(\n使用指针和 for 循环遍历数组:\n);ptr arr;end arr n;for (int i 0; ptr end; i, ptr) {printf(arr[%d] %d\n, i, *ptr);}// 释放内存free(arr);return 0;
}
1.7.3使用calloc函数实现一维数组动态遍历
#include stdio.h
#include stdlib.hint main()
{//定义一个整型变量n用于存储用户输入的数组大小int n;//提示用户输入数组的大小printf(请输入数组的大小: );//从标准输入读取用户输入的数组大小并存储在变量n中scanf(%d, n);// 动态分配内存并初始化为0//动态分配一个大小为n的整型数组并将所有元素初始化为0//calloc函数返回一个指向分配内存的指针如果分配失败则返回NULL//(int *)是类型转换将calloc返回的void *指针转换为int *指针可以正确地访问分配的内存int *arr (int *)calloc(n, sizeof(int));if (arr NULL) {printf(内存分配失败\n);return 1;}// 打印初始数组元素初始值为0printf(初始数组元素: );//使用for循环遍历数组并打印每个元素的初始值0//循环变量i从0开始每次循环增加1直到i小于nfor (int i 0; i n; i) {//在每次循环中打印数组元素arr[i]的值printf(%d , arr[i]);}printf(\n);//使用for循环初始化数组元素//循环变量i从0开始每次循环增加1直到i小于nfor (int i 0; i n; i) {//在每次循环中将数组元素arr[i]初始化为i 1arr[i] i 1;}// 使用 for 循环遍历数组//使用for循环遍历数组并打印每个元素的值//循环变量i从0开始每次循环增加1直到i小于nprintf(使用 for 循环遍历数组:\n);for (int i 0; i n; i) {//在每次循环中打印数组元素arr[i]的值printf(arr[%d] %d\n, i, arr[i]);}//释放动态分配的内存。free函数用于释放之前通过calloc分配的内存free(arr);return 0;
}使用calloc函数实现6种遍历方法
#include stdio.h
#include stdlib.hint main()
{int n;printf(请输入数组的大小: );scanf(%d, n);// 动态分配内存并初始化为0int *arr (int *)calloc(n, sizeof(int));if (arr NULL) {printf(内存分配失败\n);return 1;}// 打印初始数组元素初始值为0printf(初始数组元素: );for (int i 0; i n; i) {printf(%d , arr[i]);}printf(\n);// 初始化数组元素for (int i 0; i n; i) {arr[i] i 1;}// 使用 for 循环遍历数组printf(使用 for 循环遍历数组:\n);for (int i 0; i n; i) {printf(arr[%d] %d\n, i, arr[i]);}// 使用 while 循环遍历数组printf(\n使用 while 循环遍历数组:\n);int i 0;while (i n) {printf(arr[%d] %d\n, i, arr[i]);i;}// 使用 do-while 循环遍历数组printf(\n使用 do-while 循环遍历数组:\n);i 0;do {printf(arr[%d] %d\n, i, arr[i]);i;} while (i n);// 使用指针遍历数组printf(\n使用指针遍历数组:\n);int *ptr arr;for (int i 0; i n; i) {printf(arr[%d] %d\n, i, *(ptr i));}// 使用指针和 while 循环遍历数组printf(\n使用指针和 while 循环遍历数组:\n);ptr arr;int *end arr n;while (ptr end) {printf(arr[%ld] %d\n, ptr - arr, *ptr);ptr;}// 使用指针和 for 循环遍历数组printf(\n使用指针和 for 循环遍历数组:\n);ptr arr;end arr n;for (int i 0; ptr end; i, ptr) {printf(arr[%d] %d\n, i, *ptr);}// 释放内存free(arr);return 0;
}
1.7.4使用realloc函数实现一维数组动态遍历
#include stdio.h
#include stdlib.hint main()
{//定义一个整型变量n用于存储用户输入的数组初始大小int n;//提示用户输入数组的初始大小printf(请输入数组的初始大小: );//从标准输入读取用户输入的数组初始大小并存储在变量n中scanf(%d, n);//动态分配一个大小为n的整型数组并将所有元素初始化为0//calloc函数返回一个指向分配内存的指针如果分配失败则返回NULL//(int *)是类型转换将calloc返回的void *指针转换为int *指针可以正确地访问分配的内存int *arr (int *)calloc(n, sizeof(int))//检查内存分配是否成功,如果arr为NULL表示内存分配失败if (arr NULL) {//打印错误信息printf(内存分配失败\n);//返回1表示程序异常结束return 1;}//使用for循环初始化数组元素//循环变量i从0开始每次循环增加1直到i小于nfor (int i 0; i n; i) {//在每次循环中将数组元素arr[i]初始化为i 1arr[i] i 1;}// 提示用户接下来将打印初始数组元素printf(初始数组元素: );//使用for循环遍历数组并打印每个元素的初始值//循环变量i从0开始每次循环增加1直到i小于nfor (int i 0; i n; i) {//在每次循环中打印数组元素arr[i]的值printf(%d , arr[i]);}printf(\n);// 定义一个整型变量new_size用于存储用户输入的新数组大小int new_size;//提示用户输入新的数组大小printf(请输入新的数组大小: );//从标准输入读取用户输入的新数组大小并存储在变量new_size中scanf(%d, new_size);//realloc函数接受两个参数第一个参数是指向之前分配的内存块的指针第二个参数是新的内存块大小//realloc函数会尝试将之前分配的内存块扩展到新的大小并返回一个指向新内存块的指针//如果扩展失败realloc函数会返回NULL。(int *)是类型转换将realloc返回的void *指针转换为int *指针可以正确地访问分配的内存int *new_arr (int *)realloc(arr, new_size * sizeof(int)); //realloc扩展数组的大小//检查内存重新分配是否成功//如果new_arr为NULL表示内存重新分配失败if (new_arr NULL) {printf(内存重新分配失败\n);//打印错误信息并释放原始内存块free(arr); // 释放原始内存块//返回1表示程序异常结束return 1;}// 更新指针使其指向新的内存块arr new_arr;// 初始化新元素//使用for循环初始化新的数组元素//循环变量i从n开始每次循环增加1直到i小于new_sizefor (int i n; i new_size; i) {//在每次循环中将数组元素arr[i]初始化为i 1arr[i] i 1;}// 使用 for 循环遍历扩展后的数组printf(扩展后的数组元素: );//使用for循环遍历扩展后的数组并打印每个元素的值//循环变量i从0开始每次循环增加1直到i小于new_sizefor (int i 0; i new_size; i) {//在每次循环中打印数组元素arr[i]的值printf(%d , arr[i]);}printf(\n);// 释放之前通过calloc或realloc分配的内存free(arr);return 0;
} 使用realloc函数实现6种遍历方法
#include stdio.h
#include stdlib.hint main()
{int n;printf(请输入初始数组的大小: );scanf(%d, n);// 动态分配内存int *arr (int *)malloc(n * sizeof(int));if (arr NULL) {printf(内存分配失败\n);return 1;}// 初始化数组元素for (int i 0; i n; i) {arr[i] i 1;}// 打印初始数组元素printf(初始数组元素: );for (int i 0; i n; i) {printf(%d , arr[i]);}printf(\n);int new_n;printf(请输入新的数组大小: );scanf(%d, new_n);// 调整内存大小int *new_arr (int *)realloc(arr, new_n * sizeof(int));if (new_arr NULL) {perror(realloc failed);free(arr); // 释放原始内存块return 1;}// 初始化新分配的内存部分如果需要for (int i n; i new_n; i) {new_arr[i] i 1;}// 打印调整后的数组元素printf(调整后的数组元素: );for (int i 0; i new_n; i) {printf(%d , new_arr[i]);}printf(\n);// 释放内存free(new_arr);return 0;
}
1.8一维数组的排序算法 一维数组的排序算法有多种每种算法都有其特点和适用场景。以下是对常见排序算法的介绍:
(1) 冒泡排序Bubble Sort 简介重复地遍历要排序的数列一次比较两个元素如果它们的顺序错误就把它们交换过来。遍历数列的工作是重复地进行直到没有再需要交换也就是说该数列已经排序完成。 时间复杂度平均和最坏情况下为O(n^2)最好情况下为O(n)。 空间复杂度O(1)。 稳定性稳定。 适用场景数据规模较小且基本有序的情况。
(2) 选择排序Selection Sort 简介每一次从待排序的数据元素中选出最小或最大的一个元素存放在序列的起始位置直到全部待排序的数据元素排完。 时间复杂度O(n^2)。 空间复杂度O(1)。 稳定性不稳定。 适用场景数据规模较小且对稳定性要求不高的情况。
(3)插入排序Insertion Sort 简介通过构建有序序列对于未排序数据在已排序序列中从后向前扫描找到相应位置并插入。 时间复杂度平均和最坏情况下为O(n^2)最好情况下为O(n)。 空间复杂度O(1)。 稳定性稳定。 适用场景数据规模较小且基本有序的情况。
(4)快速排序Quick Sort 简介选择一个基准元素将数组分为两部分一部分的所有元素都比基准元素小另一部分的所有元素都比基准元素大然后对这两部分分别进行快速排序。 时间复杂度平均情况下为O(nlogn)最坏情况下为O(n^2)。 空间复杂度平均情况下为O(logn)最坏情况下为O(n)。 稳定性不稳定。 适用场景数据规模较大且对平均性能要求较高的情况。
(5)归并排序Merge Sort 简介将一个数组分成两个子数组对每个子数组进行排序然后将排序好的子数组合并成一个有序数组。 时间复杂度O(nlogn)。 空间复杂度O(n)。 稳定性稳定。 适用场景数据规模较大且对稳定性要求较高的情况。
(6)堆排序Heap Sort 简介首先将数组构建成一个最大堆然后将堆顶元素与最后一个元素交换再对剩余的元素进行堆调整重复这个过程直到整个数组有序。 时间复杂度O(nlogn)。 空间复杂度O(1)。 稳定性不稳定。 适用场景数据规模较大且对空间复杂度要求较高的情况。
1.8.1一维数组冒泡排序 用于对整型数组 arr 进行升序排序。 1.8.1.1原理 冒泡排序的原理:依次比较相邻的元素如果前面的比后面的大(小)就进行交换每一趟比较后就会得到一个最大值(小)该值不进行下一轮的比较对剩余的部分再次进行前面的操作直到剩余一个元素为止排序成功。 天蓝色:待排序 绿色:正在比较的数组元素 橙色:已经排好的数组元素
1.8.1.2冒泡排序的要素 (1) 需要双重循环来解决: 外层循环控制遍历的次数。总共需要 n-1 次遍历。 i 从 0 到 n-2。 内层循环比较相邻的元素并交换它们。 j 从 0 到 n-i-2。 (2)每趟的比较次数 所在趟数 元素总个数. (3)每次内层循环结束后最大的元素会被“冒泡”到数组的末尾。 (4)比较和交换 如果 arr[j] 大于 arr[j 1]则交换这两个元素。 使用临时变量 temp 来交换元素的值。 1.8.1.3冒泡排序算法
//定义了一个名为 bubbleSort 的函数接受一个整型数组 arr 和数组的长度 n 作为参数void bubbleSort(int arr[], int n)
{//外层循环控制所有的遍历次数//对于长度为 n 的数组最多需要进行 n-1 次遍历for (int i 0; i n - 1; i) {//内层循环进行相邻元素的比较和交换//随着外层循环的进行每次遍历后最大的元素会被“冒泡”到数组的末尾因此内层循环的范围可以逐渐减小for (int j 0; j n - i - 1; j) {//比较当前元素 arr[j] 和下一个元素 arr[j 1]//如果当前元素大于下一个元素则交换它们的位置if (arr[j] arr[j 1]) {// 交换 arr[j] 和 arr[j1],交换操作通过一个临时变量 temp 来完成int temp arr[j];arr[j] arr[j 1];arr[j 1] temp;}}}
}
1.8.2一维数组选择排序 用于对整型数组 arr 进行升序排序。
1.8.2.1原理 选择排序是一种简单直观的排序算法。其基本思想是每一次从待排序的数据元素中选出最小或最大的一个元素存放在序列的起始位置直到全部待排序的数据元素排完。
1.8.2.2选择排序的要素 (1) 需要双重循环来解决: 外层循环 for (int i 0; i n - 1; i)外层循环控制选择的起始位置从数组的第一个元素开始到倒数第二个元素结束。 内层循环 for (int j i 1; j n; j)内层循环从当前位置的下一个元素开始遍历到数组的最后一个元素寻找最小值的索引。 (2)寻找最小元素 if (arr[j] arr[min_idx])如果找到比当前最小值还小的元素则更新最小值的索引。 (3)交换元素 如果在内层循环结束后找到的最小元素索引 min_idx 不是当前外层循环的位置 i则交换这两个位置的元素。
1.8.2.3选择排序算法
//定义了一个名为 selectionSort 的函数接受一个整型数组 arr 和数组的长度 n 作为参数void selectionSort(int arr[], int n)
{//外层循环控制选择的起始位置从数组的第一个元素开始到倒数第二个元素结束for (int i 0; i n - 1; i) {// 假设当前位置的元素是最小的int min_idx i;// 内层循环查找从i1到n-1中最小元素的索引//内层循环从当前位置的下一个元素开始遍历到数组的最后一个元素寻找最小值的索引for (int j i 1; j n; j) {if (arr[j] arr[min_idx]) {min_idx j; // 更新最小元素的索引}}//如果找到比当前最小值还小的元素则更新最小值的索引if (min_idx ! i) {//如果在内层循环结束后找到的最小元素索引 min_idx 不是当前外层循环的位置 i//则交换这两个位置的元素。int temp arr[i];arr[i] arr[min_idx];arr[min_idx] temp;}}
}
1.8.3一维数组插入排序 用于对整型数组 arr 进行升序排序。
1.8.3.1原理 插入排序是一种简单直观的排序算法。它的工作原理是通过构建有序序列对于未排序数据在已排序序列中从后向前扫描找到相应位置并插入。
1.8.3.2插入排序的要素 (1) 需要双重循环来解决: 外层循环 for (int i 1; i n; i)外层循环从数组的第二个元素开始到最后一个元素结束。假设第一个元素已经被排序。 (2)关键元素 int key arr[i]将当前元素保存为 key这是需要插入到已排序部分的元素。 (3)内层循环 while (j 0 arr[j] key)内层循环从当前元素的前一个位置开始向前遍历已排序部分找到 key 应该插入的位置。如果已排序部分的元素大于 key则将该元素向后移动一位。
4()插入元素 arr[j 1] key将 key 插入到正确的位置。
1.8.3.3插入排序算法
//定义了一个名为 insertionSort 的函数接受一个整型数组 arr 和数组的长度 n 作为参数
void insertionSort(int arr[], int n) {//从第一个元素开始该元素可以认为已经被排序//外层循环从数组的第二个元素开始到最后一个元素结束for (int i 1; i n; i) {//将当前元素保存为 key这是需要插入到已排序部分的元素int key arr[i]; // 当前需要插入的元素int j i - 1;//内层循环从当前元素的前一个位置开始向前遍历已排序部分找到 key 应该插入的位置//如果已排序部分的元素大于 key则将该元素向后移动一位while (j 0 arr[j] key) //将大于key的元素向后移动一位{arr[j 1] arr[j];j j - 1;}//将 key 插入到正确的位置。arr[j 1] key;}
}
1.8.4一维数组快速排序 用于对整型数组 arr 进行升序排序。 快速排序是一种高效的排序算法采用分治法策略
1.8.4.1原理 快速排序是一种高效的排序算法采用分治法Divide and Conquer策略来把一个序列分为两个子序列然后递归地排序子序列。
1.8.4.2快速排序的要素
(1)quickSort函数逻辑 如果 low 小于 high说明数组中至少有两个元素需要排序。 调用 partition 函数对数组进行分区并获取基准元素的索引 pi。 递归地对基准元素左边和右边的子数组进行快速排序。
(2)partition 函数逻辑 选择数组的最后一个元素作为基准 pivot。 初始化较小元素的索引 i 为 low - 1。 遍历数组从 low 到 high - 1 如果当前元素 arr[j] 小于基准 pivot则增加 i 并交换 arr[i] 和 arr[j]将较小元素移到左边。 遍历结束后交换 arr[i 1] 和 arr[high]将基准元素放到正确的位置。 返回基准元素的索引 i 1。
1.8.4.1快速排序算法
/*1. quickSort 函数参数arr[]要排序的数组。low数组的起始索引。high数组的结束索引。
*/void quickSort(int arr[], int low, int high)
{//如果 low 小于 high说明数组中至少有两个元素需要排序if (low high) {//pi 是分区后的基准元素的索引//调用 partition 函数对数组进行分区并获取基准元素的索引 piint pi partition(arr, low, high);//递归排序基准元素左边的子数组quickSort(arr, low, pi - 1);//递归排序基准元素右边的子数组quickSort(arr, pi 1, high);}
}/*2. partition 函数参数arr[]要排序的数组。low数组的起始索引。high数组的结束索引。
*/int partition(int arr[], int low, int high) {int pivot arr[high]; //选择数组的最后一个元素作为基准 pivotint i (low - 1); //初始化较小元素的索引 i 为 low - 1for (int j low; j high - 1; j) //遍历数组从 low 到 high - 1{//如果当前元素 arr[j] 小于基准 pivot//则增加 i 并交换 arr[i] 和 arr[j]将较小元素移到左边if (arr[j] pivot) {i; // 增加较小元素的索引// 交换 arr[i] 和 arr[j]int temp arr[i];arr[i] arr[j];arr[j] temp;}}//遍历结束后交换 arr[i 1] 和 arr[high]将基准元素放到正确的位置int temp arr[i 1];arr[i 1] arr[high];arr[high] temp;return (i 1); // 返回基准元素的索引i1
}
1.8.5一维数组归并排序 用于对整型数组 arr 进行升序排序。 归并排序是一种稳定的排序算法采用分治法策略。
1.8.5.1原理 归并排序通过递归地将数组分成两半分别对这两半进行排序然后将排序好的两半合并在一起。这个过程一直重复直到数组被完全排序。
1.8.5.2归并排序的要素 (1)mergeSort 函数 如果 l 小于 r说明数组中至少有两个元素需要排序。 计算中间点 m将数组分成两半。 递归地对左半部分和右半部分进行归并排序。 调用 merge 函数合并两个已排序的部分。 (2)merge 函数 计算左半部分和右半部分的长度并创建临时数组 L 和 R。 将数据拷贝到临时数组 L 和 R 中。 合并临时数组回原数组 arr确保合并后的数组是有序的。 处理剩余的元素如果有。
1.8.5.3归并排序算法
/*
1. mergeSort 函数参数
arr[]要排序的数组。
l数组的起始索引。
r数组的结束索引。*/void mergeSort(int arr[], int l, int r) {//如果 l 小于 r说明数组中至少有两个元素需要排序if (l r) {//计算中间点 m将数组分成两半避免溢出int m l (r - l) / 2; mergeSort(arr, l, m); //对左半部分进行归并排序mergeSort(arr, m 1, r); //对右半部分进行归并排序merge(arr, l, m, r); //调用 merge 函数合并两个已排序的部分}
}/*
2. merge 函数参数
arr[]要排序的数组。
l数组的起始索引。
r数组的结束索引。
*/void merge(int arr[], int l, int m, int r)
{int i, j, k;int n1 m - l 1; //左半部分的长度int n2 r - m; //右半部分的长度int L[n1], R[n2]; //创建临时数组 L 和 R//拷贝数据到临时数组 L[] 和 R[]for (i 0; i n1; i) {L[i] arr[l i];}for (j 0; j n2; j) {R[j] arr[m 1 j];}//合并临时数组回原数组arr确保合并后的数组是有序的i 0; // 初始化左半部分的索引j 0; // 初始化右半部分的索引k l; // 初始化合并子数组的索引while (i n1 j n2) {if (L[i] R[j]) {arr[k] L[i];i;} else {arr[k] R[j];j;}k;}// 拷贝 L[] 的剩余元素while (i n1) {arr[k] L[i];i;k;}// 拷贝 R[] 的剩余元素while (j n2) {arr[k] R[j];j;k;}
}1.8.6一维数组堆排序 用于对整型数组 arr 进行升序排序。 堆排序是一种基于比较的排序算法利用堆这种数据结构来实现。
1.8.6.1原理 堆排序是一种基于二叉堆数据结构的排序算法。它首先将数组构建成一个最大堆然后将堆顶元素与最后一个元素交换再对剩余的元素进行堆调整重复这个过程直到整个数组有序。
1.8.6.2堆的要素
(1)heapify 函数 初始化最大元素的索引为根节点 i。 计算左子节点和右子节点的索引。 如果左子节点存在且大于根节点则更新最大元素的索引。 如果右子节点存在且大于当前最大值则更新最大元素的索引。 如果最大值不是根节点则交换根节点和最大值节点并递归地对受影响的子树进行堆化。
(2)heapSort 函数 构建最大堆从最后一个非叶子节点开始向前遍历并对每个节点进行堆化最终构建一个最大堆。 排序过程 将堆顶元素最大值与数组末尾元素交换。 减少堆的大小忽略已排序的部分。 对新的堆顶元素进行堆化确保剩余部分仍然是一个最大堆。 重复上述过程直到堆的大小为1。
1.8.6.3堆排序算法
/*
1. heapify 函数
参数
arr[]要排序的数组。
n数组的长度。
i当前子树的根节点索引。
*/void heapify(int arr[], int n, int i)
{int largest i; //初始化最大元素的索引为根节点int l 2 * i 1; //左子节点的索引int r 2 * i 2; //右子节点的索引//如果左子节点存在且大于根节点if (l n arr[l] arr[largest]) {largest l;}//如果右子节点存在且大于当前最大值if (r n arr[r] arr[largest]) {largest r;}//如果最大值不是根节点if (largest ! i) {//交换 arr[i] 和 arr[largest]int temp arr[i];arr[i] arr[largest];arr[largest] temp;// 递归地对受影响的子树进行堆化heapify(arr, n, largest);}
}/*
heapSort 函数
构建最大堆从最后一个非叶子节点开始向前遍历并对每个节点进行堆化最终构建一个最大堆。
排序过程
将堆顶元素最大值与数组末尾元素交换。
减少堆的大小忽略已排序的部分。
对新的堆顶元素进行堆化确保剩余部分仍然是一个最大堆。
重复上述过程直到堆的大小为1。
*/void heapSort(int arr[], int n)
{//构建最大堆for (int i n / 2 - 1; i 0; i--) {heapify(arr, n, i);}// 一个个从堆中取出元素for (int i n - 1; i 0; i--) {//将当前最大的元素根节点移到数组末尾int temp arr[0];arr[0] arr[i];arr[i] temp;//重新对堆进行堆化忽略已排序的部分heapify(arr, i, 0);}
}
1.9一维数组与指针 1.9.1一维数组与指针的定义 一维数组与指针有着密切的关系。 数组名在大多数情况下会被隐式地转换为指向数组第一个元素的指针。 指针算术的单位是数组元素的大小。 数组下标访问实际上是通过指针算术实现的。 当数组作为函数参数传递时实际上传递的是指向数组第一个元素的指针。 可以使用 malloc 或 calloc 函数动态分配数组并通过指针访问和操作数组元素。 arr 1 //指向数组的第二个元素。 当数组作为函数参数传递时实际上传递的是指向数组第一个元素的指针。 1.9.2一维数组与指针的区别 (1)数组名: 数组名是一个常量指针指向数组的第一个元素。 不能修改数组名的值(即不能指向其他内存地址)。 (2)指针: 指针是一个变量可以指向任何内存地址。 可以修改指针的值(即指向其他内存地址)。 3)示例:
#include stdio.hint main()
{int arr[5] {1, 2, 3, 4, 5};int *ptr arr;// 数组名作为指针//打印数组名 arr 和指针 ptr 的地址两者相同printf(数组名作为指针: %p\n, (void *)arr);printf(指针变量: %p\n, (void *)ptr);// 修改指针变量ptr arr[2];printf(修改后的指针变量: %p\n, (void *)ptr);// 不能修改数组名//将数组名 arr 修改为指向数组的第三个元素 arr[2]这是不允许的因为数组名是一个常量指针不能被修改// arr arr[2]; // 错误数组名是常量指针不能修改return 0;
} 1.9.3一维数组与指针的关系
(1)数组名作为指针 数组名在大多数情况下会被解释为指向数组第一个元素的指针。
int arr[5] {1, 2, 3, 4, 5};//这里arr 被隐式地转换为指向 arr[0] 的指针因此 ptr 指向数组的第一个元素int *ptr arr; //等价于 int *ptr arr[0];(2)指针运算 可以对指针进行加减运算以访问数组中的其他元素。
int value *(ptr 2); // 访问第三个元素
int value2 *(arr 2); // 访问第三个元素
(3)数组下标与指针的关系 数组下标操作符 [] 实际上是基于指针运算实现的。
int arr[5] {1, 2, 3, 4, 5};int *ptr arr;int x ptr[2]; //等价于 int x *(ptr 2),这里可以理解指针的偏移(4)数组作为函数参数
/*
modifyArray 函数
参数
int *arr指向整型数组的指针int size数组的大小即数组中元素的个数*/#include stdio.hvoid modifyArray(int *arr, int size)
{//使用一个 for 循环遍历数组中的所有元素for (int i 0; i size; i)
{//在每次循环中将当前元素的值乘以2并更新数组中的该元素arr[i] * 2;}
}int main()
{//定义一个包含5个整数的数组 arr并初始化为 {1, 2, 3, 4, 5}int arr[5] {1, 2, 3, 4, 5};//调用 modifyArray 函数传递数组 arr 和数组的大小 5modifyArray(arr, 5); //modifyArray 函数会将数组中的每个元素都乘以2//使用一个 for 循环遍历修改后的数组for (int i 0; i 5; i) {//使用 printf 函数打印每个元素printf(%d , arr[i]);}printf(\n); // 输出: 2 4 6 8 10return 0;
}1.9.4一维数组与指针的注意事项
(1)数组名是常量指针 数组名是一个常量指针不能被重新赋值。 例如arr NULL; 是非法的。
(2)越界访问 访问数组时需要注意不要越界否则会导致未定义行为。 例如arr[5] 访问第六个元素假设数组长度为5这是越界访问。
(3)指针的初始化 指针在使用前必须初始化否则可能会导致未定义行为。 例如int *ptr; *ptr 10; 是非法的因为 ptr 未初始化。
(4)指针类型匹配 指针类型必须与其指向的数据类型匹配否则会导致类型不匹配错误。 例如int *ptr hello; 是不合法的因为字符串常量的类型是 char *。
(5)内存管理 动态分配的内存需要手动释放否则会导致内存泄漏。 例如使用 malloc 分配的内存需要使用 free 释放。
(6)多维数组与指针 多维数组在内存中是按行优先顺序存储的。 访问多维数组元素时需要注意指针的运算。
2.二维数组
目标:通过思维导图了解学习二维数组的核心知识点: 2.1二维数组的基础概念
2.1.1定义 二维数组是一个由行和列组成的矩阵结构每个元素可以通过两个下标来访问。
//定义了一个3行4列的整数二维数组//其中a[i][j]i表示行0 i 3j表示列0 j 4可以访问特定的元素int a[3][4];
2.1.2内存布局 在C语言中二维数组在内存中是按行优先存储的。例如对于int a[2][3]其内存中元素的存储顺序为a[0][0]、a[0][1]、a[0][2]、a[1][0]、a[1][1]、a[1][2]。以下是示例代码来验证
#include stdio.h
int main()
{//这行代码定义了一个2行3列的二维整型数组a并初始化了其所有元素int a[2][3] { {1, 2, 3}, {4, 5, 6} }; //这行代码声明了一个整型指针pint *p; //二维数组a的首地址即第一个元素a[0][0]的地址赋值给指针p//这里使用了类型转换(int*)因为二维数组名a在大多数表达式中会被解释为指向其第一个元素即一维数组a[0]的指针而a[0]本身也是一个指针指向a[0][0]但类型是int (*)[3]。为了将其转换为int*类型以便与整型指针p匹配进行了显式类型转换p(int*)a; //打印指针p当前指向的值即a[0][0]的值1printf(%d , *p); //然后将指针p递增使其指向下一个整型元素即a[0][1]p; //然后打印该值即2printf(%d , *p); return 0;
} 这里将二维数组a强制转换为int*类型的指针p然后通过指针的移动可以看到是按照行优先的顺序访问元素的。
2.2二维数组的定义与初始化
2.2.1定义格式 格式为类型符 数组名[常量表达式1][常量表达式2];如int b[2][5];
2.2.2初始化方法
(1)完全初始化 可以按行初始化例如int c[2][3]{ {1, 2, 3}, {4, 5, 6}};也可以顺序初始化如int c[2][3]{1, 2, 3, 4, 5, 6};这两种方式是等价的。
(2)部分初始化 如果按行初始化如int d[2][3] { {1, 2}, {4} };未初始化的元素会被默认初始化为0。如果顺序初始化如int d[2][3]{1, 2, 4};也是前面的元素被初始化后面的为0。
2.3二维数组的引用与访问
2.3.1元素访问 通过下标访问和修改二维数组中的元素如a[i][j]表示访问第i行第j列的元素。
#include stdio.h int main()
{//定义并初始化一个2行3列的二维数组int a[2][3] { {1, 2, 3}, {4, 5, 6} }; //修改数组中第二行第三列的元素值为10a[1][2] 10; //打印数组中第二行第三列的元素值printf(%d, a[1][2]); //返回0表示程序正常结束return 0;
}2.3.2边界条件处理 在访问二维数组元素时要确保下标不越界。例如对于int a[3][4]行下标范围是0 - 2列下标范围是0 - 3,可以通过条件判断来避免越界。
#include stdio.h int main()
{//定义一个3行4列的二维数组int a[3][4]; //定义循环变量i和jint i, j; //使用嵌套循环遍历数组for (i 0; i 3; i) {for (j 0; j 4; j) {//检查索引是否在有效范围内if (i 0 i 3 j 0 j 4) {//根据索引计算并赋值给数组元素a[i][j] i * j; }}}// 返回0表示程序正常结束return 0;
}2.4二维数组的遍历
2.4.1嵌套循环 使用嵌套的for循环来遍历二维数组的每一个元素。
#include stdio.h int main()
{//定义并初始化一个2行3列的二维数组int a[2][3] { {1, 2, 3}, {4, 5, 6} }; //定义循环变量i和jint i, j; //使用嵌套循环遍历数组for (i 0; i 2; i) {for (j 0; j 3; j) {// 打印数组中的每个元素printf(%d , a[i][j]); }// 每行打印完毕后换行printf(\n); }// 返回0表示程序正常结束return 0;
}2.4.2遍历应用 在矩阵运算中如两个矩阵相加需要遍历两个二维数组对应的元素进行相加操作。在图像处理中二维数组可以表示图像的像素矩阵遍历数组可以对像素进行处理如灰度化、滤波等操作。
2.5二维数组的操作与应用
2.5.1行列交换 交换二维数组的行与列。
#include stdio.h int main()
{//定义并初始化一个2行3列的二维数组int a[2][3] { {1, 2, 3}, {4, 5, 6} }; //定义循环变量i和jint i, j; //使用嵌套循环遍历数组for (i 0; i 2; i) {for (j 0; j 3; j) {// 打印数组中的每个元素printf(%d , a[i][j]); }// 每行打印完毕后换行printf(\n); }// 返回0表示程序正常结束return 0;
}2.5.2查找操作 查找最大元素及其位置。
#include stdio.h int main()
{//定义并初始化一个3行4列的二维数组int a[3][4] { {1, 5, 3, 4}, {6, 2, 9, 7}, {8, 1, 3, 5} }; //初始化最大元素为数组的第一个元素int max a[0][0]; //初始化最大元素的行索引和列索引int max_i 0, max_j 0; //定义循环变量i和jint i, j; //使用嵌套循环遍历数组for (i 0; i 3; i) {for (j 0; j 4; j) {// 如果当前元素大于最大元素则更新最大元素及其位置if (a[i][j] max) {max a[i][j]; max_i i; max_j j; }}}//打印最大元素的值和位置printf(最大元素为%d位置在第%d行第%d列\n, max, max_i, max_j); //返回0表示程序正常结束return 0;
}2.5.3实际实际应用 在矩阵运算方面如矩阵乘法需要对二维数组进行复杂的操作。设A是m×n矩阵B是n×p矩阵结果C A×B是m×p矩阵其计算过程需要遍历A和B的行和列进行乘法和加法运算。 在数据存储方面二维数组可以用来存储表格数据如学生成绩表每行表示一个学生每列表示不同的科目成绩。
2.6二维数组与指针关系
2.6.1指针表示 二维数组可以用指针来表示。对于int a[3][4]a可以看作是一个指向包含4个int类型元素的数组的指针。可以通过指针来访问数组元素。
#include stdio.h int main()
{//定义并初始化一个3行4列的二维数组int a[3][4] { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} }; //定义一个指向包含4个整数的数组的指针int (*p)[4]; //将指针p指向数组a的第一行p a; //打印指针p所指向的行的第二个元素printf(%d, (*p)[1]); //返回0表示程序正常结束return 0;
}2.6.2指针遍历 使用指针遍历二维数组。
#include stdio.h int main()
{//定义并初始化一个2行3列的二维数组int a[2][3] { {1, 2, 3}, {4, 5, 6} }; //定义一个指向整数的指针int *p; //定义循环变量i和jint i, j; //将指针p指向数组a的第一个元素p (int*)a; //使用嵌套循环遍历数组for (i 0; i 2; i) {for (j 0; j 3; j) {// 打印指针p所指向的元素printf(%d , *p); // 指针p指向下一个元素p; }} // 返回0表示程序正常结束return 0;
}2.7二维数组的高级应用与技巧
2.7.1多维数组 三维数组的定义例如int a[2][3][4];它可以看作是由2个3×4的二维数组组成。初始化可以按层类似二维数组按行进行。
// 定义并初始化一个2层3行4列的三维数组int a[2][3][4] { { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} }, { {13, 14, 15, 16}, {17, 18, 19, 20}, {21, 22, 23, 24} } };
2.7.2算法实现 矩阵转置算法对于int a[3][4]转置后的矩阵b为int b[4][3]。
#include stdio.h /*** brief 将一个3x4的矩阵转置为4x3的矩阵* * param a 原始矩阵3x4* param b 转置后的矩阵4x3*/
void transpose(int a[3][4], int b[4][3])
{//使用嵌套循环遍历原始矩阵a的每个元素for (int i 0; i 3; i){for (int j 0; j 4; j) {// 将a[i][j]的值赋给b[j][i]实现矩阵的转置b[j][i] a[i][j];}}
}int main()
{//定义并初始化一个3x4的矩阵aint a[3][4] {{1, 2, 3, 4},{5, 6, 7, 8},{9, 10, 11, 12}};//定义一个4x3的矩阵b用于存储转置后的结果int b[4][3];//调用transpose函数将a转置为btranspose(a, b);//打印转置后的矩阵for (int i 0; i 4; i) {for (int j 0; j 3; j) {// 打印b[i][j]的值printf(%d , b[i][j]);}// 每行打印完毕后换行printf(\n);}// 返回0表示程序正常结束return 0;
}2.8二维数组在函数间传递
2.8.1传地址 当在函数间传递二维数组时传址是常用的方式。 这里printArray函数接收一个指向包含3个int类型元素的数组的指针通过这个指针可以在函数内部访问和操作二维数组。
#include stdio.h /** brief 打印一个二维数组* * param a 指向二维数组的指针数组的列数为3* param rows 二维数组的行数*/
void printArray(int (*a)[3], int rows)
{//定义循环变量i和jint i, j; //使用嵌套循环遍历数组for (i 0; i rows; i) {for (j 0; j 3; j){// 打印数组中的每个元素printf(%d , a[i][j]); }// 每行打印完毕后换行printf(\n); }
}int main()
{//定义并初始化一个2行3列的二维数组int a[2][3] { {1, 2, 3}, {4, 5, 6} }; //调用printArray函数打印数组aprintArray(a, 2); //返回0表示程序正常结束return 0;
}2.8.2传值 传值方式传递二维数组效率较低因为需要将整个数组的值进行复制。 在函数modifyArray中虽然形式上是传值但实际上由于数组名作为函数参数时会退化为指针所以这里本质上还是通过指针来操作数组。如果要在函数中修改原数组传址是更好的选择。
#include stdio.h /** brief 将一个二维数组中的每个元素乘以2* * param b 指向二维数组的指针数组的行数为2列数为3*/
void modifyArray(int b[2][3])
{//定义循环变量i和jint i, j; //使用嵌套循环遍历数组for (i 0; i 2; i) {for (j 0; j 3; j) {// 将数组中的每个元素乘以2b[i][j] b[i][j] * 2; }}
}int main()
{//定义并初始化一个2行3列的二维数组int a[2][3] { {1, 2, 3}, {4, 5, 6} }; //调用modifyArray函数将数组a中的每个元素乘以2modifyArray(a); //定义循环变量i和jint i, j; //使用嵌套循环遍历数组for (i 0; i 2; i) {for (j 0; j 3; j) {// 打印数组中的每个元素printf(%d , a[i][j]); }// 每行打印完毕后换行printf(\n); }// 返回0表示程序正常结束return 0;
}2.9二维数组操作的注意事项
2.9.1数组越界检测和处理 数组越界是常见错误如对于int a[3][4]如果访问a[3][0]就会越界。可以通过在访问数组元素时添加条件判断来检测越界情况。
#include stdio.h
int main()
{//定义一个3行4列的二维数组int a[3][4]; //定义循环变量i和jint i, j; //使用嵌套循环遍历数组for (i 0; i 3; i) {for (j 0; j 4; j) {// 检查数组索引是否越界if (i 0 i 3 j 0 j 4) {// 给数组元素赋值a[i][j] i * j; } else {// 打印数组越界访问的警告信息printf(数组越界访问可能发生在i %d, j %d\n, i, j); }}}// 返回0表示程序正常结束return 0;
}2.9.2调试方法 使用调试工具如gdb在Linux环境下可以设置断点查看变量的值等。例如在编译程序时加上-g选项然后在gdb中运行程序可以通过break命令设置断点print命令查看变量的值。
#include stdio.hint main()
{int a 10;int b 20;int sum a b;printf(The sum of %d and %d is %d\n, a, b, sum);return 0;
}1.编译程序(1)使用-g选项编译程序以便在调试时包含调试信息。(2)打开终端进入代码所在的目录然后输入以下命令bashgcc -g -o example example.c这将生成一个名为example的可执行文件。2.启动gdb在终端中输入以下命令启动gdbbashgdb example3.设置断点在gdb中使用break命令设置断点。例如在main函数的开始处设置断点bashbreak main4.运行程序使用run命令运行程序bashrun5.查看变量的值程序会在断点处停止此时可以使用print命令查看变量的值。例如查看a、b和sum的值bashprint aprint bprint sum6.继续执行程序使用continue命令继续执行程序直到下一个断点或程序结束bashcontinue7.退出gdb使用quit命令退出gdbbashquit 3.字符数组 目标:通过思维导图了解学习字符数组的核心知识点: 3.1字符数组的基本概念 字符数组的定义字符数组是C语言中用于存储一系列字符的数据结构其元素类型为char。
//就定义了一个长度为10的字符数组c
char c[10]; 字符数组与字符串的关系在C语言中字符串被视为以\0结尾的字符数组。
//这里系统会自动在字符h、e、l、l、o后面添加\0
char str[] hello;
3.2字符数组的初始化与赋值
3.2.1初始化方法 逐个字符赋值通过指定每个数组元素的值来初始化字符数组。
char c[5] {h, e, l, l, o}; 整体赋值在定义字符数组时使用花括号将一组字符包括结束符\0整体赋值给数组。
char c[6] {h, e, l, l, o, \0}; 字符串常量初始化可以直接使用字符串常量来初始化字符数组系统会自动添加结束符\0。
char str[] world;
3.2.2赋值注意事项 字符数组的大小在定义后是固定的不能像整型数组那样动态扩展。 赋值时如果提供的字符个数大于数组长度会导致语法错误。
//是错误的
char c[3] {a, b, c, d}; 如果提供的字符个数小于数组长度未赋值的元素将被自动定为空字符即\0但也可能无法确定。 字符数组的赋值只能对其元素进行一一赋值不能用字符串常量直接赋值除非在初始化时。
//是错误的
char c[5]; c hello;//是正确的
char c[5]; c[0]h; c[1]e;3.3字符串与字符串结束标记 字符串的定义在C语言中字符串是以\0作为结束标记的字符数组。 字符串的实际长度与数组长度的关系实际有效字符串的长度由\0的位置决定而不是由数组的长度决定。
//实际长度为3虽然数组可能分配了更多的空间
char str[] abc; 字符串结束标记的重要性在程序中往往依靠检测\0的位置来判定字符串是否结束。
3.4字符数组的输入与输出
3.4.1输入方法 使用scanf函数从键盘输入字符串scanf函数在读取字符串时遇到空格、制表符或换行符会停止读取但可以通过%[^\n]格式说明符来读取包含空格的字符串。
char str[100]; scanf(%[^\n], str); 使用gets函数从键盘输入字符串可以包含空格但只能输入一个字符串。
char str[100]; gets(str);。 不过gets函数存在缓冲区溢出的风险不建议在实际编程中使用。
3.4.2输出方法 使用printf函数输出字符串使用%s格式说明符。
char str[] hello; printf(%s, str);。 使用puts函数输出字符串并自动换行。
char str[] world; puts(str);。
3.5字符串处理函数
3.5.1 strcat函数 连接两个字符串将第二个字符串连接到第一个字符串的后面。
#include stdio.h // 包含标准输入输出库
#include string.h // 包含字符串处理库int main() {// 定义一个字符数组str1并初始化为hellochar str1[100] hello; // 定义一个字符数组str2并初始化为 worldchar str2[] world; // 使用strcat函数将str2连接到str1的末尾strcat(str1, str2); // 打印连接后的字符串printf(%s, str1); // 返回0表示程序正常结束return 0;
}3.5.2 strcpy函数 复制字符串将一个字符串复制到另一个字符串中。
#include stdio.h // 包含标准输入输出库
#include string.h // 包含字符串处理库int main() {// 定义一个字符数组str1并初始化为originalchar str1[100] original; // 定义一个字符数组str2并初始化为newchar str2[] new; // 使用strcpy函数将str2复制到str1中strcpy(str1, str2); // 打印复制后的字符串printf(%s, str1); // 返回0表示程序正常结束return 0;
}3.5.3strlen函数 strlen函数计算字符串长度。
#include stdio.h // 包含标准输入输出库
#include string.h // 包含字符串处理库int main() {// 定义一个字符数组str并初始化为hellochar str[] hello; // 使用strlen函数计算字符串str的长度int len strlen(str); // 打印字符串的长度printf(The length is %d, len); // 返回0表示程序正常结束return 0;
}3.5.4strcmp函数 strcmp函数比较两个字符串。
#include stdio.h // 包含标准输入输出库
#include string.h // 包含字符串处理库int main() {// 定义一个字符数组str1并初始化为abcchar str1[] abc; // 定义一个字符数组str2并初始化为abdchar str2[] abd; // 使用strcmp函数比较str1和str2的大小int result strcmp(str1, str2); // 根据比较结果打印相应的信息if (result 0) {printf(str1 is less than str2); } else if (result 0) {printf(str1 is greater than str2); } else {printf(str1 is equal to str2); }// 返回0表示程序正常结束return 0;
}3.6字符数组与字符指针比较
3.6.1字符数组 由若干个元素组成每个元素放一个字符有确定的内存地址。
//c的地址是确定的且数组在内存中占用连续的空间来存储字符
char c[5];
3.6.2字符指针 存放的是地址字符串/字符数组的首地址通过指针可以访问字符串中的字符。
//p中存储的是字符串hello的首地址
char *p hello; 在内存中字符指针本身占用一定的空间来存储地址而它指向的字符串常量存储在常量区字符数组存储在全局数据区或栈区。对于字符数组不能直接用一个字符串对其赋值而字符指针可以指向一个字符串常量。在函数传递中字符数组传递的是数组的首地址而字符指针直接传递地址。
3.7字符数组的应用实例 存储并处理用户输入的字符串。例如编写一个程序接收用户输入的名字然后进行一些处理如统计名字的长度等。 实现字符串的拼接、复制、比较等操作。如在文本处理中将多个小的字符串拼接成一个大的字符串。 用于文本处理、数据解析等场景。比如从一个包含很多信息的文本文件中通过字符数组来解析出特定的字符串内容。
3.8字符数组的多维应用
3.81定义 二维字符数组可以用来存储多个字符串。
//这里定义了一个二维字符数组可以存储3个长度不超过9加上\0的字符串char str[3][10]; 3.8.2初始化 可以像下面这样初始化二维字符数组。
char str[3][10] {{hello}, {world}, {!}};
3.8.3使用示例
#include stdio.h // 包含标准输入输出库int main() {// 定义一个3行10列的字符数组并初始化为三个名字char names[3][10] {{dididi}, {dadada}, {dududu}};int i;// 打印存储的名字for (i 0; i 3; i) {printf(%s\n, names[i]); // 打印每个名字并换行}// 返回0表示程序正常结束return 0;
}3.9字符数组的动态分配和释放 3.9.1动态分配 可以使用malloc、calloc、realloc函数来动态分配字符数组的内存。 例如使用malloc分配一个长度为n的字符数组。
#include stdio.h // 包含标准输入输出库
#include stdlib.h // 包含标准库函数如mallocint main() {// 定义一个整型变量n并初始化为10int n 10; // 使用malloc函数动态分配一块内存空间大小为n个字符char *p (char *)malloc(n * sizeof(char)); // 返回0表示程序正常结束return 0;
}3.9.2释放 使用free函数来释放动态分配的内存。 在动态分配内存时要注意内存泄漏问题如果不再使用动态分配的内存一定要及时释放。
#include stdio.h // 包含标准输入输出库
#include stdlib.h // 包含标准库函数如mallocint main() {// 定义一个整型变量n并初始化为10int n 10; // 使用malloc函数动态分配一块内存空间大小为n个字符char *p (char *)malloc(n * sizeof(char)); // 检查内存是否分配成功if (p NULL) {printf(内存分配失败\n);return 1;}// 使用分配的内存空间// 释放内存free(p);// 返回0表示程序正常结束return 0;
}3.10字符数组常见错误和注意事项 缓冲区溢出如使用gets函数时如果输入的字符串长度超过了字符数组的大小就会发生缓冲区溢出。解决方法是使用更安全的输入函数如fgets函数。 字符串越界在对字符数组进行操作时要确保操作不会超出数组的边界。例如在复制字符串时如果目标数组的空间不够就可能导致字符串越界。 未初始化的字符数组如果没有对字符数组进行初始化就使用可能会导致不可预测的结果。在使用前尽量对字符数组进行初始化。