看完这一篇还学不会指针?

本文详细介绍了C语言中的指针概念,包括内存地址、指针变量、基本运算、数组与指针的结合、不同类型的指针(如const修饰、数组指针、函数指针)、回调函数以及野指针和assert断言的使用。适合初学者和进阶者深入理解指针在编程中的关键作用。

目录

前言

一、指针——初阶

1、内存和地址

2、指针变量和地址

(1)取地址操作符

(2)指针变量

(3)指针类型

(4)解引用操作符

3、指针变量类型的意义

4、const修饰指针变量

5、指针运算

(1)指针加减整数

(2)指针-指针

(3)指针的关系运算

6、野指针

7、assert断言

二、指针在数组中的妙用

1、数组名的理解

2、一维数组传参的本质

3、二级指针

4、指针数组

三、指针类型——进阶

1、数组指针变量

2、二维数组传参本质

3、函数指针变量

4、函数指针数组

四、回调函数


前言

本章我们将学习指针,从初识到进阶完整的向大家剖析指针的原理及应用,文章分为指针——初阶、指针在数组中的妙用、指针类型——进阶、回调函数实际应用四个部分

第一部分包含指针的一些基础知识,为深入理解指针打好基础;第二部分阐释指针在数组中的应用,通过指针更好的运用数组;第三部分向大家介绍几种复杂一些的指针和数组类型及相关的实际应用;第四部分主要介绍回调函数的概念及意义,小伙伴们可以根据自身需求选择对应板块观看

一、指针——初阶

1、内存和地址

我们先讲一个生活中的案例

假设有一栋宿舍楼,把你放在楼里,楼上有100个房间,但是房间没有编号,你的一个朋友来找你玩, 如果想找到你,就得挨个房子去找,这样效率很低,但是我们如果根据楼层和楼层的房间的情况,给每个房间编上号

有了房间号,如果你的朋友得到房间号,就可以快速的找到房间,找到你

如果把上面的例子对照到计算中,又是怎么样呢? 我们知道计算上CPU(中央处理器)在处理数据的时候,需要的数据是在内存中读取的,处理后的数据也会放回内存中,那我们买电脑的时候,电脑上内存是8GB/16GB/32GB等,那这些内存空间如何高效的管理呢? 其实也是把内存划分为一个个的内存单元,每个内存单元的大小取1个字节

其中,每个内存单元,相当于一个学生宿舍,一个字节空间里面能放8个比特位,就好比同学们住的八人间,每个人是一个比特位

每个内存单元也都有一个编号(这个编号就相当 于宿舍房间的⻔牌号),有了这个内存单元的编 号,CPU就可以快速找到一个内存空间

生活中我们把门牌号也叫地址,在计算机中我们把内存单元的编号也称为地址。C语⾔中给地址起 了新的名字叫:指针

所以我们可以理解为: 内存单元的编号==地址==指针

2、指针变量和地址

(1)取地址操作符

我们了解完地址的概念,下面教大家怎样取出一个元素的地址,假设我们创建了一个整型变量a

int a = 10

创建完成后会向内存空间申请一块内存来储存a的值,由于a是整型变量,在内存空间中会占四个字节,也就是说系统会为它分配四个地址,这四个地址指向的空间是连续存放的,我们为了方便使用,取其中最小编号的一块作为a的地址,这样顺藤摸瓜就能找到其余的三块空间

如何将这个地址取出呢,我们这时就要用到取地址操作符  &  ,&a即表示取出a的地址

(2)指针变量

那我们通过取地址操作符(&)拿到的地址是一个数值,比如:0x006FFD70,这个数值有时候也是需要存储起来,方便后期再使用的,那我们把这样的地址值存放在哪里呢?答案是:指针变量

#include <stdio.h>
 int main()
{
 int a = 10;
 int* pa = &a;
 //取出a的地址并存储到指针变量pa中 
 return 0;
}

指针变量也是⼀种变量,这种变量就是用来存放地址的,存放在指针变量中的值都会理解为地址

32位平台下地址是32个bit位,指针变量大小是4个字节

64位平台下地址是64个bit位,指针变量大小是8个字节

注意指针变量的大小和类型是无关的,只要指针类型的变量,在相同的平台下,大小都是相同的

(3)指针类型

我们看到pa的类型是  int* ,我们该如何理解指针的类型呢?

这⾥pa左边写的是 int* , * 是在说明pa是指针变量,而前面的 int 是在说明pa指向的是整型(int)类型的对象

