一、string类对象是什么?
string在底层实际是basic_string模板类的别名
typedef basic_string<char, char_traits, allocator> string
template<class T>
class basic_string
{
private:
T* _str;//可以是char、wchar_t等
}
string类是basic_string模板类的一个实例,虽然大多数时候我们使用的string类型都是用char来实例化basic_string模板类,并用char_traits和allocator作为basic_string的默认参数,但是考虑到编码的问题,除了char还有wchar_t(两字节)、char16_t、char32_t等,所以string也只是basic_string模板类的一个实例
二、string的构造函数:
// string constructor
#include <iostream>
#include <string>
int main ()
{
std::string s0 ("Initial string");
// constructors used in the same order as described above:
std::string s1;//调用默认构造函数
std::string s2 (s0);//调用拷贝构造函数
std::string s3 (s0, 8, 3);//截取,从给定的字符串对象的第8个位置往后截取三个长度
//如果不写后面的3就默认截取到字符串结束 npos是string里的一个静态变量,代表整形的最大值
std::string s4 ("A character sequence");//from c-string 拷贝一个常量字符串
std::string s5 ("Another character sequence", 12);//从给定c-string的开始位置截取12个长度
std::string s6a (10, 'x');//fill 用10个x fill对象
std::string s6b (10, 42); // 42 is the ASCII code for '*' 也是fill
std::string s7 (s0.begin(), s0.begin()+7);//用某个对象的迭代器区间初始化,从初识位置往后7个长度
std::cout << "s1: " << s1 << "\ns2: " << s2 << "\ns3: " << s3;
std::cout << "\ns4: " << s4 << "\ns5: " << s5 << "\ns6a: " << s6a;
std::cout << "\ns6b: " << s6b << "\ns7: " << s7 << '\n';
return 0;
}
三、string类对象的容量操作
#include <iostream>
#include <string>
int main ()
{
std::string str ("Test string");
std::cout << "The size of str is " << str.size() << " bytes.\n";
//size函数,求字符串长度 这里是11 包含空格但是没有'\0',计算的是有效字符的长度
std::cout << "The size of str is " << str.length() << " bytes.\n"
//length函数,与size函数功能一样,推荐使用size()函数,因为别的STL容器也是用的size()函数
std::cout << "max_size: " << str.max_size() << "\n";
//输出字符串的最长长度,没什么意义,一般max_size:是4294967291
std::cout << "capacity: " << str.capacity() << "\n";
//输出字符串可以存储的有效字符的容量,比如一个字符串的容量是16(包含最后的'\0'),但是可能只有5个有效字符,size()就是5,但是capacity是15(去掉了最后的'\0'),capacity>=size
str.clear();
//直接清空str字符串 str变成空串,但是它的capacity还是存在的,只是清除了数据
str.empty();
//判断str的size是否为0,是返回true,不是返回false;
str.shrink_to_fit();
std::cout << "3. capacity of str: " << str.capacity() << '\n';
//将str的容量变得和size一样长 也就是缩减多余的capacity
str.reserve(20);
//reserve改变string对象的容量,也就是为string对象预留20个字节的空间
//这里的20是至少申请20,具体是多少取决于编译器,因为这里涉及对齐的问题
//预留足够的空间可以减少频繁增容
//如果str是空串,这时str的size还是0 但是其capacity至少是20
str.resize (100,'+');
//resize就是重新分配空间,并初始化,如果只填数字就不初始化
//如果resize中的数字大于原来字符串的有效长度,原字符串内容保留,不然就会缩减
//如果str是空串,这时它的size变为100,并且每个元素都是'+',capacity至少是100
//如果str不为空,就会在原串后面追加'+'直到100
return 0;
}
- size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()。
- clear()只是将string中有效字符清空,不改变底层空间大小。
- resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。
- reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小
四、string类对象的访问与遍历
string类对象的遍历
//方式1 普通for循环
for(int i = 0;i<s.size();i++)
{
cout<<s[i]<<" ";
}
cout<<endl;
//方式2 迭代器 内嵌类型
string::iterator it = s.begin();
while(it!=s.end())
{
cout<<*it<<" ";//可读
*it+=1;//可写
++it;
}
cout<<endl;
//同理还有vector<int>::iterator、list<int>::iterator
//方式3 范围for 自动往后迭代,自动判断结束 底层就是迭代器
for(auto& e:s)//加上&可读可写,不加就是只读
{
cout<<e<<" ";
e-=1;
}
cout<<endl;
迭代器有多种
string s("hello world");
//正着遍历
string::iterator it = s.begin();
while(it!=s.end())
{
cout<<*it<<" ";//可读
*it+=1;//可写
++it;
}
cout<<endl;
//倒着遍历 反向迭代器
string::reverse_iterator rit = s.rbegin();//直接写auto it = s.rbegin();
while(rit!=s.rend())
{
cout<<*rit<<" ";//可读
*rit+=1;//可写
++rit;
}
//普通迭代器可读可写,但是const迭代器只读不可写
void f(const string& str)
{
//此时遍历就需要const迭代器
string::const_iterator it = s.begin();
while(it!=s.end())
{
cout<<*it<<" ";//可读不可写
++it;
}
cout<<endl;
}
//c++11中cbegin和cend迭代器就是const类型迭代器
>
一个可以修改,一个不能修改
#include <iostream>
#include <string>
int main ()
{
std::string str ("Test string");
for (int i=0; i<str.length(); ++i)
{
std::cout << str[i];//返回str对象i位置的字符 就和数组一样访问数组中的元素
//但这里是函数调用 等价于str.operator[](i) 取的是第i个位置字符的引用
//因为是引用,所以除了读还可以写(修改),这里的引用返回不是为了减少拷贝,而是为了支持修改返回对象
str[i]+=1;
//如果是const那就是只读的 不可以修改
s1.at(i)+=1;
//at和[]是一样的,返回的也是引用,可以访问可以修改,同样也有const(只读不能写)
//[]和at检查越界的方式不一样,[]使用assert断言,at是抛异常
}
return 0;
}
#include <iostream>
#include <string>
int main ()
{
std::string str ("hello world.");
str.back() = '!';//因为是引用,所以可以访问对象最后一个元素,也可以修改对象最后一个元素
std::cout << str << '\n';//hello world!这里就将.修改为了!
str.front() = 'T';
//front也是一样,访问首元素,可读可修改 这里就变为Tello world!
return 0;
}
c_str
string s("hello");
cout<<s<<endl;
cout<<s.c_str<endl;//输出const char*,也就是常量字符串
//应用场景:
string file("text.txt");
FILE* fout = fopen(file.c_str(),"2");//这里就需要使用c_str将string类型变为常量字符串类型进行输出
//取文件的后缀名
int pos = file.find('.');
if(pos!=string::npos)
{
string suffix = file.substr(pos,file.size()-pos);
//substr就是从str中的pos位置往后截取n个字符并将其返回
cout<<suffix<<endl;
}
//rfind也和find一样只不过是倒着找
五、string类对象的修改操作
#include <iostream>
#include <string>
int main ()
{
std::string name ("John");
std::string family ("Smith");
name += " K. "; // 在尾部插入c-string,一个常量字符串
name += family; // 在尾部插入一个string类对象
name += '\n'; // 在尾部插入一个character,一个字符
std::cout << name;//John K. Smith
name.push_back('a');
//push_back只能在尾部插入单个字符
//append()可以在尾部插入字符串
return 0;
}
#include <iostream>
#include <string>
int main ()
{
std::string str;
std::string str2="Writing ";
std::string str3="print 10 and then 5 more";
// used in the same order as described above:
str.append(str2); // "Writing ",可以在尾部追加一个string类对象
str.append(str3,6,3); // "10 ",在尾部追加string类对象截取的一部分
str.append("dots are cool",5); // "dots ",在尾部追加常量字符串的前n个元素
str.append("here: "); // "here: ",在尾部追加常量字符串
str.append(10u,'.'); // "..........",在尾部追加自己创建的n个相同变量初始化的字符串
str.append(str3.begin()+8,str3.end()); // " and then 5 more",迭代器区间
str.append<int>(5,0x2E); // "....."
std::cout << str << '\n';//Writing 10 dots here: .......... and then 5 more.....
//其实和构造函数很类似
return 0;
}
assign就是重新给字符串赋值,赋值的方式和构造函数和append都很类似
// string::assign
#include <iostream>
#include <string>
int main ()
{
std::string str;
std::string base="The quick brown fox jumps over a lazy dog.";
// used in the same order as described above:
str.assign(base);
std::cout << str << '\n';
str.assign(base,10,9);
std::cout << str << '\n'; // "brown fox"
str.assign("pangrams are cool",7);
std::cout << str << '\n'; // "pangram"
str.assign("c-string");
std::cout << str << '\n'; // "c-string"
str.assign(10,'*');
std::cout << str << '\n'; // "**********"
str.assign<int>(10,0x2D);
std::cout << str << '\n'; // "----------"
str.assign(base.begin()+16,base.end()-12);
std::cout << str << '\n'; // "fox jumps over"
return 0;
}
string类对象的查找
// string::find
#include <iostream>
#include <string>
int main ()
{
std::string str ("There are two needles in this haystack with needles.");
std::string str2 ("needle");
// different member versions of find in the same order as above:
std::size_t found = str.find(str2);
if (found!=std::string::npos)//如果found的位置不是str的末尾
std::cout << "first 'needle' found at: " << found << '\n';
found=str.find("needles are small",found+1,6);//从found+1位置开始找 "needles are small"的前6个字符
if (found!=std::string::npos)
std::cout << "second 'needle' found at: " << found << '\n';
found=str.find("haystack");
if (found!=std::string::npos)
std::cout << "'haystack' also found at: " << found << '\n';
found=str.find('.');
if (found!=std::string::npos)
std::cout << "Period found at: " << found << '\n';
//找到第一个replace的位置,用str2代替
str.replace(str.find(str2),str2.length(),"preposition");
std::cout << str << '\n';
return 0;
}
string也可以插入删除,insert函数和erase函数,但是效率很低,所以尽量不使用
string s("hello world");
s.insert(0,1,'x');//在头部插入x
s.insert(s.begin(),'y');
s.insert(0,"hello");//插入字符串
s.erase(0,2);//从0位置往后删除2个字符,如果不写2默认0位置往后全部删除
s.erase(s.begin(),s.begin()+2)//迭代器区间
- 在string尾部追加字符时,s.push_back (‘c’) / s.append(1, c) / s += 'c’三种的实现方式差不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。
- 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好
对于输入字符串中包含空格 可以有以下的处理方式
int main()
{
string s1;
//第一种
char ch = getchar();
while(ch!='\n')//一个字符一个字符的输入
{
s1+=ch;
ch = getchar();
}
//第二种
getline(cin,s1);
}