深入理解指针(2)(C语言版)

devtools/2025/3/29 7:31:05/

文章目录

    • 前言
    • 一、数组名的理解
    • 二、使用指针访问数组
    • 三、一维数组传参的本质
    • 四、冒泡排序
    • 五、二级指针
    • 六、指针数组
    • 七、指针数组模拟二维数组
    • 总结

前言

在上一篇文章中,我们初步了解了指针的基本概念和用法。今天,我们将继续深入探索指针在数组、函数传参以及复杂数据结构中的应用,帮助大家更全面地掌握指针的精髓。
在这里插入图片描述

一、数组名的理解

数组名在 C 语言中是一个非常特殊的概念。它本质上是一个指向数组首元素的指针。例如,对于数组 int arr[5];arr 就是数组名,它指向数组的第一个元素 arr[0]。我们可以通过打印 arr&arr[0] 的值来验证这一点,它们的值是相同的。

#include <stdio.h>int main() {int arr[5] = {1, 2, 3, 4, 5};printf("arr的地址:\t\t%p\n", arr);printf("数组首元素的地址:\t%p\n", &arr[0]);return 0;
}

数组名的理解

运行结果会显示两个地址相同,这证明了数组名就是一个指向首元素的指针。

数组名作为一个指针,它具有一些特殊的性质。首先,数组名的值是常量,不能被修改。也就是说,我们不能对数组名进行赋值操作,如 arr = &arr[1]; 是非法的。其次,数组名的类型是指向数组中元素类型的指针。例如,对于 int arr[5];arr 的类型是 int*,即指向整型的指针。

二、使用指针访问数组

既然数组名是首元素的地址,我们就可以通过指针来访问数组中的元素。例如,*(arr + i) 就相当于 arr[i],表示数组中第 i 个元素的值。

#include <stdio.h>int main() {int arr[5] = {1, 2, 3, 4, 5};for (int i = 0; i < 5; i++) {printf("arr[%d] = %d\n", i, *(arr + i));}return 0;
}

在这里插入图片描述

这段代码通过指针的方式遍历了数组中的每个元素,输出结果与直接使用数组下标访问相同。

指针访问数组元素的原理是基于指针的偏移。指针本身存储的是一个内存地址,当我们对指针进行加法运算时,实际上是根据指针所指向的数据类型计算偏移量。例如,对于 int* ptr;ptr + 1 的值是 ptr 的值加上 sizeof(int),即指针指向下一个整数元素的地址。

三、一维数组传参的本质

当我们将一维数组作为参数传递给函数时,实际上传递的是数组的首地址。也就是说,函数内部接收到的并不是整个数组,而是一个指向数组首元素的指针。因此,在函数内部,我们可以通过指针来操作数组中的元素。

#include <stdio.h>void printArray(int arr[], int size) {for (int i = 0; i < size; i++) {printf("%d ", arr[i]);}printf("\n");
}int main() {int arr[5] = {1, 2, 3, 4, 5};printArray(arr, 5);return 0;
}

在这里插入图片描述

在这个例子中,printArray 函数接收一个指针和数组大小作为参数,然后通过指针访问数组中的元素并打印出来。

需要注意的是,当数组作为函数参数传递时,数组的大小信息会丢失。因此,我们需要额外传递一个参数来指定数组的大小,以便在函数内部正确地操作数组。

四、冒泡排序

冒泡排序是一种简单的算法>排序算法,它通过比较相邻元素的大小并交换位置来实现排序。在实现冒泡排序时,我们通常会使用指针来操作数组元素。

#include <stdio.h>void bubbleSort(int arr[], int size) {for (int i = 0; i < size - 1; i++) {for (int j = 0; j < size - 1 - i; j++) {if (arr[j] > arr[j + 1]) {int temp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = temp;}}}
}int main() {int arr[5] = {5, 3, 8, 1, 2};bubbleSort(arr, 5);printf("排序后的数组:");for (int i = 0; i < 5; i++) {printf("%d ", arr[i]);}return 0;
}

冒泡排序

这段代码实现了冒泡算法>排序算法,通过指针操作数组元素,将数组从小到大排序。

冒泡排序的时间复杂度为 O(n²),其中 n 是数组的大小。虽然它的效率不高,但对于小规模的数据集或者学习算法>排序算法的基本原理来说,冒泡排序是一个很好的起点。

五、二级指针

二级指针是指指向指针的指针。它在处理指针数组或者动态分配的二维数组时非常有用。例如,我们可以通过二级指针来操作一个字符串数组。

#include <stdio.h>int main() {char *strArray[] = {"Hello", "World", "C", "Language"};char **ptr = strArray;for (int i = 0; i < 4; i++) {printf("%s\n", *(ptr + i));}return 0;
}

二级指针

在这个例子中,strArray 是一个指针数组,ptr 是一个二级指针,指向 strArray 的首元素。通过 ptr 我们可以访问数组中的每个字符串。

二级指针的使用需要注意指针的初始化和解引用操作。在使用二级指针之前,必须确保它指向一个有效的内存地址,并且在解引用时要逐步进行,先获取一级指针,再访问最终的数据。

六、指针数组

指针数组是一个数组,其元素都是指针类型。常见的应用场景是存储多个字符串的首地址,方便对字符串进行操作。

#include <stdio.h>int main() {char *strArray[] = {"Apple", "Banana", "Cherry", "Date"};for (int i = 0; i < 4; i++) {printf("%s\n", strArray[i]);}return 0;
}

在这里插入图片描述

这里定义了一个指针数组 strArray,每个元素都是一个指向字符的指针,存储了不同水果名称的首地址。

