BUAA-OS-2023-Lab1-Exam
lab1-Exam
课下一时爽,课上
debug火葬场
课下出锅力!
问题出在%ld/%d时负数的判定上(也就是判定neg_flag上):我的判定方法是,if (num < 0)则neg_flag = 1; num = -num;,这样对ld来说还说得过去,但是对于int类型就出问题了,long类型的num变量装了一个int的值,然后取相反数的时候却按着long类型跑的,然后补码尊贵的符号位就登场搞事情了(寄),还好课上一个多小时修出来了,要不然计组P7历史重演力!
lab1-Exam
题目要求:修改我们课下编写的
vprintfmt函数,使其能够支持新格式符(format specifiers) range :R,同时要求格式符R也能使用与其他格式符同等的副格式符:即<flag>、<width>、<length>三个副格式符。 格式符Range的作用为:使用函数中接下来的两个整数参数a, b,输出(<a>,<b>)字符串。这两个参数共用一组副格式符,受到的修饰相同。
- 样例:对于下面的函数
range_1_check,应该有其之后的输出:
void range_1_check(void) { |
- 解题流程:仔细看下来其实就是要在
vprintfmt()函数里增加一种格式符的判断:case 'R':,作用是输出三个字符和两个整数(%c%d%c%d%c,当然这个%d是可能有副格式符的);只需要判断一下输出的整数是int还是long就解决了,(然后课下出锅寄了一个多小时) - BUG与坑点:课下对了就是对了,剩下的都可以抄
%c和%d那里的
lab1-Extra
题目要求:实现一个类似
fprintf的函数int sprintf(char *buf, const char *fmt, ...);,将fmt字符串格式化输出到buf所在的内存空间中,且返回值应是buf中存放字符串的长度。 提示:可以调用vprintfmt函数进行协助输出
- 样例:就不写了,可以拿个
fprintf逝逝,效果一样的 - 这个考题完美回答了对于
vprintfmt函数中不起作用的void *data形参和看起来有点复杂的函数形参fmt_callback_t out的问题:- 函数中的
data可以承载输出的目的地指针,在printk中我们输出至控制台,所以data的取值并不重要(最终以调用了printcharc函数作为具体输出);但在这里实现的sprintf函数含有一个目的地buf,所以我们需要把buf传入,负责接收所有需要输出的字符(写入buf指向的内存) - 函数中的
out被称为回调函数参数,不过这个概念在这里并不重要,我们在解完题之后再来回顾。
- 函数中的
- 解题流程:实现思路与
printk类似,顶层和中间层的调用都相同,不同的是最底层的输出方式:-
printk使用printcharc写入控制台 -
sprintf需要写入buf指向的内存 - (这是不是刚说过一遍)所以顶层函数照抄,我们重点实现这个替代原有
outputk的新函数outputbuf(忘了啥是outputk的快回去看lab1讲解那篇文!)
-
-
outputbuf的实现:void outputbuf(void *data, const char *buf, size_t len);
如果能把vprintfmt整明白也挺容易看的,本函数应输出的字符串序列已经被安放在buf内,需要输出的长度为len,目的地为data;需要做的就是一个循环把字符搬运过去(记得末尾加一个\0) 这里的\0不需要考虑是否会导致下次再调用时没法接上,先取一下当前data的strlen,再从下一位(实际上是上一次写进去的\0)开始继续拼就可以了,最后又会是一个\0结束 附一种可能可行的实现方法:
void outputbuf(void *data, const char *buf, size_t len) { |
而我课上的实现方法严格意义上不符合要求,因为传入的参数形式不同,但是给了警告也能照样跑(
void outputbuf(void **data, const char *buf, size_t len) { |
回调函数
好了,课上的部分大概就是这些了,现在我们回来谈谈这个回调函数fmt_callback_t到底是个买
回调函数,是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,并且被传参的函数使用了这个函数指针,我们就认为这是一个回调函数。 回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另一方(被传参函数)调用的,用于对某事件或条件进行响应。
说白了,回调函数就是一个只能被他人调用的函数,而回调函数参数就是某个函数中已经固定好返回值、自身形参的参数(类型是函数指针) 那我们还拿vprintfmt举例:
void vprintfmt(fmt_callback_t out, void *data, const char *fmt, va_list ap); |
这里fmt_callback_t out,就是一个回调参数,实际类型是一个函数指针,接收者vprintfmt在自身内部对out函数进行调用,用于处理某些事件(这个例子中就是输出处理好的字符串),这样的处理虽然看起来有些繁琐,但是实际上给了vprintfmt函数非常强的可拓展性,输出函数的接口化,意味着它可以通过传入不同的输出函数,完成更复杂情况下、更多种类的输出任务(就比如lab1-extra这个输出到内存)。 如果只考虑输出控制台,实际上完全可以不传入out参数,直接使用printcharc函数完成任务。但强拓展性可不是一个简简单单的printcharc能受的住的() 顺便提一嘴最好让同一个回调参数对应的函数类型都一致,否则可不一定能保证功能正确(反正编译器会先给你个难看的警告.jpg)
实际上,在C语言库中还有很多这样的回调函数,其中我们用的相当多的一个就是qsort中的第四个参数:cmp。每次我们调用qsort,都需要事先写好cmp函数,并且传入qsort中,让其按照我们程序员事先设定好的规则进行排序。 写到这里我也才注意到,当时程设 + DS绕的一圈一圈的cmp参数格式,实际上正是C库的高拓展性所在:
int cmp(const void* e1, const void* e2); |
首先为了符合回调函数要求,无论编写怎样的cmp函数都要保证参数类型一致;而为了能够处理更多种类的数据(甚至结构体),形参被设置成了const void*,这样不管比较的是什么结构,传入形参后强制类型转换把引用解开就可以进一步处理了。所以虽然程序员用起来有点绕,但对C库来说,这样写绝对是件好事。 在编程过程中使用回调机制,可以更好地分离代码,使得应用层和驱动层尽可能分离,降低总代码的耦合性。 受教了。






