首页 > 代码库 > 凡人视角C++之string(下)
凡人视角C++之string(下)
上篇文章中,我们着重引入了string类型,也谈及了string类型构造函数和赋值函数的应用,今天我们就来谈谈string类型对象可以利用哪些C++内置的函数。另外,文章有点长,如果大家想方便点,可以在目录里查找。
Elaboration
Iterators
begin
//string::begin
iterator begin() noexcept; //返回默认的迭代器类型对象
const_iterator begin() const noexcept; //返回常量迭代器类型对象
begin函数返回一个指向字符串第一个字符的迭代器,返回的迭代器类型有两种,视所需情况选取合适的迭代器即可。我们现在只要知道,iterator类型迭代器是可以修改指向元素的,而const_iterator类型迭代器无法修改指向元素,至于具体的关于迭代器的讲解,我将在下面提到。
end
//string::end
iterator end() noexcept; //返回默认的迭代器类型对象
const_iterator end() const noexcept; //返回常量迭代器类型对象
end函数与上面的begin函数对应,这里我们要特别注意,end函数并不是指向字符串的最后一个字符,它指向最后一个字符的后面,我们在上一篇文章中提到过,在最后一个字符的后面跟着的是’\0’,我觉得可能是指向这个玩意儿。因此,end函数返回的迭代器是无法解引用(dereference)的,也就是把它指向的值取出来,因为它没有指向实际存在的字符。我们还能想到,当对象是空字符串时,begin和end函数返回的迭代器应该是相同的。
rbegin rend
//string::rbegin
reverse_iterator rbegin() noexcept; //返回默认的反向迭代器
const_reverse_iterator rbegin() const noexcept; //返回常量反向迭代器
//string::rend
reverse_iterator rend() noexcept;
const_reverse_iterator rend() const noexcept;
rbegin&rend函数刚好和begin&end函数相反,前者是将string倒过来求取迭代器,因此,rbegin函数返回了指向字符串最后一个字符的迭代器(注意,这回不是最后一个字符的后面),而rend函数返回了指向字符串第一个字符前面的迭代器。
为了程序员的方便,避免混淆,在C++11中,把上述四种函数做了修改,单独引出了cbegin、cend、crbegin、crend,这些函数与上面函数的唯一区别是它返回的一定是const_iterator或者const_reverse_iterator类型迭代器,是不是清楚多了呢?
#include<iostream>
#include<string>
using namespace std;
int main()
{
string str1 = "im tenkywoo"; //上篇讲过的内容还记得吗?
cout << "begin&end函数结果:" << endl;
for (string::iterator a = str1.begin(); a != str1.end(); ++a)
//正序遍历字符串
cout << *a;
cout << endl;
cout << "rbegin&rend函数结果:" << endl;
for (string::reverse_iterator b = str1.rbegin(); b != str1.rend(); ++b) //逆序遍历字符串
cout << *b;
cout << endl;
string::const_iterator test1 = str1.cbegin();
//使用cbegin函数返回const_iterator类型迭代器
*test1 = ‘a‘; //VS错误提示,说明常量迭代器指向的字符无法被更改
string::const_iterator test2 = str2.begin();
//begin函数返回const_iterator类型迭代器,test2应声明为const_iterator
*test2 = ‘b‘; //VS仍然错误提示,说明常量迭代器指向的字符无法被更改
return 0;
}
Capacity
size length max_size capacity
//string::size
size_t size() const noexcept; //计算字符串长度(占多少字节)
//string::length
size_t length() const noexcept; //计算字符串长度(占多少字节)
//string::max_size
size_t max_size() const noexcept;//查看字符串所能达到的最大长度(字节)
//string::capacity
size_t capacity() const noexcept;//查看分配给该字符串的内存大小(字节)
我把这四个函数放在一起讲,是因为这四个函数比较相近且容易混淆,从我上面的注释可以看出这四个函数都是按字节数返回的。size和length其实是一样的,也比较容易理解。max_size是字符串潜在的最大可达长度,它的大小将由你的系统所决定,在现实操作中由于各种因素(比如其他变量占用内存)很难达到这个大小。capacity是个神奇的东西,这个函数恰好体现了string类型对象的可变长度性质,它大于等于现有的字符串长度,以方便新的字符进入字符串中。当字符串长度要超过capacity时,系统会重新分配字符串的空间,并扩大相应的capacity。
#include<iostream>
#include<string>
using namespace std;
int main()
{
string str = "im tenkywoo";
cout << "the size is: " << str.size() << endl; //size
cout << "the length is: " << str.length() << endl; //length
cout << "the max_size is: " << str.max_size() << endl; //max_size
cout << "the capacity is: " << str.capacity() << endl; //capacity
printf("%d\n", str.c_str()); //获取原先数据成员地址
while (str.size() < str.capacity())
{
str.push_back(‘1‘); //push_back函数后面会讲到,插入字符
}
str.push_back(‘1‘); //此时插入字符会超出capacity,系统要重新分配空间
printf("%d\n", str.c_str()); //获取重分配空间后数据成员地址
cout << "the new capacity is: " << str.capacity() << endl;
//查看重新分配空间后的capacity,发现capacity扩大了
return 0;
}
使用c_str函数(下面会提到)建立指向string类型内部数据成员的指针。可以看到字符串size没有超过capacity时,地址在1374096,而当字符串size超过capacity时,重新分配了空间,地址变为1868176,同时,capacity从15变为31。
resize reserve shrink_to_fit
//string::resize
void resize(size_t n); //将字符串的size变为n
void resize(size_t n, char c); //当n大于字符串的size,用字符c补全
//string::reserve
void reserve(size_t n = 0); //将capacity变为n
//string::shrink_to_fit
void shrink_to_fit(); //将capacity减小到合适的大小
这三个函数都与字符串的size和capacity的变化有关。resize函数会将字符串的size调整为你给定的n。如果n小于字符串的size,字符串只保留前n个字符;如果n大于字符串的size,多出来的部分将由字符c补全,若没有指定字符c,将由空字符补全,此时字符串看不出变化。reserve可以改变字符串的capacity,如果指定的n大于现有的capacity,则capacity将变为大于等于n,否则系统将自动调整capacity,而且capacity会大于n。shrink_to_fit函数用来缩小字符串的capacity,同时保证capacity仍然大于字符串的size。reserve&shrink_to_fit都不会影响字符串大小。
#include<iostream>
#include<string>
using namespace std;
int main()
{
string str = "im tenkywoo";
//resize函数,给定字符c和不给定字符c
cout << "the old size is: " << str.size() << endl;
str.resize(12, ‘!‘);
cout << "resize(given char c):" << endl << str << endl;
str.resize(13);
cout << "resize(not given char c):" << endl << str << endl;
cout << "the new size is: " << str.size() << endl;
//reserve&shrink_to_fit函数
cout << "the old capacity is: " << str.capacity() << endl;
str.reserve(1); //取n小于现在的capacity
cout << "the new capacity with reserve1: " << str.capacity() << endl;
str.reserve(20);//取n大于现在的capacity
cout << "the new capacity with reserve2: " << str.capacity() << endl;
str.shrink_to_fit();
cout << "the new capacity with shrink: " << str.capacity() << endl;
return 0;
}
我在试验的过程中,发现capacity貌似经常是几个固定的数值,我现在看到的都是15和31,不知道是为什么,知道的欢迎在下面留言指教。
clear erase empty
//string::clear
void clear() noexcept; //清除字符串内容,使长度变为0
//string::erase
string& erase(size_t pos = 0, size_t len = npos);
//(1)清除子字符串,pos表示起始位置(0是第一个字符),len代表选取长度
iterator erase(const_iterator p);
//(2)删除迭代器指向的字符,并返回指向删除位置新元素的迭代器,若没有,则返回指向最后一个元素后面的迭代器
iterator erase(const_iterator first, const_iterator last);
//(3)删除[first,last)范围内的字符,注意不包括last。并返回指向删除位置新元素的迭代器,若没有,则返回指向最后一个元素后面的迭代器
//string::empty
bool empty() const noexcept; //判断字符串是否为空
empty函数很简单,这里不多做说明。clear和erase都可以用来删除字符串中的元素,而且clear是erase的特殊化形式,只要使用erase函数第1条中的默认参数就能达到clear效果,因为pos代表起始位置,0是第一个字符,npos代表到字符串底部。
#include<iostream>
#include<string>
using namespace std;
int main()
{
string str = "im tenkywoo";
cout << "at first, character in string?" << endl;
if (str.empty() == 0) //确认字符串有字符
cout << "Yes." << endl << endl;
str.erase(0, 2); //(1)删除"im"两个字符
cout << str << endl;
str.erase(str.begin()); //(2)删除首字符空格;
cout << str << endl;
str.erase(str.begin(), str.begin() + 5);//(3)删除"tenky"
cout << str << endl;
str.clear(); //清除所有元素
cout << endl << "now, character in string?" << endl;
if (str.empty() == 1) //确认字符串无元素
cout << "No." << endl;
return 0;
}
Access
operator[] at back front
//string::operator[]
char& operator[](size_t pos); //访问pos处的字符
const char& operator[](size_t pos);
//string::at
char& at(size_t pos); //访问pos处的字符
const& at(size_t pos) const;
//string::back
char& back(); //访问最后一个字符
const char& back() const;
//string::front
char& front(); //访问第一个字符
const char& front() const;
这四个函数都能访问字符串中的元素。至于返回的是不是const类型,要看你是否对该string类型对象进行了常量声明,声明了常量,那么返回的就是const类型。operater[]和at有一个很显著的区别,那就是当pos不在字符串内部时,at函数会抛出out_of_range的异常提示。这里还要注意,当字符串长度为0时,无法使用front&back函数,因为没有元素可以访问。这几个函数较简单,我就省去编程操作了。
Modifiers
operator+= append push_back pop_back
//string::operator+=
string& operator+=(const string& str); //在字符串后面加上字符串str
string& operator+=(const char*s); //在字符串后面加上C风格字符串
string& operator+=(char c); //在字符串后面加上字符c
string& operator+=(initializer_list<char> il);//跟上初始化列表内元素
//string::append
string& append(const string& str); //(1)在字符串后面加上字符串str
string& append(const string& str, size_t subpos, size_t sublen = npos);
//(2)在字符串后面跟上str的字串,起始点为subpos,长度为sublen
string& append(const char* s); //(3)在字符串后面跟上C风格字符串
string& append(const char* s, size_t n); //(4)跟上字符串前n个字符
string& append(size_t n, char c); //(5)跟上n个c字符
template<class InputIterator>
string& append(InputIterator first, InputIterator last);
//(6)跟上[first,last)范围内的字串
string& append(initializer_list<char> il);
//(7)跟上初始化列表中的元素
//string::push_back
void push_back(char c); //在后面跟上字符c
//string::pop_back
void pop_back(); //删除最后一个字符
append函数比+=重载符内容丰富,我们这里选取append函数做程序测验
//这里省略了cout,将序号与上面注释中序号对照即可。
#include<iostream>
#include<string>
using namespace std;
int main()
{
string str = "im tenkywoo";
string s;
s.append(str); //(1)
s.append(str, 3, 8); //(2)
s.append("tenkywoo"); //(3)
s.append("tenkywoo", 1); //(4)
s.append(1, ‘e‘); //(5)
s.append(str.begin() + 5, str.end()); //(6)
initializer_list<char> il = {‘6‘, ‘6‘, ‘6‘};//(7)
s.append(il);
s.push_back(‘6‘); //push_back函数
s.pop_back(); //pop_back函数
return 0;
}
assign insert replace swap
//string::assign
//此函数与上篇文章所提的构造函数类似,不再赘述,我举个例子,其余可以自己对照
string& assign(const string& str); //复制str字符串
//string::insert
//此函数与assign函数参数类似,需要多个插入位置参数,且能使用迭代器,举个例子
iterator insert(const_iterator p, size_t n, char c);
//在迭代器p所指字符前插入n个c字符,并返回指向插入的第一个字符的迭代器
//string::replace
//举个例子
string& replace(size_t pos, size_t len, const string& str);
//用str替换pos为初始位置,len为长度的子串
string& replace(const_iterator i1, const_iterator i2, const string& str); //迭代器形式
//string::swap
void swap(string& str); //交换两个字符串内容,类名保持不变
assign、insert和replace函数参数都差不多,区别就在于使用目的上,从字面意思就很容易记忆,assign类似于赋值,insert是在字符串中插入内容,replace是将字符串中指定部分进行替换。我们可以发现,assign函数与operator=非常相似,但是assign函数比operator=多了很多内容。而且assign函数相比于构造函数的好处是它能对一个对象多次使用函数,而构造函数却不能。
C-style
c_str data copy
//string::c_str
const char* c_str() const noexcept;
//返回指向string类内部数据成员的指针
//string::data
const char* data() const noexcept; //与上面一样
//string::copy
size_t copy(char* s, size_t len, size_t pos = 0) const;
//将初始位置为pos,长度为len的子串复制到s指针指向的数组中
这三个函数将string类型对象与C风格的字符串联系在了一起。用这些函数可以获取到string内部的数据成员。在上面验证字符串size超出capacity重分配空间时我们曾使用过c_str函数。使用方法可以翻上去看capacity函数部分。
String Operation
find substr compare
//string::find
size_t find(const string& str, size_t pos = 0) const noexcept;
//从pos位置开始(包括pos)寻找str,并返回第一次寻找到的首字符位置
size_t find(const char* s, size_t pos = 0) const; //寻找C风格字符串
size_t find(const char* s, size_t pos, size_t n) const;
//从pos位置开始(包括pos)寻找str的前n个字符,并返回第一次寻找到的首字符位置
size_t find(char c, size_t pos = 0) const noexcept; //寻找字符c
//string::rfind
//内容与find函数相似,寻找方法是逆序查找,返回逆序第一次寻找到的首字符位置,相当于正序最后一个找到的首字符位置。若设定pos,则应查找pos之前的字符
//string::find_first_of
//内容仍然相似,返回第一个出现的与给定字符串中任一字符匹配的字符位置
//string::find_last_of
//find_first_of的逆序版本
//string::find_first_not_of
//正序查找,返回第一个出现的与给定字符串中任一字符不匹配的字符位置
//string::find_last_not_of
//find_first_not_of的逆序版本
//string::substr
string substr(size_t pos = 0, size_t len = npos) const;
//建立一个初始位置pos为0,长度为len的子串的副本
//string::compare
int compare(const string& str) const noexcept;//将字符串与str比较
int compare(size_t pos, size_t len, const& str, size_t subpos, size_t sublen) const; //将两个子串进行比较
int compare(size_t pos, size_t len, const char* s) const;
//子串与C风格字符串比较
int compare (size_t pos, size_t len, const char* s, size_t n) const;
//子串与C风格字符串前n个字符比较
所有的find函数都有一个共同点,它们只能找到第一次出现的位置。find&rfind函数是一类,它们要求寻找到的字符串与给定的字符串完全匹配,而其余的find函数只要给定字符串中任意字符满足就行了。从中,也可以知道find&rfind寻找的是字符串(包括字符),而其余的find函数寻找的是字符。当没有找到匹配的字符或字符串时,会返回npos常量。compare函数用来进行两个字符串间的比较,将返回一个数值。这里要注意,当返回的数值是0时,说明两个字符串完全一样;当数值大于0时,说明当前的字符串比给定的字符串长或者第一个两者不匹配的字符中当前字符串比给定字符串大;反之则数值会小于0。这里可能会有点绕口,我举个例子。比如当前字符串是“apple”,给定字符串是”ant”,两者第一个字符都是’a’,因此跳过,第二个字符一个是’p’,另一个是’n’,此时就要进行比较,而’n’是小于’p’的(看ASCII码大小),所以输出数值是大于0的。注意区分当前字符和给定字符。
#include<iostream>
#include<string>
using namespace std;
int main()
{
string str = "tenkywoo in the house with tenkywoo";
cout << "the first tenkywoo is at: " << str.find("tenkywoo") << endl;
//find函数
cout << "the last tenkywoo is at: " << str.rfind("tenkywoo") << endl;
//rfind函数
//利用find_first_of函数把所有元音找出来替换为‘!‘
size_t found = str.find_first_of("aeiou");
while (found != string::npos) //有匹配的元素时
{
str[found] = ‘!‘;
found = str.find_first_of("aeiou", found + 1);
}
cout << str << endl;
//利用find_first_not_of函数把所有辅音找出来替换为‘?‘
found = str.find_first_not_of("aeiou");
while (found != string::npos) //有匹配的元素时
{
if (str[found] != ‘!‘)
str[found] = ‘?‘;
found = str.find_first_not_of("aeiou", found + 1);
}
cout << str << endl;
string strcpy = str.substr(0, 8); //substr函数
cout << strcpy << endl;
int result = str.compare(strcpy); //compare函数
cout << "the result of comparation is: " << result << endl;
return 0;
}
因为当前字符串比给定的字符串长度长,所以输出的数值是1,比0大,很好理解。
One Detail
四种迭代器类型
不知不觉就写这么多了,最后来把这个问题解决一下。在上述Elaboration中,我们已经接触了这四种迭代器类型,即iterator、const_iterator、reverse_iterator和const_reverse_iterator。按照C++中的定义,这四种迭代器类型都属于random-access iterators(随机存取),而且不同的容器头文件中定义了专属的iterator。
Random-access iterators are iterators that can be used to access elements at an arbitrary offset position relative to the element they point to, offering the same functionality as pointers.
所谓的random-access iterators就是可以访问指向的元素,而且可以访问将迭代器移动后所指向的元素(偏移量offset),就像指针一样,因此所有的指针类型都可以归为random-access iterator。
我在这里要推荐一篇文章,对四种类型的迭代器讲的非常清晰。
http://blog.csdn.net/sptoor/article/details/6615729
我在这里要特别提个醒,const_iterator&const_reverse_iterator是不能更改所指向元素的,但这并不意味着const_iterator&const_reverse_iterator不能进行偏移,const iterator和const_iterator虽然只差个下划线,但是差别很大。当把iterator用const声明时,才意味着这个迭代器无法进行偏移,但是它能改变所指向的元素。这里就有点搞脑子了,细细体会下。我举个例子。
#include<iostream>
#include<string>
using namespace std;
int main()
{
string str = "im tenkywoo";
string::const_iterator itr1 = str.begin();
//const_iterator qualified
itr1++; //successful offset
cout << *itr1 << endl;
*itr = ‘n‘; //failed
const string::iterator itr2 = str.begin(); //const qualified
itr2++; //offset failed. it is constant
*itr2 = ‘I‘; //success
cout << str << endl;
return 0;
}
你看,确实如我们想象的一样!
真的抱歉,这回拖了这么久才把第二篇写完。写完这篇也是花了我不少功夫和脑容量,想交流的还是请在下面留言吧!
凡人视角C++之string(下)