C语言指针完全指南:从入门到精通(上)

article/2025/6/22 23:26:59

目录

一、内存和指针

1.1 指针的使用场景

二、指针变量和地址

2.1 取地址符(&)

2.2指针变量和解引用操作符(*)

2.2.1 指针变量

2.3 指针变量的大小

三、指针变量类型的意义

3.2 指针+-整数

 ​编辑

 四、指针计算

 五、const修饰指针

5.1 const修饰变量

1.2 const修饰指针变量 

六、野指针

6.1 野指针成因

1 指针未初始化

2.指针越界访问

3.指针指向的空间释放

6.2 如何规避野指针

6.2.1 指针初始化

6.2.2 小心指针越界

6.2.3 指针变量不再使用时,即使设置NULL

 七、assert断言

7.1 assert是什么

7.2 assert的优缺点 


一、内存和指针

再将内存和指针之前,我想引入一个生活中的例子: 

  •   内存可以类比为一个巨大的仓库,里面有许多存储单元,每个单元都有一个唯一的地址。这些存储单元就像仓库中的货架,每个货架都有一个编号,方便我们找到存放的物品。
  •   指针则类似于仓库的管理员,他手里有一张清单,清单上记录了每个货架的编号以及货架上存放的物品。管理员通过这张清单可以快速找到某个物品的位置,而不需要逐个货架去查找。

1.1 指针的使用场景

假设仓库中有多个货架,每个货架上存放着不同的物品。管理员(指针)可以通过清单(指针变量)找到某个货架(内存地址),并查看或修改货架上的物品(数据)。 

#include <stdio.h>
int main
{int a = 10;  // 在某个货架上存放了数字10int *p = &a; // 管理员记录下这个货架的编号*p = 20;     // 管理员找到这个货架,并将上面的数字改为20retrun 0;
}

二、指针变量和地址

2.1 取地址符(&)

 c语言中创建变量其实就是想内存申请空间,比如:

#include <stdio.h>
int main()
{int a = 10;//向内存申请4个字节的空间,存放10进去。return 0;
}

而我们应该如何得到地址呢?

#include <stdio.h>
int main()
{int a = 10;&a;//使用“&”,取出a的地址printf("%p\n", a);//%p 地址return 0;
}

2.2指针变量和解引用操作符(*)

2.2.1 指针变量

当我们通过&符号来拿到的地址其实是一个数值,比如0000000A,这个值如果我们想存储起来,那我们应该该如何把这个值给存储到一个合适的地方呢,该存在哪?

c语言中,像这么一个问题,我们一个存入指针变量中

#include <stdio.h>
int main()
{int a = 10;int *pa = &a;//取出a的地址并存储到指针变量pa中  &a指向*pareturn 0;
}

 这种变量(指针变量)是用来存放指针的,存放在指针变量中的值都会理解为地址。

2.2.2 如何拆解指针类型

  我们看到上面的代码:

int *pa = &a;

  pa的类型是int *,*是在说明pa是指针变量

  int说明pa只想的是整形(int)类型的对象

  • 指针就是地址
  • 指针变量就是变量,是专门用来存放地址的变量
  • 存放在指针变量的值,就是地址

那我们现在写一个char类型ch变量,我们应该放在什么类型当中呢?

char ch = 'w';
pc = &ch;char * pc = &ch;//pc的类型

2.2.3解应用操作符 

  • 我们现在所讲的pc = &ch;,只是讲ch这个地址起来,但是我们存起来之后该怎么使用他呢?
  • 在现实生活中,我们得知一个柜子编号要去取出或者存放物品。而c语言也一样,我们得知了这个数的地址(指针),就可以通过地址(指针)找到地址(指针)指向的对象,这里必须学习一个操作符叫做解引用操作符(*)
#include <stdio.h>
int main()
{int a = 100;int* pa = &a;*pa = 0;//pa就是a,把a改成0return 0;
}

上面代码中第六行的 int* pa = &a; 就是用了"*"解引用操作符,*pa的意思就是通过pa存放的地址,找到指向的空间,*pa其实就是a变量了;所以 *pa = 0,这个操作符就是把a改成了0.

  那么我们可以通过代码看的更直观一点:

#include <stdio.h>
int main()
{int a = 100;int* pa = &a;printf("%d\n",*pa);*pa = 0;//pa就是a,把a改成0printf("%d\n",a);return 0;
}

2.3 指针变量的大小

前面内容我们了解到在不同输出环境中,指针变量的大小是不同的,例如X86和X64就有所不同

代码如下 :

int main()
{printf("%zd\n", sizeof(char*));printf("%zd\n", sizeof(int*));printf("%zd\n", sizeof(short*));printf("%zd\n", sizeof(double*));return 0;
}

那么我们在不同环境下有什么差别呢??? 

X64

X86

结论:

  • 32位平台下地址就是32bit位,指针变量大小是4个字节
  • 64位平台下地址就是64bit位,指针变量大小是8个字节

三、指针变量类型的意义

指针变量的大小和类型无关,只要是指针变量,在一个平台下,大小都是一样的,为什么还要有各种各样的指针类型呢?

3.1 指针的解引用

对比下面两个代码,主要是观察内存的变化:

int main()
{int n = 0x11223344;int* pi = &n;*pi = 0;return 0;
}int main()
{int n = 0x11223344;char* pc = &n;*pc = 0;return 0;
}

大家看到这里可以自己在内存窗口看看他们两个的差别

int: 

 char:

调试我们可以看到代码1的4个字节全部改为0,而代码2只有第一个字节改为0。

结论:

  • 指针的类型决定了对指针解引用的时候有多大权限(例如int是是四个字节)。

3.2 指针+-整数

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);printf("%p\n", &n);
}

运行结果如下: 

 

我们可以看出,char*类型的指针变量+1跳过一个字节,int*类型的指针变量+1跳过的是4个字节。

这就是+1带来的变化,同理-1页是一样。

3.3 void指针

在指针类型中有一个特殊的类型就是void类型,可以理解为无具体类型的指针,这种类型的指针可以接受所有类型的地址。但是也有局限性:他不能+-整数和解引用计算

int main()
{int a = 10;int* pa = &a;double* pc = &a;return 0;}

使用 void*类型的指针接受地址:

#include <stdio.h>int main(){int a = 10;void* pa = &a;void* pc = &a;*pa = 10; *pc = 0;return 0;}

 四、指针计算

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

4.1指针+-整数

因为数组在内存中是连续存放的,只要知道第一个元素的地址,就可以慢慢找到所有元素。

int arr[10] = {1,2,3,4,5,6,7,8,9,10};

int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int* p = &arr[0];int i = 0;int sz = sizeof(arr) / sizeof(arr[0]);for (i = 0;i <sz;i++){printf("%d ", *(p + i));//p+i就是指针+整数}
}

4.2 指针-指针 

int my_strlen(char* s)
{char* p = s;while (*p != '\0')p++;return p - s;
}int main()
{printf("%d\n", my_strlen("abc"));return 0;
}

4.3 指针的关系运算

int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int* p = &arr[0];int sz = sizeof(arr) / sizeof(arr[0]);while (p < arr + sz)//指针大小比较{printf("%d ", *p);p++;}return 0;
}

 五、const修饰指针

5.1 const修饰变量

变量是可以修改的,如果吧变量的地址交给一个指针变量,通过指针我们也是可以修改这个便来那个的,但是我们如果不想让这个变量被修改,那我们该如何做呢,这时候就引出了const。

int main()
{int a = 1;a = 10;printf("%d", a);const int b = 1;b = 10;printf("%d", b);}

大家可以发现运行时是会报错的,无法修改b的值,这就是const的作用。

但是在这中情况下,我们用另一种方法——使用地址,去修改就可以成功啦

int main()
{const int a = 0;printf("n = %d\n", a);int* p = &a;*p = 10;printf("n = %d\n", a);return 0;
}

我们看到我们a的值确实被修改了,但是我们要想一下,我们既然要使用const来修饰a,那就是不想让a可以被修改,所以上面这个方法对于实践应用来说没有什么意义,那我们该如何让p拿到n的地址也不被修改呢?

1.2 const修饰指针变量 

int *p ;

int const * p;     const放在*左边修饰

int * const P;     const放在*左边修饰

