那些年与指针的爱恨情仇(一)---- 指针本质及其相关性质用法

2024-01-27 12:36

本文主要是介绍那些年与指针的爱恨情仇(一)---- 指针本质及其相关性质用法,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!


关注小庄 顿顿解馋 (≧∇≦)

引言: 小伙伴们在学习c语言过程中是否因为指针而困扰,指针简直就像是小说女主,它逃咱追,我们插翅难飞…本篇文章让博主为你打理打理指针这个傲娇鬼吧~
本节我们将认识到指针本质,何为指针和她的性质用法,请放心食用!

指针本质及其性质用法

  • 一.指针的本质
    • 1.🏠 内存与地址
    • 2.🏠 指针变量与地址
      • 2.1 取址符(&)
      • 2.2指针变量
      • 2.3解引用操作符(*)
      • 2.4指针变量的大小
  • 二.指针的性质
    • 1.🏠指针类型的意义
      • 1.1 不同指针类型的解引用
      • 1.2指针+-整数
    • 2.🏠 const修饰指针
      • 2.1 const修饰变量
      • 2.2 const修饰指针变量
    • 3.🏠 指针运算
      • 3.1 指针+-整数
      • 3.2 指针-指针
      • 3.3指针的关系运算
  • 三.指针的使用
    • 1.🏠 野指针
    • 3.🏠assert断言
    • 4.🏠传值调用和传址调用
    • 在这里插入图片描述

一.指针的本质

1.🏠 内存与地址

内存单元:我们在学校寄宿时是几个人为一个集体为宿舍的,为了方便入住以及内务检查学校给每个宿舍设置了门牌号方便辨认。类似地,存储数据的内存划分一个一个内存单元同时给它们编上“号”方便取出数据。

内存单元:内存最小单元,单位是字节也就是8个bit。

地址:正如上言所说,我们给内存单元的编号就是地址,在c语言中我们叫它指针

总结: 指针 == 地址 == 内存单元编号 他是我们用来取数据的东西

2.🏠 指针变量与地址

数据在内存中的地址就是指针,类比之前我们学习过的数据类型,整形变量是存储整形的变量,那么指针变量就是存指针的变量

我们知道指针变量了先不着急,我们知道指针变量存的是地址,赋值两边要相同类型你知道怎么获得地址吗?

2.1 取址符(&)

取址符顾名思义就是取出地址,打印地址时我们用的是%p,在我们编译器如何查看地址呢?接下来上图

在这里插入图片描述
在vs上进入调试模式->调试->窗口->内存/监视窗口都可查看内存,别忘记加上我们的取址符哦

2.2指针变量

指针变量也是⼀种变量,这种变量就是⽤来存放地址的,存放在指针变量中的值都会理解为地址,他的定义如下:

Datatype * name;
int a = 10;
int * pf = &a;
char c = 'c';
char * pc = &c;

理解:这里的*用来表示这是一个指针变量,前面的Datatype表示的是指针指向的数据类型,而去掉变量名就是这个指针变量的类型,比如int* pf表示pf是个指针变量它指向的数据类型是int,存储的是int类型数据的地址,指针类型是int *

类比地,聪明的你应该也会类比其他数据类型了吧~

void*类型
在指针类型中有这么一个存在,它可以可以理解为⽆具体类型的指针(或者叫泛型指针),这种类型的指针可以⽤来接受任意类型地址。就像一个公共场所。

2.3解引用操作符(*)

有的小伙伴到这里可能要问了既然知道什么是指针变量,那他怎么用啊?快端上来罢!这里我们就要了解我们的解引用操作符了

客人房间(变量)房间编号(地址)
变量a0x11223344
变量b0x12332166
变量c0x12ffaa11

如上表所示,每个变量(房间)都对应一个编号(地址),而要进入房间使用它就得用解引用操作符

int a = 1;
printf("%d ",a);
int *p = &a;
*p = 10;
printf("%d",a);

输出结果是1 10

总结: 解引用操作符(*)作用是通过地址,以地址为媒介,找到地址指向的空间对该空间进行操作。

2.4指针变量的大小

我们知道不同的数据类型所占字节大小不同,那不同的指针类型是不是也是大小不同呢?接下来我们先普及一下编址的相关知识

