前言
大家好,我是jiantaoyab,本篇文章将给大家介绍String类的常用法,和模拟实现String类。
String介绍
在cplusplus中,对String有着下面的介绍。
The standard string
class provides support for such objects with an interface similar to that of a standard container of bytes, but adding features specifically designed to operate with strings of single-byte characters.
The string
class is an instantiation of the basic_string class template that uses char
(i.e., bytes) as its character type, with its default char_traits and allocator types (see basic_string for more info on the template).
可以看到string类不是STL的容器,string类是basic_string类模板的实例化。
为什么basic_string要设计成类模板呢?
因为不同的语言有不同的编码,有gbk,unicode,ascii等,basic_string独立于所使用的编码来处理字节,如果用来处理多字节或变长字符(如UTF-8)的序列,这个类的所有成员(如长度或大小)以及它的迭代器,将仍然按照字节(而不是实际编码的字符)来操作。
String类常用接口
目前的标 string 类有 106 个接口函数(包括构造和析构函数),如果考虑上默认参数,那么就一共有 134 不同的接口。其中有 5 个函数模板还会产生无穷多个各种各样的函数,所以我们只需要知道常用的接口就够了。
string类常见构造
string() | 构造空的string类对象,即空字符串 |
---|---|
string(const char* s) | 用C-string来构造string类对象 |
string(size_t n, char c) | string类对象中包含n个字符c |
string(const string&s) | 拷贝构造函数 |
string类对象的容量操作
size | 返回字符串有效字符长度 |
---|---|
length | 返回字符串有效字符长度 |
capacity | 返回空间总大小 |
empty | 检测字符串释放为空串,是返回true,否则返回false |
clear | 清空有效字符,不改变底层空间的大小 |
reserve | 为字符串预留空间 |
resize | 将有效字符的个数该成n个,多出的空间用字符’\0’填充 |
int main()
{
string s1("hello");
s1.reserve(2); //hello 不变
s1.reserve(1000);//申请1000个空间
string s2("xxxxxxxxxxxxx");
s2.resize(1000);//申请1000个空间+初始化为0
s2.resize(5);//xxxxx只剩下5个,capacity不变
}
注意
-
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类对象的访问及遍历操作
operator[] | 返回pos位置的字符,const string类对象调用 |
---|---|
begin+ end | begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器 |
rbegin + rend | begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器 |
范围for |
string类对象的修改操作
push_back | 在字符串后尾插字符c |
---|---|
append | 在字符串后追加一个字符串 |
operator+= | 在字符串后追加字符串str |
c_str | 返回C格式字符串 |
find + npos | 从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置(包括pos) |
rfind | 从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置(包括pos) |
substr | 在str中从pos位置开始(包括pos),截取n个字符,然后将其返回 |
void test()
{
//取文件的后缀名
size_t pos = file.find('.');
if(pos != npos)
{
string suffix = file.substr(pos);
}
//取address
string url("http://www.cplusplus.com/reference/string/string/find/");
size_t start = url.find(':');
string protocol = url.substr(0, start - 0);
start += 3; //从www开始找
size_t end = url.find('/', start);
string address = url.substr(start, finish - start);
//删除
url.erase(0, npos);
}
string类非成员函数
operator+ | 尽量少用,因为传值返回,导致深拷贝效率低 |
---|---|
operator>> | |
operator<< | |
getline | 获取一行字符串 |
relational operators | 大小比较 |
String题目练习
字符串最后一个单词的长度
#include <iostream>
using namespace std;
int main() {
string s;
while(getline(cin, s))
{
size_t pos = s.rfind(' ');
cout<<s.size() - pos -1 <<endl;
}
}
class Solution {
public:
bool isletter(char a)
{
if(a>='a'&&a<='z')
return true;
if(a>='A'&&a<='Z')
return true;
if(a>='0'&&a<='9')
return true;
return false;
}
bool isPalindrome(string s) {
int left=0;
int right=s.size()-1;
while(left<right)
{
while(left<right&&!isletter(s[left]))
{
left++;
}
while(left<right&&!isletter(s[right]))
{
right--;
}
if(tolower(s[left])!=tolower(s[right]))
{
return false;
}
left++;
right--;
}
return true;
}
};
class Solution {
public:
string addStrings(string num1, string num2) {
string ret;
int size1=num1.size()-1;
int size2=num2.size()-1;
int carry=0;
//nums1 和 nums2 其中一个还没走完
while(size1 >= 0 || size2 >= 0)
{
int x = 0;
if(size1 >= 0)
{
x = num1[size1]-'0';
--size1;
}
int y = 0;
if(size2 >= 0)
{
y = num2[size2]-'0';
--size2;
}
int add_sum = x + y + carry;
if(add_sum > 9)
{
carry = 1;
add_sum -= 10;
}
else{
carry=0;
}
ret += add_sum + '0';
}
if(carry==1)
{
ret += '1';
}
reverse(ret.begin(), ret.end());
return ret;
}
};
class Solution {
public:
void reverse(string& s,int x,int y)
{
int left=x;
int right=y;
while(left<right)
{
swap(s[left],s[right]);
left++;
right--;
}
}
string reverseStr(string s, int k) {
for(int i=0;i<s.size();i+=(2*k))
{
if(i+k<=s.size())
{
reverse(s,i,i+k-1);
continue;
}
else
{
reverse(s,i,s.size()-1);
}
}
return s;
}
};
class Solution {
public:
string reverseWords(string s)
{
int Word_begin=0;
int Word_end=0;
int i=0;
int lenth=s.length();
while(i<lenth)
{
Word_begin=i;
while(i<lenth&&s[i]!=' ')
{
i++;//找空格位置
}
Word_end=i-1;
while(Word_begin<Word_end)
{
swap(s[Word_begin],s[Word_end]);
Word_begin++;
Word_end--;
}
while(i<lenth&&s[i]==' ')
{
i++;
}
}
return s;
}
};
class Solution {
public:
string multiply(string num1, string num2) {
int size1=num1.length();
int size2=num2.length();
vector<int>v(size1 + size2);
for(int i=0;i<size1;i++)
{
for(int j=0;j<size2;j++)
{
int x=num1[size1-i-1]-'0';
int y=num2[size2-j-1]-'0';
v[i+j] += x*y;
}
}
for(int i=0,carry=0;i<v.size();i++)
{
v[i]+=carry;
carry=v[i]/10;
v[i]%=10;
}
string ret;
for(int i=v.size()-1;i>=0;i--)
{
if(ret.empty()&&v[i]==0) continue;
ret+=v[i]+'0';
}
return ret.empty()? "0" :ret;
}
};
找出字符串中第一个只出现一次的字符
#include <iostream>
#include<string>
using namespace std;
int main() {
string ch;
cin >> ch;
char c;
int sum_ch[256] = { 0 };
int size = ch.size();
for (int i = 0; i<size; i++)
{
sum_ch[ch[i]]++;
}
for (int i = 0; i<size; i++)
{
if (sum_ch[ch[i]] == 1)
{
cout << ch[i] << endl;
return 0;
}
}
cout<<-1;
return 0;
}
String模拟实现
详细的代码大家可以看string模拟实现具体代码,下面我拆分成每个模块给大家介绍。
迭代器部分
typedef char* iterator;
typedef const char* const_iterator;
const_iterator begin() const
{
return _str;
}
const_iterator end()const
{
return _str + _size;
}
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
范围for的是调用迭代器实现的
string::iterator it = s1.begin();
while (it != s1.end())
{
std::cout << *it << std::endl;
it++;
}
构造、拷贝构造、赋值拷贝
string(const char* str = " ") //字符串默认给个'\0'
:_size(strlen(str))
, _capacity(_size)
{
_str = new char[_capacity + 1];
strcpy(_str, str);
}
string(const string& s)
:_str(nullptr)
, _size(0)
, _capacity(0)
{
string tmp(s._str);
this->Swap(tmp);
}
string& operator=(string& s)
{
if (this != &s)
{
string tmp(s);
this->Swap(tmp);
}
return *this;
}
空间容量
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
void resize(size_t n, char ch = '\0')
{
if (n <= _size)
{
_str[n] = '\0';
_size = n;
}
else
{
if (n > _capacity) reserve(n);
memset(_str + _size, ch, n - _size);
_size = n;
_str[_size] = '\0';
}
}
增删改查
string& insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (len + _size > _capacity) reserve(len + _size);
size_t end = _size + len;
//整体向后移动len位
while (end > pos + len) _str[end--] = _str[end - len];
strncpy(_str + pos, str, len);
_size += len;
return *this;
}
string& erase(size_t pos = 0, size_t len = npos)
{
assert(pos < _size);
if (len == npos || pos + len > _size)
{
_str[pos] = '\0';
_size = pos;
}
else
{
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
return *this;
}
void append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
strcpy(_str + _size, str); //包括'\0' 也拷贝过来
_size += len;
}
void push_bach(char ch)
{
//空间不够增容
if (_size == _capacity)
{
reserve(_size == 0 ? 4 : 2 * _capacity);
}
_str[_size] = ch;
_size++;
_str[_size] = '\0';
}
size_t find(char ch)
{
for (size_t i; i < _size; i++)
{
if (_str[i] == ch) return i;
}
return npos;
}
size_t find(const char* str, size_t pos = 0)
{
char* p = strstr(pos + _str, str);
if (p == nullptr) return npos;
else return p - _str; //找到返回子串第一个字符的下标
}
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
const char& operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
string& operator+=(char ch)
{
push_bach(ch);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
c语言几个常用的字符操作函数
可以看到上面实现的代码经常运用了c的字符操作函数,我在这里做一个总结。
strcpy
- 源字符串必须以 ‘\0’ 结束。
- 会将源字符串中的 ‘\0’ 拷贝到目标空间。
- 目标空间必须足够大,以确保能存放源字符串。
- 目标空间必须可变
模拟实现
char *my_strcpy(char* dest, const char*src)
{
char *ret = dest;
assert(dest != NULL);
assert(src != NULL);
while ((*dest++ = *src++));
return ret;
}
strncpy
拷贝num个字符从源字符串到目标空间。
如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个 。
strcmp
比较的是字符串的内容,对应的ASCLL值
- 第一个字符串大于第二个字符串,则返回大于0的数字
- 第一个字符串等于第二个字符串,则返回0
- 第一个字符串小于第二个字符串,则返回小于0的数字
模拟实现
int my_strcmp (const char * src, const char * dst)
{
int ret = 0 ;
assert(src != NULL);
assert(dest != NULL);
while( ! (ret = *(unsigned char *)src - *(unsigned char *)dst) && *dst)
++src, ++dst;
if ( ret < 0 )
ret = -1 ;
else if ( ret > 0 )
ret = 1 ;
return( ret );
}
strstr
字符串查找,在str1里找str2,找到就返回第一次找到的位置,找不到返回NULL
模拟实现
char* my_strstr(const char*str1, const char* str2)
{
assert(str1 && str2);
char* s1;
char* s2;
char* cp = str1;
if (*str2 == '\0')
return str1;
//cp是来记录从这个字母开始比较的,如果这次比较失败就++,下个字母开始重新比较。
while (*cp)
{
s1 = cp;
s2 = str2;
//while (*s1!='\0' && *s2 != '\0' && *s1 == *s2)
while (*s1 && *s2 && *s1 == *s2)
{
s1++;
s2++;
}
if (*s2 == '\0')
{
return cp;
}
cp++;
}
//找不到
return NULL;
}
strcat
字符串追加
char *my_strcat(char *dest, const char *src) //字符串追加
{
assert(dest != NULL);
assert(src != NULL);
char *ret = dest;
//1.找尾
while (*dest)
{
dest++;
}
//2.追加
while ((*dest++ = *src++))
{
;
}
return ret;
}
自己给自己追加可以吗?
两个指针同时操作一个字符串,当*dest找到 ‘\0’ 之后,*src开始追加,当追加完一个字符后,*src往后走一位,*dest指向的 '\0’被覆盖,dest就必须往后走一位,找到下一个’\0’,找到之后src又开始追加,不断重复,导致程序崩溃。
C语言常用的内存函数
memcpy
- 函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。
- 这个函数在遇到 ‘\0’ 的时候并不会停下来。
- 如果source和destination有任何的重叠,复制的结果都是未定义的
模拟实现
void *my_memcpy(void* dest, const void *str, size_t num)
{
//一个字节一个字节拷贝
void *ret = dest;
assert(dest&&str);
while (num--)
{
*(char*)dest = *(char*)str;
dest = (char*)dest + 1;
str = (char*)str + 1;
}
return ret;
}
memmove
和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。如果源空间和目标空间出现重叠,就得使用memmove函数处理。
模拟实现
void*my_memove(void *dest, const void*str, size_t num)
{
assert(dest&&str);
void *ret = dest;
if (dest > str)
{
//后向前拷贝
while (num--)
{
*((char*)dest+num) = *((char*)str+num);
}
}
else
{
//前向后拷贝
while (num--)
{
*(char*)dest = *(char*)str;
str = (char*)str+1;
dest = (char*)dest + 1;
}
}
return ret;
}
memset
从prt指向的字符串开始,插入num个 value
memcmp
比较从ptr1和ptr2指针开始的num个字节, 如果return <0 prt1<ptr2