[C][指针]详细讲解

server/2024/9/24 6:49:09/

目录


0.铺垫

  • 在C中,任何变量&都是从最低地址开始

1.指针是什么?

  • 指针是内存中一个最小单元的编号,也就是地址
  • 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量
  • 指针就是地址,口语中说的指针通常指的是指针变量

2.指针变量

  • 可以通过&(取地址操作符)取出变量在内存中的起始地址,把地址可以存放到一个变量中,这个变量就是指针变量
    int a = 10; // 在内存中开辟一块空间
    int *p = &a; // 取出a的地址,将a的4个字节的第一个字节的地址存放在p变量中,p就是要给指针变量
    
  • 总结
    • 指针变量,用来存放地址的变量,存放在指针中的值都被当成地址处理
    • 指针是用来存放地址的,地址是唯一标示一块地址空间的
    • 指针的大小在32位平台是4个字节,在64位平台是8个字节

3.指针指针类型

  • 指针的定义方式: type + *
  • char*类型的指针是为了存放 char 类型变量的地址
  • short*类型的指针是为了存放 short 类型变量的地址
  • int*类型的指针是为了存放 int 类型变量的地址

4.指针类型的意义

  • 指针类型决定了:指针解引用的权限有多大 -> 能操作几个字节
    • 比如:char*解引用就只能访问一个字节,而int*解引用就能访问四个字节
  • 指针类型决定了:指针走一步,能走多远 -> 步长

5.野指针

  • 概念指针就是指针指向的位置是不可知的 -> 随机的、不正确的、没有明确限制的

1.野指针成因

  • 指针未初始化
    int *p; // 局部变量指针未初始化,默认为随机值
    *p = 20; // error
    
  • 指针越界访问
    int arr[10] = {0};
    int *p = arr;for(int i = 0; i <= 11; i++)
    {*(p++) = i; // 当指针指向的范围超出数组arr的范围时,p就是野指针
    }
    
  • 指针指向的空间释放

2.如何规避野指针

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

6.指针运算

  • 指针±整数
    #define N 5float values[N];
    float *vp = &values[0];// 指针+-整数:指针的关系运算
    while(vp < &values[N])
    {*vp++ = 0;
    }
    
  • 指针-指针
    • 指针指针相减的前提两个指针指向同一块空间
    • 指针相减,代表指针之间所经历的元素的个数
      int MyStrlen(char* s)
      {char* p = s;while(*p != '\0'){p++;}return p - s;
      }
      
  • 指针的关系运算
    for(vp = &values[N]; vp > &values[0];)
    {*--vp = 0;
    }// 获取你想将代码简化成这样
    for(vp = &values[N - 1]; vp >= &values[0]; vp--)
    {*vp = 0;
    }
    
  • 上述第二种代码实际中在绝大部分的编译器上是可以顺利完成任务的,然而还是应该避免这样写,因为标准并不保证它可行
  • 标注规定
    • 允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较
    • 但是不允许与指向第一个元素之前的那个内存位置的指针进行比较

6.指针和数组

  • 数组名表示的是数组首元素的地址
    int arr[10] = {1, 2, 3, 4, 5, 6};
    int *p = arr; // p存放的是数组首元素的地址
    
  • 既然可以把数组名当成地址存放到一个指针中,使用指针来访问一个数组就成为可能
  • 例如
    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);
    }
    
  • (p+i)其实计算的是数组arr下标为i的地址
    int arr[10] = {1, 2, 3, 4, 5, 6};int* p = arr; // 数组名
    printf("%d\n", arr[2]);
    printf("%d\n", p[2]); // p[2] -> *(p + 2)// []是一个操作符,2和arr是两个操作数
    // 类似于 a + b,b + a
    printf("%d\n", 2[arr]);
    printf("%d\n", arr[2]);// arr[2] -> *(arr + 2) -> *(2 + arr) -> 2[arr]
    // arr[2] <-> *(arr + 2) <-> *(p + 2) <-> *(2 + p) <-> *(2 + arr) <-> 2[arr]
    // 2[arr] <-> *(2 + arr)
    