在这里插入图片描述
CPU与内存之间是有大量数据交互的。大致过程如下:CPU先通过控制总线下命令->地址总线收到命令找对应地址->通过数据总线传输数据。在这个过程中地址总线不只图中一条哦,作者只是大概画出来。不同机器地址总线总数不同,比如32位机器有32条,64位有64条,每根线只有两态,表⽰0,1【电脉冲有⽆】,那么⼀根线,就能表⽰2种含义,2根线就能表⽰4种含义,依次类推。32根地址线,就能表⽰2^32种含义,每⼀种含义都代表⼀个地址

通过上面的知识普及我们了解到地址总线发出的电信号转换成二进制序列就是我们的地址,所以不同的机器就会产生不同位的二进制序列当做地址,32位地址->32个bit->4个字节,64位地址->64个bit->8个字节。

printf("%zd\n", sizeof(char *));
printf("%zd\n", sizeof(short *));
printf("%zd\n", sizeof(int *));
printf("%zd\n", sizeof(double *));

x86(32位环境) 输出结果为4 4 4 4
x64(64位环境) 输出结果为8 8 8 8

总结: 指针变量大小与它指向的数据类型无关,而与使用的机器有关

二.指针的性质

1.🏠指针类型的意义

既然指针变量大小与指针类型无关,那为什么还要设置这么多类型的指针变量?有句话叫做存在即合理,设置了它肯定是有它的意义的。

1.1 不同指针类型的解引用

//代码1
int n = 0x11223344;
int *pi = &n;
*pi = 0;
//代码2
int n = 0x11223344;
char* pc = &n;
*pc = 0;

两段代码都是存了n的地址,只不过是不同指针类型,那是否值都变为0了呢?我们用之前的查看内存知识观察下。
在这里插入图片描述
通过上面观察我们发现不同指针类型得出的结果也不同,由此我们可以得出结论:不同指针类型解引用权限不同,也就是能操作字节个数不同,正如上面的例子int解引用能将4个字节都置为0,而char解引用只能将1个字节置为0.

1.2指针±整数

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)

输出结果:
004FF700
004FF700
004FF701
004FF700
004FF704

我们可以看出, char* 类型的指针变量+1跳过1个字节, int* 类型的指针变量+1跳过了4个字节。
这就是指针变量的类型差异带来的变化。

结论:指针类型还决定了指针向前或向后走一步有多大。
注:之前提到的void*虽然能接收各种类型指针,但却不能使用指针加减也不能解引用,可谓是上天为你关了一扇门也为你打开一扇窗啊。

2.🏠 const修饰指针

2.1 const修饰变量

变量是可以修改的,如果把变量的地址交给⼀个指针变量,通过指针变量的也可以修改这个变量。
但是如果我们希望⼀个变量加上⼀些限制,不能被修改,怎么做呢?这就是const的作⽤。

int m = 0;
m = 20;//m是可以修改的
const int n = 0;
n = 20;//n是不能被修改的

加上const后n不能动了,那我们运用之前的解引用呢?

const int n = 0;
printf("n = %d\n", n);
int*p = &n;
*p = 20;
printf("n = %d\n", n);

我们可以看到这⾥⼀个确实修改了,但是我们还是要思考⼀下,为什么n要被const修饰呢?就是为了
不能被修改,如果p拿到n的地址就能修改n,这样就打破了const的限制,这是不合理的,所以应该让
p拿到n的地址也不能修改n,那接下来怎么做呢?

2.2 const修饰指针变量

我们给上几段代码来测试下

//代码1 无const
int n = 10;
int m = 20;
int *p = &n;
*p = 20;//能否赋值
p = &m; //能否赋值
//代码2  const在*左边
int n = 10;
int m = 20;
const int *p = &n;
*p = 20;//能否赋值
p = &m; //能否赋值
//代码3 const在*右边
int n = 10;
int m = 20;
int *const p = &n;
*p = 20;//能否赋值
p = &m; //能否赋值
//代码4 const在*左边和右边
int n = 10;
int m = 20;
int const *const p = &n;
*p = 20;//能否赋值
p = &m; //能否赋值

代码1 :都能赋值
代码2 : 语句1不能赋值
代码3 :语句2不能赋值
代码4: 两语句都不能赋值

