☰
【C语言·高级】指针
2026/10/13 1:47:22 网站建设 项目流程

title: 【C语言·高级】 指针
date: 2025-03-24 22:17:43
tags: [C,学习]
categories: [学习]
excerpt: “指针也就是内存地址,指针变量是用来存放内存地址的变量。”

目录

    • 指针
    • 指针变量
    • 指针赋值指针
    • scanf与指针
    • a和*p 与 &a和p
      • *p 和 a
      • &a 和 p
      • 结论
    • 空指针
      • 为什么要使用空指针?
      • 总结
      • 无类型指针可以保存任意对象类型的地址(指向任意类型)
      • 不能直接解引用
      • 使用前必须转回原来的类型
      • 和空指针的区别
    • void指针的数据类型转换
      • int \*p
      • void \*p:
      • void \*p 能否取整型变量的地址?
      • 总结
    • 访问数组的方式
    • 指针变量的整数量增减
    • 两个指针变量的差值计算
      • 例子
      • 解释
      • 数学原理
    • 二维数组的赋值
    • 数组不能自增自减

指针

什么是指针?我们以下面的代码为例:

inta=10;int*p1=&a;

在这里,&a 是 a 的地址,p1 就是存储这个地址的变量。指针本身存储的是地址,而不是数据本身。因此,p1 的值就是一个地址,它指向一个 int 类型的变量。

如果你是问指针 p1 本身的地址,可以通过&p1来获取。

也就是说p1是一个专门用来存储int类型的地址变量,我们管它叫指针变量


指针变量

p1 是一个指针变量,它专门用来存储指向 int 类型数据的地址。我们通常叫它指针,它存储的是一个地址,而这个地址指向的内存空间保存的是 int 类型的数据。

简而言之,指针变量就是一个存放地址的变量,指向某种特定类型的数据。在你这个例子中,int *p1 表示 p1 是一个存储 int 类型数据地址的指针变量。



&求址运算符 &[简单变量]

* 指向运算符 *[指针变量]



指针变量声明的时候要在前面加上*,此时这个*不是运算符,只是告诉编译器后面的变量为指针变量,例如:

  • int *p1;

    指的是声明一个叫做p1的指针变量,*是用来表示p1为指针,并不是指向运算符

  • p1 = &a;

    此时运用求址运算符把a的地址赋值给一个指针变量

  • printf("%d\n", *p1);

    此时使用指向运算符接着一个指针变量,这代表着实际上*p1表示的是p1地址对应的值(也就是输出a)



假设我们有一串代码,p开头的是指针变量,a,b为正常变量

p1=&a;p2=&b;

此时

*p1 = *p2;

这个代码等价于a = b;

p1指向的地址是a变量的地址,p2同理


指针赋值指针


inta=10,*p,*q;p=&a;// p 指向 a 的地址q=p;// q 现在也指向 a 的地址

这行代码意味着 p 和 q 都指向同一个变量a


q = p; 这行代码的作用是让 q 指向 p 指向的地址。也就是说,q 现在指向和 p 一样的内存地址,也就是 a 的地址。

从这时起,p 和 q 都指向同一个内存位置,也就是 a。


scanf与指针

int a, *pa = &a;

如果定义了以上变量,想要scanf赋值给a应该怎么写?

{% folding black::答案:点击展开 %}

scanf("%d",pa);scanf("%d",&a);

{% note tip %}
在scanf中,你需要传入变量的地址,来让scanf能够在该地址上写入输入的值。所以,scanf的作用是通过地址来修改变量的值
{% endnote %}
{% endfolding %}




a和*p 与 &a和p

我们了解了指针的基本原理,那么是否有一个猜想:*p 和 a与&a 和 p都各自相同?

答案:正确,但要注意区分是值还是地址:


假设你有以下代码,并且a是变量p是指针变量:

inta=10;int*p=&a;// p 是指向 a 的指针

*p 和 a

*p 是指针 p 解引用的结果,即通过 p 获取它所指向的值(也就是 a 的值)。因此,*p 和 a是一样的,它们都代表 a 的值。

printf("%d\n",*p);// 输出 10,因为 *p 解引用了 p,等价于 a

&a 和 p

  • &a 是 a 的地址,也就是 p 存储的内容。所以,&a 和 p是一样的,它们都代表了 a 的地址。
printf("%p\n",(void*)&a);// 输出 a 的地址printf("%p\n",(void*)p);// 输出 p 的值,实际上是 a 的地址

结论

因此,我们得出的结论是:

*p 和 a是相同的,它们代表 a 的值。

