C语言 指针

C语言学习!

目录

文章目录

前言

一、指针是什么?

二、指针变量的大小

三、指针和指针类型

四、指针和函数

五、野指针

5.1野指针成因

5.2 如何规避野指针

六、指针运算

6.1 指针+- 整数

6.2 指针-指针

6.3 指针的关系运算

总结


前言

指针理解的2个要点:

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


一、指针是什么?

1.1 指针变量

        整个内存空间是切割成一个个1byte(字节)大小的内存单元并编号来管理的,把内存单元的编号称为地址,地址也叫指针。存放指针(地址)的变量就是指针变量,通过指针变量里存放的地址,可以找到对应的内存单元。

        就好比一个人国家划分成许多省,每个省都有邮政编码,根据邮政编码可以找到对应地址的省,而指针变量就像是记录邮政编码的小本子。

  • 内存单元有编号,而这个编号其实就是地址,地址被称为指针。
  • 存放指针(地址)的变量就是指针变量。

  • 把内存单元的编号就称为指针。
  • 指针其实就是地址,地址就是编号。
  • 指针就是内存单元的编号。

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

代码示例:

#include <stdio.h>

int main()
{
	int a = 10;//a是整型变量,占用四个字节的内存空间,存储10
	int* pa = &a;//pa是一个指针变量,用来存放地址的
	
	return 0;
}

调试窗口:

        a变量里存放着10,a的地址是0x008ffd80,&a的意思是取得a的地址,运算符&的功能就是取得对象的地址。pa变量中存放的是a的地址0x008ffd80,而pa前面的int表示p指向的对象是int类型的,*说明p是指针变量。

        这里注意:int 整形变量占4个字节的内存,&a取出的是a所占4个字节的第一个字节的地址存放在pa变量中,pa指针变量中存的就是a的首地址。

总结: 指针变量,用来存放地址的变量。(存放在指针中的值都被当成地址处理)


1.2 取地址运算符和间接寻址运算符

        C语言为指针的使用提供了一对运算符。为了找到变量的地址,可以使用 & 取地址运算符。如果 a 是变量,那么 &a 就是 a 在内存中的地址。为了获得对指针所指向对象的访问,可以使用 * 间接寻址运算符。如果 p 是指针,那么 *p 表示 p 当前指向的对象。

        只要 p 指向 a ,*p 就是 a 的别名。

代码示例:

#include <stdio.h>

int main()
{
	int a = 0;
	int* p = &a;
	*p = 10;

	return 0;
}

运行结果:

        *为解引用操作符,*p意思就是通过p中存放的地址,找到p所指向的对象(此时*p就是a)

&a:找到a的地址;*p找回对象a。


二、指针变量的大小

指针的大小是由硬件机器决定的:

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

        在64位的机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地 址。

总结:

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

代码示例:

#include <stdio.h>
int main()
{
	char* pc = NULL;
	short* ps = NULL;
	int* pi = NULL;
	double* pd = NULL;

	printf("%zu\n", sizeof(pc));
	printf("%zu\n", sizeof(ps));
	printf("%zu\n", sizeof(pi));
	printf("%zu\n", sizeof(pd));

	return 0;
}

运行结果:

4
4
4
4

        不管什么类型的指针都是在创建指针变量,指针变量是用来存放地址的。指针变量的大小取决于一个地址存放的时候需要多大空间。

三、指针和指针类型

        指针的定义方式是: type + *

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

代码示例1:

#include <stdio.h>

int main()
{
	int a = 0x11223344;
	int* pa = &a;
	*pa = 0;

	return 0;
}

调试窗口:

代码示例2:

#include <stdio.h>

int main()
{
	int a = 0x11223344;
	char* pa = &a;
	*pa = 0;

	return 0;
}

调试窗口:

结论:

        指针类型决定了指针在被解引用的时候访问几个字节。

  • int*的指针,解引用访问4个字节
  • char*的指针,解引用访问1个字节

