C程序设计语言习题7-3谈指针操作与传值调用
习题7-3在做完之后总是不会按照预想的格式化输出,也对照了答案,做法是一样的,仅仅是代码的表达方式不一样,我在处理%后面的精度的时候,将其封装成了一个函数,用来读取%后面的字符串,然后保存起来,在输出的时候直接将保存的字符串替换上去,但是问题就出来了,打印的结果是如果不添加精度%d,结果能正常打印,如果添加了精度%.2d,那么结果就会打印读取的%后面的字符串。代码如下:
/*
联系7-3 改写minprintf函数,使它能完成printf函数的更多功能
*/
#include <stdio.h>
#include <stdarg.h>//该头文件中定义了如何遍历参数表
#include <ctype.h>
/*
1.va_list是一种变量类型,变量ap用来引用后边的可变参数,指向参数的指针,实质上是char*
2.va_start(ap,fmt),该函数是初始化ap用来指向minprintf(char *fmt,...)中的最后一个有名字参数fmt
后面的第一个无名参数的首地址,使用ap之前必须调用一次va_start。
3.va_arg(ap,type)函数根据类型名type来返回当前的指针ap指向的参数的值,并把ap移到下一个参数的地址。
4.va_end(ap);是为了结束对可变参数的处理,源码中实际上把ap定义为空指针,只是为了代码对称
*/
void minprintf(char* fmt, ...) {
va_list ap;
char* p, * sval;
int ival;
unsigned uval;
double fval;
void* pval;
char jd[20];
//char* p_jd = jd;
void jingdu_ctrl(char **,char*,char *);
va_start(ap, fmt);
char localfmt[100];
for (p = fmt; *p; p++) {
if (*p == '%') {//处理占位符
if (*++p) {
//printf("函数外前p = %p\n", p);
jingdu_ctrl(p,jd,localfmt);
//printf(jd);
//printf("函数外后p = %p\n", p);
switch (*p) {
case 'd':
case 'i':
ival = va_arg(ap, int);
printf(localfmt,ival);
break;
case 'f':
fval = va_arg(ap, double);
//printf("调用本函数\n");
printf(localfmt, fval);
break;
case 's':
sval = va_arg(ap,char*);
/*for (sval = va_arg(ap, char*); *sval; sval++) {
putchar(*sval);
}*/
printf(localfmt,sval);
break;
break;
case 'o':
uval = va_arg(ap, unsigned);
printf(jd, uval);
break;
/*case 'x':
uval = va_arg(ap, unsigned);
printf("%#x", uval);
break;
case 'X':
uval = va_arg(ap, unsigned);
printf("%#X", uval);
break;
*/
case 'u':
uval = va_arg(ap, unsigned);
printf(jd, uval);
break;
case 'c'://printf函数基本转换说明中是int类型
ival = va_arg(ap, int);
printf(jd, ival);
break;
case 'p':
pval = va_arg(ap, void*);
printf(jd, pval);
break;
/*case '\\':
goto swp;
break;*/
default:
putchar(*p);
break;
}
}
else {
break;
}
}
else if (*p == '\\') {//处理转义字符
switch (*++p) {
case 'n':
putchar('\n');
break;
case 't':
putchar('\t');
break;
case 'v':
putchar('\v');
break;
case 'r':
putchar('\r');
break;
default:
putchar(*p);
break;
}
}
else {
if (*p == '2') {
printf("函数从这里经过\n");
}
putchar(*p);
continue;
}
}
va_end(ap);
}
void jingdu_ctrl(char *p,char* jd,char *localfmt) {
int i = 0;
jd[i++] = '%';
//jd[i++] = '%';
while (!isalpha(*p)) {
jd[i++] = *p;
p++;
//printf("p = %p\n",p);
//p++;
}
if (isalpha(*p)) {
jd[i++] = *p;
//jd[i++] = '\n';
jd[i] = '\0';
}
sprintf(localfmt,"%s",jd);
//jd[0] = '\0';
printf("%s\n",localfmt);
}
int main(void) {
int a = 100;
char b[] = "250%-.8lf\n";
char* p = b;
double c = 1.0;
minprintf("a = %-5d,b = %20s,c = %5.2f\n", a, b, c);
printf(b,c);
return 0;
}
经过调试,我发现在添加精度的时候,程序读取完%后就根本不从if(*p == ‘%’)的里面走了,精度的控制字符直接当成普通字符打印了:
就像这样,我才觉得,读取完%后,指针p只是指向%后面的一个字符,根本没有移动,可是我明明把指针传到函数里面进行了一系列操作的,为什么会没有动呢,难道这个指针也像普通的变量一样传值调用,没有改变指针变量本身的值吗?有点半信半疑的我上百度搜了一下之后,看了别人的博客遇到类似问题,也是指针传进去,但是指针的值没变的情况,原来问题出在我在函数里面进行了指针的值改变的操作:
原来看书的时候只知道普通变量放到函数里面调用只是复制变量的值进去进行操作,指针就不会这样,但是没有深入研究过。原来指针当作参数传进去的时候,如果你仅仅是操作指针指向的变量的值,那么确实会真真实实的改变,如果将指针传到函数里面去,还像我这个图里面的代码一样做指针的自增运算,那么此时的 指针和普通变量当作参数传进来没什么区别了,一样的是传值调用,也就是说只是复制了指针p的值进行操作,函数外的原来的指针p根本没受影响,但是本题中我又必须要在函数里面操作这个指针的值,要做自增,办法就是用指针的指针:
这样的话,我们通过二级指针传进函数,操作一级指针来进行自增运算,进而将一级指针指向的字符一个一个的存进数组就行了,二级指针作为参数传进函数,改变二级指针指向的一级指针的值,和将普通的字符指针作为参数传进函数,对字符指针指向的字符进行修改等操作原理是一样的。
函数中参数存储的系统和函数外变量存储的系统是两个独立的系统,函数外的变量存在内存中的,函数中参数存在寄存器中的,所以函数中参数虽然传了变量进来,但是只是复制进来,对原来变量的值是没有改变的。指针也是一样的,指针传进来只是指针地址的复制的值,可以对这个地址指向的值操作,但是该地址的值在函数中直接改变时徒劳的,因为这个地址的值是复制过来的,想真正的改变就用指向这个指针的指针,二级指针的值作为参数复制进函数,虽然不能改变原二级指针的值,但是可以通过这个复制的二级指针的值来操作它指向的一级指针自增或自减等等,因为这就是指针的特性,传进函数无法改变原指针本身的值,但是可以改变它指向的地址的值。这是在学习指针和函数传值调用甚至于学习之后最容易搞错的问题,关键是编译的时候从来都不报错,让你头大一晚上,所以在这里用自己亲身经历写个博客也提醒学习C的朋友少走弯路。
修改后的代码如下:
/*
联系7-3 改写minprintf函数,使它能完成printf函数的更多功能
*/
#include <stdio.h>
#include <stdarg.h>//该头文件中定义了如何遍历参数表
#include <ctype.h>
/*
1.va_list是一种变量类型,变量ap用来引用后边的可变参数,指向参数的指针,实质上是char*
2.va_start(ap,fmt),该函数是初始化ap用来指向minprintf(char *fmt,...)中的最后一个有名字参数fmt
后面的第一个无名参数的首地址,使用ap之前必须调用一次va_start。
3.va_arg(ap,type)函数根据类型名type来返回当前的指针ap指向的参数的值,并把ap移到下一个参数的地址。
4.va_end(ap);是为了结束对可变参数的处理,源码中实际上把ap定义为空指针,只是为了代码对称
*/
void minprintf(char* fmt, ...) {
va_list ap;
char* p, * sval;
char** p_to_p;
int ival;
unsigned uval;
double fval;
void* pval;
char jd[20];
//char* p_jd = jd;
void jingdu_ctrl(char **,char*,char *);
va_start(ap, fmt);
char localfmt[100];
p = fmt;
p_to_p = &p;
for (; *p; p++) {
if (*p == '%') {//处理占位符
if (*++p) {
//printf("函数外前p = %p\n", p);
jingdu_ctrl(p_to_p,jd,localfmt);
//printf(jd);
//printf("函数外后p = %p\n", p);
switch (*p) {
case 'd':
case 'i':
ival = va_arg(ap, int);
printf(localfmt,ival);
break;
case 'f':
fval = va_arg(ap, double);
//printf("调用本函数\n");
printf(localfmt, fval);
break;
case 's':
sval = va_arg(ap,char*);
/*for (sval = va_arg(ap, char*); *sval; sval++) {
putchar(*sval);
}*/
printf(localfmt,sval);
break;
break;
case 'o':
uval = va_arg(ap, unsigned);
printf(jd, uval);
break;
/*case 'x':
uval = va_arg(ap, unsigned);
printf("%#x", uval);
break;
case 'X':
uval = va_arg(ap, unsigned);
printf("%#X", uval);
break;
*/
case 'u':
uval = va_arg(ap, unsigned);
printf(jd, uval);
break;
case 'c'://printf函数基本转换说明中是int类型
ival = va_arg(ap, int);
printf(jd, ival);
break;
case 'p':
pval = va_arg(ap, void*);
printf(jd, pval);
break;
/*case '\\':
goto swp;
break;*/
default:
putchar(*p);
break;
}
}
else {
break;
}
}
else if (*p == '\\') {//处理转义字符
switch (*++p) {
case 'n':
putchar('\n');
break;
case 't':
putchar('\t');
break;
case 'v':
putchar('\v');
break;
case 'r':
putchar('\r');
break;
default:
putchar(*p);
break;
}
}
else {
if (*p == '2') {
printf("函数从这里经过\n");
}
putchar(*p);
continue;
}
}
va_end(ap);
}
void jingdu_ctrl(char **p,char* jd,char *localfmt) {
int i = 0;
jd[i++] = '%';
//jd[i++] = '%';
while (!isalpha(**p)) {
jd[i++] = **p;
(*p)++;
//printf("p = %p\n",p);
//p++;
}
if (isalpha(**p)) {
jd[i++] = **p;
//jd[i++] = '\n';
jd[i] = '\0';
}
sprintf(localfmt,"%s",jd);
//jd[0] = '\0';
printf("%s\n",localfmt);
}
int main(void) {
int a = 100;
char b[] = "250%-.8lf\n";
char* p = b;
double c = 1.0;
minprintf("a = %-5d,b = %20s,c = %5.2f\n", a, b, c);
printf(b,c);
return 0;
}