C 和 C++ 的字符串存储形式是一致的,但各自的操作方法确大不相同,奈何 LZ 对此理解的一直不是很透彻,导致刷到字符串相关题时感觉无从下手,遂有此文。
C语言没有专门用于存储字符串的变量类型,字符串都被存储在 char 类型的数组中,且以字符 \0
结尾;这是空字符(null character),C语言用它标记字符串的结束
。
char str[4] = "s v"; // char str[3] = "s v";是错的
char str[] = "s v";
char str[4] = {'s', ' ', 'v', '\0'};
上述三种写法是等价的。
再看下面的例子
char c1[] = "abc";
char c2[] = "abc\0";
char c3[4] = "ab\0";
char c4[4] = "abc\0";
char c5[] = {'a', 'b', 'c', 'd'};
char c6[4] = {'a', 'b', 'c', 'd'};
char c7[] = {'a', 'b', 'c', '\0'};
char c8[4] = {'a', 'b', 'c', '\0'};
char c9[4] = {'a', 'b', '\0'};
char c10[4] = {'a', 'b', '\0', 'c'};
char c11[4] = {'a', 'b', 'c', '\0', 'd'};
分别用 sizeof、strlen 计算字符串长度
printf("%p %s %lu %lu", &c, c, sizeof(c), strlen(c));
string | address | content | sizeof |
strlen |
---|---|---|---|---|
c1 | 0x7fff978fa3e8 | abc | 4 | 3 |
c2 | 0x7fff978fa3e3 | abc | 5 | 3 |
c3 | 0x7fff978fa3df | ab | 4 | 2 |
c4 | error: initializer-string for array of chars is too long’ |
|||
c5 | 0x7fff978fa3d7 | abcdabc | 4 | 7 |
c6 | 0x7fff978fa3d3 | abcdabcdabc | 4 | 11 |
c7 | 0x7fff978fa3cf | abc | 4 | 3 |
c8 | 0x7fff978fa3cb | abc | 4 | 3 |
c9 | 0x7fff978fa3c7 | ab | 4 | 2 |
c10 | 0x7fff978fa3c3 | abc | 4 | 2 |
c11 | too many initializers for ‘char [4]’ |
当然也可通过 char* 定义指向字符串的指针
char a[] = " as df?"
char *p = a;// p指向 char 数组 a 的首元素地址,可通过*p 改变数组 a 的内容
或
char *str;
str = "sd??a"
char *str 与 char str[ ] 的本质区别
:当定义 char str[4]
时,编译器会给数组分配 4 个单元,每个单元的数据类型为字符。而定义 char *str
时, 这是个指针变量,只占四个字节,32位,用来保存一个地址。
那么,来思考一个问题,考虑如下代码,我们能根据指针 p
来使字符串a
变长呢(即在原有字符串后追加字符)?
char a[] = "a s"
char *p = a;// p指向 char 数组 a 的首元素地址,可通过*p 改变数组 a 的内容
前面讲到 C 语言用 \0
标记字符串的结,所以我们可以利用指针 p
追加字符,但追加完后,要以\0
字符结尾。
如下代码:
char a[] = "a d"; // 等价于 char a[4] = "a d"
char *p = a;
printf("%s\n", a);
printf("sizeof(a):%zd\n", sizeof(a));
*(p+3) = '%'; // 将字符串中'\0'改为 '%', a就找不到尾巴了
printf("%s\n", a);
printf("sizeof(a):%zd\n", sizeof(a));
printf("%c\n", a[4]);
*(p+4) = '\0';
printf("%s\n", a);
printf("sizeof(a):%zd\n", sizeof(a));
printf("%c\n", a[4]);
输出为
a d
sizeof(a):4
a d%�
sizeof(a):4
�
a d%
sizeof(a):4
最后一行为不可见的空字符'\0'
,网页内不显示。可以看出在给 a 追加字符后,a的大小并没有变化,但是打印出来后面追加的字符却显示了出来,而且后面还有乱码字符(第三行);追加了尾巴'\0'
之后,打印 a 就能正常显示了(第六行)。但值得注意的是 a 的容量大小一直没变!数组的内存是动态分配的,到了末元素以后的元素都是新分配的,并不一定是空。因为数组是一片连续的空间,有可能末元素后的空间是有数据的,那么C语言会将其读取出来,
size_t strlen(const char*)
提到 strlen()
又不得不说 sizeof()
,sizeof
运算符以字节为单位给出对象的大小,它把不可见字符 '\0'
也计算在内,而 strlen运算符给出的是字符串中的字符数,并不计算字符 '\0'
,
char *strcpy(char *dest, const char *src);
把从 src 地址开始且含有NULL结束符('\0'
)的字符串复制到以dest开始的地址空间,但并不对 dest 空间大小做检测,且不检测 src 和 dest 的内存空间是否有内存重叠,所以容易溢出。
int strcmp(const char *s1, const char *s2);
当s1
当s1>s2时,返回正数。
即:两个字符串自左向右逐个字符相比(按ASCII值大小相比较),直到出现不同的字符或遇’\0’为止。如:
“A”<“B”
“A”<“AB”
“Apple”<“Banana”
“A”<“a”
“compare”<“computer”
特别注意:strcmp(const char *s1,const char * s2)这里面只能比较字符串,即可用于比较两个字符串常量,或比较数组和字符串常量,不能比较数字等其他形式的参数。
ANSI标准规定,返回值为正数,负数,0 。而确切数值是依赖不同的C实现的(gcc7.4.0测试结果输出为第一个不同字符ASCII值的差)。
char *strcat(char *dest, const char *src);
char *strchr(const char *s, int c);
char *strstr(const char *haystack, const char *needle);
C++ 提供了以下两种类型的字符串表示形式:
1. string str;
eg: string s = "ab\0\0c"; // s 内容为 ab
2. string str("ab\0\0c"); // 等价于 str="ab\0\0c"
3. string str(string s, size_type n);
eg: string str("ab\0\0c", 5); // 将"ab\0\0c"的前5个字节存到 str 里
4. string str(string s, char c, size_type n);
eg: string str("ab\0\0c", b, 4); //将"ab\0\0c"的 b 位置开头、长度为4字节的字符串存到 str 里
5. string str(size_type n, char c);
eg: string str(4, 'A') //存储 4 个'A'到s里
warning
: 可以看到如果想要在字符串中存储空字符'\0'
,只能用第3种方式构造。在第3,4种方法中,都限定了字符串长度,此时要格外注意,n不能超出字符串 s 的所含字符个数下标,否则构造出的字符串会包含一些未知字符。
string str = str1 + str2;// 字符串拼接
str[2]; // 访问str中的第三个字符,无边界检查,所以推荐使用下一种一种访问方法
str.at(2); // 访问str中的第三个字符,有边界检查
str.empty(); // 判断str是否为空,若 str 为空则返回true,否则返回 false
str.length(); // 获取字符串长度
str.size(); // 获取字符串数量,等价于length()
str.capacity(); // 获取容量,容量包含了当前string里不必增加内存就能使用的字符数
str.front();[C++11] // 返回str的首字符
str.back(); [C++11] // 返回str的末字符
str.resize(10); // 表示设置当前string里的串大小,若设置大小大于当前串长度,则用字符\0来填充多余的.
str.resize(10, char c); // 设置串大小,若设置大小大于当前串长度,则用字符c来填充多余的
str.reserve(10); //设置string里的串容量,不会填充数据.
str.swap(str1); //替换str 和 str1 的字符串
str.puch_back('A'); //在str末尾添加一个'A'字符,参数必须是字符形式
str.append("ABC"); //在str末尾添加一个"ABC"字符串,参数必须是字符串形式
str.insert(2, "ABC"); //在str的下标为2的位置,插入"ABC"
str.erase(2); //从下标为2的位置开始删除后面的字符,比如:"ABCD" --> "AB"
str.erase(2, 1); //从下标为2的位置删除1个字符,比如: "ABCD" --> "ABD"
str.clear(); //清空str内容,str.size()=str().length()=0, 但不同于
//std::vector::clear,C++标准不显式要求此函数不更改capacity,即不释放分配的内存,若str="ABC", str.capacity()=3
str.replace(2,2, "ABCD"); //从下标为2的位置,替换2个字节为"ED",比如:"ABCCA" --> "ABEDA"
str.starts_with("as");[C++20]// 判断str是否以"as"开头,是返回true,否则返回false
str.ends_with("as");[C++20]// 判断str是否以"as"结尾,是返回true,否则返回false
str.substr(2); //结果为 str从 第三个字符开始往后的子串,比如:"ASCDS" --> "CDS"
str.substr(2, 2); //结果为 str从 第三个字符为开始、第4个字符结束的子串,比如:"ASCDS" --> "CD" substr为 [pos, pos+size)
str.find("ah"); // 返回str中字符串 "ah" 首次出现的位置 str.find(string s, size_type n=0)
// 如果没找到则返回 -1
str.find("as", 2); // 从str第三个字符开始寻找 "as" 第一次出现的位置,如果没找到则返回 -1
str.rfind("ah"); //在str中反向搜索字符串 "ah" 首次出现的位置
str.rfind("as", 2);// 从str第三个字符开始往后的字符中反向寻找 "as" 第一次出现的位置
str.find_first_of('a'); //查找 str 中 a 第一次出现的位置;如果str中没有字符 a,则返回 std::string::npos(18446744073709551615)
str.find_first_of('a', 5); //从 str 的第六个字符开始查找 a 第一次出现的位置;如果str中没有字符 a,则返回 std::string::npos(18446744073709551615)
str.find_first_not_of('a'); //查找 str 中第一个非 a 字符的位置;如果str中全是 a,则返回 std::string::npos(18446744073709551615)
str.find_first_not_of('a', 5); //从 str 的第六个字符开始查找 str 中第一个非 a 字符的位置;如果从 str 的第六个字符开始全是 a,则返回std::string::npos(18446744073709551615)
// char[] -> string,直接赋值即可
char a[] = "a s_Q";
string str = a;
// char* -> string,直接赋值
const char *c = "asd";
string str = c;
// string -> char[],只能通过 strncpy() 拷贝实现
string str = "I Love u";
char c[] = str; // wrong!!!!
char c1[] = "this string should longer than str"; // c1长度必须要大于str长度
strncpy(c1, str.c_str(), str.length()+1); // 不能漏掉 \0 ,所以要加1
// string -> char*,通过类型转换
string str = "I Love u";
const char *c = str.c_str(); // 不可修改版,str.c_str()将 string 类型转化为 const char*
char *c = const_cast<char*>(str.c_str()); // 可修改版
int stoi(string str);
long stol(string str);
long long stoll(string str);
warning
: 字符串必须以数字开头,否则会抛出错误 std::invalid_argument
string str = ".9as_20.2dd3"
string str1 = "-12e33"
string str2 = "2.23e33"
cout << stoi(str) << endl; // throw error
cout << stoi(str1) << endl; // -12
cout << stoi(str2) << endl; // 2
unsigned long stoul(string str);
unsigned long long stoull(string str);
float stof(string str);
double stod(string str);
long double stold(string str);
string str = "as_20.2dd3";
string str1 = ".2e33";
string str2 = "2.23e33";
string str3 = "2.333333333333AAA1";
cout << stof(str) << endl; // throw error
cout << stof(str1) << endl; // 0.2
cout << stof(str2) << endl; // 2.23
cout << stod(str3) << endl; // 2.33333
直接使用 to_string() 函数
int i = -23;
float a = 1.23;
double b = -2.33;
double c = 1.3e12;
double d = 1.3e-4;
double e = 2.2e-10;
string i1 = to_string(i); // -23
string a1 = to_string(a); // 1.230000
string b1 = to_string(b); // -2.330000
string c1 = to_string(c); // 1300000000000.000000
string d1 = to_string(d); // 0.000130
string e1 = to_string(e); // 0.000000