C++学习(九)string类和标准模板库

1.string类

string 类是由头文件string支持(头文件string.h和从cstring支持对c风格的字符串进行操纵的c库字符串函数,不支持string类)。

1.1 构造字符串

string(cost char *s);//将string对象初始化为s指向的NBTS

string(size_type n, char c);//包含n个元素的string对象,每个元素都被初始化c

string(const string&str);//复制构造函数

string();//创建默认的string对象,长度为0

string(const char*s, size_type n);//用string对象初始化s指向的前n个字符

template<class Iter>
string(Iter begin, Iter end);//string对象初始化为[begin,end)内字符

string(const string &str, string size_type, pos = 0, size_type n = npos);//将string对象初始化为对象str从位置pos开始到结尾,或pos开始的n个字符

//C++11新增
string(string&&str)noexcept;//将一个string对象初始化string对象str,并修改str(移动构造函数)

string(initializer_list<char>il);//将一个string对象初始化为初始化列表il中的字符

1.2 string 类输入

//对于string对象有两种方式输入
char info[100];

string stuff;

cin >> stuff;//read  a word

getline(cin, stuff);//read a line,discard \n

//两个版本的getline()都有一个可选参数,用来指定哪个字符来确定边界

cin.getline(info, 100, ':');

getline(stuff, ':');
//自动调整大小的功能让string版本的getline()不需要指定读取多少个字符的数值参数

cin.getline(info, 100);

getline(cin, info);

1.3 使用字符串

(1)string对象的比较之compare
compare 成员函数有以下返回值:
小于 0 表示当前的字符串小;
等于 0 表示两个字符串相等;
大于 0 表示另一个字符串小。
例如:

string s1("hello"), s2("hello, world");
int n = s1.compare(s2);
n = s1.compare(1, 2, s2, 0, 3);  //比较s1的子串 (1,2) 和s2的子串 (0,3)

(2)string对象的子串之substr

substr 成员函数可以用于求子串 (n, m),原型如下:
string substr(int n = 0, int m = string::npos) const;
调用时,如果省略 m 或 m 超过了字符串的长度,则求出来的子串就是从下标 n 开始一直到字符串结束的部分。例如:

string s1 = "this is ok";
string s2 = s1.substr(2, 4);  // s2 = "is "
s2 = s1.substr(2);  //省略了m值,从2开始到结束 s2 = "is is ok"

(3)交换两个string对象的内容之swap

swap 成员函数可以交换两个 string 对象的内容。例如:

