天天看点

C++编程:复合数据类型—字符串

作者:尚硅谷教育

字符串我们并不陌生。之前已经介绍过,一串字符连在一起就是一个“字符串”,比如用双引号引起来的“Hello World!”就是一个字符串字面值。

字符串其实就是所谓的“纯文本”,就是各种文字、数字、符号在一起表达的一串信息;所以字符串就是C++中用来表达和处理文本信息的数据类型。

1. 标准库类型string

C++的标准库中,提供了一种用来表示字符串的数据类型string,这种类型能够表示长度可变的字符序列。和vector类似,string类型也定义在命名空间std中,使用它必须包含string头文件。

#include<string>

using namespace std;

(1)定义和初始化string

我们已经接触过C++中几种不同的初始化方式,string也是一个标准库类型,它的初始化与vector非常相似。

// 默认初始化,空字符串

string s1;

// 用另一个字符串变量,做拷贝初始化

string s2 = s1;

// 用一个字符串字面值,做拷贝初始化

string s3 = "Hello World!";

// 用一个字符串字面值,做直接初始化

string s4("hello world");

// 定义字符和重复的次数,做直接初始化,得到 hhhhhhhh

string s5(8, 'h');

C++编程:复合数据类型—字符串

初始化方式主要有:

1. 默认初始化,得到的就是一个空字符串;

2. 拷贝初始化,用赋值运算符(等号“=”)表示;可以使用另一个string对象,也可以使用字符串字面值常量;

3. 直接初始化,用括号表示;可以在括号中传入一个字符串,也可以传入字符和重复的次数

可以发现,字符串也可以看做数据元素的集合;它里面的元素,就是字符。

(2)处理字符串中的字符

通过初始化已经可以看出,string的行为与vector非常类似。string同样也可以通过下标运算符访问内部的每个字符。字符的“索引”,就是在字符串中的位置。

string str = "hello world";

// 获取第3个字符

cout << "str[2] = " << str[2] << endl;

// 将第1个字符改为'H'

str[0] = 'H';

// 将最后一个字符改为'D'

str[str.size() - 1] = 'D';

cout << "str = " << str << endl;

字符串内字符的访问,跟vector内元素的访问类似,需要注意:

  • string内字符的索引,也是从0开始;
  • string同样有一个成员函数size,可以获取字符串的长度;
  • 索引最大值为 (字符串长度 - 1),不能越界访问;如果直接越界访问并赋值,有可能导致非常严重的后果,出现安全问题;
  • 如果希望遍历字符串的元素,也可以使用普通for循环和范围for循环,依次获取每个字符

比如,我们可以考虑遍历所有字符,将小写字母换成大写:

// 遍历字符串中字符,将小写字母变成大写

for (int i = 0; i < str.size(); i++)

{

str[i] = toupper(str[i]);

}

这里又调用了string的一个函数toupper,可以把传入的字符转换成大写并返回。

(3)字符串相加

string本身的长度是不定的,可以通过“相加”的方式扩展一个字符串。

// 字符串相加

string str1 = "hello", str2("world");

string str3 = str1 + str2; // str3 = "helloworld"

string str4 = str1 + ", " + str2 + "!"; // str4 = "hello, world!"

//string str5 = "hello, " + "world!"; // 错误,不能将两个字符串字面值相加

需要注意:

  • 字符串相加使用加号“+”来表示,这是算术运算符“+”的运算符重载,含义是“字符串拼接”;
  • 两个string对象,可以直接进行字符串相加;结果是将两个字符串拼接在一起,得到一个新的string对象返回;
  • 一个string对象和一个字符串字面值常量,可以进行字符串相加,同样是得到一个拼接后的string对象返回;
  • 两个字符串字面值常量,不能相加;
  • 多个string对象和多个字符串字面值常量,可以连续相加;前提是按照左结合律,每次相加必须保证至少有一个string对象;

(4)比较字符串

string类还提供几种用来做字符串比较的运算符,“==”和“!=”用来判断两个字符串是否完全一样;而“<”“>”“<=”“>=”则用来比较两个字符串的大小。这些都是关系型运算符的重载。

str1 = "hello";

str2 = "hello world!";

str3 = "hehehe";

str1 == str2; // false

str1 < str2; // true

str1 >= str3; // true

字符串比较的规则为:

  • 如果两个字符串长度相同,每个位置包含的字符也都相同,那么两者“相等”;否则“不相等”;
  • 如果两个字符串长度不同,而较短的字符串每个字符都跟较长字符串对应位置字符相同,那么较短字符串“小于”较长字符串;
  • 如果两个字符串在某一位置上开始不同,那么就比较这两个字符的ASCII码,比较结果就代表两个字符串的大小关系

2. 字符数组(C风格字符串)

通过对string的介绍可以发现,字符串就是一串字符的集合,本质上其实就是一个“字符的数组”。

在C语言中,确实是用char[]类型来表示字符串的;不过为了区分纯粹的“字符数组”和“字符串”,C语言规定:字符串必须以空字符结束。空字符的ASCII码为0,专门用来标记字符串的结尾,在程序中写作’\0’。

