【C】指针初阶

指针

为了后续一些安排打基础,决定使用C/C++作为算法主语言,所以从这篇文章开始,从指针开始总结

指针 -> 指针进阶 -> 字符串函数 -> 自定义类型 -> 动态内存管理 -> 数据结构

还有C++一些基础语法的回顾(基于算法竞赛使用)

文章目录

  • 指针
    • 概念
      • 指针变量
    • 指针和指针类型
      • 指针+-整数
      • 解引用
    • 野指针
      • 成因
        • 指针未初始化
        • 指针越界访问
        • 指针指向的空间释放
      • 规避野指针
    • 指针运算
      • +-整数
      • 指针-指针
      • 关系运算
    • 指针和数组
    • 二级指针

概念

  1. 指针是内存中一个最小单元的编号,也就是地址

  2. 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量

总的来说,指针就是地址,,口语中说的指针通常指的是指针变量。

指针变量

我们可以通过&(取地址操作符)取出变量的内存其实就是地址,把地址可以存放到一个变量中,这个变量就是指针变量

#include <stdio.h>
int main()
{int a = 10; //在内存中开辟一块空间int *p = &a;//这里我们对变量a,取出它的地址,可以使用&操作符。//a变量占用4个字节的空间,这里是将a的4个字节的第一个字节的地址存放在p变量中,p就是一个之指针变量。return 0;
}

指针和指针类型

char  *pc = NULL;
int   *pi = NULL;
short *ps = NULL;
long  *pl = NULL;
float *pf = NULL;
double *pd = NULL;

指针±整数

#include <stdio.h>
//演示实例
int main()
{int n = 10;char *pc = (char*)&n;int *pi = &n;printf("%p\n", &n);printf("%p\n", pc);printf("%p\n", pc+1);//打印指针 pc 向后移动一个单位(1 字节)后的内存地址,使用 %p 格式说明符,并换行printf("%p\n", pi);printf("%p\n", pi+1);//打印指针 pi 向后移动一个单位(4 字节)后的内存地址,使用 %p 格式说明符,并换行。return  0;
}
//输出结果
00DDF7E8
00DDF7E8
00DDF7E9
00DDF7E8
00DDF7EC

**总结:**指针的类型决定了指针向前或者向后走一步有多大(距离)~

解引用

在C语言中,指针解引用操作使用*符号来访问指针所指向的内存位置的值。也就是说,使用*操作符可以获取指针所指向的内存位置的值,或者可以修改该内存位置的值。

#include <stdio.h>
int main()
{int n = 0x11223344;char *pc = (char *)&n;int *pi = &n;*pc = 0;   //重点在调试的过程中观察内存的变化。*pi = 0;   //重点在调试的过程中观察内存的变化。return 0;
}

另外一个例子:

#include <stdio.h>int main() {int num = 10;int *ptr = &num;printf("Value of num: %d\n", num);      // 输出:Value of num: 10printf("Value of num using pointer: %d\n", *ptr);   // 输出:Value of num using pointer: 10*ptr = 20;    // 使用指针解引用操作修改指针所指向的内存位置的值printf("Updated value of num using pointer: %d\n", *ptr);   // 输出:Updated value of num using pointer: 20printf("Updated value of num: %d\n", num);      // 输出:Updated value of num: 20return 0;
}
//结果
Value of num: 10
Value of num using pointer: 10
Updated value of num using pointer: 20
Updated value of num: 20

综上的例子:我的理解就是:指针的解引用操作就是可以修改那个被解引用对象或者变量的所在的内存的值

总结:

指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。

比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节。

野指针

所谓的野指针,就是指向无效内存地址的指针,会导致不可预测的行为~

成因

指针未初始化
#include <stdio.h>
int main()
{ int *p;//局部变量指针未初始化,默认为随机值*p = 20;return 0;
}

这段代码在声明了一个指针变量 p 后,没有将其初始化为有效的内存地址,而是直接对其进行解引用操作,将值 20 存储到目标地址中。

要修复这个问题,应该在使用指针之前先为其分配内存。可以使用 malloc() 函数来动态分配内存(后面会更新),或者将指针指向一个已有的变量或数组。以下是修复后的示例代码:

#include <stdio.h>
int main()
{int *p = malloc(sizeof(int)); // 分配内存用于存储 int 类型变量*p = 20; // 解引用指针并为其赋值// 使用指针的值printf("Value: %d\n", *p);// 释放内存free(p);return 0;
}
指针越界访问
#include <stdio.h>
int main()
{int arr[10] = {0};int *p = arr;int i = 0;for(i=0; i<=11; i++){//当指针指向的范围超出数组arr的范围时,p就是野指针*(p++) = i;}return 0;
}
指针指向的空间释放

后面在动态内存管理的博客中会再总结一次~

一般这种指针被叫做:悬空指针

当使用 free() 函数释放一个动态分配的内存块时,该内存块被标记为可重新使用,但指向该内存块的指针仍然保留着原来的地址。如果在释放内存之后继续使用该指针,它就成为了一个指向已释放内存的野指针。

下面给个示例代码:

#include <stdio.h>
#include <stdlib.h>int main()
{int *p;p = malloc(sizeof(int));*p = 10;free(p); // 释放了内存printf("Value: %d\n", *p); // 访问已释放内存的指针*p = 20; // 修改已释放内存的指针return 0;
}

在上述示例中,我们首先使用 malloc() 分配了一个整型的内存块,并将其赋值为 10。然后,我们用 free() 函数释放了该内存块,使得指针 p 成为了一个指向已释放内存的悬空指针。

接下来,我们尝试访问该悬空指针的值并打印,这个操作属于未定义行为,可能会输出错误结果、崩溃或其他不可预测的行为。

最后,我们尝试修改悬空指针的值为 20,同样也是未定义行为,可能会导致程序异常终止或产生其他意外结果。

为了避免这种问题,我们应该在释放内存后将指针设置为 NULL,并在使用指针之前检测其是否为 NULL

#include <stdio.h>
#include <stdlib.h>int main()
{int *p;p = malloc(sizeof(int));*p = 10;free(p);p = NULL; // 将指针设置为 NULLif (p != NULL) {printf("Value: %d\n", *p); // 避免访问已释放内存的指针}p = malloc(sizeof(int)); // 重新分配内存*p = 20; // 修改重新分配的内存的值free(p);return 0;
}

在修复后的示例中,我们在释放内存后将指针 p 设置为 NULL。然后,在使用指针之前,我们先检查指针是否为 NULL,以确保它指向有效的内存。我们还重新分配了内存并修改了相应的指针。

这个示例展示了正确处理指向已释放内存的指针的方式,以避免悬空指针问题的发生。

规避野指针

  1. 指针初始化

  2. 小心指针越界

  3. 指针指向空间释放,及时置NULL

  4. 避免返回局部变量的地址

  5. 指针使用之前检查有效性

指针运算

±整数

#include <stdio.h>int main() {int arr[] = {10, 20, 30, 40, 50};int *ptr = arr; // 指向数组的第一个元素// 使用指针的加法运算printf("Elements of array using pointer arithmetic:\n");for (int i = 0; i < 5; i++) {printf("%d ", *ptr);ptr++; // 指针向后移动一个 int 大小的位置}printf("\n");// 使用指针的减法运算ptr = &arr[4]; // 指向数组的最后一个元素printf("Elements of array (reverse order) using pointer arithmetic:\n");for (int i = 0; i < 5; i++) {printf("%d ", *ptr);ptr--; // 指针向前移动一个 int 大小的位置}printf("\n");return 0;
}
//输出结果
Elements of array using pointer arithmetic:
10 20 30 40 50
Elements of array (reverse order) using pointer arithmetic:
50 40 30 20 10

指针-指针

当两个指针相减时,其结果是两个指针之间的距离(以指针指向的类型大小为单位)。这个结果是一个整数类型。

指针-指针的操作通常用于比较两个指针之间的距离,或者计算数组中连续元素之间的距离。

#include <stdio.h>int main() {int arr[] = {10, 20, 30, 40, 50};int *ptr1 = &arr[1]; // 指向数组的第二个元素int *ptr2 = &arr[4]; // 指向数组的最后一个元素// 计算指针之间的距离int diff = ptr2 - ptr1;printf("Distance between ptr1 and ptr2: %d\n", diff);return 0;
}
//输出结果
Distance between ptr1 and ptr2: 3

注意:

两个指针必须指向同一个数组(或同一块内存)才能进行指针-指针的操作。

如果两个指针指向不同的数组,或者一个指针指向数组的开始而另一个指针指向数组的结束,则结果是无效的。


同时,还可以使用指针-指针操作来比较两个指针的相对位置

#include <stdio.h>int main() {int arr[] = {10, 20, 30, 40, 50};int *ptr1 = &arr[1]; // 指向数组的第二个元素int *ptr2 = &arr[3]; // 指向数组的第四个元素if (ptr1 < ptr2) {printf("ptr1 is before ptr2\n");} else if (ptr1 > ptr2) {printf("ptr1 is after ptr2\n");} else {printf("ptr1 and ptr2 are equal\n");}return 0;
}

