Windows系统下使用C语言编写单线程的文件备份程序

写在最前方

源路径:即 From-Path,你准备要备份的资料
目的路径: 即 To-Path,你准备要存储备份的资料的地方
稍微回想一下,上一次写的代码,本次的任务是遍历目录及其子目录,那么这回要干的就是将上次遍历过的数据,挪一下窝,到我们想要他们去的位置。
这涉及到两个操作,遍历 和 拷贝,前一个动作我们在上一回已经实现了,只需做小小的改动,就能够使用。后一个动作也是需要靠 Windows API来完成,至于哪些,稍后再提。
现在先让我们完成一个魔法,3, 2, 1!:

 do{
   puts("-------------------------------------------------");
   fprintf(stdout, "The Default Path is : %s \n", DEFAULT_TO_PATH);
   fprintf(stdout, "Now The Path is   : %s \n", get_backup_topath());
   puts("-------------------------------------------------");
   puts("That is a System Back Up Software for Windows! ");
   puts("List of the software function : ");
   puts("1. Back Up ");
   puts("2. Set Back Up TO-PATH ");
   puts("3. Show TO-PATH History");
   puts("4. Read Me ");
   puts("5. Exit ");
   puts("-------------------------------------------------");

对界面稍微有了一些改动。

新增了第三行和第四行的 系统默认目的路径和当前使用的目的路径。

新增了倒数第四行的查看目的路径历史纪录的功能。

在main函数外头需要 extern DEFAULT_TO_PATH;因为引用了setPath.c里的一个全局变量。

写在中间

我们曾经提到要让函数的功能更加清晰,为了达到这个目的,应该把可能用到的一些原生库函数包裹一下,让可能发生的错误尽量掌握在我们自己的手里

安全函数

新建 safeFunc.h safeFunc.c
考虑一下我们需要包裹的函数: malloc, free, fopen 三个库函数。

为了不让后方的多线程实现产生更多的以后,不单独使用全局错误输出。
让我来将他们实现一下
我不会省略一些看似不必要的东西,例如注释,而是完整的呈现出来,如果觉得篇幅过长,可以选择跳跃的阅读。
魔法来了,3, 2, 1!

  #include <stdio.h> /* size_t */
   #include <stdlib.h>
   #include <setjmp.h>
   #define TRY_TIMES 3

   typedef struct _input_para{
     char * file; /* 待打开或创建的文件名 */
     char * mode; /* 打开的模式 */
   }params;

   jmp_buf malc_jmp; /*Malloc_s*/
   jmp_buf fopn_jmp; /*Fopen*/

   /**
    * @version 1.0 2015/10/01
    * @author wushengixin
    * @param  ... 参看结构体说明
         可传入任意的个数的,形式为 .file = "xxx", .mode = "x" 的参数
    * function 用于使用默认参数,并调用函数 Fopen 进行打开操作
    */
   #define Fopen_s(...) Fopen((params){.file = NULL, .mode = "r", __VA_ARGS__})
   FILE* Fopen(const params file_open);

   /**
    * @version 1.0 2015/10/01
    * @author wushengxin
    * param  sizes 输入需要分配的大小
    * function 用于隐藏一些对错误的处理,并调用malloc库函数分配空间
    */
   void * Malloc_s(size_t sizes);

   /**
    * @version 1.0 2015/10/01
    * @author wushengxin
    * @param  input 外部传入的等待释放的指针
    * function 用于隐藏一些对错误的处理,并调用free库函数进行释放指针
    */
   void Free_s(void * input);

里面用到了一些新的特性,如果使用 GCC/Clang作为编译器的,记得要开启-std=c11 支持。

这几个函数就不再详细解释,而是简略说几个,接下来放上实现代码:

   FILE* Fopen(const params file_open)
   {
     int times = 0;
     FILE* ret_p = NULL;
     if (file_open.file == NULL)
     {
       fputs("The File Name is EMPTY! Comfirm it and Try Again", stderr);
       return ret_p;
     }
     setjmp(fopn_jmp); /* fopn_jmp To there */
     ret_p = fopen(file_open.file, file_open.mode);
     if (ret_p == NULL)
     {
       if (times++ < TRY_TIMES)
       longjmp(fopn_jmp, 0); /* fopn_jmp From here */
       fprintf(stderr, "The File : %s Open with Mode (%s) Fail!\n", file_open.file, file_open.mode);
     }
     return ret_p;
   }

   void * Malloc_s(size_t sizes)
   {
     int times = 0;
     void * ret_p = NULL;
     if (sizes == 0)
       return NULL;
     setjmp(malc_jmp); /* malc_jmp To There */
     ret_p = malloc(sizes);
     if (ret_p == NULL)
     {
       if (times++ < TRY_TIMES) /* malc_jmp From Here */
         longjmp(malc_jmp, 0);
       fputs("Allocate Memory Fail!", stderr);
     }
     return ret_p;
   }

   void Free_s(void * input)
   {
     if (input == NULL)
     {
   #if !defined(NOT_DEBUG_AT_ALL)
       fputs("Sent A NULL pointer to the Free_s Function!", stderr);
   #endif
       return;
     }
     free(input);
     input = NULL;
   }

第一个函数是用外部定义的宏 `Fopen_s`启动它,这里没有实现隐藏它。

最后一个函数中使用了预处理的机制,如果在头文件中定义了 `#define NOT_DEBUG_AT_ALL`,这个输出将不在出现
安全函数已经撰写完成,接下来就是干正事了

setPath.h

我们首先要将程序里保存上默认的目的路径,首先想到用常量#define ...
其次应该要确保当前目的路径不被其他非法的渠道访问,那就应该用一个static 字符数组存储。
接下来就是要提供一个函数当作接口(这里用了接口这个术语不知道合不合适),来获取当前实际在使用的目的路径 get_backup_topath。
这里还需要将之前实现过的 repl_str ,再次实现一次,因为之前的显示功能只是测试,并不会实际应用到程序当中。
完成这两个功能函数以后,再去考虑实现怎么样设置路径,存储路径,以及使用文件流操作来缓存历史目的路径

   #include "safeFunc.h"

   #define SELF_LOAD_DEFAULT_PATH "C:/"
   #define MIN_PATH_NAME _MAX_PATH /* 最小的限制 */
   #define LARGEST_PATH_NAME 32767 /* 路径的最大限制 */

   /*
    * @version 1.0 2015/10/02
    * @author  wushengxin
    * @function 用于返回当前使用的目的路径
    */
   const char * get_backup_topath();

   /**
   * @version 1.0 2015/09/28
   * @author wushengxin
   * @param  src 外部传入的,用于调整
   * @function 用于替换路径中的 / 为 \ 的
   */
   void repl_str(char * src);

对应的实现中,会定义一个静态的字符数组,且在头文件中能够看见,很多是在`showFiles`里定义过的。

定义过的函数,例如 `repl_str`需要把`showFiles.c`中的**实现**,使用`#if 0 ... #endif` 进行注释掉,不然会发生重定义的错误。
setPath.c

   #include "setPath.h"

   static char to_path_buf[LARGEST_PATH_NAME] = SELF_LOAD_DEFAULT_PATH;
   const char * DEFAULT_TO_PATH = SELF_LOAD_DEFAULT_PATH;
   const int LARGEST_PATH = LARGEST_PATH_NAME;

   const char * get_backup_topath()
   {
     return to_path_buf;
   }

   void repl_str(char * src)
   {
     size_t length = strlen(src);
     for (size_t i = 0; i <= length; ++i)
     {
       if (src[i] == '/')
         src[i] = '\\';
     }
     return;
   }

有了上面的代码,主界面就再次能够无误运行了,那么剩下的就是实现,设置目的路径,存储目的路径到本地,显示目的路径,分别对应主界面的2, 3。
怎么实现比较好,再开始之前,分析一下会遇到的情况:
我们在得到目的路径之后,会将其拷贝给默认路径 to_path_buf,并且将其存储到本地缓存文件中,以便下次程序开始时可以直接使用上一次的路径
还可以使用另一个文件存储所有用过的历史路径,包含时间信息。
那么这就要求我们首先实现存储目的路径的功能,其次再实现设置目的路径的功能,最后实现显示目的路径的功能
注:两个看似无用的全局变量(const)是为了其他文件的可见性而设立的,且相对于#define能够省一些无足轻重的空间。

存储目的路径 store_hist_path

setPath.h

    #include <time.h>
    /**
     * @version 1.0 2015/10/02
     * @version wushengxin
     * @param  path 需要存储的路径
     * @function 用于存储路径到本地文件 "show_hist" 和 "use_hist"
     */
    void store_hist_path(const char * path);
setPath.c

    void store_hist_path(const char * path)
    {
      time_t ctimes;
      time(&ctimes); /* 获取时间 */
      FILE* input_use = Fopen_s(.file = "LastPath.conf", .mode = "w"); /* 每次写入覆盖 */
      FILE* input_show = Fopen_s(.file = "PathHistory.txt", .mode = "a");
      if (!input_show || !input_use)
      {
    #if !defined(NOT_DEBUG_AT_ALL)
        fputs("Open/Create the File Fail!", stderr);
    #endif
        return;
      }
      fprintf(input_use, "%s\n", path); /* 写入 */
      fprintf(input_show, "%s %s", path, ctime(&ctimes));
      fclose(input_show);
      fclose(input_use);
      return;
    }

`time`和`ctime` 函数的使用网路上的介绍更加全面,这里不做解释。

完成了存储的函数之后,便是实现从键盘读取并且设置默认路径
设置目的路径 set_enter_path

在此处需要停下来在此思考一下,如果用户输入了错误的路径(无效路径或者恶意路径),也应该被读取吗?所以应该增加一个检查,用于确认路径的有效性。
setPath.h

    #include <string.h>
    #include <io.h> /* _access */
    enum {NOT_EXIST = 0, EXIST = 1};
    /**
     * @version 1.0 2015/10/02
     * @author  wushengxin
     * @function 用于读取从键盘输入的路径并将之设置为默认路径,并存储。
     */
    void set_enter_path();

    /**
     * @version 1.0 2015/10/02
     * @author  wushengxin
     * @param  path 用于检查的路径
     * @function 用于检查用户输入的路径是否是有效的
     */
    int is_valid_path(const char * path);

setPath.c

    int is_valid_path(const char * path)
    {/* _access 后方有解释 */
      if (_access(path, 0) == 0) /* 是否存在 */
        return EXIST;
      else
        return NOT_EXIST;
    }

    void set_enter_path()
    {
      int intJudge = 0; /* 用来判断是否决定完成输入 */
      char tmpBuf[LARGEST_PATH_NAME]; /** 临时缓冲区 **/
      while (1)
      {
        printf("Enter The Path You want!\n");
        fgets(tmpBuf, LARGEST_PATH_NAME*sizeof(char), stdin); /* 获取输入的路径 */
        sscanf(tmpBuf, "%s", to_path_buf);
        if (is_valid_path(to_path_buf) == NOT_EXIST)
        {
          fprintf(stderr, "Your Enter is Empty, So Load the Default Path\n");
          fprintf(stderr, "%s \n", SELF_LOAD_DEFAULT_PATH);
          strcpy(to_path_buf, SELF_LOAD_DEFAULT_PATH);
        }
        fprintf(stdout, "Your Enter is \" %s \" ?(1 for yes, 0 for no) \n", to_path_buf);

        fgets(tmpBuf, LARGEST_PATH_NAME*sizeof(char), stdin);
        sscanf(tmpBuf, "%d", &intJudge); /* 获取判断数的输入 */
        if (intJudge != 0)
        {
          if (to_path_buf[strlen(to_path_buf) - 1] != '/')
            strcat(to_path_buf, "/");/* 如果最后一个字符不是'/',则添加,这里没考虑是否越界 */
          store_hist_path(to_path_buf);
          break;
        } /* if(intJudge) */
      }/* while (1) */
      return;
    }/* set_enter_path */

这一组函数的功能稍微复杂,大体来说便是 `读取路径输入->检查路径有效性->读取判断数->是否结束循环`

其中`_access` 函数有些渊源,因为这个函数被大家所熟知的是这个形式 `access`,但由于这个形式是 **POSIX** 标准,故 **Windows** 将其实现为`_access`,用法上还是一样的,就是名字不同而已。
显示历史路径 show_hist_path

setPath.h

    /**
     * @version 1.0 2015/10/02
     * author  wushengxin
     * function 用于在窗口显示所有的历史路径
     */
    void show_hist_path();

setPath.c

    void show_hist_path()
    {
      system("cls");
      char outBufName[LARGEST_PATH_NAME] = {'\0'};
      FILE* reading = Fopen_s(.file = "PathHistory.txt", .mode = "r");
      if (!reading)
      return;

      for (int i = 1; i <= 10 && (!feof(reading)); ++i)
      {
        fgets(outBufName, LARGEST_PATH_NAME*sizeof(char), reading);
        fprintf(stdout, "%2d. %s", i, outBufName);
      }
      fclose(reading);
      system("pause");
      return;
    }

剩下最后一个收尾工作

初始化路径
每次程序启动的时候,我们都会读取本地文件,获取上一次程序使用的最后一个路径,作为当前使用的目的路径
初始化目的路径 init_path

setPath.h

    /**
     * @versions 1.0 2015/10/02
     * @author  wushengxin
     * @function 用于每次程序启动时初始化目的路径
     */
    void init_path();

setPath.c

    void init_path()
    {
      int len = 0;
      char last_path[LARGEST_PATH_NAME] = { '\0' };
      FILE* hist_file = Fopen_s(.file = "LastPath.conf", .mode = "r");
      if (!hist_file) /* 打开失败则不初始化 */
        return;
      fgets(last_path, LARGEST_PATH_NAME, hist_file);
      len = strlen(last_path);
      if (len > 1)
      {
        last_path[len - 1] = '\0'; /* 消除一个多余的 ‘\n' */
        strcpy(to_path_buf, last_path);
      }
      return;
    }

这样就大功告成了,对于这个函数中的后`8`行代码,没使用惯用的`fgets 配合 sscanf` 是因为如果这么干的话,需要搭配一个`memset`函数清零,后面会有解释。

对于memset的解释
这个函数对于大的内存块的初始化实际上是很慢的,当然我们这个30KB左右大概的内存可能影响还没有那么大,但是上兆以后,调用memset就是一种性能问题了,很多情况下,编译器在开启高优化等级之后会自动帮你取消memset的隐式调用
什么隐式调用,例如 init_path的第二行代码,声明并且用花括号初始化这个数组的时候,就会调用隐式memset。

写在中间

上面完成了界面的大部分功能,剩下的便是备份这个主要功能。
在完成备份之前,首先想想要如何构造这个备份模型

既然是备份,如果不想扩展为多线程的形式,参考第一次写的遍历函数(show_structure)直接找到文件便调用Windows API(稍后介绍)进行复制即可,不需要讲待备份的文件路径保存下来。
如果要考虑多线程扩展,我们就需要从长计议。
对于一个备份模型,最好的莫过于使用一个队列,依旧实行的是遍历模式,但是将找到的文件路径保存,并放入一个先进先出的队列中,这样我们就能够保证在扩展成多线程的时候,可以有一个很清晰的模型参考。
那么现在的任务就是实现这个用于备份的队列模型。
队列模型

  • 应该有一个容器空间:用于存放路径
  • 有队首队尾标志
  • O(1)复杂度的检查队列是否为空的接口或标志
  • O(1)复杂度的返回容器容量的接口或标志,容器容量应该固定不变

使用一些面向对象的黑魔法,保存一些操作函数防止代码混乱。

  • 初始化函数
  • 释放函数
  • 弹出操作函数
  • 压入操作函数
  • 队列实体

考虑到要存储的是字符串,并且由于Windows API的参数需求,对于一个文件,我们需要存储的路径有两个<源路径,目的路径>,对此应该再使用一个路径模型结构体包裹他们,则空间的类型就相应改变一下
新建 Queue.h Queue.c

Queue.h

  typedef struct _vector_queue queue;
  typedef struct _combine combine;

      |  返回值  | | 函数类型名 ||  参数类型  |
  typedef int       (*fpPushBack)(queue * __restrict, const char * __restrict, const char * __restrict);
  typedef const combine * (*fpPopFront)(queue *);
  typedef void      (*fpDelete)(queue *);

五个typedef不知道有没有眼前一懵。,希望能够很好的理解

前两个是结构体的声明,分别对应着 队列模型 和 路径模型。

后两个是函数指针,作用是放在结构体里,使C语言的结构体也能够拥有一些简单的面向对象功能,例如成员函数功能,原理就是可以给这些函数指针类型的变量赋值。稍后例子更加明显。试着解读一下,很简单的。

  struct _combine{
  char * src_from_path; /* 源路径 */
  char * dst_to_path;  /* 目的路径 */
  };

  struct _vector_queue{
    combine **   path_contain; /* 存储路径的容器主体 */
    unsigned int  rear;     /* 队尾坐标 */
    unsigned int  front;    /* 队首坐标 */
    int       empty;    /* 是否为空 */
    unsigned int  capcity;   /* 容器的容量 */
    fpPushBack   PushBack; /* 将元素压入队尾 */
    fpPopFront   PopFront; /* 将队首出队 */
    fpDelete    Delete;  /* 析构释放整个队列空间 */
  };

  /**
   * @version 1.0 2015/10/03
   * @author  wushengxin
   * @param  object 外部传入的对象指针,相当于 this
   * @function 初始化队列模型,建立队列实体,分配空间,以及设置属性。
   */
  int newQueue(queue* object);

可以看到,上方的函数指针类型,被用在了结构体内,此处少了一个初始化函数,是因为不打算把他当作成员函数(借用面向对象术语)

在使用的时候可以直接obj_name.PushBack(..., ..., ...);

更详细的可以看后面的实现部分。成为成员函数的三个函数,将被实现为 static 函数,不被外界访问。

queue.c

 int newQueue(queue * object)
 {
   queue*   loc_que = object;
   combine**  loc_arr = NULL;

   loc_arr = (combine**)Malloc_s(CAPCITY * sizeof(combine*));
   if (!loc_arr)
     return 1;

   loc_que->capcity = CAPCITY; /* 容量 */
   loc_que->front = 0;    /* 队首 */
   loc_que->rear = 0;    /* 队尾 */

   loc_que->path_contain = loc_arr; /* 将分配好的空间,放进对象中 */
   loc_que->PushBack = push_back;
   loc_que->PopFront = pop_front;
   loc_que->Delete  = del_queue;

   return 0;
 }

在初始化函数中,可以看到,设置了队首队尾以及容量,分配了容器空间,配置了成员函数。

最后三句配置函数的语句中,push_back, pop_front, del_queue在后方以static 函数实现。

但是由于没有声明,所以切记要将三个static函数的实现放在newQueue的前方

 /**
  * @version 1.0 2015/10/03
  * @author  wushengxin
  * @param  object 外部传入的对象指针 相当于 this
  * @function 释放整个队列实体的空间
  */
 static void del_queue(queue * object)
 {
   Free_s(object->path_contain);
   return;
 }

 /**
  * @version 1.0 2015/10/03
  * @author  wushengxin
  * @param  object 外部传入的对象指针 相当于 this
        src  源路径
        dst  目的路径
  * @function 将外部传入的<源路径,目的路径> 存入队列中
  */
 static int push_back(queue * __restrict object, const char * __restrict src, const char * __restrict dst)
 {
   int times = 0;
   char*  loc_src = NULL; /* 本地变量,尽量利用寄存器以及缓存 */
   char*  loc_dst = NULL;
   combine* loc_com = NULL;
   queue*  loc_que = object;

   size_t  len_src = strlen(src); /* 获取路径长度 */
   size_t  len_dst = strlen(dst);
   size_t  rear = loc_que->rear;  /*获取队尾*/
   size_t  front = loc_que->front; /*获取队首*/

   loc_src = Malloc_s(len_src + 1); /* 分配空间 */
   if (!loc_src)
     return 1;

   loc_dst = Malloc_s(len_dst + 1);
   if (!loc_dst)
     return 2;
   strcpy(loc_src, src);
   strcpy(loc_dst, dst);

   loc_com = Malloc_s(sizeof(combine));
   if (!loc_com)
     return 3;
   loc_com->dst_to_path = loc_dst;
   loc_com->src_from_path = loc_src;

   loc_que->path_contain[rear++] = loc_com; /* 将本地路径加入实体 */
   loc_que->rear = (rear % CAPCITY);   /* 用数组实现循环队列的步骤 */

   if (loc_que->rear == loc_que->front)
     loc_que->empty = 0;
   return 0;
 }

 /**
  * @version 1.0 2015/10/03
  * @author  wushengxin
  * @param  object 外部传入的对象指针
  */
 static const combine * pop_front(queue* object)
 {
   size_t  loc_front = object->front;          /*获取当前队首*/
   combine* loc_com  = object->path_contain[loc_front]; /*获取当前文件名*/
   object->path_contain[loc_front] = NULL;   /*出队操作*/
   object->front = ((object->front) + 1) % 20; /*完成出队*/

   if (object->front == object->rear)
     object->empty = 1;
   else
     object->empty = 0;
   return loc_com;
 }

一个一个的说这些函数

del_queue:释放函数,直接调用Free_s

push_back:压入函数,将外部传入的两个原始的没有组成的路径字符串,组合成一个combine,并压入路径,每次都判断并置是否为空标志位,实际上这个函数中有累赘代码的嫌疑,应该再分出一个函数,专门用来分配三个空间,防止这个函数过长(接近40行)

pop_front:弹出函数,将队列的队首combine弹出,用于复制,但是这里有一个隐患,就是要将释放的工作交给外者,如果疏忽大意的话,隐患就是内存泄漏。

没有特地的提供一个接口,用来判断是否为空,因为当编译器一优化,也会将这种接口给优化成直接使用成员的形式,某种形式上的内联。

队列模型设计完毕,可以开始设计备份模型
备份模型可以回想一下之前的遍历函数,大体的结构一样,只是此处为了扩展成多线程,需要添加一些多线程的调用函数,以及为了规格化,需要添加一个二级界面
先设计一下二级界面

二级界面

思考一下,这个界面要做什么
选择是否开始备份
并且源路径需要在此处输入
返回上一级
新建 backup.h backup.c 文件
在主界面选择 1 以后就会调用二级界面的函数
列出二级界面的选项
1 Start Back up
2 Back To last level
backup.h

  /**
   * @version 1.0 2015/10/03
   * @author  wushengxin
   * function 显示二级界面
   */
  void sec_main_windows();
backup.c

  void sec_main_windows()
  {
    char tmpBuf[256];
    int selects;
    do{
      setjmp(select_jmp);
      system("cls");
      puts("-------------------1. Back Up------------------ ");
      puts(" For This Select, You can choose Two Options: ");
      puts(" 1. Start Back up (The Directory Path That You Enter LATER) ");
      puts(" 2. Back To last level ");
      puts("----------------------------------------------- ");
      fprintf(stdout, "Enter Your Selection: ");
      fgets(tmpBuf, 256, stdin);
      sscanf(tmpBuf, "%d", &selects);
      if (selects != 1 && selects != 2 )
      {
        fprintf(stdout, "\n Your Select \" %s \" is Invalid!\n Try Again \n", tmpBuf);
        longjmp(select_jmp, 1);
      }

      switch (selects)
      {
        jmp_buf enter_path_jmp;
      case 1:
      {
        char tmpBuf[LARGEST_PATH], tmpPath[LARGEST_PATH]; /* 使用栈分配空间,因为只用分配一次 */
        setjmp(enter_path_jmp);     /* enter jump to there */
        puts(" Enter the Full Path You want to BackUp(e.g: C:/Programing/)");
        fprintf(stdout, " Or Enter q to back to select\nYour Enter : ");
        fgets(tmpBuf, LARGEST_PATH, stdin);
        sscanf(tmpBuf, "%s", tmpPath);
        if (_access(tmpPath, 0) != 0)  /*检查路径是否存在,有效*/
        {
          if (tmpPath[0] == 'q' || tmpPath[0] == 'Q')
            longjmp(select_jmp, 0); /* 回到可以选择返回的界面 */
          fprintf(stderr, "The Path You Enter is Not Exit! \n Try Again : ");
          longjmp(enter_path_jmp, 0); /* enter jump from here */
        }
      }
        break;
      case 2:
        return;
      default:
        break;
      }/* switch */
    } while (1);
    return;
  }

这个函数只说几点,首先是`switch`的`case 1`,之所以用**花括号**包裹起来的原因是,这样才能在里面定义**本地变量**,直接在冒号后面定义是**编译错误**,这个特性可能比较少用,这里提一下,前面也有说过。
写在最后方

剩下的就是编写主要的功能函数和线程调用函数了。

(0)

相关推荐

  • C语言 文件的随机读写详解及示例代码

    前面介绍的文件读写函数都是顺序读写,即读写文件只能从头开始,依次读写各个数据.但在实际开发中经常需要读写文件的中间部分,要解决这个问题,就得先移动文件内部的位置指针,再进行读写.这种读写方式称为随机读写,也就是说从文件的任意位置开始读写. 实现随机读写的关键是要按要求移动位置指针,这称为文件的定位. 文件定位函数rewind和fseek 移动文件内部位置指针的函数主要有两个,即 rewind() 和 fseek(). rewind() 用来将位置指针移动到文件开头,前面已经多次使用过,它的原型为

  • C语言 以字符串的形式读写文件详解及示例代码

    fgetc() 和 fputc() 函数每次只能读写一个字符,速度较慢:实际开发中往往是每次读写一个字符串或者一个数据块,这样能明显提高效率. 读字符串函数fgets fgets() 函数用来从指定的文件中读取一个字符串,并保存到字符数组中,它的原型为: char *fgets ( char *str, int n, FILE *fp ); str 为字符数组,n 为要读取的字符数目,fp 为文件指针. 返回值:读取成功时返回字符数组首地址,也即 str:读取失败时返回 NULL:如果开始读取时

  • C语言 格式化读写文件详解

    fscanf() 和 fprintf() 函数与前面使用的 scanf() 和 printf() 功能相似,都是格式化读写函数,两者的区别在于 fscanf() 和 fprintf() 的读写对象不是键盘和显示器,而是磁盘文件. 这两个函数的原型为: int fscanf ( FILE *fp, char * format, ... ); int fprintf ( FILE *fp, char * format, ... ); fp 为文件指针,format 为格式控制字符串,... 表示参数

  • C语言 以字符形式读写文件详解及示例代码

    在C语言中,读写文件比较灵活,既可以每次读写一个字符,也可以读写一个字符串,甚至是任意字节的数据(数据块).本节介绍以字符形式读写文件. 以字符形式读写文件时,每次可以从文件中读取一个字符,或者向文件中写入一个字符.主要使用两个函数:fgetc()和fputc(). 字符读取函数 fgetc fgetc 是 file get char 的缩写,意思是从指定的文件中读取一个字符.它的原型为: int fgetc (FILE *fp); fp 为文件指针.fgetc() 读取成功时返回读取到的字符,

  • 可读可执行的C语言简历源文件

    这里黑客新闻吗?作者用代码更新了自己的简历,是不是很接地气,特符合程序员的逼格.这是一份可读可执行的C语言源文件,也是作者编码风格的体现. C语言天才写的一份简历 #include <stdio.h> #include <time.h> typedef struct { union { char * company; char * school; char * project; }; union { char * location; char * url; }; union { c

  • C语言 以数据块的形式读写文件详解及实现代码

    fgets() 有局限性,每次最多只能从文件中读取一行内容,因为 fgets 遇到换行符就结束读取.如果希望读取多行内容,需要使用 fread 函数:相应地写入函数为 fwrite. fread() 函数用来从指定文件中读取块数据.所谓块数据,也就是若干个字节的数据,可以是一个字符,可以是一个字符串,可以是多行数据,并没有什么限制.fread() 的原型为: size_t fread ( void *ptr, size_t size, size_t count, FILE *fp ); fwri

  • C语言压缩文件和用MD5算法校验文件完整性的实例教程

    使用lzma SDK对7z文件简单解压缩 有时候我们只需要单纯对lzma算法压缩的7z文件进行解压,有时需要在嵌入式设备上解压,使用p7zip虽然支持多种格式,但是不容易裁剪,使用lzma SDK是首选: 可以在这里找到各种版本:http://zh.sourceforge.jp/projects/sfnet_sevenzip/releases/ 我下载了4.65版本,这个对文件名编码支持没有9.20的好,中文可能有问题,但是我的需求不需要支持中文文件名,所以足够用了. 解压后先看一下7z这个工程

  • C语言怎么获得进程的PE文件信息

    一.打印Sections信息.下面的程序打印出Windows_Graphics_Programming 1.1中第三个程序"Hello World Version 3:Create a Full-Screen Window"生成的可执行文件的Sections结构字节的信息 #include<stdio.h> #include<windows.h> char *strPath="C:/c1_hwv3/Debug/c1_hwv3.exe"; in

  • C语言文件操作 fopen, fclose, mkdir详解

    1.建文件夹 int _mkdir(const char *path,mode_t mode); 函数名: _mkdir 功 能: 建立一个目录 用 法: int _mkdir( const char *dirname ); 头文件库:direct.h 返回值:创建一个目录,若成功则返回0,否则返回-1 ===================================================== 2.打开文件fopen() 函数功能: 打开一个文件 函数原型:FILE * fope

  • C语言 文件的打开与关闭详解及示例代码

    在C语言中,文件操作都是由库函数来完成的,这节介绍文件的打开和关闭. 文件的打开(fopen函数) fopen() 函数用来打开一个文件,它的原型为: FILE *fopen(char *filename, char *mode); filename为文件名(包括文件路径),mode为打开方式,它们都是字符串.fopen() 会获取文件信息,包括文件名.文件状态.当前读写位置等,并将这些信息保存到一个FILE类型的结构体变量中,然后将该变量的地址返回. FILE是在stdio.h头文件中定义的一

  • 用C语言实现从文本文件中读取数据后进行排序的功能

    功能介绍 程序的功能是从外部读取一个包括int型数据的文本文件,然后将它保存到内部临时数组,对数组进行排序后,以文本形式输出到指定的文件上.因为是int类型的数据,没有很严重的损失精度的问题. 正常运行要求: 包括数据的源文件内不能包括其他任何除数字和空白字符(空格,制表符,换行符)之外的任何字符,源文件最开始必须是数字字符,要保证源文件的数据计数正确.同时保证文件名有效. 运行结果 data.txt: obj.txt: 完整代码 警告:版权所有,谨供参考! #include <stdio.h>

  • C语言以数据块的形式读写文件实例代码

    fgets() 有局限性,每次最多只能从文件中读取一行内容,因为 fgets 遇到换行符就结束读取.如果希望读取多行内容,需要使用 fread 函数:相应地写入函数为 fwrite. fread() 函数用来从指定文件中读取块数据.所谓块数据,也就是若干个字节的数据,可以是一个字符,可以是一个字符串,可以是多行数据,并没有什么限制.fread() 的原型为: size_t fread ( void *ptr, size_t size, size_t count, FILE *fp ); fwri

随机推荐