C自定义函数的可变参数列表实现 Windows APIS目录遍历程序 [李园7舍_404]

 The  C Programming Language例子笔记一。
The C Programming Language例子笔记二(本来应包含解析声明部分):堆及堆分配。


1可变参数列表函数实现


(1)原理

C标准库<stdarg.h>提供了一套宏支持函数f可变参数的实现,f中必须含有最后一个(从左到右,看系统对函数参数的入栈顺序)可知的arg。用以下几个工具实现:

  • va_list:依次指向f的参数列表中的每一个未知的参数。在f内定义va_list parg
  • va_start:在parg开始指向未知参数之前必须使用”va_start(parg, arg);使parg指向指向第一个(与已知参数紧接着的一个)未知的参数。

  • va_arg:在” va_start(parg, arg);”语句后,每执行一次va_arg(parg,type)就会返回对应type类型参数值,并将parg指向下一个未知参数。va_arg指向的参数不会重复。

  • va_end:在参数遍历完毕且在退出函数f之前必须执行一遍”va_end(parg)”做清理工作。

不同类型的机子之上,<stdarg.h>的实现可能有所不同。但提供的可用接口都是一致的。


(2)程序例子

The C Programming Language中调用printf()本身作为输出函数实现了一个小型的printf(),将这个函数原型定为:void minprintf(char *fmt, …)。fmt称为了函数的最后一个可知参数。


[1]书中程序代码

void minprintf(char *fmt, ...)
{
    va_list ap;
    char *p, *sval;
    int ival;
    double dval;

    va_start(ap, fmt);
    for(p = fmt; *p; ++p){
        if(*p != '%'){
            putchar(*p);
            continue;
        }

        switch(*++p){
        case 'd':
            ival    = va_arg(ap, int);
            printf("%d", ival);
            break;
        case 'f':
            dval    = va_arg(ap, double);
            printf("%f", dval);
            break;
        case 's':
            for(sval = va_arg(ap, char *); *sval; ++sval){
                putchar(*sval);
            }
            break;
        default:
            putchar(*p);
            break;
        }
    }
    va_end(ap);
}
  • 可变参数列表函数的声明形式为type function_name(typename, …)。未知参数列表用”…”表示。
  • va_startap指向了未知参数的第一个参数。

  • va_arg计算出来要返回的目标参数(type类型)的步长并将其返回,并将ap指向下一个参数。va_arg返回的参数不会重复。

  • va_end在所有的操作结束后,趁函数未结束做清理工作。


[2]自己的例子

检验一下自己是否将可变参数函数的任意一宏理解错误。

void my_add(int n, ...)
{
    char i;
    int sum = 0;

    va_list ap;
    va_start(ap, n);

    for(i = 0; i < n; ++i){
                sum += va_arg(ap, int);
                printf("%d ", sum);
    }
    va_end(ap);
    printf("\n");
}

mainMinGW编译器)中调用” my_add(3,3, 2, 1); “语句,调试并运行程序得到一下结果:

C自定义函数的可变参数列表实现 Windows APIS目录遍历程序 [李园7舍_404]_第1张图片

从程序执行结果可以明确在MinGW编译器下:

va_start初始化ap指向的未知参数列表的第一个参数是最后一个参数的邻居。还有就是若va_arg在没有定位到指定类型的参数时,没有特别的返回值。故而都不知道va_arg有没有操作成功,捎带沮丧。


[3]总结

可变参数列表函数实现的头文件头文件<stdarg.h>提供给用户的核心功能是va_arg(ap, type)。此宏能够根据用户指定的type从左到右(猜:跟系统执行语句的顺序一致),查找到对应的属type类型的实参值并将其返回给用户。所以在未知参数type的情况下,是用不好<stdarg.h>的(因为va_arg在操作失败后没有特定的返回值),它们针对的对象是调用中的函数。所以,<stdarg.h>很大程度上适用于编写已知所有未知参数类型(type)情况的函数,如printf()scanf()等,对于非此类函数,操用<stdarg.h>来使用的意义不大。


对于<stdarg.h>的实现,个人猜想为首先获取当前执行函数的地址实现。从此地址内的字符串中解析到参数从而实现va_list。再利用用户传递的最后一个可知参数实现va_start。在根据用户给的type实现va_argva_end清理的应该是存储函数字符串的堆内存吧。


2遍历目录

书中遍历目录例是在unix的系统调用下完成的,在现在的linux系统之上也可以用这些系统调用函数。程序中调用的opendir()readdir()函数属unix内核函数。系统调用函数的参数涉及的是一些关于unix文件属性的结构体。书中将函数、结构体都解释的很完整。没有安装unix/linux系统,按照系统调用的思路,通过调用windows API来完成windows之上的目录遍历。Codeblocks + MinGW组合就能够调用windows APIS


(1) 目录文件数据结构

Unix/Linux之上用来描述一个目录的结构体名为DIR,包含每个目录的“描述符”、“节点数”及“目录名”等属性,主要作为readdir()的参数。用名为stat的结构体来描述文件(这个文件有可能只是一个文件如.txt.c;也有可能还是一个目录)的“节点数”、“当前文件是真正意义上的文件还是目录”、“大小”、“文件最后被访问的时间”、“文件最后被更改的时间”、“文件被创建的时间”等属性,主要作为stat()函数的参数。所有的结构体和函数都属于内核内容,被定义在相应的<sys/type.h><sys/stat.h>头文件中。


windows之上,用结构体WIN32_FIND_DATA描述文件或目录属性。WIN32_FIND_DATA结构体包含“文件属性:文件 or目录等”、“文件被建立的时间”、“文件最后被访问的时间”、“文件最后被修改的时间”、“文件大小”、“文件名”等属性,供FindFirstFile()函数作为参数使用,在windows 7之上,需要包含的头文件为<windows.h>


