【C++】string

一、string模拟实现 

 1.1 创建类

	class string
	{
	public:

	private:
		char* _str;
		int _size;
		int _capacity;
	};

1.2 构造与析构

为什么这里用缺省值const char* str = ""----->如果在main函数中使用string s1();这种方式,创建一个什么也不初始化的空类,不使用缺省值的话编译不通过还得继续在声明定义一个string()这种形式,使用缺省值的话,则可以传递一个""空字符传里面还包含了'\0'

总的来说就是少些了一个string()这种形式的构造

声明

string(const char* str = "");
~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 = _capacity = 0;
}

1.3 迭代器封装

这里采用最简单的方式模拟string的迭代器,实际上迭代器很复杂

声明

		typedef char* iterator;
		iterator begin();
		iterator end();

  定义

	string::iterator string::begin()
	{
		return _str;
	}
	string::iterator string::end()
	{
		return _str + _size;
	}

声明 

此处是 const string类型的迭代器

		typedef const char* const_iterator;
		const_iterator begin() const;
		const_iterator end() const;

为什么这个声明只能被const string类型使用?如果没有声明定义这种形式,const string s1使用 iterator begin(); 会导致 权限放大

为什么导致权限放大? iterator begin();隐藏的形参是 string* this,传递的是const的类型导致权限放大

为什么const_iterator it1 = s1.begin(); 时候可以it1++? 因为const string* this 限制的是*this而不是this。

定义

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

1.4 遍历string 

 我们已经写好迭代器了,可以进行迭代器的循环

在main函数中,我们先定义一个string类,发生遍历循环,这里使用的是iterator封装的类型

	bit::string s2("hello world");
	bit::string::iterator it2 = s2.begin();
	while (it2 != s2.end())
	{
		cout << *it2 << " ";
		it2++;
	}

以上代码遍历成功,我们再来使用此代码遍历一下

	bit::string s2("hello world");
	bit::string::iterator it2 = s2.begin();
	for (auto e : s2)
	{
		cout << e << " ";
	}

依旧遍历成功,但是注意的是这里如果把下面的代码

		iterator begin();
		iterator end();

改成这样的代码

		iterator Begin();
		iterator end();

 上面的for( auto e : s2)就编译不成功,因为此循环底层是迭代器,接受的是begin() 


一个const string类型的遍历则是这种方式,使用的是const_iterator封装的类型

	const bit::string s1("hello");
	bit::string::const_iterator it1 = s1.begin();
	while (it1 != s1.end())
	{
		cout << *it1 << " ";
		it1++;
	}

 使用此代码也可以发生遍历

	for (auto e : s1)
	{
		cout << e << " ";
	}

 还有一种遍历的形式没有写这里需要引入operator[]

声明

		char& operator[](size_t pos);
		const char& operator[](size_t pos) const;

定义

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

为什么string类型和const string类型不能使用一个还分别声明 定义了这两种形式只写char& operator[](size_t pos);一个行不行?

不行,如果写了const string类型传不过去,因为形参是string *this依旧是权限放大的问题,用到了成员变量_str

为什么是char&?

因为用的是引用,相当于别名,可以对里面的值进行修改

	bit::string s2("hello world");
	bit::string::iterator it2 = s2.begin();
	for (int i = 0; i < s2.size(); i++)
	{
		s2[i] = 'a';
		cout << s2[i] << " ";
	}

1.5 修改数据 

提前开辟好空间 

声明 reserve

		void reserve(size_t n);

定义 用途开空间

void string::reserve(size_t n)
{
	if (n > _capacity)
	{
		char* tmp = new char[n + 1];//n + 1中的1是给\0开的空间
		strcpy(tmp, _str);
		delete[] _str;

		_str = tmp;
		_capacity = n;
	}
}

 声明 push_back,append

void push_back(char ch);
void append(const char* str);

定义 push_back尾插字符,append尾插字符串

	void string::push_back(char ch)
	{
		if (_size == _capacity) {
			size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
			reserve(newcapacity);
		}
		_str[_size] = ch;
		_str[_size + 1] = '\0';
		++_size;
	}
	void string::append(const char* str)
	{
		size_t len = strlen(str);
		if (_size + len > _capacity)
		{
			reserve(_size + len);
		}
		// strcat(_str, str);
		strcpy(_str + _size, str);
		_size += len;
	}

