C语言:指针(第一天)

C语言:指针(第一天)

预备知识

内存地址

  • 字节:字节是内存的容量单位,英文名byte,一个字节有8位,即1byte=8bits
  • 地址:系统为了便于区分每一个字节而对他们逐一进行的编号,称为内存地址,简称地址。

基地址

  • 单字节数据:对于单字节数据而言,其地址就是其字节编号。
  • 多字节数据:对于多字节数据而言,其地址是其所有字节中编号最小的那个,称为基地址(首地址)。

在这里插入图片描述

取址符

  • 每个变量都是一块内存,都可以通过取址符&获取其地址。
  • 例如:
int a = 100;
printf("整型变量a的地址是:%p\n",&a);
int c = 'x';
printf("字符变量c的地址是:%p\n",&c);// 地址采用12位16进制数表示
  • 注意:
    • 虽然不同变量的尺寸是不同的,但是他们地址的尺寸是一致的。
    • 不同的地址虽然形式上看起来是一样的,但是由于他们代表的内存尺寸和类型都不同,因此它们在逻辑上是严格区分的。

为什么要引入指针

  • 为函数修改实参提供支持。
  • 为动态内存管理提供支持。
  • 为动态数据结构(链表,队列等)提供支持。
  • 为内存访问提供另一种途径。

变量指针与指针变量

指针概述

  • 内存地址:系统为了内存管理的方便,将内存划分为一个个的内存的单元(通常1个字节),并为每一个内存单元进行编号。内存单元的编号称之为该内单元的地址。一般情况下,我们每一个变量都是由多存单元构成的,多以每个变量的内存地址,其实就是这个变量对应的第一个内存单元的地址,也叫基地址/首地址。
  • 变量指针变量地址称之为改变了的指针(本质是地址)。变量地址往往是指变量在内存中的第一个内单元的编号(首地址)。
int a;
&a  --- 变量a的地址,也称为变量a的指针
    
int arr[2];
&arr --- 数组arr的地址,也称为数组arr的指针

指针就是地址,地址就是指针。

  • 指针变量专门存放指针的变量(本质是变量),简单来说,用来存放地址的变量就是指针变量。

  • 指向:指针变量中存放的地址,就说明该指针变量指向了

  • 指针的尺寸

    • 指针的尺寸指的是指针所占内存的字节数。
    • 指针所占内存,取决于地址的长度,而地址的长度则取决于系统的寻址范围,即字长。
    • 结论:指针尺寸只跟系统的字长有关系,跟具体的指针的类型无关。

    小贴士:
    Linux系统中打印地址时,最多显示12个十六进制数,为什么?

    Linux64位操作系统中,一个内存地址占8个字节,一个字节8bit位,所以一个地址88=64bit位,每4个bit可以表示1个十六进制数;64个bit位用十六进制表示最多有16个数值位;
    系统为了寻址方便,默认当前计算机系统没必要寻址64bit为,只寻址了48个bit为,所以用12个十六进制数表示一个地址。

    二进制:01001010 十六进制:0x4A 416+10=74
    注意:==在Limnux64位操作系统中,指针类型的变量占8个字节的内存空间,==在LInux32位操作系统中,指针类型的变量占4个字节的内存空间

  • 在C语言中对内存数据(如变量、数组元素等)的存取有两种方式:

    • 直接存取:

      • 通过基本类型(整型、浮点型、字符型)的变量,访问这个变量代表的内存空间的数据
      • 通过数组元素的引用,访问这个引用代表的内存空间的数据。
      int = 10;// 存
      printf("%d",a);// 取
      
      int arr[] = {11,22,33};// 存
      arr[0] = 66;// 存
      printf("%d",arr[0]);// 取
      
    • 简接存取:

      • 通过指针变量,间接的访问内存中的数据。

      在这里插入图片描述

      • *:读作解引用,是解引用符号。如果*前有数据类型,读作指针
      #include <stdio.h>
      
      int main(int argc, char *argv[])
      {
        // 定义一个普通变量
        int i = 3;
      
        // 定义一个指针变量,并赋值
        int *i_point = &i;// 指针变量的数据类型要和存储的地址变量类型一致
      
        // 访问普通变量(直接访问)
        printf("直接访问-%d\n",i);
      
        // 访问指针(地址访问)%p 访问地址
        printf("地址访问-%p,%p\n",&i,i_point);
      
        // 访问指针变量(简介访问)解引用:通过这个指针变量存储的地址,访问这个地址对应空间的数据
        printf("间接访问-%d\n",*i_point);// 3
      }
      

