C++STL学习第一篇(什么是STL以及string的各种功能用法)

STL

STL提供了六大组件,彼此之间可以组合套用,这六大组件分别是:容器、算法、迭代器、仿函数、适配器、空间配置器。

  1. 数据结构和容器管理:STL 提供了多种数据结构和容器,如向量(vector)、链表(list)、集合(set)、映射(map)等。这些容器可以帮助程序员方便地存储和管理数据,根据需求进行动态调整和操作。
  2. 算法和数据处理:STL 中提供了大量的算法,如排序、查找、遍历等,这些算法可以直接应用于不同类型的容器,帮助程序员高效地对数据进行处理和操作。
  3. 迭代器和访问控制:STL 中的迭代器提供了统一的访问接口,使得程序员能够方便地遍历容器中的元素并进行读写操作。迭代器可以灵活地控制访问范围和方式,为数据访问提供了高度的灵活性。
  4. 泛型编程:STL 的设计基于泛型编程思想,通过模板机制实现了通用的数据结构和算法。这使得 STL 能够适用于各种数据类型,并且支持用户自定义类型的操作,从而提高了代码的重用性和可扩展性。
  5. 性能优化:STL 中的容器和算法都经过高度优化,使用 STL 可以借助这些优化的实现来提高程序的执行效率和性能。

总的来说,STL 适用于需要数据结构、算法和迭代器等功能的各种场景。它为 C++ 程序员提供了丰富的工具和功能,帮助他们更加高效地处理和操作数据,提高代码的可读性、重用性和性能。无论是简单的数据存储管理还是复杂的数据处理和算法实现,STL 都是一个强大而实用的工具库。

容器:各种数据结构,如vector、list、deque、set、map等,用来存放数据,从实现角度来看,STL容器是一种class template。

算法:各种常用的算法,如sort、find、copy、for_each。从实现的角度来看,STL算法是一种function tempalte.

迭代器:扮演了容器与算法之间的胶合剂,共有五种类型,从实现角度来看,迭代器是一种将operator* , operator-> , operator++,operator--等指针相关操作予以重载的class template. 所有STL容器都附带有自己专属的迭代器,只有容器的设计者才知道如何遍历自己的元素。原生指针(native pointer)也是一种迭代器。

仿函数:行为类似函数,可作为算法的某种策略。从实现角度来看,仿函数是一种重载了operator()的class 或者class template

适配器:一种用来修饰容器或者仿函数或迭代器接口的东西。

空间配置器:负责空间的配置与管理。从实现角度看,配置器是一个实现了动态空间配置、空间管理、空间释放的class tempalte.

STL优点

  • STL 是 C++的一部分,因此不用额外安装什么,它被内建在你的编译器之内。

  • STL 的一个重要特点是数据结构和算法的分离。尽管这是个简单的概念,但是这种分离使得 STL 变得非常通用。例如:在 STL 的 vector 容器中,可以放入元素、基础数据类型变量、元素的地址;STL 的 sort() 排序函数可以用来操作 vector,list 等容器。

  • 程序员可以不用思考 STL 具体的实现过程,只要能够熟练使用 STL 就 OK 了。这样他们就可以把精力放在程序开发的别的方面。

  • STL 具有高可重用性,高性能,高移植性,跨平台的优点。

STL三大组件

容器

常用的数据结构无外乎数组(array),链表(list),tree(树),栈(stack),队列(queue),集合(set),映射表(map),根据数据在容器中的排列特性,这些数据分为序列式容器关联式容器两种。

Ø 序列式容器就是容器元素在容器中的位置是由元素进入容器的时间和地点来决定。Vector容器、Deque容器、List容器、Stack容器、Queue容器。

Ø 关联式容器是指容器已经有了一定的规则,容器元素在容器中的位置由我的规则来决定。Set/multiset容器 Map/multimap容器

算法

算法分为:质变算法非质变算法

质变算法:是指运算过程中会更改区间内的元素的内容。例如拷贝,替换,删除等等

非质变算法:是指运算过程中不会更改区间内的元素内容,例如查找、计数、遍历、寻找极值等等

迭代器

迭代器(iterator)是一种抽象的设计概念,现实程序语言中并没有直接对应于这个概念的实物。在<>一书中提供了23中设计模式的完整描述,其中iterator模式定义如下:提供一种方法,使之能够依序寻访某个容器所含的各个元素,而又无需暴露该容器的内部表示方式。