声明 insert,erase

		void insert(size_t pos, char ch);
		void insert(size_t pos, const char* str);
		void erase(size_t pos, size_t len = npos);

 定义 insert可以在某个位置插字符

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

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

 如果pos = 0的话

为什么会报错?

我们先看这样的图

 为什么最后end为0后再一次循环变成这么大?不应该变成-1吗

因为end的类型是size_t类型

http://t.csdnimg.cn/6uIZT这篇文章的练习1的知识点有讲解

那我们如何修改?

把size_t end = _size;改成int end = _size;可以吗?我们试试

我们调试的过程中发现end=-1的时候还进入循环体,原因还是在上面的那篇文章,end与pos的类型不一样,会发生隐式类型转换,有符号会转换成无符号

怎么才能把无符号的改成有符号的呢?

我们把pos强制类型转换int,这样代码就能正常运行了

		int end = _size;

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

 除了上面的方法还有其他办法吗?非得强制类型转换吗?

我们每次都是因为最后的时候end = 0的时候 end再--,要么变成一个很大的数,要么变成-1底层进行无符号转换了,如果我们让end = 0 的时候就结束

之前是下图的方式

 现在把end挪动新_size的最后一个位置,循环体改成_str[end] = _str[end - 1];使判断条件改成end>pos,最后end是不是为0的时候结束循环

代码形式

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

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

 定义 insert在某个位置插入字符串

 这种定义方式和插入某个字符的问题相差不大,只是复杂了一点

	void string::insert(size_t pos, const char* str)
	{
		assert(pos <= _size);
		size_t len = strlen(str);
		if (_size + len > _capacity) {
			reserve(_size + len);
		}
		size_t end = _size + len;
		//while (end >= (int)pos) //使用强转这种方式也可以
		//{
		//	_str[end + len] = _str[end];
		//	--end;
		//}
		while (end > pos+len-1)
		{
			_str[end] = _str[end - len];
			--end;
		}
		memcpy(_str + pos, str, len);
		_size += len;
	}

为什么 循环条件是end > pos + len -1?
根据数学中的各个变量推算得出一共要挪动的次数

 定义erase,消除某段的数

		void erase(size_t pos, size_t len = npos);

在这里要提的是npos 

 这里在类里面声明,因为有多个包含.h的.cpp文件,所以不能在.h类外面定义,在某个定义的.cpp中定义npos,const size_t string::npos = -1; 

因为静态成员变量类似于全局变量---->为什么类似于全局变量?---->全局变量与静态成员变量都在静态区。且全局变量被多个文件包含发生重定义。在实际当中两者在.h中同时声明定义,被多个.cpp文件包含导致发生了重定义,而且他们的解决措施一样就是在.h声明,只在一个.cpp中定义。

http://t.csdnimg.cn/yh88X如果还没有理解建议看看这篇文章中的static

private:
	char* _str;
	int _size;
	int _capacity;

	const static size_t npos;//类里面声明,类外面定义

稍微了解一下

特例:只有整形才有这种特例---既是声明又是定义

const static size_t npos = -1;

不加const就不行,发生报错

 static size_t npos = -1;

不是整形也不行

const static double zz = 2.22;


 声明 operator+=

		string operator+=(char ch);
		string operator+=(const char* ch);

定义  operator+=

string string::operator+=(char ch)
{
	push_back(ch);
	return *this;
}
string string::operator+=(const char* ch)
{
	append(ch);
	return *this;
} 

 首先判断,这里两者返回的都是string行不行?

 我们返回的是 *this ----> 而*this 发生临时对象的拷贝构造,创建了一个与*this一样,但是这里注意的是我们这里没有写拷贝构造,发生的是浅拷贝

临时对象与this指向的空间一样,发生析构的时候,析构了两次,所以不行

然后我们应该如何修改错误,使用string&返回可不可以,可以,为什么?在这里记住引用返回不会生成临时对象,返回的是*this的别名,另外*this在mian函数的栈不会发生析构,所以可以。

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

http://t.csdnimg.cn/MJoME不理解可以看这篇文章的引用返回和传值返回的区别,以及了解上面的赋值重载

另外的方法就是,写一个拷贝构造---进行深拷贝

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

关于拷贝构造为什么使用必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。至于为什么看下面文章 

http://t.csdnimg.cn/2nIRi

声明 operator=

如果不声明当你定义一个 string s1("hello") string s2("hehehe")

s1 = s2 ,无法进行深拷贝

		string& operator=(const string& s);

