Cycoe@Home

与 C++ 的第三类接触-字符串、向量和数组

1. 字符串、向量和数组

1.1. 标准库 string

引入头文件与声明命名空间

#include <string>
using std::string;

1.1.1. 定义和初始化

using std::string;

string s1;            // 默认初始化,s1 是一个空字符串
string s2 = s1;       // s2 是 s1 的副本
string s3 = "hello";  // s3 是字符串字面值的副本
string s4("hello");   // s4 的内容为 "hello"
string s5(10, 'c');   // s5 的内容为 "cccccccccc"

s2, s3 的初始化方法为拷贝初始化,s4, s5 的初始化方法为直接初始化。

1.1.2. 为 string 对象赋值

using namespace std;

string s1, s2(10, 'c');
s1 = s2;       // 用 s2 的值赋值 s1,相当于拷贝了 string 对象
s2 = "Hello";  // 用字面值常量赋值 s2

cout << "s1 is: " << s1 << endl;
cout << "s2 is: " << s2 << endl;
s1 is: cccccccccc
s2 is: Hello

1.1.3. 读写 string 对象

using namespace std;
string s;            // 声明一个空字符串

cin >> s;
s = "Hello";
cout << s << endl;
Hello

1.1.4. 使用 getline 读取一整行

using namespace std;
string line;

// getline 每次读取到换行符(包括换行符),存入 string 对象中(不包括换行符)
while (getline(cin, line))
  cout << line << endl;

1.1.5. emptysize

Table 1: 多行文本
First line.
Second line.
 
This is a long line.

使用上表中的数据演示 string 对象 emptysize 方法的使用

using namespace std;

string s;
int row;

for (row = 0; row < lines_rows; row++) {
  s = lines[row][0];
  if (s.empty()) {
    cout << "Line " << row + 1 << " is empty" << endl;
  } else {
    cout << "Line " << row + 1 << "'s length is " << s.size() << endl;
  }
}
Line 1's length is 11
Line 2's length is 12
Line 3 is empty
Line 4's length is 20

1.1.6. 比较 string 对象

类似于 C 中的 strcmp 函数,依次比较各字符的字典序

using namespace std;

string s1("hello");
string s2("world");

if (s1 > s2)
  cout << "s1 is bigger than s2." << endl;
else
  cout << "s2 is bigger than s1." << endl;
s2 is bigger than s1.

1.1.7. string 对象相加

using namespace std;

string s1 = "Hello, ";
string s2 = "world.";
cout << s1 + s2 << endl;
s1 += s2;
cout << s1 << endl;
Hello, world.
Hello, world.

1.1.8. 字面值与 string 对象相加

using namespace std;

string s1 = "hello", s2 = "world";
// 需要保证每次加法运算时至少有一个是 string 对象
string s3 = s1 + ", " + s2 + '!';
cout << s3 << endl;
hello, world!

1.1.9. 处理 string 对象中的字符

用基于范围的 for 语句遍历 string 对象中的字符

using namespace std;

string str("some string");
// 基于范围的 for 语句
for (auto c : str)
  cout << c << " | ";
s | o | m | e |   | s | t | r | i | n | g |

统计 string 对象中的标点符号个数

using namespace std;

string s("Hello, world!!!");
decltype(s.size()) punct_count = 0;
for (auto c : s)
  if (ispunct(c))
    ++punct_count;

cout << punct_count << " punctuation characters in " << s << endl;
4 punctuation characters in Hello, world!!!

更改 string 对象中的字符,此时需要使用引用

using namespace std;

string s1("Talk is cheap, ");
// 使用范围的 for 循环
for (auto &c : s1)
  c = toupper(c);

cout << s1 << endl;

string s2("show me the code!");
// 使用传统的 for 循环
for (decltype(s2.size()) i = 0; i < s2.size(); i++)
  // string 对象的索引返回指向对应字符的一个引用
  s2[i] = toupper(s2[i]);

cout << s2 << endl;
TALK IS CHEAP, 
SHOW ME THE CODE!

1.1.10. 使用字符串数组初始化 string 对象

using namespace std;

char str[] = "Hello, world!";   // 初始化 C 风格的字符串
string s(str);                  // 使用字符串数组初始化 string 对象
cout << s << endl;
Hello, world!

1.1.11. 从 string 对象中获取 C 风格的字符串数组指针

using namespace std;

string s("Hello, world!");
// 获取 C 风格的字符串数组指针,并且指向 string 中的字符串数组
// 不应该使用该指针修改数组内容,因此使用 const 限定符
const char *str = s.c_str();
cout << str << endl;
Hello, world!

1.2. 标准库类型 vector

引入头文件与声明命名空间, vector 是一个类模板

#include <vector>
using std::vector;

1.2.1. 定义和初始化

#include <vector>
using std::vector;

