当前位置: 首页 > news >正文

【初阶C语言】指针的妙用

前言:在C语言中,有一个非常重要的知识点,叫做指针,指针也是数据类型中的一种。在本节内容中,我们就一起来学习指针。


 学习一个新知识的时候,我们需要从这几个方面:指针是什么,指针是怎么访问数据(指针是怎么工作的),也就是弄清楚指针类型的作用,怎么运用指针,还有注意使用指针时常犯的错误,做好上面,你对指针的学习就已经非常棒了。

1. 指针是什么
2. 指针和指针类型
3. 野指针
4. 指针运算
5. 指针和数组
6. 二级指针
7. 指针数组

本节重点,介绍指针类型的意义


一、介绍指针

讲到指针,就应该要想到跟他关联的东西--地址。内存会被分成一个个小的单元,每个单元的单位是字节,所以每个内存都会有一个编号,该编号就是内存的地址,通过地址就可以找到该内存。

指针的概念:指针就是地址,口头说的指针就是指针变量,用来存放地址。存放的地址其实就是内存的编号。

指针变量==地址==单元编号

注意:存放在指针变量中的数据,会当成地址来处理。

代码对指针的解释:

#include<stdio.h>
int main()
{int a = 10;//定义一个变量aint* p = &a;//取出变量a的地址存放到变量p中return 0;//因为变量p存放的是地址,所以称为指针变量
}

 代码解读:int说明指针变量所指的对象是int型,*在说明该变量是指针类型。

总结:

     在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以一个指针变量的大小就应该是4个字节。那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地址

    指针是用来存放地址的,地址是唯一标示一块地址空间的。
    指针的大小在32位平台是4个字节,在64位平台是8个字节

由上面可知,指针的内存大小都是4/8个字节,为什么指针还有指针类型区分呢?

二、指针和指针类型

前言:我们要学习指针类型,目的就是要知道指针类型存在的意义和怎么使用。

1.指针类型的分类

字符型指针charchar* p1
短整形指针shortshort* p2
整形指针intint* p3
长整形指针longlomg* p4
单精度浮点型指针floatfloat* p5
双精度浮点型指针doubledouble* p6

既然指针有这么多类型,我们可以对它们的内存大小一探究竟。

可以通过代码的方式一探究竟:

#include<stdio.h>
int main()
{printf("%d\n", sizeof(char*));printf("%d\n", sizeof(short*));printf("%d\n", sizeof(int*));printf("%d\n", sizeof(long*));printf("%d\n", sizeof(float*));printf("%d\n",sizeof(double*));return 0;
}

两种平台下运行的结果: 

总结:

  无论是哪一种平台下计算的结果,每种指针类型的内存大小都一样,都是4或8个字节。

2.指针类型的访问权限

    指针的访问是什么意思呢?就是通过解引用操作符(*)找到地址所指向的对象,再对其操作的过程,该操作一般是修改数据。

我们先看代码:

正常修改:

#include<stdio.h>
int main()
{int a = 0x11223344;//这里表示十六进制的数字int* p = &a;printf("%d\n", a);*p = 0;//解引用操作把a修改成0printf("%d\n",a);
}

运行结果:

 输入的0x11223344是十六进制,打印的结果287454020是十进制。

 我们看一下调试的结果图:

 ox11223344解读:0x的意思是表示十六进制的数字,这里是为了方便观察。整形int的内存大小是4个字节,也就是32个比特位(二进制);因为四个二进制数字可以转化成一个十六进制数字,所以为了方便,系统会自动把二进制转化成十六进制(8位十六进制)。

 

总结: 以上是为了说明数据存储在内存中是以十六进制存储。两个十六进制数字=一个字节,这个知识点很重要,下面会用到。所以我们对变量a赋值0x11223344,因为已经是十六进制数字,存在内存中就是11223344的形式,方便我们观察而已。

 上面的内容是引入,下面开始重点:

 

我们正常输入数据并修改:

 不正常修改的代码:

#include<stdio.h>
int main()
{int a =0x5201314;char* p = &a;//注意这里是用char类型的指针printf("%d\n", a);*p = 0;//解引用操作把a修改成0printf("%d\n", a);
}

 这里与上面不一样的区别:指针类型是char*,它一样可以存放变量a的地址。

运行结果:

 输入的数据是十六进制的0x5201314,第一次打印的结果是十进制的85988116,所以在所以观察会不明显,我们看在内存中的存储。

内存存储图:

 这里为什么不是像上面一样是把所有的数据全部修改成0呢?因为char指针类型每次只能访问一个字节的大小,所以每次修改的权限只有一个字节。

我们可以看short类型:

 修改后:

 上面那么多的例子目的是说明不同的指针类型有不同的访问权限,像char每次只能访问一个字节,short访问两个字节,int访问四个字节,取决于类型的内存大小,但是指针的大小是一样的。

总结1:不同的指针类型,可以访问数据的权限不一样

3.不同指针类型的运加减性质

这里要介绍的内容就是不同的指针类型+1或-1的结果。

例题:

未修改的代码:

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

运行结果1:

 这里跟一般的访问数组不一样,这里是通过指针来访问每个数组。

把数组赋值成0后打印:

#include<stdio.h>
int main()
{int arr[7] = { 5,2,0,1,3,1,4 };int* p1 = &arr;int i = 0;for (i = 0; i < 7; i++)printf("%d ", *(p1 + i));//第一次打印printf("\n");i = 0;for (i = 0; i < 7; i++)*(p1 + i) = 0;//全部赋值成0i = 0;for (i = 0; i < 7; i++)printf("%d ", *(p1 + i));//第二次打印return 0;
}

运行结果2;

 用其他类型指针来修改数据:

#include<stdio.h>
int main()
{int arr[7] = { 5,2,1,1,3,1,4 };char* p1 = &arr;int i = 0;for (i = 0; i < 7; i++)printf("%d ", arr[i]);//第一次打印printf("\n");i = 0;for (i = 0; i < 7; i++)*(p1 + i) = 0;//全部赋值成0i = 0;for (i = 0; i < 7; i++)printf("%d ", arr[i]);//第二次打印return 0;
}

运行结果2:

这里的结果又跟上面的不一样了,没有将数据全部修改成0。由上面我们知道,char指针每次只能访问一个字节,而数组为整形,每个数据占用四个字节。

当char指针+1的时候,是跳过一个字节,所以+1一次是没有办法访问到数组的第二个元素,也就没有办法修改。

总结2:指针类型的意义在于,决定指针+1或者-1跳过多少个字节。

指针类型大总结1.指针类型决定了指针解引用操作的时候,访问几个字节

                             2.指针类型决定了指针+1或-1操作跳过几个字节

三、野指针

野指针,其实就是非法指针,并不是要学习使用野指针,而是为了避免野指针的出现而去认识

标准概念:野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

出现野指针大概有这些情况:

1.指针未初始化

2.指针越界访问

3.返回局部变量的地址

4.释放的指针未及时置空

1.指针未初始化

#include<stdio.h>
int main()
{int* p;//指针未初始化,为野指针return 0;
}

像这样,创建指针的时候没有让他指向某块空间,也没有置空,这样就是野指针。

正确做法:

#include<stdio.h>
int main()
{int* p = NULL; // 指针置空赋值NULL;return 0;
}

一般默认指针变量置空赋值NULL,普通变量赋值为0。另外,NULL默认值为0。

2.指针越界访问数组

下面的是错误代码:

#include<stdio.h>
int main()
{int arr[5] = {0};int* p = arr;int i = 0;for (i=0;i<=5;i++)//这是错误代码{*p = 520;p++;}return 0;
}

因为数组的大小为5,循环会走6次,从而越界访问,我们看一下这种情况下的运行结果;

3.返回局部变量的地址

