雁过留声 发表于 2024-9-22 16:58:17

C++——模拟实现string

1.再谈string

string为什么要被计划成模板?日常使用string好像都是char*,char*不够使用吗,为什么要计划成模板呢?
1.1 关于编码

//计算机的存储如何区分呢?

int main()
{
        //比如在C语言中,有整型
        //如果是有符号的话,设计了原、反、补码
        //负数要存储它的补码,方便运算
        //浮点数要设计到存精度等问题

        int a = 0;

        return 0;
}
那么怎样去更好地表示其它的各种文字呢?
1.2 ASCII编码

ASCII_百度百科 (baidu.com)
https://i-blog.csdnimg.cn/direct/489231a24cb4474bb37000ec31e28945.png
   ASCII编码表示美国信息技能的编码,在一开始计划时,只思量了存储美国的文字信息

内存中存储字母时,不会把字母存储进去,内存中都是0、1组合的二进制代码,以是,要在二进制代码代表的值与符号间,建立一个一一映射对应的关系,这个关系表,一般称为编码表

ASCII表就是值与美国的文字所建立的拥有一一映射对应的关系的编码表

int main()
{       
    char str[] = "hello world";
        return 0;
}

//要显式时,发现它是字符串,会去查它的编码表 https://i-blog.csdnimg.cn/direct/b2ebb10a6022419b925e24081076ab0e.png

1.3 unicode

同一码_百度百科 (baidu.com)
https://i-blog.csdnimg.cn/direct/7906bc4f5ae5433ab38b9a6af77f5377.png
计算机向全世界推广后,不单单需要能够表示汉字,还需要能够表示其他国家的文字符号,有些国家的文字比较简单,有些可能比汉字还要复杂。因此,计划出了万国码。

   以是要表示汉字,也要使得每个汉字都对应一个数字
那怎样表示汉字呢?也是用一个byte表示一个汉字吗?
要知道1个byte才8个bit位,如许的话才只能表示256个汉字,显然不能很好地表示

方案有三种:
1.3.1 UTF-8

https://i-blog.csdnimg.cn/direct/8eb2b2cd72bb4671a4f5e9c4d11a69d0.png
int main()
{
        //UTF-8   主流使用,一个值可能在多个字节上面存储
        //但windows喜欢使用gbk,linux喜欢使用UTF-8

        char str1[] = "hello world";//12byte
        char str2[] = "工大";//5byte
        char str3[] = "工大 hello";//可以混着使用,11byte

        cout << sizeof(str1) << endl;
        cout << sizeof(str2) << endl;
        cout << sizeof(str3) << endl;

        char* a = str3;
        cout << *a << endl;

    return 0;
}

   UTF-8的缺点:变长,意味着辨认比较复杂,太差异一,好比做屏蔽时;
或是有些情况下文字不需要兼容ASCII,这时就要使用其它方式了

1.3.2 UTF-16

https://i-blog.csdnimg.cn/direct/3c71f9889e24479e862d672987f7a74a.png
1.3.3 UTF-32

https://i-blog.csdnimg.cn/direct/4434cf82db784b93a2f66c0f6b1c0f0e.png
1.4计划成模板的原因

https://i-blog.csdnimg.cn/direct/02aa042eb71643278f2c4595274cb11a.png
int main()
{
        //为了更好地兼容这两种编码,类型进行了延申
        //C++11之前,设计出了宽字节,一个char占据2byte
    //用来更好地表示其它的编码,比如UTF-16
       
    wchar_t ch1;        //宽字节
        cout << sizeof(ch1) << endl;//2byte

        char16_t ch2;
        char32_t ch3;

        cout << sizeof(ch2) << endl;//2byte
        cout << sizeof(ch3) << endl;//4byte

    return 0;
} 这也是string要计划成模板的原因,它可以传差异的模板参数,好比char16_t、char32_t等,可以实用差异的编码,表示更多国家的差异的文字。
https://i-blog.csdnimg.cn/direct/963c7647aff647d49bc87313baabf843.png
1.5作甚乱码

   好比某文字默认使用UTF-8来存储,但表现时没有使用对于的编码表来查找,就会出现乱码。
存储格式和表明方式没有对应起来
https://i-blog.csdnimg.cn/direct/a5b5e4172663465e89be35745277b7dc.png

1.6GBK

GBK字库_百度百科 (baidu.com)
固然unicode是全世界的编码,但它未必非常适合汉字的表达。GBK是中国创造的、更适合汉字表达的编码表。
https://i-blog.csdnimg.cn/direct/169830d3c9c04296a74fac736d0df428.png
int main()
{
        char s1[] = "你好!!";

        s1++;//陪
        s1++;
        s1++;
        s1++;

        s1++;//耗
        s1++;
        s1++;//号

        //在净网行动中非常有用
        //黑名单词库

    return 0;
} 2.模拟实现string

2.1无参的构造和析构

//string.h

#pragma once

