36 STL简介 && string类

目录

 一、STL简介

(一)什么是STL

(二)STL的版本

(三)STL的六大组件

二、string类

(一)C语言中的字符串 vs C++string类

1、C语言中的字符串

2、C++string类

(二)string类的常用接口

1、string类对象的常见构造

2、string类对象的容量操作

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

(1)string类对象的访问

(2)string类对象的遍历操作:

①、正向迭代器 iterator

②、逆向迭代器 reverse_iterator

③、迭代器小结

(①) 迭代器通用写法总结

(②) 类型名称的简化:auto关键字

④、范围for

(三)string类的高级操作

1、string类对象的插入与删除操作

 (1)插入操作

(2)删除操作

① npos

(3)小结

2、string类对象的获取、查找和截取操作

(四)string类的非成员函数


 一、STL简介

(一)什么是STL

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

(二)STL的版本

        ① 原始版本:

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

        ② P. J. 版本:

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

        ③ SGI版本:

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

(三)STL的六大组件

        以容器为核心,其他组件围绕着容器进行使用。

        ① 容器(Containers):容器是用来存储和管理数据的对象(提供各种数据结构)。

        ② 算法(Algorithms):STL 中的算法是一些通用的函数模板用于对容器中的数据进行各种操作,如排序(sort())、查找(find())、交换(swap())等操作。

        ③ 迭代器(Iterators):迭代器是一种对象,它提供了一种统一的方式来遍历容器中的元素类似于指针,但具有更高级的功能。可以把迭代器想象成一个 “导航器”,它知道如何在容器中从一个元素移动到下一个元素。

        ④ 函数对象(Function objects):也称为仿函数(Functors),是一种可以像函数一样被调用的对象。它们是实现了operator()(函数调用运算符)的类的实例。函数对象可以携带状态,这使得它们比普通函数更灵活。

        ⑤ 适配器(Adapters):适配器是一种机制,用于将一个类的接口转换为另一个接口,使得原本不兼容的类可以一起工作(对容器与算法进行调整)。

        ⑥ 空间配置器(Allocators):空间配置器负责管理内存的分配和回收。它们为容器提供了一种灵活的方式来获取和释放内存,使得容器可以更好地控制内存的使用。

二、string类

        string-管理字符数组的类。

        string的含义:一个字符char为8个比特位(1个字节),相当于每个字符char都是8个比特位(1个字节)的字符数组。另外,string与编码有关,因为编码本质就是字符串,string兼容ascii编码与utf-8编码

(一)C语言中的字符串 vs C++string类

1、C语言中的字符串

        C语言中,字符串是以'\0'结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,如: strlen()strcpy() 和 strcat() 等;

        但是这些库函数与字符串是分离开的,不太符合OOP的思想面向对象编程【Object-Oriented Programming,OOP】是一种编程范式,它围绕对象和类的概念来组织程序结构);

        而且底层空间(字符数组长度)需要用户自己管理,稍不留神可能还会越界访问。例如:

int main() {
    char str1[20] = "I like";
    char str2[20] = " pizza!";

    // 字符串连接
    strcat(str1, str2);
    printf("%s\n", str1);  // 输出:I like pizza!

    return 0;
}

2、C++string

        C++ 中的 string 类是一个封装好的类,它内部对字符串的存储进行了优化和管理。它可能使用动态内存分配等方式来存储字符串,用户不需要关心具体的存储细节(自己管理字符数组长度)。例如:

int main()
{
	string s1("I like");
	string s2(" pizza!");

	//字符串连接
	s1 += s2;
	cout << s1 << endl; // 输出:I like pizza!

	return 0;
}

(二)string类的常用接口

1、string类对象的常见构造

函数名称功能说明
string() (重点)
构造空的 string 类对象,即 空字符串
string(const char* s) (重点)
字符串S 来构造  string类对象
string(size_t n, char c)
构造包含 n个字符c string类对象
string(const string&s) (重点)
拷贝构造 函数

        使用例如下:

int main()
{
	string s1;					// 构造空的string类对象s1
	string s2("I like pizza!"); // 用C格式字符串构造string类对象s2
	string s3(5, 'x');			// 使用5个x字符构造string类对象s3
	string s4(s2);				// 拷贝构造s4

	cout << s1 << endl; // 输出:空
	cout << s2 << endl; // 输出:I like pizza!
	cout << s3 << endl; // 输出:xxxxx
	cout << s4 << endl; // 输出:I like pizza!

	return 0;
}

