目录

基本前提:

1构造函数的实现

1.1默认构造函数(无参)

1.2有参构造函数

1.3规范化编程

2.字符转的遍历的实现

2.1[  ]操作符重载

2.2迭代器(指针实现)

2.3整理

3.插入功能的实现

3.1扩容

3.2尾插

3.3尾部插入字符串

3.4+=操作符重载

3.5指定位置插入字符

4.整理

string.h

string.cpp

Test.cpp


基本前提:

我们知道string类是模板basic_string的实例化对象,那它就应该包含相应的成员变量。

本期内容在此基础上手打代码,自己实现string的各种接口。

1构造函数的实现

1.1默认构造函数(无参)

当默认构造函数将_str初始化成空指针时,无法对其进行输入输出。

  头文件 

#pragma once
#include<iostream>
using namespace std;

namespace xxj//创建命名空间,防止string关键词冲突		
{
	class string
	{
	public:
		string()
			:_str(nullptr)//nullptr解决了NULL的指针类型转换问题
			, _size(0)
			, _capacity(0)
		{
			
		}
		const char* c_str()
		{
			return _str;
		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity;
	};
}

  测试源文件

#define _CRT_SECURE_NO_WARNINGS 1
#include"string.h"
namespace xxj
{
	void test_string1()
	{
		string s1;//不用写明类域,优先在本域里找string,不回和库里的string发生冲突。
		cout << s1.c_str() << endl;
	}
}
int main()
{
	xxj::test_string1();
	return 0;
}

这里c_str()返回一个const char*类型的空指针,cout接受时自动识别char*类型,会对其进行解引用,而后报错。

运行结果:

1.2有参构造函数

头文件

#pragma once
#include<iostream>
using namespace std;

namespace xxj//创建命名空间,防止string关键词冲突		
{
	class string
	{
	public:
		string()//默认
			:_str(new char[1]{'\0'})//nullptr解决了NULL的指针类型转换问题
			, _size(0)
			, _capacity(0)
		{
			
		}
		string(const char* str)//有参
			:_str(new char[strlen(str)+1])//1.strlen接口会遍历,O(N),用3次,没效率
			, _size(strlen(str))          //2.与函数strlen不同,sizeof操作符只能计算数组的字节
			, _capacity(strlen(str))      //数,如果传指针,根据环境返回对应的字节数,比如32位                               
                                          //就是4个字节,且在编译阶段就已经执行完成,不可计算动
			strcpy(_str, str);            //态分配的内存
		}
		const char* c_str()
		{
			return _str;
		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity;
	};
}

既然使用3次strlen会降低代码效率,那么把代码改成下边这样可行吗?

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

不可行!改变之后的代码初始化列表内容顺序改变了,但是声明处的内容顺序没变,而初始化列表的初始化顺序与变量被声明的顺序相同,也就是说_str是第一个被初始化的,但是这是后还没有_size,此时new出来的内存是随机的。因此如果使用有参的构造函数来创建对象程序可能会崩。

如果非要这样写,需要改变声明顺序,但是这样做程序的可维护性不强,别人顺序稍微一换就出问题。

那怎么办

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

把_str和_capacity放到函数体里定义就行。所谓“两害取其轻”。

1.3规范化编程

上述项目中我们可以把较长的函数做声明和定义分离,较短的函数就直接放在.h文件中,直接内联展开。整理如下:

string.h

#pragma once
#include<iostream>
using namespace std;

namespace xxj//创建命名空间,防止string关键词冲突		
{
	class string
	{
	public:
		string(const char* str = "");//缺省参数,放在声明处,给空默认结尾有\0,直接给也没问题。
		~string();
		const char* c_str()
		{
			return _str;
		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity;
	};
}

string.cpp

#define _CRT_SECURE_NO_WARNINGS 1
#include"string.h"
namespace xxj//创建命名空间,防止string关键词冲突		
{
		string::string(const char* str )
			:_size(strlen(str))
		{
			_str = new char[_size + 1];
			_capacity = _size;
			strcpy(_str, str);
		}
		string::~string()
		{
			delete[]_str;
			_str = nullptr;
			_size = 0;
			_capacity = 0;
		}
}

Test.cpp

#define _CRT_SECURE_NO_WARNINGS 1
#include"string.h"
namespace xxj
{
	void test_string1()
	{
		string s1;
		cout << s1.c_str() << endl;
		string s2("hello world");	
		cout << s2.c_str() << endl;
	}
}
int main()
{
	try
	{
		xxj::test_string1();
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}
	return 0; 
}

2.字符转的遍历的实现

2.1[  ]操作符重载

char& operator[](size_t pos)
{
		assert(pos < _size);
		return _str[pos];
}

const char& operator[](size_t pos) const//const修饰this指针,变只读,无法改变对象成员变量
{
		assert(pos < _size);
		return _str[pos];
}

2.2迭代器(指针实现)

typedef char* iterator;
typedef const char* const_iterator;//只读遍历

iterator begin()
{
	return _str;
}

iterator end()
{
	return _str + _size;
}

const_iterator begin() const
{
	return _str;
}

const_iterator end() const
{
	return _str + _size;
}

2.3整理

将上述代码(短)添加到头文件中,在加上size,和c_str接口,我们就能用两种方式实现自定string类对象的遍历。

string.h

#pragma once
#include<iostream>
using namespace std;

namespace xxj//创建命名空间,防止string关键词冲突		
{
	class string
	{
	public:
            typedef char* iterator;
            typedef const char* const_iterator;//只读遍历
    
