基础容器--字符串

  字符串是由零个或多个字符组成的有限串行。

  子串的定义:串中任意个连续的字符组成的子序列,并规定空串是任意串的子串,任意串是其自身的子串

字符串变量与常量

C风格的字符串包括两种:字符串常量和末尾添加了'\0'的字符数组。无论怎么表示,都以'\0'结尾。

字符串变量

  • 字符串是一个特殊的字符数组,以空字符'\0'结尾;
  • 空字符'\0'自动添加到字符串的内部表示中;
  • 在声明字符串变量的时候,要时刻记得为这个空结束符预留一个额外元素的空间:char str[11] = {"helloworld"},十个字符要留11个位置

字符串常量

  • 字符串常量就是一对双引号括起来的字符序列:"helloworld";
  • 字符串常量中的每个元素可以作为一个数组元素访问;
  • 字符串常量也是以'\0'结尾的;

字符串的指针表示

1
const char* pStrHelloWorld = "helloworld";

表示一个char型的指针变量,指向内存中一个存储字符串“helloworld”的地址。

定义字符串有两种方式,char[]和char*,要区分以下两个概念:

  1. 区分地址本身和地址存储的信息;
  2. 区分可变与不可变;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int main()
{
//char strHello[10] = { "helloworld" }; //错误定义,没有给'\0'留空间

char strHello[11] = { "helloworld" };
char strHello[] = { "helloworld" }; //这样定义也可以
//strHello是数组变量,它本身的值是不允许改变的,但是它指向的区域的值strHello[index]是可以改变的
strHello[2] = '6'; //strHello = he6loworld
//strHello = { "helloworld" }; //本身不允许改变,编译不通过


//ptrHello是指针变量,它本身是可以改变的,但是ptrHello[index]指向的值可变与否要取绝与所指向的区间是否可以改变
const char* ptrHello_1 = "helloworld"; //指针指向常量区(必须要加const),ptrHello[index]不可改变,但本身指向的地址可以变。
ptrHello_1 = "change";

char tmp[7] = "change";
char* ptrHello_2 = strHello; //指针指向数组变量,ptrHello[index]可以改变,本身也可以变
ptrHello_2[2] = 'l';
ptrHello_2 = tmp;

return 0;
}

char* pstr是指针,pstr本身可变,pstr[index]是否可变取决于指向的存储区间是否可变;

char str[]是字符串数组,str本身的值不允许改变,但可以改变str[index];

字符串操作

头文件:string.h

  C/C++字符串处理函数,传递给这些标准库函数例程的指针必须具有非零值,并且指向以null结束的字符数组中的第一个元素。其中一些标准库函数会修改传递给它的字符串,这些函数将假定它们所修改的字符串具有足够大的空间接收新字符串,程序员需要保证目标字符串足够大。

字符串遍历

  一般来说,我们使用指针的算数操作来编译C风格的字符串,每次对指针进行测试并递增1,直到到达结束符null为止:

1
2
3
4
5
6
const char *cp = "helloworld";
while(*cp)
{
//do something
++cp;
}

字符串长度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
strlen(s);    //返回字符串s的长度,要注意字符串的长度不包含'\0'。

sizeof(s); //返回字符串所占用的空间,包含'\0'


