0、前言:

  • 动态内存分配是一个重要概念,要和静态数组对比着学习;
  • 指针和数组搭配在一起,让指针理解的难度上了一个台阶,尤其是二维数组搭配指针,要获取数组的值,什么时候“取地址”,什么时候“解引用”都需要深刻理解一些概念才能正确使用指针和数组。
  • 写这些东西,就是把自己学习的笔记记录下来,供自己日后翻找,若是与此同时能给别人提供些帮助,那就更好了。

1、动态内存分配:

  • ★前提知识:内存分为栈和堆,
    • “栈”是由编译器自动分配和释放,无需程序员手动操作。当函数执行结束时,其内部的局部变量会被自动弹出栈并释放内存。主要存储局部变量函数参数、返回地址等。静态分配,编译时就能确定所需内存大小。注意如果一个数组定义在自定义函数当中,那它就位于栈当中,函数周期结束,该数组自动释放。
    • “堆”需要程序员手动分配(如 C 语言的malloc)和释放(如free),否则可能导致内存泄漏。主要存储动态分配的对象、数组、大型数据结构等。动态分配,运行时才能确定所需内存大。
  • 在堆中开辟内存空间的三种方式:malloc、calloc、realloc;开辟堆空间成功后,都会返回一个 void* 类型指针,所以需要根据空间存储内容,强转这个指针。
  • 数组是静态内存分配,数组大小必须是常量。是和动态内存分配相对应的一种连续空间开辟的方式。C语言中二维数组空间地址连续,可以用指针遍历。
    • malloc:依赖于头文件 stdlib.h,函数声明:void* malloc(size_t size); // 从堆上分配一块大小为size的“连续”空间, 并且返回它的首地址。
    • calloc:malloc不会像数组初始化一样把开辟出的内存空间初始化,这时就要使用calloc,会初始化空间;
    • realloc:在不改变原来空间内容的情况下,对空间进行缩放。如果扩大了空间,就新增空间,但新增的空间不会初始化,然后返回这块空间首地址,如果缩小了空间,就会截断多余空间,其余空间内容保持不变(缩小可能导致数据丢失),然后返回这块空间首地址。使用realloc之后,会自动释放之前的空间,切记不要重复释放之前的空间,否则程序会报错。
  • sizeof运算符是无法计算malloc、calloc、realloc动态生成的空间大小的,但是通过sizeof可以获取数组的总的字节数。
#include<stdio.h>
#include<stdlib.h>// 在堆中用malloc开辟空间,存放五个double型数据,打印
void mal_loc() {// 开辟空间,把首地址给指针pdouble* p = (double*)malloc(sizeof(double) * 5);int i;// 给空间元素赋值for (i = 0; i < 5; i++) {if (p != NULL) {*(p + i) = (double)(5 + i) / 2;}}// 使用这块空间for (i = 0; i < 5; i++) {if (p != NULL) {printf("%f\n", *(p + i));  // 也可以用p[i]遍历}}// 释放这块空间free(p);// 指针指向空p = NULL;
}// 在堆中用calloc开辟空间,存放五个double型数据,验证calloc是否会初始化空间
void cal_loc() {// 开辟空间,把首地址给指针pdouble* p = (double*)calloc(5, sizeof(double));int i;// 使用这块空间for (i = 0; i < 5; i++) {if (p != NULL) {printf("%f\n", *(p + i)); // 也可以用p[i]遍历}}// 释放这块空间free(p);// 指针指向空p = NULL;
}// 尝试使用realloc对calloc开辟的5个int空间,缩小至4个int空间,验证数据丢失
void real_loc() {// 用calloc开辟空间;int* p = (int*)calloc(5, sizeof(int));if (p == NULL) {return;}int i;// 给最后一位设为1;for (i = 0; i < 5; i++) {if (i == 4) {*(p + i) = 1;printf("%d, ", *(p + i));}else {printf("%d, ", *(p + i));}}printf("\n--------------\n");// 用realloc缩小空间;int* newp = (int*)realloc(p, 4*sizeof(int));// 验证缩小空间是否成功if (newp == NULL) {free(p); p = NULL;// 避免出现悬空指针p = NULL;// 避免出现悬空指针return;}else {p = NULL;// 避免出现悬空指针// 创建成功,测试空间当中的内容for (i = 0; i < 4; i++) {printf("%d, ", *(newp+i)); // 验证结果}// 最后释放空间和避免出现悬空指针free(newp); newp = NULL;}
}int main()
{/*mal_loc();printf("-------------\n");cal_loc();printf("-------------\n");*/real_loc();return 0;
}

