【C语言】指针---初阶

news2024/12/25 22:19:50

🍁 博客主页:江池俊的博客

🍁收录专栏:C语言——探索高效编程的基石

🍁 如果觉得博主的文章还不错的话,请点赞👍收藏🌟 三连支持一下博主💞

目录

一、指针是什么?

1.1指针与内存间的关系

内存

1.2指针变量存放地址

二、指针和指针类型

2.1指针有哪些类型?

2.2指针类型的意义是什么?

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

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

 三、野指针

3.1野指针成因

3.1.1. 指针未初始化

3.1.2. 指针越界访问 

3.1.3. 指针指向的空间释放 

3.2 如何规避野指针

四、指针运算

4.1 指针+-整数

4.2 指针-指针

4.3 指针的关系运算

五、 指针和数组

六、二级指针

6.1二级指针与多级指针 

6.2二级指针运算 

七、指针数组


一、指针是什么?

在计算机科学中,指针是一种变量类型,它存储了一个内存地址。这个内存地址指向计算机内存中的一个特定位置,该位置存储了实际的数据。通过使用指针,可以直接访问和修改存储在特定内存位置的数据,而无需复制或移动数据本身。(本质上指针就是地址)

1.1指针与内存间的关系

这里我们知道char类型占1个字节的内存空间,short类型占2个字节的内存空间,int类型占4个字节的内存空间,long类型占4个字节的内存空间,long long类型占8个字节的内存空间,float类型占4个字节的内存空间,double类型占8个字节的内存空间。

内存

经过仔细的计算和权衡我们发现一个字节给一个对应的地址是比较合适的。
对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的时候产生高电平(高电压)和低电平(低电压)就是(1或者0);
那么32根地址线产生的地址就会是:
00000000 00000000 00000000 00000000

00000000 00000000 00000000 00000001

........

01111111 11111111 11111111 11111111

10000000 00000000 00000000 00000000

10000000 00000000 00000000 00000001

........

11111111 11111111 11111111 11111110

11111111 11111111 11111111 11111111

一共有2^{32}种可能的编号,每一种编号表示一个内存单元的地址,所以共有2^{32}个地址。每个地址表示一个字节,那我们就可以给 (2^32Byte=2^32/1024KB=2^32/1024/1024MB=2^32/1024/1024/1024GB = 4GB)4G的空闲进行编址。
同样的方法,那64位机器,如果给64根地址线,那么就有2^{64}个地址。

这里我们就明白:
🌴 在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储(因为一个字节等于八个比特位,而一个0或1占一个比特位),所以一个指针变量的大小就应该是4个字节。
🌴同理,那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地址。

1.2指针变量存放地址

注意:这里a的值以16进制的形式存储在内存中的并且是倒着存放的(这里涉及到大小端问题) 。

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

二、指针和指针类型

2.1指针有哪些类型?

char *pc = NULL;
int *pi = NULL;
short *ps = NULL;
long *pl = NULL;
float *pf = NULL;
double *pd = NULL;

不同类型的指针其实是为了存放对应类型变量的地址。 

2.2指针类型的意义是什么?

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

这里我们不难看出,char*类型的指针加一时,它的地址增加了1个字节,而int*类型的指针加一时,它的地址增加了4个字节 。

总结:指针变量加减 ,跳过多少字节(地址加减多少)取决于指针类型

例如:

如果是 char 类型指针,指针变量加 1或减1 实际上地址加了或减了 1 个字节

如果是 short 类型指针,指针变量加 1或减1  实际上地址加了或减了 2 个字节

如果是 int 类型指针,指针变量加 1或减1  实际上地址加了或减了 4 个字节 

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

📌char*指针

这里是将int类型的变量n的地址强制转换赋给了char类型指针pc 

指针pc解引用后:

📌int*指针 

指针pi解引用后:

 

 三、野指针

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

3.1野指针成因

3.1.1. 指针未初始化

#include <stdio.h>
int main()
{
int *p;//局部变量指针未初始化,默认为随机值
*p = 20;
return 0;
}

3.1.2. 指针越界访问 

#include <stdio.h>
int main()
{
int arr[10] = {0};
int *p = arr;
int i = 0;
for(i=0; i<=11; i++)
{
//当指针指向的范围超出数组arr的范围时,p就是野指针
*(p++) = i;
}
return 0;
}