迭代器的设计思维-STL的关键所在,STL的中心思想在于将数据容器(container)和算法(algorithms)分开,彼此独立设计,最后再一贴胶着剂将他们撮合在一起。从技术角度来看,容器和算法的泛型化并不困难,c++的class template和function template可分别达到目标,如果设计出两这个之间的良好的胶着剂,才是大难题。

迭代器的种类:

输入迭代器 提供对数据的只读访问 只读,支持++、==、!=
输出迭代器 提供对数据的只写访问 只写,支持++
前向迭代器 提供读写操作,并能向前推进迭代器 读写,支持++、==、!=
双向迭代器 提供读写操作,并能向前和向后操作 读写,支持++、--,
随机访问迭代器 提供读写操作,并能在数据中随机移动 读写,支持++、--、[n]、-n、<、<=、>、>=
void test2() {
	vector<int>r;//STL 中的标准容器之一 :动态数组
	r.push_back(1);//vector 容器提供的插入数据的方法
	r.push_back(2);
	r.push_back(7);
	//vector 容器提供了 begin()方法 返回指向第一个元素的迭代器
	//vector 容器提供了 end()方法 返回指向最后一个元素下一个位置的迭代器
	vector<int>::iterator reg = r.begin();
	vector<int>::iterator ina = r.end();//这是一种随机访问类型的迭代器
	while (reg != ina) {
		cout << *reg << " ";
		reg++;
	}
	cout << endl;

	//算法 count 算法 用于统计元素的个数
	int total = count(reg, ina, 5);
	cout << "total:" << total << endl;
}

STL 容器不单单可以存储基础数据类型,也可以存储类对象

class Regina {
public:
	Regina(int a) : age(a) {};
	int age;
	~Regina(){};
};
vector<Regina>r;
Regina r1(1), r2(2), r3(7);
r.push_back(r1);
r.push_back(r2);
r.push_back(r3);
vector<Regina>::iterator pStart = r.begin();
vector<Regina>::iterator pEnd = r.end();
while (pStart != pEnd) {
	cout << "pStart->age: " << pStart->age << " ";
	pStart++;
}
cout << endl;

还可以存储类的指针

在 C++ 中,当我们使用迭代器遍历容器时,迭代器本身是一个指向容器中元素的对象,而不是实际的元素本身。因此,为了访问容器中存储的元素,我们需要通过解引用操作符 * 来获取迭代器指向的实陨对象.具体的vector怎么用在下一篇

vector<Regina*> v;//存储 Teacher 类型指针
Regina* r1 = new Regina(1);
Regina* r2 = new Regina(2);
Regina* r3 = new Regina(7);
v.push_back(r1);
v.push_back(r2);
v.push_back(r3);
//拿到容器迭代器
vector<Regina*>::iterator pStart = v.begin();
vector<Regina*>::iterator pEnd = v.end();
//通过迭代器遍历
while (pStart != pEnd) {
	cout << (*pStart)->age << " ";
	pStart++;
}
cout << endl;

常用容器讲解

string

C风格字符串(以空字符结尾的字符数组)太过复杂难于掌握,不适合大程序的开发,所以C++标准库定义了一种string类,定义在头文件

String和c风格字符串对比:

  • Char是一个指针,String是一个类

    string封装了char,管理这个字符串,是一个char型的容器。

  • String封装了很多实用的成员方法

    查找find,拷贝copy,删除delete 替换replace,插入insert

  • 不用考虑内存释放和越界

    string管理char*所分配的内存。每一次string的复制,取值都由string类负责维护,不用担心复制越界和取值越界等。

string regina1("regina");
string regina2(7, '$');
string regina3(regina1);
cout << regina1 << endl;  // 输出 "regina"
cout << regina2 << endl;  // 输出 "$$$$$$$"
cout << regina3 << endl;  // 输出 "regina"
regina1 += "baby";
cout << regina1 << endl;  // 输出 "reginababy"
string regina4 = regina1 + regina3;
char arr[] = "arr loop";
string regina5(arr, 2);
string regina6(arr + 1, arr + 3);
cout << regina1 << endl;  // 输出 "reginababy"
cout << regina4 << endl;  // 输出 "reginababyregina"
cout << regina5 << endl;  // 输出 "ar"
cout << regina6 << endl;  // 输出 "rr"
string regina7(&regina4[1], &regina4[4]);
string regina8(regina4, 1, 5);
cout << regina7 << endl;  // 输出 "egi"
cout << regina8 << endl;  // 输出 "egina"

