【c++ 学习笔记】初识 STL以及 string 类的学习

本文介绍了STL(StandardTemplateLibrary)的基本概念和历史,重点讲解了C++中的string类,包括不同构造函数的使用、对象的容量操作如size、length、max_size、capacity,以及修改操作如push_back、append、resize、reserve、insert和erase。此外,还详细阐述了迭代器的概念和使用,包括正向、反向和const迭代器。
摘要由CSDN通过智能技术生成

🙊 STL 介绍 🙊

💖 什么是STL

STL ( standard template libaray - 标准模板库 ):是 C++ 标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架。可以认为 C++ 标准库有两个部分,一个部分是 STL,一个部分是其他实现,比如 IO 流属于标准库但不是 STL。

💖 STL的版本

  1. 原始版本
    Alexander Stepanov、Meng Lee 在惠普实验室完成的原始版本,本着开源精神,他们声明允许任何人任意运用、拷贝、修改、传播、商业使用这些代码,无需付费。唯一的条件就是也需要向原始版本一样做开源使用。 HP 版本–所有STL实现版本的始祖。
  2. P. J. 版本
    由P. J. Plauger开发,继承自HP版本,被Windows Visual C++采用,不能公开或修改,缺陷:可读性比较低,符号命名比较怪异。
  3. RW版本
    由Rouge Wage公司开发,继承自HP版本,被C+ + Builder 采用,不能公开或修改,可读性一般。
  4. SGI版本
    由Silicon Graphics Computer Systems,Inc公司开发,继承自HP版 本。被GCC(Linux)采用,可移植性好,可公开、修改甚至贩卖,从命名风格和编程 风格上看,阅读性非常高。我们后面学习STL要阅读部分源代码,主要参考的就是这个版本。

💖 STL的六大组件

在这里插入图片描述

🙊 string 类 🙊

💖 C语言中的字符串

string 类是各种语言最常见的类之一,程序需要存储除了基础类型外更复杂的信息。C 语言中,字符串是以 ‘\0’ 结尾的一些字符的集合,为了操作方便,C 标准库中提供了一些 str 系列的库函数,但是这些库函数与字符串是分离开的,不太符合 OOP 的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。为了更好的管理字符串,C++ 中提供了一个 string 类用来管理字符串。

💖 标准库中的 string 类

string 类有相当多的函数接口,我们无法将每个函数接口的功能都能记得清清楚楚。但对于一些非常常用的函数接口,需要记住函数原型、功能以及实现的过程。
string 类是表示字符串的字符串类。
string 类是 basic_string 模板类的一个实例,它使用 char 来实例化 basic_string 模板类,并用 char_traitsallocator 作为 basic_string 的默认参数。

问题:

为什么会将 string 设置成模板呢?

因为这里涉及了编码问题。编码是信息从一种形式或格式转换为另一种形式的过程(映射关系),也称为计算机编程语言的代码简称编码。如 ASCII 码(美国信息交换标准代码)可以将数字和英文字母、符号进行对应。

在这里插入图片描述
随着时代的发展各个国家也需要这种映射关系来表示自己国家的文字,ASCII 就不能满足需求,因此提出了万国码。

在这里插入图片描述
而中国也有自己编码方式即 GBK 编码。

在这里插入图片描述
string 是表示字符串的字符串类,该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作 string 的常规操作。

💖 string 类的构造函数

💖 string 类对象的常见构造

string 类对象的常见构造如下:

在这里插入图片描述

💖 string()

使用 string 的无参构造函数必须包含头文件 string,然后定义一个对象。

#include <iostream>
#include <string>
using namespace std;

int main()
{
	//可以调用无参
	string s1;
	return 0;
}

该构造函数是无参的构造函数,其构造出来的对象的 size0capacity 的大小取决于编译器是如何实现 string 类的。
在这里插入图片描述

💖 string(const char*)

该构造函数是用常量字符串去构造一个对象出来,然后依次访问每个字符,再将字符 +1 输出打印。

#include <iostream>
#include <string>
using namespace std;

int main()
{
	//可以用常量字符串初始化
	string s2("Hello World");
	//遍历string
	for (size_t i = 0; i < s2.size(); ++i)
	{
		s2[i]++;
	}
	cout << s2 << endl;
	return 0;

}

想访问每个字符再进行 +1 打印可以使用 operator[ ],如果是 const 对象,返回 const char&,如果是普通对象返回普通对象引用。

