C语言 —— 指针

news2025/2/1 10:09:15

目录

1. 指针是什么?

2. 指针和指针类型的关系

2.1 指针的解引用

2.2 指针+-整数

3. 野指针

3.1 野指针成因

1. 指针未初始化

2. 指针越界访问

3. 指针指向的空间释放

3.2 如何规避野指针

4. 指针运算

4.1 指针+-整数

4.2 指针-指针

指针-指针的使用

4.3 指针的关系运算

5. 指针和数组

6. 二级指针

7. 指针数组


1. 指针是什么?

指针是什么?
指针理解的2个要点:
1. 指针是内存中一个最小单元的编号,也就是地址
2. 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量

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


我们在初识C语言中知道, 内存是一块很大的空间, 这么大的内存空间如何管理呢?

其实我们是把内存切割成一个个的内存单元, 一个内存单元大小是1byte.

对于要点的理解:

  • 第一点

  • 第二点

当写下以下代码时:

int a = 10;//a是整型变量,占用4个字节的内存空间

意味着要在内存中找4个空间存放a.


指针=指针变量, 指针变量中存放地址, 通过这个地址可以找到一个内存单元.

总结:

  • 指针变量是用来存放地址的,地址是唯一标示一块地址空间的。
  • 指针的大小在32位平台是4个字节,在64位平台是8个字节。

注意下面所使用的环境是x86的环境, 也就是32位的环境. (如果改成x64就是64位的环境)

我们来看一下指针的大小.

int main()
{
    char* pc = NULL;
    short* ps = NULL;
    int* pi = NULL;
    double* pd = NULL;

    //sizeof 返回的值的类型是无符号整型  unsigned int
    printf("%zu\n", sizeof(pc));
    printf("%zu\n", sizeof(ps));
    printf("%zu\n", sizeof(pi));
    printf("%zu\n", sizeof(pd));

    return 0;
}

执行结果:

可以看到, 不同类型指针的大小都是4个字节.

既然这样, 不管是创建什么类型的指针, 在32位平台是4个字节,在64位平台是8个字节, 那么为什么还要区分不同类型的指针出来? 而不是直接使用一个统一的指针类型?

C语言并没有使用统一的指针类型, 说明每一种类型的指针都是有意义的, 并不多余.

接下来我们来探讨指针类型的意义.

2. 指针和指针类型的关系

2.1 指针的解引用

我们来看这段代码的效果:

int a = 0x11223344;    
int* pa = &a;
*pa = 0;

通过调试可以看到:

如果把代码改为:

int a = 0x11223344;    
char* pc = (char*)&a;//a原本应该是int*, 而非要赋给char*, 此时进行强转
*pc = 0;

pc是否有能力存下a的地址? 有, pc也是指针变量, 在x86环境下的大小也为4个字节.

我们来调试查看:

所以可以看到, a的地址是可以存到pc中.

但是, 使用的时候会有差异, 即*pc.

注: 上图为重新调试的截图, 所以会和前面的图有地址差异.

可以看到, *pc所修改的内容只有一个字节, 因为pc的类型是char*指针, 所以解引用时只访问了一个字节.

而前面使用int*pc是访问了4个字节.


综上, 我们有以下结论.

指针类型决定了指针在被解引用的时候访问几个字节
如果是int*的指针,解引用访问4个字节
如果是char*的指针,解引用访问1个字节
(还可以推广到其他类型)
这便是指针类型的第一个意义.再来看第二个意义.

2.2 指针+-整数

int main()
{
    int a = 0x11223344;
    int*  pa = &a;
    char* pc = (char*)&a;

    printf("pa = %p\n", pa);
    printf("pa+1 = %p\n", pa+1);

    printf("pc = %p\n", pc);
    printf("pc+1 = %p\n", pc+1);

    return 0;
}

执行结果:

所以我们得出以下结论:

指针的类型决定了指针+-1操作的时候,跳过几个字节, 它决定了指针的步长.

3. 野指针

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