注意

仅当两个指针指向同一个数组(或同一块内存)时,比较两个指针的相对位置才有意义。否则,结果是未定义的。

关系运算

#include <stdio.h>int main() {int arr[] = {10, 20, 30, 40, 50};int *ptr1 = arr; // 指向数组的第一个元素int *ptr2 = &arr[2]; // 指向数组的第三个元素if (ptr1 > ptr2) {printf("ptr1 is greater than ptr2\n");}if (ptr1 < ptr2) {printf("ptr1 is less than ptr2\n");}if (ptr1 >= ptr2) {printf("ptr1 is greater than or equal to ptr2\n");}if (ptr1 <= ptr2) {printf("ptr1 is less than or equal to ptr2\n");}if (ptr1 == ptr2) {printf("ptr1 is equal to ptr2\n");}if (ptr1 != ptr2) {printf("ptr1 is not equal to ptr2\n");}return 0;
}

指针和数组

#include <stdio.h>
int main()
{int arr[10] = {1,2,3,4,5,6,7,8,9,0};printf("%p\n", arr);printf("%p\n", &arr[0]);return 0;
}
00F3FBC8
00F3FBC8

可见数组名和数组首元素的地址是一样的

int arr[10] = {1,2,3,4,5,6,7,8,9,0};
int *p = arr;//p存放的是数组首元素的地址

既然可以把数组名当成地址存放到一个指针中,我们使用指针来访问一个就成为可能。

例如:

#include <stdio.h>
int main()
{int arr[10] = {1,2,3,4,5,6,7,8,9,0};int *p = arr;//p存放的是数组首元素的地址int sz = sizeof(arr)/sizeof(arr[0]);for(i=0; i<sz; i++){printf("&arr[%d] = %p  <====> p+%d = %p\n", i, &arr[i], i, p+i);}return 0;
}
&arr[0] = 00AFF708  <====> p+0 = 00AFF708
&arr[1] = 00AFF70C  <====> p+1 = 00AFF70C
&arr[2] = 00AFF710  <====> p+2 = 00AFF710
&arr[3] = 00AFF714  <====> p+3 = 00AFF714
&arr[4] = 00AFF718  <====> p+4 = 00AFF718
&arr[5] = 00AFF71C  <====> p+5 = 00AFF71C
&arr[6] = 00AFF720  <====> p+6 = 00AFF720
&arr[7] = 00AFF724  <====> p+7 = 00AFF724
&arr[8] = 00AFF728  <====> p+8 = 00AFF728
&arr[9] = 00AFF72C  <====> p+9 = 00AFF72C

所以 p+i 其实计算的是数组 arr 下标为i的地址。

那我们就可以直接通过指针来访问数组。

int main()
{int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };int *p = arr; //指针存放数组首元素的地址int sz = sizeof(arr) / sizeof(arr[0]);int i = 0;for (i = 0; i<sz; i++){printf("%d ", *(p + i));}return 0;
}

二级指针

二级指针也称为指向指针的指针,它是一种较为复杂的指针类型。通过使用二级指针,我们可以操作指针的指针,即通过一个指针间接引用另一个指针。

二级指针常用于需要在函数之间传递和修改指针的地址的情况,它提供了更灵活的指针操作方式。

对于二级指针的运算有:

  • *ppa 通过对ppa中的地址进行解引用,这样找到的是 pa *ppa 其实访问的就是 pa

    int b = 20;
    *ppa = &b;//等价于 pa = &b;
    
  • **ppa 先通过 *ppa 找到pa,然后对 pa 进行解引用操作:*pa,那找到的是 a .

    **ppa = 30;
    //等价于*pa = 30;
    //等价于a = 30;
    

    再看一下下面代码:

#include <stdio.h>
#include <stdlib.h>int main() {int val = 42;int* ptr = &val; // 一级指针,指向 val 的地址int** dptr = &ptr; // 二级指针,指向 ptr 的地址printf("Value: %d\n", **dptr); // 通过二级指针访问 val 的值int* newVal = (int*)malloc(sizeof(int)); // 动态分配内存*newVal = 99; // 通过一级指针修改 newVal 的值*dptr = newVal; // 通过二级指针修改 ptr 的值,使其指向 newValprintf("Value: %d\n", **dptr); // 通过二级指针访问 newVal 的值free(*dptr); // 释放内存*dptr = NULL; // 通过二级指针重置指针变量为 NULLreturn 0;
}