2、string类对象的容量操作

函数名称功能说明
size()(重点)
返回字符串有效字符长度(不包括 '\0')
length()
返回字符串有效字符长度, 与 size() 等价
capacity()(重点)
返回总分配空间大小
empty()
检测字符串是否为空串, 是空返回true ,不空返回false
clear()(重点)
清空有效字符
reserve()(重点)
为字符串预留空间,不改变有效字符个数(用于改善代码效率
resize()
改变字符串的 有效长度 ,若空间增大则用默认字符填充

        使用例如下:

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

int main()
{
	string s1;					// 构造空的string类对象s1
	string s2("I like pizza!"); // 用C格式字符串构造string类对象s2
	string s3(5, 'x');			// 使用5个x字符构造string类对象s3
	string s4(s2);				// 拷贝构造s4

	cout << s2.size() << endl; // 输出:13
	cout << s2.length() << endl; // 输出:13
	cout << s2.capacity() << endl; // 输出:15
	cout << s2.empty() << endl; // 输出:0(false表示不为空)
	s2.clear();
	cout << s2 << endl; // 输出:空

	cout << s4.capacity() << endl; // 输出:15
	s4.reserve(50);
	cout << s4.capacity() << endl; // 输出:63

	cout << s4 << endl; // 输出:I like pizza!
	s4.resize(6); //需要为'\0'预留一位空间
	cout << s4 << endl; // 输出:I like 
	cout << s4.size() << endl; // 输出:6
    s4.resize(20);
    cout << s4 << endl; // 输出:I like 
    cout << s4.size() << endl; // 输出:20

	return 0;
}

        注意:

       size() 与 length() 方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致一般情况下基本都是用size()

int main()
{
	string s5("pizza");

	cout << s5.size() << endl; // 输出:5
	cout << s5.length() << endl; // 输出:5
	cout << endl;

    return 0;
}

        ② clear()只是将string中有效字符清空,不改变底层空间大小

int main()
{
	string s5("pizza");
	string s6(s5);

    cout << s6.capacity() << endl; // 输出:15
	s6.clear();
	cout << s6.size() << endl; // 输出:0
	cout << s6.capacity() << endl; // 输出:15
	cout << endl;

    return 0;
}

        ③ resize(size_t n) 与 resize(size_t n, char c) 都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用 '\0' 来填充多出的元素空间resize(size_t n, char c)用字符c来填充多出的元素空间resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变

int main()
{
    string s5("pizza");
    cout << s5.size() << endl; // 输出:5
    cout << s5.capacity() << endl; // 输出:15

    s5.resize(10);

    cout << s5.size() << endl; // 输出:10
    cout << s5.capacity() << endl; // 输出:15

    cout << s5 << endl; // 输出:pizza

    return 0;
}

int main()
{
    cout << s5.size() << endl; // 输出:5
    cout << s5.capacity() << endl; // 输出:15

    s5.resize(20, 'x');

    cout << s5.size() << endl; // 输出:20
    cout << s5.capacity() << endl; // 输出:31
    cout << s5 << endl; // 输出:pizzaxxxxxxxxxxxxxxx

    return 0;
}

        ④ reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小

int main()
{
	string s5("pizza");

	cout << s5 << endl; // 输出:pizza
    cout << s5.size() << endl; // 输出:5
	cout << s5.capacity() << endl; // 输出:15

	s7.reserve(3);

	cout << s5 << endl; // 输出:pizza
    cout << s5.size() << endl; // 输出:5
	cout << s5.capacity() << endl; // 输出:15

	return 0;
}

        ⑤ string的扩容在vs2022中,除了第一次扩容,其他的基本以1.5倍扩容;string第一次储存的有效字符若小于15个,就会存储在一个buff的数组里面,等超过了这个数组的存储范围才会放入动态申请的空间中,并且进行二倍的扩容。(linux是二倍扩容)

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

函数名称
功能说明
operator[ ](重点)
operator是string类的对象名 返回或更改 [ 下标 ] 位置的字符
at( )

operator[ ]的作用一样,区别:at 会进行边界检查。如果传入的索引超出了字符串的有效范围出错了会抛异常,而operator[ ]在这种情况下会导致未定义行为。

begin() + end()(重点)
正向遍历:
begin() 获取第一个字符的 迭代器 + end() 获取最后一个字符下一个位置的 迭代器
rbegin + rend(重点)
逆向遍历:
rbegin() 获取最后一个字符的 迭代器 + rend() 获取第一个字符前一个位置的 迭代器
范围for
C++11支持更简洁的范围for的新遍历方式, 只适用于容器与数组(本质是迭代器)
(1)string类对象的访问

        【operator[ ] 】与 【string对象.at() 】使用例:

int main()
{
	string s1("I like pizza!");

    for (size_t i = 0; i < s1.size(); i++)
	{
		cout << s1[i]; 
	} 
	// 输出:I like pizza!


	for (size_t i = 0; i < s1.size(); i++)
	{
		cout << s1.at(i);
	} 
	// 输出:I like pizza!

	return 0;
}
        注意:operator[]有const版本与普通版本的接口,const版本不能写,而普通版本能读写。

(2)string类对象的遍历操作:

        【 begin() + end() 】与 【 rbegin() + rend() 】以及 【 范围for 】,这些方法都与六大组件中的迭代器相关,需要先学习一下迭代器。

①、正向迭代器 iterator

        在 C++ 中,string类里面有个iterator的自定义类型:string::iterator使用时要指定类域,不然会报语法错误,它是一种迭代器类型,用于正向遍历string类型的对象迭代器可以看作是一种广义的指针,它提供了一种统一的方式来访问容器(这里是字符串)中的元素,而不必关心容器的具体实现细节

        【 begin() + end() 就是string对象获取正向迭代器的方法:

  • begin() 获取第一个字符的正向迭代器
  • end() 获取最后一个字符下一个位置的正向迭代器

        使用例:

int main()
{
	string s1("I like pizza!");
	string::iterator i1 = s1.begin();// 获取字符'I'的迭代器
	string::iterator i2 = s1.end();// 获取字符'!'的下一个字符的迭代器
	while (i1 != i2) //开始和结尾的迭代器不相等时就循环
	{
		cout << *i1;// 使用方法与指针一样,需要*解引用获得字符
		i1++; // 像指针一样进行指针++运算到下一个迭代器
	}
	// 输出:I like pizza!

	return 0;
}

②、逆向迭代器 reverse_iterator

        reverse_iterator是 C++ 中用于反向遍历string对象的迭代器类型。它提供了一种方便的方式来从字符串的末尾开始朝着字符串开头的方向逐个访问字符。

        【 rbegin() + rend() 就是string对象获取逆向迭代器的方法:

  • rbegin() 获取最后一个字符逆向迭代器
  • rend() 获取第一个字符前一个位置的逆向迭代器

        使用例:

int main()
{
	string s1("I like pizza!");
	string::reverse_iterator i1 = s1.rbegin();// 获取字符'!'的迭代器
	string::reverse_iterator i2 = s1.rend();// 获取字符'I'的前一个字符的迭代器
	while (i1 != i2) //开始和结尾的迭代器不相等时就循环
	{
		cout << *i1;// 使用方法与指针一样,需要*解引用获得字符
		i1++; // 这里的指针是先左边走的
	}
	// 输出:!azzip ekil I

	return 0;
}

        注意 i1++ 是向左边走的,因为是逆向遍历。

③、const迭代器 const_iterator

        const对象无法使用普通迭代器,只能使用const迭代器。

        使用例:

const string s2(s1); 
string::const_iterator it1 = s2.begin();// 迭代器的通用写法

        特点:不能对内容进行修改

        也有逆向的迭代器:

const string s3(s1); 
string::const_reverse_iterator it1 = s3.rbegin();// 迭代器的通用写法

④、迭代器小结
(①) 迭代器通用写法总结

        正向迭代器通用写法:

//先定义某个容器的对象:
类型 对象;
类型::iterator 迭代器名 = 对象名.begin();//获取第一个元素的迭代器
while(迭代器名 != 对象名.end())//【第一个元素的迭代器】与【最后一个元素的下一个位置的迭代器】对比
{
    cout << *迭代器名 << " "; // 遍历操作
    迭代器名++;
}
cout << endl;

        进行遍历时建议写成while循环。

        逆向迭代器写法只需改动迭代器的名字以及调用的方法名即可。

迭代器与使用operator[]的区别:operator[]不是通用的方法,只适用于string和vector(底层为连续的空间),不再适用于更后面的容器;而迭代器是所有容器都使用的遍历方法。所以,在面对不同容器的时候,都可以使用迭代器,迭代器不管底层怎么实现的,都能进行访问。

(②) 类型名称的简化:auto关键字

        auto关键字会在定义变量时根据赋值操作符右边的值自动推导类型,可以极大简化写起来长的类型,比如迭代器类型:string::iterator,可用auto关键字代替。使用例:

int a = 0;
auto b = i; // auto为int类型

string s1("love");
string::iterator it1 = s1.begin();// 正向迭代器通用写法
auto it1 = s1.begin(); // 类型写法简化,auto为迭代器类型

        auto关键字并不会识别引用类型只能推导被引用的那个类型,想要成为引用类型,还是要手动在auto后面加&这样一来才会识别引用类型,这样的话就必须给初始值了。使用例:

int a = 5;
int& b = a;

auto c = b; 
// auto的类型为int,不加&的话只能识别被引用的类型

auto& d = b;
// auto的类型为int&,能识别引用类型

注意:

  • C++11不支持auto作函数参数,C++20开始支持C++11支持auto作返回值类型,但并不好,会对代码阅读造成极大不良影响,谨慎使用
  • 用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&。
  • 当在同一行使用auto声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量
  • auto不能直接用来声明数组

④、范围for

        对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环。for循环后的括号由冒号( : )分为两部分第一部分是范围内用于迭代的变量第二部分则表示被迭代的范围,自动迭代,自动取数据,自动判断结束。

        范围for可以作用到数组和容器对象上进行遍历。

        范围for的底层很简单,容器遍历实际就是替换为迭代器,这个从汇编层也可以看到。

        使用例:

void Test01()
{
	string s1("I like pizza!");
	for (char c : s1) //范围内迭代的变量是字符类型的c,在s1对象的范围内遍历
	{
		cout << c; 
	}// 输出:I like pizza!
}


int main()
{
	Test01();
	return 0;
}

        结合auto关键字进行使用:

void Test02()
{
	string s1("I like pizza!");
	for (auto c : s1)
	{
		cout << c;
	}// 输出:I like pizza!
}

int main()
{
	Test02();
	return 0;
}

        需要注意的是,在范围for中用于迭代的变量是从遍历对象中进行浅拷贝复制过来的,改变范围for中变量的值并不影响遍历对象中的值若想修改,则迭代的变量要使用引用&

void Test03()
{
	string s1("I like pizza!");

	for (auto c : s1)
	{
		cout << ++c;
	}// 输出:J!mjlf!qj{{b"
	cout << endl;
	cout << s1 << endl;// 输出:I like pizza!

	for (auto& c : s1)
	{
		cout << ++c;
	}// 输出:J!mjlf!qj{{b"
	cout << endl;
	cout << s1 << endl;// 输出:J!mjlf!qj{{b"
}

int main()
{
	Test03();
	return 0;
}

(三)string类的高级操作

1、string类对象的插入与删除操作

 (1)插入操作
函数名称功能说明
push_back(char c)
在字符串后尾插字符c
append()
在字符串后追加一个字符串
operator+= (重点)
在字符串后追加一个字符或字符串
insert()在字符串的指定下标位置插入字符或字符串
注意:
  • string 对象 s  尾部追加字符时, s.push_back(c) / s.append(1, c) / s += 'c' 三种的实现方式差不多, 一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串
  • string 操作时,如果能够大概预估到放多少字符, 可以先通过reserve把空间预留好,这样不用频繁扩容,提高代码效率

        使用例:

void Test04()
{
	string s1;
	s1.push_back('X');
	s1.append(" I like ");
	s1 += 'p';
	s1 += "pizza!";
	s1.insert(2, "hello ");
	cout << s1 << endl; // 输出:X hello I like ppizza!
}

int main()
{
	Test04();
	return 0;
}
(2)删除操作
函数名称功能说明
erase(pos,len)删除字符串中指定下标位置pos 开始 len 个字符

        注意: 当 len 不指定长度时,默认为npos,表示会删除到最后。

① npos
  • nposstring类中的一个静态成员常量。它是string类中用于表示 “未找到” 或 “不存在的位置” 的值。其类型为std::string::size_type,通常是一个无符号整数类型(如size_t)。通常它被定义为一个足够大的值,以表示超出字符串范围的位置。例如,在很多实现中,npos被定义为-1转换为size_type后的无符号值(42亿9千万...),适合表示 “不存在的位置”。
  • void Test07()
    {
    	string s1("I like pizza!");
    	if (s1.find("abc") == string::npos) 
        //类中的静态成员,不需要具体对象引用,
        //因为静态成员不是类中真正的成员,自然实例化的对象中也不会存在,而是在全局中,
        //限定类域即可。
    		cout << "字符串不存在" << endl; // 输出:字符串不存在
    }
    
    int main()
    {
    	Test07();
    	return 0;
    }

        删除操作使用例:

void Test05()
{
	string s1("I like pizza!");
	s1.erase(6, 5);
	cout << s1 << endl; // 输出:I likea!
	s1.erase(6);// 此时len为npos,意为把后面的都删除
	cout << s1 << endl; // 输出:I like
}

int main()
{
	Test05();
	return 0;
}
(3)小结

        string的insert插入和erase删除相对而言是比较少使用的,因为要挪动数据,效率比较低

2、string类对象的获取查找截取操作

函数名称功能说明

c_str() (重点)

返回 【调用该函数的string对象】的 C格式字符串
find
 (重点)
从【 string对象的pos下标位置(默认从头开始找,pos = 0) 】开始往后找【 指定的字符或字符串 】, 返回其首次出现的下标位置,找不到则返回 string::npos
rfind
从【string对象的pos下标位置(默认从末尾开始找,pos = npos)】开始往后找【指定的字符或字符串】,返回最后一次出现子串或字符的下标位置
substr
在string对象中 从pos位置开始,截取n个字符,然后将其返回
该方法常用在处理文件路径或URL。

        使用例: 

void Test06()
{
	string s1("I like pizza!");
	cout << s1.c_str() << endl; //输出:I like pizza!

	cout << s1.find("like") << endl; //输出:2
	cout << s1.rfind("I") << endl; //输出:0

	cout << s1.substr(2, 4) << endl; //输出:like
}

int main()
{
	Test06();
	return 0;
}

c_str接口是为了与C进行兼容写出来的,返回的是const char*类型的字符串。

(四)string类的非成员函数

函数名称功能声明
operator+
加法运算符的重载,实现两个字符或字符串的拼接
operator>> (重点)
输入运算符重载
operator<< (重点)
输出运算符重载
getline (重点)
从输入流(如std::cin或者文件输入流std::ifstream)中读取一行字符且不会受到空格影响,并将其存储到一个std::string对象中,直到遇到换行符\n
relational operators (重点)
比较大小

注意:

  • operator+尽量少用因为传值返回,导致深拷贝效率低
  • 流输入规定【换行】和【空格】是输入缓冲区的分割,若要输入英文句子,最后只会得到句子的第一个英文单词,需要使用getline函数来获得具有多个空格的字符串
  • getline函数是被包含在<string>头文件中的,使用时要包涵该头文件。

        使用例:

void Test08()
{
	string s1("I like pizza!");
	cout << s1 + '!' << endl; // 输出:I like pizza!!
	cout << s1 + "!!!" << endl; // 输出:I like pizza!!!!

	cin >> s1; // 输入:Hello world
	cout << s1 << endl; // 输出:Hello,输入会因为空格而终止

	string s2;
	std::getline(cin, s2);// 输入:Hello world
	cout << s2 << endl; // 输出:Hello world

	bool a = s2 > s1 ;// 输出:1
	bool b = s2 < s1 ;// 输出:0
	bool c = s2 == s1 ;// 输出:0
	bool d = s2 != s1 ;// 输出:1
	cout << a << " " << b << " " << c << " " << d << " " << endl;

}

int main()
{
	Test08();
	return 0;
}

一个注意点: const char* p的地址不能直接被打印,如果直接打印的话会打印出字符,想要打印出真正的地址,就要强转为(void*)打印。

const char* p1 = "xxxxx";
cout << p1 << endl; //打印xxxxx
cout << (void*)p1 << endl; 打印地址

        以上内容仅供分享,若有错误,请多指正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值