位置:首页 > C > C语言指针入门:从地址、数组到函数调用一次讲清

C语言指针入门:从地址、数组到函数调用一次讲清

时间:2026-08-24  |  作者:火苗实验室  |  阅读:0

目录

  1. 指针基础:先分清地址、取址和解引用
  2. 指针运算:移动的不是 1 字节,而是一个数据单位
  3. 指针和数组:最容易混,但也最值得搞懂
  4. 指针和函数:参数、返回值与回调分别是什么关系
  5. 多级指针、空指针和野指针:最后要避开的坑
  6. 指针声明速查
归纳空指针、野指针和多级指针的关系与常见风险
多级指针与常见风险速览这张图适合放在收尾位置,帮助读者把概念和风险边界一起记住。
展示指针在函数中的三种常见角色:参数、返回值和回调
指针在函数里的三种角色把“传地址会改外部数据”“返回指针不能指向失效局部变量”“函数指针可做回调”放在一张图里。
用结构图对比数组名、首元素指针和数组指针的差异
数组名、首元素指针与数组指针对照图这一图把最容易混淆的三种写法放到同一坐标里,方便对照 `sizeof`、`+1` 和解引用结果。

前言

很多初学者第一次学 C 指针,卡住的并不是语法,而是不清楚“变量值”和“变量地址”到底是什么关系。本文按基础概念、与数组的联系、与函数的配合以及常见风险几部分重组内容,把容易混淆的几组概念放在一起讲清,读完后你可以更快判断一段指针代码到底在访问值、传地址,还是在跨类型移动内存。

很多初学者第一次学 C 指针,卡住的并不是语法,而是不清楚“变量值”和“变量地址”到底是什么关系。本文按基础概念、与数组的联系、与函数的配合以及常见风险几部分重组内容,把容易混淆的几组概念放在一起讲清,读完后你可以更快判断一段指针代码到底在访问值、传地址,还是在跨类型移动内存。

指针基础:先分清地址、取址和解引用

变量在 C 语言里至少有两种访问方式:

  • 直接访问:通过变量名访问
  • 间接访问:通过指针访问

什么是指针

用于专门存储地址的变量,就是指针变量,通常简称为指针。

系统位数不同,地址长度也不同:

  • 64 位系统使用 64 位表示地址,所以指针通常占用 8 字节
  • 32 位系统使用 32 位表示地址,所以指针通常占用 4 字节

指针怎么定义

一般格式如下:

数据类型 *标识符 = 地址;
int * num = &a;

下面几种写法都合法,本质没有区别:

int * num = &a;
int* num = &a;
int *num = &a;

`&` 和 `*` 分别表示什么

  • & 是取地址运算符
  • * 是取值运算符,也叫解引用,用来取出指针所指向地址中的数据
int a = 1;
int * num = &a;
printf("%d", *num);

这里 num 保存的是 a 的地址,而 *num 取出的才是这个地址里存放的值,也就是 1

指针运算:移动的不是 1 字节,而是一个数据单位

指针加减

指针和整数做加减,不是简单地按字节加 1、减 1,而是按“当前指针类型对应的数据单位”移动。

例如:

int *p = &a;

如果 int 占 4 字节,那么 p+1 表示地址加 4,p-1 表示地址减 4。

同类型指针相减

两个同类型指针相减,结果表示它们之间相差多少个同类型数据单位,而不是单纯差了多少字节。

  • 高位地址减去低位地址,结果为正
  • 低位地址减去高位地址,结果为负

这类结果的类型是 ptrdiff_t,它是有符号整数类型,格式化输出占位符是 %td

指针比较

指针比较本质上是比较地址大小,返回的是 int 类型结果,通常为 10

指针和数组:最容易混,但也最值得搞懂

数组名为什么常被当成指针用

数组名在大多数情况下会隐式转换为第一个元素的指针,只有在 sizeof 这种场景下不会发生这种转换。因此它看起来很像指针,也拥有指针的大部分行为。

例如:

arr[i] 其实就是 *(arr+i) 的语法糖。

但数组名和普通指针依然有两个关键区别:

  • 数组名是标识符常量,不能改指向;指针变量可以重新赋值
  • sizeof 数组名 得到的是整个数组大小;sizeof 指针 得到的是指针本身大小
#include 

int print(int arr[]) // 形参也为地址!
{
    printf("%zun", sizeof arr); // 8
}

int main()
{
    int arr[3] = {1, 2, 3};
    int (*p)[3] = arr;

    printf("%zun", sizeof arr); // 12
    printf("%pn", arr); // 地址
    printf("%pn", p);   // 地址

    return 0;
}

这个例子里,main 中的 sizeof arr 是整个数组大小;但作为函数参数传入后,arr[] 实际已经退化成地址,所以 sizeof arr 看到的是指针大小。

指针数组和数组指针别混淆

指针数组,是“数组”,里面的每个元素都是指针:

数据类型 *指针数组名[长度];
int *arr[3] = {&a, &b, &c};