2、指针和常量:

  • 常量指针:指针指向的值不可以通过指针改变;也就是说*p = num 这条语句失效。
  • 指针常量:指针的指向不能变,也就是说 p = &num 这条语句失效。
// 常量指针
int a = 99;
int const* p1 = &a;
//*p1 = 100;  // 会报错// 指针常量
int b = 99;
int* const p2 = &b;
*p2 = 100;
//p2 = &a;  // 会报错

3、各种指针类型:一些指针在江湖上的诨名

  • 万能指针:void* p,这种万能指针,可以强转为其他任何类型的指针:
int *p2 = (int*)vp;  // 显式转换:void* → int*
  • 悬空指针:指针曾经指向有效的内存,但该内存已被释放或失效。
int *p = (int*)malloc(sizeof(int));
*p = 42;
free(p);  // 内存被释放
// 避免悬空指针:p = NULL;
printf("%d", *p);  // 悬空指针!p 指向的内存已无效
  • 空指针:指向为NULL的指针,int *p = NULL; // 空指针
  • 野指针:未初始化的指针,指向地址是随机的,int *p; // 野指针!未初始化

3、指针和数组:

  • ★首先搞明白什么是“指针数组”什么是“数组指针”这个很重要
    • 指针数组:本质是数组,数组的每个元素是一个指针。
    • 数组指针:指向一整个数组的指针。
    • 具体的代码实例:
// ---------指针数组:
int a = 10, b = 20, c = 30;
int *arr[3];  // 指针数组:3个元素的数组,每个元素是 int* 类型
arr[0] = &a;
arr[1] = &b;
arr[2] = &c;printf("%d", *arr[1]);  // 输出 20(通过指针访问 b 的值)
const char *names[] = {"Alice", "Bob", "Charlie"};  // 3个字符串的地址
printf("%s", names[0]);  // 输出 "Alice"
printf("%c\n", (names[0])[2]); // i
/*
names 是一个数组([] 表示数组)。
数组的每个元素是 const char*(指向常量字符的指针)。
因此,names 是一个 指针数组(数组的元素是指针),且这些指针指向 const char(常量指针)
*/// ---------数组指针
int arr[5] = {1, 2, 3, 4, 5};
int (*p)[5];  // 数组指针:指向一个包含 5 个 int 的数组
p = &arr;     // p 指向整个数组 arrprintf("%d", (*p)[2]);  // 输出 3(解引用 p 得到数组 arr,再访问下标 2)
  • 通过二维数组的例子深度理解下数组和指针之间的关系:
// 获取二维数组中第一个一维数组首地址
int arr[3][3] = {{1,2,3},{4,5,6},{7,8,9}
};
printf("*arr = %p\tarr[0]=%p\t&arr[0][0]=%p\tarr+0=%p\n", *arr, arr[0], &arr[0][0], arr + 0);
// 获取二维数组中第二个一维数组首地址
printf("tarr[1]=%p\t&arr[1][0]=%p\tarr+1=%p\n", arr[1], &arr[1][0], arr + 1);
// 获取二维数组中第三个一维数组首地址
printf("tarr[2]=%p\t&arr[2][0]=%p\tarr+2=%p\n", arr[2], &arr[2][0], arr + 2);
/*
理解 arr + 0/1/2作为地址的方式很简单,对于arr数组而言,里面的一维数组就是它的元素,arr
就是这个以一维数组作为元素的数组的首地址,因此,arr每加一个单位,就是移动一个元素的位置。
*/
  • 检验一下,在下面的代码中,请说出常量指针是谁?指针数组又是谁?
