4-1 指针

4-1 指针

⏱ 本节预计用时:30分钟

在讲数组那一节我们对指针做了简单的介绍,数组传参传的不是复制品,而是存首元素地址的变量,也就是指针。指针就是存地址的变量,就是这么简单。

指针的语法比较绕,但我相信学完这一讲,你能够看懂大部分使用指针的代码,另外的就是熟练度问题了。


1 两个符号:& 和 *

1.1 & ------ 取地址

每个变量在内存里都有个"门牌号"(之前我们把变量比作格子,现在比作房子),也就是地址。用 &变量名,就能查到它的数字编号是多少:

c 复制代码
#include <stdio.h>

int main() {
    int x = 42;
    printf("x 的值:%d\n", x);        // 42
    printf("x 的地址:%p\n", &x);     
    // 0x7ffd...(0x开头表示十六进制数)
    // %p 是专门打印地址的格式符。
    
    return 0;
}

运行情况:

每次运行,输出地址可能不一样,这是因为运行结束之后,房子被系统回收了,下次它再分配的可能不一样。

1.2 声明指针变量

声明时在变量名前加个 *:

c 复制代码
int x = 42;
int *p = &x;    // p 是一张小纸条,记下了 x 的门牌号

第一次看基本必懵逼,别怕,继续用"房子"来想:

  • x 是一栋房子,里面住着 42;
  • &x 是这栋房子的门牌号(一串地址数字);
  • int *p = &x; 的意思是:再造一个格子 p,这个格子专门用来装门牌号 ,现在把 x 的门牌号塞进去。

所以 p 里装的数字,和 &x 完全一样------p 就是"存着 x 门牌号"的那个格子。打个比方:x 是房子,&x 是门牌号,p 就是那张记着门牌号的小纸条。

在1.1的示例里加入int *p = &x;,printf("x 的地址:%p\n", p);

复制代码
#include <stdio.h>

int main() {
    int x = 42;
    int *p = &x; 
    printf("x 的值:%d\n", x);        
    printf("x 的地址:%p\n", &x);     
    printf("x 的地址:%p\n", p);
    return 0;
}

运行情况:

1.3 * ------ 解引用

*p 的意思是:顺着纸条找到 x 的房子,看里面的住户。不光能看,还能改:

c 复制代码
int x = 42;
int *p = &x;

printf("%d\n", *p);   // 42 ------ 看房子的住户
*p = 100;             // 把 x 房的值改成 100
printf("%d\n", x);    // 100 ------ x 真被改了!

所以指针的全部就两件事:

  • & 取地址:问门牌号
  • * 解引用:顺着门牌号找到房子,看或改里面的值

这里有一个容易乱的点:* 在1.2是声明用的,意思是"我要造一个装地址的指针变量",和这里的顺着地址去查变量不一样。

在1.2的示例里加入 printf("x 的值:%d\n", *p);

复制代码
#include <stdio.h>

int main() {
    int x = 42;
    int *p = &x; 
    printf("x 的值:%d\n", x);       
    printf("x 的地址:%p\n", &x);     
    printf("x 的地址:%p\n", p);
    printf("x 的值:%d\n", *p);          
    return 0;
}

运行情况:

1.4 野指针 和 NULL

野指针,就是声明了、却没给它一个明确地址的指针。它里面装着的是一个随机的"垃圾编号",你根本不知道它指向哪:

c 复制代码
int *p;          // p 里是垃圾值,野指针!
*p = 42;         // 顺着一个随机编号去写 42,程序很可能崩溃

就像你手里有张空白纸条,却硬要按上面的"门牌号"去找房子------大概率走错门,甚至闯进别人家。

解决办法:声明时先把它指向"空"。 C 里用 NULL 表示"这个指针现在谁都不指":

c 复制代码
int *p = NULL;   // 明确告诉编译器:p 现在谁都不指

if (p != NULL) { // 安全习惯:用它之前先问一句"你指着没"
    *p = 42;
}

NULL 就是一个特殊值(等于 0),意思是"空、没有指向"。

  • 声明指针时赋 NULL:不给它留下垃圾编号的机会
  • 解引用前先判断 NULL:确认它真的指着东西再动手

2 一张图弄清楚

复制代码
地址:  0x100    0x104    0x108
     ┌─────────┬─────────┬─────────┐
     │   42    │         │  0x100  │
     └─────────┴─────────┴─────────┘
         x                 p
     (住户42)         (记着0x100)
  • x 住在 0x100,里面是 42
  • p 住在 0x108,里面是 0x100(就是 x 的门牌号)
  • *p = 看地址 0x100 那房子的住户 = 42
  • *p = 100 = 把 0x100 那房子改写成 100 → x 变成 100