代码示例:

#include <stdio.h>

int main()
{
	int a = 0x11223344;
	int* pa = &a;
	char* pc = &a;

	printf("pa=%p\n", pa);
	printf("pa+1=%p\n", pa+1);

	printf("pc=%p\n", pa);
	printf("pc+1=%p\n", pc+1);


	return 0;
}

运行结果:

pa=008FF774
pa+1=008FF778
pc=008FF774
pc+1=008FF775

结论:

  • 指针类型决定了指针+1或-1操作的时候,跳过几个字节。
  • 指针的类型决定了指针的步长。

代码示例1:

#include <stdio.h>
int main()
{
	int a = 0;
	int* pi = &a;
	float* pf = &a;
	*pi = 100;

	return 0;
}

调试窗口:

代码示例2:

#include <stdio.h>
int main()
{
	int a = 0;
	int* pi = &a;
	float* pf = &a;
	*pf = 100.0;

	return 0;
}

调试窗口:

int* 和 float* 不能通用

  • pi 解引用访问4个字节,pi+1 也是跳过4个字节。
  • pf 解引用访问4个字节,pf+1 也是跳过4个字节。

        但整型数字和浮点型小数在内存中的存储方式不同,所以在监视内存中的结果也不同,也论证 int* 和 float* 不能通用。

四、指针和函数

        指针的一个重要作用就是作为函数参数使用。

代码示例:

#include <stdio.h>

void text(int* y)
{
	*y = 20;
}
int main()
{
	int a = 0;
	text(&a);
	printf("a=%d", a);
	return 0;
}

运行结果:

a=20

        通过函数调用表达式 text(&a) ,调用函数 text 时, text 函数中形参 y 被声明为指向 int 型变量的指针变量。函数被调用时,将 &a 复制到 y 中,指针 y 便指向了a。

        由于在指针前加上指针运算符 * ,就可以显示该指针指向的对象。因此 *y 时 a 的别名。对 *y 赋值,也就是对 a 赋值,所以即使从 text 函数返回 main 函数,a 中保存的依然是180。

        若要在函数中修改变量的值,就需要传入指向该变量的指针,即告诉程序:传入的是指针,请对该指针指向的对象进行处理。

        只要在被调用的函数里的指针前写上指针运算符 * ,就能间接地处理该指针指向的对象。这也是 * 运算符又被称为间接访问运算符的原因。另外,通过在指针前写上指针运算符 * 来访问该指针指向的对象,称为解引用。

        这里可以对照函数的传址调用加深理解 C语言 函数-CSDN博客


计算和与乘积

代码示例:

#include <stdio.h>

void sum_mul(int x, int y, int* Sum, int* Mul)
{
	*Sum = x + y;
	*Mul = x * y;
}
int main()
{
	int a = 3;
	int b = 6;
	int sum = 0;
	int mul = 0;
	sum_mul(a, b, &sum, &mul);
	printf("sum=%d\n", sum);
	printf("mul=%d\n", mul);
	return 0;
}

运行结果:

sum=9
mul=18

        调用函数 sum_mul 时,会将 sum 和 mul 的地址复制给形参 Sum 和 Mul 。因此 *Sum 就是 sum 的别名,*Mul 就是 mul 的别名。在函数体中,将求得的和赋值给 *Sum,将求得的乘积赋值给 *Mul 这就相当于给 sum 和 mul 进行赋值,因此从 sum_mul 函数返回到 main 函数之后,和与乘积也分别存储在 sum 和 mul 中了。


五、野指针

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

5.1 野指针成因

1. 指针未初始化

代码示例1:

#include <stdio.h>

int main()
{
	int* p;
	*p = 10;
	return 0;
}
  • p没有初始化,就意味着没有明确指向。
  • 一个局部变量不初始化时,放的是随机值:0xcccccccc
  • *p = 10; 这里非法访问内存了,这里的p就是野指针。

