C语言——深入理解指针(1)

目录

1.内存与地址

1.1 什么是内存

1.2 编址

2. 指针的变量和地址

2.1 取地址(&)

2.2 指针变量

2.3 解引用

2.4 指针变量大小

3. 指针变量类型存在的意义

3.1 不同类型指针的解引用

3.2 指针对整数的运算(+,-)

3.3 void* 指针

4. const 修饰

4.1 const 变量

 4.2 const(指针变量)

5. 指针的运算

 6. 野指针

7. assert断言

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

举例1:strlen 的模拟实现

传址调用例如:写⼀个函数,交换两个整型变量的值


1.内存与地址

1.1 什么是内存

在这之前我们先说一下生活中的一些与内存非常像的例子

生活中有很多高楼,假设楼里有100个房间,你就住在其中一个,你的朋友要来找你,如果房间没有编号,那么就要一间一间去找,这样等你的朋友找到你,大概也天黑了,但是如果每个房间都有自己的编号,如:一楼:101,102.......        二楼:201,202.....以此类推,这样你的朋友就可以快速找到你。

 其实计算机中的内存也是通过这样管理的,我们知道计算机CPU在处理数据的时候,需要的数据是在内存中读取的,用完了在放回内存中。

计算机也是把内存划分为一个个的内存单元,每个内存单元的大小是1字节。

补充:一个比特位可以储存一个2进制的位1或0。

bit- 比特位                    1byte = 8bit
byte- 字节                    1KB = 1024byte
KB                            1MB = 1024KB
MB                            1GB = 1024MB
GB                            1TB = 1024GB
TB                            1PB = 1024TB
PB

把这些每一个内存单元想成一个房间,一个房间可以放8个比特位。

把每个内存单元的编号想成一个内存的地址。

总结就是:内存单元的编号==地址==指针

1.2 编址

计算机中的编址,不是把每个字节的地址记录下来,而是通过硬件设计来完成的。就比如许多乐器的规则,都是设计者设计好的,所有人都遵循这个规则。其实本质上就是一直约定出来的共识。

2. 指针的变量和地址

2.1 取地址(&)

知道了内存和地址,其实在C语言中创建变量就是向内存申请一块空间。

 向这样的地址a我们应该如何得呢?

这时就要用到我们的一个操作符(&)——取地址操作符

#include <stdio.h>
//%p--打印地址
int main()
{
	int a = 10;
	printf("%p\n", &a);
	return 0;
}

 我们知道整型变量是占4个字节,这里我们只要知道第一个地址,就可以访问到4个字节的数据了

2.2 指针变量

当我们通过&得到的地址也是一个数(0x0023DF32),这个数值有时候也要储存起来,方便我们后面的使用。但是我们应该存放到哪呢?答案就是:指针变量。

#include <stdio.h>
int main()
{
	int a = 10;
	int* p = &a;//把a的地址储存在p这个指针变量中。
	return 0;
}

注意:指针变量也是变量,这种变量是用来存地址的,只要存放在指针中的值都会理解为地址。

 指针的类型:

int a=10;
int* p=&a;

这里的* 是在说明平时指针变量,而int 是在说明p指向的是整型类型的对象。

2.3 解引用

我们现在知道怎么把地址保证起来了,但是要怎么使用呢?在现实生活中,我们每个房间都会有一把打开它的钥匙,用了钥匙我们就可以打开房间,找到里面的东西。C语言其实也是一样的,我们拿到了地址,就可以通过地址找到地址指向的对象。这里就要用到解引用操作符 *()。

#include <stdio.h>
int main()
{
	int a = 10;
	int* p = &a;//把a的地址储存在p这个指针变量中。
	*p = 0;//解引用p,找到a并且将它的值改为0
	printf("%d", *p);
	return 0;
}

2.4 指针变量大小

#include <stdio.h>
//指针变量的⼤⼩取决于地址的⼤⼩
//32位平台下地址是32个bit位(即4个字节)
//64位平台下地址是64个bit位(即8个字节)
int main()
{
	printf("%zd\n", sizeof(char*));
	printf("%zd\n", sizeof(short*));
	printf("%zd\n", sizeof(int*));
	printf("%zd\n", sizeof(double*));
	return 0;
}

