【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !

LuckiBit

目录

  • C语言指针精讲
    • 1. 什么是指针?
      • 1.1 指针的内存模型
        • 1.1.1 指针演示
        • 输出
      • 1.2 指针运算
        • 1.2.1 指针算术运算
        • 输出
        • 1.2.2 指针与数组的关系
        • 输出
      • 1.3 指针类型
        • 1.3.1 不同类型的指针
        • 示例
        • 输出
        • 1.3.2 void 指针
        • 输出
      • 1.4 指针与内存管理
        • 动态内存分配
        • 输出
      • 1.5 指针与内存泄漏
        • 1.5.1 内存泄漏示例
        • 1.5.2 解决内存泄漏
      • 1.6 指针的常见错误与调试
        • 1.6.1 常见错误示例
        • 1.6.2 调试工具
    • 2. 指针的声明和初始化
      • 2.1 声明指针
      • 2.2 初始化指针
    • 3. 使用指针访问数据
      • 输出
    • 4. 指针的运算
      • 输出
    • 5. 指针与数组
      • 输出
    • 6. 指针数组和数组指针
      • 6.1 指针数组
      • 6.2 数组指针
    • 7. 函数指针
      • 输出
    • 8. 动态内存分配
      • 输出
    • 9. 指针的类型转换
    • 10. 指针的常见错误
      • 10.1 使用未初始化的指针
      • 10.2 解引用空指针(NULL)
      • 10.3 内存泄漏
      • 10.4 访问越界的内存
    • 11. 实例:交换两个变量的值
      • 输出
    • 12. 指针与结构体
      • 12.1 声明和使用结构体指针
      • 输出
      • 12.2 动态分配结构体内存
      • 输出
    • 13. 指针与函数
      • 13.1 使用指针作为函数参数
      • 输出
      • 13.2 使用指针返回多个值
      • 输出
    • 14. 二级指针
      • 14.1 声明和使用二级指针
      • 输出
      • 14.2 动态分配二维数组
      • 输出
    • 15. 指针与位操作
      • 15.1 位操作基础
      • 输出
      • 15.2 使用指针进行位操作
      • 输出
    • 16. 表格总结
    • 17. 结束语

C语言指针精讲

指针是C语言中一个非常重要和强大的概念。它允许直接操作内存,从而可以高效地处理数据和进行系统编程。下面是C语言中指针的详细讲解:

1. 什么是指针?

指针是一个变量,它存储另一个变量的内存地址。换句话说,指针“指向”存储在内存中的某个数据。
下面将从底层内存模型、指针运算、指针类型以及指针与内存管理的关系等方面进行深入探讨。

1.1 指针的内存模型

指针的核心是直接操作内存地址。每个变量在内存中都有一个地址,指针变量存储的就是这个地址。

1.1.1 指针演示

在这里插入图片描述

图1. 指针的解引用图解

// C程序,演示指针的使用
#include <stdio.h>// 函数定义
void geeks()
{int var = 10;  // 定义一个整数变量并赋值为10// 声明一个指针变量int* ptr;// 注意指针变量ptr和变量var的数据类型必须相同ptr = &var;  // 将变量var的地址赋值给指针ptr// 输出指针ptr的地址printf("指针ptr的值 = %p \n", ptr);// 输出变量var的值printf("变量var的值 = %d \n", var);// 输出指针ptr指向的值(指针的解引用)printf("指针*ptr指向的值 = %d \n", *ptr);
}// 主程序
int main()
{geeks();  // 调用geeks函数return 0; // 返回0,表示程序正常结束
}
输出
ptr 处的值 = 0x7ffca84068dc 
var 处的值 = 10 
*ptr 处的值 = 10

1.2 指针运算

指针不仅可以存储地址,还可以进行算术运算,这在数组和动态内存管理中非常有用。