&a 和 p是相同的,它们代表 a 的地址。




空指针

空指针(Null Pointer)是指一个没有指向任何有效的内存地址的指针变量。它通常用来表示指针目前不指向任何有效的对象或数据。

在 C 语言中,空指针通常由宏 NULL 表示,或者在某些情况下,你也可以用 0 来表示空指针。空指针的主要目的是在指针没有实际地址可用时,避免它指向一个随机或无效的内存位置,减少访问无效内存的风险。


空指针不指向任何有效的内存地址,可以通过空指针检查代码中是否存在指针没有正确初始化的情况。

空指针可以用于初始化指针,确保指针不会意外地指向一个随机的内存地址。

空指针不可解引用:空指针不能解引用(不能使用 * 访问它指向的值),否则会导致程序崩溃或未定义行为。


例子:

#include<stdio.h>intmain(){int*p=NULL;// p 是一个空指针if(p==NULL){printf("p 是空指针\n");}// 下面这行代码是错误的,解引用空指针会导致崩溃// printf("%d\n", *p); // 会崩溃,因为 p 没有指向任何有效的内存return0;}


为什么要使用空指针?

初始化指针:使用空指针初始化指针可以确保指针不随机指向某个内存位置,防止野指针(指向不确定地址的指针)带来的潜在问题。

指示无效值:空指针可以作为一种标志,表示某个操作或函数返回失败,或者指针还没有分配有效内存。



总结

空指针就是指向 NULL 或 0 的指针,它没有指向任何有效的内存地址,常用于指示指针的无效状态或作为初始化指针的一种方式。


## void指针

void* 通常叫 void 指针,也叫 通用指针(泛型指针) 或 无类型指针。它作为指针,在内存中存放了了一个地址,但不确定这个地址上放的是什么类型的数据

无类型指针可以保存任意对象类型的地址(指向任意类型)

inta=10;doubleb=3.14;void*p1=&a;void*p2=&b;

其实,标准允许任意对象指针隐式转换成 void*,不需要强制转换。其他类型指针之间默认不能隐式互转。

不能直接解引用

因为编译器不知道它指向的数据类型和大小

c void *p = &a; // *p; // 错误:不能解引用 void* // p[0]; // 错误 // p++; // 标准 C/C++ 中不允许,因为 void 大小未知

使用前必须转回原来的类型

c

c int a = 10; void *p = &a; int *ip = p; // C 中可隐式转换 printf("%d\n", *ip); // 10

cpp

cpp int a = 10; void *p = &a; int *ip = static_cast<int*>(p); // C++ 需要显式转换 std::cout << *ip << std::endl;

和空指针的区别