int* const ptr;//ptr 是一个 const 指针,指向 int 类型的数据。
//ptr 的指向不可更改,但可以通过 ptr 修改所指向的数据
const int* const ptr;//ptr 是一个 const 指针,指向 const int 类型的数据。
//既不能通过 ptr 修改所指向的数据,也不能修改 ptr 的指向。

见如下代码:

void test1()
{int n = 10;int m = 20;int* p = &n;*p = 20;p = &m;
}//测试const放在*左边的情况
void test2()
{int n = 10;int m = 20;const int* p = &n;//只限制*p,指针指向的内容不能修改,但不限制p*p = 20;p = &m;
}//测试const放在*的右边情况
void test3()
{int n = 10;int m = 20;int* const p = &n; //只能约束p,指针指向的内容可以通过p来改变,但是指针变量不能被改变*p = 20;p = &m;
}
//测试*的左右两边都有const
void test4()
{int n = 10;int m = 20;int const * const p = &n;*p = 20;//两个都约束p = &m;
}int main()
{test1();test2();test3();test4();return 0;
}

 结论:

  • const如果放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。但是指针变量本身的内容可变。
  • const如果放在*的右边,修饰的是指针变量本身,保证了指针变量的内容不能修改,但是指针指向的内容,可以通过指针改变。

六、野指针

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

6.1 野指针成因

1 指针未初始化

int main()
{int* p;//局部变量指针未初始化,默认为随机值*p = 20;//p就是一个野指针return 0;
}

2.指针越界访问

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

3.指针指向的空间释放

int *test()
{ int n = 100;return &n;
}//结束后n还给操作系统了int main()
{int* p = test();printf("%d\n", *p);return 0;
}

return &n的时候,n的生命周期结束了,那么n就会还给操作系统 

但结合我们之前的博客,在第二行前加上“static”,那么就没问题啦

int *test()
{ static int n = 100;return &n;
}//结束后n还给操作系统了int main()
{int* p = test();printf("%d\n", *p);return 0;
}

6.2 如何规避野指针

6.2.1 指针初始化

  • 如果明确知道指针指向哪里就直接赋值地址(例如int i = 0; int* pi = &i;)
  • 不知道的话可以给指针赋值NULL
  • NULL是c语言中定义的一个标识符常量,值是0(赋一个空值),但这个地址是无法使用的,读写改地址会报错

初始化如下:

int main()
{int num = 10;int* p1 = &num;int* p2 = NULL;return 0;
}

6.2.2 小心指针越界

一个程序向内存申请了哪些空间,通过指针也就只能访问哪些空间,不能超出范围访问,超出了就是越界访问。所以我们申请多少内存就使用多少内存。 

6.2.3 指针变量不再使用时,即使设置NULL

当一块区域不再访问的时候,我们及时将该指针设置为NULL。

只要是NULL指针就不去访问,同时使用指针之前可以判断指针是否为NULL

int main()
{int arr[10] = { 0 };int* p = &arr[0];int i = 0;for(i =0;i<10;i++){*(p++) = i;}
//此时p已经越界了,可以把p设置为NULLp = NULL;//下次使用p的时候,判断p不为NULL的时候再使用p = &arr[0];//再次定义,重新获得地址if (p != NULL){//表达式}return 0;
}

 七、assert断言

7.1 assert是什么

在编程中,assert 是一种用于调试的语句,用于验证某个条件是否为真。如果条件为假,assert 会抛出异常(通常是 AssertionError),帮助开发者快速发现逻辑错误。

  assert.h头文件定义了assert(),这个宏通常被称为“断言”

assert(p != NULL);
//验证 p!=NULL 真假——为假不运行,给出报错
#include<assert.h>
int main()
{int* p = NULL;assert(p != NULL);return 0;
}

 

7.2 assert的优缺点 

优点:

  • 它不仅能自动标识文件和出问题的行号,还有一种无需更换代码就能开启或关闭assert()的机制

缺点:

  • 因为引入了额外的检查,会增加了程序的运行时间


http://www.hkcw.cn/article/jZsSFXYGjt.shtml

相关文章

Kafka数据怎么保障不丢失