1.2.1 指针算术运算
int arr[] = {1, 2, 3, 4, 5};
int *p = arr;printf("First element: %d\n", *p);       // 输出第一个元素
printf("Second element: %d\n", *(p + 1)); // 输出第二个元素
输出
First element: 1
Second element: 2
1.2.2 指针与数组的关系

数组名在表达式中实际上是一个指向第一个元素的指针。

int arr[] = {10, 20, 30};
int *p = arr;for (int i = 0; i < 3; i++) {printf("%d ", *(p + i));
}
printf("\n");
输出
10 20 30

1.3 指针类型

指针的类型决定了它解引用时读取的数据类型。

1.3.1 不同类型的指针

常见的指针类型包括:

  • 整数指针:int *
  • 字符指针:char *
  • 浮点数指针:float *
  • 双精度指针:double *

不同类型的指针之间不能互相赋值,除非通过强制类型转换。

Copy code
int a = 10;
float b = 3.14;
int *p1 = &a;
float *p2 = &b;p1 = (int *)p2; // 强制类型转换
示例
int a = 5;
float b = 5.5;
int *pInt = &a;
float *pFloat = &b;printf("Value of a: %d\n", *pInt);
printf("Value of b: %.1f\n", *pFloat);
输出
Value of a: 5
Value of b: 5.5
1.3.2 void 指针

void指针是一种特殊的指针类型,可以指向任何类型的数据,但不能直接解引用。

int a = 10;
void *pVoid = &a;
printf("Value of a through void pointer: %d\n", *(int *)pVoid);  // 需要类型转换
输出
Value of a through void pointer: 10

1.4 指针与内存管理

指针在内存管理中扮演着重要角色,特别是在动态内存分配方面。

动态内存分配
int *p = (int *)malloc(sizeof(int) * 5);
if (p != NULL) {for (int i = 0; i < 5; i++) {p[i] = i * 2;printf("%d ", p[i]);}free(p);printf("\n");
}
输出
0 2 4 6 8

1.5 指针与内存泄漏

内存泄漏是指程序在运行过程中动态分配的内存没有被正确释放,从而导致内存资源的浪费甚至程序崩溃。使用指针时,必须注意及时释放动态分配的内存。