(4)解引用操作符

我们将地址保存起来,未来是要使用的,那怎么使用呢? 在现实生活中,我们使用地址要找到一个房间,在房间里可以拿取或者存放物品

 C语言中其实也是一样的,我们只要拿到了地址(指针),就可以通过地址(指针)找到地址(指针) 指向的对象,这里必须学习一个操作符叫解引用操作符(*)

 int main()
 {
 int a = 100;
 int* pa = &a;
 *pa = 0;
 return 0;
 }

上⾯代码中第5行就使用了解引用操作符, *pa 的意思就是通过pa中存放的地址,找到指向的空间, *pa其实就是a变量了;所以*pa=0,这个操作符是把a改成了0

3、指针变量类型的意义

(1)指针的类型决定了,对指针解引用的时候有多大的权限(一次能操作几个字节)。 比如:char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节

(2) char* 类型的指针变量+1跳过1个字节, 这就是指针变量的类型差异带来的变化。 int* 类型的指针变量+1跳过了4个字节。 结论:指针的类型决定了指针向前或者向后走一步有多大(距离)

(3)在指针类型中有一种特殊的类型是 void* 类型的,可以理解为无具体类型的指针(或者叫泛型指针),这种类型的指针可以用来接受任意类型地址。但是也有局限性,行指针的+-整数和解引用的运算

4、const修饰指针变量

我们看下面代码,来分析

 #include <stdio.h>
 //
代码
1 
void test1()
 {
 int n = 10;
 int m = 20;
 int *p = &n;
 *p = 20;//ok?
 p = &m; //ok?
 }
 void test2()
 {
 //
代码
2 
int n = 10;
 int m = 20;
 const int* p = &n;
 *p = 20;//ok?
 p = &m; //ok?
 }
 void test3()
 {
 int n = 10;
 int m = 20;
 int *const p = &n;
 *p = 20; //ok?
 p = &m;  //ok?
 }
 void test4()
 {
 int n = 10;
 int m = 20;
 int const * const p = &n;
 *p = 20; //ok?
 p = &m;  //ok?
 }
 int main()
 {
 //
测试⽆
const
修饰的情况
 
test1();
 //
测试
const
放在
*
的左边情况
 
test2();
 //
测试
const
放在
*
的右边情况
 
test3();
 //
测试
*
的左右两边都有
const 
test4();
 return 0;
 }

结论:const修饰指针变量的时候

• const如果放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。 但是指针变量本⾝的内容可变

 • const如果放在*的右边,修饰的是指针变量本⾝,保证了指针变量的内容不能修改,但是指针指 向的内容,可以通过指针改变

5、指针运算

指针的基本运算有三种,分别是:

• 指针+-整数

• 指针-指针

• 指针的关系运算

(1)指针加减整数

因为数组在内存中是连续存放的,只要知道第⼀个元素的地址,顺藤摸⽠就能找到后⾯的所有元素


 #include <stdio.h>
 //指针+- 整数
 int main()
 {
 int arr[10] = {1,2,3,4,5,6,7,8,9,10};
 int *p = &arr[0];
 int i = 0;
 int sz = sizeof(arr)/sizeof(arr[0]);
 for(i=0; i<sz; i++)
 { 
  printf("%d ", *(p+i));//p+i 这⾥就是指针+整数
 }
 return 0;
 }
(2)指针-指针
 //指针-指针
 
#include <stdio.h>
 int my_strlen(char *s)
 {
 char *p = s;
 while(*p != '\0' )
 p++;
 return p-s;
 }
 int main()
 {
 printf("%d\n", my_strlen("abc"));
 return 0;
 }
(3)指针的关系运算
 //指针的关系运算
 
#include <stdio.h>
 int main()
 {
 int arr[10] = {1,2,3,4,5,6,7,8,9,10};
 int *p = &arr[0];
 int i = 0;
 int sz = sizeof(arr)/sizeof(arr[0]);
 while(p<arr+sz) //指针的⼤⼩⽐较
 
 {
 printf("%d ", *p);
 p++;
 }
 return 0;
 }

6、野指针

概念:野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

成因有三点:

(1) 指针未初始化

(2)指针越界访问

(3)指针指向已经被释放的内存空间

7、assert断言

assert.h 头文件定义了宏 assert() ,用于在运行时确保程序符合指定条件,如果不符合,就报错终止运行。这个宏常常被称为“断言”

