news 2026/8/6 3:52:30

C语言指针学习

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C语言指针学习

一、指针基础概念

1. 内存、地址、指针、指针变量区分

  • 内存:程序运行时可访问的存储空间,计算机以 1 字节为单位给内存编号。
  • 地址:内存字节的唯一编号,用来定位内存空间。
  • 指针 = 地址:指针就是内存编号,代表一块内存的位置。
  • 指针变量:专门存储地址的变量,本身也要占用内存空间。

2. 指针变量定义语法

格式:基类型 *变量名;

  • 基类型:指针指向空间存储的数据类型(int/char/float/void 等),决定解引用时读取多少字节。
  • *:类型标识,说明该变量是指针,不是普通变量。

示例

int *pint; // 指向 int 类型的指针 char *pch; // 指向 char 类型的指针 int *pi, *pj; // 同时定义两个 int 指针

3. 指针变量占用空间

  • 64 位系统:所有指针统一占 8 字节
  • 32 位系统:所有指针统一占 4 字节

sizeof(任意指针)只和系统架构有关,和指向类型无关。

4. 指针初始化与赋值

int num = 100; int *p = NULL; // 初始化为空指针(推荐,避免野指针) int *p2 = # // 取普通变量地址赋值 p = # // 先定义后赋值
  • NULL:等价 0,代表指针不指向任何有效内存。
  • 野指针:未初始化、指向已释放内存、越界地址的指针,操作会段错误。
  • 规避方案:定义时赋值 NULL 或合法变量地址。

5. 两大核心运算符:& 取地址、* 解引用

(1)& 取地址运算符

作用:获取变量首字节地址

规则

  • 只能对变量取地址,不能对常量、表达式、寄存器变量取址。
int a; &a; // 合法,取 a 的地址 &100; // 非法
(2)* 解引用(间接访问)运算符

作用:通过指针保存的地址,访问对应内存里的数据

规则

  • *指针等价于基类型变量,读写该内存的值。
  • 读取字节数由基类型决定:
    • char *p*p只操作 1 字节
    • int *p*p操作 4 字节
    • double *p*p操作 8 字节
int num = 10; int *p = # *p = 20; // 等价 num = 20; 修改原变量值

6. 指针算术运算

指针仅支持++/--/+整数/-整数/指针-指针*/%无意义。

  • 指针 ± 整数:偏移整数 × sizeof(基类型)字节
char *pc; pc+1; // 向后偏移 1 字节 int *pi; pi+1; // 向后偏移 4 字节
  • 指针 1 - 指针 2:结果是两个地址之间间隔的元素个数

公式:元素个数 = (地址差值) / sizeof(基类型)

前提:两个指针指向同一块连续内存(数组)才有意义。

二、指针作为函数参数(地址传递核心)

1. 值传递 vs 地址传递

  • 普通值传递:实参拷贝一份给形参,形参是独立空间,无法修改主函数变量
  • 指针地址传递:实参传变量地址,形参指针接收地址,*形参直接操作主函数原变量,可修改外部变量

2. 指针传参三大用途

  1. 在被调函数修改主调函数的普通变量;
  2. 函数需要返回多个计算结果;
  3. 传递数组,避免完整拷贝数组(节省内存)。

3. 二级指针传参场景

如果要修改主函数里的指针变量本身(不是指针指向的数据),需要传指针的地址,形参用二级指针接收。

