before|正文之前:
c++实验代码及学习笔记(一)
你好! 这是一个高程实验课的代码记录及学习笔记。我将记录一些重要的知识点、易错点。但是作为大学生,水平很低,敬请指点教导、优化代码。
1问题
首先我们看一下实验目标及要求:
2思路
实际上这个实验的描述非常清晰,使用主函数参数(argc,argv[])传递文件夹路径
读取路径–列举文件(可能需要判断子目录等等)
额外的功能是递归列举(方法我们可以选择DFS)列举隐藏文件(读取文件属性)
3查找相关函数
首先我们借鉴的是百度知道下的一篇回答。
参考: 如何使用c语言列举出某个目录下的文件
原答案:
用C语言列出目录下的文件,在linux下可采用readdir()函数来实现,代码实现过程为:
1.打开目录
2.循环读目录,输出目录下文件
3.关闭目录指针
参考代码:
#include <dirent.h>
#include <stdio.h>
int main()
{
DIR *dirp;
struct dirent *dp;
dirp = opendir("."); //打开目录指针
while ((dp = readdir(dirp)) != NULL) { //通过目录指针读目录
printf("%s\n", dp->d_name );
}
(void) closedir(dirp); //关闭目录
return 0;
}
只用opendir() readdir()就能实现了,多么简洁!
然鹅,我们是windows系统……(留下了Linux大法好的眼泪)
Windows下,代码如下
#include <io.h>
#include <stdio.h>
void printDir( const char* path )
{
struct _finddata_t data;
long hnd = _findfirst( path, &data ); // 查找文件名与正则表达式chRE的匹配第一个文件
if ( hnd < 0 )
{
perror( path );
}
int nRet = (hnd <0 ) ? -1 : 1;
while ( nRet >= 0 )
{
if ( data.attrib == _A_SUBDIR ) // 如果是目录
printf(" [%s]*\n", data.name );
else
printf(" [%s]\n", data.name );
nRet = _findnext( hnd, &data );
}
_findclose( hnd ); // 关闭当前句柄
}
void main()
{
printDir("d:/*.*");
}
这一片代码就够使了,当然,要想正确使(抄)出来,需要对该函数的一定理解。
解读函数
参考文章:
【主要参考】C++用 _findfirst 和 _findnext 查找文件
【函数内部结构解释很详细】C语言windows目录操作
重要函数:
文件遍历 _findfirst( ) _findnext( )
一、这两个函数均在io.h里面。
二、首先了解一下一个文件结构体
struct _finddata_t {
unsigned attrib; //文件属性的存储位置。它存储一个unsigned单元,用于表示文件的属性。
time_t time_create; //这里的time_t是一个变量类型,用来存储文件创建时间。
time_t time_access; //文件最后一次被访问的时间。
time_t time_write; //文件最后一次被修改的时间。
_fsize_t size; //文件的大小。这里的_fsize_t应该可以相当于unsigned整型,表示文件的字节数。
char name[MAX_FNAME];//文件的文件名。这里的_MAX_FNAME是一个常量宏,它在<stdlib.h>头文件中被定义,表示的是文件名的最大长度。
};
Attrib属性
文件属性 | 含义 |
---|---|
_A_ARCH | 存档 |
_A_HIDDEN | 隐藏 |
_A_NORMAL | 正常 |
_A_RDONLY | 只读 |
_A_SUBDIR | 文件夹 |
_A_SYSTEM | 系统 |
注意:文件属性用位表示,既然是位表示,那么当一个文件有多个属性时,它往往是通过位或的方式,来得到几个属性的综合。例如只读+隐藏+系统属性, 应该为:_A_HIDDEN | _A_RDONLY |_A_SYSTEM 。
用 _findfirst 和 _findnext 查找文件
1、_findfirst函数:long _findfirst(const char , struct _finddata_t );
第一个参数为文件名,可以用".“来查找所有文件,也可以用”*.cpp"来查找.cpp文件。
第二个参数是_finddata_t结构体指针。若查找成功,返回文件句柄,若失败,返回-1。
2、_findnext函数:int _findnext(long, struct _finddata_t *);
第一个参数为文件句柄,第二个参数同样为_finddata_t结构体指针。若查找成功,返回0,失败返回-1。
3、_findclose函数:int _findclose(long);
只有一个参数,文件句柄。若关闭成功返回0,失败返回-1。
long _findfirst( char *filespec, struct _finddata_t *fileinfo );
// 功 能 : 提供与filespec指定入口泛式匹配的第一个文件.通常后继用_findnext函
// 数后续使用来完成某泛式下的文件遍历.
// 头文件 : #include <io.h>
// 参 数 : filespec - 目标文件规范,可以包含通配符
// fileinfo - 文件信息buffer
// 返回值 : 成功返回唯一的搜索句柄
// 出错返回-1,且设置errno为如下值:
// ENOENT 该泛式无法匹配
// EINVAL 无效文件名
int _findnext( long handle, struct _finddata_t *fileinfo );
// 功 能 : 按照前面_findfirst中的泛式规则,查找下一个符合该泛式的文件,并以此为依据
// 修改fileinfo中的值
// 头文件 : #include <io.h>
// 参 数 : long handle - 搜索句柄(通常由紧靠其前的_findfirst()返回)
// fileinfo - 文件信息buffer
// 返回值 : 成功返回0
// 出错返回-1,且设置errno为如下值:
// ENOENT 没有更多的符合该泛式的文件
int _findclose( long handle );
// 功 能 : 关闭搜寻句柄并释放相应资源
// 头文件 : #include <io.h>
// 参 数 : long handle - 搜索句柄(通常由紧靠其前的_findfirst()返回)
// 返回值 : 成功返回0
// 出错返回-1,且设置errno为如下值:
// ENOENT 没有更多的符合该泛式的文件
到这里我们大概清楚函数的使用方法了,开始试验
4发现问题
- 主函数参数的应用
int main(int argc,char *argv[])
{
printDir(argv[1]);
return 0;
}
- findfirst函数
void printDir( const char* path )
{
struct _finddata_t data;
long hnd = _findfirst( path, &data ); // 查找文件名与正则表达式chRE的匹配第一个文件
if ( hnd < 0 )
{
perror( path ); //如果无效/错误,返回-1,输出错误perror
}
int nRet = (hnd <0 ) ? -1 : 1; //三目运算式,当括号内成立,输出-1,若不满足,输出冒号后的1
while ( nRet >= 0 ) //也即是hnd>=0,文件名有效
{
if ( data.attrib == _A_SUBDIR ) // 如果是目录
printf(" [%s]*\n", data.name );
else
printf(" [%s]\n", data.name );
nRet = _findnext( hnd, &data );
}
_findclose( hnd ); // 关闭当前句柄
}
这个应用起来最大的问题是该函数遍历的是文件名,识别"*.* "
,而不是文件夹路径
其实解决方法非常简单,但是我想了好一会,被其他博客启发才弄明白
就是在路径后加//*.*
就可以遍历该路径下所有文件了
具体操作方法有很多种,如果输入的参数是long的,可以直接赋值,如果是char型,则需要strcat函数
struct _finddata_t data;
char file[100];
strcpy(file,path);
_chdir(path);
strcat(file,"\\*.*"); //直接反斜杠/*.*也可以,否则转义符号要两个\\
为了控制台中更加美观,做了一些小修改,下面效果图是改之前的。
long hnd = _findfirst(file, &data);
if(hnd < 0)
{
perror(file);
}
int nRet = (hnd < 0) ? -1 : 1;
while(nRet >= 0)
{
if(data.attrib & _A_SUBDIR) //我这里都改成&,用==为什么不行?
{
printf("\n [%s]*\n",data.name);//这里是后来改的,与效果图不同
}
else
printf(" %s\n",data.name);
nRet = _findnext(hnd,&data);
}
_findclose(hnd);
效果如下:(这里还有个小问题,就是[.]*被识别了,我们之后会解决)
5遍历子目录
第一段我们只是完成了基础功能。识别了出子目录,但并未进入子目录中列举子文件。下面我们通过DFS深度优先搜索来递归遍历子文件
遍历文件夹及其子文件夹下所有文件。操作系统中文件夹目录是树状结构,使用深度搜索策略遍历所有文件。用到_A_SUBDIR属性
刚才我们提到有一个小问题,这个小问题在遍历时就会麻烦:
在判断有无子目录的if分支中,由于系统在进入一个子目录时,匹配到的头两个文件(夹)是"."(当前目录),". ."(上一层目录)。需要忽略掉这两种情况。当需要对遍历到的文件做处理时,在else分支中添加相应的代码就好
标准代码参考引用文章[^1]
本人初学c++(其实c语言也是入门),不太熟悉,代码以c语言为主
特别提一下DFS,这个算法应用应该是基础了,萌新还不太会哈,会记录下来之后专门学习的。
#include<io.h>
#include<stdio.h>
#include<iostream>
#include<fstream>
using namespace std;
void dfsFolder(const char *path,ofstream &fout)
{
struct _finddata_t data;
char file[100];
strcpy(file,path);
_chdir(path);
strcat(file,"\\*.*");
long hnd = _findfirst(file, &data);
if(hnd < 0)
{
perror(file);
}
int nRet = (hnd < 0) ? -1 : 1;
while(nRet >= 0)
{
if(data.attrib & _A_SUBDIR)
{
if((strcmp(data.name,".") != 0)&&(strcmp(data.name,"..")!=0)) //这个是筛除的一种方法哈,还有很多种
{
printf("\n [%s]*\n",data.name);
char newPath[100];
strcpy(newPath,path);
strcat(newPath,"\\");
strcat(newPath,data.name);
dfsFolder(newPath,fout); //这里递归遍历哈
}
}
else
printf(" %s\n",data.name);
nRet = _findnext(hnd,&data);
}
_findclose(hnd);//关闭当前句柄
fout.close();
}
int main(int argc,char *argv[])
{
ofstream o_fstream;
dfsFolder(argv[1],o_fstream);
return 0;
}
效果如下:(有一些瑕疵,遍历的比较随意,层级关系不是很明显。但是我觉得已经差不多了,可以进一步优化)
这个其实思路上非常简单。那么,我们接下来实现第三个功能,获取文件属性。
6获取文件属性
前面提到了attrib的多个属性,我们正好可以利用这些值
关于这个功能的函数有很多种,我们这里选择适合win系统的最简单的方法
void allDir(const char *path){
struct _finddata_t data;
char file[100];
strcpy(file,path);
_chdir(path);
strcat(file,"\\*.*");
long hnd = _findfirst(file, &data);
if(hnd < 0)
{
perror(file);
}
printf("文件列表:\n");
while(_findnext(hnd, &data) == 0)
{
string tempName = data.name;
if(tempName[0] == '.')
continue;
if(data.attrib & _A_NORMAL)
{
printf(" 普通文件 ");
}
else if(data.attrib & _A_RDONLY)
{
printf(" 只读文件 ");
}
else if(data.attrib & _A_HIDDEN)
{
printf(" 隐藏文件 ");
}
else if(data.attrib &_A_SYSTEM)
{
printf(" 系统文件 ");
}
else if(data.attrib & _A_SUBDIR)
{
printf(" 子目录 ");
}
else{
printf(" 存档文件 ");
}
if(data.attrib & _A_SUBDIR)
{
if((strcmp(data.name,".") != 0)&&(strcmp(data.name,"..")!=0))
{
printf(" [%s]*\n",data.name);
}
}else
printf(" %s\n",data.name);
}
_findclose(hnd);
}
本来也是很简单的,但是在实现过程中发现,所有的data.attrib == 号都必须改成&号。否则会出现不理想的结果。这个我很不解,参考答案是可以实现的。思考了一下,&是位运算,刚才我们提到attrib这些属性是以位储存的值,这些都是在<io.h>中定义的宏,可以直接使用,而本身的意义其实是一个无符号整型 (只不过这个整型应该是2的几次幂,从而保证只有一位为1,而其他位为0)。既然是位表示,那么当一个文件有多个属性时,它往往是通过位或的方式,来得到几个属性的综合。例如只读+隐藏+系统属性,应该为:_A_HIDDEN | _A_RDONLY |_A_SYSTEM 。
但具体原理我也不清,希望大佬可以点拨一下?
效果:
7聚合功能
到这儿您就可以右上角叉了,这个臃肿代码比较多余。
老师要求我们可以做个可供选择的,本来写了个一键聚合版,除主函数外只有一个函数,简洁但能实现所有功能,但是为了实现选择功能(想打印打印、想遍历遍历),做了个最终聚合版
#include<io.h>
#include<stdio.h>
#include<direct.h>
#include<iostream>
#include<fstream>
using namespace std;
//final版 全功能
/*
1、控制台输入flist C : \Windows 即可列举出文件夹下所有文件,遍历子目录文件,同时显示文件信息
2、flist C:\Windows print可以只打印文件名
3、flist C:\Windows all 可以显示文件信息并打印文件名
*/
void dfsFolder(const char *path,ofstream &fout)
{
struct _finddata_t data;
char file[100];
strcpy(file,path);
_chdir(path);
strcat(file,"\\*.*");
long hnd = _findfirst(file, &data);
if(hnd < 0)
{
perror(file);
}
while(_findnext(hnd, &data) == 0)
{
string tempName = data.name;
if(tempName[0] == '.')
continue;
if(data.attrib & _A_NORMAL)
{
printf(" 普通文件 ");
}
else if(data.attrib & _A_RDONLY)
{
printf(" 只读文件 ");
}
else if(data.attrib & _A_HIDDEN)
{
printf(" 隐藏文件 ");
}
else if(data.attrib &_A_SYSTEM == _A_SYSTEM)
{
printf(" 系统文件 ");
}
else if(data.attrib & _A_SUBDIR)
{
printf(" 子目录 ");
}
else{
printf(" 存档文件 ");
}
if(data.attrib & _A_SUBDIR)
{
if((strcmp(data.name,".") != 0)&&(strcmp(data.name,"..")!=0))
{
printf("\n [%s]*\n",data.name);
char newPath[100];
strcpy(newPath,path);
strcat(newPath,"\\");
strcat(newPath,data.name);
dfsFolder(newPath,fout);
}
}
else
printf(" %s\n",data.name);
}
_findclose(hnd);
fout.close();
}
void printDir(const char *path){
struct _finddata_t data;
char file[100];
strcpy(file,path);
_chdir(path);
strcat(file,"\\*.*");
long hnd = _findfirst(file, &data);
if(hnd < 0)
{
perror(file);
}
int nRet = (hnd < 0) ? -1 : 1;
while(nRet >= 0)
{
if(data.attrib & _A_SUBDIR)
{
printf("\n [%s]*\n",data.name);
}
else
printf(" %s\n",data.name);
nRet = _findnext(hnd,&data);
}
_findclose(hnd);
}
void allDir(const char *path){
struct _finddata_t data;
char file[100];
strcpy(file,path);
_chdir(path);
strcat(file,"\\*.*");
long hnd = _findfirst(file, &data);
if(hnd < 0)
{
perror(file);
}
printf("文件列表:\n");
while(_findnext(hnd, &data) == 0)
{
string tempName = data.name;
if(tempName[0] == '.')
continue;
if(data.attrib & _A_NORMAL)
{
printf(" 普通文件 ");
}
else if(data.attrib & _A_RDONLY)
{
printf(" 只读文件 ");
}
else if(data.attrib & _A_HIDDEN)
{
printf(" 隐藏文件 ");
}
else if(data.attrib &_A_SYSTEM)
{
printf(" 系统文件 ");
}
else if(data.attrib & _A_SUBDIR)
{
printf(" 子目录 ");
}
else{
printf(" 存档文件 ");
}
if(data.attrib & _A_SUBDIR)
{
if((strcmp(data.name,".") != 0)&&(strcmp(data.name,"..")!=0))
{
printf(" [%s]*\n",data.name);
}
}else
printf(" %s\n",data.name);
}
_findclose(hnd);
}
int main(int argc,char *argv[])
{
ofstream o_fstream;
if(strcmp(argv[2],"all") == 0)
{
allDir(argv[1]);
}
else if(strcmp(argv[2],"print") == 0)
{
printDir(argv[1]);
}
else
dfsFolder(argv[1],o_fstream);
return 0;
}
坚持到这里的小伙伴不容易呀,感谢您的阅读,嘻嘻~
鸣谢所有无私奉献的博主,面向百度编程的萌新给您拜年了 感谢您的付出!
我的gayhub
[1]: http://www.cnblogs.com/ranjiewen/p/5960976.html