指针变量的定义

语法:

数据类型 *变量列表;

举例:

int a;// 普通变量,拥有真实的数据存储空间
int *a_,*b_;// 指针变量,无法存储数据,只能存储其他变量的地址

注意:

  1. 虽然定义指针变量*a,是在变量名前加上*,但是实际变量名依然为a,而不是*a

  2. 使用指针变量间接访问内存数据时,指针变量必须要有明确的指向。

  3. 如果想要借助指针变量间接访问指针变量保存的内存地址上的数据,可以使用指针变量前加*来间接访问;

    指针变量前加*,我们称之为对指针变量解引用

    int i = 5, *p;
    p = &i;// 将i的地址赋值给指针变量p
    printf("%x,%p\n",p,p);// 两个都是打印地址,%x打印的地址不带0x,%p打印的地址带0x
    
    printf("%d\n",*p);// 间接访问i的值,也称为解引用p对应地址空间的值
    
    *p = 10;// p是访问地址,*p是访问地址空间对应的数据
    
  4. 指针变量只能指向同类型的变量,借助指针变量访问内存,一次访问的内存大小是取决于指针变量的类型。

  5. 指针变量在定义时可以初始化:这一点和普通变量是一样的。

    int i = 5;
    int *p = &i;// 将i的地址赋值给指针变量p
    printf("%d\n",*p);
    

指针变量的使用

使用

  • 指针变量的赋值

    // 方式一:
    int a,*p;
    p = &a; // 指针变量的值是其他变量的地址
    
    // 方式二:
    int a,*p,*q = &a;
    p = q;
    
  • 操作指针变量的值

    int a,*p,*q = &a;
    p = q;
    
    printf("%p",p);// 此时返回的是变量a的地址空间
    
  • 操作指针变量指向的值

    int a = 6,*q = &a;b = 25;
    *q = 10;
    printf("%d,%d",*q,a);// 10,10
    
    q = &b;
    printf("%D,%d",*q,a);// 25,10
    

两个有关运算符的使用

  • &取地址运算符。&a是变量a的地址。
  • *指针运算符(或称之为“间接访问”运算符,解引用符),*p是指针变量p指向的对象的值。
案例1:

需求:通过指针变量访问整型变量

在这里插入图片描述

代码:

#include <stdio.h>

void main()
{
    int a = 3,b = 4,*pointer_1 = &a,*pointer_2 = &b;
    
    printf("a=%d,b=%d\n",*pointer_1,*pointer_2);
}
案例2

需求:声明a,b两个普通变量,使用间接存取的方式实现数据的交换。

在这里插入图片描述

代码:

/**
 * 需求:声明a,b两个普通变量,使用间接存取的方式实现数据的交换。
 */
#include <stdio.h>

void main()
{
    int a = 3, b = 4, *p_a, *p_b, *p_t;
    printf("%d,%d\n",*p_a,*p_b);// 3,4
    
    // 交换位置
    p_t = p_a;
    p_a = p_b;
    p_b = p_t;
    
    printf("%d,%d\n",*p_a,*p_b);// 4,3
}
案例3

指针变量应用。输入a、b两个整数,按先大后小的顺序输出a和b。

在这里插入图片描述

代码:地址交换

/**
 * 需求:指针变量应用。输入a、b两个整数,按先大后小的顺序输出a和b。
 */
#include <stdio.h>

