C++初阶 -- 初识STL和string类详细使用接口的教程(万字大章)

news/2025/2/1 23:58:29 标签: c++, 开发语言, 笔记, 算法

目录

一、STL

1.1 什么是STL

1.2 STL的版本

1.3 STL的六大组件

二、string类

2.1 string类的基本介绍

2.2  string类的默认成员函数

2.2.1 构造函数

 2.2.2 析构函数

2.2.3 赋值运算符重载

2.3  string类对象的容量操作

2.3.1 size和length

2.3.2  capacity

2.3.3 reserve和resize 

2.3.3.1 reserve

2.3.3.2 resize

2.3.4 empty

2.3.5 clear

2.4 string类的迭代器(iterator)

2.4.1 介绍

 2.4.2 begin和end(正向迭代器和const正向迭代器)

2.4.3 rbegin和rend(逆向迭代器和const逆向迭代器)

2.5  string类对象的访问及遍历操作

2.5.1 operator[]

2.5.2 at

 2.5.3 范围for循环

2.5.4 使用迭代器iterator(推荐使用)

2.6 string类对象的修改操作

2.6.1 Push_back

2.6.2 append

2.6.3  operator+=(最常用的尾插方法)

 2.6.4 c_str

2.6.5  npos

2.6.6 find

 2.6.7 rfind

 2.6.8 substr

2.6.9 erase

2.6.10 insert(效率很低,能不用就不用)

 注意:

2.7 有关string类的迭代器的补充

2.7.1 string类迭代器的底层实现

2.7.2 范围for循环与迭代器iterator的关系

一、STL

1.1 什么是STL

STL(standard template libaray-标准模板库):是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架

1.2 STL的版本

  • 原始版本

    Alexander Stepanov、Meng Lee 在惠普实验室完成的原始版本,本着开源精神,他们声明允许任何人任意运用、拷贝、修改、传播、商业使用这些代码,无需付费。唯一的条件就是也需要向原始版本一样做开源使用。 HP 版本--所有STL实现版本的始祖

  • P. J. 版本

    由P. J. Plauger开发,继承自HP版本,被Windows Visual C++采用,不能公开或修改,缺陷:可读性比较低,符号命名比较怪异。

  • RW版本

    由Rouge Wage公司开发,继承自HP版本,被C+ + Builder 采用,不能公开或修改,可读性一般。

  • SGI版本

    由Silicon Graphics Computer Systems,Inc公司开发,继承自HP版 本。被GCC(Linux)采用,可移植性好,可公开、修改甚至贩卖,从命名风格和编程 风格上看,阅读性非常高。我们后面学习STL要阅读部分源代码,主要参考的就是这个版本


1.3 STL的六大组件

二、string类

2.1 string类的基本介绍

C语言中,字符串是以'\0'结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问

总结:

  • string是表示字符串的字符串类
  • 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。
  • string在底层实际是:basic_string模板类的别名,                                               typedef basic_string<char, char_traits, allocator>string;                                                  string类是basic_string模板类的一个实例
  • 不能操作多字节或者变长字符的序列。

2.2  string类的默认成员函数

(constructor)构造函数

Construct string object (public member function )

(destructor)析构函数

String destructor (public member function )

(operator=) 赋值运算符重载

String assignment (public member function )

2.2.1 构造函数

string类常见的构造函数

(constructor)函数名称功能说明
string() (重点)构造空的string类对象,即空字符串
string(const char* s) (重点)C-string(c风格的字符串)来构造string类对象
string(size_t n, char c)string类对象中包含n个字符c(用n个字符c来构造string类对象)
string(const string&s) (重点)拷贝构造函数
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
using namespace std;

int main()
{
	string s1;
	string s2("aa aaa bbbbb");
	string s3(s2);	
    string s4(10, 'c');

    cout << s1 << endl;
	cout << s2 << endl;
	cout << s3 << endl;
	cout << s4 << endl;	

    return 0;
}

 2.2.2 析构函数

销毁string对象

2.2.3 赋值运算符重载

 分配一个新的值的string类对象来代替当前的容器

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
using namespace std;

