c语言数组学习笔记

  • C语言的数组是一种用于存储多个相同数据类型元素的数据结构。以下是与C语言数组相关的一些重要知识点:

  • 数组声明和初始化:

    • 声明数组的语法

    •  数据类型 数组名[数组大小];,例如 int numbers[5]; 声明了一个包含5个整数的数组。

    • 数组的元素可以在声明时初始化,例如 int numbers[5] = {1, 2, 3, 4, 5};。
    • 如果未显式初始化数组元素,它们将包含默认值,对于整数数组通常是0。
      • 对于C语言中的局部数组(即在函数内部声明的数组),如果未显式初始化数组元素,它们将包含随机值而不是默认值0。这是因为C语言不保证为局部数组分配默认值,而是将数组的内容留给开发者自行处理。
        • 例如,如果您声明一个整数数组但不初始化它,数组中的元素将包含未定义的值:
        • c
        • int numbers[5]; // 未初始化,数组元素中包含随机值
        • 为了确保数组的元素被初始化为0,您可以显式初始化数组,如下所示:
        • c
        • int numbers[5] = {0}; // 所有元素都初始化为0
        • 或者使用循环将所有元素设置为0:
        • c
        • int numbers[5];
        • for (int i = 0; i < 5; i++) {
        • numbers[i] = 0;
        • }
        • 请注意,全局数组(在函数外部声明的数组)会自动初始化为0,但局部数组不会,除非您显式初始化它们。
      • 全局数组是什么意思

        • 全局数组是在函数外部声明的数组,它具有全局作用域,可以在整个程序中访问。全局数组通常在程序的主函数之外定义,因此可以在多个函数中使用。
        • 以下是一个示例,展示了如何声明和使用全局数组:
        • c
        • #include
        • int globalArray[5]; // 声明一个全局整数数组,大小为5
        • void initializeArray() {
        • for (int i = 0; i < 5; i++) {
        • globalArray[i] = i * 2;
        • }
        • }
        • int main() {
        • initializeArray(); // 调用函数初始化全局数组
        • printf("Global Array Elements:\n");
        • for (int i = 0; i < 5; i++) {
        • printf("%d ", globalArray[i]);
        • }
        • return 0;
        • }
        • 在上面的示例中,globalArray 是一个全局数组,可以在 initializeArray 函数和 main 函数中访问。全局数组在程序的整个生命周期内都存在,因此可以在不同的函数中共享数据。这使得全局数组在需要在多个函数之间传递数据时非常有用。但要注意,滥用全局变量可能会导致程序难以维护和理解,因此应谨慎使用。
      • 局部数组什么意思

        • 局部数组是在函数内部声明的数组,它的作用域仅限于声明它的函数内部。这意味着局部数组只能在声明它的函数内部访问,对于其他函数来说是不可见的。局部数组的生命周期仅限于包含它的函数被调用的时间段,当函数执行完毕时,局部数组将被销毁,释放其占用的内存。
        • 以下是一个示例,展示了如何声明和使用局部数组:
        • c
        • #include
        • void processArray() {
        • int localArray[3]; // 声明一个局部整数数组,大小为3
        • // 初始化局部数组的元素
        • for (int i = 0; i < 3; i++) {
        • localArray[i] = i + 1;
        • }
        • // 打印局部数组的元素
        • for (int i = 0; i < 3; i++) {
        • printf("%d ", localArray[i]);
        • }
        • }
        • int main() {
        • processArray(); // 调用函数处理局部数组
        • // 尝试访问局部数组会导致编译错误,因为它在这个函数之外不可见
        • // printf("%d", localArray[0]); // 这会导致编译错误
        • return 0;
        • }
        • 在上面的示例中,localArray 是一个局部数组,它只能在 processArray 函数内部访问。在 main 函数中尝试访问 localArray 会导致编译错误,因为它在 main 函数的作用域之外不可见。一旦 processArray 函数执行完毕,localArray 将被销毁,不再存在于内存中。
        • 局部数组通常用于在函数内部临时存储和处理数据,它们的作用范围有限,不会对程序的其他部分产生影响。这有助于控制数据的作用域和生命周期,以提高代码的安全性和可维护性。
  • 数组元素访问:

    • 在C语言中,您可以使用下标操作符([])来访问数组中的元素。数组的下标从0开始,表示数组中的第一个元素。下面是有关数组元素访问的一些关键信息:
    • 数组元素访问的基本语法:

      • 数组元素的访问使用下标操作符([])进行。通常的语法是 数组名[索引],其中 数组名 是数组的名称,索引 是要访问的元素的位置,索引从0开始计数。
    • 示例:

      • int numbers[5]; // 声明一个包含5个整数的数组
      • numbers[0] = 10; // 将第一个元素设置为10
      • numbers[1] = 20; // 将第二个元素设置为20
      • int x = numbers[2]; // 从数组中读取第三个元素的值并存储在x中
    • 注意事项:

      • 索引必须是整数类型,通常是无符号整数。
      • 数组元素的索引必须在合法的范围内,即大于或等于0,小于数组的大小。
      • 访问超出数组边界的元素会导致未定义行为,可能会损害程序的稳定性和安全性。
    • 数组元素的修改:

      • 您可以使用下标操作符来修改数组中的元素的值,例如 numbers[0] = 100; 将第一个元素的值设置为100。
    • 数组元素的读取:

      • 您可以使用下标操作符来读取数组中的元素的值,例如 int value = numbers[1]; 会将第二个元素的值赋给变量 value。
        • 数组元素的地址:

          • 数组名本质上是一个指向数组首元素的指针,因此您可以使用 & 运算符获取数组元素的地址,例如 int *ptr = &numbers[0];。
  • 数组长度:

    • 数组的长度是在声明时指定的,它是数组能容纳的元素数量。例如,int numbers[5]; 表示数组 numbers 的长度为5。
  • 多维数组:

    • C语言支持多维数组,例如二维数组可以看作是一个表格或矩阵。
    • 声明二维数组的语法为 数据类型 数组名[行数][列数];。
    • 访问二维数组的元素需要使用两个下标,例如 matrix[1][2] 访问第2行第3列的元素。
    • 多维数组是包含多个维度(或称为行和列)的数组,通常用于表示矩阵、表格、图像等多维数据结构。在C语言中,多维数组可以是二维、三维,甚至更多维度的数组。以下是关于多维数组的基本知识:
    • 声明多维数组:

      • 声明多维数组的语法是 数据类型 数组名[维度1][维度2]...[维度n];,其中 维度1、维度2 等表示数组的不同维度。
      • 例如,声明一个二维整数数组可以使用 int matrix[3][3];。
    • 访问多维数组元素:

      • 多维数组的元素访问需要使用多个索引,每个索引对应于一个维度。
      • 例如,要访问二维数组 matrix 中的元素,可以使用 matrix[行索引][列索引] 的形式。
    • 初始化多维数组:

      • 多维数组可以在声明时进行初始化,例如:
      • c
      • int matrix[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
      • 初始化列表中的元素数量必须与数组的维度匹配。
    • 循环遍历多维数组:

      • 使用嵌套循环结构可以遍历多维数组的所有元素,嵌套循环是指在一个循环内部包含另一个循环的编程结构。也就是说,一个循环被放置在另一个循环的内部,这允许在外部循环的每次迭代中执行内部循环的多次迭代。嵌套循环通常用于处理多维数据结构(如二维数组)或需要多层迭代的问题。例如,遍历二维数组的示例:
      • c
      • #include
      • int main() {
      • int matrix[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
      • // 使用嵌套 for 循环遍历二维数组并打印每个元素
      • for (int i = 0; i < 3; i++) {
      • for (int j = 0; j < 3; j++) {
      • printf("%d ", matrix[i][j]);
      • }
      • printf("\n"); // 在外层循环结束后换行
      • }
      • return 0;
      • }
    • 多维数组的内存布局:

      • 多维数组在内存中是按照行主序(Row-Major Order)存储的,这意味着同一行的元素在内存中是连续存储的。
        • Row-Major Order(行主序)是一种多维数组的存储顺序。在行主序中,多维数组的元素按照行优先的顺序存储在内存中。这意味着在内存中,相邻行的元素会连续存储,而相邻列的元素会在内存中分开存储。
        • 考虑一个二维数组 matrix[3][3],其中包含如下数据:
        • 1 2 3
        • 4 5 6
        • 7 8 9
        • 在行主序中,内存中的存储布局将如下所示:
        • 1 2 3 4 5 6 7 8 9
        • 可以看到,第一行的元素(1、2、3)首先存储在内存中,然后是第二行的元素(4、5、6),最后是第三行的元素(7、8、9)。这使得在遍历数组时,按行访问元素的性能更好,因为相邻元素在内存中是连续的,可以有效利用缓存。这也是许多编程语言和计算机体系结构中默认的数组存储方式。
        • 与之相对应的是列主序(Column-Major Order),在列主序中,多维数组的元素按照列优先的顺序存储在内存中,相邻列的元素会连续存储。不同的编程语言和应用程序可能使用不同的存储顺序,因此在进行多维数组操作时,需要考虑数组的存储顺序以确保正确的访问。 C 语言中采用的行主序是一种常见的存储方式。
        • 在访问多维数组时,了解数组的存储顺序可以有助于编写更有效的代码,特别是在嵌套循环中遍历数组元素时。
      • 这一点对于数组的访问和性能很重要,因此在遍历多维数组时,通常外层循环用于遍历行,内层循环用于遍历列。
    • 多维数组的应用:

      • 多维数组在图像处理、矩阵运算、游戏开发等领域中经常被使用,用于表示和处理复杂的多维数据结构。
    • 以下是一个简单的示例,展示了如何声明、初始化和遍历一个二维数组:
    • c
    • #include
    • int main() {
    • int matrix[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
    • // 遍历二维数组并打印每个元素
    • for (int i = 0; i < 3; i++) {
    • for (int j = 0; j < 3; j++) {
    • printf("%d ", matrix[i][j]);
    • }
    • printf("\n"); // 换行
    • }
    • return 0;
    • }
    • 这个示例声明了一个3x3的二维整数数组 matrix,并使用嵌套循环遍历和打印每个元素的值。在实际应用中,多维数组可以用于更复杂的数据处理和表示。
  • 数组和指针:

    • 数组名本质上是一个指向数组首元素的指针。例如,int numbers[5]; 中的 numbers 可以看作是指向 numbers[0] 的指针。
    • 数组的元素可以通过指针算术运算来访问,例如 *(numbers + 2) 等价于 numbers[2]。
    • 当您使用数组名作为函数参数时,实际上传递了指向数组的第一个元素的指针。这是因为在函数调用时,数组名会被隐式转换为指向数组的指针,并传递给函数。
    • 以下是一个示例,说明数组名是指向数组首元素的指针的概念:
    • c
    • #include
    • void printArray(int arr[], int size) {
    • for (int i = 0; i < size; i++) {
    • printf("%d ", arr[i]);
    • }
    • printf("\n");
    • }
    • int main() {
    • int numbers[] = {1, 2, 3, 4, 5};
    • // 数组名 numbers 在函数调用中被隐式转换为指向数组的指针
    • printArray(numbers, 5);
    • return 0;
    • }
    • 在上面的示例中,printArray 函数接受一个整数数组和数组的大小作为参数。在 main 函数中,我们将数组名 numbers 传递给 printArray 函数。即使在函数参数中声明为 int arr[],实际上 arr 是指向数组 numbers 首元素的指针。这意味着在 printArray 函数内部,我们可以使用 arr 来访问数组元素,就像使用指针一样。
    • 这个概念在C语言中非常重要,因为它使得数组在函数间传递时更加高效,同时也意味着您可以使用指针操作来访问和修改数组的元素。
  • 数组的限制:

    • 数组在声明时需要指定固定的大小,这意味着数组的大小在运行时不能改变。
    • 如果数组的大小过大,可能会导致栈溢出,因此对于大型数据集,通常会使用动态分配的内存(malloc和free)。
  • 数组的遍历:

    • 使用循环结构(如for或while)可以方便地遍历数组的所有元素。
    • 在C语言中,您可以使用循环结构来遍历数组中的所有元素。通常,for 循环是最常用的方式来遍历数组,但也可以使用 while 循环或 do-while 循环,具体取决于您的需求。以下是使用 for 循环遍历数组的示例:
    • c
    • #include
    • int main() {
    • int numbers[] = {1, 2, 3, 4, 5}; // 声明并初始化一个整数数组
    • // 使用 for 循环遍历数组并打印每个元素
    • for (int i = 0; i < 5; i++) {
    • printf("%d ", numbers[i]);
    • }
    • return 0;
    • }
    • 在上面的示例中,我们使用 for 循环从数组的第一个元素开始遍历到最后一个元素。循环的控制条件是 i < 5,这确保了我们在数组的有效索引范围内进行遍历。
    • 如果数组的大小是已知的,可以将循环控制条件中的常数(例如5)替换为数组的大小。这有助于使代码更具可维护性,因为如果数组的大小发生变化,只需更新一次循环控制条件。
    • 在遍历数组时,您可以执行各种操作,例如访问数组元素、修改元素的值、计算元素的总和、查找特定元素等,具体取决于您的需求。使用循环结构是处理数组的一种常见且强大的方式。
    • 以下是一个简单的示例,展示了如何声明、初始化和遍历一个二维数组:
    • c
    • #include
    • int main() {
    • int matrix[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
    • // 遍历二维数组并打印每个元素
    • for (int i = 0; i < 3; i++) {
    • for (int j = 0; j < 3; j++) {
    • printf("%d ", matrix[i][j]);
    • }
    • printf("\n"); // 换行
    • }
    • return 0;
    • }
  • 数组作为函数参数:

    • 数组可以作为函数的参数传递给函数。通常需要传递数组的地址和数组的长度来遍历或修改数组元素。
    • 将数组名作为函数参数意味着您将整个数组传递给函数,以便在函数内部对数组进行操作。在C语言中,数组名作为函数参数将被隐式转换为指向数组首元素的指针,因此函数可以通过指针来访问数组的元素。
    • 这种方式使得在函数内部能够对数组进行修改,并且可以处理不同大小的数组,因为函数不需要知道数组的具体大小,只需知道数组的元素类型和指针。
    • 以下是一个简单的示例,演示了将数组名作为函数参数的情况:
    • c
    • #include
    • void modifyArray(int arr[], int size) {
    • for (int i = 0; i < size; i++) {
    • arr[i] *= 2; // 将数组中的每个元素乘以2
    • }
    • }
    • int main() {
    • int numbers[] = {1, 2, 3, 4, 5};
    • int size = sizeof(numbers) / sizeof(numbers[0]);
    • // 调用函数并将数组名作为参数传递
    • modifyArray(numbers, size);
    • // 打印修改后的数组
    • for (int i = 0; i < size; i++) {
    • printf("%d ", numbers[i]);
    • }
    • return 0;
    • }
  • 数组的初始化列表:

    • C语言允许使用初始化列表来初始化数组元素,如 {1, 2, 3, 4, 5}。
  • 数组的大小计算:

    • 使用 sizeof 运算符可以计算数组的字节大小,如 sizeof(numbers) 返回整个数组的字节数。
  • 跟本文有关的基础概念补充:

    • 内存地址什么意思

      • 内存地址是计算机内存中存储数据的位置标识。每个存储单元都有一个唯一的内存地址,以便计算机可以定位、读取和写入数据。内存地址通常用十六进制或八进制表示,例如 0x7FFF5432 或 0177256。
      • 以下是关于内存地址的一些重要概念:
      • 内存单元:内存中的每个数据单元都有一个唯一的地址,通常是按字节(byte)为单位编址的。这意味着每个内存地址对应一个字节的数据。
      • 指针:指针是一个变量,它存储了一个内存地址。指针可以用来访问和操作存储在该地址上的数据。在C语言中,指针类型指定了指针所指向的数据类型。
      • 取地址运算符:在C语言中,使用取地址运算符 & 来获取变量的内存地址。例如,&x 表示变量 x 的地址。
      • 内存分配和释放:在C语言中,可以使用函数如 malloc、calloc 和 realloc 来动态分配内存,以便在运行时存储数据。free 函数用于释放动态分配的内存,以防止内存泄漏。
      • 访问内存:可以使用指针来访问和操作内存中的数据。例如,通过 *ptr 可以访问指针 ptr 所指向的内存地址上的数据。
      • 地址运算:指针可以进行地址运算,如指针的加法和减法,以便访问相对于当前地址的其他内存位置。
      • NULL指针:NULL指针是一个特殊的指针,它不指向任何有效的内存地址。在C语言中,通常将未初始化的指针设置为NULL,以避免野指针问题。
      • 野指针:野指针是指指向未分配或已释放的内存地址的指针,访问野指针可能导致不可预测的行为和程序崩溃。
      • 了解内存地址的概念对于编程非常重要,特别是在涉及指针、动态内存分配和低级编程时。正确地管理内存地址可以确保程序的稳定性和安全性。
    • 动态分配内存什么意思

      • 动态分配内存是指在程序运行时(即动态时期)从计算机的堆(heap)中分配一块内存空间,以存储数据或对象。与之相对,静态分配内存是在编译时(即静态时期)分配内存,这是通过在程序的栈上分配内存或使用全局变量来实现的。
      • 动态分配内存的主要特点包括:
      • 灵活性:动态分配内存允许您在程序运行时根据需要分配和释放内存。这使得您能够在运行时动态地管理内存,适应不同的数据量和程序需求。
      • 内存管理控制:动态分配内存通常需要手动进行内存分配和释放。您可以使用函数如 malloc、calloc 和 realloc 来分配内存,并使用 free 函数来释放内存。这使得您有更多的控制权来管理内存。
      • 生存周期控制:动态分配的内存在分配时保持存在,直到显式释放为止。这允许数据在函数之间保持持久性,并允许创建具有不同生存周期的数据结构。
      • 避免内存浪费:动态分配内存允许您根据需要分配精确大小的内存,避免浪费过多的内存空间。这对于处理可变大小的数据集或对象非常有用。
      • 以下是动态分配内存的一些常见函数和用法:
      • malloc(size_t size):分配指定大小的内存块,并返回指向首地址的指针。
      • calloc(size_t num_elements, size_t element_size):分配一块能容纳指定数量元素的内存,每个元素的大小由 element_size 决定,并将内存初始化为零。
      • realloc(void *ptr, size_t new_size):更改先前分配的内存块的大小,可能会将其移动到新的内存位置,并返回新的指针。
      • free(void *ptr):释放先前分配的内存块,使其可供后续使用。
      • 使用动态分配内存时,需要谨慎管理内存分配和释放,以避免内存泄漏和野指针问题。动态内存分配通常用于处理不确定大小的数据结构,例如动态数组、链表和字符串,以及需要动态分配和释放资源的情况,如文件操作和网络通信。
    • heap什么意思

      • "堆"(heap)在计算机科学中有两种主要的含义,具体取决于上下文。
      • 内存堆(Memory Heap):
      • 在内存管理领域,"堆"指的是一种用于动态分配内存的数据结构,通常是一个大的内存池,程序可以在运行时从中请求和释放内存块。这种堆用于存储程序运行时动态创建的数据,例如动态数组、对象、数据结构等。
      • 堆内存由操作系统管理,程序可以使用特定的函数(如C语言中的malloc、calloc和free函数)来分配和释放堆内存。堆内存分配通常比栈内存分配更灵活,但也需要程序员负责管理内存的分配和释放,以避免内存泄漏和其他问题。
      • 二叉堆(Binary Heap):
      • 在数据结构领域,"堆"通常指的是一种特定类型的二叉树,称为"二叉堆"。它是一种经过优化的数据结构,常用于实现优先队列等抽象数据类型。二叉堆具有以下特点:
      • 它是一棵完全二叉树,通常以数组的形式存储。
      • 在最小堆(Min Heap)中,父节点的值小于或等于其子节点的值。在最大堆(Max Heap)中,父节点的值大于或等于其子节点的值。
      • 堆的根节点通常是最小(或最大)元素,因此可以快速获取最小(或最大)元素。
      • 堆支持快速插入和删除操作,并且在这些操作中保持堆的结构特性。
      • 总结来说,"堆"在不同的上下文中具有不同的含义。它可以是内存管理中的动态分配内存区域,也可以是数据结构中的一种特定类型的树结构。了解上下文有助于准确理解"堆"的含义。
    • 内存块什么意思

      • "内存块"是计算机内存中的一块连续的存储区域,通常包含一定数量的字节,用于存储数据或程序代码。内存块可以根据需要分配和释放,用于不同的目的,例如存储变量、数据结构、程序指令等。
      • 以下是有关内存块的一些重要概念和用法:
      • 内存块的大小:内存块的大小通常以字节为单位来衡量,例如,一个内存块可以包含1字节、4字节、8字节等不同大小的数据。
      • 内存块的地址:每个内存块都有一个唯一的内存地址,用于标识它在内存中的位置。程序可以使用内存地址来访问和操作内存块中的数据。
      • 动态分配内存块:在程序运行时,可以使用动态内存分配函数(如C语言中的malloc、calloc、realloc等)从堆内存中分配一块内存块,用于存储数据。这种分配方式称为动态分配,允许程序在运行时根据需要分配内存。
      • 静态分配内存块:在程序编译时,可以使用静态内存分配来为变量和全局数据分配内存块。这种分配方式称为静态分配,内存块的大小在编译时确定,通常在栈内存或全局数据区分配。
      • 内存块的用途:内存块可以用于存储各种数据,包括整数、浮点数、字符、字符串、结构体、对象等。程序可以使用内存块来存储临时数据、持久数据、程序指令等。
      • 内存块的释放:为了避免内存泄漏,程序需要在使用完内存块后将其释放。动态分配的内存块应该由程序员显式释放,静态分配的内存块则由系统在程序结束时自动释放。
      • 内存块是计算机内存管理中的基本单元,对于程序的性能和稳定性非常重要。了解如何正确分配、访问和释放内存块对于编程非常重要,特别是在处理动态数据结构和资源管理方面。
  • 补充说明:

    • 1'本人初次发表类似学习笔记,如有不当请原谅,如有好的建议可以评论指正
    • 2'然后说一说发文章目的,由于现在在我是一个双非的大二学生,学习荒废,自律性差,现在目标是每天更一篇笔记,监督自己学习
    • 3'我会继续努力,持续更新,和大家一起学习进步
    • 4'由于知识面过少,自己有很多东西不知道,文章详细知识点由我提问chatgpt辅助解答完成
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值