C语言——指针(入门详解)

news/2024/5/14 12:25:24/文章来源:https://blog.csdn.net/m0_71927622/article/details/128052329

文章目录

  • 1.什么是指针?
    • 1.1.理解指针的两个要点:
    • 1.2.指针变量:
    • 1.3.内存是如何编址?
  • 2.指针和指针类型
    • 2.1指针的创建与初始化
    • 2.2.指针类型
  • 3.野指针
    • 3.1.什么视野指针?
    • 3.2.野指针成因
    • 3.3.规避野指针
  • 4.指针运算
    • 4.1.指针+-整数
    • 4.2.指针-指针
    • 4.3指针的关系运算
  • 5.指针与数组
  • 6.二级指针
    • 6.1.什么是二级指针
    • 6.2.二级指针的运算
  • 7.指针数组
  • 总结

1.什么是指针?

1.1.理解指针的两个要点:

1.指针是内存中最小单元的编号,也就是地址。
2.平时口语中的指针,通常指的是指针变量,指针变量是用来存放内存地址的变量。
总结:指针其实就是地址,口语中的指针通常指的是指针变量。

在这里插入图片描述
补充:一个内存单元占一个字节。

1.2.指针变量:

通过&取地址操作符取出变量在内存中的起始地址,将取出的地址存放在一个变量中,这个存放地址的变量就是指针变量。

下面通过代码演示指针变量的创建与使用

int main()
{int a = 10;int* pa  = &a;//创建指针变量并初始化//打印验证printf("%p\n", &a);printf("%p\n", pa);//使用指针变量需要用*解引用操作符*pa = 20;//此时*pa等价与aprintf("%d\n", *pa);printf("%d\n", a);return 0;
}

在这里插入图片描述
补充:

1、a在内存中占4个字节,&a取出的是整型变量a的第一个字节的地址(最小的地址)。
2.当需要通过地址访问来改变变量的值时,需要使用*解引用操作符对指针变量进行解引用操作。

1.3.内存是如何编址?

对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的时候产生高电平(高电压)和低电平(低电压)就是(1或者0)。
在这里插入图片描述
每个地址标识1个字节,操作系统就会分配2的32次方个字节,也就是4GB的空间进行编址。同理可得64位机器,每个地址标识2个字节,操作系统就会分配2的64次方个字节,也就是8GB的空间进行编址。

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

总结:

1、指针变量是用来存放地址的,地址是唯一标识一个内存单元的。
2、在32位平台下,地址的大小是4个字节,指针变量的大小是4个字节。在64位平台下,地址的大小是8个字节,指针变量的大小也是8个字节。

2.指针和指针类型

2.1指针的创建与初始化

我们都知道变量是分类型的,如整型、单精度浮点型、字符型等。那指针是否也分类型呢?答案是有的。
下面我通过代码举例

int main()
{int a = 10;//创建整型指针变量pa并初始化int* pa = &a;//将变量a的地址存放到指针变量pa中return 0;
}

上例中,我将变量a的地址存放到指针变量pa中。pa的类型是int*(整形指针)。由此我们可以得到
指针的定义方式是:

type(类型) + * +prt_name(指针变量名字)

其实:
char* 类型的指针是为了存放 char 类型变量的地址。
short* 类型的指针是为了存放 short 类型变量的地址。
int* 类型的指针是为了存放 int 类型变量的地址。

让我们来看看第二个例子

int main()
{int a = 10;char* pc = &a;//char*类型的指针能否得存放int类型变量的地址呢?return 0;
}

答案是:能够存放。因为一个指针变量在32位环境下都是占4个字节。这里不要门缝里看char*类型指针,把它给看遍了。这也是我们需要注意的一个小细节。

既然指针变量的大小在32位平台下都是4个字节,那指针变量的类型存在的意义是什么呢?

2.2.指针类型

这里我依旧是通过两段代码进行举例

//例一
#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);printf("%p\n", pi);printf("%p\n", pi + 1);return  0;
}

在这里插入图片描述
通过上面的例子我们可以发现,指针的类型决定了指针+1后访问的步长。int类型指针+1向后移动了4个字节,char类型指针+1向后移动了1个字节。同理可得short类型指针+1向后移动2个字节,float类型指针+1向后移动4个字节。

