C并没有字符串类型,C字符串是一个以null('\0')结尾的字符数组,用null标识字符串结束。如{'a','b','c','d','\0'},只有包含这个'\0’才算C字符串。注意null也好,'\0'也罢,都只是0的不同表达,如:
char a[] = “ABCDEF”;
printf(“a=%s\n”,a);
a[2]=0;
printf(“a=%s\n”, a);
运行看看,明白什么是null以及它的作用了吧!
字符串与字符
要注意区分'a'和"ABCDEF"这两种形式。单引号修饰的单字符实际代表一个整数,其值为该字符在编译器默认字符集中(一般为ascii码)的映射值。如char x =’a’;查ascii表,等价于char x = 0x61。而用双引号修饰的字符序列,即字符串常量,可看作无名字符数组,该数组被双引号中的字符集和一个隐含的'\0’初始化。
字符串与字符数组
结尾是否有'\0'是字符串和一般字符数组的唯一区别,{'a','b','c','d’,'\0'}是字符串,而{'a','b','c','d’}是字符数组。这两种形式很好区分,但定义太繁琐,于是C为了简便,引入另一种字符串定义形式,即"abcd”,它等价于{'a’,'b’,'c’,'d’,'\0’}。这种定义更简单,但外表看不到null(尽管编译器知道),需要不断自我提醒"abcd”是字符串,结尾隐含'\0’。可看不见摸不到总是容易混淆。如:
char a[] = “ABCDEF”;
char b[] = {'A','B','C','D','E’,'F'};
叙述正确的是:A)ab完全相同 B)ab长度相同 C)ab都是字符串 D)a数组比b数组长
只有D。因为"ABCDEF"等价{'A','B’,'C’,'D’,'E’,'F’,'\0'},因此ab并不相同,a长度为7而b长度为6,a为字符串,b只是普通字符数组。
编程中几乎所有和C字符串相关的错误都和这个隐含的'\0’相关。
strxxx与memxxx
除标准字符串处理函数外,还有一些函数也能操作字符串,如memcpy代替strcpy,memcmp代替strcmp等。这两套怎么选择?
a. 首先,非字符串操作,不能用strxxx,如:
char a[4],b[4],c[4];
a[0]=0; a[1]=2; a[2]=0; a[3]=1;
memcpy(b, a, 4);
strncpy(c, a, 4);
源数据a[4]中间有0,strncpy会把0当成字符串结束符而提前终止拷贝。而memcpy执行内存拷贝,不受内容影响。
b. 对于字符串操作,都可以用。strxxx专门针对字符串特性,遇到null自动结束,使用起来更方便。
'\0’的代价
字符串默认以'\0’结尾,标识字符流结束,区分字符串和一般字符数组,表达形式更自然。但作为代价,'\0’也引出很多问题:
a. 字符串中除结尾外不能包含任何0,所以它不能保存二进制数据。
b. 要搞清哪些strxxx结尾自动补'\0’,哪些不补。如strncpy(dst, src, n)不自动补0,如果dst在操作前未清零,而n个src只填充部分dst,那dst就可能因缺少结束符而成为非法字符串。
c. 而strcpy(dst,src)中,如果src是未置'\0’的字符数组,strcpy就找不到结束符而死循环。两害择其轻,最好用strnxxx()代替strxxx()。
d. strxxx每次都判断是否null,性能较差且随字串长度变化(油漆工的故事)。如下strcat:
void strcat( char* dest, char* src )
{
while (*dest) dest++;
while (*dest++ = *src++);
}
char longstr[1000];
longstr[0] = '\0';
strcat(longstr,"John, ");
strcat(longstr,"Paul, ");
strcat每次都要扫描整个dst字符串,寻找尾部'\0’,字符串越长,性能越差。需要注意字符串操作的性能不确定性。
避免字符串硬编码
字符串一般不是程序的关键角色,程序员往往直接把它们随意写到代码里,如:
if (..) { MessageBox(_T("1.0.0.1, xxx Company"); }
但是,这种字符串硬编码会带来如下问题:
a. 可读性不好且无法集中修改:裸字符串和奇异数一样,含义不明确,如果分散写在代码里,难以统一维护。最好用define或全局const变量来表示,即反边统一修改维护,也能明确显示含义,如:
#define SOFTWARE_VERSION_INFO ("1.0.0.1, xxx Company ")或const char VersionInfo[] = "1.0.0.1, xxx Company ";
b. 难以统计资源占用:字符串常量占用静态只读内存,如果某程序中字符串很多,需要统计资源占用,把它们提取出来专门放置,比分散在源码里更方便统计和优化。
c. 配置多语言版本:如果字符串硬编码,要出多语言版,必须一个个去修改字符串,再重新编译,导致一种语言一份代码。可事先把所有与显示相关的字符串放到资源配置文件中(自定义的保存资源的配置文件),配合编译开关选择切换不同语言的字符串。
油漆工的笑话:
某人得到一份油漆工作,负责在马路中间喷涂画线。第一天,他带着一罐漆来到他负责的路段,喷涂了300码长的线。“干得不错!”老板称赞道,“真是一位麻利的工匠”,然后赏给他一个硬币。
第二天,他只喷涂了150码。“虽然不如昨天,但仍然算得上一位麻利的工匠!150码也不错,”老板又赏给他一个硬币。
然而第三天,他只喷涂了30码长的马路。“才30码!”老板吼道。“这太令人难以接受了!第一天你的工作量是今天的10倍!怎么回事?”
“我尽力了,”油漆工哭丧着脸。“可一天一天下来,我离油漆罐越来越远!”