namespace jxy
{
        class string
        {
        public:

//初始化的顺序是按照声明的顺序,而不是初始化列表出现的顺序

                string(const char* str)
                        :_size(strlen(str))
                        ,_capacity(_size)//capacity一般不算'\0'
                {
                        _str = new char;//开空间时要多开一个
                        strcpy(_str, str);//strcpy会把'\0'也拷贝过去
                }

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

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

        void test_string1()
        {
                string s1("hello world");
                cout << s1.c_str() << endl;

        }
}


//Test.cpp

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
//这里间接包了string的相关函数

//#include"string.h"
//注意,如果写在这里可能会报错,因为std的展开在下面
//编译器为了追求编译速度,只会向上查找

using namespace std;
#include"string.h"//编译器实际编译时是没有.h的,在预处理阶段就在.c或者.cpp展开了

int main()
{
        jxy::test_string1();

        return 0;
} 2.2单参数的构造

错误示范:
namespace jxy
{
        class string
        {
        public:

                string()
                        :_str(nullptr)
                        ,_size(0)
                        ,_capacity(0)
                {}

        };

        void test_string1()
        {
                string s2;
                cout << s2.c_str() << endl;
               
      //这样会崩溃
                //char*有个特点,自定义类型识别会以字符串去识别,会直接去解引用,遇到'\0'才终止
                //这里空指针解引用就会出错

                std::string s1;
                cout << s1.c_str() << endl;//库里面的是没问题的

        }
} 正确写法1:
namespace jxy
{
        class string
        {
        public:

                string()
                        :_str(new char{'\0'})//这里需要开空间
                        ,_size(0)
                        ,_capacity(0)
                {
                        //_str = '\0';//或者在这里初始化
                }


        };

        void test_string1()
        {
                string s2;
                cout << s2.c_str() << endl;

        }
} 正确写法2:在无参构造处给上缺省参数
namespace jxy
{
        class string
        {
        public:

                //const char* str="\0" 这样写不够规范
               
                string(const char* str="")//C语言规定常量字符串后面默认有'\0'
                        :_size(strlen(str))
                        ,_capacity(_size)
                {
                        _str = new char;
                        strcpy(_str, str);
                }

        };

        void test_string1()
        {
                string s2;
                cout << s2.c_str() << endl;
        }
} 2.3 size、capacity和c_str

namespace jxy
{
        class string
        {
        public:
                size_t capacity() const
                {
                        return _capacity;
                }

                size_t size() const
                {
                        return _size;
                }

                const char* c_str() const
                {
                        return _str;
                }

        };

        private:
                char* _str;
                size_t _size;
                size_t _capacity;
} 2.4遍历数组

2.4.1[]

namespace jxy
{
        class string
        {
        public:

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

                const char& operator[](size_t pos) const//给const对象使用
                {
                        assert(pos < _size);
                        return _str;
                }

        };
//...
} 2.4.2迭代器

namespace jxy
{
        class string
        {
        public:

                typedef char* iterator;//迭代器可以实现成指针,也可以不
                //可以使用原生指针来代替iterator

                iterator begin()
                {
                        return _str;
                }

                iterator end()
                {
                        return _str+_size;
                }

        };
//...
} 2.4.3范围for

namespace jxy
{
        class string
        {
        //...
        };

        void test_string2()
        {

//1. []
                string s1("hello world");
                for (size_t i = 0; i <s1.size() ; i++)
                {
                        cout << s1 << " ";
                }
                cout << endl;

//2.迭代器
                string::iterator it=s1.begin();//在里面typedef的或者是内部类

                while (it != s1.end())
                {
                        cout << *it << " ";
                        it++;
                }
                cout << endl;

//3.范围for
                //这里还支持范围for
                for (auto ch : s1)//底层代码和上面的迭代器类似
                {
                        //auto ch = *it;
                        cout << ch << " ";
                }
                cout << endl;

                //范围for的本质是替换成迭代器,编译时直接替换过去
                //而且有很严格的规范,名字变化一下都是不可以的
        }
} https://i-blog.csdnimg.cn/direct/59207aabec3b40519a545d09967cc7f6.png
https://i-blog.csdnimg.cn/direct/a74b92e4880d4db497b7f834ab7a1502.png
2.5复用reserve实现尾插

   实现尾插的三种方式:
1.push_back
2.append
3.+=

namespace jxy
{
        class string
        {
        public:

                void reserve(size_t n)
                {
                        assert(n > _capacity);

                        char* str1 = new char;//失败会抛异常
                        strcpy(str1,_str);//会把'\0'拷贝过去

                        delete[] _str;//越界会在这里崩溃
                        _str = str1;
                        _capacity = n;
                }

                void push_back(char ch)
                {//扩容问题
                        if (_size == _capacity)
                        {
                                reserve(_capacity==0 ? 4 :_capacity * 2);

                        }
                        _str = ch;
                        _size++;
                        _str = '\0';
                }

