目录
早上好,中文好,晚上好
1.为什么要学习string类?
在 C语言 中,字符串是以\0结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP(面向对象)的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。
于是 C++ 中就引入了String类,它可以看做是一个管理字符串的数据结构。
2.标准库中的string类
2.1string类的文档介绍
cplusplus.com/reference/string/string/?kw=string
在使用string类时,必须包含#include头文件以及using namespace std;
2.2string类的基本概念
string 就是字符串的意思,是 C++用来代替char数组的数据结构。里面封装了一些常用的方法,方便我们地对其进行一些操作,而且string的空间大小是动态变化的,大大减小了不必要的花销
2.3string类的常见接口方法
a.常见的构造函数
string() : 构造空的string类对象,即空字符串string(const char* s) : 用C-string来构造string类对象string(size_t n, char c) : string类对象中包含n个字符cstring(const string&s) : 拷贝构造函数~string():析构函数
void Teststring()
{
string s1; // 构造空的string类对象s1
string s2("hello bit"); // 用C格式字符串构造string类对象s2
string s3(s2); // 拷贝构造s3
}
b.string类对象的容量操作
size:返回字符串有效字符长度length :返回字符串有效字符长度capacity :返回空间总大小empty :检测字符串释放为空串,是返回true,否则返回falseclear :清空有效字符reserve :为字符串预留空间**resize :将有效字符的个数该成n个,多出的空间用字符c填充
注意:
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不会改变容量大小。
c.string类对象的访问及遍历操作
operator[] :返回pos位置的字符,const string类对象调用begin+ end :begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器rbegin + rend :begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器范围for :C++11支持更简洁的范围for的新遍历方式
int main()
{
string s1("hello world");
//string遍历(三种方式)
//下标+[]
for (size_t i = 0; i < s1.size(); i++)
{
s1[i]++;//修改
cout << s1[i] << " ";
}
cout << endl;
//迭代器 --- 像指针一样的对象
string::iterator it1 = s1.begin();
while (it1 != s1.end())
{
(*it1)--;//修改
cout << *it1 << " ";
++it1;
}
cout << endl;
//范围for
for (auto ch : s1)
{
cout << ch << " ";
}
cout << endl;
for (auto ch : s1)
{
ch++;
cout << ch << " ";
}
//auto 自动推导类型
int i = 0;
int j = i;
auto z= i;//int
auto x = 1.1;//double
auto& r1 = i;
auto r2 = r1;//int
auto& r3 = r1;//int&
//auto r4; //报错
//范围for 遍历数组
int a[] = { 1,2,3,4,5,6 };
for (size_t i = 0; i < sizeof(a) / sizeof(int); i++)
{
cout << a[i] << " ";
}
cout << endl;
for (auto e : a)
{
cout << e << " ";
}
cout << endl;
return 0;
}
d.string类对象的修改操作
push_back :在字符串后 尾插 字符cappend :在字符串后 追加 一个字符串operator+= :在字符串后 追加字符串 strc_str:返回C格式字符串find + npos:从字符串pos位置开始 往后找字符c ,返回该字符在字符串中的 位置rfind :从字符串pos位置开始 往前找字符c ,返回该字符在字符串中的 位置substr :在str中从pos位置开始,截取 n个字符 ,然后将其返回
3.string类相关题目
class Solution {
public:
bool isLetter(char ch)
{
if (ch >= 'a' && ch <= 'z')
return true;
if (ch >= 'A' && ch <= 'Z')
return true;
return false;
}
string reverseOnlyLetters(string s) {
if (s.empty())
return s;
size_t begin = 0, end = s.size() - 1;
while (begin < end)
{
while (begin < end && !isLetter(s[begin]))
++begin;
while (begin < end && !isLetter(s[end]))
--end;
swap(s[begin], s[end]);
++begin;
--end;
}
return s;
}
};
387. 字符串中的第一个唯一字符 - 力扣(LeetCode)
class Solution {
public:
int firstUniqChar(string s) {
int count[26]={0};
for(auto ch:s)
{
count[ch-'a']++;
}
for(size_t i =0;i<s.size();++i)
{
if(count[s[i] - 'a']==1)
return i;
}
return -1;
}
};
class Solution {
public:
bool isln(char ch)
{
return (ch>='0' && ch<='9')||
(ch>='a' && ch<='z')||
(ch>='A' && ch<='Z');
}
bool isPalindrome(string s) {
int begin=0;
int end=s.size()-1;
while(begin<end)
{
while(begin<end && !isln(s[begin]))
{
++begin;
}
while(begin<end && !isln(s[end]))
{
--end;
}
char left=std::tolower(s[begin]);
char right=std::tolower(s[end]);
if(left!=right)
{
return false;
}
++begin;
--end;
}
return true;
}
};
4.string的模拟实现
在面试中,面试官总喜欢让学生自己来模拟实现string类,最主要是实现string类的构造、拷贝构造、赋值运算符重载以及析
构函数。大家看下以下string类的实现是否有问题?
// 为了和标准库区分,此处使用String
class String
{
public:
/*String()
:_str(new char[1])
{*_str = '\0';}
*/
//String(const char* str = "\0") 错误示范
//String(const char* str = nullptr) 错误示范
String(const char* str = "")
{
// 构造String类对象时,如果传递nullptr指针,可以认为程序非
if (nullptr == str)
{
assert(false);
return;
}
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
~String()
{
if (_str)
{
delete[] _str;
_str = nullptr;
}
}
private:
char* _str;
};
// 测试
void TestString()
{
String s1("hello world");
String s2(s1);
}
说明:上述String类没有显式定义其拷贝构造函数与赋值运算符重载,此时编译器会合成默认
的,当用s1构造s2时,编译器会调用默认的拷贝构造。最终导致的问题是,s1、s2共用同一块内
存空间,在释放时同一块空间被释放多次而引起程序崩溃,这种拷贝方式,称为浅拷贝。
3.1浅拷贝
浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来 。如果 对象中管理资源 ,最后就会 导致 多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该 资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规 。
就像一个家庭中有两个孩子,但父母只买了一份玩具,两个孩子愿意一块玩,则万事大吉,万一
不想分享就你争我夺,玩具损坏。
可以采用深拷贝解决浅拷贝问题,即:
每个对象都有一份独立的资源,不要和其他对象共享
。父
母给每个孩子都买一份玩具,各自玩各自的就不会有问题了。
3.2深拷贝
如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情况都是按照深拷贝方式提供。
3.3模拟实现代码
string.h
#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include<iostream>
#include<assert.h>
#include<string>
using namespace std;
namespace pzn
{
class string
{
public:
//构造函数
string(const char* str = "");
//拷贝构造
string(const string& s);
//析构函数
~string();
//分配内存,预留内存
void reserve(size_t n);
//尾插一个字符
void push_back(char ch);
//尾插一个字符串
void append(const char* str);
//重载+=
string& operator+=(char ch);
//重载+= const
string& operator+=(const char* str);
//迭代器
using iterator = char*;
//迭代器 const
using const_iterator = const char*;
//string();
string& operator=(const string& s);
//在pos位置插入一个字符
void insert(size_t pos, char ch);
//在pos位置插入一个字符串
void insert(size_t pos, const char* str);
//在pos位置删除数据
void erase(size_t pos, size_t len = npos);//只能在声明中给缺省值
//查找字符
size_t find(char ch, size_t pos = 0);
//查找字符串
size_t find(const char* str, size_t pos = 0);
char& operator[](size_t i)
{
assert(i < _size);
return _str[i];
}
//[]重载
const char& operator[](size_t i) const
{
assert(i < _size);
return _str[i];
}
//迭代器
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
size_t size() const
{
return _size;
}
const char* c_str() const
{
return _str;
}
void clear()
{
_str[0] = '\0';
_size = 0;
}
//提取pos位置之后长度为len的字符
string substr(size_t pos, size_t len=npos);
static const size_t npos;
private:
char* _str;
size_t _size;
size_t _capacity;
};
//定义在全局
bool operator== (const string& lhs, const string& rhs);
bool operator!= (const string& lhs, const string& rhs);
bool operator> (const string& lhs, const string& rhs);
bool operator< (const string& lhs, const string& rhs);
bool operator>= (const string& lhs, const string& rhs);
bool operator<= (const string& lhs, const string& rhs);
ostream& operator<<(ostream& os, const string& str);
istream& operator>>(istream& is, string& str);
}
string.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include"string.h"
namespace pzn
{
const size_t string::npos = -1;
//构造函数
string::string(const char* str)
:_size(strlen(str))
{
_capacity = _size;
_str = new char[_size + 1];
strcpy(_str, str);
}
//拷贝构造函数
string::string(const string& s)
{
_str = new char[s._capacity + 1];
//复制原字符串到新分配的内存中
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
string& string::operator=(const string& s)
{
if (this != &s)
{
delete[] _str;
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
string::~string()
{
delete[] _str;
_size = 0;
_capacity = 0;
}
//分配内存
void string::reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];//预留\0位置,容量不包括\0
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
//尾插一个字符
void string::push_back(char ch)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = ch;
_size++;
}
//尾插字符串
void string::append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
size_t newCapacity = 2 * _capacity;
//扩容2倍不够,则需要多少扩多少
if (newCapacity < _size + len)
newCapacity = _size + len;
reserve(newCapacity);
}
strcpy(_str + _size, str);//将新字符串复制到当前字符串末尾
_size += len;
}
//重载+=
string& string::operator+=(char ch)
{
push_back(ch);
return *this;
}
//重载+= const
string& string::operator+=(const char* str)
{
append(str);
return *this;
}
//在指定的位置插入字符
void string::insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size == _capacity)
{
//扩容
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
size_t end = _size + 1;
while (end > pos)
{
//数据往前挪
_str[end] = _str[end - 1];
--end;
}
_str[pos] = ch;//插入
_size++;
}
//在pos位置插入一个字符串
void string::insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity)
{
size_t newCapacity = 2 * _capacity;
//扩2倍不够,需多少扩多少
if (newCapacity < _size + len)
newCapacity = _size + len;
//预留空间
reserve(newCapacity);
size_t end = _size + len;//插入后字符串的结尾位置
while (end > pos + len - 1)//移动字符,为插入字符串腾出空间
{
_str[end] = _str[end - len];
--end;
}
for (size_t i = 0; i < len; i++)
{
_str[pos + i] = str[i];//将str中的字符串逐一复制到目标位置
}
_size += len;//增加字符串的大小
}
}
//在pos位置删除数据
void string::erase(size_t pos, size_t len)
{
assert(pos < _size);
if (len >= _size - pos)//如果要删除的长度大于或等于从pos开始到字符串末尾的长度
{
_str[pos] = '\0';//将删除位置的字符设为'\0'
_size = pos;
}
else
{
//从后往前移动字符
size_t end = pos + len;//计算开始移动的字符位置,即要删除的字符串的最后位置
while (end <= _size)
{
_str[end = len] = _str[end];
++end;
}
_size -= len;
}
}
//查找字符
size_t string::find(char ch, size_t pos)
{
assert(pos < _size);
for (size_t i = pos; i < _size; i++)
{
if (ch == _str[i])
return i;
}
return npos;
}
//查找字符串
size_t string::find(const char* str, size_t pos)
{
assert(pos < _size);
//使用strstr在字符串_str中,从pos位置开始查找str
const char* ptr = strstr(_str + pos, str);
if (ptr == nullptr)
{
return npos;
}
else
{
return ptr - _str;
}
}
//提取pos位置之后长度为len的字符
string string::substr(size_t pos, size_t len)
{
assert(pos < _size);
//如果请求的长度len大于从pos到结尾的剩余长度
if (len > (_size - pos))
{
len = _size - pos;//设置len为剩余长度
}
pzn::string sub;
//预留len的空间,避免在构造过程中多次分配内存
sub.reserve(len);
for (size_t i = 0; i < len; i++)//将原字符串从 pos 开始的 len 个字符逐个添加到 sub 中
{
sub += _str[pos + i];
}
return sub;
}
bool operator== (const string& lhs, const string& rhs)
{
return strcmp(lhs.c_str(), rhs.c_str()) == 0;
}
bool operator!= (const string& lhs, const string& rhs)
{
return !(lhs == rhs);
}
bool operator> (const string& lhs, const string& rhs)
{
return !(lhs <= rhs);
}
bool operator< (const string& lhs, const string& rhs)
{
return strcmp(lhs.c_str(), rhs.c_str()) < 0;
}
bool operator>= (const string& lhs, const string& rhs)
{
return !(lhs < rhs);
}
bool operator<= (const string& lhs, const string& rhs)
{
return lhs < rhs || lhs == rhs;
}
ostream& operator<<(ostream& os, const string& str)
{
for (size_t i = 0; i < str.size(); i++)
{
os << str[i];
}
return os;
}
istream& operator>>(istream& is, string& str)
{
str.clear();//清空字符串
char ch;
ch = is.get();//获取一个字符
while (ch != '\n' && ch != '\0')
{
str += ch;
ch = is.get();
}
return is;
}
}
感谢观看