void fun(char **pstr) { *pstr = "new string"; // 修改外部一级指针 pstr 的指向 } int main() { char *p = "hello"; fun(&p); // 传指针变量的地址,二级指针接收 }

三、指针与一维数组

1. 数组名本质

数组名 a 是数组首元素地址常量,等价&a[0],不能被赋值(右值)。

下标语法和指针语法完全等价:

a[0] == *a; a[1] == *(a+1); a[n] == *(a+n); &a[n] == a + n;

2. 一维数组作函数参数两种写法(完全等价)

// 写法 1:数组形式 void print_arr(int a[], int len); // 写法 2:指针形式(推荐底层理解) void print_arr(int *pa, int len);

注意:函数内部sizeof(pa)得到的是指针大小(8 字节),不是数组总长度,所以必须手动传长度参数。

3. 字符数组 / 字符串指针

  • char str[] = "hello";str == &str[0] == char *
  • 字符串以'\0'结尾,函数传参不需要额外传长度,遍历至*str == '\0'停止。
  • 自定义字符串库函数(mystrlen/mystrcpy 等)形参都可用char str[]char *str

四、数组指针 vs 指针数组(极易混淆重点)

1. 数组指针:int (*p)[5]

  • 本质:指针,指向一整个数组
  • 括号不能省略()优先级高于[](*p)代表 p 是指针,[5]代表指向含 5 个 int 的数组
  • 运算特性p+1一次性偏移整个数组大小(5×4=20 字节)
  • 适用场景:接收二维数组数组名
int a[5] = {1, 2, 3, 4, 5}; int (*p)[5] = &a; // &a 是整个数组的地址,匹配数组指针类型

2. 指针数组:int *p[5]

  • 本质:数组,数组里每个元素是int *指针
  • []优先级高于*,先定义数组,数组存储指针
  • 内存占用:64 位系统 5×8=40 字节
  • 典型用途:存储多个字符串(节省空间)
// 指针数组:存放 5 个字符串常量地址,仅存指针,不存字符串本体 char *str_arr[5] = {"aa", "bb", "cc"};

3. 指针数组 vs 二维字符数组对比

char str1[3][100] = {"a", "b", "c"}; // 二维数组:存储字符串本体,总 300 字节 char *str2[3] = {"a", "b", "c"}; // 指针数组:仅存地址,总 24 字节
  • 二维字符数组:开辟连续内存存完整字符串,可修改每个字符;
  • 指针数组:仅存字符串常量区地址,字符串常量只读,不可修改字符。

五、指针与二维数组

  • 二维数组名 a 是数组指针int (*)[列数],指向第一行一维数组,等价&a[0]
int a[2][3] = {1, 2, 3, 4, 5, 6}; a + 1; // 跳过一整行(3 个 int,12 字节)

二维数组元素访问等价写法

a[m][n] == *( *(a+m) + n )

二维数组传参两种等价写法(列数必须指定,行数可省略)

void print2D(int a[][3], int row); void print2D(int (*pa)[3], int row);

六、二级指针类型 **p

  • 定义:保存一级指针变量的地址的指针
int num = 10; int *p = # int **pp = &p; // pp 是二级指针,存储一级指针 p 的地址

两大核心使用场景:

  1. 函数内修改外部一级指针变量(前文示例);
  2. 接收指针数组数组名:指针数组名 str_arr 等价char **二级指针。
char *str[3] = {"a", "b"}; void func(char **p); func(str); // 数组名是二级指针,直接传参

七、指针函数 vs 函数指针(区分难点)

1. 指针函数:返回值为指针的函数

格式:基类型 *函数名(形参)

char *get_str() { static char buf[] = "test"; return buf; // 允许返回 static/全局/字符串常量/堆内存地址 // 禁止返回局部栈数组地址,函数结束内存销毁,变成野指针 }

返回地址合法性规则

  • ✅ static 静态变量地址 / 全局变量地址 / 字符串常量地址 / 堆 malloc 地址 / 参数传入地址
  • ❌ 函数内普通局部变量、局部数组地址

2. 函数指针:指向函数入口地址的指针

格式:返回值 (*指针名)(形参列表)

// 加法函数 int add(int a, int b) { return a + b; } // 定义匹配 add 的函数指针 int (*pfun)(int, int) = NULL; pfun = add; // 函数名 = 函数入口地址,赋值给函数指针 // 通过指针调用函数 int res = pfun(1, 2);

核心用途:回调函数,将函数作为参数传给其他函数,解耦代码。

八、void* 万能指针

  • 定义void *p,可以接收任意类型数据的地址,通用地址容器;
  • 限制:不能直接解引用*p,运算前必须强制转换为对应类型指针;
  • 典型场景:malloc/free、通用工具函数参数。
int a = 10; void *vp = &a; int *p = (int *)vp; // 强制转换后才能解引用 printf("%d", *p);

九、指针核心易错点汇总

  1. 数组名是地址常量,不能a++a = p&a是数组指针,和 a 类型不同;
  2. sizeof(数组名)只有两种情况代表数组总大小:单独sizeof(a)&a;作为函数参数时退化为指针;
  3. 野指针、悬空指针不要解引用,程序崩溃;指针定义默认赋 NULL;
  4. 区分:int (*p)[5]数组指针 /int *p[5]指针数组 /int **p二级指针;
  5. 函数返回指针禁止返回栈局部变量地址;
  6. 一级指针传参只能修改指向的数据,二级指针才能修改外部指针本身;
  7. 指针算术偏移由基类型字节大小决定,不是单纯 ±1 字节;
  8. 字符串常量存在只读段,char *p = "abc";不可写p[0]='x',字符数组char str[]="abc"可修改。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/6 3:52:27

UE4多人联机开发:从零构建可搜索房间的完整指南

1. 项目概述与核心价值最近几年,独立游戏和中小团队项目里,多人联机玩法越来越常见。很多朋友在学UE4的时候,单机部分玩得挺溜,一到联机就卡壳,感觉像隔着一堵墙。网上的资料要么太零散,要么版本老旧&#…

作者头像 李华
网站建设 2026/8/6 3:52:16

OpenClaw AI Agent如何重塑软件测试:从自动化到智能化的实战解析

1. 项目概述:当AI Agent瞄准测试工程师的饭碗最近在技术社区和招聘市场,一个叫“OpenClaw”的AI Agent项目讨论热度很高,与之紧密捆绑的关键词是“测试岗”。很多测试同行,包括一些刚入行的朋友,看到“拿什么吃掉测试岗…

作者头像 李华
网站建设 2026/8/6 3:49:34

C++/CLI实战指南:打通C++与.NET的桥梁技术

1. 项目概述:为什么今天还要聊C/CLI?如果你是一位长期在Windows平台上耕耘的C开发者,或者是一个需要将庞大的遗留C代码库与现代的.NET应用(比如C#写的WPF界面或ASP.NET后端)进行集成的工程师,那么“C/CLI”…

作者头像 李华
网站建设 2026/8/6 3:48:52

客户售后维修记录怎么追溯?维修码+工单+质保一条链,再也不扯皮

干电脑店 **15 年**,售后是我最怕也最在乎的环节。最怕的是客户拿机器来说「刚买就坏」,我一查记录什么都没有;最在乎的是,售后做得好,一个客户能带来一串客户。后来我把销售、维修、质保全部数字化,每一台…

作者头像 李华
网站建设 2026/8/6 3:47:57

Elasticsearch核心原理与生产实践指南

1. Elasticsearch基础概念解析1.1 什么是ElasticsearchElasticsearch(简称ES)是一个基于Lucene构建的开源分布式搜索引擎。我第一次接触ES是在处理千万级日志数据的场景中,当时就被它惊人的查询速度所震撼。与传统数据库不同,ES采…

作者头像 李华
网站建设 2026/8/6 3:47:17

Kali Linux下Nessus专业版安装与离线插件更新实战指南

1. 项目概述与核心价值 在安全评估和渗透测试的日常工作中,资产发现和漏洞扫描是绕不开的基础环节。手动去一个个端口、一个个服务地测试效率太低,这时候就需要一款自动化、专业级的漏洞扫描器。Nessus 无疑是这个领域的标杆之一,它功能强大&…

作者头像 李华