版权声明:自由转载-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 4.0
前面的内容我们使用过字符串字面值,并了解字符串字面值的类型是字符常量的数组,现在可以更加明确的认识到:字符串字面值的类型就是 const char 类型的数组。C++从C继承下来的一种通用的结构是C风格字符串(C-Style character string),字符串字面值就是该类型的实例。实际上,C风格字符串既不能确切的归结为C语言类型,也不能归结为C++的类型,而是以空字符 null
结束的字符数组:
1 | char c1[] = {'C', '+', '+'}; //c1 维数为3,not c-style |
c1
和 cp1
都不是C风格字符串:c1
是不带结束符 null
的字符数组。其他都是C风格字符串。
1、C风格字符串的使用
C++通过 (const) char* 类型的指针来操作C风格的字符串。
1 | const char *cp = "C++"; |
输出
1 | C |
如果cp指向的字符数组没有 null 结束符,作为循环会失败。这时循环继续进行,知道遇到内存中某处的 null 结束符。
2、C风格字符串的标准库函数
下表类除了C语言标准库函数提供的一系列处理C风格字符串的库函数,这些库函数包含在 string.h 中。
库函数 | 含义 |
---|---|
strlen(s) | 返回s的长度,不包括null结束符 |
strcmp(s1, s2) | 比较s1和s2是否相同。s1等于s2,返回0;s1大于s2,返回正数;s1小于s2,返回负数 |
strcat(s1, s2) | 将字符串s2连接到s1后面,返回s1 |
strcpy(s1, s2) | 将s2复制给s2,返回s1 |
strncat(s1, s2, n) | 将s2的前n个字符连接到s1后面,并返回s1 |
strncpy(s1, s2, n | 将s2的前n个字符赋给s1,并返回s1 |
传递给这些函数的指针必须具有非零值,并且指向以null结束的字符数组中的第一个元素。并且要确保目标字符串必须有足够大的空间存放结果串。
字符串的比较和比较结果都必须使用标准库函数 strcmp 进行
1 | const char *cp1 = "C++"; |
输出
1 | 1 |
不要忘记字符串结束符null
1 | char c[] = { 'c' }; |
输出结果是不可预料的,因为会一直寻找null结束符,然后再输出。比如在我的电脑是输出是 324
,这明显是错误的,在不同的电脑会输出不同的值。
必须确保目标字符串有足够的大小
库函数 strcat 和 strcpy 的第一个参数必须有足够大的空间
1 | const char *cp1 = "C++"; |
输出
1 | C++ C |
上述程序就目前来看是完全没问题的,但如果 cp1 和 cp2 指向的字符串大小发生了改变,str 所需的大小就不满足要求了。会导致严重的安全漏洞。
strn函数处理C风格的字符串
如果必须处理C风格字符串,strncat,strncpy会比strcat,strcpy更安全
1 | char str[3 + 1 + 2]; |
分步骤解释:
- strncpy cp1 时需要复制 4 个字符:cp1 中所有字符,加上 null 结束符;此时 strlen(str) = 3.
- strncat “ “ 时需要复制 2 个字符:一个空格字符,加上 null 结束符;此时空格符会把步骤1复制的 null 结束符覆盖。此时 strlen(str) = 4.
- strncat cp2 时需要复制 2 个字符:cp2 中所有字符,加上 null 结束符;此时 strlen(str) = 6.
最后 str 的内容:cp1 和 cp2 中所有字符,一个空格,和一个null结束符。
尽可能使用string类型
1 | string str = cp1; |
可以达到上面一样的效果。
3、创建动态数组
数组类型的限制:长度不变;编译时须知道长度;数组只在定义它的语句内存在。可以使用动态分配解决这一问题,跟C中的malloc和free类似,C++中使用 new 和 delete 实现。
3.1、定义
数组变量需要指定类型、数组名和维数定义,而动态分配的数组只需指定类型和长度,不必为数组对象命名。
1 | int *ip = new int[10]; |
3.2、初始化
动态分配的数组,如果有类型,则使用类型的默认构造函数初始化;如果是内置类型,则无初始化:
1 | string *sp = new string[10]; //初始化为10个空字符串 |
圆括号要求编译器对数组进行初始化;
动态分配的数组,其元素只能初始化为默认值,而不能像变量一样提供初始化列表进行初始化
3.3、const对象的动态数组
必须为该数组提供初始化值,因为数组内都是const对象,无法赋值
1 | const int *caip_bad = new const int[10]; //error |
也可以定义类类型的const数组,但该类型必须提供默认构造函数
1 | const string *csp = new const string[10]; //ok |
3.4、动态分配空数组
有时候,编译时并不知道数组的长度,这时可以动态分配空数组。看如下程序
1 | size_t n = get_size(); |
即使 get_size()
返回 0 也是可以的。例如
1 | char arr[0]; //error |
上述 cp
指针 允许 的操作有:比较;本身加(减)0。
3.5、动态空间释放
动态分配的空间必须释放,不然内存会被逐渐耗尽
1 | delete [] cp; |
该语句回收了 cp
所指向的数组。把相应的内存返还给自由存储区。
理论上,如果少了
[]
应该会导致少释放空间,从而产生内存泄露,因此,释放动态数组时,不能忘记[]
3.6、动态数组的使用
长度不一样的两个字符串,赋给同一个新的字符数组
1 | const char *txt1 = "HAHAHA"; |
4、新旧代码的兼容
4.1、混合使用string库和C风格字符串
可用字符串字面值初始化string对象
1 | string str("Hello World"); |
通常,C风格字符串与字符串字面值有相同的数据类型,而且都是以空字符null结束,因此可以把C风格字符串用在任何可以使用字符串字面值的地方:
- C风格字符串对string对象进行赋值或初始化;
- C风格字符串可以作为string类型的加法操作两个参数中的一个;
但反之不成立,但可以通过名为 c_str()
的函数转化为C风格字符串
1 | char *str1 = str; //error |
4.2、使用数组初始化vector对象
1 | const size_t arr_size = 6; |
举例1
编写程序: 从标准输入设备读入字符串,并把字符串存放在字符数组中(输入的字符串长度不定)。
1 | int main() |
运行测试
1 | Enter some chars: |
举例2
编写程序:读入一组string类型数据,将它们存储在vector中,然后将vector中的对象复制给一个字符指针数组。即为vector中的每个元素创建一个新的字符数组,然后把vector元素的数据复制到相应的字符数组中,最后将指针插入到指针数组中。
1 | int main() |
运行结果
1 | hello↙ |
5、多维数组
严格的讲,C++中没有多维数组,只有数组的数组
初始化
1 | int ia[3][2] = { |
且等价于
1 | int ia[3][2] = {0, 1, 2, 3, 4, 5}; |
而下面情况只初始化第一个元素,则其余初始化为0
1 | int ia[3][2] = {{1}, {2}, {3}}; |
等价于
1 | int ia[3][2] = {{1, 0}, {2, 0}, {3, 0}}; |
但不会等价于
1 | int ia[3][2] = {1, 2, 3};//等价于int ia[3][2] = {1, 2, 3, 0, 0, 0} |
下标引用
1 | const size_t rsz = 3; |
访问特定元素时,必须提供行下标与列下标
指针与多维数组
与普通数组一样,多为数组名也是指向第一个元素的指针。
1 | int main() |
运行结果
1 | 2,3 |
int (ip)[2];//指向数组的指针;即ip是指向包含两个int值的数组的指针
int ip[2];//指针的数组;即ip是包含两个指针的数组
typedef简化多维数组指针
指向 ia
的指针
1 | typedef int int_arr[2]; |
用 int_arr
输出 ia
的元素
1 | int ia[3][2] = { |
结果
1 | 0 |
END.