二、指针在数组中的妙用

1、数组名的理解

数组名就是数组首元素(第一个元素)的地址

两个例外:

(1)sizeof(数组名),sizeof中单独放数组名,这⾥的数组名表示整个数组,计算的是整个数组的大小, 单位是字节

(2)&数组名,这里的数组名表示整个数组,取出的是整个数组的地址(整个数组的地址和数组首元素 的地址是有区别的)

除此之外,任何地方使用数组名,数组名都表示首元素的地址

&arr[0]和&arr[0]+1相差4个字节,arr和arr+1相差4个字节,是因为&arr[0]和arr都是首元素的地址,+1就是跳过一个元素。 但是&arr和&arr+1相差40个字节,这就是因为&arr是数组的地址,+1操作是跳过整个数组的

本质上p[i]是等价于*(p+i),同理arr[i] 应该等价于*(arr+i)

2、一维数组传参的本质

本质上数组传参传递的是数组首元素的地址

一维数组传参,形参的部分可以写成数组的形式,也可以写成指针的形式

3、二级指针

上述所说的指针是一级指针,我们要想存储一级指针的地址,这便引出了二级指针的概念,二级指针里存储的便是一级指针的地址

*ppa 通过对ppa中的地址进行解引用,这样找到的是 pa , *ppa 其实访问的就是 pa

**ppa 先通过 *ppa 找到 pa ,然后对pa 进行解引用操作: *pa ,那找到的是 a

4、指针数组

指针数组的每个元素都是用来存放地址(指针)的

指针数组的每个元素是地址,又可以指向一块区域

三、指针类型——进阶

1、数组指针变量

之前我们学习了指针数组,指针数组是⼀种数组,数组中存放的是地址(指针)。 数组指针变量是指针变量?还是数组? 答案是:指针变量

数组指针变量应该是:存放的应该是数组的地址,能够指向数组的指针变量

 int (*p)[10];

2、二维数组传参本质

首先我们再次理解一下二维数组,二维数组起始可以看做是每个元素是一维数组的数组,也就是二维数组的每个元素是一个一维数组。那么二维数组的首元素就是第一行,是个一维数组

所以,根据数组名是数组首元素的地址这个规则,二维数组的数组名表示的就是第一行的地址,是一维数组的地址

3、函数指针变量

什么是函数指针变量呢? 根据前⾯学习整型指针,数组指针的时候,我们的类比关系,我们不难得出结论: 函数指针变量应该是用来存放函数地址的,未来通过地址能够调用函数的

 int(*pf3)(int, int) = Add;
 int(*pf3)(int x, int y) = &Add;
//x和y写上或者省略都是可以的

4、函数指针数组

要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?

int (*parr1[3])();

四、回调函数

回调函数是什么? 回调函数就是一个通过函数指针调用的函数。 如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,被调用的函数就是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应


本章到这里就结束啦,希望各位小伙伴可以点个关注,支持一下阿鹿,大家的鼓励是我前进的第一动力,3Q~