vector<T> v1;                 // v1 是一个空 vector ,它潜在的元素是 T 类型
vector<T> v2(v1);             // v2 包含有 v1 所有元素的副本
vector<T> v3 = v1;            // 等价于 v3(v1)
vector<T> v4(n, val);         // 包含 n 个重复的 val
vector<T> v5(n);              // 包含 n 个重复的 T 类型元素并执行默认初始化
vector<T> v6{a, b, c...};     // 用 a, b, c... 执行初始化
vector<T> v7 = {a, b, c...};  // 等价于 v7{a, b, c...}
vector<T> v8 = (a, b, c...);  // 错误

默认初始化

vector<int> ivec(10);     // 10 个元素,每个都初始化为 0
vector<string> svec(10);  // 10 个元素,每个都旦下人空 string 对象

括号内的数字是元素数量还是初始化,要看用的是花括号还是圆括号。花括号和圆括号具有 完全不同的含义,圆括号表示实例化对象,花括号代表列表初始化,但在当编译器发现提供 的元素无法使用列表初始化时,会 fallback 到默认的圆括号实例化对象。

vector<int> v1(10);      // v1 有 10 个元素,每个值都是 0
vector<int> v2{10};      // v2 有 1 个元素,值为 10
vector<int> v3(10, 1);   // v3 有 10 个元素,每个值都是 1
vector<int> v4{10, 1};   // v4 有 2 个元素,值分别是 10 和 1

vector<string> v5{"hi"};      // 列表初始化:v5 有一个元素
vector<string> v6("hi");      // 错误:不能使用字符串字面值构建 vector 对象
vector<string> v7{10};        // v7 有 10 个默认初始化的元素
vector<string> v8{10, "hi"};  // v8 有 10 个值为 "hi" 的元素

1.2.2. 向 vector 对象中添加元素

using namespace std;

vector<int> iv;
for (int i = 0; i < 10; i++) {
  iv.push_back(i);
}
// 如果循环体内部包含有改变 vector 长度的语句,则不能使用范围 for 循环
for (auto num : iv) {
  cout << num << ", ";
}
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,

1.2.3. 其它 vector 操作

vector 对象的其它操作与 string 对象类似

Table 2: 一些单词
Keep it simple stupid
using namespace std;

vector<string> vstrings;
// 将 string 对象加入到 vector 中
for (int col = 0; col < input_cols; col++) {
  vstrings.push_back(input[0][col]);
}
// 将 vector 对象中的每个 string 对象中的每个字符转换为大写,注意 *引用*
for (string &s : vstrings) {
  for (auto &c : s) {
    c = toupper(c);
  }
}
for (string s : vstrings) {
  cout << s <<endl;
}
KEEP
IT
SIMPLE
STUPID

1.2.4. 用数组初始化 vector 对象

using namespace std;

int int_arr[] = {0, 1, 2, 3, 4};
// 指明数组的 begin 和 end 指针
vector<int> ivec1(begin(int_arr), end(int_arr));
for (auto i : ivec1) {
  cout << i << ", ";
}
cout << endl;

vector<int> ivec2(int_arr, int_arr + 5);
for (auto i : ivec2) {
  cout << i << ". ";
}
cout << endl;
0, 1, 2, 3, 4, 
0. 1. 2. 3. 4.

1.3. 迭代器

C++ 中的迭代器与 Python 中的含义有一些区别,Python 中的迭代器指的是可迭代的容器, 而 C++ 中指的是指向元素的指针对象。相较于下标索引,迭代器的好处是所有标准库容器 都支持迭代器运算

Screenshot_20200529_121858_HkqmoF.png
Figure 1: 迭代器与迭代器类型
vector<T> vec;
// 由编译器决定 b 和 e 的类型
// b 表示第一个元素,e 表示 vec 尾元素的下一位置,因此也被称为尾后迭代器
auto b = vec.begin(), e = vec.end();

// 事实上标准库使用 iterator 和 const_interator 来表示迭代器的类型
vector<int>::iterator it1;         // 读写 vector<int> 的元素
string::iterator it2;              // 读写 string 对象中的字符
vector<int>::const_iterator it3;   // 只能读
string::const_iterator it4;        // 只能读

1.3.1. 迭代器运算

*iter;            // 返回 iter 所指元素的引用
iter->mem;        // 等价于 (*iter).mem
++iter;           // 指向容器中的下一元素
--iter;           // 指向容器中的上一元素
iter + n;         // 返回新迭代器向前移动 n 个单位
iter - n;         // 返回新迭代器向后移动 n 个单位
iter1 == iter2;   // 是否指向同一元素
iter1 != iter2;   // 是否指向同一元素
iter1 - iter2;    // 返回迭代器的距离

将字符串改为大写的两种方式,对比下标运算与迭代器

using namespace std;
string s1("Keep it simple stupid");
for (int i = 0; i < s1.size(); i++)
  s1[i] = toupper(s1[i]);
cout << s1 << endl;

string s2("Keep it simple stupid");
for (auto iter = s2.begin(); iter != s2.end(); iter++)
  *iter = toupper(*iter);
