开门篇:CC++中的多字节字符、转义字符、三字母词,顺带
提及字符串常量
估计转义字符⼤家都听过,多字节字符通过微软常⽤的wchar_t也都了解过,三字母词是神马?不知道吧?之写这篇博客前我也不知道。因为编程中很少⽤得到。
但是等你碰到的时候也许会感到⼀头雾⽔。
⽐如,你想打印"What??"
printf( "What\n" );
得到的却是“What|”,你是不是会下⼀跳。
另外,你真的了解字符常量吗?
你未必知道,⽐如:
----------------------------------------------------------------------------------
'M','\n' ,'\39','\039','\xa', '\xab','\Xab','hah','??('
---------------------------------------------------------------------------------
话说它们都是 字符常量。
我们分别来看:
第⼀个是普通单字节字符,
有反斜杠(\)的分三种情况:
1.'\n' 都知道,是⼀般转义字符。
2.
a)'\39' 等同于'\039',是由1~3个⼋进制数字序列组成的⼋进制转义字符,形式为'\ooo'所以,039 (⼋进制)== 33(⼗进制)在ASICII码表中代表半⾓的感叹号字符 '!'所以:'\039' == 33 == '!'。
b)'\xa', '\xab','\XAB' 是由x或X加上1~216进制数字序列组成的⼗六进制转义字符,形式为'\xhh',如'\xa',0xa(⼗六进制) == 10(⼗进制),在ASCII 码表中代表LF(line feed),⾏满,也就是换⾏,C语⾔中定义的'\n'的ASICII码值就是10,所以'\n' == '\xa' == '\XA'。
倒数第⼆个'hah'就是多字节字符。没有反斜杠哦~
3.最后⼀个'??( ' 这是个三字母词,对应的字符是'[',还有与之对应的⼀个三字母词是 '??)'!它代表字符']'
什么!看得有点⼉晕?来,让我慢慢道来~~~~~~
⾸先说说多字节字符。
⼀、多字节字符
多字节字符,我们也称为多字节字符常量(multi-character character constant)。多字节字符使⽤多个字节表⽰⼀个字符。它是字符常量的⼀种,⽐如UNICODE编码通常使⽤2个字节表⽰⼀个字符。
在ANSI C(也就是通常说的C89)标准产⽣以前其实并没有宽字符的,直到ANSI对K&R C修订以后引⼊了宽字符概念。其主要⽬的是解决亚洲国家⽂字编码问题。⽤多个字节表⽰⼀个字符。⾃此C语⾔不只提供char类型,还提供wchar_t类型(宽字符),此类型定义在stddef.h 头⽂件中。wchar_t指定的宽字节类型⾜以表⽰某个实现版本扩展字符集的任何元素。
关于ANSI C和K&R C的关系和历史请点击⾃⾏脑补。
多字节字符和宽字符(wchar_t)的主要差异在于宽字符占⽤的字节数⽬都⼀样,⽽多字节字符的字节数⽬不等,这样的表⽰⽅式使得多字节字符串⽐宽字符串更难处理。⽐⽅说,即使字符'A'可以⽤⼀个字节来表⽰,但是要在多字节的字符串中到此字符,就不能使⽤简单的字节⽐对,因为即使在某个位置到相符合的字节,此字节也不见得是⼀个字符,它可能是另⼀个不同字符的⼀部分。然⽽,多字节字符相当适合⽤来将⽂字存储成⽂件。但是C为我们提供了⼀些多字节字符和单字节字符转换的函数:
mblen()//返回⼀个字符的字节数
mbstowcs()//把多字节字符串转换为宽字符串
mbtowc()/btowc()//把多字节字符转换为宽字符
wcstombs()//把宽字符串转换为多字节字符串
wctomb()/wctob()//把宽字符转换为多字节字符
另外,C语⾔本⾝并没有定义或指定任何编码集合,或任何字符集(基本源代码字符集和基本运⾏字符集除外),⽽是由实现指定如何编码宽字符,以及要⽀持什么类型的多字节字符编码机制。
对于Unicode字符集,许多实现版本使⽤Unicode转换格式UTF-16和UTF-32来处理宽字符。如果遵循Unicode标准,wchar_t类型⾄少是16或32位长,⽽wchar_t类型的⼀个值就代表⼀个Unicode字符。
关于Unicode和Windows下宽字符转换请移步。
那字符常量在内存中的映射结构是怎样的呢?
关于字符常量,C和C++的映射⽅式不⼀定是相同的。
使⽤单引号是在C中是“Character constants”(字符常量),在C++中是"Character literals"(字符常量),(这⾥"Character constants"和"Character literals"只是叫法不同,含义是相同的。 ”literals“有时也被译为”字⾯量“。)使⽤双引号是⽆论C还是C++都是“String literals”(字符串常量),前者通常视为单个字符,后者视为字符串,更确切的说前者只能“映射为⼀个值”,该值在C中总是映射为int类型的值,在C++中如果是⼀个字符,就映射为char类型的值,如果是多个字符那么映射为int类型。
⽆论C还是C++在Character constants多于⼀个时具体能映射多少个字符并且映射到具体什么样的值都是依赖于程序执⾏环境和编译器的,标准对此的规定都是明确说由实现定义。
下⾯⽰例代码被认为多字节字符常量最有意义的使⽤⽅式了。
enum
{
kActionForward = 'frwd',
kActionBackward = 'bkwd',
kActionLeft = 'left',
kActionRight = 'rght',
kActionZap = 'zap!'
};
enum变量的本质是int,⽆法⽤字符串常量。⽤多字节字符增加代码可读性未必不是⼀个好⽅法。
那么对于下⾯这个复赋值:
char c_a= 'abc';
int i_a = 'abc';
c_a和i_a的值应该是多少呢?(默认使⽤通⽤的g++ C89编译)。
先别着急敲代码测试,我们先分析⼀下:
‘abc’肯定是个多字节字符,不是字符串。
上⾯已经说了,对于字符常量,在C中总是映射为int,在C++中如果是⼀个字符,就映射为char类型的值,如果是多个字符那么映射为int类型。那么这⾥明显会被映射为int类型。那么它有32个⼆进制位。
所以c_a得到的值肯定是不全的,因为char类型只有8个⼆进制位。
我们写段程序看看结果:
int _tmain(int argc, _TCHAR* argv[])
{
char c_a= 'abc';
int i_a = 'abc';
printf("c_a = %c, i_a = %x\n", c_a, i_a);
while(1);
return 0;
}
这⾥i_a我是⽤16进制打印的,⽅便看结果。
可以看到c_a的结果是字符'c',i_a的结果是0x616263,为什么是这个结果呢?
对ASCII码敏感的⼈可能⼀看就知道原因了。
0x61 = 97,对应ASCII字符为'a',
0x62 = 98,对应ASCII字符为'b',
0x63 = 99,对应ASCII字符为'c',
看来我们的编译器解释⽅式是从左到右依次对应⼆进制的⾼低位。
对于c_a,被截断后只留下低8位,因此得到的是0x63,也就是字符'c'了。
那'012'对应的int值是多少呢?
当然是0x303132啦~不要问我为什么,我不告诉你。
这个怎么玩呢?
来!我们玩⼀玩:
int isNumLessThanZero(int m)
{
if(m < 0)
{
return 'yes';
}
return 'no';
}
int _tmain(int argc, _TCHAR* argv[])
{
int i_a = -1;
if(isNumLessThanZero(i_a) == 'yes')
{
printf("i_a is less than 0.");
}
else
{
printf("i_a is greater than 0.");
}
while(1);
return 0;
}
是不是感觉这时候‘yes’和'no'可以取代true和false了呢?然后⽤它来那啥了呢?哈哈哈哈哈哈哈哈
神经病⼜犯了、、、
注意:ANSI C标准虽然允许使⽤多字节字符常量,但是但是他它们的实现在不同的编译器编译环境中可能不⼀样。因此不⿎励使⽤。但是当我们遇到了应该知道为什么。
既然提到了字符串,那么就顺便说以下字符串常量的内容。
C语⾔本⾝是不存在字符串类型的,因为C语⾔认为字符串通常可以存储在字符数组中。⽽C++则提供了字符串类型string。不过C语⾔提供了字符串常量。尽管C语⾔没有提供字符串类型,但事实上却存在字符串概念。它就是⼀串以NUL字节(即'\0')结尾的零个或多个字符。
K&R C与ANSI C对字符串常量的存储形式声明是不同的。
K&R C把所有的字符和NUL终⽌符都存储于内存中的某个位置。它表明具有相同值的不同字符串常量在内存中是分开存储的。因此,许多编译器都允许程序修改字符串常量。
⽽ANSI C声明如果对⼀个字符串常量进⾏修改,其结果是未定义的。它允许把具有相同值的不同字符串存储于⼀个地⽅(这样,实际上就只有⼀个字符串了)。因此,许多ANSI编译器不允许修改字符串常量的原因。因为⼀修改,其他所有相同字符串的常量也会受到改变。
字符串比较函数实现下⾯通过⼀个程序来证明ANSI C编译器对字符串的处理是在内存中只保存⼀份拷贝。
int _tmain(int argc, _TCHAR* argv[])
{
char* p1 = "hello";
char* p2 = "hello"; // 与p1指向的字符串相同
printf("&p1[0] = %p, &p2[0] = %p\n", &p1[0], &p2[0]); // "hello"第⼀个字符的地址。
printf("p1 = %p, p2 = %p\n",p1, p2); // p1和p2指向的地址
char* q = "hellos"; // 注意多了⼀个's'
printf("&q[0] = %p, q = %p\n", &q[0], q); // 打印q指向的地址和"hellos"第⼀个字符的地址
while(1);
return 0;
}
我们打印其值:
我们看到p2,==p2==&p1[0]==&p2[0],相等的原因就是"hello"这个字符串常量⽆论出现多少次,它在内
存中只有⼀份。(事实上它的存储位于进程中虚拟存储器的只读常量区)。
因此,⼤多数编译器不允许你通过指针修改字符串常量的值,因为⼀旦修改,其它与之相同的字符串也会被修改。
你或许可以这样理解字符指针:
当使⽤char*仅仅想指代⼀个字符的地址时,它的类型就是⼀般意义的char*(字符指针)。
当使⽤char*指代⼀个字符串常量的地址时,它的类型确切的说应该是cont char*,因为你⽆法通过该指针修改字符串常量的值。
另外,你看到⼀个字符串常量可以赋值给⼀个字符指针,实际上这样的理解并不正确。字符串常量的直接值是⼀个指针,char* 指针指代的到底是⼀个字符的地址,还是⼀个字符串常量的地址,这得看你如何打印。如果⽤%c,解引⽤⽅式打印,它就代表⼀个字符的地址,如果⽤%s的⽅式打印,它就代表⼀个字符串常量的地址。并且是直到遇到字符NUL(即'\0')才算该字符串常量的结束。这也是我们有时会产⽣混乱甚⾄导致段错误的原因(segmentation fault),因为我们可能访问了不可引⽤的位置,导致核⼼转储。这是不安全的。因此C++使⽤string解决了这⼀问题。
我们⽤⼀段程序来理解:
int _tmain(int argc, _TCHAR* argv[])
{
char* q = "hellos";
char* p_m = &q[1];
printf("p_m = %c, p_m = %s", *p_m, p_m);
char n = 'abc';
char* p_n = &n;
printf("p_n = %c, p_n = %s", *p_n, p_n); // %c解引⽤打印和%s打印
while(1);
return 0;
}
看看执⾏结果:
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论