13.5 字符串常量
当一个字符串常量出现于表达式中,它的值是个指针常量。编译器把这些指定字符的一份副本存储在内存的某个位置,并存储一个指向第一个字符的指针。但是,当数组名用于表达式中时,它们的值也是指针常量。我们可以对它们进行下标引用,间接访问以及指针运算。这些操作对于字符串常量来说是不是也有意义呢?让我们来看一些例子。
下面这个表达式是什么意思呢?
"xyz" + 1
对于绝大多数程序员而言,它看上去像堆垃圾。它好像是试图在一个字符串上面执行某种类型的加法运算。但是,当你记得字符串常量实际上是个指针时,它的意义就变得清楚了。这个表达式计算“指针值加上1”的值。它的结果是个指针,指向字符串中的第2个字符:y。那么下面这个表达式又是什么呢?
*"xyz" == 'x';
对一个指针执行间接访问操作时,其结果就是指针所指向的内容。字符串常量的类型是“指向字符的指针”,所以这个间接访问的结果就是它所指向的字符:x。注意表达式的结果并不是整个字符串,而只是它的第1个字符。
下一个例子看上去也是有点奇怪,不过现在应该能够推断出这个表达式的值就是字符z:
*"xyz"[2] == 'z';
最后这个例子包含了一个错误。偏移量4超出了这个字符串的范围,所以这个表达式的结果是一个不可预测的字符:
*( "xyz" + 4 )
/*
** 字符串常量。
*/
#include <stdio.h>
#include <stdlib.h>
int main( void ){
printf( "\"xyz\" = %p, \"xyz\" = %s\n\"xyz\" + 1 = %p, \"xyz\" + 1 = %s\n",
"xyz", "xyz", "xyz" + 1, "xyz" + 1 );
printf( "*\"xyz\" = %c\n", *"xyz" );
printf( "\"xyz\"[2] = %c\n", "xyz"[2] );
/*
** a unpredictable value.
** Because "xyz" + 4 is beyond boundary.
** *("xyz" + 4)
*/
return EXIT_SUCCESS;
}
/* 输出:

*/
什么时候人们可能想使用类似上面这种形式的表达式呢?程序13.4的函数是一个有用的例子。大家都能推断出这个神秘的函数执行了什么任务吗?提示:用几个不同的输入值追踪函数的执行过程,并观察它的打印结果。答案将在本章结束时给出。
程序13.5包含了一个函数,它把二进制值转换为字符并把它们打印出来。你第一次看到这个函数是在程序7.6中。我们将修改这个例子,以十六进制的形式打印结果值。第一次修改很容易:只要把结果除以16而不是10就可以了。但是,现在余数可能是0~15的任何值,而10~15的值应该以字母A~F来表示。下面的代码是解决这个问题的一种典型方法:
remainder = value % 16;
if( remainder < 10 ){
putchar( remainder + '0' );
} else{
putchar( remainder - 10 + 'A' );
}
这里使用了一个局部变量来保存余数,而不是3次分别结算它。对于0~9的余数,只需和以前一样打印一个十进制数字。但对于其它余数,就是它们以字母的形式打印出来。代码中的测试是必要的,因为在任何常见的字符集中,字符A~F并不是立即位于数字的后面。
/*
**神秘函数。
**
**参数是一个0~100的值。
*/
#include<stdio.h>
void mystery( int n ){
n += 5;
n /= 10;
printf( "%s\n", "**********" + 10 - n );
}
程序13.4 神秘函数 mystery.c
/*
** 神秘函数。
*/
#include <stdio.h>
#include <stdlib.h>
void mystery( int n );
int main( void ){
int i;
for( i = 5; i <= 100; i += 5 ){
mystery( i );
}
return EXIT_SUCCESS;
}
/*
**神秘函数。
**
**参数是一个0~100的值。
*/
void mystery( int n ){
n += 5;
n /= 10;
printf( "%s\n", "**********" + 10 - n );
}
/* 输出:

*/
/*
**接受一个整型值(无符号),把它转换为字符,并打印出来。前导零被去除。
*/
#include<stdio.h>
void binary_to_ascii( unsigned int value ){
unsigned int quotient;
quotient = value / 10;
if( quotient != 0 ){
binary_to_ascii( quotient );
}
putchar( value % 10 + '0' );
}
把二进制值转换为字符 btoa.c
下面的代码用一种不同的方法解决这个问题:
putchar( "0123456789ABCDEF" [value % 16] );
同样,余数将是一个0~15的值。但这次它使用下标从字符串常量中选择一个字符串进行打印。前面的代码时比较复杂的,因为字符和数字在字符集中并不是相邻的。这个方法定义了一个字符串,使字母和数字相邻,从而避免了这种复杂性。余数将从字符串中选择一个正确的数字。
第二种方法比传统的方法要快,因为它所需要的操作更少。但是,它的代码并不一定比原来的方法更小。虽然指令减少了,但它付出的代价是多了一个17字节的字符串常量。
/*
**接受一个整型值(无符号),把它转换为字符,并打印出来。前导零被去除。
*/
#include<stdio.h>
#include<stdlib.h>
void binary_to_dec_ascii( unsigned int value );
void binary_to_hex_ascii( unsigned int value );
void binary_to_hex_ascii_2( unsigned int value );
int main( void ){
unsigned int value;
value = 34;
printf( "the dec form of %d is:", value );
binary_to_dec_ascii( value );
printf( "\n" );
printf( "the hex form of %d is:", value );
binary_to_hex_ascii( value );
printf( "\n" );
printf( "the hex form of %d is:", value );
binary_to_hex_ascii_2( value );
return EXIT_SUCCESS;
}
void binary_to_dec_ascii( unsigned int value ){
unsigned int quotient;
quotient = value / 10;
if( quotient != 0 ){
binary_to_dec_ascii( quotient );
}
putchar( value % 10 + '0' );
}
void binary_to_hex_ascii( unsigned int value ){
unsigned int quotient;
int remainder;
quotient = value / 16;
if( quotient != 0 ){
binary_to_hex_ascii( quotient );
}
remainder = value % 16;
if( remainder < 10 ){
putchar( remainder + '0' );
} else{
putchar( remainder - 10 + 'A' );
}
}
void binary_to_hex_ascii_2( unsigned int value ){
unsigned int quotient;
int remainder;
quotient = value / 16;
if( quotient != 0 ){
binary_to_hex_ascii_2( quotient );
}
putchar( "0123456789ABCDEF"[value % 16] );
}
/* 输出:

*/
提示:
但是,如果程序的可读性大幅下降,对于因此获得的执行速度的略微提高是得不偿失的。在使用一种不寻常的技巧或语句时,应确保增加一条注释,描述它的工作原理。一旦解释清楚了这个例子,它实际上就比传统的代码更容易理解,因为它更短一些。
神秘函数的意思是它根据参数值的一定比例打印相应数量的星号。换句话说,这个函数打印一幅柱状图的一横,它比传统的循环方案要容易得多,效率也高得多。