一、fopen简介

在C语言中,操作文件之前必须先打开文件;所谓“打开文件”,就是让程序和文件建立连接的过程。

打开文件之后,程序可以得到文件的相关信息,例如大小、类型、权限、创建者、更新时间等。在后续读写文件的过程中,程序还可以记录当前读写到了哪个位置,下次可以在此基础上继续操作。

标准输入文件 stdin(表示键盘)、标准输出文件 stdout(表示显示器)、标准错误文件 stderr(表示显示器)是由系统打开的,可直接使用。

1.1 fopen函数

使用 <stdio.h> 头文件中的 fopen() 函数即可打开文件,它的用法为:

FILE *fopen(char *filename, char *mode);

filename为文件名(包括文件路径),mode为打开方式,它们都是字符串。

1.2 fopen() 函数的返回值

fopen() 会获取文件信息,包括文件名、文件状态、当前读写位置等,并将这些信息保存到一个 FILE 类型的结构体变量中,然后将该变量的地址返回。

FILE<stdio.h> 头文件中的一个结构体,它专门用来保存文件信息。我们不用关心 FILE 的具体结构,只需要知道它的用法就行。

如果希望接收 fopen() 的返回值,就需要定义一个 FILE 类型的指针。例如:

FILE *fp = fopen("demo.txt", "r");

表示以“只读”方式打开当前目录下的 demo.txt 文件,并使 fp 指向该文件,这样就可以通过 fp 来操作 demo.txt 了。fp 通常被称为文件指针

再来看一个例子:

FILE *fp = fopen("D:\\\\demo.txt","rb+");

表示以二进制方式打开 D 盘下的 demo.txt 文件,允许读和写

1.2.1 判断文件是否打开成功

打开文件出错时,fopen() 将返回一个空指针,也就是 NULL,我们可以利用这一点来判断文件是否打开成功,请看下面的代码:

FILE *fp;
if( (fp=fopen("D:\\demo.txt","rb")) == NULL ){printf("Fail to open file!\n");exit(0);  //退出程序(结束程序)
}

我们通过判断 fopen() 的返回值是否和 NULL 相等来判断是否打开失败:
如果 fopen() 的返回值为 NULL,那么 fp 的值也为 NULL,此时 if 的判断条件成立,表示文件打开失败。

以上代码是文件操作的规范写法,读者在打开文件时一定要判断文件是否打开成功,因为一旦打开失败,后续操作就都没法进行了,往往以“结束程序”告终。

1.3 fopen() 函数的打开方式

不同的操作需要不同的文件权限。例如,只想读取文件中的数据的话,“只读”权限就够了;既想读取又想写入数据的话,“读写”权限就是必须的了。

另外,文件也有不同的类型,按照数据的存储方式可以分为二进制文件和文本文件,它们的操作细节是不同的。

在调用 fopen() 函数时,这些信息都必须提供,称为“文件打开方式”。最基本的文件打开方式有以下几种:

  • 控制读写权限的字符串(必须指明)
打开方式 说明
“r” 以“只读”方式打开文件。只允许读取,不允许写入。文件必须存在,否则打开失败。
“w” 以“写入”方式打开文件。如果文件不存在,那么创建一个新文件;如果文件存在,那么清空文件内容(相当于删除原文件,再创建一个新文件)。
“a” 以“追加”方式打开文件。如果文件不存在,那么创建一个新文件;如果文件存在,那么将写入的数据追加到文件的末尾(文件原有的内容保留)。
“r+” 以“读写”方式打开文件。既可以读取也可以写入,也就是随意更新文件。文件必须存在,否则打开失败。
“w+” 以“写入/更新”方式打开文件,相当于w和r+叠加的效果。既可以读取也可以写入,也就是随意更新文件。如果文件不存在,那么创建一个新文件;如果文件存在,那么清空文件内容(相当于删除原文件,再创建一个新文件)。
“a+” 以“追加/更新”方式打开文件,相当于a和r+叠加的效果。既可以读取也可以写入,也就是随意更新文件。如果文件不存在,那么创建一个新文件;如果文件存在,那么将写入的数据追加到文件的末尾(文件原有的内容保留)。
  • 控制读写方式的字符串(可以不写)
打开方式 说明
“t” 文本文件。如果不写,默认为"t"。
“b” 二进制文件。

调用 fopen() 函数时必须指明读写权限,但是可以不指明读写方式(此时默认为"t")。

读写权限和读写方式可以组合使用,但是必须将读写方式放在读写权限的中间或者尾部(换句话说,不能将读写方式放在读写权限的开头)。例如:

  • 将读写方式放在读写权限的末尾:“rb”、“wt”、“ab”、“r+b”、“w+t”、“a+t”
  • 将读写方式放在读写权限的中间:“rb+”、“wt+”、“ab+”

