C语言 —— 此去经年梦浪荡魂音 - 深入理解指针(卷一)

news2025/3/14 9:40:26

目录

1. 内存和地址

2. 指针变量和地址

2.1 取地址操作符(&)

2.2 指针变量

2.3 解引用操作符 (*)

3. 指针的解引用

3.1 指针 + - 整数

3.2 void* 指针

4. const修饰指针

4.1 const修饰变量

4.2 const修饰指针变量

5. 指针运算

5.1 指针 ± 整数

5.2指针 - 指针

5.3 指针的关系运算

6. 野指针

6.1 野指针成因

6.2 如何规避野指针

7. 指针的使用和传址调用

7.1 strlen的模拟实现

7.2 传值调用和传址调用


1. 内存和地址

什么是内存,我们先举个例子:假设有⼀栋宿舍楼,把你放在楼⾥,楼上有100个房间,但是房间没有编号,你的⼀个朋友来找你玩,如果想找到你,就得挨个房⼦去找,这样效率很低,但是我们如果根据楼层和楼层的房间的情况,给每个房间编上号

  

一楼:101 102 103...
二楼:201 202 203...

以此类推...

有了房间号,如果你的朋友得到房间号,就可以快速的找房间找到你

  

将这个例子对应我们的计算机里面就是:

  

CPU在处理数据的时候,需要的数据是在内存中读取的,处理后的数据也会放回内存中,那我们买电脑的时候,电脑上内存是8GB/16GB/32GB等,这些内存空间是把内存划分为⼀个个的内存单元,每个内存单元的大小取1个字节(1个字节=8 个比特位)

    

每个内存单元就相当于每间酒店房间,每个房间能住 8 个比特位,房间(内存单元)都有一个门牌编号(地址),有了门牌号,就能快速找到相应的房间,即CPU能通过地址快速找到内存空间,在C语言中,给地址起了个名字叫指针

所以我们可以理解为:

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

  

1byte = 8bit
1KB = 1024byte
1MB = 1024KB
1GB = 1024MB
1TB = 1024GB
1PB = 1024TB


2. 指针变量和地址


2.1 取地址操作符(&)

创建变量其实就是向内存申请空间,调试下面代码:

#include <stdio.h>

int main()
{
	int a = 10;
	&a;//取出a的地址

	printf("%p\n", &a);

	return 0;
}

&a取出的是a所占4个字节中地址较⼩的字节的地址也就是第一个地址

  

虽然整型变量占用4个字节,我们只要知道了第⼀个字节地址,顺藤摸⽠访问到4个字节的数据也是可行的


2.2 指针变量

我们通过取地址操作符(&)拿到的地址是⼀个数值,这个数值有时候也是需要存储起来,⽅便后期再使用的,那我们就可以把这样的地址值存放在指针变量

int main()
{
	int a = 10;
	int* pa = &a;//取出a的地址并存储到指针变量pa中

	return 0;
}

指针变量就是用来存放地址的,存放在指针变量中的值都可以当成为地址

2.3 解引用操作符 (*)

我们把地址存储在指针变量后要如何将存放在里面的东西取出使用呢?在知道地址的前提下,可以通过解引用操作符找到指针指向的对象

int main()
{
	int a = 100;
	int* p = &a;

	*p = 0;

	return 0;
}

上面这段代码就是p 通过解引用(*)找到 a 并将其值改成 0 ,就像是通过门牌号找到特定酒店房间里的特定物品,并将其替换


3. 指针的解引用
 

#include <stdio.h>

int main()
{
	int n = 0x11223344;
	int* pi = &n;

	*pi = 0;

	return 0;
}

调试一下这段代码,代码会将n的4个字节全部改为0,如果把指针类型改成 char ,那么代码只是将n的第⼀个字节改为0

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

	*pc = 0;

	return 0;
}

结论:指针的类型决定了,对指针解引⽤的时候有多⼤的权限(⼀次能操作⼏个字节)

   
⽐如: char* 的指针解引⽤就只能访问⼀个字节,⽽ int* 的指针的解引⽤就能访问四个字 

3.1 指针 + - 整数

#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;
}

char* 类型的指针变量+1跳过1个字节, int* 类型的指针变量+1跳过了4个字节

   
也就是说:指针类型决定了指针加减整数时一步走多大距离

3.2 void* 指针

在指针类型中有⼀种特殊的类型是 void* 类型的,可以理解为⽆具体类型的指针(或者叫泛型指针),这种类型的指针可以⽤来接受任意类型地址。但是也有局限性, void* 类型的指针不能直接进⾏指针的+-整数和解引⽤的运算

#include <stdio.h>

int main()
{
	int a = 10;
	void* pa = &a;
	void* pc = &a;

	*pa = 10;
	*pc = 0;

	return 0;
}

