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

news2025/1/23 17:39:53

前言

         C语言是一种直接操作内存的编程语言,我们可以直接访问和操作计算机内存中的地址空间。

 

        而C语言中存在的指针类型,指针指向的就是内存中的地址。我们可以通过指针来访问和修改内存中存储的数据。

 

         因此,深入理解指针,并且理解内存,对于编写高质量的程序和调试程序故障都非常有帮助。


 在理解指针之前,我们先引入内存地址的概念:

(一)内存与地址

从实际生活出发

地址

        从实际生活中的问题出发——假如你要去上课,但是学校里教学楼那么多,如果想要上课,就得挨个教室去找,这样当你找到上课的教室,估计也下课了。

        在实际生活中,我们是如何解决找东西效率低这一问题呢?

        ——我们可以将东西排好序,提前熟悉它们的位置,甚至编好号。

        我们将每栋教学楼的每一个房间一一编号,这样,你要去上课,只要有了楼号和房间号,就可以快速找到房间,找到教室。

于是,C语言借鉴生活中的实例,从而有了地址的概念。 

 内存

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

        我们买电脑的时候,电脑上内存是8GB/16GB/32GB等,那这些内存空间如何高效的管理呢?

        其实也是把内存划分为⼀个个的内存单元,每个内存单元的⼤⼩取1个字节。

         一个字节有多大呢?

常见内存单位

1byte(字节) = 8bit

1KB = 1024byte

1MB = 1024KB

1GB = 1024MB

1TB = 1024GB

1PB = 1024TB

 (一个内存单元的大小取为1字节是比较合适的,如果取得过大,那么计算机的内存就会小很多,如果过大,那么内存无法被高效利用)

 内存与地址关系密切

        每个内存单元也都有⼀个编号(这个编号就相当于教学楼的门牌号),有了这个内存单元的编号,CPU就可以快速找到⼀个内存空间。

        生活中我们把门牌号也叫地址,在计算机中我们 把内存单元的编号也称为地址。

        C语⾔中给地址起了新的名字————指针

所以我们可以理解为:

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


(二)指针变量

指针变量与取地址操作符

        我们对内存和地址有了初步的了解,我们再回到C语言,在C语言中创建变量其实就是向内存申请空间:

#include <stdio.h>
int main()
{
 int a = 10;
 return 0;
}

 

⽐如,上述的代码就是创建了整型变量a,内存中申请4个字节,用于存放整数10,其中每个字节都有地址,上图中4个字节的地址分别是:

0x007DF784
0x007DF785
0x007DF786
0x007DF787

        上图中,出现了一个陌生的操作符—— &(取地址操作符)

        作用:取出变量的地址

 比如,我们可以将a的地址放在pa中,并用%p  打印出来:

但是,我们发现打印出的只有一个地址, 并且与第一次看到的a的地址不同!

原因在于:

        1.每一次运行代码,编译器都会重新给变量分配内存空间,这也就解释了为什么第二次打印的地址与第一次不同。

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

         在上图中,又出现了一个陌生的操作符

指针变量与解引用操作符 

        我们通过取地址操作符(&)拿到的地址是⼀个数值,如:0x006FFD70,这个数值有时候也是需要 存储起来,⽅便后期再使用的,那我们把这样的地址值存放在哪里呢?

        答案是:指针变量中。

        我们可以通过创建一个指针变量,来存储变量的地址:

#include<stdio.h>
int main()
{
    int a = 100;
    int* pa = &a;
    *pa = 0;
}

        *pa 的意思就是通过pa中存放的地址,找到指向的空间, *pa其实就是a.

        于是,解引用操作符的作用就是通过地址找到对应的变量。


指针的大小 

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

        如果是32位机器,那么指针的大小是4个字节。

        如果是64位机器,指针的大小是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;
}

 如图,演示:

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


既然指针的大小与类型无关,那么指针类型存在的意义是什么?

在讨论这个问题之前,我们先引入指针的运算

指针的运算

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

• 指针+- 整数

• 指针-指针

• 指针的关系运算

 

指针 +- 整数

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

 1.数组在内存中是连续存放的,只要知道第⼀个元素的地址,顺藤摸瓜就能找到后面的所有元素。

2.指针加减整数,表示跳过当前指针类型的元素的个数。

 指针-指针

//指针-指针
#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;
}

 1.图中实现的是strlen函数的模拟实现

2.指针-指针  表示两个指针变量之间的元素的个数

 指针的关系运算

 

//指针的关系运算
#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;
}

1.指针的关系的比较,实际上就是地址大小的比较。