3.1 野指针成因

1. 指针未初始化

int main()
{
    int* p;
    //p没有初始化,就意味着没有明确的指向
    //一个局部变量不初始化的话,放的是随机值:0xcccccccc

    *p = 10;//非法访问内存了,这里的p就是野指针

    return 0;
}

2. 指针越界访问

int main()
{
    int arr[10] = { 0 };
    int* p = arr;//&arr[0]
    int i = 0;
    for (i = 0; i <= 10; i++)
    {
        //当指针指向的范围超出数组arr的范围时,p就是野指针
        *p = i;
        p++;
    }
    return 0;
}

3. 指针指向的空间释放

int* test()
{
    int a = 10;
    return &a;
}

int main()
{
    int*p = test();
    printf("haha\n");
    printf("abcdef\n");

    if (p != NULL)
    {
        printf("%d\n", *p);
    }
    return 0;
}

3.2 如何规避野指针

1. 明确的给指针初始化

2. 小心指针越界

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

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

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

4. 指针运算

  • 指针+- 整数
  • 指针-指针
  • 指针的关系运算

4.1 指针+-整数

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

4.2 指针-指针

指针-指针得到的结果的绝对值是指针和指针之间元素的个数.

int arr[10] = { 0 };
printf("%d\n", &arr[0] - &arr[9]);//9

注意: 不是所有的指针都能相减, 指向同一块空间的2个指针才能相减.

下面写法是错误的:

int arr[10] = { 0 };
char ch[5] = {0};
printf("%d\n", &ch[0] - &arr[5]);//err

指针-指针的使用

使用指针-指针的方式求字符串长度.

显然的, 只需要知道第一个字符的地址和\0的地址就可以知道字符串长度, 两个指针相减即可.
#include <stdio.h>
#include <string.h>

int my_strlen(char* str)
{
    char* start = str;
    while (*str != '\0')
    {
        str++;
    }
    return (str - start);
}

int main()
{
    int len = my_strlen("abcdef");
    printf("%d\n", len);
    return 0;
}

4.3 指针的关系运算

#define N_VALUES 5
float values[N_VALUES];
float *vp;

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

代码简化:

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

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

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

5. 指针和数组

我们知道:

数组:一组相同类型元素的集合
指针变量:是一个变量,存放的是地址
数组名表示的是数组首元素的地址(两种情况除外)
int main()
{
    int arr[10] = {0};
    //arr 是首元素的地址
    //&arr[0]
    int* p = arr;
    //我们可以通过指针来访问数组, 这就是两者的联系

    int sz = sizeof(arr) / sizeof(arr[0]);
    int i = 0;
    for (i = 0; i < sz; i++)
    {
        printf("%d ", *(p + i));
    }

    for (i = 0; i < sz; i++)
    {
        printf("%p ----- %p\n", &arr[i], p + i);
    }

    return 0;
}

6. 二级指针

#include <stdio.h>

int main()
{
    int a = 10;
    int* pa = &a;//pa是一个指针变量,一级指针变量
    //*pa = 20;
    printf("%d\n", a);

    //pa是变量, 它在内存中也会有自己的内存空间, 那么就能&pa.

    int** ppa = &pa;//ppa是一个二级指针变量
    **ppa = 20;

    return 0;
}
一级指针变量就是如果通过pa找a的话, 找一次就可以了.

二级指针变量是用来存放级指针变量的地址的.

7. 指针数组

指针数组是数组, 是存放指针的数组。

我们知道, 创建比较多的相同类型的变量可以使用数组, 那么假设对于这些变量都有对应的指针去存放它们的地址, 是否也能用类似的方式, 去创建一个类型都为指针的数组? 显然是可以的, 也就是说C语言是有这种语法存在的.

int a = 10;
int b = 20;
int c = 30;

int* pa = &a;
int* pb = &b;
int* pc = &c;
int arr[10];

int* parr[10] = {&a, &b, &c};
int i = 0;
for (i = 0; i < 3; i++)//0 1 2
{
    printf("%d ", *(parr[i]));
}