7.二级指针(n级指针

  • 指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里?
    • 二级指针存放一级指针的地址,解引用出来是一级指针里的内容
      • *ppa == pa
      • *pa == a
  • n级指针同理
    int a = 10;
    int* pa = &a; // pa是指针变量,一级指针// ppa是一个二级指针变量
    int** pa = &pa; // pa也是个变量,&pa取出pa在内存中起始地址
    

8.指针数组

  • **指针数组是指针还是数组?  **
    • 数组,是存放指针的数组
      int* arr[5];
      

9.数组指针

  • 例如int (*p)[10]
    • p先和*结合,说明p是一个指针变量,然后指针指向的是一个大小为10个整形的数组
    • 所以p是一个指针,指向一个数组,叫数组指针
    • 注意[]的优先级高于*,所以必须加上()来保证p先和*结合

10.&数组名VS数组名

  • &arrarr,虽然值一样,但是意义不一样
  • &arr表示的是数组的地址,而不是数组首元素的地址,例如:`int arr[10]
    • &arr的类型是:int(*)[10],是一种数组指针类型
    • 数组的地址 + 1,跳过整个数组的大小,所以&arr + 1相遇&arr的差值是40

11.函数指针

  • &函数名 = 函数名

12.函数指针数组

  • 用途:转移表
  • 样例:计算器(Test.c中)

13.回调函数

  • 回调函数:通过函数指针调用的函数
  • 如果把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,就说这是回调函数
  • 回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应
  • 样例:模仿qsort()

http://www.ppmy.cn/server/43042.html

相关文章

gpt-4o考场安排

说明 &#xff1a;经过多次交互&#xff0c;前后花了几个小时&#xff0c;总算完成了基本功能。如果做到按不同层次分配考场&#xff0c;一键出打印结果就完美了。如果不想看中间“艰苦”的过程&#xff0c;请直接跳到“最后结果”及“食用方法”。中间过程还省略了一部分交互&…

【网络】UDP协议

应用层协议是请求与响应服务&#xff0c;客户端的请求与服务器的响应是通过应用层传输到网络中的&#xff0c;但再实际上&#xff0c;应用层并不能直接通信&#xff0c;需要将数据进行报头的封装&#xff0c;向下层交付&#xff0c;贯穿整个协议栈。我们已经谈到应用层协议负责…

IP协议基本概念

IP协议全称为"网际互连协议&#xff08;Internet Protocol&#xff09;"&#xff0c;IP协议是TCP/IP体系中的网络层协议 TCP作为传输层控制协议&#xff0c;其保证的是数据传输的可靠性和传输效率&#xff0c;但TCP提供的仅仅是数据传输的策略&#xff0c;而真正负责…

十四、监控脚本

14.1 Web服务器监控 应用场景&#xff1a;监控web服务器状态&#xff0c;异常时邮件报警。 脚本说明&#xff1a;通过wget&#xff08;也可以用curl&#xff09;监控服务器状态&#xff0c;如果不能正常访问&#xff0c;ping检测网 络&#xff0c;网络正常通知管理员检查服务…

构造函数的用法

c 子类构造函数初始化及父类构造初始化_构造函数对父类进行初始化-CSDN博客

mac docker 安装mysql

在Mac上使用Docker安装MySQL的过程可以分为几个步骤&#xff0c;下面是详细的指南&#xff1a; 步骤1&#xff1a;安装Docker 确保你已经在Mac上安装了Docker。如果还没有安装&#xff0c;你可以访问Docker官网(https://www.docker.com/products/docker-desktop)下载Docker D…

JAVA面试题大全(十三)

1、Mybatis 中 #{}和 ${}的区别是什么&#xff1f; 在 MyBatis 中&#xff0c;#{} 和 ${} 是两种用于参数绑定的方式&#xff0c;它们之间的主要区别在于数据处理的方式和 SQL 注入的风险。 #{}&#xff1a;预编译处理 #{} 用于预编译处理&#xff0c;MyBatis 会为其生成 Prep…

C++实现的单例模式日志类

在实际生产中&#xff0c;日志是非常重要的调试工具&#xff0c;日志内容至少需要包括时间戳、日志级别、日志内容 推荐的日志库有&#xff1a; google/glog: C implementation of the Google logging module (github.com) Apache Log4cxx: Apache Log4cxx 自己实现的话&…