3.1.3. 指针指向的空间释放 

动态开辟一块空间时返回的值存放到一个指针中,当使用完这个指针后没有释放时,该指针就会变为野指针。

3.2 如何规避野指针

1. 指针初始化
2. 小心指针越界
3. 指针指向空间释放即使置NULL
4. 避免返回局部变量的地址
5. 指针使用之前检查有效性

#include <stdio.h>
int main()
{
int *p = NULL;

int a = 10;
p = &a;
//检查指针的有效性
if(p != NULL)
{
*p = 20;
}
return 0;
}

四、指针运算

4.1 指针+-整数

指针加+-运算用于将指针移动指定的偏移量,以便访问其他地址处的数据。例如,如果有一个指向整型数组的指针p,可以使用p+n或p-n将指针移动n个整型长度的位置。

#define N_VALUES 5
float values[N_VALUES];
float *vp;
//指针+-整数;指针的关系运算
for (vp = &values[0]; vp < &values[N_VALUES];)
{
*vp++ = 0;
}

4.2 指针-指针

可以对两个指针进行减法运算,得到它们之间的距离(以元素个数为单位)。这在处理数组或动态内存分配时非常有用。例如,假设有两个int类型的指针ptr1和ptr2,我们可以进行如下操作:

int distance = ptr2 - ptr1; // 计算ptr2和ptr1之间的距离(以int元素个数为单位)

需要注意的是,指针运算应该谨慎使用,确保指针指向的内存是有效的。否则,可能会导致未定义行为或内存错误。此外,不同类型的指针之间不应进行直接算术运算,因为它们可能引用不同大小的数据。只有在指向同一数组的元素或有效分配的内存区域时,指针之间的运算才是有效的。 

4.3 指针的关系运算

在C、C++等编程语言中,指针的关系运算用于比较指针的地址或者判断指针是否为NULL。下面是指针的关系运算符及其含义:

📌相等(==):用于判断两个指针是否指向同一个内存地址。如果两个指针指向相同的地址,则关系表达式为真,否则为假。

int* ptr1;
int* ptr2;

if (ptr1 == ptr2) {
    // 指针ptr1和ptr2指向相同的地址
}

📌不相等(!=):用于判断两个指针是否指向不同的内存地址。如果两个指针指向不同的地址,则关系表达式为真,否则为假。

int* ptr1;
int* ptr2;

if (ptr1 != ptr2) {
    // 指针ptr1和ptr2指向不同的地址
}

📌大于(>)、小于(<)、大于等于(>=)、小于等于(<=):这些关系运算符用于比较两个指针所指向的地址之间的顺序关系。只有当指针指向同一个数组中的元素或者在有效的内存范围内时,这些关系运算符才有定义。

int* ptr1;
int* ptr2;

if (ptr1 > ptr2) {
    // ptr1指向的地址在ptr2指向的地址之后
}

if (ptr1 < ptr2) {
    // ptr1指向的地址在ptr2指向的地址之前
}

if (ptr1 >= ptr2) {
    // ptr1指向的地址在ptr2指向的地址之后或相同
}

if (ptr1 <= ptr2) {
    // ptr1指向的地址在ptr2指向的地址之前或相同
}

📌空指针检查:指针的关系运算还可以用于检查指针是否为空(指向NULL)。在C和C++中,NULL是一个预定义的宏,用于表示一个空指针。

int* ptr = NULL;

if (ptr == NULL) {
    // 指针ptr是空指针
}

if (ptr != NULL) {
    // 指针ptr不是空指针
}

需要注意的是,对于关系运算符(>, <, >=, <=),只有在指针指向同一数组的元素或有效分配的内存区域时,比较的结果才是有效的。在进行指针比较之前,最好确保指针指向的内存是有效的,否则可能会导致未定义行为。

五、 指针和数组

我们来看一个例子:

#include <stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
printf("%p\n", arr);
printf("%p\n", &arr[0]);
return 0;
}

运行结果: 

可见数组名和数组首元素的地址是一样的。
结论:数组名表示的是数组首元素的地址。(一般情况下)
那么这样写代码是可行的:

int arr[10] = {1,2,3,4,5,6,7,8,9,0};
int *p = arr;//p存放的是数组首元素的地址

