C++ string类

18 篇文章 0 订阅

在c语言中,我们想要记录字符串需要创建一个字符串的数组,而c++则提供了另一种方式;

也就是这篇博客所说的string类;

目录

string类

string类的成员函数

string的构造函数

string的容量操作函数

size() / length() / capacity() 函数

reserve(size_t n) / resize(size_t n, char c) 函数

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

 operator[] (size_t pos)

begin()/end()/rbegin()/rend()

迭代器——iterator

string类对象修改操作

string的其他函数


string类

#include<string>

作为字符串数组的升级版,string类自然也有它的独特之处——可变长数组;

 c语言中的字符串数组只能在创建的时候定好初始的长度,数据长度不能超过数组长度;

而string类则没有这样的顾虑,虽然string变量都有自己的初始长度,但是它内部的成员函数会在输入数据超过初始长度的时候,会自动开辟空间,因此可以不用考虑数组长度;

此外,c++考虑到需要兼容c语言,也给string类重载了一个类似于c语言创建字符串数组的构造方式

 但是这里有一点需要注意的是,虽然string类这里的创建方式和c语言的字符串数组一样;

但是它还是有很多不同的;

首先就是它并不是以 ‘\0’ 作为结束的标志;

我们可以看下它内部:

 可以看到 s1 并不是以 '\0’ 作为结束标志的;

好,说了那么多 string类和 c语言数组的不同,那么来讲讲string类特有的功能吧;

string类的成员函数

string类中实现了许多成员函数,并且每种函数根据不同的情况有不同的重载;

这里我们就认识一下用的比较多的函数吧;

string的构造函数

                   函数名称                         功能      
string()创造一个空的string对象
string(const char* s)用一个字符串创建string对象
string(size_t n,char c)用 n 个 c 字符创建string对象
string(const string& s)用string对象拷贝构造一个string对象

void test3()
{
	string s1;
	string s2("abcd");
	string s3(5, 'c');
	string s4(s3);

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


}

 我们能够看到,使用不同方式都成功创建了对应的字符串;

string的容量操作函数

               函数名                      功能
size()返回字符串有效字符长度
length()返回字符串有效字符长度
capacity()返回空间总大小
empty()检测是否为空
clear()清空有效字符
reserve(size_t n = 0)为字符串开辟空间
resize(size_t n)/(size_t n ,char c)更改有效字符为n,多出的用字符c填充

c++中对于string的容量操作的函数可不少,我们先来一个一个学习吧;

size() / length() / capacity() 函数

void test4()
{
	string s1 = "hello world!";
	cout << s1.size() << endl;
	cout << s1.length() << endl;
	cout << s1.capacity() << endl;
}

首先是string的检查容量大小的函数;

我们发现 size() 函数和 length() 函数的返回值其实是一样的;

这是因为c++中,其他容器中的检查容量大小的函数都是 size() ,因此又引入了 size() 函数; 

此外,我们发现 capacity() 函数的返回值和其他两个函数不一样,这是由string类的实现形成的;

string类的实现可以看做是一个char类型的指针(实际上比这更复杂一点,但是为了好理解可以视为是char类型的指针),并且内部除了 char* 指针,还有一个 size 和 capacity 变量

而它们三者的关系类似下图: 

 

实际上string对象开辟的大小为 capacity 这么大,而内部存储的字符串大小为size大小;

这就是为什么两个函数返回值不同的原因;

而 clear() 函数和empty()函数只在乎size()的大小;

而 size 和 capacity 的大小也是有成员变量可以修改的;

reserve(size_t n) / resize(size_t n, char c) 函数

这两个函数都是针对 capacity 变量和 size 变量而出现的函数;

我们先看看 resize 函数;

void test5()
{
	string s1 = "hello world!";
	cout <<"s1.size:"<< s1.size()<<' ' <<"s1.capacity:"<<s1.capacity()<<' ' << s1 << endl;
	s1.resize(5);
	cout << "s1.size:" << s1.size() << ' ' << "s1.capacity:" << s1.capacity()<<' ' << s1 << endl;


}

 

我们发现,resize() 更改了有效字符的长度,而 capacity 并未改变;

并且就算我们将 size 改回原来的长度,也无法恢复原来的数据;

而 reserve() 函数则不用多说,自然是修改 capacity 的大小了,但是它改变得比较奇怪;

void test7()
{
	string s1 = "hello world!";
	cout << "s1.capacity : " << s1.capacity() << ' ' << s1 << endl;
	s1.reserve(20);
	cout << "s1.capacity : " << s1.capacity() << ' ' << s1 << endl;
	s1.reserve(10);
	cout << "s1.capacity : " << s1.capacity() << ' ' << s1 << endl;


}

 我们发现,命名我们用 reserve 函数将 capacity 更改到20,但是实际上它的大小为31;

而更改到10则没变化;

这是为什么呢?

我们先去官网上看看reserve函数的描述:

 大致意思是:

如果 n 比string 的capacity 大,那么编译器就会扩大容器的大小到 n 或者更大,而其他情况下,就根据容器的实现进行优化;

