c++手写string类

27 篇文章 0 订阅

string类是带指针的类,需要进行动态内存分配,需要考虑字符串最后一个字符用'\0'表示结束,需要用到c语言处理字符串的一些函数,以及需要考虑赋值和移动相关函数本身的特性,所以一些基础函数的定义要特别注意,很考验功底,注意构造函数和析构函数一般不设置为inline,具体原因见Effective C++条款30(避免潜在的代码膨胀)

0. 类函数声明与数据成员

class String {
public:
    String(const char *cstr = 0); //const的正确使用,默认参数
    String(const String &str); //参数格式
    String& operator=(const String &str); //返回值,操作符函数写法,参数格式
    ~String();
    String(String &&str) noexcept ; //移动构造函数参数格式,不抛出异常,声明和定义都要写,写在初始化列表前面
    String& operator=(String &&str) noexcept ; //返回类型,赋值运算符函数写法,参数类型,不抛出异常
private:
    char *m_data; //底层数据存储形式,私有的
};

1. 普通构造函数

String::String(const char *cstr) { //无返回值,定义不写默认参数
    if (cstr) { //注意空字符串的情况
        m_data = new char[strlen(cstr) + 1]; //求c语言格式字符串长度用strlen,动态分配内存
        strcpy(m_data, cstr); //拷贝c语言字符串用strcpy,目标参数在前
    }
    else {
        m_data = new char[1];
        *m_data = '\0'; //字符串以'\0'字符结尾
    }
}

2. 拷贝构造函数

String::String(const String &str) {
    m_data = new char[strlen(str.m_data) + 1]; //动态分配内存,求c语言字符串长度用strlen,字符串以'\0'字符结尾
    strcpy(m_data, str.m_data); //c语言字符串拷贝用strcpy
}

3. 拷贝赋值运算符

String& String::operator=(const String &str) {
    if (this == &str) return *this; //避免自我赋值

    delete[] m_data; //先释放=左边的字符串的数据指针
    m_data = new char[strlen(str.m_data) + 1]; //动态分配内存,求c语言字符串长度用strlen,字符串以'\0'字符结尾
    strcpy(m_data, str.m_data); //c语言字符串拷贝
    return *this; //返回引用
}

4. 析构函数

String::~String() {
    delete[] m_data; //释放内存
    m_data = nullptr; //指针置空
}

5. 移动构造函数

String::String(String &&str) noexcept { //承诺不抛出异常
    m_data = str.m_data;
    str.m_data = nullptr; //原来的数据指针置空
}

移动的两个函数都不抛出异常 

6. 移动赋值运算符

String& String::operator=(String &&str) noexcept { //承诺不抛出异常
    if (this == &str) return *this; //避免自我赋值

    delete[] m_data; //释放内存,注意[]
    m_data = str.m_data; //直接拷贝指针,不需要重新分配内存
    str.m_data = nullptr; //原指针置空
    return *this; //返回引用
}

移动的两个函数都不抛出异常 

7. 完整代码

#include <cstring>

class String {
public:
    String(const char *cstr = 0); //const的正确使用,默认参数
    String(const String &str); //参数格式
    String& operator=(const String &str); //返回值,操作符函数写法,参数格式
    ~String();
    String(String &&str) noexcept ; //移动构造函数参数格式,不抛出异常,声明和定义都要写,写在初始化列表前面
    String& operator=(String &&str) noexcept ; //返回类型,赋值运算符函数写法,参数类型,不抛出异常
private:
    char *m_data; //底层数据存储形式,私有的
};

String::String(const char *cstr) { //无返回值,定义不写默认参数
    if (cstr) { //注意空字符串的情况
        m_data = new char[strlen(cstr) + 1]; //求c语言格式字符串长度用strlen,动态分配内存
        strcpy(m_data, cstr); //拷贝c语言字符串用strcpy,目标参数在前
    }
    else {
        m_data = new char[1];
        *m_data = '\0'; //字符串以'\0'字符结尾
    }
}

String::String(const String &str) {
    m_data = new char[strlen(str.m_data) + 1]; //动态分配内存,求c语言字符串长度用strlen,字符串以'\0'字符结尾
    strcpy(m_data, str.m_data); //c语言字符串拷贝用strcpy
}

String& String::operator=(const String &str) {
    if (this == &str) return *this; //避免自我赋值

    delete[] m_data; //先释放=左边的字符串的数据指针
    m_data = new char[strlen(str.m_data) + 1]; //动态分配内存,求c语言字符串长度用strlen,字符串以'\0'字符结尾
    strcpy(m_data, str.m_data); //c语言字符串拷贝
    return *this; //返回引用
}

String::~String() {
    delete[] m_data; //释放内存
    m_data = nullptr; //指针置空
}

String::String(String &&str) noexcept { //承诺不抛出异常
    m_data = str.m_data;
    str.m_data = nullptr; //原来的数据指针置空
}