总结:1.const如果放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变,但指针变量本身的内容可变。2.const如果放在*的右边,修饰的是指针变量本身的内容,保证存储的内容不能修改,但指针指向内容可通过指针修改3.const在*的左边和右边则指针变量本身内容和指针指向内容都不能被修改。

总的来说,const修饰变量具有常量属性,但解引用相当于绕道找后门进入这个变量修改变量的值。此时用const修饰指针相当于把这个后门也关了

3.🏠 指针运算

  1. 指针±整数
  2. 指针-指针
  3. 指针关系运算

3.1 指针±整数

根据我们前面分析,指针±整数表示不同指针向前一步或向后一步的大小。这在数组可以很好运用,我们知道数组在内存中是连续存放的,这样知道其中一个元素地址再运用指针±整数就能得到其他元素地址,我们可以用它来打印数组

int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = &arr[0];
int i = 0;
int sz = sizeof(arr)/sizeof(arr[0]);
for(i=0; i<sz; i++)
{
printf("%d ", *(p+i));//p+i 这⾥就是指针+整数
}

3.2 指针-指针

int arr[10] = {1,2,3,4,5,6,7,8,9,10};
printf("%d",&arr[2]-&arr[0]);
printf("%d",&arr[0]-&arr[2]);

输出结果为2 -2
我们可以得出指针-指针的绝对值得到的是指针与指针之间的元素个数,可以类比日期-日期是天数,而指针-指针、就是元素个数。前面的指针±也可以这样类比,日期加天数还是日期

注意:该运算的前提条件是相同类型的指针变量

3.3指针的关系运算

指针也是可以比较大小的,比较的是在内存位置的高低。

三.指针的使用

  接下来我们来了解一些关于指针的使用和注意事项吧

1.🏠 野指针

野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的),通常以下三种情况会造成野指针。

  1. 指针未初始化
  2. 指针越界访问
  3. 指针指向空间释放(一般指针调用函数后内存空间被回收)
//原因3
int* test()
{
int n = 100;
return &n;
}
int main()
{
int*p = test();
printf("%d\n", *p);
return 0;
}

这个地方*p接收了返回的&n地址,但是调用函数后n就被释放了,也就是我们常说的空有其表

规避方法:

  1. 指针初始化为NULL
  2. 小心越界访问
  3. 不要返回局部变量地址
  4. 指针不用了及时置为NULL

3.🏠assert断言

assert.h 头⽂件定义了宏 assert() ,用于在运⾏时确保程序符合指定条件,如果不符合,就报错终⽌运行这个宏常常被称为“断⾔”。assert() 宏接受⼀个表达式作为参数。如果该表达式为真(返回值⾮零), assert() 不会产⽣任何作⽤,程序继续运⾏。如果该表达式为假(返回值为零), assert() 就会报错,在标准错误stderr 中写⼊⼀条错误信息,显⽰没有通过的表达式,以及包含这个表达式的⽂件名和⾏号。