const char *names[] = {"Alice", "Bob", "Charlie"}; 
// 在这个代码中,指针数组是names,其中存放的都是常量指针

4、函数指针:

  • 类比数组指针记忆,数组指针是指向整个数组的指针,函数指针就是指向整个函数的指针。
  • 函数指针的定义
#include<stdio.h>int add(int a, int b) {return a + b;
}
int sub(int a, int b) {return a - b;
}
int main() {// 定义函数指针int (*Add)(int, int);Add = add;int (*Sub)(int, int);Sub = sub;// 借助指针调用printf("%d\n", Add(1, 2)); // 3printf("%d\n", Sub(3, 1)); // 2typedef int(*Func)(int, int);  // 使用了这个重命名之后,就相当于用AddFunc代替了 int 函数名 ( int 参数1名, int 参数2名)   这种类型的指针名Func p1 = add;Func p2 = sub;printf("%d\n", p1(2, 1)); // 3printf("%d", p2(2, 1)); // 1return 0;
}
  • 在给函数指针类型用typedef 起别名的时候,发现对typedef起别名时,简单的类型还好写,这种复杂类型写起来就比较吃力了。因此总结如下:

1、给基本数据类型创建别名:typedef int Integer; // 为int起别名Integer
2、为指针类型创建别名:typedef int* IntPtr; // 为int*起别名IntPtr
3、为数组类型创建别名:typedef int IntArray5[5]; // 为数组类型创建别名(表示"包含5个int元素的数组")。例如:IntArray5 arr = {1, 2, 3, 4, 5}; // 等价于 int arr[5] = {1,2,3,4,5};
4、 ★为函数指针创建别名(最常用场景之一):typedef int (*CalcFunc)(int, int); // 定义一个函数指针类型(接收两个int,返回int)
CalcFunc func1 = add; // 假设add是已经定义好的函数,func1是CalcFunc类型的函数指针;
上述函数指针其实就相当于:int (*func1)(int, int) = add;
- 总结:在给数组或者函数指针起别名的时候,方法就和定义数组或者定义函数时写法一样,这两种相对其他数据类型起别名都比较特殊一点。

  • 函数指针的用途之一:回调函数
    • 回调函数就是往函数当中通过函数指针作为形参传递函数
    • 我在学习回调函数的时候产生过这样的疑问,为什么明明可以在一个函数当中就调用另一个函数,还非得用回调函数?经过学习我想通了,函数调用函数固然可以,但每次都是调用固定函数,而采用回调函数,就可以动态选择传入函数当中的函数。
#include<stdio.h>
// 调用函数的函数:作用是判断数组当中有几个1
int oneNum(int(*arr), int len, int (*Fun)(int)) {int i, count = 0;for (i = 0; i < len; i++) {if (Fun(arr[i])) {count += 1;}}return count;
}// 被调用的函数:作为条件判断当前值是否为1
int oneNo(int a)
{if (a == 1) {return 1;}else {return 0;}
}
// c语言标准库中快速排序qsort的回调函数
int cmp(void const * a, const void* b) {//return *(int const*)a - *(int const*)b; // 升序排列return *(int const*)b - *(int const*)a; // 降序排列
}int main() {int a[5] = { 1,5,4,2,3 };printf("%d\n", oneNum(a, 5, oneNo)); // 3qsort(a, 5,sizeof(int) , cmp);int i;for (i = 0; i < 5; i++) {printf("%d  ", a[i]);}return 0;
}

总结:

  • ★指针往细节学习,就会发现每个指针的大小都是固定的,一般电脑如果是64位的,指针大小就是64位(8个字节),如果电脑是32位的,指针大小就是32位(4个字节),这是因为指针存放的地址。指针前面的类型表示的是这个指针指向的空间当中存放的是什么类型,声明指针类型,就是让程序明白这一点,顺着指针地址过去取值的时候取多大的空间,也就清清楚楚的告诉程序了。
  • 数组指针&指针数组,其本质就是哪个词在后面它的本质就是什么。
  • 函数指针是个挺好用的东西,有了函数指针,我们就可以使用回调函数,向函数当中传递函数了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/92199.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/92199.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/92199.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