这⾥我们可以看到, void* 类型的指针可以接收不同类型的地址,但是无法直接进⾏指针运算

   
那么 void* 类型的指针到底有什么⽤呢?

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


4. const修饰指针


4.1 const修饰变量

变量是可以修改的,如果把变量的地址交给⼀个指针变量,通过指针变量的也可以修改这个变量

   

当我们希望⼀个变量加上⼀些限制,不能被修改那么这个时候我们就可以加上一个const

#include <stdio.h>

int main()
{
	int m = 0;
	m = 20;//m是可以修改的

	const int n = 0;

	n = 20;//n是不能被修改的

	return 0;
}

上述代码中n是不能被修改的,其实n本质是变量,只不过被const修饰后,在语法上加了限制,只要我们在代码中对n就⾏修改,就不符合语法规则,就报错,致使没法直接修改n

但是如果我们绕过n,使⽤n的地址,去修改n就能做到了

#include <stdio.h>

int main()
{
	const int n = 0;

	printf("n = %d\n", n);

	int* p = &n;
	*p = 20;

	printf("n = %d\n", n);

	return 0;
}

4.2 const修饰指针变量

当const修饰指针变量的时候:

   
1. const如果放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变,但是指针变量本⾝的内容可变

int const * const p = &a;

 当const放在*的左边时,限制的就是p所指向的内容,也就是&a

2. const如果放在*的右边,修饰的是指针变量本⾝,保证了指针变量的内容不能修改,但是指针指向的内容,可以通过指针改变

当const放在*的右边时,限制的就是p本身


5. 指针运算

  

5.1 指针 ± 整数

因为数组在内存中是连续存放的,只要知道第⼀个元素的地址,顺藤摸⽠就能找到后⾯的所有元素

#include <stdio.h>

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 + i));//p+i 这⾥就是指针+整数
	}

	return 0;
}

5.2指针 - 指针

代替 strlen 函数(计算字符或字符串长度),实现一个自定义的函数 my_strlen 来计算输入字符串的长度

#include <stdio.h>

int my_strlen(char* s)
{
	char* p = s;

	while (*p != '\0')
		p++;

	return p - s;
}

int main()
{
	printf("%d\n", my_strlen("abc"));

	return 0;
}

5.3 指针的关系运算

//指针的关系运算
#include <stdio.h>

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]);

	while (p < arr + sz) //指针的⼤⼩⽐较
	{
		printf("%d ", *p);
		p++;
	}

	return 0;
}

指针的关系运算,实际上就是:


6. 野指针

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

6.1 野指针成因


1. 指针未初始化

#include <stdio.h>

int main()
{
	int* p;//局部变量指针未初始化,默认为随机值
	*p = 20;

	return 0;
}

2. 指针越界访问

#include <stdio.h>

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

	for (i = 0; i <= 11; i++)
	{
		//当指针指向的范围超出数组arr的范围时,p就是野指针
		*(p++) = i;
	}

	return 0;
}

当循环执行到 i = 10 及之后时,指针 p 已经超出了数组 arr 的范围指向了数组 arr 所占用内存空间之外的未知区域,此时 p 就变成了野指针

3. 指针指向的空间释放

6.2 如何规避野指针

1.对指针变量都进行初始化操作

   
2.注意数组等变量的范围,小心指针越界

   
3.指针不使用时,及时置之为NULL空指针

    
4.不要返回局部变量的地址

 


7. 指针的使用和传址调用

   

7.1 strlen的模拟实现

库函数strlen的功能是求字符串⻓度,统计的是字符串中 \0 之前的字符的个数

size_t strlen ( const char * str );

参数str接收⼀个字符串的起始地址,然后开始统计字符串中 \0 之前的字符个数,最终返回⻓度

    
如果要模拟实现只要从起始地址开始向后逐个字符的遍历,只要不是 \0 字符,计数器就+1,这样直到 \0 就停⽌ 

strlen链接:

  

strlen - C++ Referencehttps://legacy.cplusplus.com/reference/cstring/strlen/?kw=strlen

int my_strlen(const char* str)
{
	int count = 0;

	assert(str);

	while (*str)
	{
		count++;
		str++;
	}

	return count;
}

int main()
{
	int len = my_strlen("abcdef");
	printf("%d\n", len);

	return 0;
}

7.2 传值调用和传址调用

先写一个普通的代码: 

#include <stdio.h>

void Swap1(int x, int y)
{
	int tmp = x;
	x = y;
	y = tmp;
}

int main()
{
	int a = 0;
	int b = 0;

	scanf("%d %d", &a, &b);
	printf("交换前:a=%d b=%d\n", a, b);

	Swap1(a, b);

	printf("交换后:a=%d b=%d\n", a, b);

	return 0;
}

