爆肝整理!C++ string手写实现从0到1:构造/析构/迭代器/增删查改/比较重载,面试必考干货!
5月26日每日学习
文章目录
string的实现
0.类的private部分
namespace lcj
{
class string
{
public:
private:
size_t _capacity;
char* _str;
size_t _size;
};
}
0.1:c_str
c_str是啥,能干嘛:
- 是string中的一个函数
- 能把一个string转化为c风格的字符串(char)
const char* cstr = s.c_str();
可以直接用于 C 字符串函数
——>能使用
strlen(string不行)
‼️注意点:
-
不是new出来的,不用delete(指针指向
string内部管理的内存,不应delete) -
修改原
string后,之前获取的指针可能失效——>想再次使用应该重新获取
实现
- 在.h文件中,在namespace lcj的
class string中
const char* c_str()const
{
return _str;
}
0.2:npos
原始含义: “until the end of the string” --到字符串的末尾
原始定义:static const size_t npos = -1;
实现:
- 声明在
.h文件的lcj域中的string类中的private下:static const size_t npos; - 定义在
.cpp文件的lcj域中:const size_t string::npos = -1;
(奇怪的是cpp后面支持了static const类型的整形可以在.h中直接定义)
1.头文件定义函数导致的多文件重复定义问题
是啥:
函数定义只能出现一次。那么,如果我把用来测试的函数 定义 放在 .h文件中,
——> 两个.c文件都有这个头文件,非法
原因:编译链接的过程 :
- 预处理:把
#include的头文件内容原封不动地复制到每个.cpp文件里。- 编译:分别把
a.cpp和b.cpp编译成 目标文件a.obj/a.o和b.obj/b.o。
此时a.obj里记录了“我定义了sayHello”,b.obj里也记录了“我定义了sayHello”。- 链接:把多个目标文件合并成一个可执行文件。
链接器看到a.obj和b.obj都定义了一个叫sayHello的函数,按照 C++ 的单一定义规则(ODR, One Definition Rule),这是非法的,于是报错
解决方法
静态成员函数
唯一区别:没有this指针
限制:
- 不能访问普通成员变量和成员函数(因为没有this指针)
- 只能访问静态(static)变量和函数
使用上:
普通成员函数(唯一使用方法):
a1.GetCount();( 有this指针)静态成员函数(成员变量也可以):
直接用:
A::GetCount();(✅ 推荐,更方便)(成员变量也可以)先创建类对象再用:
A a1;
a1.GetCount();(没有this指针,但能找到类域)⬇️静态成员变量,所有同类对对象共享——>用来计数
- 最标准的方法:声明和定义分离
MyString.h
#pragma once
#include<iostream>
using namespace std;
namespace lcj
{
class string
{
public: ……
private: ……
};
void test_string1();
}
MyString.cpp
#include"MyString.h"
namespace lcj
{
void test_string1()
{
string s1;
string s2("hello world");
}
}
test.cpp
#include"MyString.h"
int main()
{
lcj::test_string1();
return 0;
}
2.构造函数
2.1带参构造函数的坑点
❌看似正确:
string(const char* s)
:_size(strlen(s))
, _str(new char[_size + 1])
{
}
private:
size_t _capacity;
char* _str;
size_t _size;
‼️错误点:初始化列表的 初始化顺序 是 按 声明顺序,不是初始化列表顺序‼️
——>_str(new char[_size + 1])中的:_size 是随机数
为什么不建议在初始化列表中定义:
- 上面说的,
_str(new char[_size + 1])中的:_size可能因为声明顺序的原因,是随机数 - 即使改变了声明顺序,但,如果有人改了代码,就完了
正确的方式
——>建议在函数体内直接定义:
string(const char* s)
{
_size = strlen(s);
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, s );
}
private:
size_t _capacity;
char* _str;
size_t _size;
2.2默认构造函数
❌看似正确:但是,没法打印——打印时候对空指针解引用
string()
:_str(nullptr)
, _size(0)
, _capacity(0)
{
}
正确写法:
string()
:_str(new char[1]{'\0'})//得写成这样,才能像数组一样操作
, _size(0)
, _capacity(0)
{
}
2.3合一 ——上面两个构造函数
string(const char* s = "")//默认有一个‘\0’
{
_size = strlen(s);
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, s);//strcpy也会加上‘\0’
}
3.析构函数
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
4.size
size_t size()
{
return _size;
}
5.operator[ ]
➡️运算符重载的知识在这里⬅️
- 作用:能直接通过下标访问string中的字符
- 返回值:对应字符的引用
两个函数
-
char& operator[] (size_t pos);const char& operator[] (size_t pos) const;
const char& operator[](size_t pos) const为啥末尾有const❓- 允许 const 对象调用:如果一个
string对象被声明为const,那么它只能调用那些被const修饰的成员函数。没有末尾const的operator[]版本无法被const对象使用。
- 允许 const 对象调用:如果一个
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
const char& operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
6.实现范围for
范围for的底层:通过迭代器(begin和end)遍历数组
而迭代器(
iterator)是一种类型typedef出的结果那我自己typedef一个指针作为迭代器不就行了:
实现迭代器:
- 迭代器不一定是指针,但在此我们可以通过指针来替代
typedef char* iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
这样就可以使用范围for:
for (auto e : s2)
{
cout << e << " ";
}
实现const迭代器
- 和迭代器是函数重载,(参数不同,一个是普通this指针,一个是const this指针)
typedef const char* const_iterator;
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
5月27日每日学习
7.尾插:push_back、append和+=
7.0 .h文件中:
void reserve(size_t n);///开空间
void push_back(char ch);
void append(const char* str);
string& operator+=(char ch);
string& operator+=(const char* str);
7.1 reserve:
‼️注意,开的空间 比容量大1
void string::reserve(size_t n)///开空间
{
if (n > _capacity)
{
///开新空间,移动数据,删旧空间,改变_str和_capacity
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
7.2 push_back
❌push_back 的错误写法
- ‼️没补充最后的
\0
void string::push_back(char ch)
{
///检查容量
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
_str[_size] = ch;
++_size;
}
正确写法
void string::push_back(char ch)
{
///检查容量
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
_str[_size] = ch;
++_size;
_str[_size] = '\0';
}
7.3+=单个字符
string& string::operator+=(char ch)
{
push_back(ch);
return *this;
}
7.4 append
- 判断增加后的容量是否超过
- 求长度
- 扩容
- 通过C语言中的
strcat函数进行后插 - 改变_size
- 为什么不修改_str和_capacity❓
——>因为reserve函数中已经修改过了
void string::append(const char* str)
{
size_t len = strlen(str);
if (len + _size > _capacity)
{
reserve(len + _size > _capacity ? _capacity + len : 2 * _capacity);
}
strcat(_str, str);
_size += len;
}
7.5 +=字符串
string& string::operator+=(const char* str)
{
append(str);
return *this;
}
8.insert
a.插入一个字符:
- 断言
- 像前面的push_back一样,判断要不要扩容
- 不断向后挪动数据
‼️特殊情况:
头插,如果写end>=pos(此时pos是0 )挪动数据,就无法成功,
不论end是int还是size_t,
因为pos是size_t类型的(标准库中就是这样的),二者比较的时候会类型转换,范围大转范围小,end会变成size_t类型,导致永远都>=0
解决方式:
- 方法一:end标成int类型,且将size_t 类型的pos进行强制类型转换为int
- 方法二:使用指针不用下标
- 方法三:(end和pos都是size_t类型),前面说的都是从原本字符串的最后一个位置开始向后挪,方法三是,从最后一个位置的后一个位置,赋值为前一个位置上的字符,
- 这样,本来10个字符的string下标为0~9,
- 想要头插,先让s[10]=s[9],然后是s[9]=s[8],……最后是s[1]=s[0]。
- 就不会遇到需要end>=pos的情况了——end从_size+1开始(\0也会被挪动),直到pos+1,所以这时候条件是end> pos
MyString.h
void insert(size_t pos, char ch);
MyString.cpp
void string::insert(size_t pos, char ch)
{
assert(pos < _size);
///检查容量
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
1. end从 _size 开始(\0不 动)
//size_t end = _size;
//while (end > pos)
//{
// _str[end] = _str[end - 1];
// --end;
//}
//_str[end] = ch;
//++_size;
//_str[_size] = '\0';
1. end从 _size + 1 开始(\0 会被挪动)
size_t end = _size + 1;
while (end > pos)
{
_str[end] = _str[end - 1];
--end;
}
_str[end] = ch;
++_size;
}
b.插入字符串
MyString.h
void insert(size_t pos, const char* str);
MyString.cpp
void string::insert(size_t pos, const char* str)
{
assert(pos < _size);
size_t len = strlen(str);
if (len + _size > _capacity)
{
size_t newcapacity = len + _size > _capacity ? _capacity + len : 2 * _capacity;
reserve(newcapacity);
}
///如果不按照下面这种,逐个后移,和上面插入单个字逻辑相同,也是可以的
memmove(_str + pos + len , _str + pos, _size + 1 - pos);
memmove(_str + pos, str, len);
_size += len;
}
9.erase
a.后面的全删
b.部分删除
MyString.h
void erase(size_t pos, size_t len = npos);
MyString.cpp
void string::erase(size_t pos, size_t len )
{
assert(pos < _size);
if (len > _size - pos)
{
_str[pos] = '\0';
_size = pos;
}
else
{
memmove(_str + pos, _str + pos + len, _size - pos - len + 1);
_size = _size - len;
}
}
10.find
‼️注意:缺省参数在声明(.h)和定义(.c)中不能同时出现。建议在声明(.h)中写
MyString.h
size_t find(char ch, size_t pos = 0);
size_t find(const char* str, size_t pos = 0);
MyString.cpp
size_t string::find(char ch, size_t pos )
{
for (int i = 0; i < _size; i++)
{
if (ch == _str[i])
return i;
}
return npos;
}
size_t string::find(const char* str, size_t pos )
{
char* f = strstr(_str, str);
if (f == nullptr)
return npos;
else
return f - _str;/////////注意不要写成“str”了
}
11.拷贝构造函数
如果没有这个函数来实现深拷贝,那函数中的string作为返回值就会出错,下面的substr函数也实现不了
string(const string& s)
{
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_capacity = s._capacity;
_size = s._size ;
}
12.substr
原型:string substr (size_t pos = 0, size_t len = npos) const;
两种情况:
- 如果
len大于pos后面的字符个数,那就直接把后面的字符串全返回 - 如果
len小于pos后面的字符个数,那就部分返回
MyString.h
string substr(size_t pos = 0, size_t len = npos);
MyString.cpp
string string::substr(size_t pos , size_t len )
{
string ret;
if (len >= _size - pos)
{
for (int i = pos; i < _size; i++)
ret += _str[i];
}
else
{
for (int i = pos; i < pos+len; i++)
ret += _str[i];
}
return ret;
}
13.赋值运算符重载
‼️注意点:
- 因为在赋值之前要delete原内存,所以这时候要防止自己给自己赋值
string& operator=(const string& s)
{
if (&s != this)
{
delete[] _str;
_str = new char[_capacity + 1];
strcpy(_str, s._str);
_capacity = s._capacity;
_size = s._size;
}
return *this;
}
14. 比较
MyString.h
bool operator>(const string& s);
bool operator==(const string& s);
bool operator<(const string& s);
bool operator>=(const string& s);
bool operator<= (const string & s);
bool operator!=(const string& s);
MyString.cpp
bool string::operator>(const string& s)
{
return strcmp(_str, s._str) > 0;
}
bool string::operator==(const string& s)
{
return strcmp(_str, s._str) == 0;
}
bool string::operator<(const string& s)
{
return (!((*this) == s)) && (!((*this) > s));
}
bool string::operator>=(const string& s)
{
return !operator<(s);
}
bool string::operator<= (const string& s)
{
return !operator>(s);
}
bool string::operator!=(const string& s)
{
return !operator==(s);
}
更多推荐

所有评论(0)