纯C++操作文件和文件夹的工具类

本文介绍了一个纯C++实现的File类和其派生类Dir,用于简化文件和文件夹操作,避免引入过多库。通过实例展示了如何创建、复制文件,判断类型,以及遍历文件夹等核心功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

一般涉及到文件和文件夹的操作的时候,要么使用QtQFile或者QDir类,要么使用别的库。但是当我们的程序不想包含太多太杂的库的时候,对于文件夹的操作就不那么容易了。因此,为解决这个问题,本文创建了一个File类,以及从File类派生出的Dir类,用以对文件和文件夹进行操作。

基本想法

为什么需要从File类派生出Dir类呢?
是因为在linux下,所有东西都可以看成文件,因此文件夹属于一类特殊的文件,故从File类派生出Dir类,以重用File类的一些方法。

特点

  • 纯C++,不包含其他的任何第三方库
  • 使用简单明了

代码

#ifndef FILE_V2_H
#define FILE_V2_H

#if defined (WIN32) || defined (WIN64) || defined (_MSC_VER)
#include <io.h>
#include <direct.h>
#elif defined(_linux) || defined(MINGW)
#include <io.h>
#include <direct.h>
#endif

#include <string>
#include <list>
#include <vector>

/* ****************************************************************************************************
** Class Name: File
** ----------------------------------------------------------------------------------------------------
** Author: geocat & Little bottle
** ----------------------------------------------------------------------------------------------------
** CreateTime:
** ----------------------------------------------------------------------------------------------------
** LastEditTime:
** ----------------------------------------------------------------------------------------------------
** Brief:
**      这是文件操作类
**      包含文件的创建、复制、遍历文件夹等操作
**
**
** ***************************************************************************************************/

/** 该函数将给定的路径中的"\\" 替换成 "/",同时将路径尾后的"/"去掉
 */
void sepReplace(std::string& sPath);

/** 给定一个路径,判断该路径是文件还是文件夹
  * 返回值为1,则为文件,返回2则为文件夹,返回0则为错误,可能改路径不是常规文件/文件夹路径
  */
int isFileOrDir(const std::string& sPath);

/**
 * @brief
 * 从给定的文件,得到其多级子目录,按照其对应顺序存储到vSubDirs中
 * 如:str = "\\DirA\\DirB\\DirC\\FileA"
 * 则转换后的vSubDirs为:[
 *                     DirA,
 *                     DirA/DirB,
 *                     DirA/DirB/DirC,
 *                     DirA/DirB/DirC/FileA
 *                    ]
 * @param str
 * @param vSubDirs
 */
void getMultiLayerDirFromStr(const std::string& str, std::vector<std::string>& vSubDirs);

/**
 * @brief 字符串分割函数,按照给定的分割字符cSep进行分割。分割结果存储在vTokenRes中
 * @param str
 * @param cSep
 * @param vTokenRes
 */
void strToken(const std::string& str, const char cSep, std::vector<std::string>& vTokenRes);

class Dir;

class File
{
public:
    File(const std::string& sFilePath = "");
    virtual ~File();

    void setFilePath(const std::string& sFilePath);

    std::string fileName();
    const std::string& filePath();

    bool isFile();
    bool isDir();

    // 返回当前文件所在目录的对象
    Dir parentDir();

    // 当前文件夹下创建文件
    bool touch(const std::string& sFileName);
    // 指定文件夹,在其下创建新文件
    static bool touch(const std::string& sTarDirName, const std::string& sNewFileName);

    // 将当前文件复制到目标文件夹下
    virtual bool copy(const std::string& sTarDirPath);
    // 指定文件夹,将指定文件拷贝到指定文件夹下
    static bool copy(const std::string& sTarDirName, const std::string& sFilePath);

protected:
    std::string m_sFilePath;
    int m_iIsFileOrDir;
};

typedef std::list<File*> FileList;

class Dir: public File
{
public:
    Dir(const std::string& sDirPath = "");
    ~Dir();

    bool mkdir(const std::string& sNewDirName);
    static bool mkdir(const std::string& sTarDirPath, const std::string& sNewDirName);


    // 遍历当前文件夹下的所有文件
    // 如果bDeepTraverse为FALSE,则只遍历当前目录
    // 如果bDeepTraverse为TRUE, 则遍历当前目录的所有子目录及其文件
    const FileList& entry(bool bDeepTraverse = false);
    // 静态函数
    static FileList entry_static(const std::string& sDirPath, bool bDeepTraverse = false);      // ???