3. 指针变量类型存在的意义

 其实指针类型是有特殊意义的,不同的类型有不同的意义。

3.1 不同类型指针的解引用

我们先看两个程序:

#include <stdio.h>
int main()
{
	int n = 0x11223344;
	int* pi = &n;
	*pi = 0;
	return 0;
}
#include <stdio.h>
int main()
{
	int n = 0x11223344;
	char* pc = (char*)&n;
	*pc = 0;
	return 0;
}

通过调试我们发现第一个程序会把n的4个字节全部改为0,而第二个程序只会把第一个字节改为0

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

3.2 指针对整数的运算(+,-)

我们可以看到,char*类型的指针变量+1跳过1个字节,int* 类型的指针变量+1,跳过4次字节。

总结:指针的类型决定了指针向前或者向后走一步的距离。

3.3 void* 指针

在指针类型中有void*类型的,我们可以理解为没有具体类型的指针(泛指针),这种指针可以接收任意类型的地址。但是,void* 不能用来直接进行指针的加减整数和解引用运算。

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

4. const 修饰

4.1 const 变量

只要是变量,就是可以修改的。如果我们要想要这个变量成为不可变的,那么就要用到 const 来修饰。

 4.2 const(指针变量)

举例:

#include <stdio.h>
//代码1
void test1()
{
	int n = 10;
	int m = 20;
	int* p = &n;
	*p = 20;
	p = &m; 
}
void test2()
{
	//代码2
	int n = 10;
	int m = 20;
	const int* p = &n;
	*p = 20;
	p = &m; 
}
void test3()
{
	int n = 10;
	int m = 20;
	int* const p = &n;
	*p = 20;
	p = &m; 
}
void test4()
{
	int n = 10;
	int m = 20;
	int const* const p = &n;
	*p = 20; 
	p = &m; 
}
int main()
{
	//测试⽆const修饰的情况
	test1();
	//测试const放在*的左边情况
	test2();
	//测试const放在*的右边情况
	test3();
	//测试*的左右两边都有const
	test4();
	return 0;
}

结论:const修饰指针变量的时候

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

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

5. 指针的运算

指针的基本运算有三种,分别是:

 指针+- 整数——根据指针变量的类型,向前或向后移动

 指针-指针

 指针的关系运算

 6. 野指针

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

产生野指针的原因:
1. 指针没有初始化

2. 指针越界访问 ——只有10个元素,但是访问到第11个

3. 指针指向的空间被释放——指针指向函数

避免野指针的方法:
1. 指针初始化:指针一定要指向一个地址,没有就给指针赋值NULL(0)

2.小心指针越界:⼀个程序向内存申请了哪些空间,通过指针也就只能访问哪些空间。

3. 指针变量不再使用时一定要及时置为NULL,指针使用之前检查有效性。

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

7. assert断言

assert.h 头⽂件定义了宏 assert() ,⽤于在运⾏时确保程序符合指定条件,如果不符合,就报错终⽌运⾏。这个宏常常被称为“断⾔”。

assert (p!=NULL)
//验证p是否等于NULL
//当p不等于NULL时,程序继续运行,否则停止,并且给出报错信息

assert 的实现:assert() 宏接受⼀个表达式作为参数。如果该表达式为真(返回值⾮零),assert() 不会产⽣ 任何作⽤,程序继续运⾏。如果该表达式为假(返回值为零), assert() 就会报错,在标准错误流 stderr 中写⼊⼀条错误信息,显示没有通过的表达式,以及包含这个表达式的⽂件名和⾏号。

使用的优点:可以自动标识文件和出问题的行号,还有⼀种⽆需更改代码就能开启或关闭 assert() 的机制。

补充:如果已经确认程序没有问题,不需要再做断⾔,就在 #include 语句的前⾯,定义⼀个宏 NDEBUG

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

举例1:strlen 的模拟实现

#include<assert.h>
int my_strlen(const char* str)
{
	int count = 0;
	assert(str);//断言判的str是否为真(非0)
	while (*str)
	{
		count++;
		str++;
	}
	return count;
}
int main()
{
	int len = my_strlen("abcdef");
	printf("%d\n", len);
	return 0;
}

传址调用
例如:写⼀个函数,交换两个整型变量的值

我们一般的想法(传值调用):

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

