STL(standard template libaray-标准模板库):是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架。
STL为我们提供了许多常用的数据结构和算法,包括数组、链表、栈、队列、堆、集合、映射、排序、查找等等,可以帮助程序员大大提高开发效率。
STL中的容器包括序列式容器和关联式容器两种,序列式容器包括vector、deque、list、forward_list等,而关联式容器则包括set、multiset、map、multimap等。每种容器都有其特定的使用场景和优势,确保了程序员能够轻松地选择合适的容器来处理自己的数据。
在介绍STL中各种常用的容器,我认为容器这个概念可能是从C语言进阶到C++以来的一个陌生的概念:
在C++中,容器通常指一组可存储、访问和管理数据的对象集合。容器可以看作是数据结构的高级形式,可以帮助程序员更方便地组织和操作数据。
不同类型的容器提供了不同的功能和性能特点,以便适应各种应用场景。例如,序列式容器(如vector、deque、list等)提供了线性访问和插入/删除操作,而关联式容器(如set、map等)则提供了基于键值的查找和排序操作。
除了容器,STL中还提供了迭代器、函数对象、算法等其他重要组件。迭代器可以让程序员轻松地遍历容器中的元素,函数对象则可以封装函数并在需要时被调用,而算法则可以对容器中的元素进行各种操作,如查找、排序、去重等等。也就是常说的STL包含六大组件:
使用STL可以带来很多好处。首先,STL提供了现成的数据结构和算法,这使得程序员不再需要自己实现这些基础组件,节省了大量的时间和精力。这也是它比C语言更简洁的一个原因。其次,STL中的容器和算法都经过了充分测试和优化,因此它们的性能和稳定性都非常可靠。最后,STL的使用可以提高程序的可读性和可维护性,因为它提供了一种标准的、通用的方式来处理各种数据结构和算法。
C语言中,字符串是以’\0’结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不方便我们直接使用,而在C++中,string是一种表示字符串的类类型。它封装了一系列函数和操作符,使得字符串的操作更加方便和高效,更加方便我们使用
函数名称 | 功能 |
---|---|
string() | 构造空的string类对象,即空字符串 |
string(const char* s) | 用C-string来构造string类对象 |
string(size_t n, char c) | string类对象中包含n个字符c |
string(const string&s) | 拷贝构造函数 |
因为这四个函数简单易懂,所以只演示以上四个函数用法:
#include <iostream>
#include <string>
int main() {
std::string s; // 创建一个空字符串对象
std::cout << s << std::endl; // 输出空字符串: ""
const char* cstr = "Hello";//const char* 类型的指针 cstr 指向一个以空字符结尾的字符数组 "Hello"。通过调用 std::string 的构造函数 string(const char* s),将该字符数组转换为了 std::string 对象 s,并输出了 Hello
std::string s(cstr); // 将字符数组转换为字符串
std::cout << s << std::endl; // 输出:Hello
std::string s(5, 'A'); // 创建一个包含5个重复字符'A'的字符串
std::cout << s << std::endl; // 输出:AAAAA
std::string original("Hello");//首先创建了一个原始的字符串对象 original,其值为 "Hello"。然后使用拷贝构造函数 string(const string& s) 创建了一个副本 copy,它与 original 包含相同的内容。最后,通过输出语句分别输出 original 和 copy 的值,可以看到它们的内容是相同的
std::string copy(original); // 使用拷贝构造函数创建副本
std::cout << "Original: " << original << std::endl; // 输出:Hello
std::cout << "Copy: " << copy << std::endl; // 输出:Hello
return 0;
}
函数名称 | 功能 |
---|---|
size | 返回字符串有效字符长度 |
lenth | 返回字符串有效字符长度 |
capacity | 返回空间总大小 |
empty | 检测字符串释放为空串,是返回true,否则返回false |
clear | 清空有效字符 |
reverse | 为字符串预留空间 |
resize | 将有效字符的个数该成n个,多出的空间用字符c填充 |
示例:
#include <iostream>
#include <string>
int main() {
std::string s("Hello");
std::cout << "Length of string: " << s.size() << std::endl; // 输出:Length of string: 5
return 0;
}
首先创建了一个 std::string 对象 s,其值为 “Hello”。然后通过调用 size() 函数获取了该字符串对象的长度,输出了 5
在string::size()函数中,返回的是size_t类型的无符号整数,来表示字符串对象中字符的数量。由于无符号整数不能为负数,因此使用该函数时需要注意不要与负数进行比较或运算
capacity()函数用于返回字符串对象分配的内存容量。
#include <iostream>
#include <string>
int main() {
std::string s("Hello");
std::cout << "Capacity of string: " << s.capacity() << std::endl;
return 0;
}
在上面的代码中创建了一个 std::string 对象 s,其值为 “Hello”。然后通过调用 capacity() 函数获取了该字符串对象的容量,并输出了结果Capacity of string: 15
需要注意的是,字符串对象的容量可能大于其当前存储的字符数量。这是因为 std::string 类实现时通常会使用一些预留的内存空间,以便在需要时能够快速添加更多的字符,而不必频繁重新分配内存。因此,capacity() 函数返回的值可能大于 size() 或 length() 函数返回的值。
接下来是判断字符串对象是否为空的函数,
#include <iostream>
#include <string>
int main() {
std::string s1(""); // 空字符串
std::string s2("Hello");
if (s1.empty()) {
std::cout << "String is empty" << std::endl;
} else {
std::cout << "String is not empty" << std::endl;
}
if (s2.empty()) {
std::cout << "String is empty" << std::endl;
} else {
std::cout << "String is not empty" << std::endl;
}
return 0;
}
在上面的示例中,创建了两个 std::string 对象,分别为 s1 和 s2。s1 是一个空字符串,s2 的值为 “Hello”。通过调用 empty() 函数来判断两个字符串对象是否为空,并输出结果String is empty String is not empty
clear函数很好理解,这里不做代码演示
#include <iostream>
#include <string>
int main() {
std::string s;
std::cout << "Before reserve: Capacity is " << s.capacity() << std::endl;
s.reserve(20);
std::cout << "After reserve: Capacity is " << s.capacity() << std::endl;
return 0;
}
在代码演示中,首先创建了一个空的 std::string 对象 s。首先输出了预分配内存前的 capacity() 值,即当前字符串对象的内存容量。然后调用 reserve() 函数,将字符串对象预分配的内存大小设置为 10。最后输出预分配内存后的 capacity() 值
需要注意的是,reserve() 函数只是预分配了内存空间,并不会改变字符串对象中实际存储的字符数量。因此,在使用 reserve() 函数时,应根据预计的字符串长度来选择合适的内存空间大小,以避免过度分配导致的浪费。
此外,reserve() 函数的调用只有在预先知道字符串的最大长度时才有意义,否则可以省略该函数,让 std::string 类根据需要自动管理内存空间
#include <iostream>
#include <vector>
int main() {
std::vector<int> numbers = {1, 2, 3};
std::cout << "Before resize: ";
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
numbers.resize(5, 0);
std::cout << "After resize: ";
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
numbers.resize(2);
std::cout << "After resize again: ";
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
在上面的代码演示中,创建了一个 std::vector 对象 numbers,初始包含了三个整数元素 {1, 2, 3}。首先输出了改变大小前的数组内容,然后调用 resize() 函数将数组大小改为 5,并指定默认值为 0(如果需要扩展数组大小,则使用默认值填充新添加的元素)。接着输出改变大小后的数组内容。最后再次调用 resize() 函数将数组大小改为 2,并输出最终的数组内容。
需要注意的是,当在缩小数组大小时,超出新大小的元素将被删除,而且这些元素的内存空间也会被释放。因此,在进行数组大小缩小操作时,应确保不会丢失需要的数据。另外,如果在扩展数组大小时,指定了默认值,则新添加的元素将使用该默认值填充。