C语言指针详解

news2024/9/25 19:19:05

目录

指针是什么?

指针和指针类型

指针+-整数 

指针的解引用 

野指针

野指针成因 

如何规避野指针 

 指针运算

指针+- 整数  

指针-指针 

指针的关系运算

指针和数组

二级指针

指针数组

指针数组

 模拟二维数组


指针是什么?

指针理解的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 

模拟出二维数组的效果

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

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

相关文章

51单片机——串行口通信

目录 1、51单片机串口通信介绍 2、串行口相关寄存器 2.1 、串行口控制寄存器SCON和PCON 2.1.1 SCON&#xff1a;串行控制寄存器 (可位寻址) 2.1.2 PCON&#xff1a;电源控制寄存器&#xff08;不可位寻址&#xff09; 2.2、串行口数据缓冲寄存器SBUF 2.3、从机地址控制…

iOS - 解压ipa包中的Assert.car文件

项目在 Archive 打包后&#xff0c;生成ipa包 将 xxx.ipa文件修改为zip后缀即 xxx.zip &#xff0c;然后再双击解压&#xff0c;会生成一个 Payload 文件夹&#xff0c;里面一个文件 如下图&#xff1a; 然后显示改文件的包内容&#xff1a; 解压 Assets.car 文件的方式&…

基于x-scan的渲染算法

基于x-scan算法实现的z-buffer染色&#xff0c;.net core framework 3.1运行。 x-scan算法实现&#xff1a; public List<Vertex3> xscan() {List<Vertex3> results new List<Vertex3>();SurfaceFormula formula getFormula();Box rect getBound();for …

力扣 968. 监控二叉树

题目来源&#xff1a;https://leetcode.cn/problems/binary-tree-cameras/description/ C题解&#xff08;来源代码随想录&#xff09;&#xff1a;节点可以分为3个状态&#xff1a;0无覆盖&#xff1b;1有摄像头&#xff1b;2有覆盖。 要想放的摄像头最少&#xff0c;应当叶子…

无涯教程-jQuery - stop( clearQueue, gotoEnd)方法函数

