初识C++ · string的使用(1)

目录

1 STL简介

2 string类

2.1 构造 析构 拷贝

2.2 size length

2.3 [ ]的使用

2.4 迭代器

2.5 Push_Back和append

3 sort的使用


1 STL简介

STL是一个标准库,是C++标准库的一个重要部分,那么什么是STL?STL是一个模板库,包含了算法框架和数据结构

STL有不同的版本,比如原始版本,P.J 版本,R.W 版本,SGI版本,不同版本有不同的特点,比如SGI版本的可移植性好,比如P.J版本的可读性较差。

STL这个库里面有六大部分,分别是算法,容器,迭代器,仿函数,空间配置器,配接器。我们即将介绍的,是容器部分的string,可以形象的把容器理解为数据结构,里面还有链表list,树set,顺序表vector等。

这里简单说明,就进入string的正式部分了。


2 string类

string首先是一个类,是委员会发明类之前的一个“前车”,所以成员函数部分可能有点冗余,比如函数有120多个。

string类是字符数组,可以进行增删查改,但是这里的字符不一定是一个字节,通过后面的学习就知道了,比如w_char就是4字节,我们先不做了解。

2.1 构造 析构 拷贝

constructor就是构造的意思,所以这里面进入,就是构造函数的真面目了:

构造函数就有7个,这也是造成冗余的原因之一.

int main()
{
	string s;
	return 0;
}

 第一个构造函数使用如上,即什么也不干,那么s里面就是空的,什么也没有,也可以通过调试观察里面有什么,当然,里面有其他的,我们先不做深究。

int main()
{
	string s1("abcdefg");
	cout << s1 << endl;
	return 0;
}

第二个构造使用如上,很简单,但是为什么支持直接打印呢?因为流重载重载了string类,所以可以打印,那么这也是个验证的好方法。const string& str就是常量字符串的意思,所以我们给上常量字符串就行了。

int main()
{
	string s1("abcdefg");
	string s4(s1);
	return 0;
}

第四个构造使用如上,也就是给一个字符串的指针就可以了,使用很常见,也很实用。

以上3个构造函数的最常用到的,后面三个可以作为了解,毕竟有点鸡肋的。

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

这个构造重载的参数有3个,分别是常量字符串,pos,npos,我们看文档的时候不如连蒙带猜,平时的pos使用是位置的意思,比如之前链表,顺序表的时候pos是位置的索引,即从常量字符串的pos位置开始,那么什么时候结束呢?

到npos位置的时候结束:

查看文档发现,npos的值是-1?但是是size_t类型的,并且下面写到Maximum value for size_t,结合之前介绍的char的轮盘,可以知道npos是42亿多。

那么函数的意思就是从pos位置拷贝一直到npos?什么字符串要占4个G的大小?

所以结合文档(string太短了或者len的值是npos,就会直接拷贝完)

int main()
{
	string s1("abcdefg");
	string s2(s1, 0);
	string s3(s1, 0,30);
	return 0;
}

第一个我们只给了两个参数,那么函数就使用缺省值,即npos,第二个给了三个,但是30明显超出了s1的大小,所以这俩个字符串都是拷贝完s1,实际上使用的时候不会有第二种的写法。

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

第五个函数的意思就是从一个常量字符串里面拷贝多少个字节进去,我们从Hello World里面拷贝5个字节进去,那么打印出来就是Hello。

int main()
{
	string s7(10, 'x');
    cout << s7 << endl;
	return 0;
}

第六个函数的意思就是拷贝n个c字符到string里面去。

以上3个作为了解,实际用处不太大的。

第7个涉及到了迭代器,暂时不介绍。

destructor即析构函数,析构没什么特殊的,出了作用域,string就自己销毁了,不需要自己去销毁。

拷贝有个很舒服的地方在于可以直接使用=:

当然,重载也重载了三个拷贝函数,

int main()
{
	string s1 = "abcdefg";
	string s2 = s1;
	cout << s1 << endl;
	cout << s2 << endl;
	return 0;
}

使用起来也是很方便,底层是怎么操作的我们就不用深究了,但是肯定是发生了隐式类型准换的,s1想要引用,就加const即可。

第三个函数就不用深究了,作用不大。


2.2 size length

在C语言中,我们计算数组的大小常常是size/size,在string中,我们直接调用size就行了:

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

但是通过文档,我们发现size和length没有区别,都是返回string的长度,那么出现两个相同功能的函数的原因是因为string发明太早了,我们对于一个字符串可以说大小也可以说长度,顺序表也是,但是对于后面的树等结构,长度不太说的过去,所以对于string来说,length最初是专门为它服务的,大小是都能用的,length只有string可以用,所以最好后面统一使用size。


2.3 [ ]和at使用

以前访问数组我们通常使用下标 + [ ]进行访问,这点在string里面都是可以使用的,但是这里实际上和数组访问有区别,这里不是指针偏移,这是调用的函数,[]重载。

int main()
{
	string s1 = "abcdefg";
	for (int i = 0; i < s1.size(); i++)
	{
		cout << s1[i];
	}
	return 0;
}

可是如果到这里你觉得[]就介绍完了你就大错特错辣。

class string
{
public:
	char& operator[](size_t pos)
	{
		assert(pos < _size);
		return _str[pos];
	}

private:
	char* _str;
	size_t _size;
	size_t _capacity;
};

string里面的[]重载函数如上,第一个重点是引用返回,因为是引用返回,所以可以减少拷贝,第二个重点是,assert,因为使用了暴力检查,所以越界了就会直接报错:

既然是引用返回,所以我们可以修改字符串里面的内容,但是[]重载有两个版本,一个是普通版本没有const,一个是const版本,当我们不希望string被修改的时候就可以:

int main()
{
	const string s2("123456");
	s2[1] = 'a';
	return 0;
}

与[]相对的是at:

为什么说是相对的呢?因为[]太暴力了,不留一点余地,但是at不一样,at是通过抛异常的方式报错的,也就是可以商量一下:

 正常使用:

int main()
{
	string s1("123456");
	cout << s1.at(2) << endl;
	return 0;
}

 但是没有[]简洁,商量是这样商量的:


int main()
{
	string s2("123456");
	try
	{
		s2.at(10);
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

它不会直接来个报错,会在终端打印出错误原因,比如这里是不合法的下标访问。

当然了,一般还是喜欢[],简洁嘛。


2.4 迭代器

迭代器iterator,有如上几个函数,实际上我们了解前4个就可以了,后面以c开头的其实就是const,表示迭代的元素不能被修改而已。

先看使用:


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

因为iterator是公有函数,所以使用的时候必须加上类名+类域访问符,使it1 = s1.begin(),就是相当于it1从字符H开始,end同理,end就是字符串的最后一个标志->'\0',那么这里看起来十分像指针,但是并不是,不如我们来看看类型。

int main()
{
	cout << typeid(string::iterator).name() << endl;
	return 0;
}

吓人吧?但是目前来说我们可以把它当作指针使用,但是我们只发挥了它的一层功力。

我们现在讨论一个问题,遍历一个字符数组有多少种方式?

遍历方式1:下标 + []

int main()
{
	string s1("Hello world");
	for (int i = 0; i < s1.size(); i++)
	{
		cout << s1[i];
	}
	return 0;
}

遍历方式2:迭代器

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

遍历方式3:范围for

int main()
{
	string s1("Hello world");
	for (auto e : s1)
	{
		cout << e;
	}
	return 0;
}

第一种方式没有什么好说的,第二种是迭代,第三种实际上底层也是调用的迭代器,来看看:

已经出现了刚才那个一长串的类型了,就不用多说了吧?

所以范围for循环底层也是通过迭代器实现的。

当我们进入到begin的文档就会大仙有两个版本,const和普通版本:

int main()
{
	const string s1("Hello world");
	string::const_iterator it1 = s1.begin();
	while (it1 != s1.end())
	{
		cout << *it1;
		it1++;
	}
	return 0;
}

const的作用不用多说,这里要注意的是为什么const_iterator,而不是const iterator?

我们类比指针,如果是const int* p ,那么修饰的是*p,指向不能变,如果是int* const p,就是修饰的p本身,那么如果是const iterator,修饰的就是迭代器本身,本身不能改变,还谈何遍历呢?

所以C++采用的方式是const_iterator。

接着就是rbegin的使用,如果说begin是正方向遍历,rbegin就是逆方向遍历,r也不难猜出来,Reverse,逆置。

使用如下:

int main()
{
	string s1("Hello world");
	string::reverse_iterator it1 = s1.rbegin();
	while (it1 != s1.rend())
	{
		cout << *it1;
		it1++;
	}
	return 0;
}

加个reverse_,同const一样的,使用了之后begin都要变成rbegin。

可能有疑问了,这里++?为什么不是--,实际上++是重载之后的--,不难想象++就是倒着回去遍历的。

2.5 Push_Back和append及+=

数据离不开插入数据:

Push_back即尾插,在字符串末尾插入一个数据,插入之后,对应的字符串长度也会增加。

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

push_back是插入一个字符,append是插入一个字符串,append插入的字符串可以是一个字符吗?那也是可以的,一个字符也可以是字符串:

int main()
{
	string s1("Hello world");
	s1.append("x");
	s1.append("abcd");
	cout << s1 << endl;
	return 0;
}

当然,这里也是有许多重载的,我们也是可以连蒙带猜的去猜使用方法的,但是实际上使用最多的是第三个,后面的其实,用处不太大;

int main()
{
	string s1("Hello world");
	string s2("123456");
	s1.append(s2,4,5);
	s1.append(s2,4);
	cout << s1 << endl;
	return 0;
}

这里就不介绍了看看文档咯。

当然类似的,有插入就有删除,比如尾删pop_back等,就不介绍了:

但是呢,还是略显麻烦了,不就是加个字符吗?string有个堪称神力的重载:

 不管是push_back还是append在这个重载面前都黯然失色了,因为这个太方便:

int main()
{
	string s("aaa");
	s += "bbb";
	s += "c";
	cout << s << endl;
	return 0;
}

要加什么直接加上去就行了,很方便。 


3 sort的使用

讲了这么多string内部的函数,这里就介绍一点实际应用,string是一种容器(数据结构),那么容器是存储数据的,算法是修改数据的,他们之间的联系靠迭代器完成,为什么说迭代器不是冗余的设计,因为迭代器是两者之间的桥梁,使用如下:

int main()
{
	string s1("Hello world");
	sort(s1.begin(), s1.end());
	cout << s1 << endl;
	return 0;
}

这里按照字典序排列,即ASCII码值排列字符串,sort所在的头文件是algorithm,中文意思就是算法的意思,sort的使用要注意左闭右开

左闭右开有个优点就是好计算总共排序多少元素,因为左减右就直接算出来了,这里是对整个string进行排序,如果想要进行部分排序,只需要:

int main()
{
	string s1("Hello world");
	sort(s1.begin(), s1.end() + 5);
	cout << s1 << endl;
	return 0;
}

这里是对前5个元素进行排序,可以看到离不开迭代器。


感谢阅读!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值