在分布式消息系统中&#xff0c;数据不丢失是核心可靠性需求之一。Apache Kafka 通过生产者配置、副本机制、持久化策略、消费者偏移量管理等多层机制保障数据可靠性。以下从不同维度解析 Kafka 数据不丢失的核心策略&#xff0c;并附示意图辅助理解。 一、生产者端&#xff1a…

Win10秘笈:两种方式修改网卡物理地址(MAC)

Win10秘笈&#xff1a;两种方式修改网卡物理地址&#xff08;MAC&#xff09; 在修改之前&#xff0c;可以先确定一下要修改的网卡MAC地址&#xff0c;查询方法有很多种&#xff0c;比如&#xff1a; 1、在设置→网络和Internet→WLAN/以太网&#xff0c;如下图所示。 2、在控…

Angularjs-Hello

1 关于Angularjs 最近因为项目需要又要做这个&#xff0c;所以简单复习下。其实这个大概7&#xff0c;8年前就用过&#xff0c;当时做了几个简单页面觉得太简单就还是回去做嵌入式了。按照互联网技术的进化速度&#xff0c;本来以为早死在 沙滩上了&#xff0c;没想到现在还在坚…

红外遥控(外部中断)

目录 1.红外遥控简介 通信方式&#xff1a; 红外LED波长&#xff1a; 通信协议标准&#xff1a; 2.硬件电路 发送部分1&#xff1a; 内部元件介绍&#xff1a; 工作原理&#xff1a; 为什么要以38KHZ亮灭&#xff1f; 电路图&#xff1a; 发送部分2&#xff1a; 电…

leetcode hot100刷题日记——33.二叉树的层序遍历

解题总结二维vector的初始化方法 题目描述情况1&#xff1a;不确定行数和列数情况2&#xff1a;已知行数和列数情况3&#xff1a;已知行数但不知道列数情况4&#xff1a;已知列数但不知道行数 题目描述 解答&#xff1a;用队列 思路都差不多&#xff0c;我觉得对于我自己来说&a…

GitToolBox 插件安装与配置指南

GitToolBox 插件安装与配置指南 GitToolBox GitToolBox IntelliJ plugin 项目地址: https://gitcode.com/gh_mirrors/gi/GitToolBox 1. 项目基础介绍和主要编程语言 项目基础介绍 GitToolBox 是一个专为 JetBrains 家族 IDE&#xff08;如 IntelliJ IDEA、PyCharm 等&…

开源模型应用落地-qwen模型小试-Qwen3-8B-推理加速-vLLM-结构化输出(三)

一、前言 在人工智能技术迅猛发展的今天,高效推理框架与强大语言模型的结合正不断突破应用边界。vLLM作为新一代高性能推理引擎,凭借其创新的PagedAttention技术和内存优化能力,为大规模语言模型部署提供了全新可能。 本文将聚焦vLLM框架与QWen3-8B这一国产开源大模型的深度…

史上最全 Git 图文教程(非常详细)零基础入门到精通,收藏这一篇就够了

戳上方蓝字“Java知音”关注我 Git安装 安装 1.先去官网下载这个软件, 准备安装到本电脑中 https://git-scm.com/ 2.根据自己电脑系统下载此软件到本机 Windows 系统直接下载 .exe 文件即可&#xff0c;macOS 系统使用 Homebrew 命令行安装&#xff0c;终端输入 git --versi…

GitHub学生认证申请

想要免费使用Copilot&#xff0c;申请学生认证可以免费使用。在申请过程中&#xff0c;踩了些坑。记录一下供大家参考 认证有效期 默认时长&#xff1a;首次认证成功后&#xff0c;学生权益&#xff08;如 GitHub Pro 权限和 Student Developer Pack&#xff09;的有效期一般为…

最新Spring Security实战教程(十五)快速集成 GitHub 与 Gitee 的社交登录

&#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有坚忍不拔之志 &#x1f390; 个人CSND主页——Micro麦可乐的博客 &#x1f425;《Docker实操教程》专栏以最新的Centos版本为基础进行Docker实操教程&#xff0c;入门到实战 &#x1f33a;《RabbitMQ》…

Vue3 开源UI 框架推荐 (总有一款适合你)