#include<stdio.h>
int* test()
{int a = 0;//局部变量a出了test函数就会被销毁return &a;
}
int main()
{int* p = test();printf("%d\n",*p);return 0;
}

因为出了test函数,局部变量a就已经被销毁了,本来属于局部变量a的地址,现在却已经不是他的了。此时这块地址的指向是不确定的

4.关于置空的问题

当指针指向的空间释放,要及时将指针置空,也就是赋值为NULL。

所以当我们去使用指针的时候,就要去检查指针的有效性,指针是否为空。

p=NULL;//p为指针,指向空间被释放时要及时置空//使用前,检查指针的有效性
if(p==NULL)return;//指针为空直接返回

四、指针运算

指针的运算包括指针+-整数,如p++;和指针+-指针

1.指针加减整数

#include<stdio.h>
int main()
{int arr[5] = {1,2,3,4,5};//创建并初始化数组//利用指针加减整数打印数组int* p = arr;//定义和初始化指针int i = 0;for (i = 0; i < 5; i++){printf("%d ",*(p+i));}//*(p+i)可以写成*p++,但是后者的p会发生变化return 0;
}

运行结果:

 

 第一次循环:i=0,所以p+i=p,然后解引用p找的是arr[0],并打印。

p指向的地址是数组首元素的地址;p+i是数组下标为i的元素的地址;(p+i)跳过i*sizeof(int)个字节。

总结:指针加减整数,不是跳过整数个地址,而是跳过整数*sizeof(指针所指向的数据类型)个地址。

标准规定:
      允许指向数组元素的指针指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较
 

2.指针-指针

两个指针相减就是地址之间的运算,一般在数组中出现,因为;

指针减指针的前提条件:两个指针必须指向同一块区域,且指针类型相同。

举例:

#include<stdio.h>
int main()
{int arr[10] = {0};printf("%d\n",&arr[9]-&arr[0]);printf("%d\n",&arr[0]-&arr[9]);//最后一个元素的地址-首元素地址return 0;
}

运行结果 

 总结:指针-指针插值的绝对值,是指针和指针之间的元素个数。

 

五、指针与数组的运用

     因为数组名是首元素的地址,所以可以通过首元素地址访问该数组,也就可以将地址赋值给指针变量,再用指针去访问数组,这就是他们的关系。

知识点:数组名就是表示数组首元素的地址,但是有两个例外。

1.sizeof(数组名),数组名单独放在括号内部,数组名表示整个数组,计算的是整个数组的大小,单位是字节。

2.&数组名,数组名表示整个数组,取出的是数组的地址,数组的地址和数组首元素的地址在数值上是一样的,但是类型和意义不一样的,体现在地址+1上面。

(该知识点已经在数组的章节介绍过,属于数组的知识点)

例子说明:

#include <stdio.h>
int main()
{int arr[] = { 1,2,3,4,5,6,7,8,9,0 };int* p = arr; //指针存放数组首元素的地址int i = 0;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;
}

运行结果:

 通过指针来访问数组是可行的,因为指针所指向的地址是跟数组匹配的。

总结:因为数组名是地址的原因,所以可以和指针很好的配合。

六、二级指针

二级指针也是指针,不过二级指针里面存放的地址是一级指针的地址,依次类推,三级指针存放二级指针的地址……

#include <stdio.h>
int main()
{int a = 10;int* p = &a;//*说明p是指针,int说明p指向的类型是int//p是一级指针,存放变量a的地址int* * pp = &p;//第一个*说明pp是指针,int*说明pp指向的p变量类型是int*//pp是二级指针return 0;
}

里面的文字解析很重要

七、指针数组

指针数组本质是数组,只不过里面存放的类型是指针类型。就像字符数组里面存放的是字符一样。

如:

char* arr1[5];//存放字符指针的数组
int* arr2[6];

举例:

#include<stdio.h>
int main()
{int arr1[3] = {1,2,3};int arr2[3] = {4,5,6};int arr3[3] = { 7,8,9 };//arr就是指针数组int* arr[3] = { arr1,arr2,arr3 };//可以模拟二维数组int i = 0;for (i=0;i<3;i++){int j = 0;for (j=0;j<3;j++){printf("%d ",arr[i][j]);//i=0,访问arr1}printf("\n");}return 0;
}

运行结果:

 指针数组在初级C语言不会详细介绍,在进阶C语言我们会进一步探讨。


本章完……以上内容如有错误,欢迎各位大佬的补充,后续会持续更新指针的内容

相关文章:

【初阶C语言】指针的妙用

前言&#xff1a;在C语言中&#xff0c;有一个非常重要的知识点&#xff0c;叫做指针&#xff0c;指针也是数据类型中的一种。在本节内容中&#xff0c;我们就一起来学习指针。 学习一个新知识的时候&#xff0c;我们需要从这几个方面&#xff1a;指针是什么&#xff0c;指针是…...

链表——LinkedList类的概述和实现

LinkedList类 1.1LinkedList类概述 LinkedList类底层是基于双向链表结构实现的&#xff0c;不同于ArrayList类和Vector类是基于数组实现的&#xff1b;LinkedList类是非线程安全的&#xff1b;LinkedList类元素允许为null&#xff0c;允许重复元素&#xff1b;LinkedList类插…...

快六一啦,学习CSS3实现一个冰淇淋动画特效

快六一啦&#xff0c;小时候顶多吃个小冰棍&#xff0c;或者是那种小冰袋&#xff0c;现在的小朋友真是好&#xff0c;动不动就能吃到冰淇淋&#xff0c;今天用CSS3实现一个冰淇淋的动画特效吧 目录 实现思路 桶身的实现 冰淇淋身体的实现 五彩颗粒的实现 HTML源码 CSS3源…...

VSCode CMake vcpkg 整合

VSCode 整合 CMake 调试 CMake 工程 // launch.json {"version": "0.2.0","configurations": [{"name": "(gdb) Launch","type": "cppdbg","request": "launch",// Resolved by …...

c++ | win vscode

vscode 适合新手做一些简单的单个的编译和调试 新手适合去配置c 环境&#xff0c;尤其是当涉及复杂一点的编程&#xff0c;如多文件、多线程&#xff0c;在调试的时候会头大&#xff0c;要求会高一点 但怎么说呢&#xff1f; c 编译和调试是最接近实际开发环境的&#xff0c;与…...

算法-快速排序

给你一个整数数组 nums&#xff0c;请你将该数组升序排列。 输入&#xff1a;nums [5,2,3,1] 输出&#xff1a;[1,2,3,5] 输入&#xff1a;nums [5,1,1,2,0,0] 输出&#xff1a;[0,0,1,1,2,5] 详细思路直接看我录制的视频吧 算法-快速排序_哔哩哔哩_bilibili class Soluti…...

SSM项目-博客系统

在线体验项目&#xff1a;登陆页面 项目连接&#xff1a;huhublog_ssm: 个人博客系统 技术栈&#xff1a;SpringBoot、SpringMVC、Mybatis、Redis、JQuery、Ajax、Json (gitee.com) 1.项目技术点分析 SpringBoot、SpringWeb(SpringMVC)、MyBatis、MySQL(8.x)、Redis(存储验…...

Android Gradle Plugin 编译

1. 源码下载&#xff1a; $ mkdir studio-main $ cd studio-main $ repo init -u https://android.googlesource.com/platform/manifest -b studio-main $ repo sync -c -j4 -q 这个官方网址让下载 studio-master-dev 分支&#xff0c;这个分支很老旧了&#xff0c;我这里直接…...

如何快速掌握水土保持方案编制

1、熟悉水土保持常用的主要法律法规、部委规章、规范性文件及技术规范与标准&#xff1b; 2、了解水土保持方案、监测及验收工作开展的流程&#xff1b; 3、熟悉水土保持方案、监测及验收工作需要收集的资料、现场踏勘注意事项&#xff1b; 4、熟悉常见水土保持工程施工工艺…...