String& String::operator=(String &&str) noexcept { //承诺不抛出异常
    if (this == &str) return *this; //避免自我赋值

    delete[] m_data; //释放内存,注意[]
    m_data = str.m_data; //直接拷贝指针,不需要重新分配内存
    str.m_data = nullptr; //原指针置空
    return *this; //返回引用
}

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
不知道网上有没有Effective STL(中文),我找不到,我自己整理出了这个《Effective STL(中文)》共享给需要的人。<br><br>《Effective STL》目录:<br><br>前言<br>致谢<br>导读<br>容器<br>条款1:仔细选择你的容器<br>条款2:小心对“容器无关代码”的幻想<br>条款3:使容器里对象的拷贝操作轻量而正确<br>条款4:用empty来代替检查size()是否为0<br>条款5:尽量使用区间成员函数代替它们的单元素兄弟<br>条款6:警惕C++最令人恼怒的解析<br>条款7:当使用new得指针的容器时,记得在销毁容器前delete那些指针<br>条款8:永不建立auto_ptr的容器<br>条款9:在删除选项中仔细选择<br>条款10:注意分配器的协定和约束<br>条款11:理解自定义分配器的正确用法<br>条款12:对STL容器线程安全性的期待现实一些<br>vector和string<br>条款13:尽量使用vector和string来代替动态分配的数组<br>条款14:使用reserve来避免不必要的重新分配<br>条款15:小心string实现的多样性<br>条款16:如何将vector和string的数据传给传统的API<br>条款17:使用“交换技巧”来修整过剩容量<br>条款18:避免使用vector<bool><br>关联容器<br>条款19:了解相等和等价的区别<br>条款20:为指针的关联容器指定比较类型<br>条款21:永远让比较函数对相等的值返回false<br>条款22:避免原地修改set和multiset的键<br>条款23:考虑使用有序vector代替关联容器<br>条款24:当关乎效率时应该在map::operator[]和map-insert之间仔细选择<br>条款25:熟悉非标准散列容器<br>迭代器<br>条款26:尽量用iterator代替const_iterator,reverse_iterator和const_reverse_iterator<br>条款27:用distance和advance把const_iterator转化成iterator<br>条款28:了解如何通过reverse_iterator的base得到iterator<br>条款29:需要一个一个字符输入时考虑使用istreambuf_iterator<br>算法<br>条款30:确保目标区间足够大<br>条款31:了解你的排序选择<br>条款32:如果你真的想删除东西的话就在类似remove的算法后接上erase<br>条款33:提防在指针的容器上使用类似remove的算法<br>条款34:注意哪个算法需要有序区间<br>条款35:通过mismatch或lexicographical比较实现简单的忽略大小字符串比较<br>条款36:了解copy_if的正确实现<br>条款37:用accumulate或for_each来统计区间<br>仿函数、仿函数类、函数等<br>条款38:把仿函数类设计为用于值传递<br>条款39:用纯函数做判断式<br>条款40:使仿函数类可适配<br>条款41:了解使用ptr_fun、mem_fun和mem_fun_ref的原因<br>条款42:确定less<T>表示operator<.<br>使用STL编程<br>条款43:尽量用算法调用代替循环<br>条款44:尽量用成员函数代替同名的算法<br>条款45:注意count、find、binary_search、lower_bound、upper_bound和equal_range的区别<br>条款46:考虑使用函数对象代替函数作算法的参数<br>条款47:避免产生只代码<br>条款48:总是#include适当的头文件<br>条款49:学习破解有关STL的编译器诊断信息<br>条款50:让你自己熟悉有关STL的网站<br>参考书目<br>附录A:区域设置和忽略大小的字符串比较<br>附录B:在微软STL平台上的注意事项<br>词汇表<br>索引<br>关于本电子书
以下是一个简单的基于链表实现的Map类示例,你可以参考一下: ```c++ #include <iostream> #include <string> using namespace std; template <typename KeyType, typename ValueType> class Map { private: struct Node { KeyType key; ValueType value; Node* next; Node(const KeyType& k, const ValueType& v) : key(k), value(v), next(nullptr) {} }; Node* head; public: Map() : head(nullptr) {} ~Map() { Node* node = head; while (node != nullptr) { Node* next = node->next; delete node; node = next; } } void insert(const KeyType& k, const ValueType& v) { Node* node = head; while (node != nullptr) { if (node->key == k) { node->value = v; return; } node = node->next; } node = new Node(k, v); node->next = head; head = node; } const ValueType& operator[](const KeyType& k) const { Node* node = head; while (node != nullptr) { if (node->key == k) { return node->value; } node = node->next; } throw "Key not found!"; } bool contains(const KeyType& k) const { Node* node = head; while (node != nullptr) { if (node->key == k) { return true; } node = node->next; } return false; } }; int main() { Map<string, int> myMap; myMap.insert("apple", 3); myMap.insert("banana", 6); myMap.insert("cherry", 9); cout << myMap["apple"] << endl; // 3 cout << myMap["banana"] << endl; // 6 cout << myMap["cherry"] << endl; // 9 cout << myMap["durian"] << endl; // throws "Key not found!" if (myMap.contains("apple")) { cout << "myMap contains 'apple'" << endl; } if (!myMap.contains("durian")) { cout << "myMap does not contain 'durian'" << endl; } return 0; } ``` 在这个示例中,我们使用了一个简单的单向链表来存储键值对。Map类提供了三个基本的操作:insert、operator[]和contains。insert操作将一个键值对插入到链表中,如果键已经存在,则更新对应的值。operator[]操作用于获取特定键的值,如果键不存在,则抛出异常。contains操作用于检查特定键是否存在于Map中。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值