数组指针,是“指针”,它指向的是整个数组:

数据类型 (*数组指针名)[长度];
int (*p)[3] = &arr;

这里要特别注意:数组指针指向的是整个数组地址,不是首元素地址。所以上面这个 p 的一个单位长度是整个数组大小;若数组共占 12 字节,那么 p+1 就表示地址加 12。

字符指针的特点

字符指针常用于处理字符和字符串:

char * pStr = "hello tom";

指向字符串字面量常量的字符指针,有几个常见特点:

  • 相对字符数组名,字符指针本身可以改为指向新的字符串
  • 不能通过下标去修改某个字符
  • 字符指针不能直接接收 scanf 输入的字符串

一张表看懂 arr、ptr 和 arr_ptr

int arr[5] = {10,20,30,40,50};   // arr 数组名
int * ptr = arr;                 // ptr 显式定义的首元素指针
int (*arr_ptr)[5] = &arr;         // arr_ptr 是数组指针
标识符性质数据类型sizeof 计算结果+1 的值* 取值(解引用)能否重新赋值
arr数组名int [5]整个数组的大小第二个元素地址首元素值
ptr首元素指针int *地址大小第二个元素地址首元素值
arr_ptr数组指针int (*)[5]地址大小数组后面地址(越界)数组名(首元素地址)

指针和函数:参数、返回值与回调分别是什么关系

指针传递

如果函数参数传的是普通非地址类型,那么实参会把值拷贝给形参,函数内外是两份独立数据。

如果传的是地址类型,那么函数内部修改该地址对应的数据,函数外部也会受到影响。

void fn(int arr[]); // arr 为地址

这也是为什么数组作为函数参数时,看起来写的是数组,实际上传进来的仍然是地址。

指针函数

如果一个函数的返回值是指针,这种函数就叫指针函数。

char * strlong(char *str1, char *str2)
{
    if (strlen(str1) >= strlen(str2))
    {
        return str1;
    }
    else
    {
        return str2;
    }
}

这里返回的是传入字符串地址中的一个。需要注意的是,指针函数不能返回局部变量的地址,因为函数结束后局部变量就销毁了;如果确实需要保留,可以返回静态局部变量的地址。

函数指针

函数指针本身是一个指针,只不过它指向的是函数。

函数名、&函数名*函数名 都可以获取函数代码地址,也都可以用于调用。

返回类型 (*函数指针名)(参数列表);
int max()
{
    ...
}

int (*p)(int, int) = &max;
...
max(); *max(); &max();
p(); *p();

回调函数

函数指针还能作为另一个函数的参数使用,这就是回调函数最典型的形式。

void initArray(int *array, int arraySize, int (*f)())
{
    for (int i = 0; i < arraySize; i++)
    {
        array[i] = f();
    }
}

int main()
{
    int myarray[10];
    // rand 是系统函数,会返回一个随机整数,位于  标准库中
    initArray(myarray, 10, rand);
}

这个例子中,initArray 不关心数据具体怎么产生,它只负责逐个调用传入的函数 f,把结果写入数组。

指针常量和常量指针

这两个名字很像,但限制对象不同。

指针常量:指针本身不能改。

int * const num = &a;

常量指针:通过该指针不能修改它指向的数据。

const int * num = &b; // b 可以不是常量,但通过 num 不能修改
int const * num = &c;

多级指针、空指针和野指针:最后要避开的坑

多级指针

多级指针就是“指向指针的指针”。数组指针、二维数组相关写法里,经常会遇到这种思路。

int *p = #
int **p2 = &p;

空指针

  • 被赋值为 NULL 的指针称为空指针
  • NULL 是标准库中定义的值为 0 的宏常量
  • 声明指针变量时,如果暂时没有明确地址,先赋值为 NULL 是较好的编程习惯
  • 空指针不能被解引用
int *p = NULL;

野指针

野指针是指指针指向的位置不可知,继续使用会有很高风险。

常见原因主要有三类:

  • 指针没有初始化
  • 指针越界
  • 指向已经失效的内存空间,比如函数调用结束后局部变量被销毁

指针声明速查

变量定义类型表示含义
int iinti 是一个整型变量
int *pint *p 是一个指向整型数据的指针
int a[5]int[5]a 是一个 5 个元素的整型数组
int *p[4]int *[4]p 是一个指针数组,每个元素都是指向整型数据的指针
int (*p)[4]int (*)[4]p 是一个数组指针,指向一个 4 个元素的整型数组
int f()int ()f 是一个返回整型数据的函数
int *f()int *()f 是一个指针函数,返回指向整数数据的指针
int (*p)()int (*)()p 是一个函数指针,指向一个返回整型数据的函数
int **pint **p 是一个指向指针的指针

免责声明:文中图文均来自网络,如有侵权请联系删除,心愿游戏发布此文仅为传递信息,不代表心愿游戏认同其观点或证实其描述。

相关文章

更多

精选合集

更多

大家都在玩

热门话题

大家都在看

更多