• 植物大战 string——C++


    “朝朝暮暮”

    猛戳订阅🍁🍁 👉 [C++详解专栏] 👈 🍁🍁

    一、编码

    为什么string不能针对char来写,因为编码不同,。char只能表示256个字符。
    所以这时候要用模板

    string管理的是一个char*的字符串。
    u16string:一个字符是两个字节
    u32string:一个字符是四个字节
    wstring:叫做宽字符,一个字符占两个字节

    1.ASCII码

    ASCII码表。是美国设计的。
    ASCII码表是:计算机当中存的值,和字符的映射
    但是只有256个字符的表示。用char表示

    2.unicode编码

    也叫做万国码

    Unicode是针对全世界的语言而设计的一种编码。

    常见的有utf-8 utf-16 utf-32

    3.gbk

    gbk是叫做国标码。是针对中文创建的一个编码。其中还涉及台湾的。

    计算机上不止有英文,还要有中文,日文等语言。但是ASCII码表不足以表示。

    二、string的使用

    string是C++标准库的一部分,但是不属于stl。

    使用就不多讲了,详细操作多看C++参考网站
    因为你不可以全记住。记不住很正常,可以查。

    网站如下。
    C++reference

    string是typedef出来的,原生叫做basic_string
    可以很好兼容char字符。
    在这里插入图片描述linux下用的是utf-8,Windows一般用ASCII

    string的头文件就是,C++不分头文件不分后缀。

    为了和C语言的string.h做区分,所以可以不加.h。

    string是属于std命名空间的。所以需要加上using namesapce std。

    学习类先学习构造

    先不学C++11的右值引用。
    学习string不是要学全部的函数,学习常用的函数就行,常用的函数如下。string的函数100多个,不认识的可以查文档。

    1.构造函数

    string类一个管理动态增长字符的数组,可以认为这个就是字符数组,因为要兼容C语言,所以结尾是\0。

    注意npos是负一。负一的整形是全1,也就是**整形最大值。**这个参数是默认的,原因为,假如不给参数,给的就是整形最大值。
    在这里插入图片描述

    无参构造函数string()

    千万不要在创建对象的时候加括号()。和普通函数的调用不一样。

    string s1;
    
    • 1

    常量字符串构造string(const char* s)

    注意:缺省参数不能给nullptr,这样传过去就崩了,只能给双引号也就是空字符串“”.
    空字符串后面默认有\0

    这个参数是Cstring类型的。

    string s1("hello");
    
    • 1

    底层实现是开一段空间存放所传递的hello字符串的。
    为什么底层要这么麻烦?非要开一个空间,再把东西拷贝过去?
    因为这样可以支持后期的增删查改
    假如直接赋值的话后期不支持增删查改。

    template<class T>
    class basic_string
    {
    public:
    	basic_string(const T* str)
    	{
    		size_t len = strlen(str);
    		//开空间,方便增删查改。
    		_str = new T[len+1];
    		strcpy(_str, str);
    private:
    	T* _str;
    	size_t _size;
    	size_t _capacity;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    拷贝构造string(const string& s)

    string s1(s2);
    
    • 1

    2.析构函数

    析构函数就简单了,析构函数是自动调用的。

    3.遍历string

    重载operator[] (size_t i)

    第一种方式是下表+[]
    string 重载下表[].
    重载的意义:在于让自定义类型用着像内置类型
    内置类型是原生的指针,直接去内存空间找偏移了多少。
    可以像数组一样使用下表访问其中的每个元素

    重载函数[]返回的是char的引用
    返回引用的意义:在于可以支持连续赋值。这样字符可以也可以修改
    假如是自定义类型还可以减少拷贝。

    重载const[]。一般重载[]还有一个针对const对象的,const对象只可读不可修改,所以返回值类型也必须是const引用,也要在函数的括号()加上const。

    方式一
    相当于调的函数s1.operator[] (i);

    for(size_t i = 0; i < s1.s1.size();i++)
    {
      	cout << s1[i] << endl;
    }
    
    • 1
    • 2
    • 3
    • 4

    还有一个at()函数和重载[]效果一样,不同点在于at会检查越界,以断言的方式进行检查的。
    at一般不用,都是用[].

    迭代器遍历

    为什么要用迭代器?现阶段学的string底层物理空间连续,可以用数组下表访问,但是当学了map等底层空间不连续的容器,用迭代器非常的方便!因为它是像指针一样的东西。

    迭代器是内嵌在string整个类中。vector,list,map通用,都内嵌在各自的类中。
    一般使用如下。
    方式二

    string::iterator it = s1.begin()
    while(it != s1.end())
    {
    	cout << *it << " ";
    }
    cout << endl;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    迭代器是像指针一样的东西。

    具体要看底层的具体实现。

    重点
    begin()返回的第一个元素的位置。
    end()是指向最后一个数据的下一个位置。

    本质上属于左闭右开 [ )

    一般迭代器的标准语法都是用不等于。这样的话可以使得vector,map,set更加的使用。因为他们不是连续的结点。地址不连续。就像vector的迭代器是封装的

    迭代器遍历也有注意点。
    const迭代器
    假设有个函数func,用普通迭代器遍历不了。
    普通迭代器具有读和写的能力。

    形式参数是const对象接收的。假如传的是普通对象,就会报错。

    void Func(const string& s){}
    
    • 1

    在这里插入图片描述

    1.const迭代器返回的是const的迭代器对象。这样传参就不会报错了
    反向迭代器的const_reverse_iterator也如此。
    假如感觉类型太长了。可以用atuo来简便书写。

    反向迭代器
    reverse_iterator
    注意:
    1.反向迭代器是倒着走的。
    2.rbegin()在最后一个数据的位置。
    3.rend()在第一个数据的前一个位置。

    string reverse_iterator rit = s.rbegin()
    while(rit != s.rend())
    {
    	cout << *rit << " ";
    }
    cout << endl;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    范围for

    范围for是C++11的。底层还是迭代器
    实际的使用会被替换成迭代器。

    在linux中使用这个语法需要加上-std=c++11

    for (auto ch : s1)
    {
     	cout  << ch << " ";
    }
    cout << endl;
    
    • 1
    • 2
    • 3
    • 4
    • 5

    4.插入

    reserve()

    作用reserve可以提前开空间只改变容量。但是在vs下不会缩容量。

    push_back扩容在g++下面是1.5倍扩容的。
    在vs下面是

    因为扩容有代价,所以有什么方法可以减少扩容?
    这时候就有一个函数就叫做reserve()

    resize

    作用:开空间+初始化。可以改变size

    push_back(size_t i)

    append

    作用:一般用来在尾部插入字符串。尾插的时候使用。但是日常一般不实用,一般都用重载后的+=

    insert

    作用:一般在pos位置插入字符或者字符串(对象)

    c_str()

    作用:返回指向字符串的指针,可以很好地和C语言的接口配合。

    find()

    作用:从pos(默认为0)位置查找某个字符或者字符串。如果不匹配返回npos。

    substr

    作用:取得pos位置的字符串。

    三、string模拟实现

    string 当前不涉及模板,所以比较简单。

    1.浅拷贝

    strcpy拷贝的时候会把\0也拷贝过去。

    浅拷贝:我们不写,编译器会默认生成一个拷贝构造函数,这个拷贝是值拷贝。也叫做浅拷贝

    浅拷贝没有写拷贝构造函数,完成值拷贝。
    这样的缺点是:
    1.被析构两次
    2.改变一个对象,会牵涉到另一个对象。

    string s1("hello");
    //析构报错,同一块内存空间析构两次。
    string s2(s1);
    
    • 1
    • 2
    • 3

    实际上内存空间不是你用完后就销毁了。而是使用权操作系统回收了。回收后可能会分配给其他人用。

    内存空间就像是你租的房子,房租到了你只会还一次,而不是还两次,你还的第二次可能是别人的房子。你不能拿着别人的房子还,因为你没有使用权。

    2.深拷贝

    深拷贝需要自己写。
    1.先另开一个和原空间一样大的内存空间。
    2.再拷贝数据。
    s2(s1)

    string(const string& s)
    		//浅拷贝
    		//:_str(s._str)
    		:_str(new char[strlen(s._str+1)])//深拷贝
    {
    	strcpy(_str, s._str);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    3.赋值重载构造

    赋值重载是在两个都已经存在的对象赋值。

    有两个问题
    1.怕空间不够,空间不够没法拷贝。
    2.怕空间太大,浪费了空间。

    所以在实际中一般都不会直接拷贝。
    1.先把原空间释放
    2.再开一个和要赋值对象一样大小的新空间。
    3.拷贝数据。

    注意,引用返回的原因有两个。
    1.出了作用域,对象没有销毁。
    2.返回的是一个临时对象,临时对象还需要完成拷贝,而string完成的是深拷贝,代价有点大,所以需要返回引用。

    string& operator=(const string& s)
    {
    	delete[] _str;
    	_str = new char[strlen(s._str)+1];
    	strcpy(__str,s._str);
    
    	return *this;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    上面代码有些瑕疵,万一开空间失败了呢?
    失败了的话,不仅新空间没有了,我的_str也没了。
    所以推荐下面写法。
    这是传统的写法。还有一种现代写法。

    string& operator=(const string& s)
    {
    	char* tmp = new char[strlen(s._str)+1];
    	strcpy(tmp,s._str);
    	delete[] _str;
    	_str = tmp;
    	return *this;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    4.增删查改

    reserve

    C++扩容不用考虑realloc。

    void reserve(size_t n)
    {
    	if(n > _capacity)
    	{
    		char* tmp = new char[n+1];
    		strcpy(tmp,_str);
    		delete[] _str;
    		_str = tmp;
    		_capacity = n;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    push_back

    void push_back(char ch)
    {
    	if(_size == _capacity)
    	{
    	//开空间,拷贝,释放原来空间,管理新空间
    		/*
    		char* tmp = new char[_capacity*2+1];
    		strcpy(tmp,_str);
    		delete[] _str;
    		_str =  tmp;
    		_capacity *= 2;
    		*/
    		//复用reserve
    		reserve(_capacity == 0 ? 4 :_capacity * 2);
    	}
    		//赋值
    		_str[size] = ch;
    		++_size;
    		_str[_size] = '\0';
    	
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    append

    void append(const char* str)
    {
    	size_t len = _size + strlen(str);
    	//如果大于当前容量,扩容
    	if(len > _capacity)
    	{
    		reserve(len);
    	}
    	strcpy(_str+_size,str);
    	_size = len;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    operator+=

    一般尾部加字符或字符串都是用+=,而不用push_back或者append.

    string& operator+=(char ch)
    {
    	push_back(ch);
    	return *this;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    string& operator+=(const char* str)
    {
    	append(str);
    	return *this;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    resize

    有三种情况。假如容量是15,_size是11.

    在这里插入图片描述

    void resize(size_t n, char ch = '\0')
    {
    	//如果小于size
    	if(n < _size)
    	{
    		_size = n;
    		_str[_size] = '\0';
    	}
    	//如果大于size
    	else
    	{
    		//假如n比size大,比容量小
    		//如果n大于容量,也就是容量不够。
    		if(n > _capacity)
    		{
    			reserve(n);
    		}
    			for(size_t i = _size; i < n; i++)
    			{
    				_str[i] = ch;
    			}
    			_size = n;
    			_str[_size] = '\0';
    	}
    	
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    insert

    在任意位置前插入数据。

    	//在pos位置前插入字符
    		string& insert(size_t pos, char ch)
    		{
    			assert(pos <= _size);
    			if (_size == _capacity)
    			{
    				reserve(_capacity == 0 ? 4 : _capacity * 2);
    
    			}
    			char* end = _str + _size;
    			//将pos位置及其之后的字符向后挪动一位
    			while (end >= _str + pos)
    			{
    				*(end + 1) = *end;
    				end--;
    			}
    			_str[pos] = ch;
    			_size++;
    			return *this;
    		}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    erase

    npos是static的,static变量在类内声明,类外定义
    erase有个npos
    这个比较有意思。

    //erase删除pos位置后的len个字符,len默认为npos
    		string& erase(size_t pos, size_t len = npos)
    		{
    			assert(pos < _size);	
    			size_t n = _size - pos;
    			if (len >= n)//说明pos位置后面的字符全部删除
    			{
    				_size = pos;
    				_str[_size] = '\0';
    			}
    			else
    			{
    				strcpy(_str + pos, _str + pos + len);
    				_size -= len;
    			}
    			return *this;		
    		}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    5.迭代器

    string迭代器没啥说的,和指针差不多,当作指针用就行。

    需要注意的是
    const迭代器是返回值是const的。
    因为返回值是const的,才能避免值被修改。

    typedef const char* const_iterator;
    const_iterator begin() const
    {
    	return _str;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    6.<<流插入

    友元函数是为了访问私有,才设置的。

    流插入必须写成全局的,这样out对象才能抢占第一个参数。
    一般不用out << s打印字符串。

    一般使用如下写法
    因为输出字符串打印不出来\0.

    	ostream& operator<<(ostream & out, const string & s)
    	{
    		//使用范围for遍历字符串并输出
    		for (auto e : s)
    		{
    			cout << e;
    		}
    		return out; //支持连续输出
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    7.>>流提取

    流提取的第二个参数不能加const了,因为需要放字符串到里面。
    要注意不能用In >> ch 因为in 还是调用的cin不能获取空格,但是get()是从缓冲区获取字符的

    	istream& operator>>(istream& in, string& s)
    	{
    		s.clear();
    		//in >> ch;//这样写不可以。
    		char ch = in.get();//读取一个字符
    		while (ch != ' ' && ch != '\n')
    		{
    			s += ch;//将读取到的字符尾插到字符串后面
    			//in >> ch;
    			char ch = in.get();//读取一个字符
    		}
    		return in;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
  • 相关阅读:
    StripedFly恶意软件:悄无声息运行5年,感染百万设备
    【计算机网络】第三章:数据链路层
    下厨房网站月度最佳栏目菜谱数据获取及分析
    JDK -- 日期时间类
    数据治理建设管理方案(参考)(一)
    EXCEL 求解线性规划问题
    织信Informat如何连接其他应用?
    【ESP 保姆级教程】疯狂EspNow篇 —— 案例:ESP8266 + EspNow简单实剖析底层实现原理
    不借助 Javascript,利用 SVG 快速构建马赛克效果
    赶紧进来看看---C语言实现学生信息管理系统(3.0文件存储版)
  • 原文地址:https://blog.csdn.net/qq2466200050/article/details/127126616