基本赋值操作

string& operator=(const char* s):将 char* 类型的字符串赋值给当前的字符串。
string& operator=(const string& s):将另一个字符串对象 s 赋值给当前的字符串。
string& operator=(char c):将单个字符 c 赋值给当前的字符串。
string& assign(const char* s):将 char* 类型的字符串 s 赋值给当前的字符串。
string& assign(const char* s, int n):将 char* 类型的字符串 s 的前 n 个字符赋值给当前的字符串。
string& assign(const string& s):将另一个字符串对象 s 赋值给当前的字符串。
string& assign(int n, char c):使用 n 个字符 c 来赋值给当前的字符串。
string& assign(const string& s, int start, int n):将字符串 s 从位置 start 开始的 n 个字符赋值给当前的字符串。
	string s1, s2, s3;

	// 使用赋值操作符重载将 char* 类型的字符串赋值给当前的字符串
	s1 = "regina, baby!";
	cout << "s1: " << s1 << endl;

	// 使用赋值操作符重载将另一个字符串对象赋值给当前的字符串
	s2 = s1;
	cout << "s2: " << s2 << endl;

	// 使用 assign() 成员函数将 char* 类型的字符串赋值给当前的字符串
	s3.assign("regina", 4);
	cout << "s3: " << s3 << endl;

	// 使用 assign() 成员函数将另一个字符串对象赋值给当前的字符串
	s1.assign(s3);
	cout << "s1: " << s1 << endl;

	// 使用 assign() 成员函数使用 n 个字符 c 赋值给当前的字符串
	s2.assign(8, '*');
	cout << "s2: " << s2 << endl;

	// 使用 assign() 成员函数将字符串 s 的子串赋值给当前的字符串
	string s4 = "regina, baby!";
	string s5;
	s5.assign(s4, 7, 5); // 从索引 7 开始的 5 个字符
	cout << "s5: " << s5 << endl;

存取字符串

  1. char& operator[](int n):通过重载 [] 运算符来获取字符串中索引为 n 的字符。这种方式不进行边界检查,如果访问超出字符串范围的位置,可能导致未定义行为。
  2. char& at(int n):通过 at 方法来获取字符串中索引为 n 的字符。与使用 operator[] 不同的是,at 方法会进行边界检查,如果访问超出字符串范围的位置,会抛出 out_of_range 异常。
string r = "regina";
// 使用 operator[] 获取字符
cout << "Character at index 7 (using operator[]): " << r[5] << endl;

// 使用 at 方法获取字符
try {
	cout << "Character at index 3 (using at): " << r.at(3) << endl;
	cout << "Character at index 13 (using at): " << r.at(13) << endl;
}
catch (const out_of_range& e) {
	cerr << "Exception caught: " << e.what() << endl;
}

cerr 是 C++ 中的一个输出流对象,它与标准错误流 (stderr) 相关联。与标准输出流 (cout) 不同,标准错误流用于输出程序中的错误消息和诊断信息。

cerr 对象通常会直接将消息输出到终端或日志文件,而不会进行缓冲操作。这意味着错误消息会立即显示在终端上,而不需要等待缓冲区刷新。因此,cerr 在处理错误和调试信息时非常有用。

拼接字符串

string& operator+=(const string& str), string& operator+=(const char* str), string& operator+=(const char c):这些是重载 += 操作符的方法,用于将另一个字符串、C风格字符串或字符连接到当前字符串的末尾。

string& append(const char *s):将 C 风格的字符串 s 追加到当前字符串的末尾。

string& append(const char *s, int n):将 C 风格的字符串 s 的前 n 个字符追加到当前字符串的末尾。

string& append(const string& s):将字符串 s 追加到当前字符串的末尾,与 operator+= 的功能类似。

string& append(const string& s, int pos, int n):将字符串 s 中从索引 pos 开始的 n 个字符追加到当前字符串的末尾。