在这里插入图片描述
所以可以像访问数组的方式访问字符串并进行打印,因为重载了流插入和流提取,所以可以整体打印对象。可以用以下两种方式构造对象:

int main()
{
	//可以用常量字符串初始化
	string s2("Hello World");
	string s3 = "Hello World";
	return 0;
}

因为 s3 发生了类型转换,单参数的构造函数直接赋值的时候支持隐式的类型转换,这里的 const char* 转换成了 string,过程是先生成一个临时对象,再用临时对象去拷贝构造对象s3,但是这个过程会被编译器优化成 string s3(“hello world”)

如果不想要类型转换的现象发生,可以加一个 explicit 进行修饰。

💖 string (const string& str, size_t pos, size_t len = npos)

该函数是用对象 strpos 位置起的的 len 个字符来创建对象。注: nposstring 的静态 const size_t 成员变量,其值为 - 1,是非常大的一个正数。如果 len 很大,就会用 pos 位置到末尾位置之间的字符来创建对象。示例代码如下:

int main()
{
	string s1("Hello World");
	string s2(s1, 6, 3);
	cout << s2 << endl;
	return 0;
}

取对象 s16 个位置之后的 3 个数对 s2 进行初始化。如果取的字符数太多了,就取对象 s1 的全部内容。如果第三个参数不给,就取 nposstring 类里面 const 修饰的一个静态成员变量,其值为 -1,又 pos 是无符号整型,所以这里应该是整型的最大值。

在这里插入图片描述
所以这里就会用 pos 位置到末尾位置之间的字符来创建对象。

💖 string (const char* s, size_t n)

这个构造函数是用字符串 s 的前 n 个字符来创建一个 string 类对象。代码如下:

int main()
{
	string s3("Hello World", 5);
	cout << s3 << endl;
	return 0;
}

打印结果如下:

在这里插入图片描述

💖 string(size_t n, char c)

这个构造函数是用 n 个 字符来创建一个对象。代码如下:

int main()
{
	string s4(5, '#');
	cout << s4 << endl;
	return 0;
}

打印结果如下:
在这里插入图片描述

💖 string 类的拷贝构造和赋值

string 类支持对象赋值、字符串赋值和字符赋值。
在这里插入图片描述

💖 string 类对象的容量操作

💖 size / length

sizelength 都是返回字符串的有效字符长度,功能一样的。只有 string 类有 length 的函数,而其它容器只有 size 的函数接口。看如下代码:

int main()
{
	string s1("Hello World");
	cout << s1.size() << endl;
	cout << s1.length() << endl;
	return 0;
}

运行结果如下:

在这里插入图片描述
我们发现 sizelength 运行结果都是相同的,那么二者有什么区别?

因为 string 是在 C++ 标准库中产生的,不属于 STL,最开始函数接口的名字就叫 length,随着 STL 的出现,为了和标准库中的 length 区分,STL 增加了 size 函数接口。

💖 max_size

max_size 函数接口返回的是 string 类对象能存储有效字符的最多个数,该大小取决于编译器的实现。

int main()
{
	string s1("Hello World");
	cout << s1.max_size() << endl;
	return 0;
}

运行结果如下:
在这里插入图片描述

💖 capacity

capacity 是返回 string 类对象的容量大小的函数接口。代码如下:

int main()
{
	string s1("Hello World");
	cout << s1.capacity() << endl;
	return 0;
}

运行结果如下:
在这里插入图片描述

💖 string 类对象的修改操作

💖 push_back

可以使用 push_back 在字符串后尾插字符,代码如下:

int main()
{
	string s1("Hello World");
	s1.push_back(' ');
	s1.push_back('!');
	cout << s1 << endl;
	return 0;
}

运行结果如下:
在这里插入图片描述

💖 append

如果想加入一个字符串,可以使用 append 函数。代码如下:

int main()
{
	string s1("Hello World");
	s1.append("! Hello World !");
	cout << s1 << endl;
	return 0;
}

运行结果如下:
在这里插入图片描述

💖 operator +=

字符串重载了一个 += 用来进行字符和字符串的添加。代码如下:

int main()
{
	string s1("Hello World");
	s1 += " Hello World";
	s1 += '!';
	cout << s1 << endl;
	return 0;
}

运行结果如下:

在这里插入图片描述
注意 += 只是进行了封装,它的底层还是调用的 appendpush_back 这些函数。

💖 resize 和 reserve

resize 是修改 string 类的有效字符的个数 size 的,而 reserve 是修改 string 类对象的容量。下面先介绍以下 string 类的扩容机制。看以下代码:

int main()
{
	string s;
	size_t size = s.capacity();
	cout << "making s grow:\n";
	cout << "capacity changed: " << size << endl;
	for (int i = 0; i < 100; ++i)
	{
		s.push_back('a');
		if (size != s.capacity())
		{
			size = s.capacity();
			cout << "capacity changed: " << size << '\n';
		}
	}
}

运行结果如下,可以看到 capacity1.5 倍的扩容:
在这里插入图片描述>注: g++ 编译器是 2 倍扩容。
reserve 的作用就是提前开辟空间,这里使用 reserve 来申请空间:

int main()
{
	string s;
	s.reserve(100);
	size_t size = s.capacity();
	cout << "making s grow:\n";
	cout << "capacity changed: " << size << endl;
	for (int i = 0; i < 100; ++i)
	{
		s.push_back('a');
		if (size != s.capacity())
		{
			size = s.capacity();
			cout << "capacity changed: " << size << '\n';
		}
	}
}

虽然这里申请了 100 个字节的容量,但是因为对其等原因实际开辟比 100 大的空间。运行结果如下:

在这里插入图片描述
reserve 是开空间,resize 可以开空间同时初始化,代码如下:

int main()
{
	string s1("Hello World");
	s1.reserve(100);
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;

	string s2("Hello World");
	s2.resize(100);
	cout << s2.size() << endl;
	cout << s2.capacity() << endl;
	return 0;
}

运行结果如下:
在这里插入图片描述
可以看到 resize 不仅改变了 capacity,而且改变了 size。改变之后填的是缺省值,这里 char 的缺省值是 \0。如果不想填 \0 还可以写成 s2.resize(100,‘x’); 来填成自己想写的值。也可以认为 resize扩容 + 初始化,而 reserve 仅仅是扩容

💖 insert

使用 insert 可以在头部或者中间插入字符。代码如下:

int main()
{
	string s1("world");
	s1.insert(0, "hello");
	cout << s1 << endl;

	s1.insert(s1.begin()+5, ' ');
	cout << s1 << endl;

	return 0;
}

因为插入数据必然会导致数据的挪动,这里不推荐经常使用 insert

💖 erase

示例代码如下:

int main()
{
	string s1("hello world");
	cout << s1 << endl;
	//从第5个位置删除一个字符
	s1.erase(5, 1);
	cout << s1 << endl;
	return 0;
}

运行结果如下:

在这里插入图片描述
erase 也不推荐使用,因为也是需要挪动数据,效率低下。

💖 find

在这里插入图片描述>使用 find 查找文件后缀名,代码如下:

int main()
{
	string file("string.cpp");
	size_t pos = file.find('.');
	if (pos != string::npos)
	{
		string suffix = file.substr(pos, file.size() - pos);
		cout << suffix << endl;
	}
	return 0;
}

运行结果如下:

在这里插入图片描述

💖 迭代器的使用

💖 什么是迭代器(iterator)

迭代器iterator)是一种可以遍历容器元素的数据类型。迭代器是一个变量,相当于容器和操纵容器的算法之间的中介。C++ 更趋向于使用迭代器而不是数组下标操作,因为标准库为每一种标准容器(如 vectormaplist 等)定义了一种迭代器类型,而只有少数容器(如 vector)支持数组下标操作访问容器元素。可以通过迭代器指向你想访问容器的元素地址,通过 *x 打印出元素值。这和我们所熟知的指针极其类似。
C 语言有指针,指针用起来十分灵活高效,C++ 语言有迭代器,迭代器相对于指针而言功能更为丰富。

💖 迭代器(iterator)的使用

容器都有成员 beginend,其中 begin 成员复制返回指向第一个元素的迭代器(用 *迭代器打印出元素值),而 end 成员返回指向容器尾元素的下一个位置的迭代器,它是一个不存在的元素位置。可以使用迭代器访问 string,首先看下面一段代码:

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

	for (auto ch : s1)
	{
		cout << ch << " ";
	}
	cout << endl;

	return 0;
}

图示如下:

在这里插入图片描述也可以使用范围 for 访问 string。

int main()
{ 
	string s1("hello world");
	string::iterator it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	return 0;
}

注意范围 for 的底层是迭代器。

💖 反向迭代器

下面是反向迭代器的代码:

int main()
{
	string s1("Hello World");
	string::iterator it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	string::reverse_iterator rit = s1.rbegin();
	while (rit != s1.rend())
	{
		cout << *rit << " ";
		++rit;
	}
	return 0;
}

运行结果如下:

在这里插入图片描述

💖 const 迭代器

看下面一段代码:

void Func(const string& s)
{
	string::iterator it = s.begin();
	while (it != s.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
}

int main()
{
	string s1("Hello World");
	Func(s1);
	return 0;
}

这段代码并不能编译通过,这里不能将 s.begin() 赋值给 it,因为 begin 函数有两个构成函数重载的版本,一个普通对象调用 begin 返回普通的迭代器,一个 const 对象调用 begin 应该返回一个 const 迭代器。

在这里插入图片描述
因为普通对象可以进行读操作和写操作,而 const 对象不允许修改,所以上面代码只能使用 const 迭代器,只能读容器的数据,不能写。而普通迭代器可以遍历和读写容器的数据。

void Func(const string& s)
{	//const迭代器
	string::const_iterator it = s.begin();
	while (it != s.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
}

int main()
{
	string s1("Hello World");
	//普通正向迭代器
	string::iterator it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	//普通反向迭代器
	string::reverse_iterator rit = s1.rbegin();
	while (rit != s1.rend())
	{
		cout << *rit << " ";
		++rit;
	}
	return 0;
}

当然以上代码可以用 auto 进行自动推导,可以进一步简化:

int main()
{
	string s1("Hello World");
	//普通正向迭代器
	auto it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	return 0;
}

注意:
这里 auto 的类型由右边表达式返回对象的类型决定的。这里 begin 返回值类型为 iterator

💖 练习题

练习一:
在这里插入图片描述在这里插入图片描述思路:

左边和右边同时寻找英文字母进行交换,代码如下:

class Solution
{
public:
    bool isLetter(char ch)
    {
        if(ch >= 'a' && ch <= 'z')
        {
            return true;
        }
        if(ch >= 'A' && ch <= 'Z')
        {
            return true;
        }
        return false;
    }

    string reverseOnlyLetters(string s){
    size_t begin = 0, end = s.size()-1;
    while(begin < end)
    {
        while(begin < end && !isLetter(s[begin]))
        {
            ++begin;
        }
        while(begin < end && !isLetter(s[end]))
        {
            --end;
        }
        swap(s[begin],s[end]);
        ++begin;
        --end;
    }
    return s;
	}
};

练习二:
在这里插入图片描述
在这里插入图片描述
思路:
用计数排序统计出现的次数,因为这里只包含小写字母,再将字母进行映射。代码如下:

class Solution {
public:
    int firstUniqChar(string s) {
        int countA[26] = {0};
        for(auto ch:s)
        {
            countA[ch - 'a']++;
        }
        for(int i = 0;i < s.size(); ++i)
        {
            if(countA[s[i] - 'a'] == 1)
            return i;
        }
        return -1;
    }
};

练习三:

在这里插入图片描述
在这里插入图片描述

#include <iostream>
using namespace std;

int main() {
   string str;
   //cin >> str;
   getline(cin,str);
   size_t pos = str.rfind(' ');
   if(pos != string::npos)
   {
        cout << str.size() - pos -1 << endl;
   }
   else 
   {
    cout << str.size();
   }
   
}
// 64 位输出请用 printf("%lld")

练习四:

在这里插入图片描述
在这里插入图片描述
代码如下:

class Solution {
public:
    string addStrings(string num1, string num2) {
        int end1 = num1.size()-1, end2 = num2.size()-1;
        //next 为进位
        int next = 0;
        string strRet;
        strRet.reserve(num1.size()>num2.size() ? num1.size()+1 : num2.size()+1);
        while(end1 >= 0 || end2 >= 0)
        {
            //如果字符串一走完了,就会越界,这里先判断一下
            int val1 = 0;
            if(end1 >= 0)
            val1 = num1[end1] - '0';

            int val2 = 0;
            if(end2 >= 0)
            val2 = num2[end2] - '0';

            int ret = val1 + val2 + next;
            if(ret > 9)
            {
                ret -= 10;
                next = 1;
            }
            else
            {
                next = 0;
            }
            //头插
            //strRet.insert(0,1,ret+'0');
            //尾插
            strRet += ('0'+ret);
            --end1;
            --end2;
        }
        //如果两个字符串都结束了最后还有进位,就再头插一个1
        if(next == 1)
        strRet += '1';
        reverse(strRet.begin(),strRet.end());
        return strRet;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值