我们发现其实没产⽣交换的效果,调试⼀下

Swap1函数在使⽤的时候,是把变量本⾝直接传递给了函数,这种调⽤函数的⽅式我们之前在函数的时候就知道了,值是会出了作用域就会自动销毁,这种叫传值调用

   

实参传递给形参的时候,形参会单独创建⼀份临时空间来接收实参,对形参的修改不影响实

我们使用指针的方法:

void Swap2(int* px, int* py)
{
	int tmp = 0;

	tmp = *px;
	*px = *py;
	*py = tmp;
}

int main()
{
	int a = 0;
	int b = 0;

	scanf("%d %d", &a, &b);
	printf("交换前:a=%d b=%d\n", a, b);

	Swap1(&a, &b);
	printf("交换后:a=%d b=%d\n", a, b);

	return 0;
}

我们可以看到实现成Swap2的⽅式,顺利完成了任务,这⾥调⽤Swap2函数的时候是将变量的地址传递给了函数,这种函数调⽤⽅式叫:传址调用

传址调⽤,可以让函数和主调函数之间建⽴真正的联系,在函数内部可以修改主调函数中的变量

    

所以未来函数中只是需要主调函数中的变量值来实现计算,就可以采⽤传值调⽤。如果函数内部要修改主调函数中的变量的值,就需要传址调⽤ 


完结撒花~ 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

计算机毕业设计:留守儿童的可视化界面