也就是说,reserve 函数并非根据你输入的数据来扩大容量,而是根据编译器内部的实现来进行扩大;

因此这就是为什么我们扩大到20的时候,会扩大到31;

而至于为什么我们想要缩小到10的时候,则没变呢?

虽然官网描述中说自由优化,实际上大部分编译器的实现当 n 小于 capacity 的时候,并不会对capacity 进行修改;

此外,官网的描述中,规定这个函数不会影响字符串的内容,因此当 n 小于 capacity 的时候就不作修改了;

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

                 函数名称                          功能
operator[] (size_t pos)返回 pos 位置的字符
begin()+end()迭代器访问
rbegin()+end()反向迭代器访问
范围forc++支持的新型for循环访问

通过之前的实践,我们都知道 string 类对象都可以直接通过 cout 直接输出;

但是c++还提供了其他方法遍历 string 类对象;

 operator[] (size_t pos)

首先就是运算符重载的 operator[] 访问了;

void test8()
{
	string s1 = "hello world!";
	for (int i = 0; i < s1.size(); i++)
	{
		cout << s1[i];
	}
	cout << endl;
}

 这种访问方式就和 c 语言中访问字符数组一样了;

而这样的访问方式实际上是在类内部实现的;

begin()/end()/rbegin()/rend()

c++中有各种各样的容器,由于底层结构不同,所以各种容器的访问方式也不同;

但是c++为了保证容器的统一性,而创造出了迭代器——iterator,来保证容器访问方式的一致性;

迭代器——iterator

每个容器里面都有 iterator ,它是一个类似于指针的东西,用户通过迭代器能够访问容器的内容;

每一个容器的 begin() 和 end() 函数都是为迭代器服务的

接下来我们看看迭代器的使用方式吧!

void test9()
{
	string s1 = "hello world!";
	string::iterator is1 = s1.begin();

	while (is1 != s1.end())
	{
		cout << *is1;
		is1++;
	}
	cout << endl;

}

 

迭代器并非通过下标访问那样,只用比较 i 和 size 之间的大小;

迭代器只能通过和容器内部的 end() 返回值来比较,看是否到达变量的尾部;

并且,迭代器也有 const 类型的迭代器,用于被 const 修饰容器变量;

void test9()
{
	const string s1 = "hello world!";
	string::const_iterator is1 = s1.begin();

	while (is1 != s1.end())
	{
		cout << *is1;
		is1++;
	}
	cout << endl;

}

rbegin() 和 rend()

前面说过,这两个函数属于反向迭代器,而它的用法和普通迭代器一模一样;

void test9()
{
	string s1 = "hello world!";
	string::reverse_iterator is1 = s1.rbegin();

	while (is1 != s1.rend())
	{
		cout << *is1;
		is1++;
	}
	cout<<endl;

}

 通过反向迭代器,我们能够反向遍历容器内容的内容,其使用方式和普通迭代器是一样的;

范围for

范围for应该算是比较通用的,就不用过多讲解了;

void test9()
{
	string s1 = "hello world!";
	string::reverse_iterator is1 = s1.rbegin();

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

}

 

string类对象修改操作

函数名功能
push_back(char c)在字符串后面添加c
append()    //有多种重载在字符串后面追加字符串
operator+=在字符串后面追加字符串str
c_str()返回c格式字符串
find()+npos在pos位置往后找字符c,并返回所在位置
rfind()在pos位置往前找字符c,并返回所在位置
erase()删除对应位置的元素,并且返回该元素的下一个位置
substr在 str 中从pos位置截取 n 个字符,并返回

 push_back(char c)

 这个函数一次只能在字符串尾端放一个字符;

可以直接放,也能够放char类型的变量;

void test10()
{
	string s1 = "hello world!";
	char a = 'a';
	cout << s1 << endl;
	s1.push_back('!');
	s1.push_back('!');
	s1.push_back('!');
	s1.push_back('!');
	s1.push_back(a);

	cout << s1 << endl;
}

 这里可以看到成功的在尾端放入了字符;

append(const char * s)

append(const char* s ,size_t n)

append(const string & s)

append(const string& s,size_t subpos,size_t sublen);

append(size_t n ,char c)

template<class InputIterator>
append(InputIterator first,InputIterator last)

append这个函数有比较多的重载,接下来我们看看这些重载都是怎么用的吧:

void test10()
{
	string s1 = "hello world!";
	cout << s1 << endl;
	//append(const char * s)
	s1.append("!!!");//添加!!!到s1尾部
	cout << s1 << endl;

	//append(const char* s ,size_t n)
	s1.append("++++++",5);//有6个 +  ,但只加了5个上去
	s1.append("@", 5);//n为5,但是由于字符串只有一个@,因此只加一个@
	cout << s1 << endl;


	string s2 = "how are you?";
	//append(const string & s)
	s1.append(s2);//将s2添加上去
	cout << s1 << endl;

	//append(const string& s,size_t subpos,size_t sublen);
	s1.append(s2, 4,3);//在 s2 中从 subpos位置开始添加sublen长度的字符串到s1中
	cout << s1 << endl;

	//append(size_t n ,char c)
	s1.append(5, '*');//添加n个c到s1中
	cout << s1 << endl;

	//template<class InputIterator>
	//append(InputIterator first,InputIterator last)
	string s3 = "goodbye!";
	s1.append(s3.begin(), s3.begin() + 3);//将s3中从begin()位置开始到begin()+3位置的字符添加到s1中
	cout << s1 << endl;

}

 

 用法如上

