PART II 字符串、向量和数组
“Mankind was born on Earth.It was never meant to die here.”
——Interstellar
1. using 注意事项
在C++中,using 头文件是一种将命名空间或类型引入到当前命名空间中的方式。它通常用于简化代码,使代码更加清晰,同时避免使用长而复杂的限定符。
- 头文件不应该包含using声明,using引入命名空间: 你可以使用 using namespace 来引入一个命名空间的所有成员到当前作用域。这样可以避免在代码中反复写命名空间限定符。
Eg1:
#include <iostream>
usingnamespace std;
intmain() {
cout << "Hello, World!" << endl; // 不需要写 std::cout 和 std::endl
return 0;
}
- 引入特定成员: 你也可以使用 using 来引入特定命名空间中的成员到当前作用域。
Eg2:
#include <iostream>
usingstd::cout;
usingstd::endl;
intmain() {
cout << "Hello, World!" << endl; // 只引入了 cout 和 endl
return 0;
}
- 类型别名: using 也可以用来创建类型别名,让类型名称更简洁。
Eg3:
usingMyInt = int; // 创建 MyInt 作为 int 的别名
intmain() {
MyInt x = 5;
return 0;
}
2. 直接/拷贝初始化
直接初始化: 直接初始化是通过在变量名称后面使用括号初始化器或等号初始化器来完成的。在直接初始化中,编译器会尝试将提供的值直接传递给合适的构造函数来创建对象。
拷贝初始化: 拷贝初始化是通过使用等号初始化器(赋值号)来完成的。在拷贝初始化中,编译器会尝试调用适当的构造函数创建一个临时对象,然后使用拷贝构造函数将这个临时对象的值复制给目标对象。
当初始值只有一个时,使用直接初始化或拷贝初始化都行。如果初始化要用到的值有多个,一般来说只能使用直接初始化的方式:
Eg4:
string s5 = "hiya";// 拷贝初始化string
s6("hiya");// 直接初始化
string s7(10,'c');// 直接初始化,三内内容是cccccccccc
3.vector/string
- 初始化:
string对象会自动忽略开头的空白(即空格符、换行符、制表符等)并从第一个真正的字符开始读起,直到遇见下一处空白为止。
Eg5:
- string s4(n,c');
- //把s4 初始化为由连续n个字符 c 组成的串
vector初始化注意事项:
Eg6:
vector<int> v3(10,1); // v3有10个元素,每个的值都是1
//注意括号
vector<int> v4{10,1};// v4有2个元素,值分别是10和1
vector 对象(以及 string 对象)的下标运算符可用于访问已存在的元素,而不能用于添加元素,解释如下:
使用下标运算符来添加元素会产生问题,因为添加元素可能需要动态地调整内存空间,这可能涉及到内存重新分配和元素的数据迁移。如果在添加元素时进行内存重新分配,原来的指向旧内存的指针可能会失效,导致程序崩溃或未定义的行为。
因此,为了安全起见,std::vector 和 std::string 都不支持直接使用下标运算符来添加元素。相反,它们提供了方法如 .push_back() 和 .emplace_back() 来添加新的元素到容器的末尾。这些方法会自动处理内存分配和数据迁移的问题,确保在添加元素时不会破坏容器的内部结构。
- 使用范围 for 语句改变字符串中的字符:
如果想要改变 string 对象中字符的值,必须把循环变量定义成引用类型。记住,所谓引用只是给定对象的一个别名,因此当使用引用作为循环控制变量时,这个变量实际上被依次绑定到了序列的每个元素上。使用这个引用,我们就能改变它绑定的字符。
Eg7:
string s("Hello World!!!");//转换成大写形式。
for(auto &c : s)// 对于s中的每个字符(注意:c 是引用)
c = toupper(c);// c 是一个引用,因此赋值语的将改变 s 中宇符的值
cout << s << endl;
- 下标运算符:
string对象的下标从0计起。如果 string 对象 s 至少包含两个字符,则s[0]是第1个字符、s[1]是第2个字符、s[size()-1]是最后一个字符,string 对象的下标必须大于等于0而小于s.size()。
- string::size_type类型
string 类及其他大多数标准库类型都定义了几种配套的类型。这些配套类型体现了标准库类型与机器无关的特性,类型 size type 即是其中的一种。在具体使用的时候,通过作用域操作符来表明名字 size type 是在类 string 中定义的。
它是一个无符号类型的值而且能足够存放下任 string 对象的大小所有用于存放 string 类的 size 函数返回值的变量,都应该是 string::size_type类型的。
如果一条表达式中已经有了size()函数就不再使用int 了,这样可以避免混用int 和unsigned 可能带来的问题。
Eg8:
1.auto len = line.size(); // len 的类型是string::size type
4迭代器iterater
在C++中,迭代器(Iterator)是一种用于遍历容器中元素的对象。它提供了一种统一的方式来访问容器中的元素,而不需要了解容器内部的具体实现细节。通过使用迭代器,你可以在不知道容器类型的情况下遍历其中的元素。
迭代器类型: 不同类型的容器(如数组、向量、链表、映射等)都有自己对应的迭代器类型。这些迭代器提供了适用于容器的操作,如访问元素、移动到下一个元素等。
迭代器操作: 迭代器通常提供以下操作:
*it:返回迭代器指向的元素的引用(解引用操作)。
it++ 或 ++it:将迭代器移动到容器中的下一个元素。
it-- 或 --it:将迭代器移动到容器中的上一个元素(不适用于所有类型的迭代器)。
it1 == it2:比较两个迭代器是否指向同一个元素。
迭代器范围: 迭代器通常用于表示一个范围,包括起始迭代器和结束迭代器。范围通常是左闭右开的,即起始迭代器被包含在范围内,而结束迭代器不被包含在范围内。
Eg9:
#include <iostream>
#include <vector>
intmain() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
// 使用迭代器遍历 vector 中的元素
for (std::vector<int>::iterator it = numbers.begin(); it != numbers.end(); ++it) {
std::cout << *it << " "; // 通过解引用迭代器获取元素值
}
return 0;
}
使用迭代器遍历容器: 通过使用迭代器,你可以遍历容器中的元素,执行操作或获取值,而无需关心容器的内部实现。
Eg10:
#include <iostream>
#include <vector>
intmain() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
// 使用范围基础的 for 循环遍历 vector 中的元素
for (auto num : numbers) {
std::cout << num << " ";
}
return0;
}
5.数组与auto/decltype
在大多数表达式中,使用数组类型的对象其实是使用一个指向该数组首元素的指针。
Eg11:
int ia[] = {0,1,2,3,4,5,6,7,8,9};//a是一个含有 10 个整数的数组
auto ia2(ia):// ia2 是一个整型指针,指向ia 的第一个元素
ia2 = 42;// 错误: ia2 是一个指针,不能用nt 值给指针赋值
尽管i是由 10 个整数构成的数组,但当使用ia作为初始值时,编译器实际执行的初始化过程类似于下面的形式:
Eg12:
1.auto ia2(&ia[0]); //显然 ia2 的类型是 int*
必须指出的是,当使用decltype关键字时上述转换不会发生,decltype(ia)返回的类型是由10个整数构成的数组:
Eg13:
// ia3 是一个含有10 个整数的数组
decltype(ia) ia3=(0,1,2,3,4,5,6,7,8,9);
ia3 = p;// 错误:不能用整型指针给数组赋值
ia3[4] = i;// 正确:把的值默给ia3 的一个元素
尾后指针不能进行解引用和递增操作。
6.用数组初始化vector
要使用数组初始化vector,只需指明要拷贝区域的首元素地址和尾后地址就可以了,这种初始化方法可以通过将数组指针作为参数传递给 std::vector 的构造函数来完成:
Eg14:
#include <iostream>
#include <vector>
intmain() {
int array[] = {1, 2, 3, 4, 5};
// 使用数组初始化 vector 对象
std::vector<int> numbers(array, array + sizeof(array) / sizeof(int));
// 输出 vector 的元素
for(int num : numbers) {
std::cout << num << " ";
}
return0;
}
更简单的方法(实则为找头尾指针处不同):
Eg15:
intint_arr[] ={0,1,2,3,4,5};// ivec 有6个元素,分别是 int arr 中对应元素的副本
std::vector<int> ivec(std::begin(int_arr), std::end(int_arr));
7.多维数组的理解
严格来说,C++语言中没有多维数组,通常所说的多维数组其实是数组的数组。谨记这一点,对今后理解和使用多维数组大有益处。
当一个数组的元素仍然是数组时,通常使用两个维度来定义它:一个维度表示数组本身大小,另外一个维度表示其元素(也是数组) 大小。
考虑到本节所说的范围 for 语句,我们也可以使用for语句来访问:
Eg16:
for(const auto &row : ia) //对于外层数组的每个元素
for (auto col : row) //对于内层数组的每一个元素
cout << col << endl;
这个循环中并没有任何写操作,可是我们还是将外层循环的控制变量声明成了引用类型,这是为了避免数组被自动转成指针。要使用范围 for 语句处理多维数组,除了最内层的循环外,其他所有循环的控制变量都应该是引用类型。
Eg17:
当然,使用标准库函数 begin 和 end也能实现同样的功能,而且看起来更简洁一些:
// p指向ia 的第一个数组
for(auto p = begin(ia); p != end(ia); ++p) {
// q 指向内层数组的首元素
for (auto q = begin(*p); q != end(*p); ++q)
cout <<*q <<' '; // 输出q所指的整数值
cout << endl;
PS:由于格式问题会造成部分内容粘合,欢迎交流。
2023年8月28日
NHG:<
文章评论