C语言指针详解

目录

指针是什么?

指针和指针类型

指针+-整数 

指针的解引用 

野指针

野指针成因 

如何规避野指针 

 指针运算

指针+- 整数  

指针-指针 

指针的关系运算

指针和数组

二级指针

指针数组

指针数组

 模拟二维数组


指针是什么?

指针理解的2个要点:

1. 指针是内存中一个最小单元的编号,也就是地址

2. 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量

总结:指针就是地址,口语中说的指针通常指的是指针变量。

那我们就可以这样理解:

内存

 

指针变量

我们可以通过&(取地址操作符)取出变量的内存其实地址,把地址可以存放到一个变量中,这个 变量就是指针变量

#include <stdio.h>
int main() 
{
int a = 10;//在内存中开辟一块空间
int *p = &a;
//这里我们对变量a,取出它的地址,可以使用&操作符。
//a变量占用4个字节的空间,这里是将a的4个字节的第一个字节的地址存放在p变量 中,p就是一个之指针变量。
return 0; 
}

int a = 10; //是向内存中的栈区空间申请4个字节的空间,这四个字节用来存放10这个数值

int *p = &a;//这里我们对变量a,取出它的地址,可以使用&操作符。
//a变量占用4个字节的空间,这里是将a的4个字节的第一个字节的地址存放在p变量中,p就是一个指针变量。

 总结:

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

那这里的问题是:

一个小的单元到底是多大?  (1个字节)

如何编址? 经过仔细的计算和权衡我们发现一个字节给一个对应的地址是比较合适的。

对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的时候产生高电平(高电压)和低电平(低电压)就是(1或者0);

那么32根地址线产生的地址就会是:

00000000 00000000 00000000 00000000

00000000 00000000 00000000 00000001

 ...

11111111 11111111 11111111 11111111

这里就有2的32次方个地址。

每个地址标识一个字节,那我们就可以给

(2^32Byte == 2^32/1024KB==2^32/1024/1024MB==2^32/1024/1024/1024GB == 4GB)

4G的空闲进行编址。

同样的方法,那64位机器,如果给64根地址线,那能编址多大空间,自己计算。

这里我们就明白:

在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以一个指针变量的大小就应该是4个字节。

那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地址。

 

如图32位系统下 各个类型的指针都是4个字节 

 

 如图64位系统下 各个类型的指针都是8个字节 

总结:

内存被划分成一个个内存单元,每个内存单元的大小是1个字节

每个字节的内存单元都有一个编号,这个编号就是地址,地址在C语言中是指针

地址要存储的话,存放在指针变量中

每个内存单元都有一个唯一的指针来标识

指针是用来存放地址的,地址是唯一标示一块地址空间的。 

指针的大小在32位平台是4个字节,在64位平台是8个字节。

指针和指针类型

这里我们在讨论一下:指针的类型

我们都知道,变量有不同的类型,整形,浮点型等。

那指针有没有类型呢? 准确的说:有的

当有这样的代码:

int num = 10;
p = &num;

要将&num(num的地址)保存到p中,我们知道p就是一个指针变量,那它的类型是怎样的呢? 我们给指针变量相应的类型。

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

这里可以看到,指针的定义方式是: type + * 

其实:

char* 类型的指针是为了存放 char 类型变量的地址。

short* 类型的指针是为了存放 short 类型变量的地址。

int* 类型的指针是为了存放 int 类型变量的地址。

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

指针+-整数 

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

 

 

总结:指针的类型决定了指针向前和向后走一步有多大

int* 的指针+1,跳过4个字节

char* 的指针+1,跳过1个字节

short* 的指针+1,跳过2个字节

double* 的指针+1,跳过8个字节

指针的解引用 

//演示实例
#include <stdio.h>
int main() {
int n = 0x11223344;
char *pc = (char *)&n;
int *pi = &n;
*pc = 0; //重点在调试的过程中观察内存的变化。 
*pi = 0; //重点在调试的过程中观察内存的变化。
 return 0;
}

 

  