指针的类型的意义

 

         1.指针的类型决定了,对指针解引用的时候有多大的权限(⼀次能操作几个字节)。

 

//代码2
#include <stdio.h>
int main()
{
	int n = 0x11223344;
	char* pc = (char*)&n;
	*pc = 0;
	return 0;

 对于n的地址:

 *pc = 0;之前

 之后:

 

         2.指针的类型决定了指针向前或者向后一步有多大(距离)。

#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个字节。 这就是指针变量的类型差异带来的变化。 

 void* 指针

 

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

 const修饰指针

见详解

const修饰指针icon-default.png?t=N7T8https://mp.csdn.net/mp_blog/creation/editor/134341320

野指针

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

野指针成因

1. 指针未初始化

2. 指针越界访问

3. 指针指向的空间释放

3的实例

#include <stdio.h>
int* test()
{
 int n = 100;
 return &n;
}
int main()
{
 int*p = test();
 printf("%d\n", *p);
 return 0;
}

 如何规避野指针

1.指针初始

2.小心指针越界

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

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

5.assert断言

 


完~

未经作者同意禁止转载

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

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

相关文章

基于SSH的员工管理系统(一)——包结构

基于SSH的员工管理系统&#xff08;一&#xff09;——包结构 包结构 1、整体包结构 2、action包 3、domain实体包 4、service层 5、dao层 6、util工具包 7、页面层

【Oracle】数据库登陆错误:ORA-28000:the account is locked解决方法

问题描述 在连接Oracle数据库的时候出现了ORA-28000:the account is locked报错&#xff0c;登录账号被锁定&#xff0c;出现这种情况就需要将被锁定用户解锁。 解决方法 解锁方法就是通过用system账号登录数据库&#xff0c;然后修改被锁定账户状态&#xff0c;具体如下图所示…

03 数仓平台 Kafka

kafka概述 定义 Kafka 是一个开源的分布式事件流平台&#xff08;Event Streaming Plantform&#xff09;&#xff0c;主要用于大数据实时领域。本质上是一个分布式的基于发布/订阅模式的消息队列&#xff08;Message Queue&#xff09;。 消息队列 在大数据场景中主要采用…

右值引用和移动语句(C++11)

左值引用和右值引用 回顾引用 我们之前就了解到了左值引用&#xff0c;首先我们要了解引用在编译器底层其实就是指针。具体来说&#xff0c;当声明引用时&#xff0c;编译器会在底层生成一个指针来表示引用&#xff0c;但在代码编写和使用时&#xff0c;我们可以像使用变量类…

鸿蒙绘制折线图基金走势图

鉴于鸿蒙下一代剥离aosp&#xff0c;对于小公司而言&#xff0c;要么用h5重构&#xff0c;要么等大厂完善工具、等华为出转换工具后跟进&#xff0c;用鸿蒙重新开发一套代码对于一般公司而言成本会大幅增加。但对于广大开发者来说&#xff0c;暂且不论未来鸿蒙发展如何&#xf…

中国消费电子行业发展趋势及消费者需求洞察|徐礼昭

一、引言 近年来&#xff0c;随着科技的飞速发展&#xff0c;消费电子行业面临着前所未有的挑战与机遇。本文将从行业发展趋势、消费者需求洞察以及企业数字化转型的方向和动作三个方面&#xff0c;对消费电子行业进行深入剖析。 二、消费电子行业发展趋势 5G技术的普及和应…

WEB安全之Python

WEB安全之python python-pyc反编译 python类似java一样&#xff0c;存在编译过程&#xff0c;先将源码文件*.py编译成 *.pyc文件&#xff0c;然后通过python解释器执行 生成pyc文件 创建一个py文件随便输入几句代码(1.py) 通过python交互终端 >>>import py_compil…

【C++练级之路】【Lv.1】C++,启动!(命名空间,缺省参数,函数重载,引用,内联函数,auto,范围for,nullptr)

目录 引言入门须知一、命名空间1.1 作用域限定符1.2 命名空间的意义1.3 命名空间的定义1.4 命名空间的使用 二、C输入&输出2.1 cout输出2.2 cin输入2.3 std命名空间的使用惯例 三、缺省参数3.1 缺省参数概念3.2 缺省参数分类 四、函数重载4.1 函数重载概念4.2 函数重载分类…

JavaSE自定义验证码图片生成器

设计项目的时候打算在原有的功能上补充验证码功能&#xff0c;在实现了邮箱验证码之后想着顺便把一个简单的图片验证码生成器也实现一下&#xff0c;用作分享。 注意&#xff0c;实际开发中验证码往往采用各种组件&#xff0c;通过导入依赖来在后端开发时使用相关功能&#xf…

泊车功能专题介绍 ———— 汽车全景影像监测系统性能要求及试验方法(国标未公布)

文章目录 术语和定义一般要求功能要求故障指示 性能要求响应时间图像时延单视图视野范围平面拼接视图视野平面拼接效果总体要求行列畸变拼接错位及拼接无效区域 试验方法环境条件仪器和设备车辆条件系统响应时间试验图像时延试验单视图视野范围试验平面拼接视图视野试验平面拼接…

【大学英语视听说上】Mid-term Test 2

Section A 【短篇新闻1】 You probably think college students are experts at sleeping, but parties, preparations for tests, personal problems and general stress can rack a students sleep habits, which can be bad for the body and the mind. Texas Tech Univer…

51爱心流水灯32灯炫酷代码

源代码摘自远眺883的文章&#xff0c;大佬是30个灯的&#xff0c;感兴趣的铁汁们可以去看看哦~&#xff08;已取得原作者的许可&#xff09;&#xff1a;基于STC89C51单片机设计的心形流水灯软件代码部分_单片机流水灯代码_远眺883的博客-CSDN博客 由于博主是个小菜鸡&#xff…

【Python从入门到进阶】43.验证码识别工具结合requests的使用

接上篇《42、使用requests的Cookie登录古诗文网站》 上一篇我们介绍了如何利用requests的Cookie登录古诗文网。本篇我们来学习如何使用验证码识别工具进行登录验证的自动识别。 一、图片验证码识别过程及手段 上一篇我们通过requests的session方法&#xff0c;带着原网页登录…

前缀和 LeetCode1423. 可获得的最大点数

几张卡牌 排成一行&#xff0c;每张卡牌都有一个对应的点数。点数由整数数组 cardPoints 给出。 每次行动&#xff0c;你可以从行的开头或者末尾拿一张卡牌&#xff0c;最终你必须正好拿 k 张卡牌。 你的点数就是你拿到手中的所有卡牌的点数之和。 给你一个整数数组 cardPoi…

一个网站,四种创建制作电子期刊的方法

想象一下&#xff0c;你正在走进一家神奇的商店&#xff0c;里面陈列着各种精美的杂志和期刊。但是&#xff0c;这些杂志和期刊并不是印刷品&#xff0c;而是可以直接在网站上制作和发布的电子期刊。 但是像这样能在网上发的电子期刊该怎么制作呢&#xff1f;不知道如何制作的小…

数字媒体技术基础之:常见字体类型

字体 Font在数字设计和排版中起着至关重要的作用&#xff0c;不同的字体类型为文本呈现和创意表达提供了丰富多样的可能性。 .fon 字体 .fon 文件是 Windows 早期系统中使用的一种字体文件格式。 特点&#xff1a; 1、基于像素的位图字体。 2、不支持无损缩放&#xff0c;主要用…

国际语音通知是什么?国际语音通知系统有哪些功能?

一、国际语音通知是什么&#xff1f; 如同国际短信通知&#xff0c;国际语音通知也在多种生活场景中扮演着重要的角色&#xff0c;如会议通知、商品发货通知、物流更新通知、快递取件通知、外卖取餐通知等。那么什么是语音通知呢&#xff1f; 国际语音通知可将通知的文本信息使…

算法复习,数据结构 ,算法特性,冒泡法动态演示,复杂度,辗转相除法*,寻找最大公因数

算法复习 知识点 1. 程序 数据结构 算法 2. 算法&#xff1a; 求解问题的策略数据结构&#xff1a;问题的数学模型程序&#xff1a;微计算机处理问题编制的一组指令 3. **特性 ** 有穷性&#xff1a;算法在执行有穷步后能结束确定性&#xff1a;每一指令有确切的含义&a…

规则引擎专题---5、Groovy环境搭建和基础语法

概述 Groovy是用于Java虚拟机的一种敏捷的动态语言&#xff0c;它是一种成熟的面向对象编程语言&#xff0c;既可以用于面向对象编程&#xff0c;又可以用作纯粹的脚本语言。使用该种语言不必编写过多的代码&#xff0c;同时又具有闭包和动态语言中的其他特性。 Groovy是从Jav…

线程安全的问题以及解决方案

线程安全 线程安全的定义 线程安全:某个代码无论是在单线程上运行还是在多线程上运行,都不会产生bug. 线程不安全:单线程上运行正常,多线程上运行会产生bug. 观察线程不安全 看看下面的代码: public class ThreadTest1 {public static int count 0;public static void main…