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

指针的学习1

目录

什么是指针?

野指针

造成野指针的原因:

如何避免野指针?

内存和指针

如何理解编址?

指针变量和地址

取地址操作符&

指针变量和解引用操作符

指针变量

如何拆解指针类型?

指针变量的大小

指针变量类型的意义

指针的解引用

指针+整数

void*指针

void* 类型的指针有什么用呢?

const修饰指针

const修饰变量

const修饰指针变量

指针运算

指针+-整数

指针-指针

指针的关系运算

assert断言

使用assert()有几个好处:

指针的使用和传址调用

strlen的模拟实现

传值调用和传址调用


什么是指针?

指针(pointer)是编程语言中的一个对象,利用地址,它的值直接指向存在电脑存储器中另一个地方的值,由于通过地址能找到所需的变量单位,可以说,地址指向该变量单位,因此,将地址形象化的称为“指针”,意思是,通过它能找到以它为地址的内存单元

指针是一个变量,存放内存单元的地址(编号)

#include <stdio.h>
int main()
{int a = 10;//在内存中开辟一组空间int* p = &a;//将a的地址存放在p变量中,p就是一个指针变量return 0;
}

野指针

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

造成野指针的原因:
  1. 指针未初始化;
  2. 指针越界访问;
  3. 指针指向内存空间释放; 
如何避免野指针?
  • 指针初始化;

如果明确知道指针指向哪里就直接赋值地址,如果不知道指针应该指向哪里,可以给指针赋值NULL

NULL是C语言中定义的一个标识符常量,值是0,0也是地址,这个地址是无法使用的,读写该地址会报错

  • 小心指针越界;
  • 指针指向空间释放即使置NULL;指针使用之前检查有效性 

当指针变量指向一块区域的时候,我们可以通过指针访问该区域,后期不再使用这个指针访问空间的时候,我们可以把该指针置为NULL,因为约定俗成的一个规则就是:只要是NULL指针就不去访问,同时使用指针之前可以判断指针是否为NULL

  • 避免返回局部变量的地址

内存和指针

计算机上CPU(中央处理器)在处理数据的时候,需要的数据是在内存中读取的,处理后的数据也会放回内存中

把内存划分为一个个的内存单元,每个内存单元的大小取1个字节

1byte=8bit(bit是最小的内存单元)

一个比特位可以存储一个2进制的位1或者0

内存单元的编号=地址=指针

如何理解编址?

首先,必须理解,计算机内有很多的硬件单元,这些硬件单元要互相协同工作。所谓的协作,至少相互之间要能够进行数据传递

但是,硬件与硬件之间是相互独立的,需要用“线”进行通信

CPU和内存之间也是有大量的数据交互的,所以两者必须用线连起来-地址总线

CPU访问内存中的某个字节空间,必须知道这个字节空间在内存的什么位置,而内存中字节很多,需要给内存进行编址

计算机中的编址,并不是把每个字节的地址记录下来,而是通过硬件设计完成的

32位机器有32根地址总线,每根线有两种状态,表示0或1(电脉冲有无),一根线表示2种含义,2根线表示4种含义,32根地址线表示2^32种含义,每一种含义代表一个地址

地址信息被下达给内存,在内存上就可以找到该地址对应的数据,将数据通过数据总线传入CPU内寄存器

指针变量和地址

取地址操作符&

在C语言中创建变量其实就是向内存申请空间

&a取出的是a所占4个字节中地址较小的字节地址

指针--地址

指针变量--存放地址的变量

指针变量和解引用操作符
指针变量

通过取地址操作符(&)拿到的地址是一个数值,这个数值需要存放在指针变量

指针变量也是一种变量,这种变量是用来存放地址的,存放在指针变量中的值都会理解为地址

指针变量中存储的是一个地址,指向同类型的一块内存空间

如何拆解指针类型?
int a=10;
int* pa=&a;

pa左边写的是int*,*说明pa是指针变量,前面的int说明pa指向的是整型(int)类型的对象

int main()
{int a = 10;int* p = &a;*p = 0;//*解引用操作符(间接访问操作符)//*&a=0;printf("%d", a);//0return 0;
}
指针变量的大小