指针类型决定了指针进行解引用操作的时候,访问几个字节

char 1个字节

short 2个字节

int  4个字节

总结:

指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节。

野指针

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

野指针成因 

指针未初始化

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

指针越界访问

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

指针指向的空间释放

这里放在动态内存开辟的时候详细说 

如何规避野指针 

  1. 指针初始化 ——如果明确指针应该指向哪里,就初始化正确的地址

  2.                              int a =10;

  3.                              int*p = &a;

  4.                     ——如果指针不知道初始化什么值,为了安全,初始化NULL

  5.                              int*p = NULL;

  6. 小心指针越界

  7. 指针指向空间释放即使置NULL

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

  9. 指针使用之前检查有效性

初始化指针

在定义指针变量时,立即将其初始化为 NULL 或有效的内存地址,这样可以避免它成为野指针。

避免未初始化的指针

在使用指针之前,确保为其分配了有效的内存空间。

可以使用动态内存分配函数(如 malloc、calloc 等)或者将指针指向有效的对象或数组。

避免释放后仍然使用指针

在释放了指针所指向的内存后,不要再对其进行访问或引用。可以在释放后将指针设置为 NULL,以避免意外使用。

合理使用指针

确保在使用指针之前检查其是否为 NULL。可以使用条件语句(如 if)或者断言来验证指针的有效性。

避免悬挂指针

当一个指针指向已经释放的内存时,将其设置为 NULL,以避免成为悬挂指针。

谨慎使用指针操作

在进行指针操作时,要确保操作的对象是有效的,并且不会越界访问。

#include <stdio.h>
int main()
 {
    int *p = NULL;
    //....
    int a = 10;
    p = &a;
    if(p != NULL)
    {
*p = 20; 
    }
return 0; 
}

 指针运算

指针+- 整数

指针-指针

指针的关系运算

指针+- 整数  

 

//arr --> p

//arr == p

//arr+i == p+i

//*(arr+i) ==*(p+i) == arr[i] 

//*(arr+i)== arr[i] 

//*(i+arr)== i[arr]  ——方块仅仅只是操作符

//p指向的是数组首元素

//p+i是数组中下标为i的元素的地址

//p+i起始时跳过了i*sizeof(int)个字符

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

指针加减整数是指对指针进行偏移操作。当一个指针与一个整数相加或相减时,编译器会根据指针所指向的数据类型来计算偏移量,然后将指针移动到相应位置。

例如,假设有一个指向整型数据的指针ptr,可以使用以下方式进行偏移操作:

ptr + n:将指针ptr向后移动n个单位,每个单位的大小由指针所指向的数据类型决定。

ptr - n:将指针ptr向前移动n个单位。

指针-指针 

指针与指针之间的减法运算用于计算两个指针之间的偏移量。如果有两个指针ptr1和ptr2,它们指向同一数组或同一内存块的不同位置,可以使用以下方式计算它们之间的偏移量:

ptr2 - ptr1:计算ptr2相对于ptr1的偏移量,结果将以单位为指针所指向的数据类型的大小表示。

指针的关系运算包括大于(>)、小于(<)、大于等于(>=)、小于等于(<=)、等于(==)和不等于(!=)等比较运算符。这些运算符用于比较两个指针的值,比较结果基于指针所指向的内存地址。

//指针减去指针的前提:两个指针指向同一块区域,指针类型相同的

// 指针减去指针差值的绝对值:得到的是指针之间的元素个数

模拟strlen

1.计数器

2.递归 

int my_strlen(char *s)
{
       char *p = s;
       while(*p != '\0' ) //八进制的0
       p++;
}
return p-s;

  

指针的关系运算

for(vp = &values[N_VALUES]; vp > &values[0];)
{
   *--vp = 0; 
}

代码简化, 这将代码修改如下:

for(vp = &values[N_VALUES-1]; vp >= &values[0];vp--)
{
   *vp = 0; 
}