(2) 命令行参数和函数实参的区别

命令行参数”F:\\*”与函数实参”F:\\*”的区别:在命令行下F盘内有多少个子文件”F:\\*”就代表多少个参数,命令行会将通配符’*’代表的内容展开,每个子文件名会代表一个参数;函数实参”F:\\*”不会将通配符展开,程序要调用相关函数才能找到”F:\\”下找到’*’代表的所有子文件。


(3) windows实现目录遍历

windows之上的目录遍历同样可以调用系统内核函数windows APISFindFirstFile()及FindNextFile()


HANDLE WINAPI FindFirstFile(_In_  LPCTSTRlpFileName, _Out_  LPWIN32_FIND_DATA lpFindFileData)是获取lpFileName确定的文件或目录的属性于lpFindFileData结构体中。若lpFileName以通配符的形式存在,则函数先找到第一个文件的属性于lpFindFileData结构体中,然后返回一个“文件搜索句柄”,此句柄供FindNextFile()函数作为参数查询下一个文件的依据(标识)。


BOOL WINAPI FindNextFile(_In_   HANDLEhFindFile, _Out_  LPWIN32_FIND_DATA lpFindFileData)函数的hFindFile参数是FindFirstFile()返回值-----“文件搜索句柄”。只有在目录集下,如F盘下具有众多文件,”F:\\*”传递给lpFileNameFindFirstFile就可以返回“文件搜索句柄”。FindNextFile以此“文件搜索句柄”继续搜索,将文件夹下搜索到的下一个文件或目录属性给lpFindFileData结构体返回,直到将F盘下所有的文件搜索完毕。


FindFirstFile()FindNextFile()的组合可以将一个目录下的所有文件搜索到。以目录为对象,分析一下访问目录的递归过程:

C自定义函数的可变参数列表实现 Windows APIS目录遍历程序 [李园7舍_404]_第2张图片

对于递归,关键理解递归函数的递归过程,将递归过程和函数调用过程结合到一起,简单的递归操作还是很容易编写的。


代码:

#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>

int travle_dir(const char *path_name);

int main(int argc, char *argv[])
{
    if(argc < 2)
    {
        printf("win_travle_directory USAGE: win_travle_directory dir_path1, ...\n");
        return -1;
    }
    while(--argc)
    {
        //Travle the argument directory
        printf("\n--------Now travle %s dir--------\n", argv[argc]);
        travle_dir(argv[argc]);
    }
    return 0;
}

//Travle current directory
int travle_dir(const char *path_name)
{
	if(!path_name){
		printf("NULL DIR\n");
		return -1;
	}

	WIN32_FIND_DATA	ffd;
	LPSYSTEMTIME 	ptime;
	FILETIME 	ftLocal;
	HANDLE		hFind;
	LARGE_INTEGER	filesize;
	char		dir[MAX_PATH];
	char		cdir[MAX_PATH];

	hFind	= INVALID_HANDLE_VALUE;

	strcpy(dir, path_name);
	strcat(dir, "\\*");

	hFind	= FindFirstFile(dir, &ffd);
	if(INVALID_HANDLE_VALUE == hFind){
		printf("%s opened failed\n", dir);
		return -1;
	}


	do{
		if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY){
			//Travle child directory
			if(strcmp(ffd.cFileName, ".") && strcmp(ffd.cFileName, "..")){
				strcpy(cdir, path_name);
				strcat(cdir, "\\");
				strcat(cdir, ffd.cFileName);
				travle_dir(cdir);
				printf("--------%s   <DIR>--------\n\n", ffd.cFileName);
			}

		}else{
			//Filename
			printf("	%-25s", ffd.cFileName);

			//Size
			filesize.LowPart = ffd.nFileSizeLow;
			filesize.HighPart = ffd.nFileSizeHigh;
			printf("	%-10ld	bytes\n", filesize.QuadPart);
		}
	}while(0 != FindNextFile(hFind, &ffd));

	FindClose(hFind);
	return 0;
}

核心是采用Windows API FindFirst()FindNextFile()两个函数、递归思想及windows目录知识完成的程序。根据程序运行结果不断纠正程序即可,不用专门对应去学习每个模块(递归思想除外)。


程序执行结果:

C自定义函数的可变参数列表实现 Windows APIS目录遍历程序 [李园7舍_404]_第3张图片

win32cmd下执行。将检索到的文件的“文件名”和“大小属性”输出。如“演讲<DIR>”及“Princess…<DIR>””F:moves”下的目录,二者之上的内容是自己目录里面的所有文件。

程序中未对过多的异常进行处理,再次体现以下比较简单的递归思想。没有将文件的其它属性输出来,原因之一是cmd或者控制台的窗口太小,输出来也不一定好看。原因之二是有的windows APIs的用法依赖性比较大,需要狠狠的调试才能将windows API调试正确。现在的主要工作不是这个。


(4) Linux/windows目录遍历区别

不同点:系统调用函数不同,描述文件结的结构体不同。

相同点:描述文件结构体包含的信息差不多。都会体现一点关于递归的思想,而且是程序实现的关键地方。


如果需要进一步体会遍历目录的思想,可以自动编写opendir()readdir()FindFirstFile()FindNextFile()函数。只要理清了遍历目录和递归过程之间的联系,都可以将遍历目录的程序编写出来。当然,要将遍历目录的程序应用于实际,要考虑的其它因素(如异常、数据)甚至比程序功能实现要复杂得多。


C Note Over。

你可能感兴趣的:(C自定义函数的可变参数列表实现 Windows APIS目录遍历程序 [李园7舍_404])