总结
C语言没有原生的字符串类型!
C-风格字符串就是最后一位为'\0'的字符数组!
C语言通过字符指针来管理字符串!
指向字符串的指针和字符串本身是分开的两个东西
存储多个字符的两种方式:申请连续字符空间和字符数组
这个链接,对c-风格字符串的理解也是很到位的,辅助理解很重要!
https://www.jianshu.com/p/4db7a5eedc42?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation
课本理解,C++ primer plus 中文版第五版,P225
首先从C语言开始说
1.什么是人理解的字符串?
program就是字符串,没有单引号,没有双引号,没有\0,任何连续的字符都认为是字符串(字符串在一起就是字符串),包括写的这句话,中英文夹杂,带标点符号,这在人看来也可以认为是字符串。
计算机没法理解人的思维,到底啥是字符串,就得给字符串立个规定,规定啥是字符串,让计算机一看,噢,这个东西就是个字符串!规定!规定!规定!
怎么立规定就要理解计算机中的数据类型。
类型是计算机存储数据的方式,让计算机知道,这个变量是int类型,那个变量是double类型。
首先明确一点,字符串不是一种类型,字符串是一种概念,概念的实现才是类型。
比如整型是概念,不是类型,int是类型,unsigned int是类型!
可以通过人为创建某个数据类型,让这个数据类型来表示字符串,计算机就理解了什么是字符串。
问题又来了,怎么创建这种类型来存储字符串,方便后续对字符串的操作?
C语言没有创建一个新的类型符来表示字符串,而是通过char类型的数组来存储字符串,暂且叫他char数组类型,这里强调下,char数组类型不是字符串的一种类型,只是字符串依靠char数组来存储。
前面提到了,字符串是概念,概念的实现是类型,而类型表示的是存储方法,那么表示字符串的char数组这种类型是如何存储字符串呢?
答案是把字符串拆分成一个个字符,依次放到数组里面,注意这里强调下,字符串是依靠字符数组,char[]来存储的,不代表字符数组就是字符串!
char str[8] = { 'p', 'r', 'o', 'g', 'r', 'a', 'm'};
这里也解释下,为啥会有单引号和双引号,单引号圈住了p,表示p是个字符常量,p是个值。
到这里,我们就明白了,c语言是如何在计算机中存储一个人类认知中的字符串。通过字符数组来依次存放每个字符。这里强调了人类认知的字符串,因为它与计算机认识的字符串是不同的。
2.接下来介绍什么是编译器眼中的字符串
编译器规定了,字符串和字符数组的区别:最后一位是否是空字符。最后一个字符为'\0'的字符序列就是字符串,也就是所谓的c-风格字符串。
这里是字符序列,不是字符数组。比如刚才提到的char str[8] = { 'p', 'r', 'o', 'g', 'r', 'a', 'm'};不是c-风格字符串,而char str[8] = { 'p', 'r', 'o', 'g', 'r', 'a', 'm', '\0' };是c-风格字符串,序列最后以'\0'结尾。
如何把人类眼中的字符串赋值给代码中的变量,让编译器知道变量是个字符串,而不是字符数组
这里强调是如何完成赋值!
赋值操作需要4个东西,0.变量的类型,1.左边的变量名,2.=号,3.右边的值,重点是右边的值
3.人得告诉计算机,我输的这个值是什么类型,比如1,它既可以是整型,也可以是字符类型。问题来了,怎么告诉,这个值是什么类型。
这是个有趣的问题
比如,可以这么写,int ch='a';这肯定是对的,'a'是个字符此时ch的值打印出来是97,这是因为ASCII码上的字符有对应的int值,这里属于映射。想说明的问题是,赋值这个操作得到的结果是否是我们想要的取决于2个因素,1.变量的类型,2.值的类型。这里就是值为char类型,变量为int类型,进行了类型转换,通过ASCII映射表,找到了char类型的值对应的int类型的值。再强调下变量的类型与值的类型影响赋值过程。
回到刚才的问题,人得告诉计算机,我输的这个值是什么类型。
变量设为int类型,然后进行赋值,那么编译器很自然知道,2是个数字,不是个字符
int a; // a是整型
a=2; // 2是个整型数字
很自然,设定一个属于字符串概念的某种类型变量的语句也应该类型,如下
[实现字符串的类型名称] a;
a=字符串;
如何把字符串program赋值给a,又回到了刚才的问题,人得告诉计算机,我输的这个值是什么类型
字符的值是通过单引号告诉编译器,这个值是字符类型,那么字符串的值怎么告诉编译器,这个值属于c-风格字符串。
这里注意,我强调的是C-风格字符串,不是c-风格字符串类型或字符串类型!,C语言没有字符串类型,只有C-风格的字符串。
规定:用单引号表示单个字符,双引号表示人类认知中的字符串,单双引号两者不可混用,这就是字符串的值。
使用单引号赋值就是前面的数组赋值的方法,只不过最后一个字符要求是'\0'
char str[8] = { 'p', 'r', 'o', 'g', 'r', 'a', 'm', '\0' };或者char str[] = { 'p', 'r', 'o', 'g', 'r', 'a', 'm', '\0' };
使用双引号赋值有两种语法
初始化方法1:char* str= "program";或者const char* str= "program"
初始化方法2: char str[] = "program";或者char str[8] = "program";本质是一样的
再次强调下,1和2的存储结构是一样的,都是依靠字符数组进行存储的,其实到这里就回答了问题,c语言是如何表示字符串的:
答案:通过指针,指向字符数组。
4.编译器认为字符串是什么类型
C语言中的字符串语句
char * p="hello world";
右值"hello world"的类型不叫字符串常量类型,c语言没有字符串常量类型,有很多种叫法,字符串直接量,字符串字面值等。
字符串字面值是一串常量字符,用双引号括起来的零个或多个字符表示字符串字面值
使用typeid("hello").name()来看其类型,返回的是A6_c,表示char 型 Array(数组),数组有6个元素,实际是个char * 的指针类型
5.如何理解字符串初始化在内存层次上的执行顺序是什么?
1.char* str = "program",在rom中申请(类似malloc)一个连续的内存空间,大小为7+1个字节,7字节存储字符数组program,编译器自动在字符数组后面添加了一个'\0'字符,存储在1字节上,于是便把"program"转换成c-风格字符串;接着,成功申请内存存放数据后,接着就会返回该字符数组申请的连续内存空间的首地址,这个地址是个char*类型,把地址赋值给同样是char *类型的变量str,完成这条语句。
- char str[] = "program",在rom中申请(类似malloc)一个连续的内存空间,大小为7+1个字节,7字节存储字符数组program,编译器自动在字符数组后面添加了一个'\0'字符,存储在1字节上。接着左侧的str变量会在栈内存中申请8个字节的连续空间,str的内存申请完成后,编译器把rom上连续内存上存储的program一一复制(等号的作用)到str指向的栈的连续内存空间中
第一种属于字符串常量,无法被修改,与字符串直接量相关联的内存空间位于只读部分。但是c语言是没有字符串常量这种类型,它是常量字符数组类型,即const char *。通过字符指针来管理字符串。
第二种当将字符串直接量赋值给字符数组的初始值的时候。由于字符数组存放与栈中,不允许引用其他地方的内存,因此编译器会将字符串直接量复制到栈的数组内存中。因此,可以进行相应的修改。通过字符数组来管理字符串。
你会发现字符串中没有字符'\0',怎么说是c-风格字符串呢。
可以发现,char str[8] = "program";这里用了8个字符空间,而program是7个字符,那么用7个可以吗?
答案是不可以,因为编译器会默认在最后一个位置填充字符'\0',占用了一个字符位置。也就是编译器通过自动在字符数组末尾添加'\0',把双引号的内容转换成了c-风格的字符串,然后赋值给了变量!
sizeof("C++"); //结果是4!
char c1[] = {'C', '+', '+'}; //不是c-风格,没有‘\0’。求sizeof(c1),结果为3
char c2[] = {'C', '+', '+', '\0'}; //是c-风格。求sizeof(c2),结果为4
char c3[] = "C++"; //自动添加空字符。求sizeof(c3),结果为4
c语言中字符串操作函数,比如scp,scm等操作的都是c-风格字符串,这些字符串函数定义中就默认了字符串的最后一个字符是'\0'。
6.你可能会问,为啥要这么设定'\0'是一个字符串的结尾?
这里就涉及到一个编码问题:
在C语言中,字符串是以字符数组的形式进行存储的,且在数组中以'\0'作为终结符。由于'\0'在ASCII中表示空字符(NULL),即在语意上不可能有有效字符与之重复,故用其来表示字符串的结尾至少在ASCII编码下是合理的。
ASCII编码及其扩充规范中,每个字符长度都不超过1Byte,因此,在C风格字符串中用'\0'表示结尾是合法的。
但在UTF16编码中,每个字符使用2Byte进行编码,故会出现其中一个字符为0x00的情况,此时如果仍使用C风格字符串,则在使用相关函数进行处理时,会在第一个0x00出现的位置就被认为是字符串已经结束,但其实字符串并不在此处终结。
UTF8是一种很好的解决方案,UTF8中字符的编码非定长,可能是1Byte或者是2Byte,但是这种编码方案中用每个字符的前缀来表示当前字符的长度,因此既有足够的空间来存储较多的字符,又不会出现0x00导致字符串在被以C风格字符串处理时异常结束。
7.设计字符串这种数据结构,一般有两种想法
1.类似c-风格字符串,以一个标识符表示字符串的结尾
2.在字符串的开头记录了字符的个数,其实在C++中设计的字符串string类,就不是在末尾以'\0'表示字符串的结尾,而是依靠在字符串前面放置了字符的个数!
8.c++中
在C++语言中,除了继承了C语言中的这种字符串表达形式外,还新添了string类用来表达字符串。
为了区分C++中这两种不同的字符串,使用“C风格字符串”来特指来源于C语言的字符串存储方式。