当前位置: 首页 > article >正文

C语言------数组从入门到精通

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一维数组动态初始化

   定义 : 

        在程序运行时为数组分配内存并初始化数组元素

        动态分配可以通过 malloccalloc 和 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.h>int 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.h>int 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.h>int 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.h>int 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.h>int 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.h>int 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.h>int 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.h>int 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 + n)int *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.h>int 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 + n)int *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一维数组动态遍历

        动态遍历数组通常涉及动态分配内存来创建数组,并使用循环来访问和操作这些元素。

        动态分配内存可以通过 malloccalloc 或 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.h>int 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.h>int 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.h>int 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;}// 打印初始数组元素(初始值为0)printf("初始数组元素: ");//使用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.h>int main() 
{int n;printf("请输入数组的大小: ");scanf("%d", &n);// 动态分配内存并初始化为0int *arr = (int *)calloc(n, sizeof(int));if (arr == NULL) {printf("内存分配失败\n");return 1;}// 打印初始数组元素(初始值为0)printf("初始数组元素: ");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.h>int 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.h>int 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[j+1],交换操作通过一个临时变量 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;// 内层循环查找从i+1到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); // 返回基准元素的索引i+1
}

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.h>int 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.h>void 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 < 3;j表示列,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]的值1)printf("%d ", *p); //然后将指针p递增,使其指向下一个整型元素(即a[0][1])p++; //然后打印该值(即2)printf("%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实际实际应用

        在矩阵运算方面,如矩阵乘法,需要对二维数组进行复杂的操作。设Am×n矩阵,Bn×p矩阵,结果C = A×Bm×p矩阵,其计算过程需要遍历AB的行和列进行乘法和加法运算。

        在数据存储方面,二维数组可以用来存储表格数据,如学生成绩表,每行表示一个学生,每列表示不同的科目成绩。

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],转置后的矩阵bint 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.h>int 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:在终端中输入以下命令启动gdb:bashgdb example3.设置断点:在gdb中,使用break命令设置断点。例如,在main函数的开始处设置断点:bashbreak main4.运行程序:使用run命令运行程序:bashrun5.查看变量的值:程序会在断点处停止,此时可以使用print命令查看变量的值。例如,查看a、b和sum的值:bashprint aprint bprint sum6.继续执行程序:使用continue命令继续执行程序,直到下一个断点或程序结束:bashcontinue7.退出gdb:使用quit命令退出gdb:bashquit

    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,并初始化为"hello"char str1[100] = "hello"; // 定义一个字符数组str2,并初始化为" world"char 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,并初始化为"original"char str1[100] = "original"; // 定义一个字符数组str2,并初始化为"new"char 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,并初始化为"hello"char 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,并初始化为"abc"char str1[] = "abc"; // 定义一个字符数组str2,并初始化为"abd"char 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函数。

            字符串越界:在对字符数组进行操作时,要确保操作不会超出数组的边界。例如在复制字符串时,如果目标数组的空间不够,就可能导致字符串越界。

            未初始化的字符数组:如果没有对字符数组进行初始化就使用,可能会导致不可预测的结果。在使用前尽量对字符数组进行初始化。

    相关文章:

    C语言------数组从入门到精通

    1.一维数组 目标:通过思维导图了解学习一维数组的核心知识点: 1.1定义 使用 类型名 数组名[数组长度]; 定义数组。 // 示例&#xff1a; int arr[5]; 1.2一维数组初始化 数组的初始化可以分为静态初始化和动态初始化两种方式。 它们的主要区别在于初始化的时机和内存分配的方…...

    FLTK - FLTK1.4.1 - 搭建模板,将FLTK自带的实现搬过来做实验

    文章目录 FLTK - FLTK1.4.1 - 搭建模板&#xff0c;将FLTK自带的实现搬过来做实验概述笔记my_fltk_test.cppfltk_test.hfltk_test.cxx用adjuster工程试了一下&#xff0c;好使。END FLTK - FLTK1.4.1 - 搭建模板&#xff0c;将FLTK自带的实现搬过来做实验 概述 用fluid搭建UI…...

    postgres基准测试工具pgbench如何使用自定义的表结构和自定义sql

    使用 pgbench 进行 PostgreSQL 性能测试时&#xff0c;可以自定义表结构和测试脚本来更好地模拟实际使用场景。以下是一个示例&#xff0c;说明如何自定义表结构和测试脚本。 自定义表结构 创建自定义表结构的 SQL 脚本。例如&#xff0c;创建一个名为 custom_schema.sql 的文…...

    开发者交流平台项目部署到阿里云服务器教程

    本文使用PuTTY软件在本地Windows系统远程控制Linux服务器&#xff1b;其中&#xff0c;Windows系统为Windows 10专业版&#xff0c;Linux系统为CentOS 7.6 64位。 1.工具软件的准备 maven&#xff1a;https://archive.apache.org/dist/maven/maven-3/3.6.1/binaries/apache-m…...

    Seed Edge- AGI(人工智能通用智能)长期研究计划

    Seed Edge 是字节跳动豆包大模型团队推出的 AGI&#xff08;人工智能通用智能&#xff09;长期研究计划12。以下是对它的具体介绍1&#xff1a; 名称含义 “Seed” 即豆包大模型团队名称&#xff0c;“Edge” 代表最前沿的 AGI 探索&#xff0c;整体意味着该项目将在 AGI 领域…...

    DeepSeek学术写作测评第二弹:数据分析、图表解读,效果怎么样?

    我是娜姐 迪娜学姐 &#xff0c;一个SCI医学期刊编辑&#xff0c;探索用AI工具提效论文写作和发表。 针对最近全球热议的DeepSeek开源大模型&#xff0c;娜姐昨天分析了关于论文润色、中译英的详细效果测评&#xff1a; DeepSeek学术写作测评第一弹&#xff1a;论文润色&#…...

    从单体应用到微服务的迁移过程

    目录 1. 理解单体应用与微服务架构2. 微服务架构的优势3. 迁移的步骤步骤 1&#xff1a;评估当前单体应用步骤 2&#xff1a;确定服务边界步骤 3&#xff1a;逐步拆分单体应用步骤 4&#xff1a;微服务的基础设施和工具步骤 5&#xff1a;管理和优化微服务步骤 6&#xff1a;逐…...

    Direct2D 极速教程(2) —— 画淳平

    极速导航 创建新项目&#xff1a;002-DrawJunpeiWIC 是什么用 WIC 加载图片画淳平 创建新项目&#xff1a;002-DrawJunpei 右键解决方案 -> 添加 -> 新建项目 选择"空项目"&#xff0c;项目名称为 “002-DrawJunpei”&#xff0c;然后按"创建" 将 “…...

    Lustre Core 语法 - 比较表达式

    概述 Lustre v6 中的 Lustre Core 部分支持的表达式种类中&#xff0c;支持比较表达式。相关的表达式包括 , <>, <, >, <, >。 相应的文法定义为 Expression :: Expression Expression | Expression <> Expression | Expression < Expression |…...

    C# 中 [MethodImpl(MethodImplOptions.Synchronized)] 的使用详解

    总目录 前言 在C#中&#xff0c;[MethodImpl(MethodImplOptions.Synchronized)] 是一个特性&#xff08;attribute&#xff09;&#xff0c;用于标记方法&#xff0c;使其在执行时自动获得锁。这类似于Java中的 synchronized 关键字&#xff0c;确保同一时刻只有一个线程可以执…...

    在win11系统笔记本中使用Ollama部署deepseek制作一个本地AI小助手!原来如此简单!!!

    大家新年好啊&#xff0c;明天就是蛇年啦&#xff0c;蛇年快乐&#xff01; 最近DeepSeek真的太火了&#xff0c;我也跟随B站&#xff0c;使用Ollama在一台Win11系统的笔记本电脑部署了DeepSeek。由于我的云服务器性能很差&#xff0c;虽然笔记本的性能也一般&#xff0c;但是…...

    03.01、三合一

    03.01、[简单] 三合一 1、题目描述 三合一。描述如何只用一个数组来实现三个栈。 你应该实现push(stackNum, value)、pop(stackNum)、isEmpty(stackNum)、peek(stackNum)方法。stackNum表示栈下标&#xff0c;value表示压入的值。 构造函数会传入一个stackSize参数&#xf…...

    【Super Tilemap Editor使用详解】(十五):从 TMX 文件导入地图(Importing from TMX files)

    Super Tilemap Editor 支持从 TMX 文件(Tiled Map Editor 的文件格式)导入图块地图。通过导入 TMX 文件,你可以将 Tiled 中设计的地图快速转换为 Unity 中的图块地图,并自动创建图块地图组(Tilemap Group)。以下是详细的导入步骤和准备工作。 一、导入前的准备工作 在导…...

    在FreeBSD下安装Ollama并体验DeepSeek r1大模型

    在FreeBSD下安装Ollama并体验DeepSeek r1大模型 在FreeBSD下安装Ollama 直接使用pkg安装即可&#xff1a; sudo pkg install ollama 安装完成后&#xff0c;提示&#xff1a; You installed ollama: the AI model runner. To run ollama, plese open 2 terminals. 1. In t…...

    低代码系统-产品架构案例介绍、明道云(十一)

    明道云HAP-超级应用平台(Hyper Application Platform)&#xff0c;其实就是企业级应用平台&#xff0c;跟微搭类似。 通过自设计底层架构&#xff0c;兼容各种平台&#xff0c;使用低代码做到应用搭建、应用运维。 企业级应用平台最大的特点就是隐藏在冰山下的功能很深&#xf…...

    编解码技术:最大秩距离码(Maximum Rank Distance Code)

    最大秩距离码&#xff08;Maximum Rank Distance Code&#xff0c;简称MRD码&#xff09;是一类用于处理矩阵或线性空间中错误校正的编码。其主要特点是在矩阵数据结构中具备检测和纠正错误的能力&#xff0c;设计目标是实现给定矩阵尺寸和错误纠正能力下的最大可能码字数。MRD…...

    Linux 4.19内核中的内存管理:x86_64架构下的实现与源码解析

    在现代操作系统中,内存管理是核心功能之一,它直接影响系统的性能、稳定性和多任务处理能力。Linux 内核在 x86_64 架构下,通过复杂的机制实现了高效的内存管理,涵盖了虚拟内存、分页机制、内存分配、内存映射、内存保护、缓存管理等多个方面。本文将深入探讨这些机制,并结…...

    python:taichi 绘制太极图

    安装 pip install taichi pip install opencv-python pycairo where ti # -- taichi 高性能可视化 Demo 展览 ti gallery D:\Python39\Lib\site-packages\taichi\examples\algorithm\circle-packing\ 点击图片&#xff0c;执行 circle_packing_image.py 可见 编写 taijitu.py 如…...

    Linux(19)——使用正则表达式匹配文本

    新年快乐&#xff01; 目录 一、正则表达式&#xff1a; 二、通过 grep 匹配正则表达式&#xff1a; 三、查找匹配项&#xff1a; 一、正则表达式&#xff1a; 正则表达式使用模式匹配机制查找特定内容&#xff0c;vim、grep 和 less 命令都可以使用正则表达式&#xff0c;P…...

    USB 3.1-GL3510-52芯片原理图设计

    USB 3.1-GL3510-52芯片原理图设计 端口功能与兼容性物理层集成与性能电源相关特性充电功能其他特性原理图接口防护ESD 保护要求 GL3510-52是一款由Genesys Logic&#xff08;创惟科技&#xff09;研发的USB转换芯片&#xff0c;具有以下特点&#xff1a; 端口功能与兼容性 它…...

    TCP是怎么判断丢包的?

    丢包在复杂的网络环境中&#xff0c;是一种常见的现象。 TCP&#xff08;传输控制协议&#xff09;作为一种可靠传输协议&#xff0c;内置了多种机制来检测和处理丢包现象&#xff0c;从而保证数据的完整性和传输的可靠性。本文将介绍TCP判断丢包的原理和机制。 一、TCP可靠传…...

    DevEco Studio 4.1中如何创建OpenHarmony的Native C++ (NAPI)程序

    目录 引言 操作步骤 结语 引言 OpenHarmony的开发工具变化很快&#xff0c;有的时候你安装以前的教程进行操作时会发现界面和操作方式都变了&#xff0c;进行不下去了。比如要在OpenHarmony中通过NAPI调用C程序&#xff0c;很多博文&#xff08;如NAPI篇【1】——如何创建含…...

    deepseek R1的确不错,特别是深度思考模式

    deepseek R1的确不错&#xff0c;特别是深度思考模式&#xff0c;每次都能自我反省改进。比如我让 它写文案&#xff1a; 【赛博朋克版程序员新春密码——2025我们来破局】 亲爱的代码骑士们&#xff1a; 当CtrlS的肌肉记忆遇上抢票插件&#xff0c;当Spring Boot的…...

    【PyQt5】数据库连接失败: Driver not loaded Driver not loaded

    报错内容如下&#xff1a; 可以看到目前所支持的数据库驱动仅有[‘QSQLITE’, ‘QMARIADB’, ‘QODBC’, ‘QODBC3’, ‘QPSQL’, ‘QPSQL7’] 我在网上查找半天解决方法未果&#xff0c;其中有一篇看评论反馈是可以使用的&#xff0c;但是PyQt5的版本有点低&#xff0c;5.12…...

    文献阅读 250128-Tropical forests are approaching critical temperature thresholds

    Tropical forests are approaching critical temperature thresholds 来自 <Tropical forests are approaching critical temperature thresholds | Nature> 热带森林正在接近临界温度阈值 ## Abstract: The critical temperature beyond which photosynthetic machinery…...

    使用 Redis List 和 Pub/Sub 实现简单的消息队列

    使用 Redis List 和 Pub/Sub 实现简单的消息队列 Redis 本身不是专门的消息队列系统&#xff0c;但它提供了多种数据结构&#xff08;如 List、Pub/Sub、Stream&#xff09;来实现消息队列功能。根据不同的业务需求&#xff0c;可以选择不同的方式&#xff1a; 在 Redis 中&a…...

    RockyLinxu9远程登录问题

    不能远程登录的问题解决 因为安装时没有勾选root远程登录权限&#xff0c;默认不能远程登录&#xff0c;需要修改 vim /etc/ssh/sshd_confi# 找到PermitRootLogin prohibit-password # 修改为:PermitRootLogin yessystemctl restart sshd 关闭防火墙 systemctl status fire…...

    DataWhale组队学习 leetCode task4

    1. 滑动窗口算法介绍 想象你正在用一台望远镜观察一片星空。望远镜的镜头大小是固定的&#xff0c;你可以通过滑动镜头来观察不同的星区。滑动窗口算法就像这台望远镜&#xff0c;它通过一个固定或可变大小的“窗口”来观察数组或字符串中的连续区间。 滑动操作&#xff1a;就像…...

    升级到Mac15.1后pod install报错

    升级Mac后&#xff0c;Flutter项目里的ios项目运行 pod install报错&#xff0c; 遇到这种问题&#xff0c;不要着急去百度&#xff0c;大概看一下报错信息&#xff0c;每个人遇到的问题都不一样。 别人的解决方法并不一定适合你&#xff1b; 下面是报错信息&#xff1a; #…...

    [c语言日寄]越界访问:意外的死循环

    【作者主页】siy2333 【专栏介绍】⌈c语言日寄⌋&#xff1a;这是一个专注于C语言刷题的专栏&#xff0c;精选题目&#xff0c;搭配详细题解、拓展算法。从基础语法到复杂算法&#xff0c;题目涉及的知识点全面覆盖&#xff0c;助力你系统提升。无论你是初学者&#xff0c;还是…...