2024-2025-1 学号:20241301 《计算机基础与程序设计》第九周学习总结

|这个作业属于哪个课程|2024-2025-1-计算机基础与程序设计|
|这个作业要求在哪里|2024-2025-1计算机基础与程序设计第一周作业|
|这个作业的目标|<复习知识,巩固基础>|
|作业正文|https://www.cnblogs.com/HonJo/p/18566259|

一、教材学习内容总结
(一)指针与数组
在C语言中,指针和数组有着密切的关系。理解它们之间的关系对于有效地使用C语言至关重要。以下是指针与数组之间的一些关键联系:

1. 数组名作为指针

在C语言中,数组名可以被用作指向数组第一个元素的指针。当你有一个数组时,如 int arr[10];arr 本身就是一个常量指针,指向数组的第一个元素。这意味着 arr&arr[0] 是等价的。

2. 通过指针访问数组元素

你可以使用指针运算来访问数组中的元素。如果你有一个指向数组第一个元素的指针 int *p = arr;,那么你可以通过 p[i] 来访问数组的第 i 个元素,这与 arr[i] 是等价的。

3. 指针和数组的遍历

在遍历数组时,你可以使用一个指针来逐个移动到数组的每个元素。例如:

int arr[] = {1, 2, 3, 4, 5};
int *p = arr;
for (int i = 0; i < 5; i++) {
    printf("%d ", *(p + i)); // 等价于 printf("%d ", arr[i]);
}

4. 指针和数组的内存布局

数组在内存中是连续存储的,这意味着你可以使用指针运算来移动到下一个元素。p + i 实际上是将指针 p 向前移动 i 个元素的大小。

5. 传递数组到函数

当你将数组传递给函数时,实际上传递的是指向数组第一个元素的指针。因此,函数接收到的是数组的指针副本,任何对数组元素的修改都会反映到原始数组上。

void func(int arr[]) {
    // 这里的 arr 实际上是一个指向数组第一个元素的指针
}

int main() {
    int myArray[10];
    func(myArray); // 传递 myArray 实际上是传递 &myArray[0]
    return 0;
}

6. 指针数组和数组指针

  • 指针数组:一个包含指针的数组,例如 int *arr[10];
  • 数组指针:一个指向数组的指针,例如 int (**arr)[10];

7. 多维数组和指针

对于多维数组,每一维都可以看作是指针。例如,对于 int arr[10][5];arr 是一个指向包含5个整数的数组的指针,arr[i] 是一个指向整数的指针。

8. 指针和数组的安全性

使用指针访问数组时,必须小心不要越界,因为这会导致未定义行为,可能引发程序崩溃或数据损坏。

理解指针和数组之间的关系对于高效、灵活地操作数组至关重要,也是理解更高级的内存操作和数据结构(如链表、树等)的基础。

(二)函数指针
函数指针是指向函数的指针,它存储了一个函数的地址。在C语言中,函数也被视为内存中的对象,因此它们有自己的地址。函数指针可以被用来存储函数的地址,并且可以像使用普通指针一样使用它们来调用函数。

函数指针的声明

声明一个函数指针时,需要指定指针指向的函数的返回类型和参数类型。语法如下:

返回类型 (*指针名称)(参数类型列表);

例如,假设有一个返回 int 并接受两个 int 参数的函数,声明一个指向这种函数的指针如下:

int (*funcPtr)(int, int);

函数指针的初始化

你可以将函数指针初始化为指向一个具体的函数:

int add(int a, int b) {
    return a + b;
}

int (*funcPtr)(int, int) = add; // 将函数指针指向 add 函数

调用函数指针

通过函数指针调用函数与直接调用函数类似,只需要在函数指针后加上括号和参数:

int result = funcPtr(5, 10); // 调用 add 函数

函数指针作为参数

函数指针经常作为参数传递给其他函数,这允许在运行时决定调用哪个函数。例如,你可以编写一个函数,它根据传入的函数指针来处理不同的操作:

void applyFunction(int a, int b, int (*func)(int, int)) {
    int result = func(a, b);
    printf("Result: %d\n", result);
}

int main() {
    applyFunction(3, 4, add); // 传递 add 函数
    return 0;
}

函数指针数组

你也可以创建函数指针数组,这允许你根据索引来选择不同的函数:

int (*funcArray[5])(int, int) = {add, subtract, multiply, divide, modulo};

// 调用数组中的函数
int result = funcArray[0](10, 5); // 调用 add 函数

注意事项

  • 确保函数指针指向的函数的签名(参数类型和返回类型)与函数指针声明时的签名匹配。
  • 未初始化的函数指针是危险的,因为它们可能指向任意内存地址,使用未初始化的函数指针调用函数会导致未定义行为。
  • 函数指针可以用来实现回调函数,策略模式等高级编程技巧。

函数指针是C语言中一个强大的特性,它提供了函数级别的抽象和灵活性。在C语言库中,特别是在信号处理和回调函数中,函数指针被广泛使用。

(三)内存分配
在C语言中,内存分配主要分为两类:栈内存分配和堆内存分配。

栈内存分配(自动内存分配)

栈内存分配是自动的,通常用于局部变量的存储。当函数被调用时,其局部变量的存储空间会自动在栈上分配。当函数返回时,这些局部变量的存储空间会自动释放。

int func() {
    int localVar = 10; // 栈内存自动分配
    // 使用 localVar
    return localVar;
}

堆内存分配(动态内存分配)

堆内存分配是手动的,通常使用 malloccallocreallocfree 函数来管理。

  • malloc:分配指定大小的内存块,返回指向它的指针。
  • calloc:分配指定数量的元素,每个元素指定大小的内存块,并将它们初始化为0。
  • realloc:重新分配内存块的大小,可以增加或减少。
  • free:释放之前分配的内存。