void和空指针但不是一个东西,void* 是一种指针类型;NULL / nullptr 表示空指针。 ```c void *p = NULL; // p 是 void 指针,当前不指向任何对象

void指针的数据类型转换

你可能有疑问:
int *p是定义一个整型指针,void *p是定义 一个空指针,int指针可以被整形变量取地址,那空指针能吗?

int *p 和 void *p 确实是定义了不同类型的指针变量。让我们逐一解释:


int *p

int *p 是定义了一个指向 int 类型的指针,意味着 p 可以存储 int 类型变量的地址。

例如:

inta=10;int*p=&a;// p 存储 a 的地址,p 是 int 类型指针

这里,p 指向 a,它是一个 int * 类型的指针,可以通过 *p 访问 a 的值。


void *p:

void *p 是定义了一个指向未知类型的指针,也称为空指针。它不指向任何特定类型的变量,所以你不能直接通过 *p 解引用它。

void *p 允许你存储任意类型的指针地址,但你必须在使用前将其转换成具体的类型。例如:

inta=10;void*p=&a;// p 存储 a 的地址,但 p 是 void 指针

如果你想使用 void *p 来访问 a 的值,你需要先将 void * 类型转换为 int *,然后再解引用:

printf("%d\n", *(int *)p); // 需要转换 void * 为 int * 然后解引用


void *p 能否取整型变量的地址?

是的,void *p 可以存储 int 类型变量的地址。空指针 void * 可以指向任何类型的变量地址,但是由于 void * 没有明确的类型信息,它本身不能直接解引用。你需要将它转换成具体类型的指针(如 int *)后才能访问值。

例子:

inta=10;void*p=&a;// p 存储 a 的地址(p 是 void 指针)printf("%d\n",*(int*)p);// 将 void * 转换为 int *,然后解引用

总结

int *p 是一个指向 int 类型的指针,存储 int 类型变量的地址,且可以直接解引用。

void *p 是一个通用指针,可以存储任意类型的地址,包括 int 类型的地址,但在解引用前必须先将其转换为特定类型的指针。




访问数组的方式

inta[5]={1,2,3,4,5},*p;//下面这两段代码等价p=&a[0];P=a;

当打印这两个变量时,会发现a[i]和p[i]输出是一样的
由此得知,数组是通过地址访问的,a[i]代表着a(首地址)第i个地址指向的值
与p[i]一样


指针变量的整数量增减

加减一个整数,例如p++,p+=1,可以让p[i]变成p[i+1]

通过修改指针变量的自增自减,也可以改变p指向的地址

例如:

inta[5]={1,2,3,4,5},*p;p=a;p++;

此时p指向的是a数组第2个int的内存地址,也就是说指向的是 a[1]



Tips :p[i] 与 *(p+i)是等价的




两个指针变量的差值计算

在 C 语言中,两个指针变量的减法是指针之间的差值计算。这种操作的结果不是直接的数值差异,而是两个指针之间相差的元素数量,而非字节数。这是因为指针的加减是基于它们指向的元素类型的大小来进行的。

例子

假设你有两个指向同一类型数组的指针 p 和 q,那么 p - q 会返回它们之间相差的元素个数,而不是字节数。

#include<stdio.h>intmain(){intarr[]={10,20,30,40,50};int*p=&arr[1];// p 指向 arr[1]int*q=&arr[4];// q 指向 arr[4]intdiff=q-p;// 计算指针之间的差值printf("The difference between p and q is: %d\n",diff);// 输出指针间的元素个数return0;}

运行:

The difference between p and q is: 3

解释

p 指向 arr[1],而 q 指向 arr[4]。

q - p 计算的是 q 和 p 之间的元素差异,在这个例子中,是 4 - 1 = 3,即 p 和 q 相差 3 个 int 元素。

注意:这是基于指针所指向的元素类型的大小。例如,如果指针是 int * 类型,那么它们之间的差值表示的是 int 类型元素的数量,而不是字节数。


数学原理

指针相减的结果是两个指针相差的元素个数。例如,p 和 q 是指向同一类型数组的指针,那么 p - q 计算的是它们之间相差的“元素数”,而不是字节数。

  • p - q 返回的是 p 和 q 之间的元素数量。
  • 计算时,指针会根据其指向的类型的大小(例如,int 类型为 4 字节)自动进行相应的计算,而不需要你手动去计算字节差。

总结:

  • 指针减法返回的是两个指针之间相差的元素个数,而不是字节数。
  • 计算结果是基于指针所指向的元素类型的大小的,指针相减会返回“元素数”而非字节差。



二维数组的赋值


inta[3][3],*p,i;p=a;for(i=0;i<9;i++)p[i]=i;

提问:这块的p[i] = i;赋值的时候不会数组越界吗?


{% folding black::答案:点击展开 %}

在这段代码中,p[i] = i; 并不会导致数组越界,原因在于指针 p 的行为。

解释:

a是一个二维数组:

a[3][3] 表示一个 3x3 的二维数组。二维数组的内存布局是连续的,也就是说,a[0][0], a[0][1], …, a[2][2] 都是存储在一块连续的内存区域中。

指针p指向一维数组:

p = a; 使得 p 指向二维数组 a 的首元素 a[0]。在内存中,二维数组 a 可以视为一个一维数组,包含了 9 个元素。也就是说,p 实际上是指向一个连续内存区域,存储了二维数组 a 的所有元素。指针 p 的类型是 int*,它可以访问这块连续的内存区域中的每个元素。

p[i] = i;的行为:

p[i] 表示指针 p 向后移动 i 个元素的位置,依次访问内存中连续的元素。所以,p[i] 并不是访问二维数组 a[i/3][i%3],而是直接访问一维数组中的第 i 个元素。当 i 从 0 到 8 时,p[i] 会依次访问二维数组 a 的每个元素,并赋值为 i。

也就是说,p[i] = i; 并不会导致数组越界,因为 p 访问的是一个连续内存区域,而 a 是一个 3x3 的二维数组,实际上它在内存中是连续的 9 个 int 元素。

{% endfolding %}



数组不能自增自减

数组不能自增或者自减,尽管它有着和指针变量相似的"指向地址"的作用


在 C 或 C++ 中,数组名其实是一个指向数组首元素的指针。因此,当你尝试对数组名进行自增或自减时,你实际上是在改变这个指针的指向位置,而不是改变数组本身。这会导致数组头部的位置改变(这本来就是你的职责啊喂),从而引发一些意外的结果。





Email | GitHub | Bilibili

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询