int main()
{
	string s1("aa aaa bbbbb");
    string s2(10, 'c');

    cout << s1 << endl;
	cout << s2 << endl;

    return 0;
}

2.3  string类对象的容量操作

函数名称功能说明

size(重点)(常用)

返回字符串有效字符长度

length

返回字符串有效字符长度

capacity

返回空间总大小

empty(重点)

检测字符串是否为空串,是返回true,否则返回false

clear(重点)

清空有效字符

reserve(重点)

为字符串预留空间

resize(重点)

将有效字符的个数该成n个,多出的空间用字符c填充

2.3.1 size和length

这两个函数都是返回当前对象的有效字符长度。

由于历史的种种原因,还是推荐使用size()函数

int main()
{
	string s2("aa aaa bbbbb");
	cout << s2.size() << endl;
	cout << s2.length() << endl;

	return 0;
}

2.3.2  capacity

  • 格式:size_t capacity() const
  •   返回值:返回当前为字符串分配的存储空间的大小
int main()
{
	string s1("hello");
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
    
    return 0;
}

注意一个问题:当构建一个空string类对象时,它的容量是0还是其他数?

  1.  通过上图可以看出,在VS2022下默认会给生成的字符串对象开辟15字节的空间(由于编译器的不同,默认开辟的空间大小也会不同)。Linux的g++环境下默认不给对象开辟空间。
  2. 当字符串添加新字符后,字符串当前长度大于原本为其开辟的空间。对象就会自动扩容(即重新分配空间)
  3. 在VS2022下,string类对象自动扩容是按照原大小的1.5倍进行扩容。在不同的编译器下,扩容的倍数也会不同。Linux下的g++环境按照原大小的两倍进行扩容。
  4. capacity函数返回的是当前对象的空间大小,但由于在c/c++中'\0'并不是有效字符,所以capacity函数并没有将'\0'算进去。所以对象实际的空间大小应该在原有的基础上多加1字节,因为字符串的末尾还有个'\0'.

2.3.3 reserve和resize 

2.3.3.1 reserve
  • 格式:void reserve (size_t n = 0)
  • 返回值:None

请求根据计划改变字符串容量,长度最多为n个字符。(只影响容量,不影响数据) 

对于reserve函数,可以有两种改变容量的情况。一个是扩容,另一个则是缩容

1、扩容

int main()	
{
	string s1;

	// reserve()的使用最好是已经确定需要多少空间,根据需求开好空间即可
	s1.reserve(100);

	size_t old = s1.capacity();
	cout << old << endl;

	for (size_t i = 0; i < 100; i++)
	{
		s1.push_back('x');

		if (old != s1.capacity())
		{
			old = s1.capacity();
			cout << old << endl;
		}
	}

	return 0;
}

2、 缩容

在VS2022下是不会发生缩容的,但是在Linux的g++环境下就会发生缩容,最多只能缩到对象的size()

int main()
{
	string s1("hello");
	s1.reserve(1);
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	cout << s1 << endl;

	return 0;
}

2.3.3.2 resize
  • 格式:void resize (size_t n) 或 void resize (size_t n, char c)

  • 返回值: None

将字符串的大小改为n个字符c的长度。(既影响容量,也影响数据) 

注意:有关容量大小n的问题

1、如果n小于当前字符串的长度,则当前值将缩短为其前n个字符并删除第n个字符以外的字符。

int main()
{
	string s1("hello");
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	cout << s1 << endl;

	// n < size --> 将容量缩短为前n个字符,并删除第n个字符以外的字符
	s1.resize(3);
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	cout << s1 << endl;

    return 0;
}

2、如果n大于当前字符串存储空间的大小,则通过在末尾插入所需数量的字符来扩展当前内容,以到达n的大小。如果指定了补充的字符为c,则新元素将初始化为c的副本。否则,它们的值将初始化为空字符('\0')。

int main()
{
	string s1("hello");
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	cout << s1 << endl;

	//  n > capacity --> 不指定字符,默认插入空字符'\0'并扩容到n个字节
	s1.resize(100);
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	cout << s1 << endl;

    return 0;
}

3、 如果n小于当前字符串的存储空间的大小且大于当前字符串有效字符的个数,会将当前字符串有效字符的个数的位置的后面的空间全部设置为字符 c。