// str1没有结尾空字符,并不是一个字符串

char str1[5] = {'h','e','l','l','o'};

// str2是一个字符串

char str2[6] = { 'h','e','l','l','o','\0'};

cout << "str1 = " << str1 << endl;

cout << "str2 = " << str2 << endl;

如果每次用到字符串都要这样定义,对程序员来说就非常不友好了。所以字符串可以用另一种更方便的形式定义出来,那就是使用双引号:

char str3[] = "hello";

//char str3[5] = "hello"; // 错误,"hello"的长度为6

cout << "str3 = " << str3 << endl;

这就是我们所熟悉的字符串“字面值常量”。这里需要注意的是,我们不需要再考虑末尾的空字符,编译器会自动帮我们补全;但真实的字符串的长度,依然要包含空字符,所以上面的字符串“hello”长度不是5、而是6。

所以,C++中的字符串字面值常量,为了兼容C依然定义为字符数组(char[])类型,这和string是两种不同类型;两者的区别,跟数组和vector的区别类似,char[]是更底层的类型。一般情况下,使用string会带来更多方便,也会更加安全。

3. 读取输入的字符串

程序中往往需要一些交互操作,如果想获取从键盘输入的字符串,可以使用多种方法。

(1) 使用输入操作符读取单词

标准库中提供了iostream,可以使用内置的cin对象,调用重载的输入操作符>>来读取键盘输入。

string str;

// 读取键盘输入,遇到空白符停止

cin >> str;

cout << str;

这种方式的特点是:忽略开始的空白符,遇到下一个空白符(空格、回车、制表等)就会停止。所以如果我们输入“hello world”,那么读取给str的只有“hello”:这相当于读取了一个“单词”。

剩下的内容“world”其实也没有丢,而是保存在了输入流的“输入队列”里。如果我们想读取更多的输入信息,就需要使用更多的string对象来获取:

string str1, str2;

cin >> str1 >> str2;

cout << str1 << str2 << endl;

这样,如果输入“hello world”,就可以输出“helloworld”。

(2)使用getline读取一行

如果希望直接读取一整行输入信息,可以使用getline函数来替代输入操作符。

string str3;

getline(cin, str3);

cout << "str3 = " << str3 << endl;

getline函数有两个参数:一个是输入流对象cin,另一个是保存字符串的string对象;它会一直读取输入流中的内容,直到遇到换行符为止,然后把所有内容保存到string对象中。所以现在可以完整读取一整行信息了。

(3)使用get读取字符

还有一种方法,是调用cin的get函数读取一个字符。

char ch;

ch = cin.get(); // 将捕获到的字符赋值给ch

cin.get(ch); // 直接将ch作为参数传给get

有两种方式:

  • 调用cin.get()函数,不传参数,得到一个字符赋给char类型变量;
  • 将char类型变量作为参数传入,将捕获的字符赋值给它,返回的是istream对象

get函数还可以读取一行内容。这种方式跟getline很相似,也可以读取一整行内容,以回车结束。主要区别在于,它需要把信息保存在一个char[]类型的字符数组中,调用的是cin的成员函数:

// get读取一整行

char str4[20];

cin.get(str4, 20);

cout << "str4 = " << str4 << endl;

// get读取一个字符

cin.get(); // 先读取之前留下的回车符

cin.get(); // 再等待下一次输入

get函数同样需要传入两个参数:一个是保存信息的字符数组,另一个是字符数组的长度。

这里还要注意跟getline的另一个区别:键盘输入总是以回车作为结束的;getline会把最后的回车符丢弃,而get会将回车符保留在输入队列中。

这样的效果是,下次再调用get试图读取一行数据时,会因为直接读到了回车符而返回空行。这就需要再次调用get函数,捕获下一个字符:

cin.get(); // 先读取之前留下的回车符

cin.get(); // 再等待下一次输入

这样就可以将之前的回车符捕获,从而为读取下一行做好准备。这也就解释了之前为什么要写两个cin.get():第一个用来处理之前保留在输入队列的回车符;第二个用来等待下一次输入,让窗口保持开启状态。

4. 简单读写文件

实际应用中,我们往往会遇到读写文件的需求,这也是一种IO操作,整体用法跟命令行的输入输出非常类似。

C++的IO库中提供了专门用于文件输入的ifstream类和用于文件输出的ofstream类,要使用它们需要引入头文件fstream。ifstream用于读取文件内容,跟istream的用法类似;也可以通过输入操作符>>来读“单词”(空格分隔),通过getline函数来读取一行,通过get函数来读取一个字符:

ifstream input("input.txt");

// 逐词读取

string word;

while (input >> word)

cout << word << endl;

// 逐行读取

string line;

while (getline(input, line))

cout << line << endl;

// 逐字符读取

char ch;

while (input.get(ch))

cout << ch << endl;

类似地,写入文件也可以通过使用输出运算符 << 来实现:

ofstream output("output.txt");

output << word << endl;

继续阅读