在上述示例中,我们首先声明了一个整型变量 val,然后声明了一个一级指针 ptr,该指针指向 val 的地址。然后,我们声明了一个二级指针 dptr,该指针指向 ptr 的地址。

在打印 **dptr 时,我们使用两次解引用操作符 *,可以间接访问 val 的值。

接下来,我们使用 malloc 函数动态分配了一个新的整型变量,并通过一级指针 newVal 修改了它的值。然后,我们通过二级指针 dptr 修改了一级指针 ptr 的指向,使其指向新分配的内存。

最后,我们使用 free 函数释放了动态分配的内存,并将指针变量重置为 NULL


至此,指针的初阶总结到此结束,接下来会持续感谢~

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

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

相关文章

【postgresql 】 ERROR: “name“ is not supported as an alias

org.postgresql.util.PSQLException: ERROR: "name" is not supported as an alias 错误&#xff1a;不支持将“name”作为别名 SELECT real_name name FROM doc_user 加上 在关键词上加上 “” 示例&#xff1a; SELECT real_name "name" FROM do…

SWIFT中最常见的内存泄漏陷阱

SWIFT中最常见的内存泄漏陷阱 如果您有内存循环&#xff0c;它将在调试器中向您显示警告&#xff1a; 如果确实有一个&#xff08;或通常是一堆&#xff09;&#xff0c;则表示您有一个泄漏的物体。 您如何预防呢&#xff1f; 就像在关闭的第一行中添加[unowned self]一样简…

“Vue进阶:深入理解插值、指令、过滤器、计算属性和监听器“

目录 引言&#xff1a;Vue的插值Vue的指令Vue的过滤器Vue的计算属性和监听器vue购物车案例总结&#xff1a; 引言&#xff1a; Vue.js是一款流行的JavaScript框架&#xff0c;它提供了许多强大的功能来简化前端开发。在本篇博客中&#xff0c;我们将深入探讨Vue的一些高级特性…

Java项目:SSM的网上书城系统

作者主页&#xff1a;Java毕设网 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文末获取源码 一、相关文档 1、关于雅博书城在线系统的基本要求 &#xff08;1&#xff09;功能要求&#xff1a;可以管理个人中心、用户管理、图书分类管理、图书信息管理、…

Java 函数式编程思考 —— 授人以渔

引言 最近在使用函数式编程时&#xff0c;突然有了一点心得体会&#xff0c;简单说&#xff0c;用好了函数式编程&#xff0c;可以极大的实现方法调用的解耦&#xff0c;业务逻辑高度内聚&#xff0c;同时减少不必要的分支语句&#xff08;if-else&#xff09;。 一、函数式编…

【Vue3+Vite+Ts+element-plus】vue 使用 tsx语法详解

系列文章目录 【Vue3ViteTselement-plus】 超级详细 最新 vite4vue3tselement-pluseslint-prettier 项目搭建流程 【Vue3ViteTselement-plus】使用tsx实现左侧栏菜单无限层级封装 【Ts 系列】 TypeScript 从入门到进阶之基础篇(一) ts类型篇 文章目录 系列文章目录前言一、必要…

MySQL 数据库学习(六)备份与binlog日志

1 案例1&#xff1a;完全备份与恢复 1.1 问题 练习物理备份与恢复练习mysqldump备份与恢复 1.2 方案 在数据库服务器192.168.88.50 练习数据的备份与恢复 1.3 步骤 实现此案例需要按照如下步骤进行。 步骤一&#xff1a;练习物理备份与恢复 冷备份&#xff0c;需停止数…

Python手写人脸识别

Python手写人脸识别 引言 人脸识别是一种通过计算机视觉和模式识别技术来识别和验证人脸的技术。Python是一种广泛使用的编程语言,它提供了许多强大的库和工具来实现人脸识别。 在Python中,可以使用多种方法来实现人脸识别,包括基于特征提取的方法、基于深度学习的方法等…

clickhouse学习之路----clickhouse的特点及安装

clickhouse学习笔记 反正都有学不完的技术&#xff0c;不如就学一学clickhouse吧 文章目录 clickhouse学习笔记clickhouse的特点1.列式存储2. DBMS 的功能3.多样化引擎4.高吞吐写入能力5.数据分区与线程级并行 clickhouse安装1.关闭防火墙2.CentOS 取消打开文件数限制3.安装依…

java字符串的学习总结