int main()
{
	string s1("hello");
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	cout << s1 << endl;

	//  size  < n < capacity --> 不指定字符,默认插入n个空字符'\0'。但不扩容
	s1.resize(8);
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	cout << s1 << endl;

    return 0;
}

2.3.4 empty

  • 格式:bool empty() const
  • 返回值:如果字符串长度为0就返回true,否则返回true
int main()
{
	string s1;
	cout << s1.empty() << endl;

	string s2 = "aaaaaa";
	cout << s2.empty() << endl;

	return 0;
}

2.3.5 clear

  • 格式: void clear()

  • 返回值:None

清空字符串内的有效字符,但并不会影响底层空间的大小

int main()
{
	string s2 = "aaaaaa";
	s2.clear();
	cout << s2.empty() << endl;

	return 0;
}

 

总结:

  1. size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()。
  2. clear()只是将string中有效字符清空不改变底层空间大小
  3. resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。
  4. reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小。

2.4 string类的迭代器(iterator)

2.4.1 介绍

在C++中,迭代器(lterator) 是一种设计模式,它提供了一种统一的方式来遍历容器(如vector、list、map 等)中的元素,而无需暴露容器的内部实现细节。迭代器的行为类似于指针允许你逐个访问容器中的元素

迭代器名称迭代器的作用

begin + end

begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器

rbegin + rend

rbegin获取最后一个字符的迭代器 + rend获取一个字符的前一个位置的迭代器

string类提供了不同类型的迭代器:

  1. 正向迭代器(iterator):遍历可修改字符串的迭代器
  2. 常属性正向迭代器(const_iterator):只能读取字符串的元素不能修改的迭代器
  3. 反向迭代器(reverse_iterator):逆向遍历字符串
  4. 常属性反向迭代器(const_reverse_iterator):只能逆向读取字符串中的元素不能修改的迭代器

 2.4.2 begin和end(正向迭代器和const正向迭代器)

 begin:获取第一个字符的迭代器

const_begin:获取第一个字符但不能修改的迭代器

end获取最后一个字符下一个位置的迭代器(类似于哨兵)

const_end:获取最后一个字符下一个位置但不能修改的迭代器

int main()
{
	string s1 = "abcdefghijk";
	string::iterator sit = s1.begin();
	string::iterator eit = s1.end();
    
    // 利用迭代器遍历字符串
	while (sit != eit)
	{
		cout << *sit << " ";
		++sit;
	}

	return 0;
}

2.4.3 rbegin和rend(逆向迭代器和const逆向迭代器)

用法类似于正向迭代器begin和end以及const正向迭代器const_reverse_iterator和const_reverse_iterator,但方向是相反的。

2.5  string类对象的访问及遍历操作

函数名称功能说明

operator[](重点)

返回pos位置的字符,const string类或string类对象都可以调用

begin + end

begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器

rbegin + rend

rbegin获取最后一个字符下一个位置的迭代器 + rend获取一个字符的迭代器
范围forC++11支持更简洁的范围for的新遍历方式

2.5.1 operator[]

得到字符串中的字符,但要注意的是返回值的类型是char&。说明使用operator[]不仅可以访问字符串中的字符,还可以修改字符。 

int main()
{
	string s1 = "abcdefghijk";
	cout << s1[0] << endl; // 访问字符串s1的第一个字符
	
	s1[0] = '0'; // 修改字符串s1的第一个字符
	cout << s1[0] << endl; // 访问字符串s1的第一个字符

	return 0;
}

2.5.2 at

at和operator[]的功能是一样的,唯一的区别就是处理越界的方式不同

int main()
{
	try
	{
		string s1("hello world");
		cout << s1[11] << endl;
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}

	return 0;
}

 为什么访问数组的下标已经越界,却不报错?

原因在于还有一个字符'\0',在C++中还是允许访问的。

1、operator[]处理越界的方法

int main()
{
	try
	{
		string s1("hello world");
		cout << s1[20] << endl;
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}

	return 0;
}

2、at()处理越界的方法