int arr1[4] = { 1,2,3,4 };
int arr2[4] = { 2,3,4,5 };
int arr3[4] = { 3,4,5,6 };

int* parr[3] = {arr1, arr2, arr3};
int i = 0;
for (i = 0; i < 3; i++)
{
    int j = 0;
    for (j = 0; j < 4; j++)
    {
        printf("%d ", parr[i][j]);
    }
    printf("\n");
}

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

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

相关文章

3.7 static关键字

思维导图&#xff1a; 3.7.1 静态属性 ### 3.7 static关键字 --- Java提供了一个称为static的关键字&#xff0c;用于修饰类的成员&#xff0c;如成员变量、成员方法和代码块。使用static修饰的成员拥有特殊性。 --- #### 3.7.1 静态属性 当属性被static关键字修饰时&…

嵌入式实时操作系统的设计与开发(轮询系统学习)

轮询系统具有以下工作特点&#xff1a;系统完成一个轮询的时间取决于循环中需要执行的函数个数。此外&#xff0c;轮询的次序是静态固定的&#xff0c;在运行时不能进行动态调整。 典型系统 许多工业线程网络中&#xff0c;由于需要控制的设备较多、相互距离又较远&#xff0…

手把手教你用Python绘制神经网络图

接下来教大家如何使用 Python 中的 networkx 库&#xff0c;绘制美观且标准的神经网络。会根据指定的层和节点数量&#xff0c;绘制不同结构的神经网络。 networkx 库可以用来创建和操作图类型的数据结构&#xff0c;其中包括无向图、有向图、带权图等等。 神经网络可以看做是一…

字节码之 Lambda 表达式底层原理

文章目录 0.前言0. lambda程序示例1. 编译程序&#xff1a;2. 使用 javap 分析字节码3. 输出字节码4. 分析指令 1. Lambda 表达式的字节码实现1.1 什么是invokedynamic 指令invokedynamic 的工作原理为何 invokedynamic 如此特殊&#xff1f; 1.2 bootstrap method 详解1.1 Lam…

Qt之给控件添加右键菜单

一、设置控件 在对应控件的属性中&#xff0c;将contextMenuPolicy设置为CustomContextMenu。 二、添加槽函数 在对应控件上右键选择槽函数customContextMenuRequested(QPoint)。 三、在槽函数中添加右键菜单 在槽函数中输入如下代码&#xff0c;添加右键菜单。 //右键菜单 …

红帽Linux的安装和部署

目录 一、红帽Linux的安装阶段 1、下载redhat7.9的iso镜像 2、安装阶段 二、红帽Linux的配置阶段 1、第一次进入装机配置 2、进入机器后的一些配置 三、远程连接阶段 1、关闭防火墙 2、使用Xshell远程连接&#xff08;其他连接工具也行&#xff09; 1.开启SSH服务 2.连…

二十、【钢笔工具组】

文章目录 钢笔工具自由钢笔工具弯度钢笔工具 钢笔工具 钢笔工具在photoshop作图中是一款使用频率较高的路径工具,我们可以在窗口选项栏中将路径编辑栏打开&#xff0c;如果我们需要选中使用路径&#xff0c;需要用到后边的路径工具才能去拖动&#xff0c;而选择工具不能拖动&a…

9月大型语言模型研究论文总结

大型语言模型(llm)在今年发展迅速&#xff0c;随着新一代模型不断地被开发&#xff0c;研究人员和工程师了解最新进展变得非常重要。本文总结9-10月期间发布了一些重要的LLM论文。 这些论文涵盖了一系列语言模型的主题&#xff0c;从模型优化和缩放到推理、基准测试和增强性能…

Sigma中的数字增益放大/降低方法

1 是否需要申请加入数字音频系统研究开发交流答疑群(课题组)&#xff1f;加他微信hezkz17, 本群提供音频技术答疑服务

如何快速分析一款产品?