operator+= 

相比于 append ,+= 的重载就比较少;

它的用法就是单纯的将 += 右边的字符串添加到左边字符串的尾端;

也是比较常用的一种接口; 

void test11()
{
	string s1 = "hello world!";
	s1 += "!!";
	cout << s1 << endl;

	string s2 = "how are you?";
	s1 += s2;
	cout << s1 << endl;

	s1 += '#';
	cout << s1 << endl;
}

 

const char* c_str() const

c_str()是一个普通的函数,就是返回string对象中的c字符串;

void test11()
{
	string s1 = "hello world!";
	cout << s1.c_str() << endl;


}

find(const string& s,size_t pos = 0)

find(const char* s,size_t pos = 0)

find(const char* s,size_t pos,size_t n)

find(char c,size_t pos = 0)

该函数就是在对象找寻找对应字符的位置,并且返回,若是没找到就会返回-1;

void test11()
{
	string s1 = "hello world!";
	string s2 = " wo";

	//find(const string& s,size_t pos = 0)
	int pos = s1.find(s2);//默认从0位置开始找
	cout << pos << endl;

	pos = s1.find(s2, 3);//从3位置开始找
	cout << pos << endl;
	
	//find(const char* s,size_t pos = 0)
	pos = s1.find("llo", 0);//从0位置开始找
	cout << pos << endl;

	//find(const char* s,size_t pos,size_t n)
	pos = s1.find("ld!",0,5);//从0位置开始找,只找5个位置
	cout << pos << endl;

	//find(char c,size_t pos = 0)
	pos = s1.find('d', 0);//从0位置开始找
	cout << pos << endl;
}

rfind(const string& s,size_t pos = npos)

rfind(const char* s,size_t pos = npos)

rfind(const char* s,size_t pos,size_t n)

rfind(char c,size_t pos = npos)

rfind 和 find 的使用方式一样,只不过是向前寻找,就不做多解释了;

而上面的 npos 实际上就是表示 string 字符串的尾部,因为 rfind 是向前寻找,因此缺省值给的尾部值;

erase(iterator p)

该函数会删除p位置的元素,然后将后面的元素一个一个往前移,然后返回p元素的下一个元素的位置;

 

void test1()
{
	string s1 = "abcd";
	string::iterator is1 = s1.begin();
	is1 = s1.erase(is1);
	cout << *is1 << endl;
}

当使用erase函数的时候,若是在erase函数前面容器扩容了,就会导致迭代器失效

因此需要更新迭代器位置;

 

substr(size_t pos = 0,size_t  len = npos)

void test12()
{
	string s1 = "hello world!";
	cout<<s1.substr(0,5)<<endl;
}

这个函数就是在字符串中从 pos 位置截取 len 长度的字符并返回;

不过原字符串不会消失;

string的其他函数

函数名功能
operator+将两个string或者字符串加起来并返回
operator>>整体输入string类
operator<<整体输出string类
getline()以行接收string类
relational_operators比较大小

operator+ 

void test13()
{
	string s1 = "hello world!";

	cout << s1 << endl;

	s1 = s1 + "!!!";
	
	cout << s1 << endl;

}

 这是一个运算符重载,返回一个 + 左边的字符串在前,右边的字符串在后组合而成的字符串;

当然,它也可以多个字符串相加;

void test13()
{
	string s1 = "hello world!";

	cout << s1 << endl;

	s1 = s1 + "!!!";
	
	cout << s1 << endl;

	s1 = "!!!" + s1 + "!!!";
	cout << s1 << endl;

}

 operator>> / operator<<

这两个其实在前面就已经用过多次了:

void test13()
{
	string s1;
	cin >> s1;
	cout << s1;

}

 

其中的 operator>> 和普通的输入一样,遇见空格或者换行就会中断,如上图;

getline(istream& in,string& s,char delim)

getline(istream& in,string& s)

 为了能够按行输入string,c++有 getline 函数;

一种形式是遇到和 delim 相同的字符就停止

void test13()
{
	string s1;
	//getline(istream& in,string& s,char delim)
	getline(cin, s1,'o');//按行接收字符串,遇到delim就结束,这里遇到o就不接收
	cout << s1 << endl;

}

一种解释单纯的按行接收

void test13()
{
	string s1;
	//getline(istream& in,string& s)
	getline(cin, s1);//按行接收字符串
	cout << s1 << endl;

}

 

 以上就是string类常用的几种函数。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值