指针详解(从基础到入门)

news2025/2/27 3:02:08

一、什么是指针

在计算机科学中,指针是编程语言中的一个对象,利用地址,它直接指向存在电脑储存器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,指针指向该变量单元。因此,将地址形象化地称为指针。意思是通过它能找到以它为地址的内存单元。从另外一个方面来讲,指针就是一个变量,用来存放内存单元的地址(编码),地址唯一标识一块内存空间。

可通过下面C代码来加以说明:

#include<stdio.h>
int main()
{
    int a=10;//在内存中开辟一块空间
    int *p=&a;//取出a的地址,将a的地址存放在p变量中,p就是一个指针变量
    return 0}

ps:存放在指针中的值都被当做地址来处理,在32位平台上一个指针变量占4个字节,在64位平台上一个指针变量占8给字节

二、指针类型的意义

既然指针变量的大小是固定的,那么为什么还有各种指针类型呢?

1、指针类型决定了指针解进行引用操作时,能够访问空间的大小
下面举例说明:

当指针类型为字符型时,有以下C代码
在这里插入图片描述
通过观察a的地址变化可知,对char类型的指针进行解引用操作并改变其对应的值时,可以改变两个字节内容(只能访问两个字节的空间)。

当指针类型为整形时,有以下C代码
在这里插入图片描述
通过观察a的地址在内存中的变化可知,对int类型的指针进行解引用操作并改变其对应的值时,可以改变四个字节内容(可以访问四个字节的空间)。

总结:指针的类型决定了对指针进行解引用操作时有多大权限。比如,char *的指针解引用就只能访问一个字节,而int *的指针解引用能访问四个字节,double *的指针解引用就能访问八个字节。

2、指针的类型决定了指针加减整数的步长

观察以下C代码及其运行结果:
在这里插入图片描述
我们不难发现,整形指针变量(pa)+1后,地址由C4跳跃到C8,向后跳跃了4个字节(一个整形);字符型指针变量(pc)+1后,地址由C4跳跃到C5,向后跳跃了1个字节(一个字符);类似的,double类型的指针变量+1后,地址应向后跳跃8个字节。

总结:指针类型决定了指针向前或向后走一步有多大(距离)。

可通过以下例题加深我们对其的理解

例题:已知数组int arr[10]={0},利用指针将数组元素全置为1

#include<stdio.h>
int main()
    {
    int arr[10]={0};
    int* p=arr;//数组名表示首元素地址
    int i=0;
    for(i=0;i<10;i++)//将各数组元素全部置为1
    {
        *(p+i)=1;//p为int类型的指针,每加一向后跳跃一个整形(4个字节)
    }
    for(i=0;i<10;i++)//打印输出
    {
        printf("%d ",arr[i]);
    }
    return 0;
    }

若p为char类型的指针,还可通过以下代码进行实现

#include<stdio.h>
int main()
    {
    int arr[10]={0};
    char* p=arr;//数组名表示首元素地址
    int i=0;
    for(i=0;i<10;i++)//将各数组元素全部置为1
    {
        *(p+4*i)=1;//p为char类型的指针,每加一向后跳跃一个字符(1个字节),题设数组元素为整形(4个字节),应该加上4*i
    }
    for(i=0;i<10;i++)//打印输出
    {
        printf("%d ",arr[i]);
    }
    return 0;
    }

三、野指针

**

(一)概念

**

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

**

(二)野指针的成因

**

1、指针为初始化

如以下C代码

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

2、指针越界访问

如以下C代码

#include<stdio.h>
int main()
{
   int arr[10]={ 0 };
   int* p=arr;//数组名代表首元素地址
   int i=0;
   for(i=0;i<12;i++)
   {
       *(p++)=i;
       //数组元素个数为10,当i>9时,指针指向的范围超过了数组arr的范围,此时p就是野指针
   }
   return 0;
}

3、指针指向被释放的空间

如以下C代码

#include<stdio.h>
int* test()
{
    int a = 10;
    //a为局部变量,函数开始时创建,函数结束时销毁
    return &a;
}
int main()
{
    int* p = test();
    *p = 20;
    //此时p指向的空间已销毁(被释放),不属于当前程序,此时指针p就为野指针
    return 0;
}

(三)如何避免野指针的出现

1、指针初始化
2、注意指针是否越界
3、指针指向被释放空间时置为NULL(空指针)
4、指针使用之前应检查其有效性

四、指针与数组

(一)指针与数组

数组名表示首元素地址

在这里插入图片描述
注:以下两种情况例外
1、&数组名(如&arr)
数组名不是首元素地址,数组名(arr)表示整个数组,&数组名(&arr)表示取出整个数组的地址
在这里插入图片描述
arr—>arr+1,向后跳跃了4个字节
&arr—>&arr+1,向后跳跃了40个字节(十六进制A20减9F8得28,换算成十进制为40)