好处:
1.能⾃动标识⽂件和出问题的⾏号
2.有⽆需更改代码就能开启或关闭 assert() 的机制。(如果已经确认程序没有问题,不需要再做断⾔,就在 #include <assert.h> 语句的前⾯,定义⼀个宏NDEBUG)

4.🏠传值调用和传址调用

我们知道函数传值调用时形参是实参的一份临时拷贝,改变形参不影响实参,那有什么方法能改变我们的实参呢?答案是我们的传址调用。

oid Swap2(int*px, int*py)
{
int tmp = 0;
tmp = *px;
*px = *py;
*py = tmp;
}
int main()
{
int a = 10;
int b = 20;
printf("交换前:a=%d b=%d\n", a, b);
Swap1(&a, &b);
printf("交换后:a=%d b=%d\n", a, b);
return 0;
}

输出结果
交换前 a=10 b=20
交换后 a=20 b=10

由此可以得出,传址调用本质上也是传值,也是拷贝地址的值传到函数内部,只不过地址指向的空间是唯一的,我们便能通过地址来改变它的空间。值得注意的是,在函数内部创建的px,py跟a,b不是同个变量只不过他们存储的值(地址)一样,存储的值能干的事一样!

在这里插入图片描述

本次分享到这里就结束了,希望小伙伴能够学到知识,喜欢的话给小庄来个三连吧!

这篇关于那些年与指针的爱恨情仇(一)---- 指针本质及其相关性质用法的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/650257

相关文章

Redis的Hash类型及相关命令小结

《Redis的Hash类型及相关命令小结》edisHash是一种数据结构,用于存储字段和值的映射关系,本文就来介绍一下Redis的Hash类型及相关命令小结,具有一定的参考价值,感兴趣的可以了解一下... 目录HSETHGETHEXISTSHDELHKEYSHVALSHGETALLHMGETHLENHSET

Springboot中Jackson用法详解

《Springboot中Jackson用法详解》Springboot自带默认json解析Jackson,可以在不引入其他json解析包情况下,解析json字段,下面我们就来聊聊Springboot中J... 目录前言Jackson用法将对象解析为json字符串将json解析为对象将json文件转换为json

python中的与时间相关的模块应用场景分析

《python中的与时间相关的模块应用场景分析》本文介绍了Python中与时间相关的几个重要模块:`time`、`datetime`、`calendar`、`timeit`、`pytz`和`dateu... 目录1. time 模块2. datetime 模块3. calendar 模块4. timeit

sqlite3 相关知识

WAL 模式 VS 回滚模式 特性WAL 模式回滚模式(Rollback Journal)定义使用写前日志来记录变更。使用回滚日志来记录事务的所有修改。特点更高的并发性和性能;支持多读者和单写者。支持安全的事务回滚,但并发性较低。性能写入性能更好,尤其是读多写少的场景。写操作会造成较大的性能开销,尤其是在事务开始时。写入流程数据首先写入 WAL 文件,然后才从 WAL 刷新到主数据库。数据在开始

bytes.split的用法和注意事项

当然,我很乐意详细介绍 bytes.Split 的用法和注意事项。这个函数是 Go 标准库中 bytes 包的一个重要组成部分,用于分割字节切片。 基本用法 bytes.Split 的函数签名如下: func Split(s, sep []byte) [][]byte s 是要分割的字节切片sep 是用作分隔符的字节切片返回值是一个二维字节切片,包含分割后的结果 基本使用示例: pa

两个月冲刺软考——访问位与修改位的题型(淘汰哪一页);内聚的类型;关于码制的知识点;地址映射的相关内容

1.访问位与修改位的题型(淘汰哪一页) 访问位:为1时表示在内存期间被访问过,为0时表示未被访问;修改位:为1时表示该页面自从被装入内存后被修改过,为0时表示未修改过。 置换页面时,最先置换访问位和修改位为00的,其次是01(没被访问但被修改过)的,之后是10(被访问了但没被修改过),最后是11。 2.内聚的类型 功能内聚:完成一个单一功能,各个部分协同工作,缺一不可。 顺序内聚:

log4j2相关配置说明以及${sys:catalina.home}应用

${sys:catalina.home} 等价于 System.getProperty("catalina.home") 就是Tomcat的根目录:  C:\apache-tomcat-7.0.77 <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} [%t] %-5p %c{1}:%L - %msg%n" /> 2017-08-10

【C++学习笔记 20】C++中的智能指针

智能指针的功能 在上一篇笔记提到了在栈和堆上创建变量的区别,使用new关键字创建变量时,需要搭配delete关键字销毁变量。而智能指针的作用就是调用new分配内存时,不必自己去调用delete,甚至不用调用new。 智能指针实际上就是对原始指针的包装。 unique_ptr 最简单的智能指针,是一种作用域指针,意思是当指针超出该作用域时,会自动调用delete。它名为unique的原因是这个

Node Linux相关安装

下载经编译好的文件cd /optwget https://nodejs.org/dist/v10.15.3/node-v10.15.3-linux-x64.tar.gztar -xvf node-v10.15.3-linux-x64.tar.gzln -s /opt/node-v10.15.3-linux-x64/bin/npm /usr/local/bin/ln -s /opt/nod

git ssh key相关

step1、进入.ssh文件夹   (windows下 下载git客户端)   cd ~/.ssh(windows mkdir ~/.ssh) step2、配置name和email git config --global user.name "你的名称"git config --global user.email "你的邮箱" step3、生成key ssh-keygen