单变量单步时序预测:CNN-GRU卷积神经网络结合门控循环单元

目录预测效果1. **CNN-GRU的基本原理**2. **应用场景**3. **模型结构与实现**4. **优势与挑战**5. **相关研究与实现**6. **未来发展方向**结论代码设计预测效果 CNN-GRU卷积神经网络结合门控循环单元是一种结合了卷积神经网络&#xff08;CNN&#xff09;和门控循环单元&#…

MonoFusion 与 Genie 3

卡内基梅隆大学的研究者发明了一种叫 MonoFusion 的新技术&#xff0c;它能用很少的普通相机&#xff08;比如4个&#xff09;&#xff0c;就能拍出像电影特效一样细腻流畅的动态3D场景&#xff08;4D重建&#xff09;&#xff0c;比如弹钢琴、修自行车这种复杂动作&#xff0c…

kubernets命令行创建Token并附加权限给dashboard控制台登录

1、创建登录token kubectl create token default -n graph-node-test dgjeojrgopejgeropjgpsdjgerjglsdjfsjogjeojgeorjgortlfhj4yu493460uwperg3wef;lsj2y3r934tnrhifrlfe9t4h5tlhobdrmlgw485tw4yp653ut9ogogjerolj4w9erjgotj3fgjletyj49yr20o359truyo5u6908430jt5grjsdtgj49…

什么是SpringBoot

题目详细答案Spring Boot 是由 Pivotal 团队提供的一个基于 Spring 框架的项目&#xff0c;它旨在简化 Spring 应用的开发和部署。Spring Boot 通过提供一系列的约定和开箱即用的功能&#xff0c;使得开发者可以更快地构建独立的、生产级的 Spring 应用程序&#xff0c;而无需进…

从零开始设计一个分布式KV存储:基于Raft的协程化实现

从零开始设计一个分布式KV存储&#xff1a;基于Raft的协程化实现 本文将以一个最小可运行的分布式KV系统为例&#xff0c;带你拆解如何用C、Raft算法和协程模型构建高可用的Key-Value存储。 一、为什么需要分布式KV&#xff1f; 单机KV&#xff08;如Redis&#xff09;存在单点…

虚拟机或docker的ubuntu无界面安装完成后镜像源设置

ubuntu系统源 在装好虚拟机或者docker镜像后&#xff0c;直接使用apt update && apt upgrade是无法完更新的。 此时系统中也没有vim工具&#xff0c;我们可以在清华源的网站中找到帮助文档。mirrors.tuna.tsinghua.edu.cn/help/ubuntu/为了避免冲突&#xff0c;我们使用…

串口通信02 温度传感DS18B20 01 day49

九&#xff1a;串口通信 通信&#xff1a;无线和有线 ​ 单工 半双工 全双工 并行&#xff1a;多个数据线 串行&#xff1a;一根数据线 同步&#xff1a;通信双方使用同一个时钟&#xff0c;SPI信息帧&#xff0c;有CLK引脚 异步&#xff1a;通信双方使用不同时钟&#xff0c;双…

【FreeRTOS 】任务通知

FreeRTOS 任务通知任务通知简介一 、发送通知1.1 xTaskNotify()1.2 xTaskNotifyFromISR()1.3 xTaskNotifyGive()1.4 xTaskNotifyAndQuery()1.5 xTaskNotifyAndQueryFromISR()二、接收通知2.1 ulTaskNotifyTake()2.2 xTaskNotifyWait()三、清除通知状态和值3.1 xTaskNotifyState…

Android视图状态以及重绘

一、视图状态&#xff08;View States&#xff09;1. 五种核心状态状态作用修改方法特点enabled视图是否响应交互setEnabled(boolean)禁用状态下不响应onTouch事件focused视图是否获得焦点requestFocus()需同时满足focusable和focusableInTouchModewindow_focused视图所在窗口是…

vue3接收SSE流数据进行实时渲染日志

后端使用的是 Spring Boot WebFlux&#xff08;响应式编程框架&#xff09;&#xff0c;并且返回的是 Server-Sent Events (SSE) 流式数据&#xff0c;那么在 Vue3 中&#xff0c;需要使用 EventSource API 或 fetch 流式读取 来正确获取响应内容。方案 1&#xff1a;使用 Eve…