实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证 它可行。

标准规定:

允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与 指向第一个元素之前的那个内存位置的指针进行比较。

指针和数组

指针就是指针 (指针变量,存放地址,指针变量的大小是4/8个字节)

数组就是数组 (存放一组数,数组的大小是取决于元素的类型和个数)

数组的数组名是数组首元素的地址,地址是可以访问指针变量中

我们看一个例子:

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

运行结果: 

可见数组名和数组首元素的地址是一样的。

结论:数组名表示的是数组首元素的地址。(2种情况除外,数组章节讲解了)

sizeof(数组名),数组名单独放在sizeof内部,数组名表示整个数组,计算的是数组的大小,单位是字节

&数组名,数组名表示整个数组,取出的是数组的地址,数组的地址和数组首元素的地址,值是一样的,但是类型和意义是不一样的。

那么这样写代码是可行的 

 

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

注:前两个(arr//&arr[0])表示是数组首元素 +1 跳过4个字节

&arr表示数组  +1 跳过40个字节

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

例如:

#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(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的地址。

那我们就可以直接通过指针来访问数组。

如下:

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

二级指针

二级指针是指一个指针变量的值是另一个指针变量的地址,也就是说这个指针变量存储的是另一个指针变量的地址。在C语言中,可以通过使用二级指针来传递指针的地址,以便在函数内部修改指针的值。

 

具体来说,我们先来看一下一级指针的定义和使用。一级指针是最常见的指针,它存储了一个对象的地址。通过解引用操作符(*),我们可以获取到该地址所对应的对象的值。

而二级指针则更进一步,它存储了一个一级指针变量的地址。通过解引用操作符(**),我们可以获取到该一级指针所指向的对象的值。

为了更好地理解二级指针,我们可以举个例子。

假设我们有一个整型变量x,并且有两个指针p1和p2,其中p1指向x的地址,p2指向p1的地址。这样,p2就成为了一个二级指针。

下面是一个简单的示例代码,演示了如何声明和使用二级指针:

#include <stdio.h>

int main() {
    int x = 10;
    int* p1 = &x;
    int** p2 = &p1;

    printf("x = %d\n", x);
    printf("*p1 = %d\n", *p1);
    printf("**p2 = %d\n", **p2);

    return 0;
}

在上面的代码中,我们首先声明了一个整型变量x,并初始化为10。然后声明了一个一级指针p1,并将其指向x的地址。接着声明了一个二级指针p2,并将其指向p1的地址。

在打印输出部分,我们使用解引用操作符()来获取到指针所指向的对象的值。可以看到,通过p1我们可以获取到x的值,通过**p2我们也可以获取到x的值。

二级指针在某些情况下非常有用,例如在函数中传递指针的地址,以便在函数内部修改指针的值。它也可以用于动态分配二维数组等复杂数据结构。

 

*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 arr1[5]; //整型数组
char arr2[6]; //字符数组

 那指针数组是怎样的?

int* arr3[5];//是什么?

arr3是一个数组,有五个元素,每个元素是一个整形指针。

整型指针数组

 

 

 模拟二维数组

 i 遍历指针数组arr // j 遍历数组arr1/2/3 

模拟出二维数组的效果

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

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

相关文章

百度文心一言接入教程-Java版

原文链接 前言 前段时间由于种种原因我的AI BOT网站停运了数天&#xff0c;后来申请了百度的文心一言和阿里的通义千问开放接口&#xff0c;文心一言的接口很快就通过了&#xff0c;但是文心一言至今杳无音讯。文心一言通过审之后&#xff0c;很快将AI BOT的AI能力接入了文心…

【Nodejs】操作mongodb数据库

1.简介 Mongoose是一个让我们可以通过Node来操作MongoDB的模块。Mongoose是一个对象文档模型(ODM)库,它对Node原生的MongoDB模块进行了进一步的优化封装&#xff0c;并提供了更多的功能。在大多数情况下&#xff0c;它被用来把结构化的模式应用到一个MongoDB集合&#xff0c;并…

【C#】async和await 续

前言 在文章《async和await》中&#xff0c;我们观察到了一下客观的规律&#xff0c;但是没有讲到本质&#xff0c;而且还遗留了一个问题: 这篇文章中&#xff0c;我们继续看看这个问题如何解决! 我们再看看之前写的代码&#xff1a; static public void TestWait2() {var t…

【Postman】Postman接口测试进阶用法详解:断言、全局与环境变量、关联、批量执行用例、读取外部文件实现参数化

文章目录 一、Postman断言1、断言位置2、Postman的常用断言3、操作实例 二、全局变量与环境变量1、二者区分2、设置全局变量3、设置环境变量 三、Postman接口关联1、概念2、操作步骤 四、批量执行测试用例1、操作步骤2、查看结果 五、读取外部文件实现参数化1、使用场景2、操作…

【代理模式】了解篇:静态代理 动态代理~

目录 1、什么是代理模式&#xff1f; 2、静态代理 3、动态代理 3.1 JDK动态代理类 3.2 CGLIB动态代理类 4、JDK动态代理和CGLIB动态代理的区别&#xff1f; 1、什么是代理模式&#xff1f; 定义&#xff1a; 代理模式就是为其他对象提供一种代理以控制这个对象的访问。在某…

[VRTK4.0]添加一个Curved Pointer

学习目标&#xff1a; 演示如何将 Tilia曲线指针添加到场景&#xff0c;以及如何使用 OpenXR 指针姿势来确保指针方向始终与 OpenXR 控制器的正确方向匹配 流程&#xff1a; 步骤一&#xff1a; 现在我们需要Tilia包&#xff0c;所以我们转到窗口Tilia包导入器&#xff0c;既…

如何将表格中的状态数据转换为Tag标签显示

考虑到系统前端页面的美观程度&#xff0c;通常通过Tag标签来代替某条数据中的状态信息。仅通过一点操作&#xff0c;便能够使得页面美观程度得到较大提升&#xff0c;前后对比如下所示。代码基于Vue以及Element-ui组件实现。 修改前&#xff1a; 修改后&#xff1a; 修改前…

【图论】LCA(倍增)

一.LCA介绍 LCA通常指的是“最近共同祖先”&#xff08;Lowest Common Ancestor&#xff09;。LCA是一种用于解决树或图结构中两个节点的最低共同祖先的问题的算法。 在树结构中&#xff0c;LCA是指两个节点的最近层级的共同祖先节点。例如&#xff0c;考虑一棵树&#xff0c;…

多态的学习

多态指的是父类引用指向子类对象或者接口引用指向实现类的对象。 格式 父类名称 对象名new 子类名字(); 接口名称 对象名new 实现类名(); 对象的向上转型&#xff0c;一定是安全的。但是无法调用子类或者实现类特有的方法&#xff0c;转型的时候可以理解为子类或者实现类将与…

Jenkins配置自动化构建的几个问题

在创建构建任务时&#xff0c;填写git远程仓库地址时&#xff0c;出现以下报错 解决此报错先排查一下linux机器上的git版本 git --version 如果git 版本过低&#xff0c;可能会导致拉取失败&#xff0c;此时需要下载更高的git版本。 参考 Git安装 第二个解决办法报错信息中…

NICE-SLAM: Neural Implicit Scalable Encoding for SLAM论文阅读

论文信息 标题&#xff1a;NICE-SLAM: Neural Implicit Scalable Encoding for SLAM 作者&#xff1a;Zihan Zhu&#xff0c; Songyou Peng&#xff0c;Viktor Larsson — Zhejiang University 来源&#xff1a;CVPR 代码&#xff1a;https://pengsongyou.github.io/nice-slam…

小黑子—JavaWeb:第四章 Request与Response

JavaWeb入门4.0 1. Request(请求)& Response (响应)2. Request2.1 Request 继承体系2.2 Request 获取请求数据2.2.1 通用方式获取请求参数2.2.2 IDEA模板创建Servlet2.2.3 请求参数中文乱码处理2.2.3 - I POST解决方案2.2.3 - II GET解决方案 2.3 Request 请求转发 3. Resp…

uniapp h5 竖向的swiper内嵌视频实现抖音短视频垂直切换,丝滑切换视频效果,无限数据加载不卡顿

一、项目背景&#xff1a;实现仿抖音短视频全屏视频播放、点赞、评论、上下切换视频、视频播放暂停、分页加载、上拉加载下一页、下拉加载上一页等功能。。。 二、前言&#xff1a;博主一开始一直想实现类似抖音进入页面自动播放当前视频&#xff0c;上下滑动切换之后播放当前…

CAN学习笔记3:STM32 CAN控制器介绍

STM32 CAN控制器 1 概述 STM32 CAN控制器&#xff08;bxCAN&#xff09;&#xff0c;支持CAN 2.0A 和 CAN 2.0B Active版本协议。CAN 2.0A 只能处理标准数据帧且扩展帧的内容会识别错误&#xff0c;而CAN 2.0B Active 可以处理标准数据帧和扩展数据帧。 2 bxCAN 特性 波特率…

24考研数据结构-数组和特殊矩阵

目录 数据结构&#xff1a;数组与特殊矩阵数组数组的特点数组的用途 特殊矩阵对角矩阵上三角矩阵和下三角矩阵稀疏矩阵特殊矩阵的用途 结论 3.4 数组和特殊矩阵3.4.1数组的存储结构3.4.2普通矩阵的存储3.4.3特殊矩阵的存储1. 对称矩阵(方阵)2. 三角矩阵(方阵)3. 三对角矩阵(方阵…

【Go语言】Golang保姆级入门教程 Go初学者介绍chapter1

Golang 开山篇 Golang的学习方向 区块链研发工程师&#xff1a; 去中心化 虚拟货币 金融 Go服务器端、游戏软件工程师 &#xff1a; C C 处理日志 数据打包 文件系统 数据处理 很厉害 处理大并发 Golang分布式、云计算软件工程师&#xff1a;盛大云 cdn 京东 消息推送 分布式文…

汉明距离,两个整数之间的 汉明距离 指的是这两个数字对应二进制位不同的位置的数目。

题记&#xff1a; 两个整数之间的 汉明距离 指的是这两个数字对应二进制位不同的位置的数目。 给你两个整数 x 和 y&#xff0c;计算并返回它们之间的汉明距离。 示例 1&#xff1a; 输入&#xff1a;x 1, y 4 输出&#xff1a;2 解释&#xff1a; 1 (0 0 0 1) 4 (0 1 0 0…

spring5源码篇(13)——spring mvc无xml整合tomcat与父子容器的启动

spring-framework 版本&#xff1a;v5.3.19 文章目录 整合步骤实现原理ServletContainerInitializer与WebApplicationInitializer父容器的启动子容器的启动 相关面试题 整合步骤 试想这么一个场景。只用 spring mvc&#xff08;确切来说是spring-framework&#xff09;&#x…

PostgreSQL 简洁、使用、正排索引与倒排索引、空间搜索、用户与角色

PostgreSQL使用 PostgreSQL 是一个免费的对象-关系数据库服务器(ORDBMS)&#xff0c;在灵活的BSD许可证下发行。PostgreSQL 9.0 &#xff1a;支持64位windows系统&#xff0c;异步流数据复制、Hot Standby&#xff1b;生产环境主流的版本是PostgreSQL 12 BSD协议 与 GPL协议 …

TypeScript -- 类

文章目录 TypeScript -- 类TS -- 类的概念创建一个简单的ts类继承 public / private / protected-- 公共/私有/受保护的public -- 公共private -- 私有的protected -- 受保护的 其他特性readonly -- 只读属性静态属性 -- static修饰ts的getter /setter抽象类abstract TypeScrip…