2. 指针越界访问

代码示例2:

#include <stdio.h>

int main()
{
	int arr[10] = { 0 };
	int* p = &arr;
	int i = 0;
	for (i = 0; i <= 10; i++)
	{
		*p = i;
		p++;
	}

	return 0;
}

        当指针指向的范围超出数组arr的范围时,p就是野指针。


3. 指针指向的空间释放

代码示例3:

#include <stdio.h>

int* text()
{
	int a = 10;
	return &a;
}
int main()
{
	int* p = text();
	return 0;
}

        调用text函数,返回变量a的地址给指针p。

        a是局部变量,他所占的空间一旦出了text函数就销毁了,就是将a所占的空间还给了操作系统。所以返回地址的同时,a所占空间销毁。

        p在存a的地址的时候,地址所对应的空间已经销毁。p虽然可以通过地址找到对应空间,但是p不能访问和使用这块空间。此时p就是野指针。


5.2 如何规避野指针

1. 指针初始化

2. 小心指针越界

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

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

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

代码示例:

#include <stdio.h>

int main()
{
	int* p = NULL;
	int a = 10;
	p = &a;
	if (p != NULL)
	{
		*p = 100;
	}
	return 0;
}

六、指针运算

6.1 指针+- 整数

        如果 p 指向数组元素 arr[ i ] ,那么 p+j 指向 arr[ i+j ]。

        如果 p 指向数组元素 arr[ i ] ,那么 p-j 指向 arr[ i-j ]。

代码示例1:

#include <stdio.h>

int main()
{
	int arr[10] = { 0 };
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	int* p = arr;
	for (i = 0; i < sz; i++)
	{
		*p = 1;
		p++;
	}

	return 0;
}

调试窗口:


        

代码示例2:

#include <stdio.h>

int main()
{
	int arr[10] = { 0 };
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	int* p = arr;
	for (i = 0; i < sz; i++)
	{
		*(p + i) = 1;	
	}
	return 0;
}

调试窗口:


6.2 指针-指针

        当两个指针相减时,结果为指针之间的距离,也就是两指针之间数组元素的个数。因此,若 p 指向 arr[ i ] 且 q 指向 arr[ j ],那么 p - q 就等于 i - j 。

代码示例:

#include <stdio.h>

int main()
{
	int arr[10] = { 0 };
	printf("%d\n", &arr[9]-&arr[0]);

	return 0;
}

运行结果:

9

  • 指针-指针的绝对值得到的是指针和指针之间元素个数。
  • 不是所有指针都能相减,指向同一块空间的2个指针才能相减。

 代码示例:

#include <stdio.h>

int my_strlen(char* str)
{
	char* start = str;
	while (*str != '\0')
	{
		str++;
	}
	return (str - start);
}
int main()
{
	int len = my_strlen("abcdefg");
	printf("%d\n", len);
	return 0;
}

运行结果:

7

6.3 指针的关系运算

        可以用关系运算符( < 、<= 、> 、>= )和判等运算符(== 、!=)进行指针比较。

代码示例:

#include <stdio.h>

#define N_VALUES 10
int main()
{
	int values[N_VALUES] = { 0 };
	int* vp = NULL;
	for (vp = &values[N_VALUES]; vp > &values[0];)
	{
		*--vp = 1;
	}

	return 0;
}


总结

        以上就是今天要讲的内容,本文仅仅简单介绍了指针。

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

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

相关文章

(2023|CVPR,Corgi,偏移扩散,参数高斯分布,弥合差距)用于文本到图像生成的偏移扩散

Shifted Diffusion for Text-to-image Generation 公众&#xff1a;EDPJ&#xff08;添加 VX&#xff1a;CV_EDPJ 或直接进 Q 交流群&#xff1a;922230617 获取资料&#xff09; 目录 0. 摘要 1. 简介 2. 方法 2.1 偏移扩散 3. 实验 3.1 无监督文本到图像生成 3.2 无…