1.5.1 内存泄漏示例
void memoryLeakExample() {int *p = (int *)malloc(sizeof(int) * 10);// 忘记调用free(p); 导致内存泄漏
}
1.5.2 解决内存泄漏
void correctMemoryManagement() {int *p = (int *)malloc(sizeof(int) * 10);if (p != NULL) {// 使用p...free(p);  // 正确释放内存}
}

1.6 指针的常见错误与调试

使用指针时,常见错误包括解引用空指针、使用未初始化的指针、内存越界等。调试这些错误需要细致的检查和使用调试工具。

1.6.1 常见错误示例
int *p;  // 未初始化的指针
*p = 10; // 未定义行为,可能导致程序崩溃int *q = NULL; 
*q = 10; // 解引用空指针,可能导致程序崩溃
1.6.2 调试工具

使用工具如gdb可以帮助发现和调试指针相关的错误。例如,设置断点并逐步执行代码,检查指针的值和指向的内存内容。

2. 指针的声明和初始化

2.1 声明指针

声明指针时,需要指定指针将要指向的数据类型。例如:

int *p;  // 声明一个指向int类型的指针变量p

2.2 初始化指针

初始化指针时,可以将其设置为一个有效的内存地址。例如:

int a = 10;
int *p = &a;  // p指向变量a的地址

3. 使用指针访问数据

通过指针访问和修改指向的数据,可以使用解引用操作符(*)。例如:

int a = 10;
int *p = &a;printf("a = %d\n", *p);  // 输出a的值,即10*p = 20;  // 修改p指向的变量的值
printf("a = %d\n", a);  // 输出修改后的a的值,即20

输出

a = 10
a = 20

4. 指针的运算

指针可以进行一些算术运算,如加法、减法等。这些运算通常用于数组遍历。

int arr[] = {1, 2, 3, 4, 5};
int *p = arr;for (int i = 0; i < 5; i++) {printf("%d ", *(p + i));  // 输出数组元素
}

输出

1 2 3 4 5

5. 指针与数组

数组名本身就是一个指针,指向数组的第一个元素。例如:

int arr[] = {1, 2, 3};
int *p = arr;printf("%d\n", *(p + 1));  // 输出第二个元素,即2

输出

2

6. 指针数组和数组指针

6.1 指针数组

指针数组:数组的每个元素都是一个指针。

int *p[3];

6.2 数组指针

数组指针:指向数组的指针。

int (*p)[3];

7. 函数指针

函数指针是指向函数的指针,允许通过指针调用函数。

void func() {printf("Hello, World!\n");
}void (*pFunc)() = func;  // 声明并初始化函数指针
pFunc();  // 通过指针调用函数

输出

Hello, World!

8. 动态内存分配

使用指针进行动态内存分配可以更加灵活地管理内存。常用的函数有malloccallocfree

int *p = (int *)malloc(sizeof(int) * 5);  // 分配内存
if (p != NULL) {for (int i = 0; i < 5; i++) {p[i] = i * 2;}for (int i = 0; i < 5; i++) {printf("%d ", p[i]);}free(p);  // 释放内存
}

输出

0 2 4 6 8

9. 指针的类型转换

指针可以进行类型转换,但需要谨慎使用,以避免不安全的操作。

void *p = malloc(10);
int *intP = (int *)p;  // 将void指针转换为int指针

10. 指针的常见错误

10.1 使用未初始化的指针

int *p;  // p未初始化
*p = 10; // 未定义行为,可能导致程序崩溃

解释和原理:
未初始化的指针没有指向有效的内存地址,因此对它进行解引用操作会导致未定义行为,可能引发程序崩溃或其他错误。

10.2 解引用空指针(NULL)

int *p = NULL;
*p = 10;  // 未定义行为,可能导致程序崩溃

解释和原理:
空指针(NULL)表示指针不指向任何有效的内存地址。对NULL指针进行解引用操作会导致未定义行为,通常会引发程序崩溃。

10.3 内存泄漏

int *p = (int *)malloc(sizeof(int) * 5);
// 忘记调用free(p); 释放内存

解释和原理:
动态分配的内存在不再需要时必须释放。如果忘记释放,会导致内存泄漏,长时间运行的程序可能耗尽内存资源,导致系统性能下降或崩溃。

10.4 访问越界的内存

int arr[5];
int *p = arr;
p[5] = 10;  // 越界访问,未定义行为

解释和原理:
访问数组越界的内存会导致未定义行为,可能覆盖其他重要数据或导致程序崩溃。编译器无法检测所有的越界访问,必须在编写代码时注意避免。

11. 实例:交换两个变量的值

void swap(int *a, int *b) {int temp = *a;*a = *b;*b = temp;
}int main() {int x = 10, y = 20;swap(&x, &y);printf("x = %d, y = %d\n", x, y);  // 输出x=20, y=10return 0;
}

输出

x = 20, y = 10

好的,下面是修改和优化后的内容:

12. 指针与结构体

在C语言中,指针和结构体的结合可以实现更加复杂的数据结构和操作。

12.1 声明和使用结构体指针

struct Person {char name[50];int age;
};struct Person person1 = {"Alice", 30};
struct Person *pPerson = &person1;printf("Name: %s, Age: %d\n", pPerson->name, pPerson->age);  // 使用箭头操作符访问成员

输出

Name: Alice, Age: 30

12.2 动态分配结构体内存

struct Person *pPerson = (struct Person *)malloc(sizeof(struct Person));
if (pPerson != NULL) {strcpy(pPerson->name, "Bob");pPerson->age = 25;printf("Name: %s, Age: %d\n", pPerson->name, pPerson->age);  // 输出动态分配的结构体数据free(pPerson);  // 释放动态分配的内存
}

输出

Name: Bob, Age: 25

13. 指针与函数

指针与函数结合使用,可以实现函数参数的传递和返回更为复杂的数据类型。

13.1 使用指针作为函数参数

void increment(int *p) {(*p)++;
}int main() {int value = 10;increment(&value);printf("Value: %d\n", value);  // 输出经过增量操作后的值return 0;
}

输出

Value: 11

13.2 使用指针返回多个值

void getMinMax(int *arr, int size, int *min, int *max) {*min = *max = arr[0];for (int i = 1; i < size; i++) {if (arr[i] < *min) *min = arr[i];if (arr[i] > *max) *max = arr[i];}
}int main() {int arr[] = {3, 5, 1, 9, 2};int min, max;getMinMax(arr, 5, &min, &max);printf("Min: %d, Max: %d\n", min, max);  // 输出数组中的最小值和最大值return 0;
}

输出

Min: 1, Max: 9

14. 二级指针

二级指针是指向指针的指针,常用于动态分配二维数组或处理指针数组。

14.1 声明和使用二级指针

int a = 10;
int *p = &a;
int **pp = &p;printf("Value of a: %d\n", **pp);  // 使用二级指针访问a的值

输出

Value of a: 10

14.2 动态分配二维数组

int rows = 3, cols = 4;
int **matrix = (int **)malloc(rows * sizeof(int *));
for (int i = 0; i < rows; i++) {matrix[i] = (int *)malloc(cols * sizeof(int));
}// 初始化并打印二维数组
for (int i = 0; i < rows; i++) {for (int j = 0; j < cols; j++) {matrix[i][j] = i * cols + j;printf("%2d ", matrix[i][j]);}printf("\n");
}// 释放二维数组的内存
for (int i = 0; i < rows; i++) {free(matrix[i]);
}
free(matrix);

输出

 0  1  2  34  5  6  78  9 10 11

15. 指针与位操作

指针与位操作结合使用,可以更高效地处理低层数据操作,尤其在嵌入式系统中。

15.1 位操作基础

unsigned char a = 0b10101010;
unsigned char b = 0b11001100;
unsigned char c = a & b;  // 按位与操作printf("Result: %02X\n", c);  // 输出结果

输出

Result: 88

15.2 使用指针进行位操作

void setBit(unsigned char *byte, int bit) {*byte |= (1 << bit);  // 设置指定位
}int main() {unsigned char value = 0x00;setBit(&value, 3);printf("Value: %02X\n", value);  // 输出设置指定位后的值return 0;
}

输出

Value: 08

16. 表格总结

概念描述
指针声明int *p; 声明一个指向int类型的指针变量p
指针初始化int *p = &a; 将指针p初始化为变量a的地址
指针解引用*p 访问指针p指向的变量的值
指针运算*(p + i) 访问指针p偏移i个位置后的值
指针数组int *p[3]; 声明一个指针数组,每个元素都是一个指针
数组指针int (*p)[3]; 声明一个数组指针,指向一个包含3个int类型元素的数组
函数指针void (*pFunc)(); 声明一个指向函数的指针
动态内存分配int *p = (int *)malloc(sizeof(int) * 5); 使用malloc分配内存
指针类型转换int *intP = (int *)p; 将void指针转换为int指针
指针常见错误未初始化指针、解引用空指针、内存泄漏、访问越界内存
交换两个变量的值使用指针参数进行值交换 void swap(int *a, int *b);

17. 结束语

  1. 本节内容已经全部介绍完毕,希望通过这篇文章,大家对C语言中的指针有了更深入的理解和认识。
  2. 感谢各位的阅读和支持,如果觉得这篇文章对你有帮助,请不要吝惜你的点赞和评论,这对我们非常重要。再次感谢大家的关注和支持!LuckiBit

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

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

相关文章

vue 两个页面切换, 再回到当前页,还是离开前的数据

1、要保证页面的name 和 建路由的大小写一致 2、页面不用生命周期--activated 调接口刷新

在 Kubernetes 中设置 Pod 优先级及其调度策略详解

个人名片 &#x1f393;作者简介&#xff1a;java领域优质创作者 &#x1f310;个人主页&#xff1a;码农阿豪 &#x1f4de;工作室&#xff1a;新空间代码工作室&#xff08;提供各种软件服务&#xff09; &#x1f48c;个人邮箱&#xff1a;[2435024119qq.com] &#x1f4f1…

【论文解读】大模型算法发展

一、简要介绍 论文研究了自深度学习出现以来&#xff0c;预训练语言模型的算法的改进速度。使用Wikitext和Penn Treebank上超过200个语言模型评估的数据集(2012-2023年)&#xff0c;论文发现达到设定性能阈值所需的计算大约每8个月减半一次&#xff0c;95%置信区间约为5到14个月…

雪花算法 集群uid重复问题 uid-generator-spring-boot-starter

1、在生成环境 在某个业务使用该插件生成uid,由于业务整合了 mybatis-plus模块 2、该业务是分部署集群部署以及使用的多线程获取uid&#xff0c;使用中发现唯一建冲突&#xff0c;生成的uid有重复。 然后查看日志发现 workerId 始终为0 怀疑是生成workerId出了问题。 查看跟…

开发日志:windows修复SSL漏洞CVE-2016-2183(3389端口)

漏洞危害&#xff1a; 具有足够资源的中间人攻击者可利用此漏洞,通过“birthday”攻击检测会在固定密码与已知纯文本之间泄露 XOR 的冲突,进而泄露密码文本(例如安全 HTTPS Cookie),并可能导致劫持经认证的会话。 参见《支持SSL 64位块大小的密码套件(SWEET32)-修复方案》 参考…

主流树模型讲解、行列抽样、特征重要性梳理总结

本文旨在总结一下常见树模型的行、列抽样特点以及特征重要性的计算方式&#xff0c;也会带着过一遍算法基本原理&#xff0c;一些细节很容易忘记啊。 主要是分类和回归两类任务&#xff0c;相信能搜索这篇文章的你&#xff0c;应该对树模型有一定的了解。 可以搜索 总结 &…

老鼠后五毒也来凑热闹!网红食品惊现「壁虎头」,胖东来已下架…

上周&#xff0c;老鼠有点忙&#xff0c;比如其连续被曝出&#xff0c;出现在了方便面知名品牌的调料包、知名连锁餐饮品牌的黄焖鸡饭中。‍‍‍‍‍‍‍‍‍‍‍‍‍‍ 在小柴「被「添加」进方便面、黄焖鸡饭&#xff1f;老鼠最近忙疯了……」这篇文章的评论区&#xff0c;柴油…

计算机视觉与面部识别:技术、应用与未来发展

引言 在当今数字化时代&#xff0c;计算机视觉技术迅速发展&#xff0c;成为人工智能领域的一个重要分支。计算机视觉旨在让机器理解和解释视觉信息&#xff0c;模拟人类的视觉系统。它在各行各业中发挥着重要作用&#xff0c;从自动驾驶汽车到智能监控系统&#xff0c;再到医疗…

Python:对常见报错导致的崩溃的处理

Python的注释&#xff1a; mac用cmd/即可 # 注释内容 代码正常运行会报以0退出&#xff0c;如果是1&#xff0c;则表示代码崩溃 age int(input(Age: )) print(age) 如果输入非数字&#xff0c;程序会崩溃&#xff0c;也就是破坏了程序&#xff0c;终止运行 解决方案&#xf…

贪心算法(三) ---cmp_to_key, 力扣452,力扣179

目录 cmp_to_key 比较函数 键函数 cmp_to_key 的作用 使用 cmp_to_key 代码解释 力扣452 ---射气球 题目 分析 代码 力扣179 ---最大数 题目 分析 代码 cmp_to_key 在Python中&#xff0c;cmp_to_key 是一个函数&#xff0c;它将一个比较函数转换成一个键函数…

Problems retrieving the embeddings data form OpenAI API Batch embedding job

题意&#xff1a;从OpenAI API批量嵌入作业中检索嵌入数据时遇到问题 问题背景&#xff1a; I have to embed over 300,000 products description for a multi-classification project. I split the descriptions onto chunks of 34,337 descriptions to be under the Batch e…

Nginx优化、防盗链

目录 Nginx优化 隐藏版本信息 网站缓存 日志切割 超时时间 更改进程数 网页压缩 防盗链 在使用源码软件包安装过Nginx服务&#xff0c;具体步骤看上一篇文章 功能模块位置 在Nginx的解压目录下的auto目录内的options文件可以查看Nginx可以安装的功能模块 [rootlocal…

数据结构初阶-单链表

链表的结构非常多样&#xff0c;以下情况组合起来就有8种&#xff08;2 x 2 x 2&#xff09;链表结构&#xff1a; 而我们主要要熟悉的单链表与双向链表的全称分别为&#xff1a;不带头单向不循环链表&#xff0c;带头双向循环链表&#xff0c;当我们对这两种链表熟悉后&#x…

重生之我们在ES顶端相遇第5章-常用字段类型

思维导图 前置 在第4章&#xff0c;我们提到了 keyword&#xff08;一笔带过&#xff09;。在本章&#xff0c;我们将介绍 ES 的字段类型。全面的带大家了解 ES 各个字段类型的使用场景。 字段类型 ES 支持以下字段类型&#xff08;仅介绍开发中常用&#xff0c;更多内容请自…

AI App Store-AI用户评价-多维度打分对比pk-AI社区

C端用户、创作者、AI达人们在选择众多国内外AI厂商的服务时候往往感到一头雾水&#xff0c;那么多功能接近的AI应用(智能对话类、文档总结类、文生图、AI搜索引擎) 究竟在不同用户需求场景下表现怎么样。大部分人如果有需求都会所有平台都尝试一遍&#xff0c;比如一个博主生成…

C++自定义字典树结构

代码 #include <iostream> using namespace std;class TrieNode { public:char data;TrieNode* children[26];bool isTerminal;TrieNode(char ch){data ch;for (int i 0; i < 26; i){children[i] NULL;}isTerminal false;} }; class Trie { public:TrieNode* ro…

基于区块链技术的中药饮片代煎配送服务与监管平台

业务背景 近年来&#xff0c;随着公众对中医药青睐有加&#xff0c;中药代煎服务作为中医药现代化的重要一环&#xff0c;在全国各地蓬勃兴起。鉴于传统煎煮方式的繁琐耗时&#xff0c;医疗机构纷纷转向与第三方中药饮片企业合作&#xff0c;采用集中代煎模式。这些第三方煎药中…

Proactor模型

文章目录 概述1. 异步I/O操作2. 事件通知3. 事件处理函数4. 事件循环5. 多线程支持6. 非阻塞I/O7. 可扩展性8. 错误处理9. 资源管理10. 编程复杂性11. 应用场景流程图 结论 概述 Proactor模型是一种基于异步I/O操作的事件驱动编程模型&#xff0c;主要用于处理并发的I/O事件&a…

冒泡排序(数组作为函数参数)

什么是冒泡排序&#xff1f; 冒泡排序&#xff08;Bubble Sort&#xff09;也是一种简单直观的排序算法。它重复地走访过要排序的数列&#xff0c;一次比较两个元素&#xff0c;如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换&#xff0c;…

docker--容器数据进行持久化存储的三种方式

文章目录 为什么Docker容器需要使用持久化存储1.什么是Docker容器&#xff1f;2.什么是持久化存储&#xff1f;3.为什么Docker容器需要持久化存储&#xff1f;4.Docker如何实现持久化存储&#xff1f;(1)、Docker卷(Volumes)简介适用环境:使用场景:使用案例: (2)、绑定挂载&…