既然可以把数组名当成地址存放到一个指针中,我们使用指针来访问一个就成为可能。
例如:

#include <stdio.h>
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,0};
int *p = arr; //指针存放数组首元素的地址
int sz = sizeof(arr)/sizeof(arr[0]);
for(int i=0; i<sz; i++)
{
printf("&arr[%d] = %p <====> p+%d = %p\n", i, &arr[i], i, p+i);
}
return 0;
}

运行结果: 

所以 p+i 其实计算的是数组 arr 下标为i的地址。
那我们就可以直接通过指针来访问数组。
如下:

#include<stdio.h>
int main()
{
int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
int *p = arr; //指针存放数组首元素的地址
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
for (i = 0; i<sz; i++)
{
printf("%d ", *(p + i));
}
return 0;
}

运行结果: 

 

六、二级指针

二级指针(double pointer)是指指向指针的指针。我们可以使用指向指针的指针来处理指针的引用或修改。它们在一些情况下非常有用,特别是在涉及到函数参数传递和动态内存分配等方面。

定义二级指针的语法为在指针类型名称前加上两个星号(**):

int** doublePtr;

6.1二级指针与多级指针 

#include<stdio.h>
int main()
{
	int a = 10;
	int* pa = &a;
	int** ppa = &pa;
	int*** pppa = &ppa;
	printf("%p\n", a);
	printf("%p\n", &a);

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

	printf("\n%p\n", ppa);
	printf("%p\n", &ppa);
	
	printf("\n%p\n", pppa);
	printf("%p\n", &pppa);
	return 0;
}

 

6.2二级指针运算 

📌*ppa 通过对ppa中的地址进行解引用,这样找到的是 pa , *ppa 其实访问的就是 pa .

int b = 20;
*ppa = &b;//等价于 pa = &b;

📌**ppa 先通过 *ppa 找到 pa ,然后对 pa 进行解引用操作: *pa ,那找到的是 a .

**ppa = 30;
//等价于*pa = 30;
//等价于a = 30;

七、指针数组

指针数组是一个数组,其中的每个元素都是指针。我们可以创建指针数组来存储一组指向不同数据类型的指针。这些指针可以指向数组、字符串、函数等各种数据类型,从而实现更灵活的数据结构和操作。

定义指针数组的语法为在指针类型名称后加上方括号([]):

int* ptrArray[5]; // 定义一个包含5个int指针的数组

 

在上述示例中,ptrArray是一个包含5个指向int类型数据的指针的数组。我们可以通过数组下表来访问和操作这些指针:

int num1 = 10;
int num2 = 20;
int num3 = 30;

int* ptrArray[3]; // 定义一个包含3个int指针的数组

ptrArray[0] = &num1; // 第一个指针指向num1的地址
ptrArray[1] = &num2; // 第二个指针指向num2的地址
ptrArray[2] = &num3; // 第三个指针指向num3的地址

printf("Value at index 0: %d\n", *ptrArray[0]); // 输出:Value at index 0: 10
printf("Value at index 1: %d\n", *ptrArray[1]); // 输出:Value at index 1: 20
printf("Value at index 2: %d\n", *ptrArray[2]); // 输出:Value at index 2: 30

指针数组非常适用于存储字符串数组。在C语言中,字符串被表示为字符数组,并且每个字符串都以空字符('\0')结尾。使用指针数组可以更方便地管理字符串的集合:

const char* names[] = { "Alice", "Bob", "Charlie" };

在上面的示例中,我们创建了一个包含3个指针的指针数组,每个指针都指向一个字符串常量。

需要注意的是,指针数组中的每个指针应该指向有效的内存地址。否则,当我们尝试通过这些指针访问数据时,可能会导致未定义行为或者出现野指针的问题。确保在使用指针数组之前,为每个指针分配合适的内存或者让它们指向有效的数据。同时,记得在不再需要这些指针数组时,及时释放其所指向的内存,避免内存泄漏。

🔥今天的分享就到这里,如果觉得博主的文章还不错的话,请👍三连支持一下博主哦🤞

 

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

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

相关文章

FTP服务器无法连接解决办法