2、sizeof(数组名),如sizeof(arr)
数组名(arr)表示整个数组,sizeof(arr)计算的是整个数组的大小

(二)指针数组

指针数组实质上为数组,与整形数组(存放整形)、字符型数组(存放字符型)类比可知,指针数组用于存放指针。
在这里插入图片描述

(三)数组指针

具体解释见如下代码

在这里插入图片描述

数组指针的用法如下

#include<stdio.h>
void print2(int (*p)[5], int x, int y)
//第一个形参int (*p)[5]为数组指针,存放的是第一行元素构成的一维数组的地址
{
    int i = 0;
    int j = 0;
    for (i = 0; i < x; i++)
    {
        for (j = 0; j < y; j++)
        {
            printf("%d ", *(*(p + i)+j));
            /* 
            p的类型为数组指针,指向为一个一维数组的地址,p+1表示向后跳过一个数组大小,p + i表示向
            后跳过i行,假设二维数组每一行分别代表一个一维数组,则 *(p + i)表示对应的一维数组的数
            组名,而数组名代表该一维数组首元素的地址。该数组为整形数组,+j表示向后跳跃j个整形的大
            小,得到该一维数组各个元素的地址,即*(p + i)+j)表示二维数组中各个元素的地址,再通过解
            引用操作得到各个元素的值,即*(*(p + i)+j)表示二维数组每个元素的值
            */
            
        }
        printf("\n");
    }
}
int main()
{
    int arr[3][5] = { {0,1,2,3,4},{1,2,3,4,5},{2,3,4,5,6} };
    print2(arr, 3, 5);
    //在二维数组中,数组名表示第一行数组的地址(可看成一个一维数组的地址)
    return 0;
}

通过指针数组与数组指针的学习,我们应该掌握下列代码的含义
int arr[5]
(一个五个元素的整形数组)
int *arr[5]
(指针数组,数组包含五个元素,每个元素类型为int *)
int ( * arr)[5]
(数组指针,指向一个有五个元素的数组)
int( * arr[10] )[5]
(是一个数组,数组有十个元素,每个元素为一个数组指针,该数组指针指向的数组有五个元素,每个元素的类型为int)

五、函数指针

顾名思义,函数指针就是指向函数的指针,用来存放函数地址的指针

函数指针的表示方法
在这里插入图片描述
可通过以下代码加以理解

#include<stdio.h>
int Add(int x, int y)
{
    return x + y;
}
int main()
{
    int a = 0;
    int b = 0;
    int (*p)(int, int) = Add;//定义一个函数指针
    printf("%d\n", (*p)(10, 20));//通过函数指针调用函数
    //输出结果为30
    return 0;
}
#include<stdio.h>
void Print(char* str)
{
    printf("%s\n", str);
}
int main()
{
    void (*p)(char*) = Print;//定义一个函数指针
    (*p)("Hello World");//通过函数指针调用函数
    return 0;
}

例:
解释如下代码

//解释以下代码
void(*fun(int, void(*)(int)))(int);
        //fun是一个函数声明
        //fun函数的参数有两个,第一个为int类型,第二个为函数指针类型,该函数指针指向的函数参数类型为int,返回类型为void
        //fun函数的返回类型也是一个指针函数,该指针函数指向的函数的参数为int,返回类型为void(空)
//上述代码可通过typedef关键字进行简化,简化代码如下:
    typedef void(*fun_t)(int);//将该函数指针类型创建别名fun_t
    fun_t(fun(int, fun_t));

六、函数指针数组

函数指针数组实质上为一个数组,数组的每个元素为一个函数的地址

#include<stdio.h>
int Add(int x, int y)
{
    return x + y;
}
int Sub(int x, int y)
{
    return x - y;
}
int Mul(int x, int y)
{
    return x * y;
}
int Div(int x, int y)
{
    return x / y;
}
int main()
{
    int(*p[4])(int, int) = { Add,Sub,Mul,Div };//定义一个函数指针数组
    int i = 0;
    for (i = 0; i < 4; i++)
    {
        printf("%d\n", p[i](10, 5));//循环逐个调用函数
    }
    return 0;
}

在这里插入图片描述

函数指针数组用途:转移表
例:计算器的实现