string& append(int n, char c):在当前字符串的末尾添加 n 个字符 c。
string str = "Hello";

    // 使用 += 操作符连接字符串
    str += ", world!";
    cout << "After +=: " << str << endl;

    // 使用 append(const char*) 连接字符串
    str.append(" This is a test.");
    cout << "After append(const char*): " << str << endl;

    // 使用 append(const string&) 连接字符串
    string additional = " Have a nice day!";
    str.append(additional);
    cout << "After append(const string&): " << str << endl;

    // 使用 append(const char*, int) 连接部分字符串
    str.append(" Testing", 4); // 添加 " Test"
    cout << "After append(const char*, int): " << str << endl;

    // 使用 append(int, char) 添加字符
    str.append(3, '!'); // 添加 "!!!"
    cout << "After append(int, char): " << str << endl;

查找和替换

	string words = "regina baby is my sM baby";
	int pos = words.find("baby");
	cout << pos << '\n';
	pos = words.find("sM", 4, 2);
	cout << pos << '\n';
	pos = words.rfind("baby");
	cout << pos << '\n';

比较操作

/*
compare函数在>时返回 1,<时返回 -1,==时返回 0。
比较区分大小写,比较时参考字典顺序,排越前面的越小。
大写的A比小写的a小。
*/
int compare(const string& s) const;//与字符串s比较
int compare(const char *s) const;//与字符串s比较

	string s1 = "regina";
	string s2 = "ivanlee";
	cout << "s1.compare(s2): " << s1.compare(s2) << "\n";
	const char* cs2 = "leeivan";
	cout << "s2.compare(cs2): " << s2.compare(cs2) << "\n";

插入和删除

string& insert(int pos, constchar* s); //插入字符串
string& insert(int pos, conststring& str); //插入字符串
string& insert(int pos, int n, char c);//在指定位置插入n个字符c
string& erase(int pos, int n = npos);//删除从Pos开始的n个字符 
	string s = "hello regina";
	s.insert(5, "beautiful");
	cout << s << "\n";
	s.insert(s.length(), 2, '!'); //单引号
	cout << s << "\n";
	s.erase(5, 3);
	cout << s << "\n";

string和c-style字符串转换

	string s = "regina";
	const char* c = s.c_str();//返回的是一个指向常量字符的指针
	cout << "s.c_str(): " << typeid(c).name() << endl;
	const char* c1 = "regina";
	string s1(s);
	cout << "string s1(s); " << typeid(s1).name() << endl;

在c++中存在一个从const char*到string的隐式类型转换,却不存在从一个string对象到C_string的自动类型转换。对于string类型的字符串,可以通过c_str()函数返回string对象对应的C_string.

通常,程序员在整个程序中应坚持使用string类对象,直到必须将内容转化为char*时才将其转换为C_string.

为了修改string字符串的内容,下标操作符[]和at都会返回字符的引用。但当字符串的内存被重新分配之后,可能发生错误.

自动扩容

在 C++ 的标准库中,std::string 类具有自动调整大小的功能。当你向 std::string 对象添加字符时,如果当前容量不足以存储新的字符,std::string 会自动重新分配内存以扩展容量,从而确保可以存储新的字符并保持字符串的有效性。

这种自动调整大小的过程是由 std::string 类内部管理的,开发者无需手动管理字符串的内存大小。当添加字符导致字符串超出当前容量时,std::string 会选择一个新的更大的内存块,将旧数据复制到新的内存块中,并释放旧的内存块。

这种自动调整大小的机制使得 std::string 更加方便和易用,开发者可以专注于操作字符串内容而不必担心内存管理的细节。

	std::string str = "regina";
	std::cout << "Initial capacity: " << str.capacity() << std::endl;
	// 添加字符,观察容量的变化
	for (char c = 'a'; c <= 'z'; ++c) {
		str.push_back(c);
		std::cout << "Capacity after adding '" << c << "': " << str.capacity() << std::endl;
	}

std::string 类提供了 capacity() 方法,用于返回当前字符串对象的容量大小。容量表示为当前存储空间的大小,它可能大于等于字符串的长度,因为 std::string 可能会分配比实际字符串长度更多的内存,以便进行后续的添加操作而不需要频繁地重新分配内存。

通常情况下,当你向 std::string 对象添加字符时,如果当前的容量不足以存储新的字符,std::string 会自动重新分配内存以扩展容量,从而确保可以存储新的字符并保持字符串的有效性。这样的设计可以提高性能,因为它避免了频繁的内存分配和释放操作。

热门相关:最牛兵王   最牛兵王   绝代疯少   榴绽朱门   君归矣