定义 operator=

	string& string::operator=(const string& s)
	{
		if (this != &s)
		{
			char* tmp = new char[s._capacity + 1];
			strcpy(tmp, s._str);
			delete[] _str;
			_str = tmp;
			_size = s._size;
			_capacity = s._capacity;
		}
		return *this;
	}

 声明 swap

		void swap(string& s);

定义 swap

	void string::swap(string& s)
	{
		std::swap(_str, s._str);
		std::swap(_size, s._size);
		std::swap(_capacity, s._capacity);
	}

 1.6 查找字符

声明 find

		size_t find(char ch, size_t pos = 0);
		size_t find(const char* sub, size_t pos = 0);

 定义 find,查找字符

	size_t string::find(char ch, size_t pos)
	{
		for (size_t i = pos; i < _size; i++)
		{
			if (_str[i] == ch)
			{
				return i - pos + 1;
			}
		}
		return npos;
	}
	size_t string::find(const char* sub, size_t pos)
	{
		const char* p = strstr(_str + pos, sub);
		return p - _str;
	}

这里我们使用了strstr库函数

 从str1中查找str2
http://t.csdnimg.cn/2nIRi

 1.7 取子串

声明 substr

string substr(size_t pos = 0, size_t len = npos);

定义 substr从哪个位置开始取多少长度

string string::substr(size_t pos, size_t len)
{
	//len大于后面剩余字符,有多少取多少
	if (len > _size - pos)
	{
		string sub(_str + pos);
		return sub;
	}
	else
	{
		string sub;
		sub.reserve(len);
		for (size_t i = 0; i < len; i++)
		{
			sub += _str[pos + i];
		}

		return sub;
	}
}

 1.8 比较大小

定义 


		bool operator<(const string& s) const;
		bool operator>(const string& s) const;
		bool operator<=(const string& s) const;
		bool operator>=(const string& s) const;
		bool operator==(const string& s) const;

声明 这里使用strcmp 来进行大小的判断,这里还使用复用了

	bool string::operator<(const string& s) const
	{
		return strcmp(_str, s._str) < 0;
	}
	bool string::operator>(const string& s) const
	{
		return !(*this <= s);
	}
	bool string::operator<=(const string& s) const
	{
		return *this < s || *this == s;
	}
	bool string::operator>=(const string& s) const
	{
		return !(*this < s);
	}
	bool string::operator==(const string& s) const
	{
		return strcmp(_str, s._str) == 0;
	}

 1.9 流插入流提取

声明

	istream& operator>> (istream& is, string& str);
	ostream& operator<< (ostream& os, const string& str);

 定义

	istream& operator>> (istream& is, string& str)
	{
		str.clear();
		char ch;
		ch = is.get();
		char buff[32];//缓冲数组
		size_t i = 0;
		while (ch != ' ' && ch != '\n')//读字符读不到空格
		{
			buff[i++] = ch;
			if (i == 31)
			{
				buff[32] = '\0';
				str += buff;
				i = 0;
			}
			ch = is.get();
		}
		if (i > 0)
		{
			buff[i] = '\0';
			str += buff;
		}
		return is;
	}
	ostream& operator<< (ostream& os, const string& str)
	{
		for (size_t i = 0; i < str.size(); i++)
		{
			os << str[i];
		}
		return os;
	}

cin会跳过空白字符,比如空格和换行,而get()从输入缓冲区读取单个字符时不忽略分隔符

1.10 简便写法

传统写法的拷贝

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

老老实实的开辟一个空间,把size,capacity都赋值过去,然后把里面的内容赋值过来 

现代写法---->让编译器去干活

我创建一个变量tmp去拷贝构造,然后与*this发生交换

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

传统写法的赋值

	string& string::operator=(const string& s)
	{
		if (this != &s)
		{
			char* tmp = new char[s._capacity + 1];
			strcpy(tmp, s._str);
			delete[] _str;
			_str = tmp;
			_size = s._size;
			_capacity = s._capacity;
		}
		return *this;
	}

现代写法 

	string& string::operator=(const string& s)
	{
		if (this != &s)
		{
			string tmp(s._str);
			swap(tmp);
		}
		return *this;
	}

创建一个变量,和s一模一样,然后让tmp与this指向的方向一换,最后让this析构,过河拆桥 

二、string-练习题 

2.1 字符串相加 

核心思想采用高位补0 。