前端笔试---acm模式

前言 之前一直刷力扣&#xff0c;昨天做了小红书笔试&#xff0c;发现是acm模式&#xff0c;不太熟悉&#xff0c;特此总结。其实如果是acm模式就需要自己写一下输入输出。前端一般有两个选择&#xff0c;一个是基于 V8 环境&#xff0c;另一个是基于 node。 V8 // 对于有多…...

国联易安网页防篡改保护系统“渠道招募”启动啦!

作为业内专注于保密与非密领域的分级保护、等级保护、业务连续性安全和大数据安全的领军企业&#xff0c;国联易安网页防篡改保护系统基于“高效同步”、“安全传输”两项技术&#xff0c;具备了独特的“五重防护”新特性&#xff0c;支持网页的全自动发布、网页监控、报警和自…...

JavaScript--WebStorage

目录 WebStorage概述 WebStorage分类 注意&#xff1a; localStorage方法 介绍&#xff1a; 常见方法&#xff1a; 案例演示&#xff1a; sessionStorage方法 介绍&#xff1a; 常见方法&#xff1a; 案例演示&#xff1a; WebStorage概述 WebStorage是HTML5中…...

elementui 的 dialog 常用逻辑总结

菜鸟最近写后台管理系统&#xff0c;发现不管是弹窗、还是编辑、查看、添加等功能&#xff0c;真的代码都差不多&#xff0c;但是每次都要重新写里面的关闭逻辑等&#xff0c;菜鸟就感觉不如搞一个模版&#xff0c;后面只关注于逻辑&#xff0c;其他都直接来这里复制了&#xf…...

ip网络广播系统网络音频解码终端公共广播SV-7101

SV-7101V网络音频终端产品简介 网络广播终端SV-7101V&#xff0c;接收网络音频流&#xff0c;实时解码播放。本设备只有网络广播功能&#xff0c;是一款简单的网络广播终端。提供一路线路输出接功放或有源音箱。 产品特点 ■ 提供固件网络远程升级■ 标准RJ45网络接口&…...

【Winform学习笔记(七)】Winform无边框窗体拖动功能

Winform无边框窗体拖动功能 前言正文1、设置无边框模式2、无边框窗体拖动方法1、通过Panel控件实现窗体移动2、通过窗体事件实现窗体移动3、调用系统API实现窗体移动4、重写WndProc()实现窗体移动 前言 在本文中主要介绍 如何将窗体设置成无边框模式、以及实现无边框窗体拖动功…...

【Nginx】静态资源部署、反向代理、负载均衡

个人简介&#xff1a;Java领域新星创作者&#xff1b;阿里云技术博主、星级博主、专家博主&#xff1b;正在Java学习的路上摸爬滚打&#xff0c;记录学习的过程~ 个人主页&#xff1a;.29.的博客 学习社区&#xff1a;进去逛一逛~ nginx静态资源部署、反向代理、负载均衡 &…...

二、框架篇

框架篇 Spring 1. 基础核心技术 第1章-Spring的模块与应用场景 第2章-Spring基于XML配置的容器 第3章-Spring基于注解配置的容器 第4章-Spring基于Java配置的容器 第5章-Spring三种配置方式的混合和迁移 第6章-Spring同类型多个Bean的注入 第7章-Spring的Bean生命周期…...

[LitCTF 2023]Http pro max plus

打开环境后提示说&#xff0c;只允许在本地访问&#xff0c;本地访问&#xff0c;还是想到了XFF字段 好家伙的&#xff0c;直接被嘲讽&#xff0c;还是了解太少了&#xff0c;都不知道还有没有其他方式可以控制ip地址信息 经过查看wp&#xff0c;得知一种新的方式 Client-IP …...

科技的成就(四十九)

