
title: 【C语言·高级】 指针date: 2025-03-24 22:17:43tags: [C,学习]categories: [学习]excerpt: “指针也就是内存地址指针变量是用来存放内存地址的变量。”目录指针指针变量指针赋值指针scanf与指针a和*p 与 a和p*p 和 aa 和 p结论空指针为什么要使用空指针总结无类型指针可以保存任意对象类型的地址指向任意类型不能直接解引用使用前必须转回原来的类型和空指针的区别void指针的数据类型转换int \*pvoid \*pvoid \*p 能否取整型变量的地址总结访问数组的方式指针变量的整数量增减两个指针变量的差值计算例子解释数学原理二维数组的赋值数组不能自增自减指针什么是指针我们以下面的代码为例inta10;int*p1a;在这里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为正常变量p1a;p2b;此时*p1 *p2;这个代码等价于a b;p1指向的地址是a变量的地址p2同理指针赋值指针inta10,*p,*q;pa;// p 指向 a 的地址qp;// q 现在也指向 a 的地址这行代码意味着 p 和 q 都指向同一个变量aq 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是指针变量inta10;int*pa;// p 是指向 a 的指针*p 和 a*p 是指针 p 解引用的结果即通过 p 获取它所指向的值也就是 a 的值。因此*p 和 a是一样的它们都代表 a 的值。printf(%d\n,*p);// 输出 10因为 *p 解引用了 p等价于 aa 和 pa 是 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 来表示空指针。空指针的主要目的是在指针没有实际地址可用时避免它指向一个随机或无效的内存位置减少访问无效内存的风险。空指针不指向任何有效的内存地址可以通过空指针检查代码中是否存在指针没有正确初始化的情况。空指针可以用于初始化指针确保指针不会意外地指向一个随机的内存地址。空指针不可解引用空指针不能解引用不能使用 * 访问它指向的值否则会导致程序崩溃或未定义行为。例子#includestdio.hintmain(){int*pNULL;// p 是一个空指针if(pNULL){printf(p 是空指针\n);}// 下面这行代码是错误的解引用空指针会导致崩溃// printf(%d\n, *p); // 会崩溃因为 p 没有指向任何有效的内存return0;}为什么要使用空指针初始化指针使用空指针初始化指针可以确保指针不随机指向某个内存位置防止野指针指向不确定地址的指针带来的潜在问题。指示无效值空指针可以作为一种标志表示某个操作或函数返回失败或者指针还没有分配有效内存。总结空指针就是指向 NULL 或 0 的指针它没有指向任何有效的内存地址常用于指示指针的无效状态或作为初始化指针的一种方式。## void指针void* 通常叫 void 指针也叫 通用指针泛型指针 或 无类型指针。它作为指针在内存中存放了了一个地址但不确定这个地址上放的是什么类型的数据无类型指针可以保存任意对象类型的地址指向任意类型inta10;doubleb3.14;void*p1a;void*p2b;其实标准允许任意对象指针隐式转换成 void*不需要强制转换。其他类型指针之间默认不能隐式互转。不能直接解引用因为编译器不知道它指向的数据类型和大小c void *p a; // *p; // 错误不能解引用 void* // p[0]; // 错误 // p; // 标准 C/C 中不允许因为 void 大小未知使用前必须转回原来的类型cc int a 10; void *p a; int *ip p; // C 中可隐式转换 printf(%d\n, *ip); // 10cppcpp int a 10; void *p a; int *ip static_castint*(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 *pint *p 是定义了一个指向 int 类型的指针意味着 p 可以存储 int 类型变量的地址。例如inta10;int*pa;// p 存储 a 的地址p 是 int 类型指针这里p 指向 a它是一个 int * 类型的指针可以通过 *p 访问 a 的值。void *pvoid *p 是定义了一个指向未知类型的指针也称为空指针。它不指向任何特定类型的变量所以你不能直接通过 *p 解引用它。void *p 允许你存储任意类型的指针地址但你必须在使用前将其转换成具体的类型。例如inta10;void*pa;// p 存储 a 的地址但 p 是 void 指针如果你想使用 void *p 来访问 a 的值你需要先将 void * 类型转换为 int *然后再解引用printf(%d\n, *(int *)p); // 需要转换 void * 为 int * 然后解引用void *p 能否取整型变量的地址是的void *p 可以存储 int 类型变量的地址。空指针 void * 可以指向任何类型的变量地址但是由于 void * 没有明确的类型信息它本身不能直接解引用。你需要将它转换成具体类型的指针如 int *后才能访问值。例子inta10;void*pa;// 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;//下面这两段代码等价pa[0];Pa;当打印这两个变量时会发现a[i]和p[i]输出是一样的由此得知数组是通过地址访问的a[i]代表着a首地址第i个地址指向的值与p[i]一样指针变量的整数量增减加减一个整数例如pp1可以让p[i]变成p[i1]通过修改指针变量的自增自减也可以改变p指向的地址例如inta[5]{1,2,3,4,5},*p;pa;p此时p指向的是a数组第2个int的内存地址也就是说指向的是 a[1]Tips :p[i] 与 *(pi)是等价的两个指针变量的差值计算在 C 语言中两个指针变量的减法是指针之间的差值计算。这种操作的结果不是直接的数值差异而是两个指针之间相差的元素数量而非字节数。这是因为指针的加减是基于它们指向的元素类型的大小来进行的。例子假设你有两个指向同一类型数组的指针 p 和 q那么 p - q 会返回它们之间相差的元素个数而不是字节数。#includestdio.hintmain(){intarr[]{10,20,30,40,50};int*parr[1];// p 指向 arr[1]int*qarr[4];// q 指向 arr[4]intdiffq-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;pa;for(i0;i9;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