如果把要交换值的地址传给函数呢?

我们可以看到交换成功了。这种把变量的地址传递给了函数的方式叫:传址调用

补充:传址调⽤,可以让函数和主调函数之间建⽴真正的联系,在函数内部可以修改主调函数中的变量;所以未来函数中只是需要主调函数中的变量值来实现计算,就可以采⽤传值调⽤。如果函数内部要修改主调函数中的变量的值,就需要传址调⽤。

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

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

相关文章

CentOS Stream 9系统Cgroup问题处理

安装docker容器启动失败 之前适配过Ubuntu系统的容器&#xff0c;由于版本比较高&#xff0c;没有挂载Cgroup的路径。这次使用Centos Stream 9系统安装docker容器时也遇到了这个情况。由于处理方式有些不一样&#xff0c;所以记录一下。 这是docker容器启动过报错的输出日志。…

实例讲解:在3dMax中如何使用python脚本?

如果你是Python或Maxscript的新手&#xff0c;你现在可以跟着这篇文章开始做一些代码了&#xff0c;本文将让我们从非常基本的东西开始学习。 如何在3dmax中获取选定的节点并打印出它们的名称&#xff1f;所有场景对象如何&#xff1f;我们直接看代码&#xff1a; import MaxP…

斯坦福大学引入FlashFFTConv来优化机器学习中长序列的FFT卷积

斯坦福大学的FlashFFTConv优化了扩展序列的快速傅里叶变换(FFT)卷积。该方法引入Monarch分解&#xff0c;在FLOP和I/O成本之间取得平衡&#xff0c;提高模型质量和效率。并且优于PyTorch和FlashAttention-v2。它可以处理更长的序列&#xff0c;并在人工智能应用程序中打开新的可…

预处理机制

跟着肯哥&#xff08;不是我&#xff09;学预处理机制 预处理类别 宏定义&#xff1a;#define 将文本替换为表达式或语句 条件编译&#xff1a;#ifdef、#ifndef和#if、#elif、#endif 根据标识符是否被定义选择编译代码 头文件包含&#xff1a;#include 将其他文件&#x…

万界星空科技商业开源MES/免费MES/低代码MES

万界星空科技商业开源MES可以提供包括制造数据管理、计划排程管理、生产调度管理、库存管理、质量管理、人力资源管理、工作中心/设备管理、工具工装管理、采购管理、成本管理、项目看板管理、生产过程控制、底层数据集成分析、上层数据集成分解等管理模块&#xff0c;打造一个…

Python+jieba+wordcloud实现文本分词、词频统计、条形图绘制及不同主题的词云图绘制

目录 序言&#xff1a;第三方库及所需材料函数模块介绍分词词频统计条形图绘制词云绘制主函数 效果预览全部代码 序言&#xff1a;第三方库及所需材料 编程语言&#xff1a;Python3.9。 编程环境&#xff1a;Anaconda3&#xff0c;Spyder5。 使用到的主要第三方库&#xff1a;…

gitee推荐-PHP面试准备的资料

该内容为giee项目。PHP-Interview: 这个项目是自己准备PHP面试整理的资料。包括PHP、MySQL、Linux、计算机网络等资料。方便自己以后查阅&#xff0c;会不定期更新&#xff0c;欢迎提交pr&#xff0c;如果错误&#xff0c;请指出&#xff0c;谢谢 在线预览地址&#xff1a;Intr…

【LeetCode】每日一题 2023_11_23 HTML 实体解析器(调库/打工)

文章目录 刷题前唠嗑题目&#xff1a;HTML 实体解析器题目描述代码与解题思路 结语 刷题前唠嗑 题目&#xff1a;HTML 实体解析器 题目链接&#xff1a;1410. HTML 实体解析器 题目描述 代码与解题思路 func entityParser(s string) (ans string) {return strings.NewRepla…

OpenHarmony之NAPI框架介绍

张志成 诚迈科技高级技术专家 NAPI是什么 NAPI的概念源自Nodejs&#xff0c;为了实现javascript脚本与C库之间的相互调用&#xff0c;Nodejs对V8引擎的api做了一层封装&#xff0c;称为NAPI。可以在Nodejs官网&#xff08;https://nodejs.org/dist/latest-v20.x/docs/api/n-api…