381、机器人 Unimate 诞生 "1961 年&#xff0c;第一款工业机器人 Unimate 诞生。工程师恩格尔伯格受阿西莫夫小说《我&#xff0c;机器人》影响&#xff0c;与发明家德沃尔成立了 Unimation。1961 年&#xff0c;公司的第一台机器 人 Unimate 开始在通用电气新泽西工厂试…...

地理信息系统空间分析实验教程 第三版 第八章示例与练习 学校选址

学校选址 背景 合理的学校空间位置布局有利于学生的上课与生活。学校的选址问题需要考虑地理 E八位置、学生娱乐场所配套设施、与现有学校的距离等因素&#xff0c;从总体上把握这些国素能够确定出适宜性比较好的学校选址区 目的 通过练习&#xff0c;熟悉 ArcGIS 栅格数据…...

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …...

golang循环变量捕获问题​​

在 Go 语言中&#xff0c;当在循环中启动协程&#xff08;goroutine&#xff09;时&#xff0c;如果在协程闭包中直接引用循环变量&#xff0c;可能会遇到一个常见的陷阱 - ​​循环变量捕获问题​​。让我详细解释一下&#xff1a; 问题背景 看这个代码片段&#xff1a; fo…...

SciencePlots——绘制论文中的图片

文章目录 安装一、风格二、1 资源 安装 # 安装最新版 pip install githttps://github.com/garrettj403/SciencePlots.git# 安装稳定版 pip install SciencePlots一、风格 简单好用的深度学习论文绘图专用工具包–Science Plot 二、 1 资源 论文绘图神器来了&#xff1a;一行…...

云启出海,智联未来|阿里云网络「企业出海」系列客户沙龙上海站圆满落地

借阿里云中企出海大会的东风&#xff0c;以**「云启出海&#xff0c;智联未来&#xff5c;打造安全可靠的出海云网络引擎」为主题的阿里云企业出海客户沙龙云网络&安全专场于5.28日下午在上海顺利举办&#xff0c;现场吸引了来自携程、小红书、米哈游、哔哩哔哩、波克城市、…...

【位运算】消失的两个数字(hard)

消失的两个数字&#xff08;hard&#xff09; 题⽬描述&#xff1a;解法&#xff08;位运算&#xff09;&#xff1a;Java 算法代码&#xff1a;更简便代码 题⽬链接&#xff1a;⾯试题 17.19. 消失的两个数字 题⽬描述&#xff1a; 给定⼀个数组&#xff0c;包含从 1 到 N 所有…...

条件运算符

C中的三目运算符&#xff08;也称条件运算符&#xff0c;英文&#xff1a;ternary operator&#xff09;是一种简洁的条件选择语句&#xff0c;语法如下&#xff1a; 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true&#xff0c;则整个表达式的结果为“表达式1”…...

(二)原型模式

原型的功能是将一个已经存在的对象作为源目标,其余对象都是通过这个源目标创建。发挥复制的作用就是原型模式的核心思想。 一、源型模式的定义 原型模式是指第二次创建对象可以通过复制已经存在的原型对象来实现,忽略对象创建过程中的其它细节。 📌 核心特点: 避免重复初…...

ESP32 I2S音频总线学习笔记(四): INMP441采集音频并实时播放

简介 前面两期文章我们介绍了I2S的读取和写入&#xff0c;一个是通过INMP441麦克风模块采集音频&#xff0c;一个是通过PCM5102A模块播放音频&#xff0c;那如果我们将两者结合起来&#xff0c;将麦克风采集到的音频通过PCM5102A播放&#xff0c;是不是就可以做一个扩音器了呢…...

【论文笔记】若干矿井粉尘检测算法概述

总的来说&#xff0c;传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度&#xff0c;通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...

Python Ovito统计金刚石结构数量

大家好,我是小马老师。 本文介绍python ovito方法统计金刚石结构的方法。 Ovito Identify diamond structure命令可以识别和统计金刚石结构,但是无法直接输出结构的变化情况。 本文使用python调用ovito包的方法,可以持续统计各步的金刚石结构,具体代码如下: from ovito…...