/* 总结: 1. ★★★★★★★String 是字符串,内容不可改变★★★★★★★★ 常用方法: (1)length() 长度(2)equals(string类型) 比较当前字符串于括号里的字符串是否相同(3)startsWith(st…

支持向量机基本原理,Libsvm工具箱详细介绍,基于支持向量机SVM的遥感图像分类

目录 支持向量机SVM的详细原理 SVM的定义 SVM理论 Libsvm工具箱详解 简介 参数说明 易错及常见问题 完整代码和数据下载链接: 基于SVM的遥感图像分类识别,基于支持向量机SVM的遥感图像分类识别(代码完整,数据齐全)_图像匹配中SVM多分类问题资源-CSDN文库 https://downloa…

Python 运行代码

一、Python运行代码 可以使用三种方式运行Python&#xff0c;如下&#xff1a; 1、交互式 通过命令行窗口进入 Python 并开始在交互式解释器中开始编写 Python 代码 2、命令行脚本 可以把代码放到文件中&#xff0c;通过python 文件名.py命令执行代码&#xff0c;如下&#xff…

ARMv7处理器

本文档介绍常见的 ARM 架构,包括 Cortex-A5,Cortex-A7, Cortex-A8, Cortex-A9, Cortex-A15. 常见的术语 DFT(Design for Test), 为了增强芯片可测性而采用的一种设计方法 APB(Advanced Peripheral Bus), 是一种低速外设总线接口,通常用于将外部设备(如I/O端口、定时器、UA…

机器学习——pca降维/交叉验证/网格交叉验证

1、pca降维&#xff1a;目的是提升模型训练速度 定义&#xff1a; 使用方法&#xff1a;给训练数据或者测试数据进行降维处理 给训练数据降维 给测试数据降维&#xff1a;这里1就要用transform&#xff0c;而不是fit_transform&#xff0c;因为之前训练数据降维时特征已经确定…

构建基于neo4j知识图谱、elasticsearch全文检索的数字知识库

前言&#xff1a; 在数字化时代&#xff0c;知识库的建设正逐渐成为企业、学术机构和个人的重要资产。本文将介绍如何使用neo4j和elasticsearch这两种强大的数据库技术来构建知识库&#xff0c;并对其进行比较和探讨。 技术栈&#xff1a; springbootvueneo4jelasticsearch…

【每日一题】1993. 树上的操作

文章目录 Tag题目来源题目解读解题思路方法一&#xff1a;深度优先搜索 写在最后 Tag 【深度优先搜索】【树】【设计数据结构】【2023-09-23】 题目来源 1993. 树上的操作 题目解读 本题是一个设计类的题目&#xff0c;对于设计类的题目就一步步的实现题目要求的成员方法即可…

【ACDC数据集】:预处理ACDC心脏3D MRI影像数据集到VOC数据集格式,nii转为jpg,label转为png

【Segment Anything Model】做分割的专栏链接&#xff0c;欢迎来学习。 【博主微信】cvxiaoyixiao 本专栏为公开数据集的预处理&#xff0c;持续更新中。 文章目录 1️⃣ ACDC数据集介绍2️⃣ ACDC数据集样例 3️⃣ 预处理ACDC目标 4️⃣ 处理结果样图 5️⃣ 代码 6️⃣ 划分测…

十五)Stable Diffusion使用教程:另一个线稿出3D例子

案例:黄金首饰出图 1)线稿,可以进行色阶加深,不易丢失细节; 2)文生图,精确材质、光泽、工艺(抛光、拉丝等)、形状(包括深度等,比如镂空)和渲染方式(3D、素描、线稿等)提示词,负面提示词; 3)seed调-1,让ai随机出图; 4)开启controlnet,上传线稿图,选择cann…

leetcode 2560. 打家劫舍 IV

2560. 打家劫舍 IV 沿街有一排连续的房屋。每间房屋内都藏有一定的现金。现在有一位小偷计划从这些房屋中窃取现金。 由于相邻的房屋装有相互连通的防盗系统&#xff0c;所以小偷 不会窃取相邻的房屋 。 小偷的 窃取能力 定义为他在窃取过程中能从单间房屋中窃取的 最大金额 。…

《Java并发编程实战》第4章-对象的组合

0.概念理解 状态空间&#xff1a;对象与变量所有可能的取值&#xff0c;状态空间越小&#xff0c;就越容易判断线程的状态&#xff0c;final域用得越多&#xff0c;就越能简化对象可能状态的分析过程&#xff08;不可变对象只有唯一的状态&#xff09;。 实例封闭&#xff1a;…