<think> 嗯,用户的问题是关于在Android环境下使用C++进行文件读写操作时文件指针的位置变化。用户特别想知道在执行write操作后立即进行read操作,文件指针是否停留在写入数据的最后一个字节之后。 从用户的问题描述来看,用户已经理解文件指针的基本概念,但需要确认在Android环境下的具体行为。用户引用了三篇关于C++文件操作的技术资料,其中提到write操作会使文件指针向后移动,以及read操作会从当前文件指针位置开始读取。这说明用户对文件指针机制有一定基础认识,但可能遇到实际开发中的具体问题。 需要明确几个关键点:首先,Android系统底层文件操作与标准C++一致,因为Android NDK使用的就是标准C++库。其次,文件指针是跨操作的,即读写共享同一个指针位置。最后,Android的特殊文件系统(如FAT32或ext4)不会改变这个基础机制。 用户可能正在开发需要频繁读写二进制数据的应用场景,比如游戏存档或传感器数据记录。ta的困惑点在于是否需要在read前手动调整文件指针位置。实际上用户真正关心的可能是如何确保读写位置同步,避免数据错位。 根据C++标准库规范,当调用write()写入N字节后,文件指针会准确停留在最后一个写入字节的下一个位置。如果此时立即调用read(),确实会从这个位置开始读取。不过用户需要注意:如果文件以追加模式(ios::app)打开,初始指针会在文件末尾;如果以截断模式(ios::trunc)打开,指针则在文件开头。 特别提醒用户容易忽略的坑:二进制读写时需要严格保持数据结构一致性,跨平台时还需处理字节序问题。建议用户在实际开发中使用tellg()/tellp()验证指针位置,或通过seek操作显式定位更可靠。 用户的问题质量很高,显示出对底层机制的深入思考。不过ta可能低估了标准库的跨平台一致性——Android NDK环境下的fstream行为与Linux/Windows完全一致,这个问题的答案其实与操作系统无关。 </think> 在 Android 环境下使用 C++ (通常通过 NDK) 进行文件操作 (`write` 后进行 `read`),**`read` 操作开始时的文件指针位置取决于文件打开模式和操作顺序**,但根据标准 C++ 库的行为 (在 Android NDK 中同样遵循): 1. **共享指针位置:** `fstream` 对象(或同时使用 `ofstream` 写入和 `ifstream` 读取同一个文件时)内部维护一个**单一的、共享的文件位置指针**,用于跟踪下次读取或写入操作发生的位置。这个指针既不是单独的读指针也不是单独的写指针[^1][^2]。 2. **`write` 操作的影响:** 当调用 `write(buffer, size)` 写入 `size` 个字节的数据时: * 写入操作从**当前文件位置指针**指向的地方开始。 * 成功写入 `size` 个字节后,文件位置指针会**自动向后移动 `size` 个字节**,指向刚写入数据的**最后一个字节的下一个字节**的位置[^1][^2]。 3. **紧接着的 `read` 操作:** 如果在 `write` 操作之后立即调用 `read(buffer, size)`: * 这个 `read` 操作会从**当前文件位置指针**开始读取数据。 * 由于 `write` 操作已经将文件位置指针移动到了它写入数据的末尾之后,**这个 `read` 操作将从写入数据的最后一个字节之后的位置开始读取**。 4. **关键结论:** 是的,在同一个 `fstream` 对象上(或同一个文件描述符上)执行 `write` 操作后紧接着执行 `read` 操作,`read` 操作开始时的文件指针位置**确实是在之前 `write` 操作写入的数据的最后一个字节之后**。 **图示说明:** ``` 初始文件内容: | A | B | C | (EOF) 文件指针位置: ^ (指向开头 'A' 之前) 写入 "XYZ" (write): 写入前指针: ^ (指向 'A' 前) 写入 "XYZ" 后指针移动到: ^ (指向 'C' 之后,EOF 之前) 文件变为: | X | Y | Z | A | B | C | (假设 overwrite 模式,或追加后) 或 | A | B | C | X | Y | Z | (追加模式) 写入前指针^ -> 写入后指针^ 此时调用 read: 开始位置: ^ (即 'Z' 之后) 读取到的将是 'Z' 之后的内容 (可能是原来的数据后续部分,也可能是 EOF)。 ``` **重要注意事项:** * **打开模式:** 文件打开模式 (`ios::in`, `ios::out`, `ios::app`, `ios::ate`, `ios::binary` 等) 会影响初始文件指针位置和写入行为。 * 例如,使用 `ios::app`(追加模式)打开时,所有写入都在文件末尾进行,并且每次写入前指针都会自动移动到文件末尾,写入后指针也停留在新末尾之后。 * 使用 `ios::ate` 打开时,初始指针在文件末尾,但后续写入位置可由用户控制。 * **读写切换:** 当你在读操作后切换为写操作,或写操作后切换为读操作时,**通常需要在切换时刷新缓冲区或重新定位指针** (`flush()`, `seekg()`, `seekp()`),尤其是在没有使用 `ios::binary` 模式或涉及文本转换的场景下,以确保操作发生在预期位置。虽然标准规定共享指针位置,但显式操作更安全可靠。 * **二进制模式:** 如果处理的是二进制数据(而非文本),**强烈建议使用 `ios::binary` 模式打开文件**,以避免平台相关的换行符转换等问题影响文件指针的精确位置[^3]。 **总结:** 在 Android NDK 的 C++ 文件 I/O 中,对于同一个 `fstream` 对象(或同一个底层文件描述符),执行 `write` 操作后,文件内部的共享位置指针会被更新到所写入数据的末尾之后。紧接着执行 `read` 操作时,读取**确实会从这个新的位置(写入数据末尾之后)开始**。理解文件位置指针的行为对于正确操作文件内容至关重要[^1][^2]。
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值