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