    // 重载函数,将当前文件夹复制到目标文件夹下
    bool copy(const std::string &sTarDirPath) override;
    // 将一个给定的文件夹,复制到目标文件夹,不在目标文件夹下创建当前文件夹,只是把当前文件夹里的所有内容复制到目标文件夹下。
    static bool copy(const std::string &sSrcDirPath, const std::string &sTarDirPath);

protected:
    static void listFiles(const std::string& sDirPath, FileList& list);
    static void listFiles_Deep(const std::string& sDirPath, FileList& list);

protected:
    FileList m_lstFileList;
};

// 获取给定的文件相对于给定的文件夹的相对路径
// 文件需要存在于文件夹中,如果文件不存在与文件夹,返回空字符串
std::string getRelativePath(File& file, Dir& dir);

#endif // FILE_V2_H

以上是头文件的一些方法的定义,接下来就是具体实现了。上代码:

#include "File.h"
#include <fstream>

void sepReplace(std::string &sPath)
{
    int iPos = 0;
    while (sPath.find('\\') != sPath.npos) {
        iPos = sPath.find('\\', iPos);
        sPath = sPath.replace(iPos, 1, "/");
    }

    if(sPath.back() == '/')
        sPath.pop_back();
}

int isFileOrDir(const std::string &sPath)
{
    intptr_t handle;
    _finddata_t findData;

    handle = _findfirst(sPath.c_str(), &findData);
    if(findData.attrib & _A_SUBDIR)
        return 2;
    else
        return 1;

    _findclose(handle);
}

void getMultiLayerDirFromStr(const std::string &str, std::vector<std::string> &vSubDirs)
{
    std::string sOriStrCp = str;
    // 将原始字符串的"\\"替换成"/"
    sepReplace(sOriStrCp);
    // 将字符串进行分割
    std::vector<std::string> vTokenRes;
    strToken(sOriStrCp, '/', vTokenRes);
    // 将分割后的结果,按照逐步累加的方式,存储到vSubDirs中
    vSubDirs.push_back(vTokenRes.front());
    for(int i = 1; i < (int)vTokenRes.size(); i++)
    {
        vSubDirs.push_back(vSubDirs[i - 1] + "/" + vTokenRes[i]);
    }
}

void strToken(const std::string& str, const char cSep, std::vector<std::string>& vTokenRes)
{
    int iPos = 0;
    while (str.find(cSep, iPos) != str.npos) {
        vTokenRes.push_back(str.substr(iPos, str.find(cSep, iPos)));
        iPos = str.find(cSep, iPos) + 1;
    }
    vTokenRes.push_back(str.substr(iPos));
}

File::File(const std::string& sFilePath):
    m_sFilePath(sFilePath)
{
    m_iIsFileOrDir = isFileOrDir(m_sFilePath);
}

File::~File()
{

}

void File::setFilePath(const std::string &sFilePath)
{
    this->m_sFilePath = sFilePath;
    sepReplace(m_sFilePath);
}

std::string File::fileName()
{
    return m_sFilePath.substr(m_sFilePath.find_last_of('/') + 1);
}

const std::string& File::filePath()
{
    return m_sFilePath;
}

bool File::isFile()
{
    return m_iIsFileOrDir == 1;
}

bool File::isDir()
{
    return m_iIsFileOrDir == 2;
}

Dir File::parentDir()
{
    Dir pD;
    pD.setFilePath(this->m_sFilePath.substr(0, m_sFilePath.find_last_of('/')));

    return pD;
}

bool File::touch(const std::string &sFileName)
{
    std::string sDirPath = this->parentDir().filePath();

    std::string sNewFilePath;
    sNewFilePath = sDirPath + std::string("/") + std::string(sFileName);

    /* _access函数说明:(参考链接:https://blog.youkuaiyun.com/monk1992/article/details/81906013)
     * 头文件:<io.h>
     * 函数原型:int _access(const char *pathname, int mode);
     * 参数:pathname 为文件路径或目录路径 mode 为访问权限(在不同系统中可能用不能的宏定义重新定义)
     * 返回值:如果文件具有指定的访问权限,则函数返回0;如果文件不存在或者不能访问指定的权限,则返回-1.
     * 备注:当pathname为文件时,_access函数判断文件是否存在,并判断文件是否可以用mode值指定的模式进行访问。当pathname为目录时,_access只判断指定目录是否存在,在Windows NT和Windows 2000中,所有的目录都只有读写权限。
     * mode的值和含义如下所示:
     * 00——只检查文件是否存在
     * 02——写权限
     * 04——读权限
     * 06——读写权限
    */
    if(0 != _access(sDirPath.c_str(), 2))
    {
        printf("***ERROR***: Dest dir has no writing access.\n");
        return false;
    }

    std::ofstream os(sNewFilePath, std::ios::out);
    if(os.is_open())
    {
        os.close();
        return true;
    }
    else
    {
        os.close();
        return false;
    }
}