stop([clearQueue&#xff0c;gotoEnd])方法停止所有指定元素上的所有当前正在运行的动画。 stop( [clearQueue, gotoEnd ]) - 语法 selector.stop( [clearQueue], [gotoEnd] ) ; 这是此方法使用的所有参数的描述- clearQueue - 这是可选的布尔参数。设置为true会清除动画…

绕过TLS/akamai指纹护盾

文章目录 前言TLS指纹什么是TLS指纹测试TLS指纹绕过TLS指纹使用原生urllib使用其他成熟库&#xff01;&#xff01;修改requests底层代码 Akamai指纹相关&#xff08;HTTP/2指纹&#xff09;什么是Akamai指纹测试Akamai指纹绕过Akamai指纹使用其他成熟库 实操参考 前言 有道是…

Eureka 学习笔记3:EurekaHttpClient

版本 awsVersion ‘1.11.277’ EurekaTransport 用于客户端和服务端之间进行通信&#xff0c;封装了以下接口的实现&#xff1a; ClosableResolver 接口实现TransportClientFactory 接口实现EurekaHttpClient 接口实现及其对应的 EurekaHttpClientFactory 接口实现 private …

【雕爷学编程】MicroPython动手做(16)——掌控板之图片图像显示2

知识点&#xff1a;什么是掌控板&#xff1f; 掌控板是一块普及STEAM创客教育、人工智能教育、机器人编程教育的开源智能硬件。它集成ESP-32高性能双核芯片&#xff0c;支持WiFi和蓝牙双模通信&#xff0c;可作为物联网节点&#xff0c;实现物联网应用。同时掌控板上集成了OLED…

第120天:免杀对抗-防朔源防流量防特征CDN节点SSL证书OSS存储上线

知识点 #知识点&#xff1a; 1、CS-CDN节点-防拉黑 2、CS-SSL证书-防特征 3、CS-OSS存储-防流量#章节点&#xff1a; 编译代码面-ShellCode-混淆 编译代码面-编辑执行器-编写 编译代码面-分离加载器-编写 程序文件面-特征码定位-修改 程序文件面-加壳花指令-资源 代码加载面-D…

springCloud Eureka注册中心配置详解

1、创建一个springBoot项目 2、在springBoot项目中添加SpringCloud依赖 <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>2021.0.3</version><type>…

IDEA 使用 maven 搭建 spring mvc

1. 创建项目 1.1 创建成功之后配置 Spring MVC 1.2 勾选 Spring MVC 2.更改配置文件 2.1 更改web.xml配置 更改为 <servlet-mapping><servlet-name>dispatcher</servlet-name><url-pattern>/</url-pattern></servlet-mapping>2.2 dispat…

CF1833 A-E

A题 题目链接&#xff1a;https://codeforces.com/problemset/problem/1833/A 基本思路&#xff1a;for循环遍历字符串s&#xff0c;依次截取字符串s的子串str&#xff0c;并保存到集合中&#xff0c;最后输出集合内元素的数目即可 AC代码&#xff1a; #include <iostrea…

Michael.W基于Foundry精读Openzeppelin第15期——SignedMath.sol

Michael.W基于Foundry精读Openzeppelin第15期——SignedMath.sol 0. 版本0.1 SignedMath.sol 1. 目标合约2. 代码精读2.1 max(int256 a, int256 b) && min(int256 a, int256 b)2.2 average(int256 a, int256 b)2.3 abs(int256 n) 0. 版本 [openzeppelin]&#xff1a;v…

final的使用以及权限修饰符

final表示最终的、不可改变的 final跟abstract不可以同时使用&#xff0c;因为二者是冲突的。final表示不可变&#xff0c;abstrac表示必须要重写、必须要变。 常见的四种用法 修饰一个类 格式&#xff1a; public final class 类名称{ }final修饰之后&#xff0c;这个类不能…

杂谈项——关于我在bw上的见闻,以及个人对二次元游戏行业方面的前瞻

君兮_的个人主页 勤时当勉励 岁月不待人 C/C 游戏开发 Hello,米娜桑们&#xff0c;这里是君兮_&#xff0c;今天为大家带来一点不一样的&#xff0c;首先先光速叠一下甲&#xff1a; 在此说明博主并不是一个什么都知道的大佬&#xff0c;只是一个普通的老二次元以及期望以后能…

大数据-Spark批处理实用广播Broadcast构建一个全局缓存Cache

1、broadcast广播 在Spark中&#xff0c;broadcast是一种优化技术&#xff0c;它可以将一个只读变量缓存到每个节点上&#xff0c;以便在执行任务时使用。这样可以避免在每个任务中重复传输数据。 2、构建缓存 import org.apache.spark.sql.SparkSession import org.apache.s…

Xshell配置ssh免密码登录-公钥与私钥登录linux服务器

目录 简介 提示 方法步骤 步骤1&#xff1a;生成密钥公钥&#xff08;Public key&#xff09;与私钥(Private Key) 方法1&#xff1a;使用xshell工具 方法2&#xff1a;使用命令行 步骤2&#xff1a;放置公钥(Public Key)到服务器 方法1&#xff1a;&#xff08;我使用的是…

LeetCode551.Student-Attendance-Record-i<学生出勤记录 I>

题目&#xff1a; 思路&#xff1a; 遍历就完事了.连续三天不来return false; 超过两次缺勤 fasle; 代码是&#xff1a; //codeclass Solution { public:bool checkRecord(string s) {int n s.length();int abtimes0,latimes0;for(int i0;i<n;i){switch(s[i]){case(A):l…

总结942

5:40起床 6:00~7:00单词复习300个&#xff0c;记100个 7:15~8:00早读&#xff0c;《love is as strong as death》第一第二段 8:10~9:10三大计算回顾 9:15~10:06 习题880第一章基础选择纠错 10:10~10&#xff1a;30单词默写 10:30~11:40强化第一讲习题 11:40~12:30继续…

【雕爷学编程】MicroPython动手做(16)——掌控板之图片图像显示3

知识点&#xff1a;什么是掌控板&#xff1f; 掌控板是一块普及STEAM创客教育、人工智能教育、机器人编程教育的开源智能硬件。它集成ESP-32高性能双核芯片&#xff0c;支持WiFi和蓝牙双模通信&#xff0c;可作为物联网节点&#xff0c;实现物联网应用。同时掌控板上集成了OLED…