cout << s2 << endl;
KEEP IT SIMPLE STUPID
KEEP IT SIMPLE STUPID

1.3.2. beginend 运算符

默认情况下 begin 和 end 返回的迭代器具体类型由容器的类型决定

vector<int> v;
const vector<int> cv;
auto it = v.begin();      // it 的类型是 vector<int>::iterator
auto cit = cv.begin();    // cit 的类型是 vector<int>::const_iterator

C++ 11 标准引入了两个新函数 cbegincend ,不论 vector 对象是否本身是常量,返 回的迭代器都是 const_iterator

auto cit = v.cbegin();

1.3.3. 迭代器算数运算

using namespace std;

vector<int> vi(10);
// 两个迭代器的差是 difference_type 类型
cout << "Begin - end is: " << vi.begin() - vi.end() << endl;
cout << "End - begin is: " << vi.end() - vi.begin() << endl;
Begin - end is: -10
End - begin is: 10

1.4. 数组

C++ 中的数组与 C 中基本一致,也同时支持指针和下标索引操作。定义数组时必须指针数 组的类型,不允许用 auto 关键字由初始值的列表推断类型。

int *ptrs[10];             // ptrs 是含有 10 个整型指针的数组
int &refs[10] = ...;       // 错误:不存在引用的数组
int (*Parray)[10] = &arr;  // Parray 指向一个含有 10 个整数的数组
int (&arrRef)[10] = arr;   // arrRef 引用一个含有 10 个整数的数组

1.4.1. 范围 for 语句

数组也可以使用范围 for 语句

using namespace std;

int arr[] = {0, 1, 2, 3, 4};
for (auto i : arr)
  cout << i << " | ";
cout << endl;
0 | 1 | 2 | 3 | 4 |

1.4.2. 数组中的自动类型推断

int a1[] =  {0, 1, 2, 3, 4};
auto a2(a1);                        // a2 是一个整型指针,指向 a1 的第一个元素

decltype(a1) a3 = {0, 1, 2, 3, 4};  // a3 是一个数组对象
a3 = a1;                            // 错误:不能用指针给数组赋值
a3[0] = 2;                          // 正确

1.4.3. 标准库函数 beginend

C++ 11 新标准引入了 beginend 函数,定义在 iterator 头文件中

using namespace std;

int ia[] = {0, 1, 2, 3, 4};
int *begin_ = begin(ia);
int *end_ = end(ia);
// 下标如指针,begin_[0] 等价于 *(begin_ + 0)
//             end_[-1] 等价于 *(end_ - 1)
// 需注意:标准库类型如 string 和 vector 的下标不能是负值
cout << "First elements is: " << begin_[0] << endl;
cout << "Last elements is: " << end_[-1] << endl;
First elements is: 0
Last elements is: 4

1.4.4. 指针的差值

指针的差值与迭代器类似,同样只能对同种类型的指针作差,更近一步,参与运算的两个指 针要指向同一数组中的元素。对不同数组中的指针作差属于未定义的行为,并且也没有意义。 指针相减的结果是 ptrdiff_t 类型,同样是带符号类型,定义在 cstddef 头文件中。

1.4.5. 多维数组与下标

using namespace std;

constexpr size_t rows = 3, cols = 4;
int ia[rows][cols];
for (size_t i = 0; i != rows; i++) {
  for (size_t j = 0; j != cols; j++) {
    ia[i][j] = i * cols + j;
  }
}

// 这个循环中没有任何写操作,但还是将外层的控制变量声明成了引用类型
// 这是为了避免数组被自动转成指针
for (const auto &row : ia) {
  for (auto col : row)
    cout << col << ", ";
  cout << endl;
}
0, 1, 2, 3, 
4, 5, 6, 7, 
8, 9, 10, 11,

1.4.6. 多维数组与指针

using namespace std;

int ia[3][4];
int (*p)[4] = ia;   // p 指向含有 4 个 int 的数组
p = &ia[2];

// 使用 auto 自动推断数组指针类型
for (auto p = ia; p != ia + 3; p++) {
  for (auto q = *p; q != *p + 4; q++) {
    *q = q - *ia;
  }
}

// 使用 begin 和 end 函数
for (auto p = begin(ia); p != end(ia); p++) {
  for (auto q = begin(*p); q != end(*p); q++) {
    cout << *q << ", ";
  }
  cout << endl;
}
0, 1, 2, 3, 
4, 5, 6, 7, 
8, 9, 10, 11,

1.4.7. 类型别名简化多维数组指针

using namespace std;

using int_array = int[4];   // 新标准的类型别名声明
typedef int int_array[4];   // 等价的 typedef 类型别名

for (int_array *p = ia; p != ia + 3; p++)
  for (int *q = *p; q != 4; q++)
    *q = q - *ia;
Author: Cycoe (cycoejoo@163.com)
Date: <2020-05-30 Sat 21:03>
Generator: Emacs 29.1 (Org mode 9.6.6)
Built: <2024-01-27 Sat 21:20>