FTP服务器无法连接解决办法 目录 一&#xff0e; 报错状态二&#xff0e; 解决办法三&#xff0e; 总结 一&#xff0e; 报错状态 二&#xff0e; 解决办法 &#xff08;确保客户机与服务器可以ping通的情况下&#xff0c;再进行下面操作&#xff09; 检查ftp地址是否输错。默…

Kendo UI,一个加速Web应用界面开发的JavaScript组件库!

Kendo UI是什么&#xff1f; 首先&#xff0c;Kendo UI是一个由四个JavaScript UI库组成的包&#xff0c;这些库是专为jQuery、Angular、React和Vue原生构建的&#xff0c;每一个都是用一致的API和主题构建的。所以无论开发者怎么选择&#xff0c;所开发的Web应用始终保持了现…

QueryWrapper,LambdaQueryWrapper用法简介(MyBatis Plus进阶)

目录 一、项目结构及数据库内容如图二、数据库内容三、代码示例 关于如何使用MyBatis Plus自动生成service&#xff0c;mapper&#xff0c;domain参考我的这篇博客&#x1f449;使用MyBatis Plus自动生成service&#xff0c;mapper&#xff0c;domain 一、项目结构及数据库内容…

使用Docker构建LNMP环境并运行Wordpress网站平台

使用Docker构建LNMP环境并运行Wordpress网站平台 1.基于Dockerfile构建LNMP镜像1.1 基于Dockerfile构建nginx镜像1.1.1 修改/usr/local/nginx/conf/nginx.conf文件1.1.2 再次修改nginx服务的Dockerfile文件&#xff08;多级构建&#xff09;1.1.3 创建安装环境依赖包的镜像 1.2…

系统设计《System Design Interview》读书笔记

设计性能认知 延时 操作名称时间1级缓存引用0.5ns2级缓存引用7ns互斥锁/解锁100ns主存引用100ns用zippy压缩1k字节10,000ns10μs通过1GB网络传输2KB字节20,000ns 20μs内存按照顺序读取1MB250,000ns250μs同一个数据中心内的往返500,000ns 500μs磁盘寻找10,000.000ns10ms从…

Qt - .ui 文件的使用

文章目录 目录工具栏Dock Widget代码控制 ui添加资源添加文件 目录 子目录只能输入英文&#xff0c;想要显示中文&#xff0c;可以修改右下方表中的 text 属性&#xff1a; 工具栏 让工具栏共用 菜单栏的 new 和 open&#xff0c;只需将下方列表的控件&#xff0c;拖拽到工具栏…

解决@Scope(“prototype“)不生效的问题

目录 Scope(“prototype“)不生效Scope(“prototype“)正确用法——解决Bean多例问题 1.问题&#xff0c;Spring管理的某个Bean需要使用多例2.问题升级3. Spring给出的解决问题的办法&#xff08;解决Bean链中某个Bean需要多例的问题&#xff09; Scope(“prototype“)不生效 …

STM32(HAL库)驱动st7789LCD屏幕(7引脚240*240)

目录 1、简介 2、CubeMX初始化配置 2.1 基础配置 2.1.1 SYS配置 2.1.2 RCC配置 2.2 屏幕引脚配置 2.3 项目生成 3、KEIL端程序整合 3.1 LCD驱动添加 3.2 函数修改 3.2.1 lcd.h修改 3.2.2 lcd_innit.h 修改 3.2.3 lcd.c修改 3.2.4 lcd_inut.c修改 3.3 主函数代码 3.3…

VsCode上传到gitee码云仓库详细教程

首先下载git&#xff0c;地址:https://git-scm.com/downloads 1、到你要提交项目的gitee的项目中&#xff0c;右键点击Git Bash Here进入&#xff0c;进入后按顺序输入&#xff1a; 2、第二步输入你的gitee码云账户 输入ssh-keygen -t ed25519 -C "xxxxxxxxxxxxxxxxx&qu…

docker部署应用的三种方式——最后一种直接使用shell脚本一键化部署

docker命令部署 拉取ubuntu的基础镜像 docker pull ubuntu注意基础镜像是压缩版的&#xff0c;只保证能够运行项目的最基础条件&#xff0c;很多命令都是没有的&#xff0c;在使用过程中如果需要那些命令需要提前安装。 安装openjdk sudo apt install openjdk-11-jdk安装mys…

【c++】万字长文,浅析c++继承特性