每日五个pyecharts可视化图表-bars(6)

探索pyecharts库中条形图的高级用法与定制技巧 在数据可视化中&#xff0c;条形图是最常用的图表类型之一&#xff0c;它能够清晰地展示不同类别之间的数量对比。今天&#xff0c;我们将继续学习如何使用pyecharts创建5种不同风格的条形图。pyecahts源码 图表1&#xff1a;带…

【C语言】文件操作全解析

文章目录一、为什么需要文件操作&#xff1f;二、认识文件&#xff1a;不止是磁盘上的存储2.1 程序文件2.2 数据文件2.3 文件名的构成三、文本文件与二进制文件&#xff1a;数据的两种形态3.1 存储方式差异3.2 实例对比&#xff1a;整数10000的存储3.3 二进制文件操作示例四、文…

C结构体的几种定义形式 + typedef结合使用的好处

struct 语句定义了一个包含多个成员的新的数据类型&#xff0c;struct 语句的格式如下&#xff1a; struct tag { member-list member-list member-list ... } variable-…

SPICE电容矩阵

SPICE电容矩阵: 如果有许多条传输线,就可以用下标来标记每一条线。例如,如果有5条线,就用1~5分别标记,依惯例把返回路径导体标记为导线0。图10.6给出了5条导线和一个公共返回平面的横截面图。首先研究电容器元件,下一节再讨论电感器元件。 在这个线的集合中,每对导线之间…

【Java】栈和队列

文章目录1.栈1.1 栈的定义1.2 栈的使用1.3 栈的模拟实现2.队列2.1 队列的定义2.2 队列的使用2.3 队列的模拟实现3.循环队列3.1 循环队列的概念3.2 循环队列判断空和满4.双端队列Deque1.栈 1.1 栈的定义 栈是一种特殊的线性表&#xff0c;其只允许在固定的一段进行数据的插入或…

【性能测试】---测试工具篇(jmeter)

目录 1、安装并启动jemeter 2、重点组件 2.1、线程组&#xff1a; 2.2、HTTP取样器​编辑 2.3、查看结果树 2.4、HTTP请求默认值 2.5、HTTP信息头管理器 2.6、JSON提取器 2.7、JSON断言 2.8、同步定时器 2.9、CSV数据文件设置 2.10、HTTP Cookie管理器 3、测试报告…

机器学习(12):拉索回归Lasso

- 拉索回归可以将一些权重压缩到零&#xff0c;从而实现特征选择。这意味着模型最终可能只包含一部分特征。 - 适用于特征数量远大于样本数量的情况&#xff0c;或者当特征间存在相关性时&#xff0c;可以从中选择最相关的特征。 - 拉索回归产生的模型可能更简单&#xff0c;因…

Redis持久化存储

Redis持久化存储详解 一、核心持久化机制 Redis提供两种主要持久化方式&#xff1a;RDB&#xff08;快照&#xff09; 和 AOF&#xff08;追加文件&#xff09;&#xff0c;以及两者的混合模式。 RDB&#xff08;Redis Database&#xff09;快照持久化 工作原理 RDB通过创建数据…

python学智能算法(三十四)|SVM-KKT条件回顾

【1】引言 前序学习进程中&#xff0c;对软边界拉格朗日方程进行了初步构建。 其中约定了两个拉格朗日乘子要非负&#xff0c;其本质是要满足KKT条件。 今天就乘此次机会&#xff0c;在回顾一下KKT条件。 【2】定义 当问题无约束的时候&#xff0c;只要让函数梯度为零&#…

【网络基础】计算机网络发展背景及传输数据过程介绍

本文旨在帮助初学者建立起计算机网络的基础认知&#xff0c;从网络的发展背景到网络协议的分层模型&#xff0c;再到IP与MAC地址的基本概念&#xff0c;全面覆盖第一阶段学习重点。 &#x1f4cc; 本节重点 了解计算机网络的发展背景&#xff0c;掌握局域网&#xff08;LAN&am…