void main()
{
  int a = 3, b = 5, *p_a = &a, *p_b = &b, *p_t;
  if (a < b)
  {
    p_t = p_a; // 操作指针变量,不会影响到数据本身
    p_a = p_b;
    p_b = p_t;
  }
  printf("按从大到小输出a,b的值:%d > %d\n", *p_a, *p_b);
}

代码:数据交换,不推荐

/**
 *需求:指针变量应用。输入a、b两个整数,按先大后小的顺序输出a和b。
 */
#include <stdio.h>
void main()
{
  int a = 3, b = 5, *p_a = &a, *p_b = &b, *p_t;
  if (a < b)
  {
    *p_t = *p_a; // 操作指针地址指向的内存空间,也就是直接操作变量a
    *p_a = *p_b;
    *p_b = *p_t;
  }
  printf("按从大到小输出a,b的值:%d > %d\n", *p_a, *p_b);
}

指针变量做函数参数

指针变量做函数参数往往传递的是变量的地址(首地址)借助于指针变量间接访问是可以修改实参变量数据的。

案例1

需求:要求函数处理,用指针变量做函数的参数

  • 方式1:交换指向(指向的普通变量的值不变)

    #include <stdio.h>
    
    // 自定义一个函数,实现两个数的比较
    void swap(int *p_a,int *P_b)
    {
        int *P_t;
        // 这种写法只会改变指向,并不会改变地址对应空间的数据
        p_t = p_a;
        p_a = p_b;
        p_b = p_t;
        
        printf("%d > %d\n",*p_a,*p_b);
    }
    
    void main()
    {
        int a = 3, b = 5;
        // int *p_a = &a, *p_b = &b;
        // if(a < b) swap(p_a,p_b);
        if(a < b) 
            swap(&a,&b);
        else 
            printf("%d > %d\n",a,b);
    }
    
  • 方式2:交换数据(指向普通变量的值改变)

    #include <stdio.h>
    
    // 自定义一个函数,实现两个数的比较
    void swap(int *p_a,int *P_b)
    {
        int t;
        // 这种写法,改变的是指针指向地址空间的数据
        t = *p_a;
        *p_a = *p_b;
        *p_b = t;
        
        printf("%d > %d\n",*p_a,*p_b);
    }
    
    void main()
    {
        int a = 3, b = 5;
        // int *p_a = &a, *p_b = &b;
        // if(a < b) swap(p_a,p_b);
        if(a < b) 
            swap(&a,&b);
        else 
            printf("%d > %d\n",a,b);
    }
    

指针变量指向数组

数组元素的指针
  • 数组的指针就是数组中第一个元素的地址,也就是数组的首地址。

  • 数组的元素的指针是指数组元素的首地址。因此,同样可以用指针变量来指向数组或数组元素。

  • 在C语言中,由于数组名代表数组的首地址,因此,数组名实际上也是指针。

    #include <stdio.h>
    
    int main()
    {
      // 定义一个普通数组
      int a[] = {11,22,33}// int a[]可以看作匿名创建了三个连续空间的int变量
          
      // 使用指针变量存储数组的第一个元素的首地址,也就是数组的首地址
      int *p1 = &a[0]; // 数组的首地址
      int *p2 = a;
        
      // 在我们C语言中,由于数组名代表数组的首地址,因此,数组名实际上也就是指针
      printf("%p,%p,%p", p1, p2, a);
      return 0;
    }
    

    注意:虽然我们定义了一个指针变量接收了数组地址,但不能理解为指针变量指向了数组,而应该理解为指向了数组的元素。

指针的运算

指针变量必须要指向数组的某个元素。

序号指针运算说明
1自增:p++、++p、p=p+1 | p+=1让指针变量指向下一个元素
2自减:p–、–p、p-=1让指针变量指向上一个元素
3加1个数:p+1下一个元素的(首)地址
4减1个数:p-1上一个元素的(首)地址
5指针相减:p1-p2p1,p2之间相差几个元素
6指针比较:p1<p2前面的指针小于后面的指针

说明:

① 如果指针变量p已指向数组中的一个元素,则p+1指向同一数组中的 下一个元素,p-1指向同一数组中的上一个元素。即p+1或p-1也表示地址。但要注意的是,虽然指针变量p中存放的是地址,但p+1并不表示该地址加1,而表示在原地址的基础上加了该数据类型所占的字节数d。

② 如果p原来指向a[0],执行++p后p的值改变了,在p的原值基础上加d,这样p就指向数组的下一个元素a[1]。d是数组元素占的字节数。

③ 如果p的初值为&a[0]则p+i 和a+i 就是数组元素a[i]的地址,或者说,它们指向a数组的第 i个元素 。

(p+i) 或(a+i)是p+i或a+i所指向的数组元素,即a[i]。

⑤ 如果指针变量p1和p2都指向同一数组,如执行p2-p1,结果是两个地址之差除以数组元素的长度d。

#include <stdio.h>

int main(int argc, char *argv[])
{
  int arr[] = {11, 22, 33, 44, 55};
  int *p1 = arr + 4;
  int *p2 = arr + 1;
  printf("%ld\n", p1 - p2); // 3
  return 0;
}

在这里插入图片描述

案例1:

通过下标法和指针法遍历数组

#include <stdio.h>

int arr1()
{
  // 定义一个普通数组
  int arr[] = {11,22,33,44,55};

  // 计算数组的大小
  int len = sizeof(arr) / sizeof(arr[0]);

  //创建指针变量
  int *p = arr;

  // 创建循环变量
  register int i = 0;// 将我们的变量存放在寄存器中,提高执行效率

  // 遍历
  for(;i < len;i++)
  {
    printf("[1] %d ",arr[i]);// 下标法,这种写法可读可写
    printf("[2] %d ",*(arr+i));// 指针法,这种写法需要注意,arr+i无法修改数组,只读
    // printf("[3] %d ",*(p+i));// 指针法,这种更为灵活,可读可写,建议
    printf("[3] %d ",*p);// p 获取当前p指向的数组元素的地址
    p++;
    printf("\n");
  }

}
int main(int argc,char *argv[])
{
    arr1();
    return 0;
}

案例2:

需求:推到一下代码的运行结果

#include <stdio.h>

int arr2()
{
  int arr[] = {11,22,33,44,55,66,77,88};

  int *p = arr;// *p取出来的数据是11
  printf("%d\n",*p);// 11

  p++;// 指针+1,元素的值不变,*p取出来的元素是22
  printf("%d\n",*p);// 22

  int x = *p++;// x = 22,p++指向下一个元素空间 → 33,*p = 33;++ 的运算级高于*

  printf("%d,%d\n",x,*p);// 22,33

  int y = *(++p);// y = 44,++p 指向下一个元素空间 = 44,*(++p) = 44
  printf("%d,%d\n",y,*p);// 44,44

  (*p)++;// 先解引用,取出p对应地址空间的值,然后再给其++,45+1=45
  printf("%d\n",*p);
}


int arr1()
{
  // 定义一个普通数组
  int arr[] = {11,22,33,44,55};

  // 计算数组的大小
  int len = sizeof(arr) / sizeof(arr[0]);

  //创建指针变量
  int *p = arr;

  // 创建循环变量
  register int i = 0;// 将我们的变量存放在寄存器中,提高执行效率

  // 遍历
  for(;i < len;i++)
  {
    printf("[1] %d ",arr[i]);// 下标法,这种写法可读可写
    printf("[2] %d ",*(arr+i));// 指针法,这里的只读,指的是arr不能再冲新赋值
    // printf("[3] %d ",*(p+i));// 指针法,这种更为灵活,可读可写,建议,这里的p是可以重新赋值的
    printf("[3] %d ",*p);// p 获取当前p指向的数组元素的地址
    p++;
    printf("\n");
  }

}
int main(int argc,char *argv[])
{
    arr2();
    return 0;
}

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

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

相关文章

vue3如何实现点击回车,自动登录?

vue3如何实现点击回车&#xff0c;自动登录&#xff1f; 场景&#xff1a;登录账号的时候&#xff0c;可能有的人不习惯直接点击登录&#xff0c;而是通过顺手敲个回车键实现登录 解决办法&#xff1a;上代码 //监听回车 function onKeyUp(e) {//console.log(e)if (e.key En…

