1.printf函数解析
前面我们有讲过printf函数的格式为:
printf(“占位1 占位2 占位3……”, 替代1, 替代2, 替代3……);
今天我们进一步深入的解析一下这个函数
2.printf函数的特点
1.printf函数是一个变参函数(即参数的数量和类型都不确定)
2.printf函数的第一个参数是字符串
3.printf函数的第一个字符串参数中的具体内容就是需要打印的字符以及需要被替代的占位符
4.printf函数第一个参数之后的参数依次替代占位符
5.printf函数中占位和替代的数量和类型对应
3.变参函数
int main(){
printf("%c\n", 'a');// a
printf("%c %d\n", 'b', 10);// b 10
return 0;
}
4.第一个参数必须是字符串
int main(){
printf("the first argument must be string\n");
return 0;
}
5.第一个参数包含输出字符以及占位符
int main(){
printf("the first argument includes %s and %s\n", "character", "placeholder");
return 0;
}
后续的两个特点我就不一一阐述了
2.整型占位符
1.有符号整型的类型提升
在C语言中 有符号整型遵循一元数字提升的原则 即比int类型小的类型诸如char和short类型的一元数字都会自动提升为int类型
下面这段代码中 char类型的字节原本为1 但是由于进行了一元数字提升 导致变成了int类型 所以打印的结果是为4
int main(){
char a = 'a';
printf("%zu\n", sizeof(+a));// 4
return 0;
}
但是还有一个疑问 就是在visual studio中 long和int类型占用的字节数是一致的 既然这些小于等于int类型的类型都使用%d作为占位符的话 那么long这种等于int的类型适合使用%d作为占位符吗 其实是不妥当的
因为前面讲过C标准没有明确规定类型占用的字节数 所以不同的平台和编译器有着自己的标准 long类型占用的字节数在不同平台和编译器下是不尽相同的
为了体现程序的可移植性 防止代码打印结果产生偏差 我们对于long类型的占位符尽量使用%ld
int main(){
long l = 11;
printf("%ld\n", l);
return 0;
}
2.无符号整型的类型提升
其实他也同有符号整型的类型提升一样遵循一元数字提升 只不过区别在于说无符号整型涵盖的范围就是无符号的整数 而有符号整型涵盖的范围就是有符号的整数
说白了就是 比unsigned int小的无符号整型会自动提升为unsigned int类型 诸如unsigned char、unsigned short在内的一元数字会自动提升为unsigned int类型
下面这段代码可以体现无符号整型的一元数字提升
int main(){
unsigned char ch = 'a';
printf("%zu\n", sizeof(+ch));// 4
return 0;
}
下面这段代码则体现了无符号整型的占位符
int main(){
unsigned char ch = 'a';
unsigned int i = 11;
unsigned long l = 11;
unsigned long long ll = 11;
printf("%u %u %lu %llu\n", ch, i, l, ll);
return 0;
}
3.转换规范
其实占位符这种说法并不准确 较为准确的说法是转换规范 我们称%d这种形式的叫做转换规范
我可以就此举一个例子:% -#0 12 .4 l d
我们可以发现真正的转换规范包含了很多东西 不仅仅是一个字母
1.转换规范的组成
转换规范以%开头 然后依次跟着以下这些元素:
1.零个或者多个标志字符 比如:- # 0
2.一个可选的十进制整型常量表示的最小字符宽度
3.一个可选的.开头的精度 后跟十进制整形常量
4.一个可选的长度指示符
5.一个字符包含的转换操作 诸如:d u f这些字符
2.转换操作
转换操作会根据不同的转换方式 截取n个字节的二进制数据 转换成不同类型的字符或者字符串
我们从中就可以解释前面的一个疑惑:
double和float类型都可以使用占位符%f 原因在于%f是截取sizeof(double)字节的二进制数据 然后转换为双精度浮点型的字符
1.转换操作d
对于char、short类型的整型来说 通过转换操作d是可以被正确打印的 但是对于long或者long long类型来说 由于他们可能存在int类型范围之外的数据 所以通过这个转换操作显然是不能够被正确打印的 所以对于这种比int大的整型来说 采用他们各自相应的转换操作才是最合适的做法
int main(){
char c1 = 127;// char类型的最大数据
short s1 = 32767;// short类型的最大数据
char c2 = -128;// char类型的最小数据
short s2 = -32768;// short类型的最小数据
long long ll1 = 2200000000;// 超出int类型范围的数据
long long ll2 = 2100000000;// 包含在int类型范围的数据
printf("%d\n", c1);// 127
printf("%d\n", s1);// 32767
printf("%d\n", c2);// -128
printf("%d\n", s2);// -32767
printf("%d\n", ll1);// -2094967296
printf("%d\n", ll2);// 2100000000
return 0;
}
2.转换操作u
int main(){
unsigned char c1 = 255;// unsigned char的最大数据
unsigned short s1 = 65535;// unsigned short的最大数据
unsigned char c2 = 0;// unsigned char的最小数据
unsigned short s2 = 0;// unsigned short的最小数据
unsigned long long ll1 = 4294967295;// unsigned long long类型中包含在unsigned int类型中的数据
unsigned long long ll2 = 4300000000;// unsigned long long类型中不包含在unsigned int类型中的数据
printf("%u\n", c1);// 255
printf("%u\n", s1);// 65536
printf("%u\n", c2);// 0
printf("%u\n", s2);// 0
printf("%u\n", ll1);// 4294967295
printf("%u\n", ll2);// 5032704
return 0;
}
3.转换操作d和u的错误搭配
由于有符号int和无符号int的取值范围不一致 导致当我们错误搭配他们的时候 可能会产生不符合预期的结果
如果当错误搭配时 数据处于两者的共同范围之内 那么打印结果符合预期
但是如果不处于两者的共同范围之内的话 那么打印的结果就不符合预期
int main(){
unsigned int u1 = 2100000000;// 该数据处于有符号int和无符号int的共同范围之内
unsigned int u2 = 2200000000;// 该数据并不处于两者的共同范围之内
printf("%d\n", u1);// 2100000000
printf("%d\n", u2);// -2094967296
return 0;
}
4.注意数据类型的取值范围
如果数据超出了类型本身的取值范围 那么无论如何打印 都无法获取正确结果
int main(){
char ch = 200;// 200超出了char类型本身的数据范围 -128~127
printf("%d\n", ch);// -56
return 0;
}
5.转换操作c
打印ASCII码表中数值对应的字符
int main(){
char ch = 65;
short s = 66;
int i = 67;
long l = 68;
long long ll = 69;
printf("%c %c %c %c %c\n", ch, s, i, l, ll);
return 0;
}
6.转换操作f、e、E
截取sizeof(double)个字节的二进制数据 然后转换为双精度浮点型的字符
int main(){
float f = 12.34;
double d = 12.34;
printf("%f\n", f);// 12.34
printf("%f\n", d);// 12.34
printf("%e\n", f);// 1.234000e+01
printf("%E\n", f);// 1.234000e+01
return 0;
}
7.转换操作o、x、X
截取sizeof(int)个字节的二进制数据 然后将其转换为无符号八进制、十六进制形式整数的字符
int main(){
unsigned int i = 20;
printf("%u %o %x %X", i, i, i, i);// 20 24 14 14
return 0;
}
8.转换操作s
截取sizeof(char*)个字节的二进制数据 即地址值占用的字节数 接着以这个地址开始输出字符串
int main(){
printf("%s\n", "Hello World!");
return 0;
}