int main()
{
	try
	{
		string s1("hello world");
		cout << s1.at(20) << endl;
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}

	return 0;
}

 2.5.3 范围for循环

int main()
{
	string s1 = "abcdefghijk";
	
	// 遍历字符串 -- 搭配auto关键字一起使用
	for (auto& c : s1)
	{
		cout << c << " ";
	}

	return 0;
}

2.5.4 使用迭代器iterator(推荐使用)

int main()
{
	string s1 = "abcdefghijk";
	string::iterator sit = s1.begin();
	string::iterator eit = s1.end();
    
    // 利用迭代器遍历字符串
	while (sit != eit)
	{
		cout << *sit << " ";
		++sit;
	}

	return 0;
}

2.6 string类对象的修改操作

函数名称功能说明

push_back

在字符串后尾插字符c
append在字符串后追加一个字符串

operator+= (重点)

在字符串后追加字符串str

c_str (重点)

返回C格式字符串
find + npos(重点)从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置

rfind

从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置

substr

在str中从pos位置开始,截取n个字符,然后将其返回

2.6.1 Push_back

  • 格式:void push_back (char c);

  • 用法:在字符串末尾尾插字符c。

int main()
{
	string s1 = "abcdefghijk";
	cout << s1 << endl;

	s1.push_back('2');
	cout << s1 << endl;

	return 0;
}

2.6.2 append

int main()
{
	string s1 = "hello";
	string s2 = "world";
	s1.append(" ");
	s1.append(s2);

	cout << s1 << endl;
	return 0;
}

2.6.3  operator+=(最常用的尾插方法)

将str的内容追加到当前字符串的末尾

int main()
{
	string s1 = "hello";
	string s2 = "world";
	s1 += " ";
	s1 += s2;

	cout << s1 << endl;
	return 0;
}

 2.6.4 c_str

返回指向一个不可修改数组的指针,该数组包含以null结尾的字符序列(即C字符串),表示字符串对象的当前值。

int main()
{
	string filename("Test.cpp");
	FILE* fout = fopen(filename.c_str(), "r");
	char ch = fgetc(fout);
	while (ch != EOF)
	{
		cout << ch;
		ch = fgetc(fout);
	}
	
	return 0;
}

2.6.5  npos

在谈及下一个函数前,先得了解一个数值,他的名叫npos 

npos是一个静态且不可被修改的无符号整数,它表示无符号整型的最大值(-1)

2.6.6 find

寻找在字符串中的内容

注意:如果找到目标内容就返回目标的下标,如果没找到目标则返回npos

int main()
{
	string s1 = "hello";

	cout << s1.find("e") << endl;
	cout << s1.find("d");
	return 0;
}

 2.6.7 rfind

返回最后一个匹配字符/字符串的位置,整体与find相似。

 2.6.8 substr

返回一个新构造的string对象,它的初始值为当前字符串的子字符串(在str中从pos位位置开始,截取n个字符)

int main()
{
	string s1 = "hello";
	cout << s1.substr(0 , 4) << endl;

	return 0;
}

2.6.9 erase

删除字符串的一部分,减少字符串的长度。

int main()
{
	string s1("hello world");
	string s2("xxx");

	s1.erase(3, 2);
	cout << s1 << endl;

	s1.erase(3);
	cout << s1 << endl;

	s2.erase();
	cout << s2 << endl;
	return 0;
}

2.6.10 insert(效率很低,能不用就不用)

insert函数由于每次插入数据时都得挪动字符串中的数据,导致内存占用时间过长。所以非必要就不要使用insert函数。

一些常用的重载

int main()
{
	string s1("hello world");
	string s2("xxx");
	
	s1.insert(s1.begin(), 1, '3');
	cout << s1 << endl;

	s1.insert(0, 1, 'x');
	cout << s1 << endl;

	return 0;
} 

int main()
{
	string s1("hello world");
	string s2("xxx");

	s1.insert(0, s2);
	cout << s1 << endl;

	s1.insert(5, s2, 2, 4);
	cout << s1 << endl;


	s1.insert(5, "p");
	cout << s1 << endl;

	return 0;
} 

 注意:

  1. 在string尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c'三种的实现方式差不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串
  2. 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好