3 传地址,函数就能改外面的变量

回忆函数那章的"值传递":传进去的是复印件的值,函数里改了,外面纹丝不动。

想把外面的变量也改掉,就把门牌号传进去,让函数顺着地址改原件:

c 复制代码
#include <stdio.h>

void add10(int *p) {
    *p += 10;     // 顺着 p 改外面那个变量
}

int main() {
    int n = 5;
    printf("%d\n", n);  // 5
    add10(&n);    
    printf("%d\n", n);  // 15
    return 0;
}

传地址 → 就能通过地址改外面的变量。 这也是数组传参能改外面的原因------数组名本身就是首元素地址,传数组本质就是传指针。

运行情况:


4 数组名就是指针

数组名是存放着首元素地址的指针常量。

c 复制代码
#include <stdio.h>

int main() {
    int a[5] = {10, 20, 30, 40, 50};

    printf("%p\n", a);      // 数组名 = 首元素地址
    printf("%p\n", &a[0]);  // 和 &a[0] 打印出来一样

    int *p = a;             // 数组名可以直接塞进指针

    printf("%d\n", a[2]);   // 30
    printf("%d\n", *(a+2)); // 30 ------ a[2] 就是 *(a+2)
    printf("%d\n", p[2]);   // 30 ------ 指针也能用 []
    return 0;
}

运行情况:

记住三点:

  1. 数组名是首元素的地址 :a 和 &a[0] 是同一个数字,所以 int *p = a; 合法。
  2. a[i] 本质就是 *(a+i) :先首地址加偏移量,再解引用。[] 只是语法糖------所以只要手里有地址,p[2] 和 a[2] 都好使。
  3. 数组名是指针常量,改不了 :它相当于是const int *a,不能 a++、不能 a = p(门牌号改不了);p 是变量,可以 p++ 往后挪。

看到数组名,就看成"首元素的门牌号"。 以后传数组、遍历数组,还有学生系统里 scanf("%31s", stu.name) 不用写 &,都是因为这个。


5 总结

概念 含义 代码
地址 变量的门牌号 &x → 0x7fff...
指针 存地址的变量 int *p = &x;
*p 顺着地址看/改房子里的值 *p → 42
传地址 函数能改外面 add10(&n);
数组名 首元素的地址,能当指针用 int *p = a;
野指针 声明后没赋地址,装着垃圾值 int *p; 别乱用
NULL 空指针,谁都不指 int *p = NULL; 用前先判断
  1. 指针 = 存地址的变量,本身没魔法
  2. & 取地址,* 解引用------全部就这两个操作
  3. 传地址能改外面------数组传参能改外面的本质
  4. 数组名 = 首元素地址 :a[i] 就是 *(a+i),传数组就是传指针
  5. 声明指针先赋 NULL,用前先判断,躲开野指针的坑

到这里,你已经会用指针指一个变量、一个数组了。可真实的数据往往不是"一个数",而是"一坨"------比如一个学生,就有学号、姓名、三门成绩。

下一讲我们专门解决这件事:用指针指一整个结构体,看懂它在内存里怎么摆。学完那一讲,我们就正式动手做学生成绩管理系统。

相关推荐
Quz1 小时前
记录第一次使用Pi Agent,以及插件怎么选
agent·ai编程
Pniubi1 小时前
agent处理封号怎么解决误封问题
开发语言·python
.Hypocritical.2 小时前
【Java 进阶】泛型深度解析|泛型类、泛型方法、通配符、类型擦除、PECS 原则
java·开发语言·泛型
VX_bysjlw9852 小时前
基于PHP的论坛系统设计与实现 35136-计算机毕设原创(免费领源码+带部署教程)
开发语言·php·apache·laravel·thinkphp·phpstudy·wampserver
忆~遂愿2 小时前
电影照片散在硬盘里不好找?我用群晖+Plex整理成可远程访问的家庭媒体库
开发语言·php
挖掘狂人2 小时前
AI 写代码越来越快,我们的代码能力却在悄悄退化
人工智能·程序员·ai编程
前端双越老师2 小时前
程序员 1v1 访谈系列第三期:先不要想在工作中有突破
前端·ai编程·全栈
小李不想当小白2 小时前
USART串口数据包(STM32标准库学习笔记)
经验分享·笔记·stm32·单片机·嵌入式硬件·学习
codigger2 小时前
AI 写代码越来越快,我们的代码能力却在悄悄退化
程序员·编程·ai编程·#人工智能