bool File::touch(const std::string &sTarDirName, const std::string &sNewFileName)
{
    std::string sParent = sTarDirName;
    sepReplace(sParent);

    if(0 != _access(sParent.c_str(), 2))
    {
        printf("***ERROR***: Dest dir has no writing access.\n");
        return false;
    }

    std::string sNewFilePath = sParent;

    // 如果sNewFileName是包含多级目录的文件,如:DirA\\DirB\\DirB\\FileA.txt;
    // 则需要将该文件路径拆分开,分别创建多级目录,再创建文件
//    if(sNewFileName.find('/') != sNewFileName.npos || sNewFileName.find('\\') != sNewFileName.npos)
//    {
//        std::vector<std::string> sSubDirs;
//        getMultiLayerDirFromStr(sNewFileName, sSubDirs);
//        for(int i = 0; i < (int)sSubDirs.size() - 1; i++)
//        {
//            Dir::mkdir(sTarDirName, sSubDirs[i]);
//        }
//    }

    sNewFilePath.append("/").append(sNewFileName);

    std::ofstream os(sNewFilePath, std::ios::out);
    if(os.is_open())
    {
        os.close();
        return true;
    }
    else
    {
        os.close();
        return false;
    }
}

bool File::copy(const std::string &sTarDirPath)
{
    if(0 != _access(sTarDirPath.c_str(), 2))
    {
        printf("***ERROR***: Dest dir has no writing access.\n");
        return false;
    }

    /* 将本文件复制到pTarDirPath下 */
    // 1. 读文件,二进制方式打开
    std::ifstream inFile(m_sFilePath, std::ios::in | std::ios::binary);
    if(! inFile.is_open())
    {
        printf("***ERROR***: File open failure.\n");
        inFile.close();
        return false;
    }

    // 2. 在目标文件夹下创建新当前同名文件
        // 可以做个判断该文件是否存在
    bool touchRes = File::touch(sTarDirPath, this->fileName().c_str());
    if(touchRes == false)
    {
        printf("***ERROR***: New file created to target dir failed.\n");
        inFile.close();
        return false;
    }

    std::string sNewFilePath = std::string(sTarDirPath) + "/" + this->fileName();

    // 3. 打开创建的该新文件,并将数据复制到该文件中
    std::ofstream outFile(sNewFilePath, std::ios::out | std::ios::binary);
    if(! outFile.is_open())
    {
        printf("***ERROR***: Open copied new file failed.\n");
        inFile.close();
        outFile.close();
        return false;
    }

    outFile << inFile.rdbuf();  // 这句话网上也是这样用的,说速度很快

    inFile.close();
    outFile.close();

    return true;
}

bool File::copy(const std::string &sTarDirName, const std::string &sFilePath)
{
    if(0 != _access(sTarDirName.c_str(), 2))
    {
        printf("***ERROR***: Dest dir has no writing access.\n");
        return false;
    }

    std::ifstream inFile(sFilePath, std::ios::in | std::ios::binary);
    if(! inFile.is_open())
    {
        printf("***ERROR***: Open file failure.\n");
        inFile.close();
        return false;
    }

    // 在目标文件夹下创建新文件
    std::string sTarDir = sTarDirName;
    sepReplace(sTarDir);
    File oriFile(sFilePath);
    bool touchRes = File::touch(sTarDir, oriFile.fileName());
    if(touchRes == false)
    {
        printf("***ERROR***: Touch new file failure.\n");
        inFile.close();
        return false;
    }

    std::string sNewFilePath = sTarDir + "/" + oriFile.fileName();

    // 3. 打开创建的该新文件,并将数据复制到该文件中
    std::ofstream outFile(sNewFilePath, std::ios::out | std::ios::binary);
    if(! outFile.is_open())
    {
        printf("***ERROR***: Open copied new file failed.\n");
        inFile.close();
        outFile.close();
        return false;
    }

    outFile << inFile.rdbuf();  // 这句话网上也是这样用的,说速度很快

    inFile.close();
    outFile.close();

    return true;
}