留守儿童的可视化界面mysql数据库创建语句留守儿童的可视化界面oracle数据库创建语句留守儿童的可视化界面sqlserver数据库创建语句留守儿童的可视化界面springspringMVChibernate框架对象(javaBean,pojo)设计留守儿童的可视化界面springspringMVCmybatis框架对象(javaBean,poj…

golang算法二叉树对称平衡右视图

100. 相同的树 给你两棵二叉树的根节点 p 和 q &#xff0c;编写一个函数来检验这两棵树是否相同。 如果两个树在结构上相同&#xff0c;并且节点具有相同的值&#xff0c;则认为它们是相同的。 示例 1&#xff1a; 输入&#xff1a;p [1,2,3], q [1,2,3] 输出&#xff1a…

Chatbox通过百炼调用DeepSeek

解决方案链接&#xff1a;评测&#xff5c;零门槛&#xff0c;即刻拥有DeepSeek-R1满血版 方案概览 本方案以 DeepSeek-R1 满血版为例进行演示&#xff0c;通过百炼模型服务进行 DeepSeek 开源模型调用&#xff0c;可以根据实际需求选择其他参数规模的 DeepSeek 模型。百炼平台…

【数据结构】6栈

0 章节 3&#xff0e;1到3&#xff0e;3小节。 认知与理解栈结构&#xff1b; 列举栈的操作特点。 理解并列举栈的应用案例。 重点 栈的特点与实现&#xff1b; 难点 栈的灵活实现与应用 作业或思考题 完成学习测试&#xff12;&#xff0c;&#xff1f; 内容达成以下标准(考核…

PyTorch 入门学习

目录 PyTorch 定义 核心作用 应用场景 Pytorch 基本语法 1. 张量的创建 2. 张量的类型转换 3. 张量数值计算 4. 张量运算函数 5. 张量索引操作 6. 张量形状操作 7. 张量拼接操作 8. 自动微分模块 9. 案例-线性回归案例 PyTorch 定义 PyTorch 是一个基于 Python 深…

mov格式视频如何转换mp4?

mov格式视频如何转换mp4&#xff1f;在日常的视频处理中&#xff0c;经常需要将MOV格式的视频转换为MP4格式&#xff0c;以兼容更多的播放设备和平台。下面给大家分享如何将MOV视频转换为MP4&#xff0c;4款视频格式转换工具分享。 一、牛学长转码大师 牛学长转码大师是一款功…

二进制求和(js实现,LeetCode:67)

这道题我的解决思路是先将a和b的长度保持一致以方便后续按位加减 let lena a.length let lenb b.length if (lena ! lenb) {if (lena > lenb) {for (let i 0; i <lena-lenb; i) {b 0 b}} else {for (let i 0; i < lenb-lena; i) {a 0 a}} } 下一步直接进行按…

【C#】使用DeepSeek帮助评估数据库性能问题,C# 使用定时任务,每隔一分钟移除一次表,再重新创建表,和往新创建的表追加5万多条记录

&#x1f339;欢迎来到《小5讲堂》&#x1f339; &#x1f339;这是《C#》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解。&#x1f339; &#x1f339;温馨提示&#xff1a;博主能力有限&#xff0c;理解水平有限&#xff0c;若有不对之处望指正&#xff01;&#…

【openGauss】物理备份恢复

文章目录 1. gs_backup&#xff08;1&#xff09;备份&#xff08;2&#xff09;恢复&#xff08;3&#xff09;手动恢复的办法 2. gs_basebackup&#xff08;1&#xff09;备份&#xff08;2&#xff09;恢复① 伪造数据目录丢失② 恢复 3. gs_probackup&#xff08;1&#xf…

蓝桥杯备赛-基础练习 day1

1、闰年判断 问题描述 给定一个年份&#xff0c;判断这一年是不是闰年。 当以下情况之一满足时&#xff0c;这一年是闰年:1.年份是4的倍数而不是100的倍数 2&#xff0e;年份是400的倍数。 其他的年份都不是闰年。 输入格式 输入包含一个…

实验四 Python聚类决策树训练与预测 基于神经网络的MNIST手写体识别

一、实验目的 Python聚类决策树训练与预测&#xff1a; 1、掌握决策树的基本原理并理解监督学习的基本思想。 2、掌握Python实现决策树的方法。 基于神经网络的MNIST手写体识别&#xff1a; 1、学习导入和使用Tensorflow。 2、理解学习神经网络的基本原理。 3、学习使用…

【原创】在高性能服务器上,使用受限用户运行Nginx,充当反向代理服务器[未完待续]

起因 在公共高性能服务器上运行OllamaDeepSeek&#xff0c;如果按照默认配置启动Ollama程序&#xff0c;则自己在远程无法连接你启动的Ollama服务。 如果修改配置&#xff0c;则会遇到你的Ollama被他人完全控制的安全风险。 不过&#xff0c;我们可以使用一个方向代理&#…

Spring boot3-WebClient远程调用非阻塞、响应式HTTP客户端

来吧&#xff0c;会用就行具体理论不讨论 1、首先pom.xml引入webflux依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId> </dependency> 别问为什么因为是响应式....…

18 | 实现简洁架构的 Handler 层

提示&#xff1a; 所有体系课见专栏&#xff1a;Go 项目开发极速入门实战课&#xff1b;欢迎加入 云原生 AI 实战 星球&#xff0c;12 高质量体系课、20 高质量实战项目助你在 AI 时代建立技术竞争力&#xff08;聚焦于 Go、云原生、AI Infra&#xff09;&#xff1b;本节课最终…

coding ability 展开第三幕(滑动指针——基础篇)超详细!!!!

文章目录 前言滑动窗口长度最小的子数组思路 无重复字符的最长子串思路 最大连续1的个数思路 将x减到0的最小操作数思路 总结 前言 前面我们已经把双指针的一些习题练习的差不多啦 今天我们来学习新的算法知识——滑动窗口 让我们一起来探索滑动窗口的魅力吧 滑动窗口 滑动窗口…

如何自己做奶茶,从此告别奶茶店

自制大白兔奶茶&#xff0c;奶香与茶香激情碰撞&#xff0c;每一口都是香浓与甜蜜的双重诱惑&#xff0c;好喝到跺脚&#xff01;丝滑口感在舌尖舞动&#xff0c;仿佛味蕾在开派对。 简单几步就能复刻&#xff0c;成本超低&#xff0c;轻松在家享受奶茶自由。 材料:大白兔奶糖&…

宇树人形机器人开源模型

1. 下载源码 https://github.com/unitreerobotics/unitree_ros.git2. 启动Gazebo roslaunch h1_description gazebo.launch3. 仿真效果 H1 GO2 B2 Laikago Z1 4. VMware: vmw_ioctl_command error Invalid argument 这个错误通常出现在虚拟机环境中运行需要OpenGL支持的应用…

【Linux】浅谈冯诺依曼和进程

一、冯诺依曼体系结构 冯诺依曼由 输入设备、输出设备、运算器、控制器、存储器 五部分组成。 冯诺依曼的设计特点 二进制表示 所有数据&#xff08;包括程序指令&#xff09;均以二进制形式存储和运算&#xff0c;简化了硬件逻辑设计&#xff0c;提高了可靠性。 存储程序原理…

linux操作系统实战

第一题 创建根目录结构中的所有的普通文件 [rootlocalhost ~]# cd /[rootlocalhost /]# mkdir /text[rootlocalhost /]# cd /text[rootlocalhost text]# mkdir /text/boot /text/root /text/home /text/bin /text/sbin /text/lib /text/lib64 /text/usr /text/opt /text/etc /…

浅谈时钟启动和Systemlnit函数

时钟是STM32的关键&#xff0c;是整个系统的心脏&#xff0c;时钟如何启动&#xff0c;时钟源如何选择&#xff0c;各个参数如何设置&#xff0c;我们从源码来简单分析一下时钟的启动函数Systemlnit&#xff08;&#xff09;。 Systemlnit函数简介 我们先来看一下源程序的注释…