整体来说,文件打开方式由 r、w、a、t、b、+ 六个字符拼成,各字符的含义是:

  • r(read):读
  • w(write):写
  • a(append):追加
  • t(text):文本文件
  • b(binary):二进制文件
  • +:读和写

1.4 关闭文件

文件一旦使用完毕,应该用 fclose() 函数把文件关闭,以释放相关资源,避免数据丢失。fclose() 的用法为:

int fclose(FILE *fp);

fclose(fp)函数关闭fp指定的文件,必要时刷新缓冲区。
对于较正式的程序,应该检查是否成功关闭。
如果成功关闭,fclose()返回值为0,否则返回EOF

if(fclose(fp) != 0){printf("Error in closing file %s \n", argv[1]);
}

如果磁盘已满、移动硬盘被移除或者出现I/O错误,都会导致fclose()函数失败。

1.5 实例演示

最后,我们通过一段完整的代码来演示 fopen 函数的用法,这个例子会一行一行地读取文本文件的所有内容:

#include <stdio.h>
#include <stdlib.h>#define N 100int main() {FILE *fp;char str[N + 1];//判断文件是否打开失败if ( (fp = fopen("d:\\demo.txt", "rt")) == NULL ) {puts("Fail to open file!");exit(0);}//循环读取文件的每一行数据while( fgets(str, N, fp) != NULL ) {printf("%s", str);}//操作结束后关闭文件fclose(fp);return 0;
}

二、 fopen实例

2.1 读取常规文件

如果您开发一个带有一些原生 C/C++ 层 (NDK) 的 Android 应用程序,并且
尝试通过执行

FILE *pFile = fopen ("myfile.txt" , "w" )

从其中打开文件,fopen 调用将失败。

原因是您的应用程序的当前工作目录是文件系统“ /”的根目录,您的应用程序自然无权访问它。

为了解决这个问题,您必须将文件的完整路径传递给fopen调用。
一种方法是将基本路径传递到您的应用程序具有读/写权限的应用程序数据文件夹。

例如,您可以使用上下文 API getFilesDir().getAbsolutePath()
http://developer.android.com/reference/android/content/Context.html#getFilesDir()
并将结果传递给本机函数调用。

这个特定的 API 当前解析为:“ /data/data/[app package]/文件/"

在您的本机代码中, fopen 调用应该成功

FILE *pFile = fopen (" /data/data/ [应用程序包]/文件/我的文件.txt" , "w" );

比如我写了下面这段代码来测试

我们读取一下/data/data/com.oyp.face2dsticker/files/HMS_MLKIT_FACE/version.txt 这个文件的内容

这个文件内容如下所示:

下面代码是处于cpp文件中的某个函数的代码,这个函数最终通过jni的方式被Java业务层调用。

  FILE *pFile = nullptr;// 这个路径可以在Java业务层通过 getFilesDir().getAbsolutePath() 来获取,然后传递到cpp层pFile = fopen ("/data/data/com.oyp.face2dsticker/files/HMS_MLKIT_FACE/version.txt" ,"r");if(nullptr == pFile){LOGD("File open fail!\n")}char tmp[100];fread(tmp, 1, 100, pFile);LOGD("File open success, content is :【%s】\n", tmp)fclose(pFile);pFile = nullptr;

运行程序,成功读取到文件内容,打印出来的日志如下:

 File open success, content is :【0.1.9.10q】

2.2 读取Asset目录文件

需要引入头文件

#include <android/asset_manager_jni.h>

typedef一下AAssetesFile

typedef AAsset esFile;

