目录
string的常用接口说明
string类对象的常见构造
void Teststring()
{
string s1;//构造空的string类对象s1
string s2("hello world");//用C格式字符串构造string类对象s2
string s3(s2);//拷贝构造s3
}
string类对象的容量操作
void Teststring1()
{
string s("hello world");
cout << s.size() << endl;//计算字符串的有效长度
cout << s.length() << endl;//计算字符串的有效长度
cout << s.capacity() << endl;//计算字符串的空间总大小
cout << s << endl;//输出字符串
s.clear();//将s中的字符串清空,只清空数据不改变空间
s.resize(10, 'a');//将s中有效字符个数增加到10个,多出位置用'a'进行填充
s.resize(15);//将s中有效字符个数增加到15个,多出位置用缺省值'\0'进行填充
s.resize(5);// 将s中有效字符个数缩小到5个
s.reserve(100);//与resize相同只不过不对其他多出位置补充'\0'
}
注意:
1. size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一 致,一般情况下基本都是用size()。
2. clear()只是将string中有效字符清空,不改变底层空间大小。
3. resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字 符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的 元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大 小,如果是将元素个数减少,底层空间总大小不变。
4. reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于 string的底层空间总大小时,reserver不会改变容量大小。
深浅拷贝
拷贝构造
#define _CRT_SECURE_NO_WARNINGS
#include<assert.h>
#include<iostream>
#include<string>
class string
{
public:
string(const char* str)
//:_str(new char[strlen(str)+1])//+1算\0
:_size(strlen(str))
{
_str = (new char[strlen(str) + 1]);//+1算\0
strcpy(_str, str);
}
string& operator=(const string & s)
{
//if (this != &s)
//{
// delete[] this->_str;//把s1的空间给释放了
// //delete[] _str;
// _str = new char[strlen(s._str) + 1];
// //给s1开辟一个与s3空间相同的空间
// strcpy(_str, s._str);
// //将s3的内容赋值给s1
//}
if (this != &s)
{
char* tmp = new char[strlen(s._str) + 1];
strcpy(tmp, s._str);
delete[] _str;
_str = tmp;
}
return *this;
}
~string()
{
delete[]_str;
}
private:
char* _str;
size_t _size;
size_t _capacity;
};
void test_string1()
{
string s1("hello world");
string s2(s1);
string s3("sort");
s1 = s3;
}
int main()
{
test_string1();
return 0;
}
我们运行以上代码可以发现程序发生错误
这是什么原因造成的呢,我们画图来更直观的看一下。
由上图我们可以发现,我们由于类中并没有自己生成拷贝构造函数,那么他们会调用类中默认生成的拷贝构造函数。
默认生成的拷贝构造函数说白了也就是让s2也使用s1的空间,说的直白一点就是只是把s1的名字给更改了一下,但是呢,我们如果去查看s2中的数据的时候其实并不会发生错误,但是我们要知道,类中会有默认生成的析构函数会对已经创建的变量进行释放,当我们释放空间时就会出错,因为s1和s2指向的是同一块空间,通过上图右边的监视信息我们也可以发现他们拥有一样的地址,一旦释放他们其中某一个,另一个再进行释放的时候就会报错!
那么我们如何解决这个问题呢,如图所示:
我们可以给s2也申请一个空间,然后将s1的值拷贝给s2,那么他们在析构的时候就不会报错了。
做法就是:自己实现一个拷贝构造函数,在这个拷贝构造函数中我们为s2开辟空间。代码如下:
string(const string& s)//这里传的是对象的别名
//由于默认的拷贝构造只能进行浅拷贝
//所以我们这里需要自己实现拷贝构造函数
:_str(new char[strlen(s._str) + 1])
{
strcpy(_str, s._str);
}
赋值操作
class string
{
public:
string(const char* str)
//:_str(new char[strlen(str)+1])//+1算\0
:_size(strlen(str))
{
_str = (new char[strlen(str) + 1]);//+1算\0
strcpy(_str, str);
}
~string()
{
delete[]_str;
}
private:
char* _str;
size_t _size;
size_t _capacity;
};
void test_string1()
{
string s1("hello world");
string s3("sort");
s1 = s3;
}
int main()
{
test_string1();
return 0;
}
同样我们运行上述代码会发现报错了
这里与拷贝构造的原理其实是一样的,我们在进行赋值操作的时候如果不写的话会自动调用默认生成的函数,默认生成的函数进行的同样是浅拷贝,如图所示:
那么我们也可以根据拷贝构造函数来推断出,当进行析构的时候肯定会发生报错。
跟拷贝构造函数一样我们需要自己实现一个赋值函数,那么应该如何实现呢?
我们可以发现,无论拷贝还是赋值,重中之重都是两个变量指向了 同一个空间然后析构的时候发生错误导致的,所以我们不妨再给s1开辟一个空间,然后将s3的内容给拷贝过去,然后再释放s1原本的空间,这样不就可以实现深拷贝了。如图所示:
代码如下:
string& operator=(const string& s)
{
//if (this != &s)
//{
// delete[] this->_str;//把s1的空间给释放了
// //delete[] _str;
// _str = new char[strlen(s._str) + 1];
// //给s1开辟一个与s3空间相同的空间
// strcpy(_str, s._str);
// //将s3的内容赋值给s1
//}
if (this != &s)
{
char* tmp = new char[strlen(s._str) + 1];
strcpy(tmp, s._str);
delete[] _str;
_str = tmp;
}
return *this;
}
string类对象的访问及遍历操作
operator[] (重 点):
返回pos位置的字符,const string类对象调用。
begin+ end:
begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭 代器
rbegin + rend:
begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭 代器
范围for:
C++11支持更简洁的范围for的新遍历方式
注意:
由于operator[]是const类型的,所以不能修改。
三种遍历方式与迭代器的使用
void test_string1()
{
string s1("hello world");
// 遍历+修改
// 方式1:下标+[]
for (size_t i = 0; i < s1.size(); i++)
{
s1[i] += 1;
}
for (size_t i = 0; i < s1.size(); i++)
{
cout << s1[i] << " ";
}
cout << endl;
//方式2:迭代器
string::iterator it = s1.begin();
//const_iterator begin()const;
while (it != s1.end())
//while (it < s1.end());
//可以这样用因为string底层是数组但是不建议这样用
{
*it -= 1;//类似于指针可以对解引用进行操作
it++;
}
it = s1.begin();
//类似于指针 it此时指向s1的第一个字符
while (it != s1.end())
{
cout << *it << " ";//与指针相同是解引用
it++;//it向后遍历
}
cout << endl;
// 方式3:范围for
// 范围for实质上就是花架子
// 底层:在编译的时候自动被替换为迭代器
//for(char&e : s1)
for (auto& e : s1)
//这里的auto可以根据s1的类型自动推
//取出一个个s1中的数值赋值给e
//这里e就是s1的拷贝
//改变e就不会影响s1
//所以应该加&使e是s1的别名
{
e -= 1;
}
for (auto e : s1)//将s1中的数值一个个赋值给e
//自动往后迭代,自动判断结束
{
cout << e << " ";
}
cout << endl;
}
string类对象的修改操作
尾部插入
int main()
{
string s1;
s1.push_back('a');//在尾部插入单个字符
s1.append("bcde");//在尾部插入一个字符串
cout << s1 << endl;
s1 += ' ';//在尾部插入单个字符
s1 += "hello world";//在尾部插入一个字符串
cout << s1 << endl;
return 0;
}
取出文件名的后缀
void test_string4()
{
string s("hello world");
cout << s << endl;
cout << s.c_str() << endl;//返回C格式字符串
string file("test.txt");
FILE* fout = fopen(file.c_str(), "w");//可读
// 要求你取出文件的后缀名
size_t pos = file.find('.');//记录'.'的位置
if (pos != string::npos)//
{
//string suffix = file.substr(pos, file.size() - pos);
string suffix = file.substr(pos);//第二个参数不写默认截断到最后
cout << suffix << endl;
}
string file("test.txt.zip");
FILE* fout = fopen(file.c_str(), "w");
// 要求你取出文件的后缀名
size_t pos = file.rfind('.');
//由于可能有多个'.'所以我们使用rfind从后寻找最后一个'.'的位置
if (pos != string::npos)
{
//string suffix = file.substr(pos, file.size() - pos);
string suffix = file.substr(pos);
cout << suffix << endl;
}
// http://www.cplusplus.com/reference/string/string/
// https://translate.google.cn/?sl=zh-CN&tl=en&text=%E5%90%8E%E7%BC%80&op=translate
string url("http://www.cplusplus.com/reference/string/string/find/");
size_t pos1 = url.find(':');//同样去寻找':'的位置
string protocol = url.substr(0, pos1 - 0);
cout << protocol << endl;
size_t pos2 = url.find('/', pos1 + 3);
string domain = url.substr(pos1 + 3, pos2 - (pos1 + 3));
cout << domain << endl;
string uri = url.substr(pos2 + 1);
cout << uri << endl;
}
注意:
1. 在string尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c'三种的实现方式差不多,一般 情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。
2. 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好。
补充:
getline:获取一行字符(区别于cin的是它可以获取空格以\0作为结束标志)
扩容问题
通过测试我们可以发现,在vs下容量以1.5倍进行扩容,然而Linux下以2倍进行扩容。
void TestPushBack()
{
string s;
s.reserve(1000);
//申请至少能存储1000个字符的空间
size_t sz = s.capacity();
cout << "capacity changed: " << sz << '\n';
//先打印一遍原始容量
cout << "making s grow:\n";
for (int i = 0; i < 100; ++i)
{
//s.push_back('c');
s += 'c';
if (sz != s.capacity())
//只要sz!=s.capacity时说明容量已经增加了
{
sz = s.capacity();
//记录新的容量
cout << "capacity changed: " << sz << '\n';
}
}
}
vs下:
Linux下:
头部插入与中间插入
void test_string5()
{
string s("hello world");
s += ' ';
s += "!!!!";
cout << endl;
//头插 效率,O(N),尽量少用
s.insert(0, 1, 'x');
s.insert(s.begin(), 'y');
s.insert(0,"test");
cout << s << endl;
//中间位置插入
s.insert(4, "&&&&&");
cout << s << endl;
}
删除
void test_string6()
{
string s("hello world");
//尽量少用头部和中间删除,因为要挪动数据,效率低
//头插
s.erase(0, 1);
//尾插
s.erase(s.size() - 1, 1);
cout << s << endl;
s.erase(3);
cout << s << endl;
}
比较
void test_string7()
{
string s1("hello world");
string s2("string");
//比较的原则是acill码值
cout << (s1 < s2) << endl;
cout << ("hhhhh" < s2) << endl;
cout << (s1 < string("hhhhh")) << endl;
}
字符串与整型之间的互相转换
void test_string8()
{
int val = stoi("1234");
//字符串转整型
cout << val << endl;
string str = to_string(3.1415);
//整型转字符串
cout << str << endl;
}
相关OJ题目
字符串相加
class Solution {
public:
string addStrings(string num1, string num2)
{
int next=0;
int end1=num1.size()-1,end2=num2.size()-1;
string str;
while(end1>=0||end2>=0)
{
int x1=0;
//如果这段字符结束那么自动补充为0
if(end1>=0)
{
x1=num1[end1]-'0';
end1--;
}
int x2=0;
//如果这段字符结束那么自动补充为0
if(end2>=0)
{
x2=num2[end2]-'0';
end2--;
}
int newbit=x1+x2+next;
//计算相加后的位
if(newbit>9)
{
newbit-=10;
next=1;
}
else
{
next=0;
}
str.insert(str.begin(),newbit+'0');
}
if(next==1)
{
str.insert(str.begin(),'1');
}
return str;
}
};
把字符串转换为整数
bool isnum(char c)
{
if(c>='1'&&c<='9')
return true;
else
return false;
}
class Solution {
public:
int StrToInt(string str) {
int begin=0;
int flag=0;
if(str[0]=='+')
{
begin++;
}
else if(str[0] == '-' )
{
begin++;
flag=1;
}
int size=str.size();
int longint=0;
for(int i=begin;i<str.size();i++)
{
if(!isnum(str[i]))
{
return 0;
}
else
{
if(longint==0)
longint=str[i]-'0';
else
{
int nextbit=str[i]-'0';
longint=longint*10+nextbit;
}
}
}
if(flag==0)
return longint;
else
return -longint;
}
};