【程设大作业】printf 的实现
我决定挂(biao)一挂(biao)我们的这个程设大作业。
(同样是大一,别人家的大作业是写一个 jumping game,怎么到你这就是个 printf 呢。。。
Task
一句话,就是要手写 printf。
具体来讲,你需要自己实现一个函数(C 语言),名叫 myprintf,其功能和 printf 一致——参数第一个是字符串format[]
,后面是任意个参数,然后能把这些东西输出出来,返回值是一共输出了多少个字符。
当然你不能在 myprintf 里内嵌 printf,只能用 putchar。
为了简化问题,对于 % 后面的转换字符只作部分规定(见下表),其他如 %lld、%a 等视为未定义。下面是题目的部分pdf:
考点
第一自然是可变参列表的使用了。
第二就是对format
这个字符串的处理。
第三就是如何输出这些参数
隐藏的第四点是你要发现 printf 的所有隐藏鬼畜
sol
可变参列表
头文件是:
#include<stdarg.h>
参数数量、类型是不定的,故曰可变参列表,用三个点来表示:
int myprintf(const char format[], ...)
{
}
然后你要需要一个指向参数的指针:
va_list ap; // 定义指针ap
va_start(ap,format); // 初始化ap,将它指向format的末尾,这样它后面就是那个...列表了。
接着就是通过指针ap
提取列表里的参数,例如左到右依次是 int、char、double、字符串,那么可以这样:
int a=va_arg(ap,int); // 执行完这个操作之后ap会自动往后跳,比如这里就会跳到这个int的末尾
char b=va_arg(ap,int); // 因为char也是int。。。比较奇怪的设定。。
double c=va_arg(ap,double);
char *d=va_arg(ap,char*);
用完了之后要把ap
释放掉:
va_end(ap);
字符串处理
这个瞎处理就好了,一位一位扫format
,碰到 % 就后面接一堆判断和 switch,否则就直接输出。
至于许多小朋友们担心的 \n、\t、\233 等转义字符怎么办,其实 C 已经自动帮你转好的了,它们本身就只是一个字符而已。
输出参数
有机智的小朋友说直接 _vsnprintf 就好啦。。。快去请卢来佛祖
基本的思想都是把数字转化成数组,然后倒着输出。这个大家在学进制转换的时候天天玩的了。
然后为了方便统计一共输出了多少字符,建议最后的输出统一用字符串进行。即把数组也再转换成字符串,丢到统一的输出函数里去。
麻烦的是实数,你不能把任何一个部分转成整数来处理,因为会爆 long long。然后你还要保持精度。
所以要想个办法。可以先转成科学记数法,意思就是对于实数 要先求出一个最大的 的幂 使得 ,然后从高位开始一位一位除下去,存到数组里。当然,视不同的输出格式要做些调整,比如 %f 输出的时候无视 的情况。
另一个办法是 C 里有一个库函数可以直接把实数转成字符串。但是这个精度不好,输出 的时候最后一位就被吞掉了。所以不推荐。
注意四舍五入,这里会有一个类似于高精度的运算。(恶心吧。。
鬼畜区
真正实现起来的时候,你会发现 printf 是有多么的鬼畜。。。
四舍五入对吧,好那我就四舍五入。诶为什么printf("%.f",3.5);
输出 3 啊???
诶为什么printf("%.f",3.55);
就是 4 了啊???
再输出一个printf("%.f",3.5000001);
,懂了,这逼居然是恰好 0.5 的时候不进位,要严格大于 0.5 才进位。。。
这时忽然想起竞赛的时候常用的四舍五入保留整数的方法——先加 然后再转成 int。确实只有这种进位规则才能使这个方法正确,唉,学艺不精。
好的现在肝完 %f 和 %e 来肝 %g 了。
诶不对为啥 printf("%.6g",0.00001234);
是 1.234e-005 啊??不是说好了默认 6 位精度的吗??诶为啥printf("%.4g",acos(-1));
是 3.142 啊这个精度也不对啊??
输出研究了一通,%f 和 %e 的精度是指小数点后保留多少位,%g 的精度是指有效数字。。。
好的这些都只是小鬼畜。我们试着输出一发:
int haha=2147483647;
double tst=3.6;
printf("%yha %.yha %33.33ttttt %%..... %%d %hd\n",haha,haha);
诶这啥玩意???
再来。。
double tst=3.6;
printf("%...f %----10.f\n",tst,tst);
这。。。
自动去掉重复的东西??
printf("%.---3f\n",tst);
这。。。
精度自动取绝对值,好像都能解释,没什么问题。。那大概就是这样了吧,验证下。。
printf("%----10.....----10f\n",tst);
?????????????????
弃疗
代码
话说为什么 **** 的代码框变得这么丑。。再这样我要搬家了。。
(upd:居然要在```后面加语种。。。
先是 myprintf
#include<math.h>
#include<stdio.h>
#include<string.h>
#include<stdarg.h>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define maxlen 1000005
typedef unsigned int uint;
const double eps=1e-14;
int max(int x,int y) {return (x>y) ?x :y ;}
int charnum,d0,d[maxlen],len,ali,width;
char S[maxlen];
void Put(char ch)
{
putchar(ch);
charnum++;
}
void PutString(char *S,int limit)
{
int len=strlen(S);
if (limit>-1 && limit<len) len=limit;
if (ali) fo(i,1,width-len) Put(' ');
fo(i,0,len-1) Put(S[i]);
if (!ali) fo(i,1,width-len) Put(' ');
}
void PutChar(char ch)
{
S[0]=ch, S[1]='\000';
PutString(S,-1);
}
void PutInt(int x,int limit)
{
if (x<0)
{
S[len++]='-';
x=-x;
}
if (x==0) d[d0=1]=0;
else for(d0=0; x; x/=10) d[++d0]=x%10;
fo(i,1,limit-d0) S[len++]='0';
fd(i,d0,1) S[len++]=d[i]+'0';
S[len++]='\000';
PutString(S,-1);
}
void PutUInt(uint x,int ty,int cap,int limit)
{
if (x==0) d[d0=1]=0;
else for(d0=0; x; x/=ty) d[++d0]=x%ty;
fo(i,1,limit-d0) S[len++]='0';
fd(i,d0,1) S[len++]=(d[i]<10) ?d[i]+'0' :d[i]-10+(cap?'A':'a');
S[len++]='\000';
PutString(S,-1);
}
void DigitToArray(double x,double ep,int len)
{
d[d0=0]=0;
fo(i,1,len)
{
d[++d0]=fmod(x,ep*10)/ep;
x-=d[d0]*ep;
ep/=10;
}
d[d0]+=(fmod(x,ep*10)/ep>5+eps);
for(int i=d0; d[i]>9; i--) d[i]-=10, d[i-1]++;
}
void PutFloat(double x,int acr)
{
if (x<0)
{
S[len++]='-';
x=-x;
}
int w=1;
double ep=1;
for(double xx=x; xx+eps>=10; xx/=10) w++, ep*=10;
DigitToArray(x,ep,w+acr);
int st=(d[0]==0);
fo(i,st,w) S[len++]=d[i]+'0';
if (acr)
{
S[len++]='.';
fo(i,w+1,w+acr) S[len++]=d[i]+'0';
}
S[len++]='\000';
PutString(S,-1);
}
void PutFloatE(double x,int acr,char cap)
{
if (x<0)
{
S[len++]='-';
x=-x;
}
int index=0;
double ep=1;
for(double xx=x; xx+eps>=10; xx/=10) index++, ep*=10;
for(double xx=x; xx>eps && xx+eps<1; xx*=10) index--, ep/=10;
DigitToArray(x,ep,1+acr);
index+=(d[0]>0);
int st=(d[0]==0);
S[len++]=d[st]+'0';
if (acr)
{
S[len++]='.';
fo(i,st+1,st+acr) S[len++]=d[i]+'0';
}
S[len++]=cap;
if (index>=0) S[len++]='+'; else S[len++]='-', index=-index;
for(int i=100; i>=1; i/=10) S[len++]=(index/i)%10+'0';
S[len]='\000';
PutString(S,-1);
}
void PutFloatG(double x,int acr,char cap)
{
if (x<0)
{
S[len++]='-';
x=-x;
}
int index=0;
double ep=1;
for(double xx=x; xx+eps>=10; xx/=10) index++, ep*=10;
for(double xx=x; xx>eps && xx+eps<1; xx*=10) index--, ep/=10;
DigitToArray(x,ep,acr);
index+=(d[0]>0);
int st=(d[0]==0);
if (index<-4 || index>=acr)
{
while (st<d0 && d[d0]==0) d0--;
S[len++]=d[st]+'0';
if (st<d0)
{
S[len++]='.';
fo(i,st+1,d0) S[len++]=d[i]+'0';
}
S[len++]=cap;
if (index>=0) S[len++]='+'; else S[len++]='-', index=-index;
for(int i=100; i>=1; i/=10) S[len++]=(index/i)%10+'0';
} else
{
int w=max(index+1,0);
while (st+w-1<d0 && d[d0]==0) d0--;
if (!w) S[len++]='0';
else fo(i,st,st+w-1) S[len++]=d[i]+'0';
if (st+w-1<d0) S[len++]='.';
fo(i,1,-index-1) S[len++]='0';
fo(i,st+w,d0) S[len++]=d[i]+'0';
}
S[len]='\000';
PutString(S,-1);
}
int myprintf(const char format[],...)
{
int n=strlen(format);
charnum=0;
va_list ap;
va_start(ap,format);
fo(i,0,n-1) if (format[i]=='%' && i<n-1)
{
int now=i;
ali=1, width=0;
int acr=0, hasacr=0, h=0, l=0, get;
while (format[i+1]=='-') ali=0, i++;
while (format[i+1]>='0' && format[i+1]<='9') width=width*10+format[++i]-'0';
if (format[i+1]=='.') hasacr=1, i++;
if (format[i+1]=='-') i++;
while (format[i+1]>='0' && format[i+1]<='9') acr=acr*10+format[++i]-'0';
if (format[i+1]=='h') h=1, i++;
if (format[i+1]=='l') l=1, i++;
len=0;
switch (format[++i]) {
case 'd': case 'i':
get= h ?(short)va_arg(ap,int) :va_arg(ap,int) ;
PutInt(get,acr);
break;
case 'o':
get= h ?(short)va_arg(ap,uint) :va_arg(ap,uint) ;
PutUInt(get,8,0,acr);
break;
case 'x': case 'X':
get= h ?(short)va_arg(ap,uint) :va_arg(ap,uint) ;
PutUInt(get,16,(format[i]=='X'),acr);
break;
case 'u':
get= h ?(short)va_arg(ap,uint) :va_arg(ap,uint) ;
PutUInt(get,10,0,acr);
break;
case 'c':
PutChar(va_arg(ap,int));
break;
case 's':
PutString(va_arg(ap,char*),(hasacr)?acr:-1);
break;
case 'f':
PutFloat(va_arg(ap,double),(hasacr)?acr:6);
break;
case 'e': case 'E':
PutFloatE(va_arg(ap,double),(hasacr)?acr:6,format[i]);
break;
case 'g': case 'G':
PutFloatG(va_arg(ap,double),(hasacr)?max(acr,1):6,format[i]-2);
break;
case 'p':
PutUInt((uint)va_arg(ap,void*),16,0,8);
break;
case '%':
Put('%');
break;
default:
fo(j,now,i) Put(format[j]);
}
} else Put(format[i]);
va_end(ap);
return charnum;
}
然后是一些测试
#include"myprintf.c"
int main()
{
//freopen("main.out","w",stdout);
long long ll=2147483647; ll++; ll=ll*ll; ll=ll-1+ll;
printf("%e\n",acos(-1)-3);
myprintf("%e\n",acos(-1)-3);
printf("%f %f %f\n",-acos(-1),(double)ll,1.0/22);
myprintf("%f %f %f\n",-acos(-1),(double)ll,1.0/22);
puts("");
int a=20, b=30, n;
double c=10.5, d=100000000;
char e='m';
char s[9]="myprintf";
myprintf("%d %d\n",a,a+b);
myprintf("%f %e\n",c,d);
n=myprintf("%c\t%s\n",e,s);
myprintf("%d\n",n);
puts("");
printf("%g %g %.2g %.2g\n", 0.00001234,0.0001234,123.45,23.45);
myprintf("%g %g %.2g %.2g\n", 0.00001234,0.0001234,123.45,23.45);
float x=654.321; double pi=acos(-1);
printf("%f %e %g %.8f %.8e %.8g %.4g\n",x,x,x,pi,pi,pi,pi);
myprintf("%f %e %g %.8f %.8e %.8g %.4g\n",x,x,x,pi,pi,pi,pi);
printf("%g %.5g %.3g\n",0.034,9999.99,0.0009999);
myprintf("%g %.5g %.3g\n",0.034,9999.99,0.0009999);
puts("");
int haha=2147483647; double tst=3.6;
printf("%yha %.yha %33.33ttttt %%..... %%d %hd\n",haha,haha);
myprintf("%yha %.yha %33.33ttttt %t %..... %%d %hd\n",haha,haha);
printf("%33.33\n");
myprintf("%33.33\n");
printf("%----10.....----10f\n",tst);
myprintf("%----10.....----10f\n",tst);
printf("%-10..3f\n",tst);
myprintf("%-10..3f\n",tst);
printf("%...f %----10.f\n",tst,tst);
myprintf("%...f %----10.f\n",tst,tst);
printf("%.---3f\n",tst);
myprintf("%.---3f\n",tst);
printf("%d\n",printf("\0"));
printf("%d\n",myprintf("\0"));
}