int main()
{
char strA[11] = "helloworld";
auto n1 = sizeof(strA); //11
auto n2 = strlen(strA); //10


wchar_t strB[11] = L"helloworld";
auto n3 = sizeof(strB); //22
auto n4 = wcslen(strB); //10

return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//自定义实现strlen
int strlen(const char* str)
{
assert(str != NULL);
int len = 0;
while((*str++) != '\0'){
len++;
}
return len;
}

//或者不使用临时变量,用递归:
int strlen(const char* str)
{
assert(str != NULL);
return *str == '\0' ? 0 : (1 + strlen(++str));
}

字符串比较

1
2
3
4
5
strcmp(s1,s2); 
wcscmp(s1,s2);
//s1 == s2,返回0
//s1 < s2 ,返回值小于0
//s1 > s2 ,返回值大于0

两个字符串自左向右逐个字符相比,按照ASCII值大小来比较,直到出现不同字符或遇到'\0'为止;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//自定义实现strcmp:
int strcmp(const char* str1, const char* str2)
{
assert(str1 != NULL && str2 != NULL);
int ret = 0;
while(!(ret = *(unsigned char*)str1 - *(unsigned char*)str2) && str1)
{
str1++;
str2++;
}
if(ret < 0) ret = -1;
else if(ret > 0) ret = 1;

return ret;
}

字符串拷贝

1
2
3
strcpy(s1,s2);   //赋值字符串s2到字符串s1的地址空间

strncpy(s1,s2,n); //将字符串s2中的前n个字符拷贝到s1的地址空间

要注意一个陷阱,一定要保证字符串s1的内存大小能存的下要拷贝的s2的大小

1
2
3
4
5
6
7
8
//自定义实现strcpy
char* strcpy(char* strDestination, const char* strSource)
{
assert(strDestination != NULL && strSource != NULL);
char* strD = strDestination;
while((*strDestination++ = *strSource++) != '\0');
return strD;
}

拷贝还可以使用内存拷贝函数:

1
2
//从src所指向的内存地址起始位置开始拷贝n个字节到dest所指的内存地址的起始位置中。返回指向dest的指针
void* memcpy(void* dest, const* src, size_t n);

memcpy和strcpy的区别:

  1. 复制内容不同。strcpy只能复制字符串,而且不仅复制字符串内容,也复制结束符'\0';memcpy可以复制任意内容,用途更广;
  2. strcpy不需要指定长度,memcpy需要指定长度

字符串拼接

1
2
3
strcat(s1,s2);   //将字符串s2拼接到s1后面(覆盖s1的'\0'),返回s1

strncat(s1, s2, n); //将s2的前n个字符拼接到s1,并返回s1
1
2
3
4
5
6
7
8
9
10
11
12
//自定义实现strcat:
char *strcat(char* strDest, const char* strSrc)
{
char* address = strDest;
assert((strDest != NULL) && strSrc != NULL);
while(*strDest) //如果是strncat,这里用n作为结束标志
{
strDest++;
}
while(*strDest++ = *strSrc++);
return address;
}

这个更加要注意,s1的大小要足够存放拼接后的s1+s2字符串的大小

两个char*不能直接相加,字符串拼接必须要用这个函数。(指针相加是什么操作啊(#`O′))

字符串查找

1
2
strchr(s1,ch);   //指向字符串s1中字符ch第一次出现的位置
strstr(s1,s2); //指向字符串s1中字符串s2的第一次出现的位置

内容替换

1
2
//将s中的前n个字节用ch替换并返回s
void* memset(void* s, int ch, size_t n);

这个函数的作用是在一块内存中填充某个给定的值,是对较大的结构体或者数组进行清零的最快方法。

安全函数

C语言的字符串处理有其漏洞所在。上面的所有方法都无法做边界检查,如果不注意就会发生缓冲区溢出的问题,这个问题在编码时是很严重的事故。。现在我们往往在程序中使用更加安全的API函数:

上述所有API函数都有其_s版本,如strcpy_s(),在执行操作的同时要告诉系统当前可使用的缓冲区的大小。 strlen()的安全版本是strnlen_s(),传入字符串的同时要传入一个最大的边界,以防止某些字符串没有'\0'结束标志。

C++新型字符串--string类

C++标准库中提供了string类型专门表示字符串

1
2
#include<string>
using namespace std;

使用string可以更加方便和安全的管理字符串,对性能要求不是特别高的场景可以使用。

定义

1
2
3
4
string s;   //定义空字符串
string s = "helloworld"; //定义并初始化
string s("helloworld");
string s = string("helloworld");

字符串长度

1
2
3
cout<<s1.length()<<endl;    //输出字符串长度
cout<<s1.size()<<endl; //本质和上面一样
cout<<s1.capacity()<<endl; // 字符串的容量

字符串比较

直接使用运算符== != < > >= < = 即可,通过ASCII码来比较。

转换为C风格字符串

1
2
string s1 = "hello";
const char* c_str1 = s1.c_str();

随机访问

字符串本身就是数组,可以使用下标的方式访问

1
2
string s = "hello";
cout<<s[0]<<endl;

字符串拷贝

不需要使用函数,直接使用=赋值

1
2
string s1 = "hello";
string s2 = s1;

字符串拼接

string重载了+和+=,不需要使用函数

1
2
3
string s1 = "hell0",s2 = "world";
string s3 = s1+s2;
s1 += s2;