先转换为整数,但是整数有最大限制,只能一个字符一个字符的转换,利用数学思想进行加法运算,位数不够采用高位补0。 

class Solution {
public:
    string addStrings(string num1, string num2) {
        int end1 = num1.size() - 1;
        int end2 = num2.size() - 1;
        int next = 0;
        string ans;
        while(end1 >= 0 || end2 >= 0)
        {
            //用char减去‘0’,得到int值,若该位不存在则为0
            int val1 = end1 >= 0 ? num1[end1]-'0' : 0;
            int val2 = end2 >= 0 ? num2[end2]-'0' : 0;
            //相加
            int sum = val1 + val2 + next;
            next = sum / 10;
            sum = sum % 10;
            //将该位相加的结果存到ans字符串中
            ans += ('0'+sum);
            //更新end1、end2
            --end1;
            --end2;
        }
        if(next == 1)
        {
            ans += ('1');
        }
        reverse(ans.begin(), ans.end());
        return ans;
    }
};

 2.2 字符串中的第一个唯一字符

这里采用的是先计算次数,再找次数为1的字符 

class Solution {
public:
    int firstUniqChar(string s) {
        int cout[26] = {0};
        for(int i = 0; i < s.size(); i++)
        {
            cout[s[i]-'a']++;
        }
        for(int i = 0;i < s.size(); i++)
        {
            if(cout[s[i]-'a'] == 1)
            {
                return i;
            }
        }
        return -1;
    }
};

2.3 验证回文串 

class Solution {
public:
    bool isPalindrome(string s) {
        //先统一
        string s2;
        for(int i = 0; i < s.size(); i++)
        {
            //只要大小写,数字
            if(('a' <= s[i] && s[i] <= 'z') || ('A' <= s[i] && s[i] <= 'Z') || ('0' <= s[i] && s[i] <= '9') )
            {
                //把大写转变成小写
                if('A' <= s[i] && s[i] <= 'Z')
                {
                    s[i] += 32;
                }
                //小写字母加到新字符串
                s2 += s[i];
            }
        }
        cout<<s2<<endl;
        int left = 0;
        int right = s2.size()-1;
        while(left<=right)
        {
            if(s2[left] != s2[right])
            {
                return false;
            }
            left++;
            right--;
        }
        return true;
    }
};

在这里学习几个库函数 

检查是否是字符数字,字母

检查是不是字母

检测是不是字符数字

把大写字母转换成小写字母

把小写字母转换成大写字母

这里是判断是不是小写,大写,字母的 

上面代码(把大小写字符数字保留下来)~可以转换成这样

        for(char ch: s)
        {
            if(isalnum(ch))
            {
                s2 += tolower(ch);
            }
        }

 2.4 字符串相乘

思路:

拆分,相加

class Solution {
public:
    string multiply(string num1, string num2) {
        if (num1 == "0" || num2 == "0") {
            return "0";
        }
        int end1 = num1.size() - 1;
        int end2 = num2.size() - 1;
        int next = 0;
        string num3;
        int cont = 0;
        string string_sum = "0";
        for(int i = end1; i >= 0 ; i--)
        {
            int _num1 = num1[i] - '0';
            for(int j = end2; j >= 0; j--)
            {
                int _num2 = num2[j] - '0';
                int sum = _num1 * _num2 + next;               
                num3.push_back(sum%10 + '0');
                next = sum/10;
            }
            if(next>0)
            {
                num3.push_back(next+'0');
            }
            reverse(num3.begin(),num3.end());
            cout<<num3<<endl;
            for(int i = 0;i<cont;i++)
            {
                num3 += '0';
            }
            string_sum = Add(string_sum,num3);
            cont++;
            num3.clear();
            next = 0;
        }
        return string_sum;
    }
    string Add(string num1,string num2)
    {
        int end1 = num1.size() - 1;
        int end2 = num2.size() - 1;
        int next = 0;
        string ans;
        while(end1 >= 0 || end2 >= 0)
        {
            int val1 = end1 >= 0 ? num1[end1]-'0' : 0;
            int val2 = end2 >= 0 ? num2[end2]-'0' : 0;
            int sum = val1 + val2 + next;
            next = sum / 10;
            sum = sum % 10;
            ans += ('0'+sum);
            --end1;
            --end2;
        }
        if(next == 1)
        {
            ans += ('1');
        }
        reverse(ans.begin(), ans.end());
        return ans;
    }
};

  • 15
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值