LNMP和Discuz论坛

文章目录 LNMP和Discuz论坛1 LNMP搭建1.1 编译安装nginx服务1.1.1 编译安装1.1.2 添加到系统服务 1.2 编译安装MySQL服务1.2.1 准备工作1.2.2 编辑配置文件1.2.3 设置路径环境变量1.2.4 数据库初始化1.2.5 添加mysqld系统服务1.2.6 修改mysql的登录密码 1.3 编译安装PHP服务1.3…

目标跟踪算法:SORT、卡尔曼滤波、匈牙利算法

目录 1 目标检测 2 卡尔曼滤波 3《从放弃到精通&#xff01;卡尔曼滤波从理论到实践》视频简单学习笔记 3.1 入门 3.2 进阶 3.2.1 状态空间表达式 3.2.2 高斯分布 3.3 放弃 3.4 精通 4 匈牙利算法 5 《【运筹学】-指派问题&#xff08;匈牙利算法&#xff09;》视…

Linux DNS之进阶篇bind-chroot企业级部署方式

BIND-chroot 服务是利用 chroot 机制为 BIND 服务创建伪根目录以限制其访问范围&#xff0c;增强安全性&#xff0c;但配置与维护相对较为复杂的一种服务机制。 本章我们将部署chroot模式的DNS服务&#xff0c;以增加安全性 案例要求&#xff1a; 此案例域名为xjh.com www 解析…

241206学习日志——[CSDIY] [InternStudio] 大模型训练营 [21].md

CSDIY&#xff1a;这是一个非科班学生的努力之路&#xff0c;从今天开始这个系列会长期更新&#xff0c;&#xff08;最好做到日更&#xff09;&#xff0c;我会慢慢把自己目前对CS的努力逐一上传&#xff0c;帮助那些和我一样有着梦想的玩家取得胜利&#xff01;&#xff01;&…

YOLOv5白皮书-第Y2周:训练自己的数据集

>- **&#x1f368; 本文为[&#x1f517;365天深度学习训练营]中的学习记录博客** >- **&#x1f356; 原作者&#xff1a;[K同学啊]** 本人往期文章可查阅&#xff1a; 深度学习总结 接着上一篇文章 YOLOv5白皮书-第Y1周&#xff1a;调用官方权重进行检测 &#xff0c;…

Java的Mvc整合Swagger的knife4框架

Swagger的介绍 Swagger 是一个规范和完整的框架&#xff0c;用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。使用Swagger&#xff0c;就是把相关的信息存储在它定义的描述文件里面&#xff08;yml或json格式&#xff09;&#xff0c;再通过维护这个描述 文件可以去更…

共筑数字安全防线,2024开源和软件安全沙龙即将启幕

随着数字化转型进程的加快以及开源代码的广泛应用&#xff0c;开源凭借平等、开放、协作、共享的优秀创作模式&#xff0c;逐渐成为推动数字技术创新、加速传统行业转型升级的重要模式。但随着软件供应链日趋复杂多元&#xff0c;使得其安全风险不断加剧&#xff0c;针对软件供…

SAP导出表结构并保存到Excel 源码程序

SAP导出表结构并保存到Excel,方便写代码时复制粘贴 经常做接口,需要copy表结构,找到了这样一个程程,特别有用。 01. 先看结果

LDR6500:音频双C支持,数字与模拟的完美结合

在当今数字化快速发展的时代&#xff0c;音频设备的兼容性和性能成为了用户关注的重点。LDR6500&#xff0c;作为乐得瑞科技精心研发的USB Power Delivery&#xff08;PD&#xff09;协议芯片&#xff0c;凭借其卓越的性能和广泛的应用兼容性&#xff0c;为音频设备领域带来了新…

Linux下mysql环境的搭建

