【C语言】11. 深入理解指针1

【C语言】11. 深入理解指针1

  • 一、内存和地址
    • [1.1 内存(宿舍楼类比)](#1.1 内存(宿舍楼类比))
    • [1.2 编址原理](#1.2 编址原理)
  • 二、指针变量和地址
    • [2.1 取地址操作符(&)](#2.1 取地址操作符(&))
    • [2.2 指针变量和解引用操作符(*)](#2.2 指针变量和解引用操作符(*))
      • [(1)指针变量: 存放地址的变量](#(1)指针变量: 存放地址的变量)
      • [(2)解引用操作符(*): 通过地址找到指向的对象](#(2)解引用操作符(*): 通过地址找到指向的对象)
    • [2.3 指针变量的大小](#2.3 指针变量的大小)
  • 三、指针变量类型的意义
    • [3.1 指针的解引用(权限不同)](#3.1 指针的解引用(权限不同))
    • [3.2 指针±整数(步长不同)](#3.2 指针±整数(步长不同))
    • [3.3 void* 指针(泛型指针)](#3.3 void* 指针(泛型指针))
  • [四、const 修饰指针](#四、const 修饰指针)
    • [4.1 const 修饰变量](#4.1 const 修饰变量)
    • [4.2 const修饰指针变量](#4.2 const修饰指针变量)
  • 五、指针运算
    • [5.1 指针 ± 整数](#5.1 指针 ± 整数)
    • [5.2 指针 - 指针](#5.2 指针 - 指针)
    • [5.3 指针的关系运算](#5.3 指针的关系运算)
  • 六、野指针
    • [6.1 野指针三大成因](#6.1 野指针三大成因)
    • [6.2 规避野指针四步走](#6.2 规避野指针四步走)
  • 七、assert断言
    • [7.1 基本用法](#7.1 基本用法)
    • [7.2 特点](#7.2 特点)
    • [7.3 开启/关闭](#7.3 开启/关闭)
  • 八、指针的使用和传址调用
    • [8.1 模拟实现 strlen](#8.1 模拟实现 strlen)
    • [8.2 传值调用 vs 传址调用(Swap经典案例)](#8.2 传值调用 vs 传址调用(Swap经典案例))
    • [8.3 对比总结](#8.3 对比总结)

一、内存和地址

1.1 内存(宿舍楼类比)

我们知道计算机上CPU(中央处理器)在处理数据的时候,需要的数据是在内存中读取的,处理后的数据也会放回内存中,那我们买电脑的时候,电脑上内存是8GB/16GB/32GB等,那这些内存空间如何⾼效的管理呢?

其实也是把内存划分为⼀个个的内存单元,每个内存单元的大小取1个字节。

现实世界 计算机世界
宿舍楼 内存空间
房间 内存单元(每个1字节)
门牌号 地址(编号)
找到房间 CPU通过地址访问内存

其中,每个内存单元,相当于一个学生宿舍,一个字节空间里面能放8个比特位,就好比同学们住的八人间,每个人是一个比特位。

每个内存单元也都有一个编号(这个编号就相当于宿舍房间的门牌号),有了这个内存单元的编号,CPU就可以快速找到一个内存空间。

生活中我们把门牌号也叫地址,在计算机中我们把内存单元的编号也称为地址。

C语言中给地址起了新的名学叫:指针。

内存单元的编号 = 地址 = 指针

每个内存单元1个字节,里面存放8个比特位。

1.2 编址原理

  • CPU通过地址总线发送地址信号到内存
  • 32位机器:32根地址线 → 2³²种地址 → 指针占4字节
  • 64位机器:64根地址线 → 2⁶⁴种地址 → 指针占8字节
  • 硬件层面已完成编址设计,是约定好的共识

二、指针变量和地址

2.1 取地址操作符(&)

c 复制代码
int a = 10;
&a;        // 取出a的地址
printf("%p\n", &a);  // %p 打印地址

整型变量a占4个字节,&a取的是首字节地址(较小的那个地址)

知道首地址,就能顺藤摸瓜访问全部4个字节

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

(1)指针变量: 存放地址的变量

c 复制代码
int a = 10;
int *pa = &a;   // pa是指针变量,存放a的地址

如何拆解指针类型:

c 复制代码
int *pa = &a;
// int *  → 整体是指针类型
// int    → 指向的对象是int类型
// *      → 说明pa是指针变量

char类型指针示例:

c 复制代码
char ch = 'w';
char *pc = &ch;  // char* 指向char类型

(2)解引用操作符(*): 通过地址找到指向的对象

c 复制代码
int a = 100;
int *pa = &a;
*pa = 0;    // 等价于 a = 0,通过地址把a改成0

💡 这给了我们修改a的另一种途径,让代码更灵活。

2.3 指针变量的大小

c 复制代码
printf("%zd\n", sizeof(char *));   // 4(32位)或8(64位)
printf("%zd\n", sizeof(int *));    // 同上
printf("%zd\n", sizeof(double *)); // 同上

⚠️ 结论:指针变量的大小和类型无关,只与系统位数有关;同一平台下所有指针大小都相同。

三、指针变量类型的意义

既然指针大小都一样,为什么还要分不同类型?

3.1 指针的解引用(权限不同)

c 复制代码
// 代码1:int* 解引用
int n = 0x11223344;
int *pi = &n;
*pi = 0;   // 把n的4个字节全部改为0

// 代码2:char* 解引用
int n = 0x11223344;
char *pc = (char*)&n;
*pc = 0;   // 只把n的第一个字节改为0

📌 结论:指针的类型决定了,在对指针进行解引用时,一次能访问几个字节。

3.2 指针±整数(步长不同)

c 复制代码
int n = 10;
char *pc = (char*)&n;
int *pi = &n;

printf("%p\n", pc);    // 假设 0x1000
printf("%p\n", pc+1);  // 0x1001(跳1字节)
printf("%p\n", pi);    // 假设 0x1000
printf("%p\n", pi+1);  // 0x1004(跳4字节)

📌 结论:指针的类型决定了指针向前或向后走一步有多大距离。

3.3 void* 指针(泛型指针)

c 复制代码
int a = 10;
void *p = &a;   // void*:无具体类型的指针,可以接收任意类型的地址

局限性:

❌ 不能直接解引用:*p → 报错

❌ 不能直接±整数:p+1 → 报错

用途: 函数参数中使用,接收不同类型数据的地址,实现泛型编程效果。

四、const 修饰指针

4.1 const 修饰变量

c 复制代码
const int n = 0;
n = 20;   // ❌ 报错!n不能被修改

但可以通过指针绕过限制(不推荐):

c 复制代码
const int n = 0;
int *p = &n;
*p = 20;   // ✅ 能改,但破坏了const的本意

4.2 const修饰指针变量

🎯 速记口诀:const 在 * 左边管内容,在 * 右边管指针本身

c 复制代码
int *p;              // 无限制
const int *p;        // const在*左边 → 指向的内容不可变
int *const p;        // const在*右边 → 指针本身不可变
const int *const p;  // 两边都有 → 内容和指针都不可变

对比表格:

五、指针运算

5.1 指针 ± 整数

利用数组连续存储的特性遍历数组:

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

5.2 指针 - 指针

得到两个地址之间相差的元素个数:

c 复制代码
// 模拟实现strlen
int my_strlen(char *s)
{
    char *p = s;
    while(*p != '\0')
        p++;
    return p - s;   // 指针相减得到长度
}

⚠️ 要求:两个指针指向同一块连续空间才有意义。

5.3 指针的关系运算

比较地址的高低(大小):

c 复制代码
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++;
}

六、野指针

概念:野指针就是指针指向的位置是未知的、随机的、不正确的。

6.1 野指针三大成因

① 指针未初始化

c 复制代码
int *p;   // 局部变量未初始化,默认随机值
*p = 20;  // ❌ 危险!p指向未知位置

② 指针越界访问

c 复制代码
int arr[10] = {0};
int *p = &arr[0];
for(int i = 0; i <= 11; i++)  // 循环12次,越界
{
    *(p++) = i;   // 最后两次访问越界 → 野指针
}

③ 指针指向的空间被释放

c 复制代码
int* test()
{
    int n = 100;
    return &n;   // ❌ 返回局部变量地址
}
int main()
{
    int *p = test();   // p成为野指针
    printf("%d\n", *p); // ❌ 危险!空间已释放
}

6.2 规避野指针四步走

NULL的定义:

c 复制代码
#define NULL ((void*)0)  // 0地址不可读写

正确示例:

c 复制代码
int *p = NULL;          // 不知道指向哪就赋NULL
// ... 使用时
if(p != NULL)           // 先检查
{
    *p = 10;            // 安全使用
}

七、assert断言

7.1 基本用法

c 复制代码
#include <assert.h>
assert(p != NULL);   // 条件为假时终止程序并报错

7.2 特点

7.3 开启/关闭

c 复制代码
#define NDEBUG          // 定义后禁用assert
#include <assert.h>
  • Debug版本:启用,帮助排查问题
  • Release版本:自动优化掉,不影响效率

八、指针的使用和传址调用

8.1 模拟实现 strlen

c 复制代码
int my_strlen(const char *str)
{
    int count = 0;
    assert(str);           // 确保指针非空
    while(*str)            // 等价于 *str != '\0'
    {
        count++;
        str++;
    }
    return count;
}

8.2 传值调用 vs 传址调用(Swap经典案例)

❌ 传值调用(失败):

c 复制代码
void Swap1(int x, int y)
{
    int tmp = x;
    x = y;
    y = tmp;   // 只交换了形参,不影响实参
}

int main()
{
    int a = 10, b = 20;
    Swap1(a, b);   // 传值 → 交换失败
    // a=10, b=20(没变)
}

✅ 传址调用(成功):

c 复制代码
void Swap2(int *px, int *py)
{
    int tmp = *px;
    *px = *py;
    *py = tmp;   // 通过地址直接修改main中的a和b
}

int main()
{
    int a = 10, b = 20;
    Swap2(&a, &b);   // 传址 → 交换成功
    // a=20, b=10(交换成功!)
}

8.3 对比总结

🎯 终极速记卡(一页总结)

相关推荐
charlie1145141911 小时前
现代C++工程实践——WeakPtr 第二关:核心骨架与控制块
开发语言·c++
ttwuai2 小时前
Go 后台接口 401/403 排查:JWT 过期、刷新请求和权限码怎么定位
开发语言·golang·状态模式
漫随流水2 小时前
Java——springboot web案例
java·开发语言·spring boot
从此以后自律3 小时前
Java Object 类常用方法全讲解
java·开发语言
XH华3 小时前
C++语言第二章类和对象(下)
开发语言·c++
从零开始的代码生活_3 小时前
C++ stack、queue 与 priority_queue:容器适配器原理与实战
开发语言·c++·后端·学习·算法
techdashen3 小时前
Go 1.25 新增 `reflect.TypeAssert`:更直接、更高效地从 `reflect.Value` 取出类型值
开发语言·后端·golang
2zcode3 小时前
项目文档:基于MATLAB低采样率ISAR成像的快速稀疏重建算法研究
开发语言·算法·matlab
xixi09244 小时前
JMeter5.6.3基础使用教程1
java·开发语言