2.7 有关string类的迭代器的补充

2.7.1 string类迭代器的底层实现

typedef char* iterator;
typedef const char* const_iterator;

// 非const版本的迭代器
iterator begin()
{
	return _str;
}

iterator end()
{
	return _str + _size;
}

2.7.2 范围for循环与迭代器iterator的关系

int main()
{
	string s1("hello world");

	string::iterator it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;

	// 范围for循环实际上是傻瓜式替换为迭代器
	for (auto ch : s1)
	{
		cout << ch << " ";
	}
}

 

从结果上看,范围for循环的结果跟用迭代器iterator的结果是一样的。那是不是它们的底层实现逻辑也是一样的?

实际上是这样的,范围for循环实际上就是傻瓜式替换为迭代器。

只要将任意一个迭代器改名了,那范围for循环就使用不了了。 

typedef char* Iterator;
typedef const char* const_iterator;

// 非const版本的迭代器
Iterator begin()
{
	return _str;
}

Iterator end()
{
	return _str + _size;
}


http://www.niftyadmin.cn/n/5839648.html

相关文章

计算机网络一点事(24)

TCP可靠传输&#xff0c;流量控制 可靠传输&#xff1a;每字节对应一个序号 累计确认&#xff1a;收到ack则正确接收 返回ack推迟确认&#xff08;不超过0.5s&#xff09; 两种ack&#xff1a;专门确认&#xff08;只有首部无数据&#xff09; 捎带确认&#xff08;带数据…

三天急速通关JavaWeb基础知识:Day 3 依赖管理项目构建工具Maven

三天急速通关JavaWeb基础知识&#xff1a;Day 3 依赖管理项目构建工具Maven 0 文章说明1 介绍2 安装与配置2.1 安装2.2 手动配置 3 创建Maven工程4 Maven构建工程5 Maven依赖管理6 Maven工程Build构建配置7 Maven依赖传递与依赖冲突7.1 依赖传递7.1 依赖冲突 8 Maven工程继承和…

华为Ascend产品

文章目录 昇腾 AI服务器 昇腾 AI服务器 昇腾 AI服务器 - ZOMI Atlas 800 训练服务器 - 模型图

2025年01月27日Github流行趋势

项目名称&#xff1a;onlook项目地址url&#xff1a;https://github.com/onlook-dev/onlook项目语言&#xff1a;TypeScript历史star数&#xff1a;5340今日star数&#xff1a;211项目维护者&#xff1a;Kitenite, drfarrell, iNerdStack, abhiroopc84, apps/dependabot项目简介…

gcc和g++的区别以及明明函数有定义为何链接找不到

初级代码游戏的专栏介绍与文章目录-CSDN博客 我的github&#xff1a;codetoys&#xff0c;所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。 这些代码大部分以Linux为目标但部分代码是纯C的&#xff0c;可以在任何平台上使用。 源码指引&#xff1a;github源…

DeepSeek-V3 与 DeepSeek R1 对比分析:技术与应用的全面解析

一、背景 在当今科技飞速发展的时代&#xff0c;深度学习技术如同一股强大的浪潮&#xff0c;席卷了自然语言处理&#xff08;NLP&#xff09;、计算机视觉&#xff08;CV&#xff09;以及多模态模型等众多领域。从智能语音助手到图像识别技术&#xff0c;从文本生成工具到多模…

Java 16进制 10进制 2进制数 相互的转换

在 Java 中&#xff0c;进行进制之间的转换时&#xff0c;除了功能的正确性外&#xff0c;效率和安全性也很重要。为了确保高效和相对安全的转换&#xff0c;我们通常需要考虑&#xff1a; 性能&#xff1a;使用内置的转换方法&#xff0c;如 Integer.toHexString()、Integer.…

MySQL知识点总结(十一)

如何查看 InnoDB 表所占用的实际存储空间大小&#xff1f; 可以查询 INFORMATION_SCHEMA.FILES 视图&#xff0c;其中包含有 InnoDB 表空间的磁盘大小信息。 也可以在文件系统直接查看 .ibd 文件的大小&#xff0c;但仅适用于单表文件包空间。 谈谈对 MySQL 数据库进行纵…