一、何时需要对一个产品进行分析&#xff1f; 首先&#xff0c;当你刚刚融入一个新的产品团队&#xff0c;尤其是当你需要深入了解你将负责的产品时&#xff0c;分析产品就显得尤为重要。这有助于你对产品的全面理解&#xff0c;发现其中的优势和不足&#xff0c;为未来的工作提…

14.5 Socket 应用组播通信

组播通信是一种基于UDP协议的网络通信方式&#xff0c;它允许发送方将消息同时传递给多个接收方。在组播通信中&#xff0c;发送方和接收方都会加入一个共同的组播组&#xff0c;这个组播组对应一个特定的IP地址&#xff0c;所有加入该组播组的主机都能够接收到发送方发送的消息…

C++概述

一、C特色 1.C是面向对象的高级程序设计语言 2.支持数据封装&#xff0c;将数据和对该数据进行操作的函数封装在一个类中&#xff0c;对象就是某一个具体的类。即类是数据封装的工具&#xff0c;对象是数据封装的实现。 3.具有继承性 4.具有函数重载 二、拓展介绍 1.C标准&a…

基于音频SOC开发板的主动降噪ANC算法源码实现

基于音频SOC开发板的主动降噪ANC算法源码实现 是否需要申请加入数字音频系统研究开发交流答疑群(课题组)?可加我微信hezkz17, 本群提供音频技术答疑服务,+群附加赠送降噪开发资料,

IO流:java中解码和编码出现乱码说明及代码实现

IO流&#xff1a;java中解码和编码的代码实现 一、UTF-8和GBK编码方式二、idea和eclipse的默认编码方式三、解码和编码方法四、代码实现编码解码 五、额外知识扩展 一、UTF-8和GBK编码方式 如果采用的是UTF-8的编码方式&#xff0c;那么1个英文字母 占 1个字节&#xff0c;1个…

深度学习-优化算法与梯度下降

文章目录 前置知识指数移动平均EMAL2正则&#xff08;L2 Regularization)权重衰减&#xff08;Weight Decay) 优化器SDGMomentumNAGAdagradRMSPropAdaDeltaAdamAdamW综上 学习率StepLRMultiStepLRExponentialCosineAnealingRLROP&#xff08;ReduceLRonPlateau)lambda总结 前置…

【图像处理】图像配准、图像增强和图像分割研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

网络编程基础知识总结——IP,端口,协议

目录 1. 什么是网络编程&#xff1f; 2. 网络编程的三要素 3. IP 3.1 IP地址的概念 3.2 IP地址的分类 3.3 IPv4解析 3.4 Ipv6解析 4. IPv4 的使用细节 5. 特殊IP地址 4. 端口号 5. 协议 5.1 UDP协议 5.2 TCP协议 1. 什么是网络编程&#xff1f; 总的来说就是一句…

RuntimeWarning: More than 20 figures have been opened

在画图操作结束后使用plt.close(all)语句&#xff0c;但是此时图像仍然不能正常显示&#xff0c;之前是可以正常显示的&#xff0c;然后又在最后的画图的部分添加plt.show()语句&#xff0c;可以正常显示图像并且不再出现警告了。

Android---DVM以及ART对JVM进行优化

Dalvik Dalvik 是 Google 公司自己设计用于 Android 平台的 Java 虚拟机&#xff0c;Android 工程师编写的 Java 或者 Kotlin 代码最终都是在这台虚拟机中被执行的。在 Android 5.0 之前叫作 DVM&#xff0c;5.0 之后改为 ART&#xff08;Android Runtime&#xff09;。在整个…

oracle创建数据库,导入dmp操作全家桶

背景&#xff1a;小明在一家IT公司就职&#xff0c;通过查看项目&#xff0c;公司使用的是oracle&#xff0c;几天后&#xff0c;经理要求他从服务器导入数据库到公司服务器&#xff0c;聪明的小明就开始干了起来&#xff0c;整理如下教程。 说明&#xff1a;此次演示环境oracl…