C语言——从键盘输人三角形的三个边长 a、b、c,求出三角形的面积。

从键盘输人三角形的三个边长 a、b、c,求出三角形的面积。求三角形的面积用公式areasqrt(s*(s-a)*(s-b)*(s-c)),其中 s1/2(a十bc)。注:要求对输人三角形的三个边长做出有效性判断。 #define _CRT_SECURE_NO_WARNINGS 1#include<stdio.h> #include<math.h> int main…

System-V共享内存和基于管道通信实现的进程池

文章目录 一.进程间通信:进程间通信的本质: 二.Linux管道通信匿名管道:关于管道通信的要点:基于匿名管道构建进程池: 三.System-V共享内存共享内存和命名管道协同通信 参考Linux内核源码版本------linux-2.4.3 一.进程间通信: 操作系统中,为了保证安全性,进程之间具有严格的独…

<JavaEE> 什么是线程(Thread)?进程和线程有什么区别?

目录 一、线程&#xff08;Thread&#xff09;的概念 二、线程存在的意义 2.1 并发编程 2.2 比进程更“轻量” 三、使用线程时应该注意 四、进程和线程的区别 五、Java中的线程和操作系统中的线程是不同的概念 六、多线程编程 一、线程&#xff08;Thread&#xff09;的…

Web服务器(go net/http) 处理Get、Post请求

大家好 我是寸铁&#x1f44a; 总结了一篇Go Web服务器(go net/http) 处理Get、Post请求的文章✨ 喜欢的小伙伴可以点点关注 &#x1f49d; 前言 go http请求如何编写简单的函数去拿到前端的请求(Get和Post) 服务器(后端)接收到请求后&#xff0c;又是怎么处理请求&#xff0c…

gitlab各版本安装注意点:

研发团队在安装gitlab各版本过程中可能遇到各种问题&#xff0c;为了后续容易查看特将我们在实践过程中遇到的各类问题要点总结如下&#xff1a; gitlab 10.8.3 (564c342&#xff09;安装 centos Linux yum安装网址查找网址&#xff1a;gitlab/gitlab-ce - Results for gitla…

【赠书第7期】从零基础到精通Flutter开发

文章目录 前言 1 安装Flutter和Dart 2 了解Flutter的基础概念 2.1 Widget 2.2 MaterialApp和Scaffold 2.3 Hot Reload 3 编写你的第一个Flutter应用 3.1 创建一个Flutter项目 3.2 修改默认页面 3.3 添加交互 4 深入学习Flutter高级特性 4.1 路由和导航 4.2 状态管…

RubyMine 2023:提升Rails/Ruby开发效率的强大利器

在Rails/Ruby开发领域&#xff0c;JetBrains RubyMine一直以其强大的功能和优秀的性能而备受开发者的青睐。现如今&#xff0c;我们迎来了全新的RubyMine 2023版本&#xff0c;它将为开发者们带来更高效的开发体验和无可比拟的工具支持。 首先&#xff0c;RubyMine 2023提供了…

C/C++小写字母的判断 2022年3月电子学会中小学生软件编程(C/C++)等级考试一级真题答案解析

目录 C/C小写字母的判断 一、题目要求 1、编程实现 2、输入输出 二、算法分析 三、程序编写 四、程序说明 五、运行结果 六、考点分析 C/C小写字母的判断 2022年3月 C/C编程等级考试一级编程题 一、题目要求 1、编程实现 输入一个字符&#xff0c;判断是否是英文小…

在两个java项目中实现Redis的发布订阅模式

如何在两个java项目中实现Redis的发布订阅模式&#xff1f; 1. Redis简介2. 发布订阅模式介绍3. 实现思路4. 代码实现及详细解释4.1. RedisUtil4.2. Publisher4.3. Subscriber4.4. 运行程序 目录&#xff1a; Redis简介发布订阅模式介绍实现思路代码实现及详细解释 1. Redis简…

Python----类对象和实例对象

目录 一.类和类的实例 二.类属性和实例属性 三.私有属性和公有属性 四.静态方法和类方法 五.__init__方法&#xff0c;__new__方法和__del__方法&#xff1a; 六.私有方法和公有方法 七.方法的重载 八.方法的继承 九.方法的重写 十.对象的特殊方法 十一.对象的引用&a…