总结

指针变量的类型决定了指针的步长(向前或向后走一步的距离)。

//例二int main()
{int a = 0x11223344;//0x表示十六进制类型char* pc = (char*)&a;
//a是整型,需要对&a进行强制类型转换成char*后编译器才不会报错int* pi = &a;*pc = 0;   *pi = 0;   return 0;
}

先调试这段代码
在这里插入图片描述

让我们看看pc = 0的效果
在这里插入图片描述
接下来是
pi = 0;这句代码的效果
在这里插入图片描述
通过调试的内存窗口,可以发现当对char类型指针变量进行解引用操作后,访问的权限是1个字节,而对int类型指针变量进行解引用访问操作,访问的权限是4个字节。所以指针变量的类型其实是由意义的。

总结:

指针变量的类型决定了解引用操作后的指针的访问权限。int类型指针变量解引用操作访问权限是4个字节,char类型指针变量解引用操作访问权限是1个字节

3.野指针

3.1.什么视野指针?

野指针的概念

野指针指的是指向不可知的指针变量(随机的、不正确的、不可控的)。

3.2.野指针成因

1.指针变量未进行初始化

int main()
{int* pa;*pa = 20;return 0;
}

上例中,指针变量pa未进行初始化,所以pa是一个野指针,对pa进行仅引用操作是极其危险的,因为pa的指向是不可知的。

指针越界访问

int main()
{int arr[5]={1,2,3,4,5};int *p = arr;int i = 0;for(i = 0; i <= 5; i++){*(p++) = i;}return 0;
}

当运行这段代码后,编译器会进行报错
在这里插入图片描述

这里编译器告诉我们因为指针访问越界,数组arr被破坏了。所以当p超出数组的合理范围后,p就是一个野指针,对野指针进行解引用操作是很危险的。

3.3.规避野指针

1.创建指针变量同时初始化指针变量。
2. 小心指针越界。
3. 指针指向空间释放,及时置NULL。
4. 避免返回局部变量的地址。
5. 指针使用之前检查有效性。

4.指针运算

4.1.指针±整数

代码举例如下

#include<stdio.h>int main()
{int arr[5] = { 0 };int* pa = arr;int i = 0;//将数组内容赋值成1~5for (i = 0; i < 5; i++){*pa = i + 1;pa = pa + 1;//指针加整数}//打印数组内容for (i = 0; i < 5; i++){printf("%d ", arr[i]);}return 0;
}

在这里插入图片描述

4.2.指针-指针

指针-指针的前提条件是,两个指针变量指向同一块空间。

int main()
{int arr[10] = { 0 };int* pa = &arr[9];int* pb = &arr[0];//此时pa和pb都是指向数组arr的内容printf("%d\n",pa - pb);return 0;
}

在这里插入图片描述

指针-指针得到的绝对值是,两个指针之间相同类型的元素的个数。

下面我通过指针-指针的方式模拟实现库函数strlen

#include<assert.h>size_t MyStrlen(const char* str)
{assert(str != NULL);char* start = str;//记录初始地址while (*str++){;}//通过指针-指针返回字符串长度return str - start - 1;}int main()
{char str[] = "abcde";printf("%d\n",MyStrlen(str));return 0;
}

4.3指针的关系运算

#define N_VALUES 5
int main()
{float values[N_VALUES];float *vp;//								指针的关系运算for(vp = &values[N_VALUES]; vp > &values[0];){*--vp = 0;}return 0;
}

在这里插入图片描述
将上边的代码稍作修改

#define N_VALUES 5
int main()
{float values[N_VALUES];float *vp;//								指针的关系运算for(vp = &values[N_VALUES]; vp > &values[0];vp--){*vp = 0;}return 0;
}

在这里插入图片描述

代码修改后,看起来是更易读了,可是我们应该避免这样写代码。虽然上面的代码在市面上绝大部分编译器都可以正常运行,但是C语言标准中并不能保证它是对的。

C语言标准规定:
允许指向数组元素的指针与指向数组的最后一个元素后面那个内存空间的指针作比较,但是不允许与第一个元素前面那个内存空间的指针进行比较。

5.指针与数组

指针和数组是不同的对象。指针是一种用来存放地址的变量,大小是4/8个字节。数组是一组相同类型元素的集合,可以放多个元素,大小取决于元素个数和元素类型的。数组的数组名是数组首元素的地址,地址是可以存放在指针变量中的。可以通过指针来访问数组。

#include<stdio.h>
int main()
{int arr[10] = {0};printf("%p\n",arr);printf("%p\n",&arr);return 0;
}

在这里插入图片描述
由上例可知,数组名本质是首元素的地址(排除&数组名和sizeof(数组名)这两种情况)。既然数组名是数组首元素的地址,我们不妨将数组名存入一个指针变量中,再通过这个指针变量来遍历整个数组。

#include<stdio.h>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("&arr[%d] = %p   <====> p+%d = %p\n", i, &arr[i], i, p + i);}return 0;
}