Dir::Dir(const std::string& sDirPath)
{
    m_sFilePath = sDirPath;
    sepReplace(m_sFilePath);

    m_iIsFileOrDir = isFileOrDir(m_sFilePath);
}

Dir::~Dir()
{
    while (! m_lstFileList.empty()) {
        auto file = m_lstFileList.front();
        if(file != nullptr)
        {
            delete file;
            m_lstFileList.pop_front();
        }
    }
}

bool Dir::mkdir(const std::string &sNewDirName)
{
    if(0 != _access(m_sFilePath.c_str(), 2))
    {
        printf("***ERROR***: Curent dir has no writing access.\n");
        return false;
    }

    // 如果sNewDirName是多级子目录组成的,则对其进行拆分处理
    std::vector<std::string> vSubDirs;
    getMultiLayerDirFromStr(sNewDirName, vSubDirs);
    for(int i = 0; i < (int)vSubDirs.size(); i++)
    {
        std::string sSubDirPath = sNewDirName + vSubDirs[i];
        _mkdir(sSubDirPath.c_str());
    }

//    std::string sNewDirPath;
//    sNewDirPath = m_sFilePath + std::string("/") + sNewDirName;
//    int iMkdirRes = _mkdir(sNewDirPath.c_str());

    return true;
}

bool Dir::mkdir(const std::string &sTarDirPath, const std::string &sNewDirName)
{
    std::string sParentDirPath = sTarDirPath;
    sepReplace(sParentDirPath);
    if(0 != _access(sParentDirPath.c_str(), 2))
    {
        printf("***ERROR***: Dest dir has no writing access.\n");
        return false;
    }

    // 如果sNewDirName是多级子目录组成的,则对其进行拆分处理
    std::vector<std::string> vSubDirs;
    getMultiLayerDirFromStr(sNewDirName, vSubDirs);
    for(int i = 0; i < (int)vSubDirs.size(); i++)
    {
        std::string sSubDirPath = sTarDirPath + "/" + vSubDirs[i];
        _mkdir(sSubDirPath.c_str());
    }

//    std::string sNewDirPath;
//    sNewDirPath = sParentDirPath + std::string("/") + sNewDirName;
//    int iMkdirRes = _mkdir(sNewDirPath.c_str());

    return true;
}

const FileList& Dir::entry(bool bDeepTraverse)
{
    if(bDeepTraverse == false)
        listFiles(m_sFilePath, m_lstFileList);
    else
        listFiles_Deep(m_sFilePath, m_lstFileList);
    return m_lstFileList;
}

FileList Dir::entry_static(const std::string& sDirPath, bool bDeepTraverse)
{
    FileList list;
    if(bDeepTraverse == false)
        listFiles(sDirPath, list);
    else
        listFiles_Deep(sDirPath, list);
    return list;
}

bool Dir::copy(const std::string &sTarDirPath)
{
    // 判断目标文件夹是否有写权限
    if(0 != _access(sTarDirPath.c_str(), 2))
    {
        printf("***ERROR***: Dest dir path has no writing access.\n");
        return false;
    }
    auto curFiles = this->entry(true);
    for(auto file: curFiles)
    {
        if(file->isFile())
        {
            // 将当前文件的文件路径减去当前文件夹的路径,得到当前文件相对当前文件夹的相对路径
            std::string sRelPath = getRelativePath(*file, *this);
            // 相对路径的前缀
            std::string sPrefix;
            if(sRelPath.find('/') != sRelPath.npos)
                sPrefix = sRelPath.substr(0, sRelPath.find_last_of('/'));
            else
                sPrefix = "";
            // 在目标目录下创建相对路径的前缀
            if(sPrefix == "")
            {
                File::copy(sTarDirPath, file->filePath());
            }
            else
            {
                Dir::mkdir(sTarDirPath, sPrefix);
                File::copy(sTarDirPath + "/" + sPrefix, file->filePath()); // ??????????????????????????  TODO
            }
        }
    }

    // 内存回收 —— curFiles是在entry_static中创建的,因此需要手动回收
    while (! curFiles.empty()) {
        auto file = curFiles.front();
        if(file != nullptr)
        {
            delete file;
            curFiles.pop_front();
        }
    }
    return true;
}