            iterator begin()
            {
            	return _str;
            }

            iterator end()
            {
            	return _str + _size;
            }

            const_iterator begin() const
            {
            	return _str;
            }

            const_iterator end() const
            {
    	        return _str + _size;
            }

            char& operator[](size_t pos)
            {
	        	assert(pos < _size);
	        	return _str[pos];
            }

            const char& operator[](size_t pos) const//const修饰this指针,变只读,无法改变对 //象私有成员变量
            {
		        assert(pos < _size);
	        	return _str[pos];
            }

		    string(const char* str = "");//缺省参数,放在声明处,给空默认结尾有\0,直接给也没  //问题
	    	~string();
            
	    	size_t size() const
	    	{
		    	return _size;
		    }

	    	const char* c_str()const
	    	{
		    	return _str;
	    	}
	private:
	    	char* _str;
		    size_t _size;
		    size_t _capacity;
	};
}

Test.cpp

#include"string.h"

namespace bit
{
	void test_string1()
	{
		bit::string s1;
		cout << s1.c_str() << endl;

		string s2("hello world");
		cout << s2.c_str() << endl;
		s2[0] = 'x';

		for (size_t i = 0; i < s2.size(); i++)
		{
			s2[i]++;
		}
		cout << s2.c_str() << endl;

		string s3 = "hello world"; // 隐式类型转换,构造+拷贝构造->优化为构造
		string s4("hello world");
		const string s5("hello world");

		for (size_t i = 0; i < s2.size(); i++)
		{
			cout << s5[i] << "-";
		}
		cout << endl;

		for (auto ch : s4)//范围for需要迭代器的支持,需要手动实现迭代器
		{
			cout << ch << " ";
		}
		cout << endl;

		string::iterator it4 = s4.begin();
		while (it4 != s4.end())
		{
			*it4 += 1;
			cout << *it4 << " ";
			++it4;
		}
		cout << endl;

		for (auto ch : s5)
		{
			cout << ch << " ";
		}
		cout << endl;

		string::const_iterator it5 = s5.begin();
		while (it5 != s5.end())
		{
			// *it5 += 1;
			cout << *it5 << " ";
			++it5;
		}
		cout << endl;
	}

3.插入功能的实现

3.1扩容

实现插入功能,首先要扩容,标准string容器的resreve接口是会缩容的,我们自己实现的reserve不会。

void string::reserve(size_t n)
	{
		if (n > _capacity)
		{
			// 
			char* tmp = new char[n + 1];//真正的容器,是直接向内存池申请内存的,这里直接用new了
                                        //记得号多申请一个,size和capacity的位置是倒数第二个
                                         //最后还有一个\0
			strcpy(tmp, _str);
			delete[] _str;
			_str = tmp;
			_capacity = n;
		}
	}

3.2尾插

void string::push_back(char ch)
	{
		if (_size == _capacity)
		{
			reserve(_capacity == 0 ? 4 : _capacity * 2);
		}
		_str[_size] = ch;
		_size++;
		_str[_size] = '\0';
	}

3.3尾部插入字符串

void string::append(const char* str)
	{
		size_t len = strlen(str);
		if (_size + len > _capacity)
		{
			reserve(std::max(_size + len, _capacity * 2));//二倍不一定够
                                                          //要多少扩多少,每次只插入一点,就得
                                                           //频繁扩,很麻烦
		}
		strcpy(_str + _size, str);
		_size += len;
	}

3.4+=操作符重载