在这里插入图片描述
可以发现其实 p+i 是完全等价于 arr[ i ]的。接下来试试通过指针运算来便利整形数组并且打印数组

int main()
{int arr[10] = {0};int* pa = arr;int sz = sizeof(arr)/sizeof(arr[0]);int i = 0;
//改变数组内容1~10for(i = 0; i < sz; i++){*pa = i + 1;pa++;}pa = arr;//重新指定指针的位置以避免越界
//打印数组内容for(i = 0; i < sz; i++){printf("%d ",*pa);pa++;}return 0;
}
int main()
{int arr[10] = {0};int* pa = arr;int sz = sizeof(arr)/sizeof(arr[0]);int i = 0;
//改变数组内容1~10for(i = 0; i < sz; i++){*(pa+i) = i + 1;}//打印数组内容for(i = 0; i < sz; i++){printf("%d ",*(pa+i));}return 0;
}

补充:
[ ]下标访问操作符两边,arr 和 i 是下标访问操作符的两个操作数。[ ]下标访问操作符是支持交换律的,所以 arr[ i ] 等价于 i[arr]。

6.二级指针

6.1.什么是二级指针

指针变量是用来存放地址的,那指针变量的地址该存放到哪里呢?这就要引出二级指针这个概念了。二级指针是用来存放指针变量的地址的。

int main()
{int a = 10;int* pa = &a;int** ppa = &pa;return 0;
}

在这里插入图片描述

6.2.二级指针的运算

通过 * 解引用操作符对二级指针进行解引用操作。

int main()
{int a = 10;int* pa = &a;int** ppa = &pa;**ppa = 20;//*(*ppa) =*(pa) = a; //通过对* *ppa进行解引用操作得到pa,再通过对pa进行解引用操作得到a//**ppa等价于aprintf("%d\n", a);
}

在这里插入图片描述

7.指针数组

指针数组是什么呢?是数组?还是指针?答案是:指针数组是用来存放指针的数组。通过前面的学习,我们了解到的数组类型有字符数组、整型数组、单精度浮点型数组等。

int arr[] = {1,2,3,4,5};
char ch[] = {'a', 'b', 'c'};

在这里插入图片描述

int* arrp[] = {0x0012ff40, 0x0012ff41, 0x0012ff42};

在这里插入图片描述

下面我通过一个指针数组加三个一维数组来模拟实现二维数组