IDEA Maven Helper插件 解决jar冲突

Jar包冲突报错 程序抛出java.lang.ClassNotFoundException异常&#xff1b; 程序抛出java.lang.NoSuchMethodError异常&#xff1b; 程序抛出java.lang.NoClassDefFoundError异常&#xff1b; 程序抛出java.lang.LinkageError异常等&#xff1b;Maven Jar包管理机制 在Maven项…

设计模式--工厂方法模式

实验3&#xff1a;工厂方法模式 本次实验属于模仿型实验&#xff0c;通过本次实验学生将掌握以下内容&#xff1a; 1、理解工厂方法模式的动机&#xff0c;掌握该模式的结构&#xff1b; 2、能够利用工厂方法模式解决实际问题。 [实验任务]&#xff1a;加密算法 目前常用…

数据库管理-第127期 LSM Tree(202301225)

数据库管理-第127期 LSM Tree&#xff08;202301225&#xff09; 说起分布式数据库&#xff0c;绕不开的一个话题就是LSM Tree&#xff0c;全称为log-structured merge-tree&#xff0c;回到吕海波老师授权过的那句话“没搞过Oracle的&#xff0c;但又是数据库圈里的人&#x…

《我在北京送快递》平凡隽永的时刻,对人生更具意义

《我在北京送快递》平凡隽永的时刻&#xff0c;对人生更具意义 胡安焉 文章目录 《我在北京送快递》平凡隽永的时刻&#xff0c;对人生更具意义[toc]摘录感悟 摘录 转“没有期限的承诺无疑就是委婉的拒绝” 转书友&#xff1a;亨利福特说&#xff0c;我聘的是一双手&#xff0…

基于 FFmpeg 的跨平台视频播放器简明教程(十二):Android SurfaceView 显示图片和播放视频

系列文章目录 基于 FFmpeg 的跨平台视频播放器简明教程&#xff08;一&#xff09;&#xff1a;FFMPEG Conan 环境集成基于 FFmpeg 的跨平台视频播放器简明教程&#xff08;二&#xff09;&#xff1a;基础知识和解封装&#xff08;demux&#xff09;基于 FFmpeg 的跨平台视频…

LeetCode-回文链表(234)

题目描述&#xff1a; 给你一个单链表的头节点 head &#xff0c;请你判断该链表是否为回文链表。如果是&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 因为这一题是受到876题求链表中间节点的启发&#xff0c;所以在这里也加一下。 876.链表的中间结点…

格密码:傅里叶矩阵

目录 一. 铺垫性介绍 1.1 傅里叶级数 1.2 傅里叶矩阵的来源 二. 格基与傅里叶矩阵 2.1 傅里叶矩阵详细解释 2.2 格基与傅里叶矩阵 写在前面&#xff1a;有关傅里叶变换的解释太多了&#xff0c;这篇博客主要总结傅里叶矩阵在格密码中的运用。对于有一定傅里叶变换基础的同…

python 解决手机拍的书籍图片发灰的问题

老师给发的作业经常是手机拍的&#xff0c;而不是扫描&#xff0c;背景发灰&#xff0c;如果二次打印就没有看了&#xff0c;象这样&#xff1a; 如果使用photoshop 处理&#xff0c;有些地方还是扣不干净&#xff0c;不如python 做的好&#xff0c;处理后如下&#xff1a; 具体…

一个基于多接口的业务自动化测试框架!

这是一个成熟的框架&#xff0c;不是要让别人当小白鼠&#xff0c;它已经先后在两家公司的5条业务线进行了推广应用&#xff0c;用例条数到了几千条以上&#xff0c;并且从2018年开始每天都在CI/CD流程中被调用执行。 已有那么多接口测试框架&#xff0c;为什么重复造轮子&…

详解Java反射机制reflect(一学就会,通俗易懂)