#include<stdio.h>
void menu()
{
    printf("**********************\n");
    printf("******   1.Add  ******\n");
    printf("******   2.Sub  ******\n");
    printf("******   3.Mul  ******\n");
    printf("******   4.Div  ******\n");
    printf("******   0.exit ******\n");
    printf("**********************\n");
}
int Add(int x, int y)
{
    return x + y;
}
int Sub(int x, int y)
{
    return x - y;
}
int Mul(int x, int y)
{
    return x * y;
}
int Div(int x, int y)
{
    return x / y;
}
int main()
{
    int input = 0;
    int x = 0;
    int y = 0;
    int (*pfarr[])(int, int) = {0, Add,Sub,Mul,Div };
    //pfarr为一个函数指针数组—转移表,通过函数指针数组下标转到对应的函数
    do
    {
        printf("请输入你的选择:\n");
        scanf_s("%d", &input);
        if (input >= 1 && input <= 4)
        {
            printf("请输入两个数:\n");
            scanf_s("%d %d", &x,&y);
            printf("%d\n", pfarr[input](x, y));
        }
        else if (input == 0)
        {
            printf("退出!\n");
        }
        else
        {
            printf("输入错误,请重新输入\n");
        }
    } while (input);
    return 0;
}

七、函数指针数组指针

【函数指针数组】指针,为指针,指向一个函数指针数组

    int (*pf)(int, int);//函数指针
    int(*pfarr[5])(int, int);//函数指针数组
    int(*(*pparr)[5])(int, int)=&pfarr;//函数指针数组指针,指向【函数指针数组】的指针
    //所指向的函数指针数组有5个元素,每个元素为一个函数指针,类型为int(*)(int,int)

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

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

相关文章

如何使用Hexo搭建个人博客

文章目录 如何使用Hexo搭建个人博客环境搭建连接 Github创建 Github Pages 仓库本地安装 Hexo 博客程序安装 HexoHexo 初始化和本地预览 部署 Hexo 到 GitHub Pages开始使用发布文章网站设置更换主题常用命令 插件安装解决成功上传github但是web不更新不想上传文章处理方式链接…

python处理geojson为本地shp文件

一.成果展示 二.环境 我是在Anaconda下的jupyter notebook完成代码的编写&#xff0c;下面是我对应的版本号&#xff0c;我建议大家在这个环境下编写&#xff0c;因为在下载gdal等包的时候会更方便。 二.参考网站 osgeo.osr module — GDAL documentation osgeo.ogr module …

Codesys的Memory存储区数据定义

以上为例&#xff1a; 假定&#xff0c;数据整个大小为131072个字节 即存在 MB0-MB131071个地址 MW0-65534个地址 MD0-32766个地址 每一个MD2个MW4个MB 即MD0MW0-MW1MB0-MB1-MB2-MB3。 即MD75MW150-MW151MB300-MB301-MB302-MB303。 对以上地址赋值时&#xff0c;会同时…

SQL中常见的DDL操作及示例,数据库操作及表操作

目录 一、数据库操作 1、创建数据库 2、查看所有数据库 3、使用数据库 4、删除数据库 二、表操作&#xff1a; 1、创建表 2、查看表结构 3、修改表结构 3.1 添加列 3.2 修改列数据类型 3.3 修改列名 3.4 删除列 3.5 修改表名 3.6 删除表 注意&#xff1a; 在数…

2.15SRAM,DRAM,ROM,主存与CPU连接

MDR取出数据&#xff0c;然后通过数据总线传递给CPU&#xff0c; 地址总线传递信号给到MAR 就是说片选线是用来选择操作哪个芯片 8*8位 第一个代表存储单元的数量&#xff0c;第二个代表存储字长 有几个存储单元&#xff0c;就对应多少位地址以及多少的地址线 存储字长决定数…

【兔子机器人】修改GO电机id(软件方法、硬件方法)

一、硬件方法 利用上位机直接修改GO电机的id号&#xff1a; 打开调试助手&#xff0c;点击“调试”&#xff0c;查询电机&#xff0c;修改id号&#xff0c;即可。 但先将四个GO电机连接线拔掉&#xff0c;不然会将连接的电机一并修改。 利用24V电源给GO电机供电。 二、软件方…

Swift 入门学习:集合(Collection)类型趣谈-上

概览 集合的概念在任何编程语言中都占有重要的位置&#xff0c;正所谓&#xff1a;“古来聚散地&#xff0c;宿昔长荆棘&#xff1b;游人聚散中&#xff0c;一片湖光里”。把那一片片、一瓣瓣、一粒粒“可耐”的小精灵全部收拢、吸纳的井然有序、条条有理&#xff0c;怎能不让…

政安晨:【深度学习处理实践】(四)—— 实施一个温度预测示例

在开始使用像黑盒子一样的深度学习模型解决温度预测问题之前&#xff0c;我们先尝试一种基于常识的简单方法。 它可以作为一种合理性检查&#xff0c;还可以建立一个基准&#xff0c;更高级的机器学习模型需要超越这个基准才能证明其有效性。对于一个尚没有已知解决方案的新问…

从零开始:神经网络(1)——神经元和梯度下降