指针数组的优势在于可以方便地对一组指针进行统一管理。例如,我们可以对指针数组中的字符串进行排序、查找等操作。

七、指针数组模拟二维数组

利用指针数组,我们可以模拟二维数组的操作。通过动态分配内存,我们可以根据需要创建不同大小的“二维数组”。

#include <stdio.h>
#include <stdlib.h>int main() {int rows = 3, cols = 4;int **array = (int **)malloc(rows * sizeof(int *));for (int i = 0; i < rows; i++) {array[i] = (int *)malloc(cols * sizeof(int));}// 初始化数组for (int i = 0; i < rows; i++) {for (int j = 0; j < cols; j++) {array[i][j] = i * cols + j + 1;}}// 打印数组for (int i = 0; i < rows; i++) {for (int j = 0; j < cols; j++) {printf("%d ", array[i][j]);}printf("\n");}// 释放内存for (int i = 0; i < rows; i++) {free(array[i]);}free(array);return 0;
}

在这里插入图片描述

这段代码通过指针数组模拟了一个 3 行 4 列的二维数组,实现了动态内存分配、初始化、打印和释放内存的操作。

在动态分配内存时,我们需要先为指针数组本身分配内存,然后再为每个指针元素分配相应的内存。这样可以灵活地控制每一行的大小,甚至可以创建不规则的二维数组。

总结

通过本文的学习,我们深入理解了指针在数组、函数传参以及复杂数据结构中的应用。从数组名作为指针的概念,到使用指针访问数组元素,再到一维数组传参的本质,我们逐步掌握了指针在数组操作中的核心技巧。同时,我们还学习了冒泡算法>排序算法的实现,以及二级指针、指针数组和指针数组模拟二维数组的用法。这些知识将为我们进一步学习 C 语言中的高级指针应用打下坚实的基础。在实际编程中,灵活运用指针可以让我们更高效地操作数据,实现复杂的功能。希望同学们能够多加练习,通过实际编写代码来加深对指针的理解和掌握。


http://www.ppmy.cn/devtools/171323.html

相关文章

Uniapp使用大疆SDK打包离线原生插件二

上一篇讲了如何下载及配置原生插件&#xff0c;今天深入的了解下如何将java代码的SDK引入Uniapp 一、配置libs: 在Android开发中&#xff0c;libs目录通常用于存放项目所需的第三方库文件。 将sdk中的包lib.5plus.base-release.aar、android-gif-drawable-release1.2.23.aa…

日志截断/日志中途清空/不停止程序

使用场景&#xff1a; nohup ./abc.sh > 123.log 2>&1 & 若想在不停止程序的前提下减小 123.log 的占用空间或者对日志进行分割&#xff0c;可采用如下方法&#xff1a; 1. 手动截断日志 可以手动截断日志文件&#xff0c;把文件内容清空&#xff0c;但保留文…

100天精通Python(爬虫篇)——第122天:基于selenium接管已启动的浏览器(反反爬策略)

文章目录 1、问题描述2、问题推测3、解决方法3.1 selenium自动启动浏览器3.2 selenium接管已启动的浏览器3.3 区别总结4、代码实战4.1 手动方法(手动打开浏览器输入账号密码)4.2 自动方法(.bat文件启动的浏览器)1、问题描述 使用selenium自动化测试爬取pdd的时候,通过携带…

Android 12.0 WiFi连接默认设置静态IP地址功能实现

1.前言 在12.0的系统rom定制化开发中,在定制化某些功能开发中,在wifi模块中,有产品需要要求设置wifi静态ip功能,而系统中wifi连接 后ip是动态的,每次开机后 连接wifi的ip就是不固定的,所以产品需要采用固定ip,就需要实现静态ip功能 2.WiFi连接默认设置静态IP地址功能实…

专题|Python贝叶斯网络BN动态推理因果建模:MLE/Bayes、有向无环图DAG可视化分析呼吸疾病、汽车效能数据2实例合集

原文链接&#xff1a;https://tecdat.cn/?p41199 作为数据科学家&#xff0c;我们始终在探索能够有效处理复杂系统不确定性的建模工具。本专题合集系统性地解构了贝叶斯网络&#xff08;BN&#xff09;这一概率图模型在当代数据分析中的创新应用&#xff0c;通过开源工具bnlea…

C++ 性能优化隐藏陷阱:从系统调用到并发开销的深度反思

作为一名C++技术专家,我深知性能优化不仅是代码层面的艺术,更是理解硬件与语言交互的科学。在现代计算中,C++的抽象为开发者提供了便利,却也隐藏了硬件的复杂性。如何揭开这些“谎言”,让代码与硬件协同工作?本文将以小案例为载体,通过优化前后的对比,深入剖析每个章节…

小程序跳转到h5页面

本组件使用useState、WebView、Taro的钩子&#xff0c;以及taro-hooks中的useRouter。组件内部使用了useRouter获取路由信息&#xff0c;从存储中获取openId和TOKEN&#xff0c;然后通过useReady生命周期钩子设置URL。分享功能部分使用useShareAppMessage处理&#xff0c;构建分…

怎么绑定一个计算属性或数据属性来控制元素的类名

在 Vue 中&#xff0c;你可以通过绑定计算属性或数据属性来控制元素的类名&#xff0c;这样能避免直接操作 DOM&#xff0c;符合 Vue 的响应式原理。下面分别介绍如何使用计算属性和数据属性来控制类名。 使用计算属性控制类名 使用计算属性控制类名 计算属性是基于响应式依赖进…