C++实现一个自定义字符串类(string)

本博客将详细介绍如何在C++中实现一个自定义的字符串类 string,这个类模仿了标准库中 std::string 的关键功能。这个过程将涵盖从声明到定义的每一步,重点介绍内存管理、操作符重载以及提供一些关键的实现细节。

首先:我们采用函数的声明与定义分离

目的是为了增加代码的可维护性以及提高效率:

1.在vs中,如果我们不实现声明与定义分离,那么编译器会默认认为你当前的函数是内联函数

:内联函数是一种编译器指令,它告诉编译器尝试在每个调用点展开函数体,而不是进行常规的函数调用。 

也就是说每次调用时会直接展开代码,这只适合一些代码量很小但调用机器频繁的函数。所以我们采用函数的声明与定义分离也可以进一步的优化效率。

2.函数声明与定义分离的注意事项和特点

  1. 改善编译依赖性:将函数声明放在头文件中,而将实现(定义)放在源文件中,可以减少编译时的依赖关系。当实现改变时,只需重新编译该源文件及其直接依赖,而不需要重新编译包含头文件的所有文件。

  2. 封装:通过隐藏实现细节,可以保护数据和实现,减少模块间的耦合。这符合封装的面向对象原则,有助于维护和扩展。

  3. 链接考虑:在多个源文件中分散定义的函数只有在最终链接时才会解析。这种分离确保了更好的模块化和错误隔离,尤其是在大型项目中。

  4. 避免多重定义:如果在多个源文件中包含相同的函数定义而未进行适当的静态或内联标记,将导致链接错误。正确的声明和定义分离有助于避免这种问题。

类声明和成员变量

首先,我们需要在 my_string 命名空间中定义字符串类的结构。这包括成员变量和函数的声明

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <assert.h>
#include <string.h>

namespace my_string {
	class string {
	public:
		typedef char* iterator;
		typedef const char* const_iterator;
	public:
		const_iterator begin() const;
		const_iterator end() const;
		iterator begin();
		iterator end();
		const char* c_str() const;
		void earse(size_t pos, size_t len = npos);
		void insert(size_t pos, char ch);
		void insert(size_t pos, const char* str);
		void swap(string& s);
		void clear();
		void pushback(char ch);
		void append(const char* str);
		void reserve(size_t n);
		void resize(size_t n, char ch = '\0');
		size_t size()const;
		size_t capacity()const;
		size_t find(char ch, size_t pos = 0)const;
		size_t find(char* sub, size_t pos = 0)const;
		string(const char* str = "");
		string(const string& str);
		~string();
		string& operator=(string tmp);
		string& operator+=(const char* str);
		string& operator+=(char ch);
		bool empty()const;
		const char& operator[](size_t pos) const
		{
			assert(pos < _size);
			return _str[pos];
		}
	private:
		char* _str = nullptr;
		size_t _size = 0;
		size_t _capacity = 0;
	public:
		static const int npos;
	};
	
	

在定义成员变量时我们采用缺省值来增加安全性。

经典的string类问题

浅拷贝

浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来 。如果 对象中管理资源 ,最后就会 导致多个对象共 享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为为 有效,所以当继续对资源进项操作时,就会发生发生了访问违规

深拷贝

如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情 况都是按照深拷贝方式提供。

 也就是说我们如果不显示构造就会用默认的拷贝构造函数,就会出错!

 构造函数和析构函数实现

构造函数初始化字符串,并为字符数组分配内存。我们同时提供了拷贝构造函数和析构函数以处理资源管理:

string::string(const char* str )
	:_size(strlen(str))
{
	_capacity = _size ;
	_str = new char[_size + 1];
	strcpy(_str, str);
}



string::string(const string& str)
{
	string tmp(str._str);
	swap(tmp);
}



string::~string()
{
	delete[] _str;
	_str = nullptr;
	_size = 0;
	_capacity = 0;
}

动态内存管理实现

对于内存管理,我们实现了 reserveresize 方法来处理内存分配和调整大小的需求:

void string::reserve(size_t n) {
    if (n > _capacity) {
        char* tmp = new char[n + 1];
        strcpy(tmp, _str);
        delete[] _str;
        _str = tmp;
        _capacity = n;
    }
}

void string::resize(size_t n, char ch) {
    if (n > _size) {
        reserve(n);
        std::fill(_str + _size, _str + n, ch);
        _size = n;
    }
    _str[_size] = '\0';
}

操作符重载

操作符重载使得字符串类的对象能以类似于原生数据类型的方式使用:

string& string::operator=(string tmp) {
    swap(tmp);
    return *this;
}

bool operator==(const string& s1, const string& s2) {
    return strcmp(s1.c_str(), s2.c_str()) == 0;
}

std::ostream& operator<<(std::ostream& out, const string& s) {
    out << s._str;
    return out;
}

 实现自定义迭代器

迭代器提供了几种操作,如递增 (++), 递减 (--), 解引用 (*), 等。在我们的例子中,由于使用了原始指针作为迭代器,这些操作已经由指针的自然行为直接支持