static JNIEnv *sEnv = nullptr;
static jobject sAssetManager = nullptr;void GLUtils::setEnvAndAssetManager(JNIEnv *env, jobject assetManager) {sEnv = env;sAssetManager = assetManager;
}static AAsset *loadAsset(const char *path) {AAssetManager *nativeManager = AAssetManager_fromJava(sEnv, sAssetManager);if (nativeManager == nullptr) {return nullptr;}return AAssetManager_open(nativeManager, path, AASSET_MODE_UNKNOWN);
}//
// File open
//
static esFile *esFileOpen(const char *fileName) {esFile *file;FUN_BEGIN_TIME("GLUtils::esFileOpen")AAssetManager *nativeManager = AAssetManager_fromJava(sEnv, sAssetManager);if (nativeManager == nullptr) {return nullptr;}file = AAssetManager_open(nativeManager, fileName, AASSET_MODE_BUFFER);FUN_END_TIME("GLUtils::esFileOpen")return file;
}//
// File close
//
static void esFileClose(esFile *pFile) {FUN_BEGIN_TIME("GLUtils::esFileClose")if (pFile != nullptr) {AAsset_close(pFile);}FUN_END_TIME("GLUtils::esFileClose")
}//
// File read
//
static int esFileRead(esFile *pFile, int bytesToRead, void *buffer) {int bytesRead = 0;FUN_BEGIN_TIME("GLUtils::esFileRead")if (pFile == nullptr) {return bytesRead;}bytesRead = AAsset_read(pFile, buffer, bytesToRead);FUN_END_TIME("GLUtils::esFileRead")return bytesRead;
}char *GLUtils::openTextFile(const char *path) {char *buffer;FUN_BEGIN_TIME("GLUtils::openTextFile")LOGI("GLUtils::openTextFile path [%s]", path)AAsset *asset = loadAsset(path);if (asset == nullptr) {LOGE("Couldn't load %s", path)return nullptr;}off_t length = AAsset_getLength(asset);buffer = new char[length + 1];int num = AAsset_read(asset, buffer, length);AAsset_close(asset);if (num != length) {LOGE("Couldn't read %s", path)delete[] buffer;return nullptr;}buffer[length] = '\0';FUN_END_TIME("GLUtils::openTextFile")return buffer;
}

比如,我使用上面的openTextFile方法读取asset目录下的vertex/vertex_sticker_normal.glsl文件,代码如下:

char *VERTEX_SHADER  = GLUtils::openTextFile("vertex/vertex_sticker_normal.glsl");

但是在调用上面代码之前,需要先调用一下setEnvAndAssetManager 初始化一下assetManager

 // 初始化设置assetManager  一定要记得初始化,否则会报空指针异常GLUtils::setEnvAndAssetManager(env, assetManager);

这个assetManager是java层传入进来的,下面讲解一下怎么传下来。

  1. 业务层通过这样调用,传assetManager过来
 AssetManager assetManager = context.getAssets();OypCustomEffectRender.onSurfaceCreated(assetManager);
  1. onSurfaceCreated方法最终会调用nativeOnSurfaceCreated方法
 // 业务层方法public static void onSurfaceCreated(AssetManager assetManager) {nativeOnSurfaceCreated(assetManager);}// JNI方法private static native void nativeOnSurfaceCreated(AssetManager assetManager);
  1. 而nativeOnSurfaceCreated方法最终的JNI实现如下:
extern "C"
JNIEXPORT void JNICALL
Java_com_oyp_opengl_OypCustomEffectRender_nativeOnSurfaceCreated(JNIEnv *env, jclass clazz, jobject asset_manager) {// 初始化设置assetManager  一定要记得初始化,否则会报空指针异常GLUtils::setEnvAndAssetManager(env, assetManager);// 干其他的业务

这样通过调用GLUtils::setEnvAndAssetManager(env, assetManager);就将java层传进来的assetManager初始化了,那么在cpp层就可以使用去读取asset目录下的文件了。

运行结果如下:

2022-05-20 10:37:16.335 7627-7655/com.oyp.face2dsticker I/LOG_TAG_XtcCustomEffectRender: [GLUtils.cpp][openTextFile][309]: [GLUtils::openTextFile] func start
2022-05-20 10:37:16.335 7627-7655/com.oyp.face2dsticker I/LOG_TAG_XtcCustomEffectRender: [GLUtils.cpp][openTextFile][310]: GLUtils::openTextFile path [vertex/vertex_point.glsl]
2022-05-20 10:37:16.336 7627-7655/com.oyp.face2dsticker I/LOG_TAG_XtcCustomEffectRender: [GLUtils.cpp][openTextFile][326]: [GLUtils::openTextFile] func cost time 1ms

【我的C/C++语言学习进阶之旅】NDK开发之Native层使用fopen打开Android设备上的文件相关推荐

  1. 【我的C语言学习进阶之旅】介绍一下NDK开发中关于JNI函数的两种注册方式:静态注册和动态注册

    目录 一.要介绍本篇博客的原因 二.静态注册 2.1 实现原理 2.2 实现过程 2.3 弊端 2.4 示例 三.动态注册 3.1 实现原理 3.2 实现过程 3.3 优点 3.4 示例 一.要介绍本 ...

  2. 【我的C/C++语言学习进阶之旅】NDK开发之解决错误:signal 5 (SIGTRAP), code 1 (TRAP_BRKPT), fault addr 0xXXX

    一.错误描述 今天在使用C++实现一个OpenGL特效的时候,运行出错,如下所示: 错误描述为: signal 5 (SIGTRAP), code 1 (TRAP_BRKPT), fault addr ...

  3. 【我的C语言学习进阶之旅】什么是.hpp文件?

    目录 一. `.hpp`文件是啥? 2.1 .hpp的疑问来源 2.2 什么是hpp文件? 2.2.1 以往套路 2.2.2 .hpp 新方式 2.3 使用`.hpp`文件有什么好处? 三.思考一下为 ...

  4. 【我的C语言学习进阶之旅】解决 Visual Studio 2019 报错:错误 C4996 ‘fscanf‘: This function or variable may be unsafe.

    一.问题描述 今天在Visual Studio 2019中写一段C语言的代码,发生生成错误.弹框如下: 点击[否(N)],提示如下: 错误具体信息为: 错误 C4996 'fscanf': This ...

  5. 【我的OpenGL学习进阶之旅】C++如何加载TGA文件?

    一.TGA文件相关介绍 通过前面的博客 [我的OpenGL学习进阶之旅]什么是TGA文件以及如何打开TGA文件? 地址:https://ouyangpeng.blog.csdn.net/article ...

  6. 【我的OpenGL学习进阶之旅】【持续更新】关于学习OpenGL的一些资料

    目录 一.相关书籍 OpenGL 方面 C方面 NDK 线性代数 二.相关博客 2.0 一些比较官方的链接 2.1 OpenGL着色器语言相关 2.2 [[yfan]](https://segment ...

  7. 【我的OpenGL学习进阶之旅】OpenGL ES 3.0新功能

    目录 1.1 纹理 1.2 着色器 1.3 几何形状 1.4 缓冲区对象 1.5 帧缓冲区 OpenGL ES 2.0 开创了手持设备可编程着色器的时代,在驱动大量设备的游戏.应用程序和用户接口中获得 ...

  8. 【我的OpenGL学习进阶之旅】着色器和程序(上)------着色器

    着色器和程序 一.前言 二.着色器和程序 2.1 创建和编译一个着色器 2.1.1 创建着色器 2.1.2 删除着色器 2.1.3 提供着色器源代码 2.1.4 编译色器 2.1.4 查询有关着色器对 ...

  9. 【我的OpenGL学习进阶之旅】介绍一下 绘制图元

    目录 一.绘制图元 1.1 `glDrawArrays` 1.1.1 `glDrawArrays`API说明 1.1.2 `glDrawArrays`API示例 1.2 `glDrawElements ...

  10. C语言学习教程:超级玛丽游戏开发源码分享

    C语言学习教程:超级玛丽游戏开发源码分享 这里推荐一下我建的C/C++语言学习交流秋秋裙,秋秋搜索群名称:小凯C/C++语言学习之家,裙里有不错的学习教程,从入门到项目实战.学习开发用到的开发工具,专 ...

最新文章

  1. tensflower官方测试案例_大数据性能测试介绍
  2. HDU2019 数列有序
  3. 数据结构练习 00-自测1. 打印沙漏(20)
  4. 主机和虚拟机ping不通的原因
  5. Javascript学习笔记12——Ajax入门
  6. CoreJava学习3——​基本类型的包装类
  7. 整合看点: DellEMC的HCI市场如何来看?
  8. Python多重继承(一分钟读懂)
  9. 浅入浅出 Java 排序算法
  10. 《TCP IP 详解卷1:协议》阅读笔记 - 第十四章
  11. Premiere 视频基本调色
  12. PS小知识(二)——画固定大小的形状
  13. welearn考试切屏会有显示吗_welearn随行课堂班级测试答案
  14. App Tamer for Mac(CPU优化电池管理工具)
  15. Vue2.x+Element UI 密码规则组件封装
  16. 启用计算机无线网络连接,哪位清楚笔记本电脑如何启用无线网络连接
  17. 一文读懂 | 数据中台如何为企业赋能?
  18. 利用sentinel hub Python开发包查询和下载Sentinel-2等卫星遥感数据
  19. google、bing ,baidu 等搜索引擎 查询参数
  20. 计算机基础知识题精选

热门文章

  1. Unity学习Day14--协程和WWW
  2. 基于收发一体超声波探头的超声波测距方案(附源代码和原理图)
  3. 通过V90PN通讯故障实例来看线路干扰与线路错误的区别
  4. 我看肖老师的《明日世界-云端计算下的程序设计需求》视频时,做的简单笔记
  5. 华为鸿蒙爆出惊天骗局,华为鸿蒙系统爆出惊天骗局!
  6. 巴菲特对优质公司买卖
  7. Cannot uninstall .. It is a distutils installed project and thus we cannot accurately determine 解决方案
  8. Torrent 文件图文解析
  9. STM32CubeMX配置读取MLX90614(GY-906)非接触红外测温传感器
  10. 主成分分析结果成分不显著_数据分析|主成分分析