- UID
- 3808
- 精华
- 积分
- 1480
- 威望
- 点
- 宅币
- 个
- 贡献
- 次
- 宅之契约
- 份
- 最后登录
- 1970-1-1
- 在线时间
- 小时
|
本帖最后由 溯影 于 2018-7-7 16:39 编辑
小弟最近看到了WindowsAPI中一个API是WriteConsole,书上讲利用这个函数可以自己来写一个printf,因为书上是这么说的:“实际上,类似于printf等标准C函数在Windows系统中都是通过系统的动态链接库crtdll.dll导出,printf函数的实现程序也位于crtdll.dll中,分析printf等函数的实现代码可以发现,在Windows平台上,实际printf函数在做了格式化字符串的处理后,是调用WriteConsole等API来进行界面操作的”-----《精通WindowsAPI》 人民邮电出版社 范文庆等著 P231语
那我想,我也要自己动手写一个printf函数。
printf函数中有一个地方之前一直困扰着我,就是他那个不定的参数问题,printf可以用来打印字符串,打印格式字符,例如printf("哈哈,你好,我是%s\n","周西瓜"); 这个语句中逗号运算符右边的参数是可以改变的,个数也是不确定的,我已开始想用python来写这个函数,因为python中有一个可以def函数中*params(可变长参数),但是心里总是感觉怪怪的。
不过通过网上来查找资料,我终于找到了方法,C语言中可变参数函数是通过栈来进行实现的,正常情况下C的函数参数入栈规则为__stdcall, 它是从右到左的,即函数中的最右边的参数最先入栈。我们只要之前有一个确定的参数的地址,那么通过变量的字节数来进行偏移量的确定最终就可以实现确定所有可变参数,例如我们要写一个可变参数函数void Test(const char *fmt,...){........},根据上一句画,最右边的参数先入栈,那么const char *fmt我们是有一个确定的参数,我们可以用fmt这个参数的地址来进行偏移量的计算,最终确定所有的参数。
但是C库帮我们做好了这个可变参数的一系列函数,我们可以用stdarg.h这个头文件中的函数来进行可变参数的确定,不用我们自己再用sizeof和地址来计算偏移量了。
这里我推荐一篇博客,我的知识盲区是在这篇博客中得到填补的:http://www.cnblogs.com/cpoint/p/3368993.html
把最恼火的printf中可变参数的问题解决完毕以后就轻松多了。我们用的WriteConsole函数定义如下:
BOOL WINAPI WriteConsole(
in HANDLE hConsoleOutput, //控制台句柄,应该为标准输出句柄
in const VOID* lpBuffer, //要输出内容的指针
in DWORD nNumberOfCharsToWrite, //需要输入字符的数量
out LPVOID lpNumberOfCharsWritten, //实际输入的字符的数量
LPVOID lpReversed //保留参数,设置为NULL
);
获取控制台标准输出句柄用GetStdHandle函数
下面的这个程序中我们不引入头文件stdio.h,不使用printf函数来打印,用WriteConsole函数来进行打印结果,不使用与printf功能相似的函数或者相关的函数,例如不使用wsprintf等函数
- #include <stdlib.h>
- #include <stdarg.h> //用于可变参数
- #include <windows.h>
- VOID DoubleToString(DOUBLE number, CHAR *result); //浮点数转化为字符串的函数说明
- //自定义的SelfPrintf函数,模仿printf
- VOID SelfPrintf(CHAR *fmt, ...)
- {
- //为可变参数做准备
- va_list va_Ptr;
- va_start(va_Ptr, fmt);
- HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); //获取控制台的标准输出句柄
- if (hConsole == INVALID_HANDLE_VALUE || hConsole == NULL)
- {
- MessageBox(NULL, "GetStdHandle出错", "Console Error", MB_OK);
- return;
- }
- CHAR *fmtPtr = fmt; //指向fmt的指针
- for (; *fmtPtr; ) //开始遍历字符串
- {
- if (*fmtPtr == '%' && *(fmtPtr + 1))
- {
- switch (*(fmtPtr + 1))
- {
- case 'd':
- {
- CHAR szTemp[128];
- itoa(va_arg(va_Ptr, INT), szTemp, 10); //将那个位置上的整数转化为字符串
- //用WriteConsole进行控制台输出
- WriteConsole(hConsole, //控制台句柄
- szTemp,
- lstrlen(szTemp),
- NULL,
- NULL);
- break;
- }
- case 'c':
- {
- WriteConsole(hConsole,
- &va_arg(va_Ptr, CHAR), //打印当前位置上的字符
- 1,
- NULL,
- NULL);
- break;
- }
- case 's':
- {
- CHAR *szTemp = va_arg(va_Ptr, CHAR*);
- WriteConsole(hConsole,
- szTemp, //打印当前参数位置上的字符串
- lstrlen(szTemp),
- NULL,
- NULL);
- break;
- }
- case '%':
- {
- CHAR c = '%';
- WriteConsole(hConsole,
- &c,
- 1,
- NULL,
- NULL);
- break;
- }
- case 'f':
- {
- CHAR result[128];
- DoubleToString(va_arg(va_Ptr, double), result); //将浮点数转化为字符串
- WriteConsole(hConsole,
- result,
- lstrlen(result),
- NULL,
- NULL);
- break;
- }
- }
- fmtPtr += 2; //指针偏移两个字符,跳过%和字母
-
- }
- //不是格式字符,就按照平常的方式打印出来就好了
- else
- {
- WriteConsole(hConsole,
- fmtPtr,
- 1,
- NULL,
- NULL);
- fmtPtr++; //指针变量自增1
- }
- }
- CloseHandle(hConsole); //关闭句柄
- }
-
- //浮点数转化为字符串
- VOID DoubleToString(DOUBLE number, CHAR *result)
- {
- CHAR left[128]; //小数点左边整数部分
- itoa((INT)number, left, 10);
- lstrcpy(result, left);
- lstrcat(result, "."); //拼接上小数点
- number = number - (INT)number; //取小数部分
- while (number)
- {
- number = number * 10;
- CHAR temp[128];
- itoa((INT)number, temp, 10);
- lstrcat(result, temp);
- number = number - (INT)number;
- }
- }
-
- int main(void)
- {
- INT a = 1;
- DOUBLE b = 1.23456;
- CHAR c = 'K';
- CHAR d[128] = "Hello World!";
- SelfPrintf("%%哈哈,这是测试实例.\n* %d\n* %f\n* %c\n* %s\n%%测试完毕\n", a, b, c, d);
- return 0;
- }
复制代码
运行结果:
%哈哈,这是测试实例.
* 1
* 1.2345600000000001017497197608463466167449951171875
* K
* Hello World!
%测试完毕
请按任意键继续. . . |
|