一 、前言 &#x1f4a5;这篇文章主要推荐了支持 Vue3 的开源 UI 框架&#xff0c;包括 web 端和移动端的多个框架&#xff0c;如 Element-Plus、Ant Design Vue 等 web 端框架&#xff0c;以及 Vant、NutUI 等移动端框架&#xff0c;并分别介绍了它们的特性和资源地址。&#…

Java 版 Manus 实现来了,Spring AI Alibaba 发布开源 OpenManus 实现

大家好&#xff0c;我是玄姐。 此次官方发布的 Spring AI Alibaba OpenManus 实现&#xff0c;涵盖了完整的多智能体任务规划、思考与执行流程。这一版本专为 Java 开发者设计&#xff0c;能够让开发者亲身体验多智能体协同工作的强大效果。它具备根据用户问题进行深度分析、操…

Linux之基础开发工具二(makefile,git,gdb)

目录 一、自动化构建-make/makefile 1.1、背景 1.2、基本使用 1.3、推导过程 1.4、语法拓展 二、进度条小程序 2.1、回车与换行 2.2、行缓冲区 2.3、练手-倒计时程序 2.4、进度条程序 三、版本控制器-Git 3.1、版本控制器 3.2、gitee的使用 3.2.1、如何创建仓库 …

如何使用gitee进行代码管理(常见的两种私人令牌-HTTPS和公钥SSH)

Getee平台提供了四种方式管理代码&#xff0c;如下图所示&#xff1a; 一、使用私人令牌&#xff08;HTTPS&#xff09;管理代码 优点&#xff1a;账户下所有项目都可以操作&#xff0c;并且使用快捷&#xff0c;过程简单&#xff0c;可以选择令牌的权限范围&#xff0c;HTTPS…

@PathVariable注解-补充

这段代码是 Spring MVC 框架中使用 RESTful 风格的请求处理方法&#xff0c;详细解释其功能和注解&#xff1a; 代码功能概述 这段 Java 代码定义了一个 Spring MVC 控制器方法&#xff0c;用于处理 RESTful 风格的 URL 请求。它可以从 URL 路径中提取参数&#xff0c;并将这…

Canvas实例篇:十二星座之天秤座

Canvas实例篇&#xff1a;十二星座之天秤座 前言效果预览代码实现代码说明星座特定星 结语 前言 星座总给人浪漫而神秘的感觉&#xff0c;如何用代码还原星空中的浪漫&#xff1f;本文将通过 Canvas 技术&#xff0c;讲述如何实现一个可交互的天秤座星空图&#xff0c;包含星星…

VIP》》IP地址漂移

IP地址漂移&#xff0c;就是一个虚拟的IP地址&#xff0c;能够在不同的物理服务器或网络接口之家来回转换&#xff0c;所以当你或者其他的网络设备跟这个虚拟IP地址连接的时候&#xff0c;并不会察觉到设备的转换。这对于网络流量调度&#xff0c;服务器负载均衡的使用意义重大…

【C语言】讲解 程序分配的区域(新手)

目录 代码区 数据区 堆区 栈区 常量区 重点比较一下堆区与 栈区 总结&#xff1a; 前言&#xff1a; C语言程序的内存分配区域是理解其运行机制的重要部分。根据提供的多条证据&#xff0c;我们可以总结出C语言程序在运行时主要涉及以下五个关键内存区域&#xff1a; 代…

时间序列预测入门喂饭教程,python代码示例

目录 前言一、基本原理1、啥是时间序列&#xff1f;2、预测前的准备什么材料&#xff1f;3、搭建你的预测工具&#xff08;模型&#xff09; 二、建模模拟实战&#xff1a;用Python搭个简单模型**Step 1&#xff1a;先把需要的工具库准备好****Step 2&#xff1a;生成一组模拟的…

BLE 广播与扫描机制详解:如何让设备“被看见”?

在 BLE 通信中,“广播”是设备展示自己的方式,“扫描”是发现外设的入口。 作为 BLE 协议的核心机制之一,广播与扫描的设计直接影响通信的稳定性、功耗与连接效率。本篇将从 BLE 广播/扫描原理、数据结构、事件流程到调试技巧全面展开,配合实战案例深入讲解 BLE 设备“可被…