使用 mallocfree

#include <stdlib.h>

int *allocateMemory() {
    int *ptr = (int *)malloc(sizeof(int) * 10); // 分配一个可以存储10个int的内存块
    if (ptr == NULL) {
        // 处理内存分配失败
    }
    // 使用内存
    free(ptr); // 释放内存
    return ptr;
}

使用 callocfree

#include <stdlib.h>

int *allocateMemory() {
    int *ptr = (int *)calloc(10, sizeof(int)); // 分配10个int的内存块,并初始化为0
    if (ptr == NULL) {
        // 处理内存分配失败
    }
    // 使用内存
    free(ptr); // 释放内存
    return ptr;
}

注意事项

  • 检查 malloccallocrealloc 的返回值是否为 NULL,以确保内存分配成功。
  • 总是使用 free 释放使用 malloccallocrealloc 分配的内存,避免内存泄漏。
  • 避免多次释放同一块内存,这可能会导致未定义行为。
  • 堆内存分配和释放是编程中常见的错误来源,需要谨慎处理。

堆内存分配提供了更大的灵活性,允许程序在运行时根据需要分配和释放内存。这对于处理大小未知的数据或大量数据时非常有用。然而,这也意味着程序员需要更加小心地管理内存,以避免内存泄漏和其它内存相关的错误。

二、教材学习中遇到的问题
(一)只用指针遍历数组
在C语言中,使用指针遍历数组是一种常见的操作。你可以使用指针算术来遍历数组中的每一个元素。以下是如何使用指针遍历数组的步骤:

1. 定义数组和指针

首先,定义一个数组和一个指向数组第一个元素的指针。

int array[] = {10, 20, 30, 40, 50};
int *ptr = array; // 指针指向数组的第一个元素

2. 使用循环遍历数组

使用一个循环,通过指针递增来遍历数组中的每个元素。

for (int i = 0; i < sizeof(array) / sizeof(array[0]); i++) {
    printf("%d ", *ptr); // 打印当前指针指向的元素的值
    ptr++; // 将指针移动到下一个元素
}

在这个例子中,sizeof(array) / sizeof(array[0]) 计算数组中的元素数量。*ptr 是解引用操作符,它获取指针指向的元素的值。

3. 使用指针和循环变量

你也可以使用循环变量来控制指针的移动。

for (int i = 0; i < sizeof(array) / sizeof(array[0]); i++) {
    printf("%d ", array + i); // 打印当前索引处的元素的值
}

这里,array + i 是数组的第 i 个元素的地址,*(array + i) 将是该地址处的值。

4. 使用指针和循环变量(指针递增)

另一种常见的方法是直接在循环中递增指针。

for (int *ptr = array; ptr < array + sizeof(array) / sizeof(array[0]); ptr++) {
    printf("%d ", *ptr); // 打印当前指针指向的元素的值
}

在这个例子中,指针 ptr 从数组的开始地址开始,每次循环递增 ptr 以移动到下一个元素,直到它指向数组的末尾。

5. 使用标准库函数

如果你使用的是C99或更新的标准,你可以使用 sizeof 关键字来获取数组的大小,这使得代码更加简洁。

for (int *ptr = array; ptr < array + sizeof(array) / sizeof(array[0]); ptr++) {
    printf("%d ", *ptr);
}

注意事项

  • 确保不要越界访问数组,这意味着指针不应该超过数组的末尾。
  • 使用指针遍历数组时,要小心不要解引用未初始化或非法的指针。
  • 在多维数组的情况下,你需要使用多个指针或指针的指针来遍历数组。

使用指针遍历数组是一种高效的方法,它允许你直接操作内存中的数据,但也需要你注意指针的使用和内存安全。
(二)如何避免内存泄漏?
在C语言中避免内存泄漏,可以遵循以下几个原则和实践:

  1. 及时释放内存:在使用完动态分配的内存后,一定要记得调用free()函数来释放内存。这是避免内存泄漏的基本做法。

  2. 避免重复释放内存:一旦内存被释放,应避免再次释放同一块内存。这可以通过在释放后将指针设置为NULL来实现,从而防止悬垂指针问题。

  3. 检查内存分配的返回值:在使用malloc()calloc()realloc()分配内存时,必须检查返回值是否为NULL,以确保内存分配成功。如果分配失败,应避免使用该内存块。

  4. 使用内存泄漏检测工具:使用如Valgrind、AddressSanitizer等工具来检测程序中的内存泄漏。这些工具可以分析程序的内存使用情况,发现潜在的内存泄漏问题。

  5. 编写测试代码:针对可能出现内存泄漏的代码段,编写专门的测试代码来验证其正确性。通过模拟各种场景和边界条件,确保内存分配和释放的正确性。

  6. 日志记录和监控:在关键位置添加日志记录,以便跟踪内存分配和释放的过程。同时,可以使用监控工具来实时观察程序的内存使用情况,及时发现异常。

  7. 避免野指针和悬垂指针:确保指针在使用前已经被正确初始化,避免指向随机或无效的内存地址。

  8. 团队合作和代码审查:通过团队成员之间的相互检查和交流,可以及时发现并纠正潜在的内存泄漏问题。定期的代码审查和测试也是确保程序质量的关键环节。

  9. 使用智能指针:虽然C语言没有内置的智能指针,但可以通过封装内存分配和释放逻辑来模拟智能指针的行为,自动管理内存。

  10. 优化内存使用:通过减少不必要的内存分配和重用已分配的内存,可以降低内存泄漏的风险。

遵循这些原则和实践,可以有效地减少C语言中内存泄漏的风险,提高程序的稳定性和性能。

(三)基于AI的学习




posted @   HonJo  阅读(14)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
点击右上角即可分享
微信分享提示