        string& operator+=(const char* str)//字符串
		{
			append(str);
			return *this;
		}

		string& operator+=(char ch)//字符
		{
			push_back(ch);
			return *this;
		}

3.5指定位置插入字符

这里要注意隐式类型转换

void string::insert(size_t pos, char ch)
	{
		assert(pos <= _size);
		if (_size == _capacity)
		{
			reserve(_capacity == 0 ? 4 : _capacity * 2);
		}

		size_t end = _size;
		while (end >= pos)
		{
			_str[end + 1] = _str[end];
			--end;
		}

		_str[pos] = ch;
		_size++;
	}

上述代码的第一个问题:头插,会出现end>=pos的情况恒成立(end是size_t类型,大于等于0),导致程序崩溃

void string::insert(size_t pos, char ch)
	{
		assert(pos <= _size);
		if (_size == _capacity)
		{
			reserve(_capacity == 0 ? 4 : _capacity * 2);
		}

		int end = _size;//把size_t类型改成int,使之能出现负数的情况,跳出循环 
		while (end >= pos)
		{
			_str[end + 1] = _str[end];
			--end;
		}

		_str[pos] = ch;
		_size++;
	}

第二个问题:运算符进行运算的时候,如果操作数类型不同,会先转换成一样类型,再进行运算,默认有符号整型会转换成无符号整型。那么这里的end又会被转换成size_t类型。

void string::insert(size_t pos, char ch)
	{
		assert(pos <= _size);
		if (_size == _capacity)
		{
			reserve(_capacity == 0 ? 4 : _capacity * 2);
		}

		int end = _size;//把size_t类型改成int,使之能出现负数的情况,跳出循环 
		while (end >= (int)pos)//强制类型转换
		{
			_str[end + 1] = _str[end];
			--end;
		}

		_str[pos] = ch;
		_size++;
	}

4.整理

string.h

#pragma once
#include<iostream>
using namespace std;

namespace xxj//创建命名空间,防止string关键词冲突		
{
	class string
	{
	public:
            typedef char* iterator;
            typedef const char* const_iterator;//只读遍历
    
            iterator begin()
            {
            	return _str;
            }

            iterator end()
            {
            	return _str + _size;
            }

            const_iterator begin() const
            {
            	return _str;
            }

            const_iterator end() const
            {
    	        return _str + _size;
            }

            char& operator[](size_t pos)
            {
	        	assert(pos < _size);
	        	return _str[pos];
            }

            const char& operator[](size_t pos) const//const修饰this指针,变只读,无法改变对 //象私有成员变量
            {
		        assert(pos < _size);
	        	return _str[pos];
            }

		    string(const char* str = "");//缺省参数,放在声明处,给空默认结尾有\0,直接给也没  //问题
	    	~string();
            
	    	size_t size() const
	    	{
		    	return _size;
		    }

	    	const char* c_str()const
	    	{
		    	return _str;
	    	}
            void reserve(size_t n);
		    void push_back(char ch);
	    	void append(const char* str);