                void append(const char* str)
                {
                        size_t len = strlen(str);
                        if (_size + len > _capacity)
                        {
                                reserve(_size + len);
                        }

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

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

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

        void test_string3()
        {
                string s1("hello world");
                cout << s1.c_str() << endl;

                s1.push_back('0');
                cout << s1.c_str() << endl;

                s1.append("hello lxy");
                cout << s1.c_str() << endl;

                s1 += '$';
                cout << s1.c_str() << endl;

                s1+="hellox";
                cout << s1.c_str() << endl;

        }
} 2.6 insert和erase

insert错误写法:
namespace jxy
{
        class string
        {
        public:
                void insert(size_t pos,char ch)
                {
                        assert(pos <= _size);
                        if (_size == _capacity)
                        {
                                reserve(_capacity == 0 ? 4 : _capacity * 2);

                        }

                        size_t end = _size;//头插会出问题,这里是无符号数

                        //int end = _size; //改为有符号数也是行不通的
                        //在操作符,如 >= 两边,类型不同时,会发生类型提升
                        //这里end虽然是有符号数,但会被提升成无符号数
                        while (end >= pos)
                        {
                                _str = _str;
                                end--;
                        }
                        _str = ch;
                        _size++;

                }
//...
        };

        void test_string4()
        {
                string s1("hello world");
                cout << s1.c_str() << endl;

                s1.insert(5, '#');
                cout << s1.c_str() << endl;

                s1.insert(s1.size(), '#');
                cout << s1.c_str() << endl;

                s1.insert(0, '#');//头插
                cout << s1.c_str() << endl;
        }

} 正确写法1:
namespace jxy
{
        class string
        {
        public:
                void insert(size_t pos,char ch)
                {
                        assert(pos <= _size);
                        if (_size == _capacity)
                        {
                                reserve(_capacity == 0 ? 4 : _capacity * 2);

                        }

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

                }
//...
        };
} 正确写法2:
namespace jxy
{
        class string
        {
        public:
                void 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 = _str;
                                end--;
                        }
                        _str = ch;
                        _size++;

                }
//...
        };
}
namespace jxy
{
        class string
        {
        public:

                void insert(size_t pos,size_t len=npos)
                {

                }

                void insert(size_t pos,const char* str)
                {

                }

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

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

//注意
                //const static size_t npos =-1;//特例
                //这里是特殊情况,按理说不能在这里初始化
                //这里给值给的是缺省值,缺省值是给初始化列表使用的
                //静态成员变量不会去执行初始化列表,它不属于对象,它属于整个类,按理说要在类外面初始化
                //但是const修饰的静态的整型可以,所以这里既是定义,又是定义初始化
               
                //const static double npos = 1.2;//这样都不会去支持

                const static size_t npos;
        };

        const size_t string::npos = -1;
} 2.7运算符重载

namespace jxy
{
        class string
        {
        public:

//运算符重载
//设计成非成员函数更好,便于模板的使用
                bool operator<(const string& str1)
                {
                        return strcmp(_str, str1._str)<0;
                }

                bool operator==(const string& str1)
                {
                        return strcmp(_str, str1._str)==0;
                }

                bool operator<=(const string& str1)
                {
                        return *this<str1 || *this==str1;
                }

                bool operator>=(const string& str1)
                {
                        return !(*this<str1);
                }

                bool operator>(const string& str1)
                {
                        return !(*this<=str1);
                }

                bool operator!=(const string& str1)
                {
                        return !(*this == str1);
                }
//...
        };
} 2.8流插入

namespace jxy
{
        class string
        {
    public:

                typedef const char* const_iterator;//const迭代器指向的内容不能修改
                //指针本身可以修改,指向的内容不能修改

                const_iterator begin() const
                {
                        return _str;
                }

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

    //...
        };

        //没有必要定义为友元,想访问私有成员变量才需要定义为友元
        ostream& operator<<(ostream& out, const string& s)
        {
                1.
                //for (size_t i = 0; i < s.size(); i++)
                //{
                //        out << s;
                //}
                //return out;

                //2.可以使用范围for,但要替换成const迭代器
                for (auto ch : s)
                        out << ch;

                return out;
        }
} 2.9流提取和clear

namespace jxy
{
        class string
        {
    public:
                void clear()
                {
                        _str = '\0';
                        _size = 0;
                }

    //...
        };

        //没有必要定义为友元,想访问私有成员变量才需要定义为友元

        istream& operator>>(istream& in,string& s)
        {//流提取要从缓冲区中取一个个的字符
                s.clear();

                char ch;
                //in >> ch; //这样写获取不到空格
                ch = in.get();
                while (ch != ' ' && ch != '\n')
                {
                        s += ch;
                        //in >> ch;
                        ch = in.get();

                }
                return in;
        }

        void test_string6()
        {
                string s1("hello world");
                cout << s1 << endl;

                cin >> s1;
                cout << s1 << endl;

        }
}

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: C++——模拟实现string