1.定义 #2. 获取Class对象的三种方式 sout(c1)结果为class com.itheima.d2_reflect.TestClass 获取到了Class对象就相当于获取到了该类 2.获取类的构造器 3.获取全部构造器对象 2.根据参数类型获取构造器对象 类型后必须加.class 3.构造器对象调用构造器方法 4.暴力访问 4.获…

11-GraalVM元原生时代的Java虚拟机

文章目录 GraalVM诞生的背景Java在微服务/云原生时代的困境事实矛盾 问题根源Java离不开虚拟机 解决方案革命派保守派 GraalVM入门GraalVM特征GraalVM下载和安装GraalVM下载win10安装及配置linux安装及配置 GraalVM初体验(Linux)多语言开发(了解即可、官网有Demo)GraalCompiler…

【Gitlab】CICD流水线自动化部署教程

第一步&#xff0c;准备 GitLab 仓库 这个不用多说&#xff0c;得先保证你的项目已经托管在一个 GitLab 仓库中。 第二步&#xff0c;定义 .gitlab-ci.yml 文件 在你的项目根目录中创建一个 .gitlab-ci.yml 文件。这个文件将定义所有 CI/CD 的工作流程&#xff0c;包括构建、测…

连锁餐饮数字化:一体化运营管控平台

内容来自演讲&#xff1a;刘腾飞 | 上海奥谱创网络科技有限公司 | CEO 摘要 本文介绍了企业级管理系统的需求和现状&#xff0c;以及如何通过数据指标为依据的改善循环来优化企业的运营。文章还提出了场景驱动、迭代上线的方法&#xff0c;并介绍了两个平台、三个统一的解决方…

RK3568平台开发系列讲解(Linux系统篇)Linux 热拔插机制 mdev的使能

🚀返回专栏总目录 文章目录 一、什么是热插拔二、热插拔的机制三、mdev的开启沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇将介绍 Linux 热拔插。 一、什么是热插拔 热插拔是指在设备运行的情况下,能够安全地插入或拔出硬件设备,而无需关闭或重启系统。这意…

自动驾驶中的“雷达”

自动驾驶中有好几种雷达&#xff0c;新手可能会蒙蔽&#xff0c;这里统一介绍一下它们。 首先&#xff0c;所有雷达的原理都是发射波&#xff0c;接收回波&#xff0c;并通过发射和接收的时间差以及波的速度计算距离。只不过发射的波不同&#xff0c;功能也不同。 激光雷达 …

kubelet源码学习(二):kubelet创建Pod流程

本文基于Kubernetes v1.22.4版本进行源码学习 4、kubelet创建Pod流程 syncLoop()的主要逻辑是在syncLoopIteration()方法中实现&#xff0c;Pod创建相关代码只需要看处理configCh部分的代码 // pkg/kubelet/kubelet.go // 该方法会监听多个channel,当发现任何一个channel有数…

Jenkins的特殊操作定时自动执行任务以及测试报告调优

java -Dhudson.model.DirectoryBrowserSupport.CSP -jar Jenkins.war 测试报告 不美丽 执行上面的代码 重启jenkins 就好了

基于SpringBoot+Vue实现的电影院售票系统

文章目录 项目介绍影院管理影片管理影厅管理订单管理用户管理角色权限管理 技术选型成果展示前台系统后台管理系统 账号及其他说明 项目介绍 基于SpringBootVue实现的电影院售票系统整体设计了用户、管理员两个角色。 用户登录系统可进行电影查看、分类查看、影片搜索、选择影…

如何解决HTTP 404错误,这里给出详细解决办法

404错误是一个HTTP状态代码,这意味着你试图在网站上访问的页面在他们的服务器上找不到。 需要明确的是,该错误表示虽然服务器本身是可访问的,但显示该错误的特定页面是不可访问的。 个别网站经常自定义这个错误信息。所以,请记住,错误可能会以任何可以想象的方式出现,这…
最新文章