继承 1. 继承的概念和定义1.1 概念1.2 定义1.2.1 定义格式 2.基类和派生类对象赋值转换&#xff08;##&#xff09;3. 继承中的变量和函数隐藏(#)4.派生类的默认成员函数&#xff08;###&#xff09;5.友元函数和静态成员5.1.友元函数5.2.静态成员 6.菱形继承&#xff08;###&a…

广德上汽通用汽车平行试车场

技术栈&#xff1a;使用vue2JavaScriptElement UIvuexaxioscesium 项目描述&#xff1a;广德上汽通用汽车平行试车场是依托千寻孪界开发的一套展示实时车辆位置同步展示光照&#xff0c;时间&#xff0c;阴影等特效&#xff0c;完成平行时空效果的一款软件。 工作内容&#xff…

Linux 桌面份额突破 3%

导读今天来聊一聊linux桌面。 Linux 桌面份额突破 3% 根据 Statcounter 的数据&#xff0c;Linux 的使用率在过去几年中一直在缓慢上升&#xff0c;趋势非常明显。今年&#xff0c;Linux 桌面的统计数据如下&#xff1a;一月&#xff0c;2.91%&#xff1b;二月&#xff0c;2.9…

基于Python+WaveNet+CTC+Tensorflow智能语音识别与方言分类—深度学习算法应用(含全部工程源码)

目录 前言总体设计系统整体结构图系统流程图 运行环境Python 环境Tensorflow 环境 模块实现1. 方言分类数据下载及预处理模型构建模型训练及保存 2. 语音识别数据预处理模型构建模型训练及保存 3. 模型测试功能选择界面语言识别功能实现界面方言分类功能实现界面 系统测试1. 训…

SpringCloud是SpringBoot 的升级版吗?有什么区别?

目录 一、什么是SpringBoot 二、什么是SpringCloud 三、SpringCloud是SpringBoot 的升级版吗 四、SpringCloud和SpringBoot 有什么区别 一、什么是SpringBoot Spring Boot是一种用于快速构建基于Spring框架的Java应用程序的开发框架。它简化了Spring应用程序的配置和部署过…

超详细图文教程:3DS Max 中创建低多边形游戏长剑模型-下部

推荐&#xff1a; NSDT场景编辑器助你快速搭建可二次开发的3D应用场景 在由两部分组成的教程的第一部分中&#xff0c;我向您展示了如何&#xff1a; 剑柄建模为剑的护手建模剑刃建模 在本教程系列的第二部分中&#xff0c;我将向您展示如何&#xff1a; 打开紫外线包装创建…

【Redis深度专题】「核心技术提升」探究Redis服务启动的过程机制的技术原理和流程分析的指南(基础功能分析)

探究Redis服务启动的过程机制的技术原理和流程分析的指南 Redis基本概念Redis特点说明 Redis源码结构Redis功能架构Redis启动流程初始化全局服务器配置源码分析分析说明initServerConfig方法初始化的内容保存机制的初始化策略优化的初始化策略 指定配置文件加载配置文件默认的数…

【每日运维】RockyLinux8非容器化安装Mysql、Redis、RabitMQ单机环境

系统版本&#xff1a;RockyLinux 8.6 安装方式&#xff1a;非容器化单机部署 安装版本&#xff1a;mysql 8.0.32 redis 6.2.11 rabbitmq 3.11.11 elasticsearch 6.7.1 前置条件&#xff1a;时间同步、关闭selinux、主机名、主机解析host 环境说明&#xff1a;PC电脑VMware Work…

Hadoop生态体系-HDFS

目录标题 1、Apache Hadoop2、HDFS2.1 设计目标&#xff1a;2.2 特性&#xff1a;2.3 架构2.4 注意点2.5 HDFS基本操作2.5.1 shell命令选项2.5.2 shell常用命令介绍 3、HDFS基本原理3.1 NameNode 概述3.2 Datanode概述 1、Apache Hadoop Hadoop&#xff1a;允许使用简单的编程…

webpack require.context

require.context((directory: String),(includeSubdirs: Boolean) /* 可选的&#xff0c;默认值是 true */,(filter: RegExp) /* 可选的&#xff0c;默认值是 /^\.\/.*$/&#xff0c;所有文件 */,(mode: String) /* 可选的&#xff0c; sync | eager | weak | lazy | lazy-onc…