声明&#xff1a;本文章是根据网上资料&#xff0c;加上自己整理和理解而成&#xff0c;仅为记录自己学习的点点滴滴。可能有错误&#xff0c;欢迎大家指正。 一. 神经网络 1. 神经网络的发展 先了解一下神经网络发展的历程。从单层神经网络&#xff08;感知器&#xff09;开…

【Java网络编程】TCP核心特性(下)

1. 拥塞控制 拥塞控制&#xff1a;是基于滑动窗口机制下的一大特性&#xff0c;与流量控制类似都是用来限制发送方的传送速率的 区别就在于&#xff1a;"流量控制"是从接收方的角度出发&#xff0c;根据接收方剩余接收缓冲区大小来动态调整发送窗口的&#xff1b;而…

【编译原理】1、python 实现一个 JSON parser:lex 词法分析、parser 句法分析

文章目录 一、实现 JSON lexer&#xff08;词法解析器&#xff09;二、lex 词法分析2.1 lex string 解析2.2 lex number 解析2.3 lex bool 和 null 解析 三、syntax parser 句法分析3.1 parse array 解析数组3.2 parse object 解析对象 四、封装接口 一、实现 JSON lexer&#…

论文阅读:Diffusion Model-Based Image Editing: A Survey

Diffusion Model-Based Image Editing: A Survey 论文链接 GitHub仓库 摘要 这篇文章是一篇基于扩散模型&#xff08;Diffusion Model&#xff09;的图片编辑&#xff08;image editing&#xff09;方法综述。作者从多个方面对当前的方法进行分类和分析&#xff0c;包括学习…

时间感知自适应RAG(TA-ARE)

原文地址&#xff1a;Time-Aware Adaptive RAG (TA-ARE) 2024 年 3 月 1 日 介绍 随着大型语言模型&#xff08;LLM&#xff09;的出现&#xff0c;出现了新兴能力的概念。前提或假设是LLMs具有隐藏的和未知的能力&#xff0c;等待被发现。企业家们渴望在LLMs中发现一些无人知晓…

LLM实施的五个阶段

原文地址&#xff1a;Five Stages Of LLM Implementation 大型语言模型显着提高了对话式人工智能系统的能力&#xff0c;实现了更自然和上下文感知的交互。这导致各个行业越来越多地采用人工智能驱动的聊天机器人和虚拟助手。 2024 年 2 月 20 日 介绍 从LLMs的市场采用情况可以…

Day26:安全开发-PHP应用模版引用Smarty渲染MVC模型数据联动RCE安全

目录 新闻列表 自写模版引用 Smarty模版引用 代码RCE安全测试 思维导图 PHP知识点&#xff1a; 功能&#xff1a;新闻列表&#xff0c;会员中心&#xff0c;资源下载&#xff0c;留言版&#xff0c;后台模块&#xff0c;模版引用&#xff0c;框架开发等 技术&#xff1a;输…

超分辨率(1)--基于GAN网络实现图像超分辨率重建

目录 一.项目介绍 二.项目流程详解 2.1.数据加载与配置 2.2.构建生成网络 2.3.构建判别网络 2.4.VGG特征提取网络 2.5.损失函数 三.完整代码 四.数据集 五.测试网络 一.项目介绍 超分辨率&#xff08;Super-Resolution&#xff09;&#xff0c;简称超分&#xff08…

React组件(函数式组件,类式组件)

函数式组件 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>React Demo</title> <!-- 引…

嵌入式Linux串口和 poll() 函数的使用

一、poll() 函数的介绍 poll() 函数用于监控多个文件描述符的变化的函数。它可以用来检查一个或多个文件描述符的状态是否改变&#xff0c;比如是否可读、可写或有错误发生。它常用于处理 I/O 多路复用&#xff0c;这在需要同时处理多个网络连接或文件操作时非常有用。 头文件…

ZJUBCA研报分享 | 《BTC/USDT周内效应研究》

ZJUBCA研报分享 引言 2023 年 11 月 — 2024 年初&#xff0c;浙大链协顺利举办为期 6 周的浙大链协加密创投训练营 &#xff08;ZJUBCA Community Crypto VC Course&#xff09;。在本次训练营中&#xff0c;我们组织了投研比赛&#xff0c;鼓励学员分析感兴趣的 Web3 前沿话题…

【杂记】IDEA和Eclipse如何查看GC日志

1.Eclipse查看GC日志 1.1 右击代码编辑区 -> Run As -> Run Configurations 1.2 点击Arguments栏 -> VM arguments:区域填写XX参数 -> Run 1.3 控制台输出GC详细日志 2.IDEA查看GC日志 2.1 鼠标右击代码编辑器空白区域&#xff0c;选择Edit 项目名.main()... 2.…