1.mysql的下载 去MySQL官网下载mysql的linux压缩包 MySQL :: Download MySQL Community Server 如果下载慢请到网盘中自行下载 通过网盘分享的文件&#xff1a;mysql-8.0.40-1.el7.x86_64.rpm-bundle.tar 链接: https://pan.baidu.com/s/1vUJ-VuTwer1nLPT-haQCqw?pwd6342 提…

可视化建模以及UML期末复习篇----UML图

这是一篇相对较长的文章&#xff0c;如你们所见&#xff0c;比较详细&#xff0c;全长两万字。我不建议你们一次性看完&#xff0c;直接跳目录找你需要的知识点即可。 --------欢迎各位来到我UML国&#xff01; 一、UML图 总共有如下几种&#xff1a; 用例图&#xff08;Use Ca…

jenkins邮件的配置详解

Jenkins邮件的配置涉及多个步骤和细节,以下是详细的配置指南: 一、前期准备 确定邮件服务:明确Jenkins将要使用的邮件服务,如QQ邮箱、163邮箱、公司邮箱(基于Microsoft 365或Exchange Server)等。获取SMTP配置信息:根据邮件服务类型,获取相应的SMTP服务器地址、端口号…

JaveEE初阶--网络编程套接字

目录 一、引言 二、网络编程基本概念 2.1 什么是网络编程&#xff1f; 2.2 基本知识 三、Socket套接字 3.1 概念 3.2 分类 1.流套接字&#xff1a;使用传输层TCP协议 2. 数据报套接字&#xff1a;使用传输层UDP协议 3.原始套接字&#xff1a;用于自定义传输层协议 四、…

Python爬虫——HTML中Xpath定位

Xpath是一种路径查询语言。利用一个路径表达式从html文档中找到我们需要的数据位置&#xff0c;进而将其写入到本地或者数据库中。 学习Xpath爬虫&#xff0c;我们首先学习一下python中lxml库 关于库 lxml 终端下载Xpath需要用到的模块 pip install lxml 关于HTML 超文本标…

Spring Cloud Alibaba 之 “Sentinel”

从网上下载好sentinel-dashboard-1.6.3.jar&#xff0c;然后执行 java -jar sentinel-dashboard-1.6.3.jar,执行成功之后在浏览器输入localhost:8080&#xff0c;Sentinel的登录名和密码都是sentinel,登陆成功之后看到只有一个首页。 接下来开始整合Spring Cloud Alibaba Sen…

伟测科技再融资11.75亿:增收不增利,毛利率近年来持续下滑

《港湾商业观察》施子夫 王璐 12月9日&#xff0c;上海证券交易所上市审核委员会召开2024年第34次上市审核委员会审议会议&#xff0c;审议上海伟测半导体科技股份有限公司(再融资)&#xff08;以下简称&#xff0c;伟测科技&#xff1b;688372.SH&#xff09;事项。 今年8月…

Postman cURL命令导入导出

你是否曾为在Postman和终端之间切换、整理请求而抓狂&#xff1f;其实&#xff0c;Postman支持与cURL命令的无缝互通&#xff0c;通过导入导出&#xff0c;极大提升效率。用好这个功能&#xff0c;分分钟让接口测试更高效&#xff01; Postman如何快速导入cURL命令&#xff1f;…

MR30分布式 I/O 模块助力 CNC 设备产能飞跃

背景分析 在现代制造业中&#xff0c;CNC 设备扮演着极为关键的角色。然而&#xff0c;CNC 设备在运行过程中也存在着诸多痛点。传统的 CNC 设备往往在控制与通信方面存在局限&#xff0c;其内部的 I/O 系统大多采用集中式架构。这种架构下&#xff0c;一旦需要处理大量的输入输…

《ODIN: A Single Model for 2D and 3D Segmentation》CVPR2024

斯坦福和微软&#xff1a; 代码链接&#xff1a;ODIN: A Single Model For 2D and 3D Perception 论文链接&#xff1a;2401.02416 摘要 这篇论文介绍了ODIN&#xff08;Omni-Dimensional INstance segmentation&#xff09;&#xff0c;一个能够同时处理2D RGB图像和3D点云…