一、基础研究
写一个函数showshr(char *,...)它可以接收不定数量的char *型参数,并打印这些指针所指向的字符串。这个题目也是要实现不定长参数的函数,而我们之前研究过printf()函数的打印机制,可以发现他们的原理是一样的,只不过printf()函数是点对点打印,即通过说明符识别要打印的数据类型和要打印的数据个数,它的参数就是要打印的数据本身,而这里要求的函数没有说明符,它的参数是要打印的字符串的地址,只是在最后的参数为0以识别停止打印并返回。
那么我觉得这里有两种思路:一种是在showstr()函数内部使用输出函数函数,这样就要求我们将参数即要打印的字符串的地址转换成字符串的值,即转换成一个字符数组,这需要我们把所有参数指向的空间当作是一段空间,并将它的大小即总的字符串长度计算出来,这样才能定义字符串数组并打印。还有一种思路是直接将字符串打印到屏幕上的固定位置,即将字符串赋值到以b800为段地址的某个地址空间,这是更底层的写法。但是我希望我在运行程序时能够将将字符串打印到输入命令的下一行,即打印位置是不固定的,所以我倾向于使用第一种思路。
那么接下来可以开始写程序了,首先我们把showstr的第一个形参命名为p,首先p的值是第一个字符串的首地址,判断p是否为0,如果是则返回函数,如果不是则输出p指向的字符串,在这里我们用putchar()函数输出,因为每次只能输出一个字符,所以要循环输出。那么怎么判断字符串结束呢?我的办法是判断当前位置是否为符号,即是否为句号或感叹号,但这样做的缺点是如果字符串不以符号结尾或者是中间出现符号的话输出就会出现错误。
现在要跳到下一个参数来进行输出,怎么使p的值为下一个参数呢?我们知道形式参数是在栈里存放的,而且是紧邻的,所以如果传递的是p1、p2、p3,那么入栈顺序是p1、p2、
P3,因为他们的类型是char *型,所以p1的地址减2个字节就是p2的地址,所以我们对p取址再减2就是下一个参数的地址。
编写程序如下:
因为发现程序停不下来,所以我把while循环换成了for循环打印有限个结果。这个程序的运行结果如下:
可以发现,程序存在的问题有两个:
(1)不能调到第三条字符串,导致程序运行停不下来;
(2)打印的句号和问号有两个。
对于第二个问题,是因为判断语句里面多了一条输出语句。
在字符串的跳转语句前面加一条输出语句:
发现p只有第一次改变了:
这是因为p是在栈段中的存储第一个参数的首地址,它的地址是不变的,所以po=&p+1;是不变的,所以第一次改变后,p的值就不变了。那么把p的地址给po,让po每次加1来跳到下一个参数的首地址,再将*po赋给p,即将下一个参数所指向的字符串的首地址赋给p,这样p就指向下一个字符串。
修改后的程序如下:
这里要注意的是po是存储参数的地址,而p是存储字符串的首地址,所以po应该是int *型。运行结果为:
但是我觉得只判断最后的符号来判断字符串是否结束不是很严谨,还有别的办法判断字符串是否结束吗?查找资料可以发现字符串是以’\0’结尾的,所以我们应该可以判断*(p+i)是否为‘\0’。修改后的程序如下:
二、扩展研究
1、我们之前是使用bp寄存器对栈进行操作来实现printf()函数,那么如果用这种方法该怎么实现这个题目?
答:我们可以用bp寄存器对栈操作来代替po指针的功能跳转参数地址和字符串首地址。
修改后的程序如下:
这里_BP还要加4是因为定义了两个局部变量i和j,它们是存储在栈中的,占4个字节。
2、如果不用printf函数输出可以吗?
答:我们常用的输出函数还有puts函数、putchar()函数,但是他们都需要头文件。我们也可以将结果输出到屏幕上指定位置,即将每个字符串都输出到b800段显示。
三、研究总结
我们认识的输出函数,都是将数据放进输出流中,从底层看就是将数据放进一个缓存,之后再放到b800数据段来显示,只不过我们写的底层输出函数只能将数据显示在屏幕的固定位置,而我们使用的输出函数会进行一些处理,使数据显示在屏幕上合适的位置而已。
我们写的这一个输出函数只能输出字符串,而且无法控制输出的位数等,而printf函数就引入了说明符,这样就可以人为地控制数据输出的格式等,更加的方便。
字符串其实就是字符数组,只不过因为它是连续的,为了更方便地输出,输出函数为它提供了专门的输出模式,这个模式也是以字符串的输出为基础的。