#include<stdio.h>int main()
{int arr1[3] = { 1,2,3 };int arr2[3] = { 4,5,6 };int arr3[3] = { 7,8,9 };int* arrp[3] = { arr1,arr2,arr3 };int i = 0;int j = 0;for (i = 0; i < 3; i++){for (j = 0; j < 3; j++){printf("%d ",arrp[i][j]);//arr[i]相当于是arr[列号]。通过指针运算,就可以遍历整个数组。}printf("\n");}return 0;
}

在这里插入图片描述
在这里插入图片描述

通过了访问指针数组arrp中存放的三个整形数组的首元素地址,然后再通过指针运算,就可以模拟实现一个二维数组了。

总结

总有人说指针多难多难,在我看来只要搞清楚指针的概念以及指针的用法,理解指针也不是特别难的。这只需要我们多去思考和总结。学习没有捷径,只有你真的认真学了才能够真正的掌握知识。最后,也希望看完本篇文章的你有所收获,有什么问题也欢迎在评论区进行讨论。

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

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

相关文章

【MySQL】数据库服务器硬件优化与实战详解(调优篇)(实战篇)(MySQL专栏启动)

&#x1f4eb;作者简介&#xff1a;小明java问道之路&#xff0c;专注于研究 Java/ Liunx内核/ C及汇编/计算机底层原理/源码&#xff0c;就职于大型金融公司后端高级工程师&#xff0c;擅长交易领域的高安全/可用/并发/性能的架构设计与演进、系统优化与稳定性建设。 &#x1…

【一文秒懂——SLF4j日志】

目录 1. SLF4j日志 2. 日志输出 1. SLF4j日志 在添加了spring-boot-starter的项目中&#xff0c;已经包含了SLF4j日志的相关依赖项。 在添加了lombok的项目中&#xff0c;可以在类上添加Slf4j注解&#xff0c;则lombok框架会在编译期在类中声明名为log的变量&#xff0c;通…

2022Flink大数据比赛项目-焦点科技大数据编程大赛

文章目录0.数据获取1.需求概要2.数据标准2.1.输入数据格式2.2.输出数据格式2.3.数据主键及关系3.表详细1.order_info2.bill_info3.bill_item4.ord_pay5.ord_pay_log6.pay_method7.pay_bank_card4.开发工具、语言版本、工具版本5.赛题解答数据分流任务1任务2任务3小结附录0.数据…

聊聊雪花算法?

随便聊聊 哈喽&#xff0c;大家好&#xff0c;最近换了份工作&#xff0c;虽然后端技术栈是老了点&#xff0c;但是呢&#xff0c;这边的前端技术确是现在市面上最新的那一套技术&#xff1a;Vue3ViteTSXPinaElement-PlusNativeUI。我本人主要是学后端的&#xff0c;确被拉去做…

【博客545】从交换机视角看四种报文:广播、组播、未知单播、已知单播

从交换机视角看四种报文&#xff1a;广播、组播、未知单播、已知单播 交换机视角的四种报文 对于二层交换机来说&#xff0c;它在转发报文时&#xff0c;只有四种类型的报文&#xff1a; 1、广播 2、组播 3、未知单播 4、已知单播。四种报文剖析 1、二层广播报文 当二层交换…

SignalR简介及实践指南

SigalR简介 ASP.NET Core SignalR 是一个开放源代码库&#xff0c;可用于简化向应用添加实时 Web 功能。 实时 Web 功能使服务器端代码能够将内容推送到客户端。 适合 SignalR 的候选项&#xff1a; 需要从服务器进行高频率更新的应用。 示例包括游戏、社交网络、投票、拍卖…

易观千帆 | 2022年10月银行APP月活跃用户规模盘点

易观分析&#xff1a;易观千帆数据显示&#xff0c;10月手机银行服务应用活跃人数52285.79万&#xff0c;环比下降3.52%。手机银行服务应用月活规模经历了连续5个月的持续增长后&#xff0c;10月出现下降。 10月城商行手机银行服务应用活跃人数3565.56万&#xff0c;环比下降2…

UNIAPP实战项目笔记46 订单确认页面的布局

UNIAPP实战项目笔记46 订单确认页面的布局 实际案例图片 订单页面 具体内容图片自己替换哈&#xff0c;随便找了个图片的做示例 具体位置见目录结构 完善布局页面和样式 代码 confirm-order.vue部分 confirm-order.vue 确认订单页面布局和渲染 flex 样式布局 <template>…

字符串5:剑指Offer58-II.左旋转字符串

主要是我自己刷题的一些记录过程。如果有错可以指出哦&#xff0c;大家一起进步。 转载代码随想录 原文链接&#xff1a; 代码随想录 leetcode链接&#xff1a;344. 反转字符串 题目&#xff1a; 字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个…

衡师11月月赛web题目wp

目录 1.丢三落四的学姐 2.wep&#xff1f;Pwn&#xff01;&#xff01;&#xff01; 这题web部分是buuctf中的DASCTF X GFCTF 2022十月挑战赛&#xff01;的原题 1.丢三落四的学姐 访问题目位置&#xff0c;很明显的phpstudy搭建的痕迹 访问一下经常信息泄露的几个文件&…

Baklib|知识库应用场景:制作员工培训手册

持续的专业发展对于想要加入、保留和提升员工的组织来说是必不可少的。为了确保员工总是能从学习能力中受益&#xff0c;您需要考虑创建培训手册&#xff0c;使员工能够胜任并保持他们的工作能力。 在过去&#xff0c;您可能认为培训手册是一本厚重的册子&#xff0c;充满了密…

一文彻底搞懂Mysql索引优化

专属小彩蛋&#xff1a;前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff08;前言 - 床长人工智能教程&#xff09; 目录 一、索引介绍 二、性能分析 三、查询优化 一、索引介绍…

Oracle中ALTER TABLE的五种用法(三)

首发微信公众号&#xff1a;SQL数据库运维 原文链接&#xff1a;https://mp.weixin.qq.com/s?__bizMzI1NTQyNzg3MQ&mid2247485212&idx1&sn450e9e94fa709b5eeff0de371c62072b&chksmea37536cdd40da7a94e165ce4b4c6e70fb1360d51bed4b3566eee438b587fa231315d0a5a…

通俗易懂的java设计模式(1)-单例模式

什么是单例模式&#xff1f; 单例模式是java中最简单的一种设计模式 需要注意的问题&#xff1a; 1.单例类有且只能有一个实例 2.单例类必须自己创建出这个实例&#xff0c;并提供给外界 那么如何自己创建实例而不让外界创建呢&#xff1f;很简单&#xff0c;我们将无参的构造函…

传输线理论基础01——相关定义、信号速率、分布参数与电报方程

前言一直以来都对高频信号、信号完整性、传输线、分布参数这些概念似懂非懂&#xff0c;上学时没学过相关课程&#xff0c;这导致我对高频电路和PCB理解较差&#xff0c;这里新开一个专栏&#xff0c;补齐这方面知识。 一. 传输线相关定义1.1 传输线定义 传输线指的是传输信号…

【Hack The Box】Linux练习-- Seventeen

HTB 学习笔记 【Hack The Box】Linux练习-- Seventeen &#x1f525;系列专栏&#xff1a;Hack The Box &#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; &#x1f4c6;首发时间&#xff1a;&#x1f334;2022年9月7日&#x1f334; &#x1f…

【数据去噪】SG-多项式平滑算法

文章目录一、简介二、原理5点3次多项式平滑三、代码1. 3点线性平滑2. 5点线性平滑3. 5点2次线性平滑4. 5点3次线性平滑5. 7点线性平滑6. 7点2次线性平滑一、简介 在处理工业数据的时候&#xff0c;工业数据有数据颗粒细&#xff0c;噪声大&#xff0c;量大&#xff0c;随着测量…

拿捏Fiddler抓包教程(10)-Fiddler如何设置捕获Firefox浏览器的Https会话

1.简介 经过上一篇对Fiddler的配置后&#xff0c;绝大多数的Https的会话&#xff0c;我们可以成功捕获抓取到&#xff0c;但是有些版本的Firefox浏览器仍然是捕获不到其的Https会话&#xff0c;需要我们更进一步的配置才能捕获到会话进行抓包。 2.宏哥环境 1.宏哥的环境是Win…

【Python】四、程序顺序和分支控制结构

文章目录实验目的一、赋值语句应用练习二、if- else分支结构三、多分支选择结构四、选择嵌套五、编程实现百分制换成五级制(优,良,中,及格,不及格)1.设计思路2.设计算法3.参考代码4.实验截图实验目的 掌握顺序和分支结构&#xff1b;培养学生动手查阅资料能力和解决实际问题的能…

CAD特殊符号,你不一定会

在CAD软件中&#xff0c;有时候会输入一些特殊的符号。比如在标明高低差的时候会输入“”号&#xff0c;在标明管子或者钢筋的直径为输入直径符号“”&#xff0c;为了标明角度值需要输入符号“”&#xff0c;那么这些符号怎么快速的绘制出来呢&#xff1f;我们一起用CAD梦想画…