		//这里我们手搓一个迭代器
		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 循环

在C++定义中,范围for的底层实现是迭代器,所以哪怕我们要自定义使用范围for

我们就必须手动实现一个一模一样的迭代器这样就可以使用了

实现流插入和输出操作

在自定义实现流插入时,我们发现只能从右边传递给左边,但左边默认是*this,此时如果还是右边传递给左边,那不就成立 cout 传递给 *this 了,道反天罡!所以我们采取定义全局函数,将参数变成两个自定义的就可以解决,由于cout 是ostream流里的  cin是istream流里的所以返回类型也是对应的流类型

std::ostream& operator<<(std::ostream& out, const string& s)
{
	for (auto ch : s)
	{
		out << ch;
	}
	return out;
}

std::istream& operator>>(std::istream& in, string s)
{
	s.clear();
	char ch;
	//in >> ch;  //不能用下标 还没开孔加呢
	// 
	ch = in.get();
	char buff[128];
	size_t i = 0;

	//cin 和 scanf读不到空格
	//C语言用getchar 
	while (ch != ' ' && ch != '\n')
	{
		buff[i++] = ch;
		if (i == 127)
		{
			buff[127] = '\0';
			s += buff;
			i = 0;
		}
		ch = in.get();
	}

	if (i > 0)
	{
		buff[i] = '\0';
		s += buff;
	}
	return in;
}
总结

以上就是如何在C++中从头到尾实现一个自定义的 string 类。我们不仅关注了如何实现基础功能,还涉及了如何通过操作符重载提高类的可用性,以及如何确保类在处理动态内存时的安全和效率。通过这种方式,我们能更深入地理解标准 std::string 类的内部工作机制,并能在需要时为自己的应用定制特定的行为。

  • 56
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 如果你想自己实现一个 C 字符串类,你可以按照以下步骤来实现: 1. 定义结构体:首先,你需要定义一个结构体来存储字符串的相关信息。你可以包含一个字符数组来存储字符串的内容,以及一个整数来记录字符串的长度。 2. 实现初始化函数:接下来,你可以实现一个初始化函数,用于创建字符串类的实例。你可以在这个函数中,根据传入的参数来初始化字符数组和长度变量。 3. 实现其他操作函数:然后,你可以根据你的需要,实现其他相关的操作函数。例如,你可以实现字符串拼接、比较、查找子串等操作。 4. 注意内存管理:在实现这个字符串类时,要注意内存管理的问题。例如,在拼接字符串时,要注意重新分配内存空间,避免内存泄漏。 ### 回答2: C字符串类是一种用于处理字符串的数据类型。在实现一个C字符串类时,我们首先需要定义一些基本的成员变量和方法。 成员变量: 1. `length`:表示字符串的长度。 2. `capacity`:表示字符串的总容量。 3. `data`:存储字符串的字符数组。 方法: 1. `构造函数`:初始化字符串对象,设置初始容量和长度。 2. `析构函数`:释放字符串对象的内存空间。 3. `拷贝构造函数`:用于创建一个新的字符串对象,内容与已有对象相同。 4. `赋值运算符重载`:将一个字符串对象的内容复制给另一个对象。 5. `获取长度`:返回字符串的长度。 6. `获取容量`:返回字符串的总容量。 7. `获取字符串内容`:返回存储的字符串数据。 8. `修改字符串`:修改字符串对象中存储的字符串。 9. `连接字符串`:将两个字符串对象连接成一个新的字符串对象。 10. `比较字符串`:比较两个字符串对象的内容是否相同。 在实现C字符串类时,需要注意以下几点: 1. 为了支持灵活的字符串长度,可以使用动态内存分配,通过`malloc`和`free`函数进行字符串内存的分配和释放。 2. 为了提高字符串的操作效率,可以动态调整字符串容量,根据实际需要进行扩容和缩容操作。 3. 在修改字符串时,需要注意边界条件和内存溢出的问题。 4. 在连接字符串时,可以先判断字符串对象的容量是否足够,如果不够,则进行扩容操作。 5. 在比较字符串时,可以逐个字符比较,直到遇到不相等的字符或者两个字符串的结束符。 通过实现一个C字符串类,我们可以更方便地处理字符串操作,提高代码的可读性和可维护性。 ### 回答3: C语言中没有现成的字符串类,但可以自己实现一个简单的字符串类。 ``` #include <stdio.h> #include <string.h> #define MAX_LENGTH 100 typedef struct { char data[MAX_LENGTH]; int length; } MyString; void initialize(MyString* myStr) { memset(myStr->data, '\0', sizeof(myStr->data)); myStr->length = 0; } void assign(MyString* myStr, const char* source) { strncpy(myStr->data, source, MAX_LENGTH); myStr->length = strlen(myStr->data); } void concat(MyString* dest, const MyString* src) { strncat(dest->data, src->data, MAX_LENGTH-dest->length-1); dest->length = strlen(dest->data); } void print(const MyString* myStr) { printf("%s\n", myStr->data); } int main() { MyString str1; initialize(&str1); assign(&str1, "Hello"); print(&str1); MyString str2; initialize(&str2); assign(&str2, " World!"); concat(&str1, &str2); print(&str1); return 0; } ``` 上面的例子展示了一个简单的C字符串类的实现。该类包含一个存储字符串数据的字符数组和一个记录字符串长度的整数。使用initialize函数初始化字符串对象,assign函数将给定的const char*类型字符串赋值给字符串对象,concat函数将另一个字符串对象追加到目标字符串对象的末尾,print函数将字符串对象打印到控制台。在main函数中展示了如何使用这个自定义字符串类。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hqxnb666

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值