bool Dir::copy(const std::string &sSrcDirPath, const std::string &sTarDirPath)
{
    // 判断对目标文件夹是否有写权限
    if(0 != _access(sTarDirPath.c_str(), 2))
    {
        printf("***ERROR***: Dest dir path has no writing access.\n");
        return false;
    }

    Dir srcDir(sSrcDirPath);

    // 遍历当前文件夹,得到所有的文件
    auto curFiles = Dir::entry_static(sSrcDirPath, true);
    for(auto file: curFiles)
    {
        if(file->isFile())
        {
            // 将当前文件的文件路径减去当前文件夹的路径,得到当前文件相对当前文件夹的相对路径
            std::string sRelPath = getRelativePath(*file, srcDir);
            // 相对路径的前缀
            std::string sPrefix;
            if(sRelPath.find('/') != sRelPath.npos)
                sPrefix = sRelPath.substr(0, sRelPath.find_last_of('/'));
            else
                sPrefix = "";
            // 在目标目录下创建相对路径的前缀
            if(sPrefix == "")
            {
                File::copy(sTarDirPath, file->filePath());
            }
            else
            {
                Dir::mkdir(sTarDirPath, sPrefix);
                File::copy(sTarDirPath + "/" + sPrefix, file->filePath()); // ??????????????????????????  TODO
            }
        }
    }

    // 内存回收 —— curFiles是在entry_static中创建的,因此需要手动回收
    while (! curFiles.empty()) {
        auto file = curFiles.front();
        if(file != nullptr)
        {
            delete file;
            curFiles.pop_front();
        }
    }
    return true;
}

void Dir::listFiles(const std::string& sDirPath, FileList& list)
{
    intptr_t handle;
    _finddata_t findData;

    std::string sDir = sDirPath;
    sepReplace(sDir);

    std::string sAddWildcardCharacter = sDir + "/*.*";   // 必须加上这种通配符,否则只循环一次
//    std::string sAddWildcardCharacter = m_sFilePath + "/";    // 这种目录后加斜杠的不行,会find失败

    handle = _findfirst(sAddWildcardCharacter.c_str(), &findData);
    if(-1 == handle)
    {
        printf("***ERROR***: Find first file failure.\n");
        return;
    }

    do {
        if(findData.attrib & _A_SUBDIR)
        {
            if (strcmp(findData.name, ".") == 0 || strcmp(findData.name, "..") == 0)
                continue;

            Dir* pDir = new Dir(sDir + "/" + findData.name);
            list.push_back(pDir);
        }
        else
        {
            File* pFile = new File(sDir + "/" + findData.name);
            list.push_back(pFile);
        }
    } while (_findnext(handle, &findData) == 0);

    _findclose(handle);
}

void Dir::listFiles_Deep(const std::string &sDirPath, FileList& list)
{
    intptr_t handle;
    _finddata_t findData;

    std::string sDir = sDirPath;
    sepReplace(sDir);

    std::string sAddWildcardCharacter = sDir + "/*.*";   // 必须加上这种通配符,否则只循环一次
//    std::string sAddWildcardCharacter = m_sFilePath + "/";    // 这种目录后加斜杠的不行,会find失败

    handle = _findfirst(sAddWildcardCharacter.c_str(), &findData);
    if(-1 == handle)
    {
        printf("***ERROR***: Find first file failure.\n");
        return;
    }

    do {
        if(findData.attrib & _A_SUBDIR)
        {
            if (strcmp(findData.name, ".") == 0 || strcmp(findData.name, "..") == 0)
                continue;

            Dir* pDir = new Dir(sDir + "/" + findData.name);
            list.push_back(pDir);

            if(pDir->fileName().back() == '.')
                continue;
            listFiles_Deep(sDir + "/" + findData.name, list);
        }
        else
        {
            File* pFile = new File(sDir + "/" + findData.name);
            list.push_back(pFile);
        }
    } while (_findnext(handle, &findData) == 0);

    _findclose(handle);
}

std::string getRelativePath(File &file, Dir &dir)
{
    std::string sFilePath = file.filePath();
    std::string sDirPath = dir.filePath();

    if(sFilePath.find(sDirPath) == std::string::npos)
        return "";

    return sFilePath.substr(sDirPath.length() + 1);
}

代码说明

没什么好说明,这两个文件,在要使用的时候直接包含到你的工程中就行。每一个函数的用法都在上面。

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

geocat

球球大佬们赏赐点吃喝!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值