string s1("West”), s2("East");
s1.swap(s2);  // s1 = "East",s2 = "West"

(4)查找字符串之find

string 类有一些查找子串和字符的成员函数,它们的返回值都是子串或字符在 string 对象字符串中的位置(即下标)。如果查不到,则返回 string::npos。string: :npos 是在 string 类中定义的一个静态常量。这些函数如下:
find:从前往后查找子串或字符出现的位置。
rfind:从后往前查找子串或字符出现的位置。
find_first_of:从前往后查找何处出现另一个字符串中包含的字符。例如:
s1.find_first_of("abc");  //查找s1中第一次出现"abc"中任一字符的位置
find_last_of:从后往前查找何处出现另一个字符串中包含的字符。
find_first_not_of:从前往后查找何处出现另一个字符串中没有包含的字符。
find_last_not_of:从后往前查找何处出现另一个字符串中没有包含的字符。

下面是 string 类的查找成员函数的示例程序。

#include <iostream>
#include <string>
using namespace std;
int main()
{
    string s1("Source Code");
    int n;
    if ((n = s1.find('u')) != string::npos) //查找 u 出现的位置
        cout << "1) " << n << "," << s1.substr(n) << endl;
    //输出 l)2,urce Code
    if ((n = s1.find("Source", 3)) == string::npos)
        //从下标3开始查找"Source",找不到
        cout << "2) " << "Not Found" << endl;  //输出 2) Not Found
    if ((n = s1.find("Co")) != string::npos)
        //查找子串"Co"。能找到,返回"Co"的位置
        cout << "3) " << n << ", " << s1.substr(n) << endl;
    //输出 3) 7, Code
    if ((n = s1.find_first_of("ceo")) != string::npos)
        //查找第一次出现或 'c'、'e'或'o'的位置
        cout << "4) " << n << ", " << s1.substr(n) << endl;
    //输出 4) l, ource Code
    if ((n = s1.find_last_of('e')) != string::npos)
        //查找最后一个 'e' 的位置
        cout << "5) " << n << ", " << s1.substr(n) << endl;  //输出 5) 10, e
    if ((n = s1.find_first_not_of("eou", 1)) != string::npos)
        //从下标1开始查找第一次出现非 'e'、'o' 或 'u' 字符的位置
        cout << "6) " << n << ", " << s1.substr(n) << endl;
    //输出 6) 3, rce Code
    return 0;
}

(5)替换字符串之replace

replace 成员函数可以对 string 对象中的子串进行替换,返回值为对象自身的引用。例如:

	string s1("Real Steel");
	s1.replace(1, 3, "123456", 2, 4);  //用 "123456" 的子串(2,4) 替换 s1 的子串(1,3)
	cout << s1 << endl;  //输出 R3456 Steel
	int n = s2.find("lllll");  //查找子串 "00000" 的位置,n=2
	s2.replace(n, 5, "XXX");  //将子串(n,5)替换为"XXX"
	cout << s2 < < endl;  //输出 HaXXX Potter

(6)删除子串之erase

erase 成员函数可以删除 string 对象中的子串,返回值为对象自身的引用。例如:

string s1("Real Steel");
s1.erase(1, 3);  //删除子串(1, 3),此后 s1 = "R Steel"
s1.erase(5);  //删除下标5及其后面的所有字符,此后 s1 = "R Ste"

(7)插入子串之insert

insert 成员函数可以在 string 对象中插入另一个字符串,返回值为对象自身的引用。例如:

string s1("Limitless"), s2("00");
s1.insert(2, "123");  //在下标 2 处插入字符串"123",s1 = "Li123mitless"
s1.insert(3, s2);  //在下标 2 处插入 s2 , s1 = "Li10023mitless"
s1.insert(3, 5, 'X');  //在下标 3 处插入 5 个 'X',s1 = "Li1XXXXX0023mitless"

2 智能指针模板类

void remodel(std::string & str)
{
	std::string *ps=new std::string(str);//new分配内存
	...
	str=ps;
	return; 	


}

上面函数都分配堆中的内存,都从不回收,从而导师内存泄露。return前应加
delepe ps;

void remodel(std::string & str)
{
	std::string *ps=new std::string(str);//new分配内存
	...
	if(weird_thing())
		throw except();
	str=*ps;
	delete ps;
	return; 	
}

尽管使用了delete ,但是出现异常时,不被执行,将导致内存泄露。
针对上面的问题,我们引入智能指针。

2.1 智能指针

(关于智能指针,在学习五也涉及到)
auto_ptr、unique_ptr和shared_ptr都定义了类似指针的对象,可以将new获得的地址赋给对象,当智能指针过期时,其析构函数使用delete来释放内存。因此,如果将new返回的地址赋给对象,将无需记住稍后释放的内存:在智能指针过期时,这些内存将自动被释放。

要创建智能指针,必须包含头文件memory。
智能指针语法:

	auto_ptr<double>pd(new double);//new double 是new返回的指针,指向分配的内存块,它是构造函数auto_ptr<double>的参数
    unique_ptr<double>pdu(new double);
    shared_ptr<string>pss(new double);

上面的代码可改为:

#include<memory>
void remodel(std::string & str)
{
	std::auto_ptr<std::string>ps (new std::string(str));
	...
	if(weird_thing())
		throw except();
	str=*ps;
	//delete ps;
	return; 	
}

由上面也可注意到智能指针模板位于名称空间std中,每个智能指针都放在一个代码块中,离开代码块,指针将过期。

#include<iostream>
#include<string>
#include<memory>
class Report 
{
private:
	std::string str;
public:
	Report(const std::string s) :str(s)
	{
		std::cout << "object create!\n";
	}
	~Report()
	{
		std::cout << "object delete!\n";

}
void comment() const 
{
	std::cout << str << "\n";
}
};

int  main()
{
	{
		std::auto_ptr<Report>ps(new Report("using auto_ptr"));
		ps->comment();

}
{
	std::shared_ptr<Report>ps(new Report("using auto_ptr"));
	ps->comment();

}
{
	std::unique_ptr<Report>ps(new Report("using auto_ptr"));
	ps->comment();

}
system("pause");
return 0;
}

注意事项:
当两个指针指向同一个string对象时,这是不能接受的,因为程序将试图删除同一对象两次。要避免这问题,方法有多种。
(1)auto_ptr和unique_ptr采用的策略是建立所有权概念。对于特定的对象,只有一个智能指针可拥有它,这样只有拥有对象的智能指针的构造函数会删除对象,然后赋值操作转让出所有权。
(2)shared_ptr采用的策略是引用计数。即创建更高的智能指针,跟踪特定的对象的智能指针数。如: 赋值时,计数将加一,而指针过期时,计数将减一。仅当最后一个指针过期时,才调用delete。

3 标准模板库

STL就不用自己来创建模板了,这些模板都定义在标准模板库中,我们只需要学会怎么使用这些类模板来定义一个具体的类。
STL具有六大组件

  • 容器(containers)
  • 算法(algorithms)
  • 迭代器(iterators)
  • 函数对象(functors)
  • 适配器(adapters)
  • 分配器(allocators)

STL对定义的通用容器分三类:顺序性容器、关联式容器和容器适配器。
顺序性容器:vector、deque、list

关联性容器:set、multiset、map、multimap

容器适配器:stack、queue、

3.1 模板类vector

矢量(vector)/向量是一种顺序行容器。相当于数组,但其大小可以不预先指定,并且自动扩展。它可以像数组一样被操作,由于它的特性我们完全可以将vector 看作动态数组。其包含在vector头文件中。
在创建一个vector 后,它会自动在内存中分配一块连续的内存空间进行数据存储,初始的空间大小可以预先指定也可以由vector 默认指定。当存储的数据超过分配的空间时vector 会重新分配一块内存块,但这样的分配是很耗时的,在重新分配空间时它会做这样的动作:

首先,vector 会申请一块更大的内存块;
然后,将原来的数据拷贝到新的内存块中;
其次,销毁掉原内存块中的对象(调用对象的析构函数);
最后,将原来的内存空间释放掉。
当vector保存的数据量很大时,如果此时进行插入数据导致需要更大的空间来存放这些数据量,那么将会大大的影响程序运行的效率,所以我们应该合理的使用vector。
(1)vector 初始化

vector<T>v1;//默认初始化方式,内容为空
vector<T>v2(v1);
vector<T>v3(n);//n个数值为0
vector<T>v4(n,i);//v4中包含n个数值为i的 元素

(2)为vector声明迭代器
迭代器:它是一个广义的指针,也可以是一个对其执行类似指针的操作——如接触引用(operator++)的对象。通过迭代器,让stl能够为各种不同的容器类,提供统一的接口。

vector<double>::iterator pd;
vector<double> scores;
pd=scores.begin();
*pd=22.3;
++pd;

(3)vector常用函数

empty():判断向量是否为空,为空返回真,否则为假

begin():返回向量(数组)的首元素地址

end(): 返回向量(数组)的末元素的下一个元素的地址

clear():清空向量

front():返回得到向量的第一个元素的数据

back():返回得到向量的最后一个元素的数据

size():返回得到向量中元素的个数

push_back(数据):将数据插入到向量的尾部

pop_back():删除向量尾部的数据

.....

//例子

#include<iostream>
#include<vector>
using namespace std;
int main()
{
	vector<int>vec;

for (int i = 1; i <= 5; i++)
{
	vec.push_back(i);
}
for (int i = 0; i < vec.size(); i++)
	cout << vec[i] << endl;

for (vector<int>::iterator it = vec.begin(); it != vec.end(); it++)
{
	cout << *it <<" ";

}
system("pause");
return 0;

}

3.2 list

List是一种可在常数时间内在任何位置执行插入和删除操作的顺序容器。list是双向链表,其迭代器是双向的。与其他顺序容器(array, vector, deque)相比,list容器在任意位置执行插入、提取、和移动元素的操作更高效,但它不能通过在容器中的位置直接获取元素(不能像vector那样随机读取访问)。

(1)初始化list对象的方式

	list<int> L0;    //空链表
	
	list<int> L1(3);   //建一个含三个默认值是0的元素的链表
	
	list<int> L2(5,2); //建一个含五个元素的链表,值都是2
	
	list<int> L3(L2); //L3是L2的副本
	
	list<int> L4(L1.begin(),L1.end());    //c5含c1一个区域的元素[begin, end]。

(2)list常用函数

begin():返回list容器的第一个元素的地址

end():返回list容器的最后一个元素之后的地址

rbegin():返回逆向链表的第一个元素的地址(也就是最后一个元素的地址)

rend():返回逆向链表的最后一个元素之后的地址(也就是第一个元素再往前的位置)

front():返回链表中第一个数据值

back():返回链表中最后一个数据值

empty():判断链表是否为空

size():返回链表容器的元素个数

clear():清除容器中所有元素

insert(pos,num):将数据num插入到pos位置处(pos是一个地址)

insert(pos,n,num):在pos位置处插入n个元素num

erase(pos):删除pos位置处的元素

push_back(num):在链表尾部插入数据num

pop_back():删除链表尾部的元素

push_front(num):在链表头部插入数据num

pop_front():删除链表头部的元素

sort():将链表排序,默认升序

......

例如:

#include <iostream>
#include <list>
#include <string>
#include <vector>
#include<iterator>
#include <algorithm>

using namespace std;

int main()
{
	list<string> lt;
	lt.push_back("i");
	lt.push_back("am a boy");
	lt.push_front("hello");
	for (list<string>::iterator it = lt.begin(); it != lt.end(); it++)
		cout << *it <<" ";
	cout << endl;

	lt.insert(lt.begin(), "you");// 向list容器中插入指定元素, 需要使用指定的迭代器来指明插入位置,然后将在该迭代器指向的元素之前进行插入
	lt.insert(++lt.begin(), "are");

	for (list<string>::iterator it = lt.begin(); it != lt.end(); it++)
		cout << *it <<" ";
	cout << endl;
	lt.remove("are");
	for (list<string>::iterator it = lt.begin(); it != lt.end(); it++)
		cout << *it << " ";

	system("pause");
	return 0;
}

3.3 map

Map是STL的一个关联容器,它提供一对一(其中第一个可以称为关键字,每个关键字只能在map中出现一次,第二个可能称为该关键字的值)的数据 处理能力,由于这个特性,它完成有可能在我们处理一对一数据的时候,在编程上提供快速通道。这里说下map内部数据的组织,map内部自建一颗红黑树(一 种非严格意义上的平衡二叉树),这颗树具有对数据自动排序的功能,所以在map内部所有的数据都是有序的。
(1)初始化map对象的方式

map<int, string> m= { { 1, "me" }, { 2, "you" }, { 3, "he" } };   // 实例化一个map容器,还有3组数据
map<char, string> m2;             // 实例化一个空map容器

(2)map常用函数

begin():返回容器第一个元素的迭代器

end():返回容器最后一个元素之后的迭代器

rbegin():

rend():

clera():清除容器中所有元素

empty():判断容器是否为空

insert(p1):插入元素  p1 是通过pair函数建立的映射关系对

insert(pair<char, string>('S', "shenzhen")): 插入元素

size():返回容器中元素的个数

count():返回指定键对应的数据的出现的次数

get_allocator():返回map的配置器

swap():交换两个map容器的元素

.....

#include <iostream>  
#include <map>  

using namespace std;
int main()
{
	// map是一种关联容器类,里面存储的元素类型为pair<const KEY, DATA>。不同的元素KEY值不同。     
	// 定义map及其对应的迭代器  
	map<char, int> mp;
	map<char, int>::iterator it;

// 在map中插入元素  ,也相当于第一种利用数组方式插入数据
// 这种利用下标值的插入方式,当map中没有对应键值的元素时,插入。当map中存在对应键值的元素时,修改其值或者获取其值。  
mp['a'] = 10;
mp['b'] = 20;
mp['c'] = 30;
mp['a'] = 15;//用数组方式就不同了,它可以覆盖以前该关键字对应的值


// 这种使用insert的插入方式,当map中没有对应键值的元素时,插入。当map中存在对应键值的元素时,不插入元素。  
mp.insert(pair<char,int>('g', 62));//第二种:用insert函数插入pair数据
pair<map<char, int>::iterator, bool> ret;//pair的第二个变量判断插入是否成功
mp.insert(map<char, int>::value_type('d', 40));//第三种用insert函数插入value_type数据,insert函数插入数据,在数据的插入上涉及到集合的唯一性这个概念,即当map中有这个关键字时,insert操作是插入数据不了的,与数组方式不同
ret = mp.insert(make_pair('d', 50));//无法覆盖,返回的迭代器第二个值为0
mp.insert(make_pair('e', 50));

// 当使用insert函数后会返回pair<map<char, int>::iterator, bool>类型的值,bool值表示是否插入成功。迭代器指向插入的元素。  
cout << ret.second << endl;


// map中查找某个指定键值的元素,查找成功则返回对应的迭代器。查找失败则返回.end()对应的容器边界迭代器。  
it = mp.find('f');
cout << (it == mp.end()) << " find: 0 means success, 1 means failure" << endl;

// 正向遍历  
cout << "正向" << endl;
for (it = mp.begin(); it != mp.end(); it++)
{
	cout << it->first << " " << it->second << endl;
}

// 反向遍历  
cout << "反向" << endl;
map<char, int>::reverse_iterator iter;
for (iter = mp.rbegin(); iter != mp.rend(); iter++)
{
	cout << iter->first << " " << iter->second << endl;
}

// 使用size获取容器中元素个数  
int num;
num = (int)mp.size();
cout << num << endl;

// 使用clear清空容器  
mp.clear();

// 使用empty判断容器是否为空  
if (mp.empty())
{
	cout << "The map is empty" << endl;
}
system("pause");
return 0;

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值