	    	string& operator+=(const char* str)
		    {
		    	append(str);
		    	return *this;
		    }

	    	string& operator+=(char ch)
	    	{
	    		push_back(ch);
		    	return *this;
	       	}

		    void insert(size_t pos, char ch);
            
	private:
	    	char* _str;
		    size_t _size;
		    size_t _capacity;
            
		    // const static整形可以这么用,特殊处理
	    	const static size_t npos = -1;

		    // double不支持
	    	// const static double x = 1.1;	
}

string.cpp

#define _CRT_SECURE_NO_WARNINGS 1

#include"string.h"

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

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

	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::push_back(char ch)
	{
		if (_size == _capacity)
		{
			reserve(_capacity == 0 ? 4 : _capacity * 2);
		}
		_str[_size] = ch;
		_size++;
		_str[_size] = '\0';
	}

	void string::append(const char* str)
	{
		size_t len = strlen(str);
		if (_size + len > _capacity)
		{
			reserve(std::max(_size + len, _capacity * 2));
		}

		strcpy(_str + _size, str);
		_size += len;
	}

	void string::insert(size_t pos, char ch)
	{
		assert(pos <= _size);

		if (_size == _capacity)
		{
			reserve(_capacity == 0 ? 4 : _capacity * 2);
		}

		int end = _size;
		while (end >= (int)pos)
		{
			_str[end + 1] = _str[end];
			--end;
		}

		_str[pos] = ch;
		_size++;
	}

	//const size_t string::npos = -1;
}

Test.cpp

#include"string.h"
namespace bit
{
	void test_string1()
	{
		bit::string s1;
		cout << s1.c_str() << endl;

		string s2("hello world");
		cout << s2.c_str() << endl;
		s2[0] = 'x';

		for (size_t i = 0; i < s2.size(); i++)
		{
			s2[i]++;
		}
		cout << s2.c_str() << endl;

		string s3 = "hello world"; // 隐式类型转换,构造+拷贝构造->优化为构造
		string s4("hello world");
		const string s5("hello world");

		for (size_t i = 0; i < s2.size(); i++)
		{
			cout << s5[i] << "-";
		}
		cout << endl;

		for (auto ch : s4)
		{
			cout << ch << " ";
		}
		cout << endl;

		string::iterator it4 = s4.begin();
		while (it4 != s4.end())
		{
			*it4 += 1;
			cout << *it4 << " ";
			++it4;
		}
		cout << endl;

		for (auto ch : s5)
		{
			cout << ch << " ";
		}
		cout << endl;

		string::const_iterator it5 = s5.begin();
		while (it5 != s5.end())
		{
			// *it5 += 1;
			cout << *it5 << " ";
			++it5;
		}
		cout << endl;
	}

	void test_string2()
	{
		bit::string s1;
		cout << s1.c_str() << endl;
		s1.push_back('x');
		s1.push_back('x');
		s1.push_back('x');
		cout << s1.c_str() << endl;

		string s2("hello world");
		cout << s2.c_str() << endl;
		s2.push_back('x');
		s2.push_back('y');
		s2.push_back('z');
		cout << s2.c_str() << endl;

		string s3("hello");
		s3.append("xxxxxxxxxxxxxxxxxxxxxxxxx");
		cout << s3.c_str() << endl;

		string s4("hello");
		s4.append("xx");
		s4.append("xx");
		cout << s4.c_str() << endl;

		s4 += '*';
		s4 += "hello bit";
		cout << s4.c_str() << endl;
	}

	void test_string3()
	{
		string s1("hello world");
		cout << s1.c_str() << endl;
		s1.insert(5, 'x');
		cout << s1.c_str() << endl;

		s1.insert(0, 'x');
		cout << s1.c_str() << endl;
	}
}

int main()
{
	try
	{
		bit::test_string3();

		//cout << typeid(bit::string::iterator).name() << endl;
		//cout << typeid(std::string::iterator).name() << endl;

	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}

	return 0;
}

更多推荐