32位机器假设有32根地址总线,每根地址线出来的电信号转换成数字信号后是1或0,把32根地址线产生的2进制序列当作一个地址,那么一个地址就是32个bit位,需要4个字节才能存储

如果指针变量是用来存放地址的,那么指针变量的大小就得是4个字节的空间才可以

同理,64位机器,假设有64根地址线,一个地址就是64个二进制位组成的二进制序列,存储起来就需要8个字节的空间,指针变量的大小就是8个字节

指针变量--存放地址的

一个字节对应一个地址

指针用来存放地址,地址是唯一标示一块地址空间的

总结:

1.32位平台下地址是32个bit位,指针变量大小是4个字节

2.64位平台下地址是64个bit位,指针变量大小是8个字节

3.注意指针变量的大小和类型是无关的,只要是指针类型的变量,在相同的平台下大小都是相同的

指针变量类型的意义

指针的解引用

指针类型决定了指针进行解引用操作时,能够访问空间的大小(对指针解引用的时候有多大的权限--一次能操作几个字节)

int* p;*p能够访问4个字节

char* p;*p能够访问1个字节

doble* p;*p能够访问8个字节

指针+整数

指针类型决定了,指针走一步能走多远(指针的步长,单位是字节)

void*指针

void* 无具体类型的指针(或者泛型指针),这种类型的指针可以用来接受任意类型的地址,但也有局限性,void* 类型的指针不能进行指针的+-整数和解引用的运算

void* 类型的指针有什么用呢?

一般void* 类型的指针是使用在函数参数的部分,用来接收不同类型数据的地址,这样的设计可以实现泛型编程的效果,使得一个函数来处理多种类型的数据

const修饰指针

const修饰变量

变量不再被改变,变量有了常属性,本质仍是变量,常变量(在C++中,const修饰的变量就是常量)

如果要修改变量,就要通过指针来修改,这样就打破了const的限制,是不合理的,所以应该让变量的地址也不能被修改

const修饰指针变量

const修饰指针变量,可以放在*的左边,也可以放在*的右边,意义是不一样的

int main()
{int a = 10;int b = 20;int* const p = &a;//const在*右边//p=&b;//err*p = 100;printf("%d", a);return 0;
}
  • const限制的是指针变量本身,指针变量不能再指向其他变量了,但是可以通过指针变量,修改指针变量指向的内容
int main()
{int a = 10;int b = 20;int const* p = &a;//const在*右边printf("p=%d\n", p);p=&b;//*p = 100;//errprintf("p=%d", p);return 0;
}

运行结果:

  • const放在*左边,限制的是:指针指向的内容,不能通过指针来修改指向的内容,但是可以修改指针变量本身的值(修改指针变量的指向)

*两边可以同时加const

指针运算

指针的作用就是访问内存的

指针的基本运算有三种:

  • 指针+-整数
  • 指针-指针
  • 指针的关系运算
指针+-整数
int main()
{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);p++;*///等同于下面一句:printf("%d ", *(p + i));//p没有变化}return 0;
}
指针-指针

指针-指针的绝对值是:两个指针之间的元素个数;指针+指针无意义

计算的前提是:两个指针指向了同一块空间

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int my_strlen(char* str)
{char* start = str;while (*str != '\0'){str++;}return str - start;//指针-指针
}
int main()
{//strlen-求字符串长度,‘\0’之前的字符个数char arr[] = "abcdef";int len = my_strlen(arr);printf("%d", len);return 0;
}
指针的关系运算
int main()
{int arr[] = { 1,2,3,4,5,6,7,8,9,10 };int sz = sizeof(arr) / sizeof(arr[0]);int* p = arr;//&arr[0]while (p < arr + sz){printf("%d ", *p);p++;}return 0;
}

assert断言

assert.h头文件定义了宏assert(),用于在运行时确保程序符合指定条件,如果不符合,就报错终止运行,这个宏常常被称为“断言”

assert(p != NULL);

上面代码在程序运行到这一行语句时,验证变量是否等于NULL,如果不等于NULL,程序继续运行,否则就会终止运行,并且会给出报错信息提示

assert()宏接受一个表达式作为参数,如果该表达式为真(返回值非零),assert()不会产生任何作用,程序继续运行。如果该表达式为假(返回值为0),assert()就会报错,在标准错误流stderr中写入一条错误信息,显示没有通过的表达式,以及包含这个表达式的文件名和行号

使用assert()有几个好处:

assert()的使用对程序员是非常友好的,它不仅能自动标识文件和出问题的行号,还有一种无需更改代码就能开启或关闭assert()的机制。如果已经确认程序没有问题,不需要再做断言,就在#include <assert.h>语句的前面,定义一个宏NDEBUG

#define NDEBUG
#include <assert.h>

然后,重新编译程序,编译器就会禁用文件中所有的assert()语句,如果程序又出现问题,可以移除这条#define NDEBUG指令(或者把它注释掉),再次编译,这样就重新启用了assert()语句

assert的缺点是:因为引入了额外的检查,增加了程序的运行时间

一般我们可以在Debug中使用,在Release版本中选择禁用assert就行,在VS这样的集成开发环境中,在Release版本中,直接优化掉了,这样在Debug版本写有利于程序员排查问题,在Release版本不影响用户使用时程序的效率

指针的使用和传址调用

strlen的模拟实现
size_t my_strlen(const char* str)//size_t无符号的整型
{size_t count = 0;assert(str != NULL);while (*str != '\0'){count++;str++;}return count;
}
int main()
{char arr[] = "abcdef";size_t len = my_strlen(arr);printf("%d", len);return 0;
}
传值调用和传址调用

下面看一个典型代码,曾几何时,我被它坑过:

void Swap(int x, int y)
{int temp = x;x = y;y = temp;
}
int main()
{int a = 10;int b = 20;printf("交换前:a=%d,b=%d\n", a, b);Swap(a, b);//传值调用printf("交换后:a=%d,b=%d", a, b);return 0;
}

当实参传递给形参的时候,形参是实参的一份临时拷贝,对形参的修改不会影响实参

如果要交换两个数的值,参考以下代码:

void Swap(int* pa, int* pb)
{int temp = *pa;*pa = *pb;*pb = temp;
}
int main(){int a = 10;int b = 20;printf("交换前:a=%d,b=%d\n", a, b);Swap(&a, &b);//传址调用printf("交换后:a=%d,b=%d", a, b);return 0;
}

http://www.lryc.cn/news/292959.html

相关文章:

  • c++:敲桌子
  • Linux中判断文件系统的方法
  • 聊聊ClickHouse MergeTree引擎的固定/自适应索引粒度
  • 20240202在WIN10下使用whisper.cpp
  • 【Linux】基本指令(上)
  • 【DB2】—— 一次关于db2 sqlcode -420 22018的记录
  • 账簿和明细账
  • C# Onnx GroundingDINO 开放世界目标检测
  • PyCharm / DataSpell 导入WSL2 解析器,实现GPU加速
  • Android矩阵Matrix裁切setRectToRect拉伸Bitmap替代Bitmap.createScaledBitmap缩放,Kotlin
  • TensorFlow2实战-系列教程11:RNN文本分类3
  • 故障诊断 | 一文解决,RF随机森林的故障诊断(Matlab)
  • DAO设计模式
  • 【Midjourney】新手指南:参数设置
  • 阿里云a10GPU,centos7,cuda11.2环境配置
  • RTSP/Onvif协议视频平台EasyNVR激活码授权异常该如何解决
  • React16源码: React中event事件对象的创建过程源码实现
  • 深度学习(12)--Mnist分类任务
  • AI工具【OCR 01】Java可使用的OCR工具Tess4J使用举例(身份证信息识别核心代码及信息提取方法分享)
  • 【MySQL复制】半同步复制
  • PHP面试知识点--echo、print、print_r、var_dump区别
  • centos 7 部署若依前后端分离项目
  • RFID手持终端_智能pda手持终端设备定制方案
  • 51单片机学习——矩阵按键
  • 重写Sylar基于协程的服务器(1、日志模块的架构)
  • ElementUI Form:Radio 单选框
  • react-activation实现缓存,且部分页面刷新缓存,清除缓存
  • idea 中 tomcat 乱码问题修复
  • Modbus协议学习第七篇之libmodbus库API介绍(modbus_write_bits等)
  • 第九节HarmonyOS 常用基础组件13-TimePicker