C++模拟实现string类

源代码(含注释与细节讲解)

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <algorithm>
#include <utility>
#include <assert.h>

using namespace std;
//using namespace mystring;

namespace mystring {
    class string {
    private:
        char* _a;
        size_t _size;
        size_t _capacity;
        //静态常量
    public:
        const static size_t npos;//静态常量类型npos
        //一、构造、析构、赋值重载
    public:
        无参构造
        //string()
        //{
        //    _a = nullptr;
        //    _size = 0;
        //    _capacity = 0;
        //}
        指针拷贝构造
        //string(const char* s)
        //    //:_a(s)//1.不能直接将常量字符串s赋值给_a,因为类型不匹配(_a得到的是常量字符串s的地址)
        //            //应当自己申请空间,再将常量字符串拷贝过去
        //    :_size(strlen(s))
        //{
        //    _capacity = _size;//2.细节优化,_capacity不使用初始化列表,可以少调用一次strlen函数
        //    _a = new char[_size + 1];//3.开空间需要多开一个空间存放\0,但是_size和_capacity不需要
        //    strcpy(_a, s);           //为什么要多开一个空间存放\0?
        //}                            // 因为C++需要兼容C语言,使用c_str返回常量字符串时,如果末尾没有标识符\0,则打印该字符串后面就会出现乱码
                                                     //因为_size是看存了多少个有效字符,_capacity是看能存多少个有效字符
                                                     //即使_capacity为0,也要开一个空间,存放\0

        //再优化:无参构造与指针拷贝构造合并
        string(const char* s = "")//4.设置默认参数为"",常量字符串结尾默认有一个\0
            :_size(strlen(s))     //不可以使用'\0',因为strlen()只能计算字符串
        {                         //也不建议使用"\0",因为常量字符串结尾默认有\0,相当于有两个\0,
            _capacity = _size;
            _a = new char[_size + 1];
            strcpy(_a, s);
            //_a[_size] = '\0';//5.不需要手动在数据末尾或者多开的位置存放\0,因为strcpy会拷贝空字符串中的\0到多开的位置
        }
        //6.还有一个无参构造与指针拷贝构造合并的原因是,string类中有一个函数是c_str
        //该函数是返回C语言的字符串
        //如果无参构造中_a的值设置为nullptr,那么c_str返回的就是空指针
        //在打印输出时,不能打印空指针,所以报错!
        //如果不想合并,无参构造函数应该如下这样
        //无参构造
        //string()
        //    :_a(new char[1])
        //    ,_size(0)
        //    ,_capacity(0)
        //{
        //    _a[0] = '\0';
        //}

        //拷贝构造
        //string(const string& str)
        //    :_a(new char[str._capacity + 1])
        //    , _size(str._size)
        //    , _capacity(str._capacity)
        //{
        //    strcpy(_a, str._a);
        //    //_a[_size] = '\0';//7.不需要手动在数据末尾或者多开的位置存放\0,因为strcpy会拷贝空字符串中的\0到多开的位置
        //}

        //拷贝构造函数的现代写法
        string(const string& str)
        {
            string tmp(str._a);
            swap(tmp);
        }

        //子串构造
        string(const string& str, size_t pos, size_t len = npos)
        {
            size_t length = str._size - pos;//从pos位置开始剩余的数据长度
            if (len > length)
            {
                _a = new char[length + 1];
                _size = length;
                strcpy(_a, str._a + pos);//8.从pos位置拷贝数据,len长度大于数据剩余长度,将剩余数据全部拷贝过来,空间刚好够,最后多开的空间存放\0;
            }
            else
            {
                _a = new char[len + 1];
                _size = len;
                for (size_t i = pos, j = 0; i < pos + len; ++i, ++j) _a[j] = str._a[i];//9.从pos位置拷贝数据,不能用strcpy函数    
                _a[_size] = '\0';//10.数据末尾(即最后多开的空间)需要手动赋值为\0        //因为strcpy函数要求目标地址空间大小必须大于源地址空间大小
            }
        }

        //指针限定拷贝构造函数
        string(const char* s, size_t n)
            :_a(new char[n + 1])
            , _size(n)
            , _capacity(n)
        {
            //strcpy(_a, s);//11.不能使用strcpy函数拷贝,因为strcpy函数要求目标地址空间大小必须大于源地址空间大小
            for (size_t i = 0; i < n; ++i) _a[i] = s[i];
            _a[_size] = '\0';//12.数据末尾(即最后多开的空间)需要手动赋值为\0
        }
        //填充构造函数
        string(size_t n, char c)
            :_a(new char[n + 1])
            , _size(n)
            , _capacity(n)
        {
            for (size_t i = 0; i < n; ++i) _a[i] = c;
            _a[_size] = '\0';//13.数据末尾(即最后多开的空间)需要手动赋值为\0
        }

        //析构函数
        ~string()
        {
            delete[] _a;
            _a = nullptr;
            _size = 0;
            _capacity = 0;
        }

        //赋值运算符重载operator=
        //string& operator=(const string& str)
        //{
        //    char* tmp = new char[str._capacity + 1];//14.赋值运算符重载不同于拷贝构造,原对象中存在数据,需要将原对象中的数据销毁,重新开辟新空间拷贝
        //    strcpy(tmp, str._a);
        //    delete[] _a;
        //    _a = tmp;
        //    _size = str._size;
        //    _capacity = str._capacity;
        //}

        //赋值运算符重载的现代写法
        string& operator=(const string& str)
        {
            string tmp(str);
            swap(tmp);
            return *this;
        }
        //二、容量操作
    public:
        //求大小
        size_t size() const//1.设置为常函数,普通对象和常对象都可以调用
        {
            return _size;
        }
        //求容量
        size_t capacity() const
        {
            return _capacity;
        }
        //扩容
        void reserve(size_t n = 0)
        {
            if (n > _capacity)//2.需要加上判断,新空间的大小比旧空间大才能扩容,防止变为缩容数据丢失
            {
                char* tmp = new char[n + 1];//3.开新空间,要多开一个空间存放\0,但\0不是有效数据
                strcpy(tmp, _a);//拷贝数据
                delete[] _a;//释放旧空间
                _a = tmp;
                _capacity = n;//修改容量
            }
        }
        //改变容器
        void resize(size_t n, char c = '\0')
        {
            if (n <= _size)//删除多余数据
            {
                _a[n] = '\0';
                _size = n;
            }
            else
            {
                reserve(n);//4.不管需不需要扩容,都reserve一下,在reserve函数中会自行判断需不需要扩容
                for (size_t i = _size; i < n; ++i)//5.此处需要画图理解
                    _a[i] = c;
                _a[n] = '\0';
                _size = n;
            }
        }

        //清除数据
        void clear()
        {
            _size = 0;
            _a[_size] = '\0';
        }

        //三、元素访问
    public:
        //1.对于[]访问使用较多的函数,会被设置为内联函数,这样键能减少函数栈帧的建立
        //2.为什么将常对象[]访问与普通对象[]访问重载为两个函数呢?
        //直接将函数设置为常函数虽然可以被常对象调用,但是不能防止数据被修改
        //所以需要重载为两个函数,其中常对象的[]访问函数的返回值要加const修饰,由于是返回引用,要防止被修改

        //普通对象[]访问
        char& operator[](size_t pos)
        {
            //assert(pos >= 0 && pos < _size);
            //3.无需判断pos是否>0,因为pos是无符号类型size_t
            assert(pos < _size);
            return _a[pos];
        }
        //常对象[]访问
        const char& operator[](size_t pos) const
        {
            //assert(pos >= 0 && pos < _size);
            assert(pos < _size);
            return _a[pos];
        }

        //四、迭代器
    public:
        typedef char* iterator;
        typedef const char* const_iterator;
        //begin
        iterator begin()
        {
            return _a;
        }
        //常对象begin
        const_iterator begin() const
        {
            return _a;
        }
        //end
        iterator end()
        {
            return _a + _size;
        }
        //常对象end
        const_iterator end() const
        {
            return _a + _size;
        }
        //反向迭代器
        //反向迭代器涉及适配器模式,暂时略过

    //五、修改操作
    public:
        //尾插
        void push_back(char c)
        {
            //2倍扩容
            if (_size == _capacity)
            {
                reserve(_capacity == 0 ? 4 : 2 * _capacity);//1.当容量为0时,2倍扩容后容量依然为0,所以要判断初始容量是否为0
            }
            _a[_size++] = c;//插入
            _a[_size] = '\0';//2.数据末尾需要加上\0,因为原先数据末尾的\0会被新数据覆盖
        }

        //尾插字符串
        string& append(const char* s)
        {
            //数据多少,扩容多少
            size_t len = strlen(s);
            if (_size + len > _capacity)
            {
                reserve(_size + len);//扩容
                _capacity = _size + len;//修改容量
            }
            strcpy(_a + _size, s);//3.使用strcpy函数,将B位置的数据拷贝到A位置(从B位置开始搜索直到\0,包括\0也拷贝过去)
            _size = _size + len;//修改大小
            return *this;
        }

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

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

        //insert单个字符
        string& insert(size_t pos, char c)
        {

            if (_size == _capacity)
                reserve(_capacity == 0 ? 4 : 2 * _capacity);//扩容
            assert(pos <= _size);//4.检查插入位置的合法性

            5.挪动数据
            //int end = _size;//end指向\0位置,因为\0也要挪动
            //while (end >= (int)pos)//6.为什么设置end的类型为int而不是size_t?
            //{                      //因为当pos为0时,--end永远不会小于0,因此程序进入死循环。
            //    _a[end + 1] = _a[end];//所以pos类型设置为int,但int类型的end与size_t类型的pos比较时,会发生算数转换,int会被转换为size_t,程序还会死循环
            //    --end;                //因此还要将pos强制类型转换为int
            //}
            //_a[pos] = c;
            //++_size;
            //6.还有一种方法,也可以解决pos为0时的死循环问题,就是设置end初始为原数据最后位置的后一位
            size_t end = _size + 1;
            while (end > pos)
            {
                _a[end] = _a[end - 1];
                --end;
            }
            _a[pos] = c;
            ++_size;
            return *this;
        }

        //insert字符串
        string& insert(size_t pos, const char* s)//7.insert字符串和insert字符原理相同,只是需要向后多移动几个数据
        {
            assert(pos <= _size);//检查插入位置合法性
            size_t length = strlen(s);
            if (_size + length > _capacity)//扩容
                reserve(_size + length);
            //挪动数据
            size_t end = _size + length;
            while (end > pos + length - 1)//8.此处的数据挪动要画图才能容易理解
            {
                _a[end] = _a[end - length];
                --end;
            }
            strncpy(_a + pos, s, length);//9.拷贝可以使用strncpy函数,与strcpy函数的区别就是不会拷贝\0字符
            //for (size_t i = 0; i < length; ++i)
            //    _a[i + pos] = s[i];
            _size += length;
            return *this;
        }

        //erase
        string& erase(size_t pos = 0, size_t len = npos)
        {
            assert(pos < _size);//检查删除位置合法性
            //if (pos + len >= _size)//删除pos位置后所有数据
            if (len >= _size - pos)//10.此处改为len >= _size - pos是为了防止当len为npos时或len接近npos时,pos + len会溢出
            {
                _a[pos] = '\0';
                _size = pos;
            }
            else//删除pos位置后len个数据
            {
                //挪动数据
                //for (size_t i = pos; i <= _size - len; ++i)
                //{
                //    _a[i] = _a[i + len];
                //}
                //11.挪动数据可以直接利用strcpy函数,只要目标地址空间比源地址空间大即可使用strcpy函数
                strcpy(_a + pos, _a + pos + len);
                _size -= len;
            }
            return *this;
        }

        //swap
        void swap(string& str)//12.常规操作是创建临时对象tmp,再交换两个对象,但是代价较高,需要调用一次拷贝构造+两次赋值运算符重载+一次析构函数
        {                     //(这也是算法库中的swap使用的方法,因此使用时尽量使用string类中swap函数,而不是算法库的函数)
            //string tmp(*this);//可以使用算法库中的swap,直接交换两个对象的成员
            //*this = str;
            //str = tmp;
            std::swap(_a, str._a);//13.需要使用命名空间std,因为编译器查找会根据就近原则
            std::swap(_size, str._size);
            std::swap(_capacity, str._capacity);
        }

        //14.关于swap函数,类内实现了swap函数,全局又实现了一个swap函数,算法库中刚还实现了一个模板swap,为什么要实现这么多swap函数呢?
        //全局中的swap函数底层是调用类内的swap函数实现的,这是为了防止使用swap函数时编译器取调用算法库中的swap函数(算法库中的swap函数是模板函数效率低)
        //类外实现的swap函数和算法库中的swap函数都是全局函数,但是算法库中的swap函数时模板函数,编译器会优先使用非模板函数,底层就睡调用类内swap函数
        //效率更高

    //六、字符串操作
    public:
        //c_str
        const char* c_str() const
        {
            return _a;
        }

        //find查找某个字符
        size_t find(char c, size_t pos = 0) const
        {
            assert(pos < _size);
            for (size_t i = pos; i < _size; ++i)
                if (_a[i] == c) return i;
            return npos;
        }

        //find查找某个字符串
        size_t find(const char* s, size_t pos = 0) const
        {
            //字符串匹配问题,可以使用KMP算法,但是该算法不实用,效率并没有很高
            //实际上可以直接使用暴力算法取匹配
            //此处使用C语言库中的strstr函数
            //strstr函数匹配成功了返回子串的初始位置指针,匹配失败返回空指针
            assert(pos < _size);
            char* p = strstr(_a + pos, s);
            if (p != nullptr) return p - _a;
            else return npos;
        }

        //substr取子串
        string substr(size_t pos = 0, size_t len = npos) const
        {
            string s;
            if (len == npos || len >= _size - pos)
            {
                for (size_t i = pos; i < _size; ++i)
                    s += _a[i];
            }
            else
            {
                for (size_t i = pos; i < pos + len; ++i)
                    s += _a[i];
            }
            return s;
        }

    };
    //关于数据末尾放不放\0的问题?
    //数据末尾当然要存放\0,这样在使用c_str返回常量字符串时才是一个正确的字符串

    const size_t string::npos = -1;//静态成员变量,类内声明,类外定义

    //1.类外swap(通过类内swap函数实现,防止调用算法库swap,提高效率)
    void swap(string& x, string& y)
    {
        x.swap(y);
    }

    //关系运算符重载
    //2.为什么关系运算符重载要重载为全局函数?
    //以==运算符为例,如果写成成员函数则不支持"123"==str,但支持str=="123"
    //因为调用成员函数的必须是对象,即使单参数的构造函数支持隐式类型转换,但是也不能先隐式类型转换转换为对象再调用函数

    //==重载
    bool operator==(const string& lhs, const string& rhs)
    {
        return strcmp(lhs.c_str(), rhs.c_str());//3.此处可以直接使用接口c_str,无需访问私有成员
    }
    //!=重载
    bool operator!= (const string& lhs, const string& rhs)
    {
        return !(lhs == rhs);
    }
    //<重载
    bool operator<(const string& lhs, const string& rhs)
    {
        int ret = strcmp(lhs.c_str(), rhs.c_str());
        return ret < 0;
    }
    //<=重载
    bool operator<=(const string& lhs, const string& rhs)
    {
        return (lhs < rhs) || (lhs == rhs);
    }
    //>重载
    bool operator>(const string& lhs, const string& rhs)
    {
        int ret = strcmp(lhs.c_str(), rhs.c_str());
        return ret > 0;
    }
    //>=重载
    bool operator>=(const string& lhs, const string& rhs)
    {
        return (lhs > rhs) || (lhs == rhs);
    }

    //<<重载
    ostream& operator<<(ostream& out, const string& str)
    {
        out << str.c_str();
        return out;
    }

    //>>重载
    //istream& operator>>(istream& in, string& str)
    //{
    //    str.clear();//4.输入是覆盖操作,原先有字符的对象需要删除
    //    again:
    //    char ch;
    //    ch = in.get();//5.cin和scanf规定读不到空格和换行符(因为scanf和cin默认空格符是两个对象输入的分割,换行符是输入结束标志)
    //    if(ch != ' ' && ch != '\n')//所以改使用get,可以读取空格和换行
    //    {                          //get读取的字符是先存储到缓冲区,再一个一个去读取字符,即使在缓冲区输入如12345 12345
    //        str += ch;             //get只会读取到空格之前的字符
    //        goto again;            //剩下的字符仍保留在缓冲区,留给下一次字符输入时get读取
    //    }
    //    return in;
    //}

    //6.>>重载的改进
    //在>>重载中,str+=ch,每一次+=如果空间不够需要扩容,频繁扩容效率很低
    //可以使用一个buff字符数组临时存储需要+=的字符,最后一次性全部+=
    istream& operator>>(istream& in, string& str)
    {
        str.clear();
        char buff[128];
        int i = 0;
    again:
        char ch = in.get();
        if (ch != ' ' && ch != '\n')
        {
            buff[i++] = ch;
            if (i == 127)
            {
                buff[127] = '\0';
                i = 0;
                str += buff;
            }
            goto again;
        }
        if (i > 0)
        {
            buff[i] = '\0';
            str += buff;
        }
        return in;
    }

    //getline
    istream& getline(istream& in, string& str)
    {
        str.clear();
    again:
        char ch = in.get();
        if (ch != '\n')
        {
            str += ch;
            goto again;
        }
        return in;
    }
    void test()
    {
        string s1;
        string s2("74829174589072390");
        s1 = s2;
        cout << s1 << endl;
        //string s1;
        getline(cin, s1);
        //cin >> s1;
        //cout << s1 << endl;
        //string s1("123");
        //string s2("456");
        //cin >> s1 >> s2;
        //cout << s1 << " " << s2 << endl;
        //string s1("12345");
        //cin >> s1;
        //cout << s1.c_str() << endl;
        //cout << s1 << endl;
        //string s1("12345");
        //string s2 = s1.substr(1, 6);
        //size_t pos = s1.find("34", 0);
        //s1[pos] = 'x';
        //cout << s2.c_str() << endl;
        //string s1("12345");
        //s1.insert(3, "xxx");
        //cout << s1.c_str() << endl;
        //string s2("ccc");
        //s1.swap(s2);
        //cout << s1.c_str() << endl;
        //cout << s2.c_str() << endl;
        //string s1("123456789");
        //s1.resize(5);
        //cout << s1.c_str() << endl;
        //s1.resize(20, 'c');
        //cout << s1.c_str() << endl;
        //s1.insert(3, 'x');
        //cout << s1.c_str() << endl;
        //string s2("12345");
        //s2.insert(0, "xxxXXXXXXXXX");
        //cout << s2.c_str() << endl;

        //string str1("12345");
        //str1 += '9';
        //cout << str1.c_str() << endl;
        //str1 += "xxxxxxxx";
        //cout << str1.c_str() << endl;
        //string str1;
        //const char* s1 = str1.c_str();
        //cout << s1 << endl;
        //string str2("12345");
        //const char* s2 = str2.c_str();
        //cout << s2 << endl;
        //string str3(str2);
        //const char* s3 = str3.c_str();
        //cout << s3 << endl;
        //string str4(s3, 2, 2);
        //const char* s4 = str4.c_str();
        //cout << s4 << endl;
        //char p[] = "123456789";
        //string str5(p, 5);
        //const char* s5 = str5.c_str();
        //cout << s5 << endl;
        //string str6(10, 'c');
        //const char* s6 = str6.c_str();
        //cout << s6 << endl;
        //str6.push_back('c');
        //const char* s7 = str6.c_str();
        //cout << s7 << endl;
        //str6.append("12345");
        //const char* s8 = str6.c_str();
        //cout << s8 << endl;

        /*string str7;
        str7.reserve(5);
        const char* s7 = str7.c_str();
        cout << s7 << endl;*/
        /*string s1("12345");
        string::iterator it = s1.begin();
        while (it != s1.end())
        {
            cout << *it << " ";
            ++it;
        }
        cout << endl;

        for (auto e : s1)
            cout << e << " ";*/
            //const string s2("56789");
            //string::const_iterator it = s2.begin();
            //while (it != s2.end())
            //{
            //    cout << *it << " ";
            //    ++it;
            //}
            //cout << endl;
            //string s1("12345");
            //s1.push_back('6');
            //s1.append("123");
            //for (auto e : s1)
            //{
            //    cout << e << " ";
            //}
    }
}

int main()
{

    mystring::test();
    //未初始化的位置是乱码
    //char* p = new char[5];
    //for (int i = 0; i < 3; ++i) p[i] = 'a';
    //cout << p << endl;
    //printf("%s\n", p);
    //string s1("12345");
    //cin >> s1;
    //cout << s1 << endl;
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/595467.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

1.python爬虫爬取视频网站的视频可下载的源url

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、爬取的源网站二、实现代码总结 一、爬取的源网站 http://www.lzizy9.com/ 在这里以电影片栏下的动作片为例来爬取。 可以看到视频有多页&#xff0c;因此需要…

Java设计模式 _结构型模式_享元模式

一、享元模式 1、享元模式 享元模式&#xff08;Flyweight Pattern&#xff09;是一种结构型模式。主要用于减少创建对象的数量&#xff0c;以减少内存占用和提高性能。主要解决有大量对象时&#xff0c;有可能会造成内存溢出&#xff0c;我们把其中共同的部分抽象出来&#x…

(网络初识)

网络发展史 独立模式 在最开始计算机被发明出来&#xff0c;但网络还未普及的情况下&#xff0c;每个计算机之间都是相互独立的&#xff1a; 假设现在有一份数据需要处理&#xff0c;然后这份数据的处理又分给三个人分别处理。假设小松处理进行第一部分的处理&#xff0c;当小…

Java二维码、条码生成及解码工具类

功能描述 生成二维码、条码解码使用谷歌的zxing依赖 引入依赖 <dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.4.1</version> </dependency><dependency><groupId>…

2023ccpc深圳G题相似基因序列问题

样例&#xff1a; 6 4 4 1 kaki kika manu nana tepu tero kaka mana teri anan 输出&#xff1a; 2 2 1 0 解析&#xff1a; 如果是用暴力的话是 300*300*6000&#xff0c;这样子一定会超时。 这时候我们可以利用hash函数进行处理&#xff0c;对比一个字符串的小于为O&a…

打开3d模型时显示不匹配是什么原因---模大狮模型网

在当今数字化时代&#xff0c;3D模型在各个领域的应用越来越广泛&#xff0c;从建筑设计到工程制造&#xff0c;再到虚拟现实技术的发展&#xff0c;都需要使用到3D模型。然而&#xff0c;在打开3D模型时遇到显示不匹配的问题并非罕见&#xff0c;可能会给工作和项目带来不便。…

堡垒机——网络技术手段

目录 一、简介 1.什么是跳板机 2.跳板机缺陷 3.什么是堡垒机 4.为什么要使用堡垒机 4.1堡垒机设计理念 4.2堡垒机的建设目标 4.3堡垒机的价值 4.4总结 5.堡垒机的分类 6.堡垒机的原理 7.堡垒机的身份认证 8.堡垒机的运维方式常见有以下几种 9.堡垒机其他常见功能…

多线程详解

目录 1.线程的概念 2.进程和线程的区别 3.线程操作 4.线程互斥 5.可重入和线程安全 6.常见锁的概念 7.线程同步 1.线程的概念(lwp) 1.1 线程概念 (1) 线程是cpu调度的基本单位(轻量化的进程), 和进程不同的是进程是承担系统资源的基本实体. (2) 一个进程里面至少有一个线…

2024年电化学、可再生能源与绿色发展国际会议(ICERGD2024)

2024年电化学、可再生能源与绿色发展国际会议(ICERGD2024) 会议简介 2024国际电化学、可再生能源与绿色发展大会&#xff08;ICERGD2024&#xff09;将在青岛隆重举行。本次会议聚焦电化学、可再生能源和绿色发展领域的最新研究成果和技术趋势&#xff0c;旨在促进相关领域…

如何快速找出文件夹里的全部带有数字纯数字的文件

参考此文章&#xff1a;如何快速找出文件夹里的全部带有中文&纯中文的文件 只需要根据自己的需求&#xff0c;把下面相关的设置调整好即可

【电商-虾皮】

电商-虾皮 ■ 人口分布■ 市场■ 欧美市场■ 东南亚市场 ■ ■ 人口分布 ■ 市场 ■ 欧美市场 亚马逊 ■ 东南亚市场 shopee ■

如何通过前端表格控件在10分钟内完成一张分组报表?

前言&#xff1a; 当今时代&#xff0c;报表作为信息化系统的重要组成部分&#xff0c;在日常的使用中发挥着关键作用。借助报表工具使得数据录入、分析和传递的过程被数字化和智能化&#xff0c;大大提高了数据的准确性及利用的高效性。而在此过程中&#xff0c;信息化系统能…

【前端学习——防抖和节流+案例】

定义 【前端八股文】节流和防抖 防抖 连续触发事件但是在设定的一段时间内只执行最后一次 代码实现思路【定时器】 大概意思就是&#xff1a; 每次按起键盘后&#xff0c;都将之前的定时器删除&#xff0c;重新开始计时。 节流 连续触发事件&#xff0c;只执行一次 …

【005_音频开发_基础篇_ALSA_Codec_驱动-MA120x0P功放】

005_音频开发_基础篇_ALSA_Codec_驱动-MA120x0P功放 文章目录 005_音频开发_基础篇_ALSA_Codec_驱动-MA120x0P功放创作背景MA120X0P输出模式BTLSEPBTLSEBTL 硬件配置方式/硬件Limiter限幅器限幅器作用过程 主要寄存器操作指令 ma120x0p.cma120x0p.h 创作背景 学历代表过去、能…

node应用部署运行案例

生产环境: 系统&#xff1a;linux centos 7.9 node版本&#xff1a;v16.14.0 npm版本:8.3.1 node应用程序结构 [rootRainYun-Q7c3pCXM wiki]# dir assets config.yml data LICENSE node_modules nohup.out output.log package.json server wiki.log [rootRainYun-Q7c…

Coursera: An Introduction to American Law 学习笔记 Week 06: Civil Procedure (完结)

An Introduction to American Law Course Certificate Course Introduction 本文是 https://www.coursera.org/programs/career-training-for-nevadans-k7yhc/learn/american-law 这门课的学习笔记。 文章目录 An Introduction to American LawInstructors Week 06: Civil Pro…

伙伴匹配(后端)-- 组队功能

文章目录 需求分析数据库表设计基础接口开发系统设计及开发创建队伍业务逻辑细化业务层代码优化完成控制层 查询队伍列表业务逻辑vo层业务层代码接口代码 修改队伍信息业务逻辑同样在请求包里封装一个用户登录请求体接口修改业务实现类 用户可以加入队伍同样在请求包里封装一个…

电脑问题2【彻底删除CompatTelRunner】

彻底删除CompatTelRunner 电脑偶尔会运行CompatTelRunner造成CPU占用的资源非常大,所以这里要想办法彻底关闭他 本文摘录于&#xff1a;https://mwell.tech/archives/539只是做学习备份之用&#xff0c;绝无抄袭之意&#xff0c;有疑惑请联系本人&#xff01; 解决办法是进入W…

WinForm DataGridView 垂直滑动条显示异常

WinForm DataGridView的垂直滑动条不正常显示&#xff0c;当总行高超过控件高度&#xff08;控件高度为227及以下不会出现该问题&#xff09;时&#xff0c;右下角会出现一个灰框&#xff0c;因为表格控件位处TabControl下&#xff0c;当切换其他选项卡后再切回来时&#xff0c…

Python类方法探秘:从单例模式到版本控制

引言&#xff1a; 在Python编程中&#xff0c;类方法作为一种特殊的实例方法&#xff0c;以其独特的魅力在众多编程范式中脱颖而出。它们不仅提供了无需实例即可调用的便捷性&#xff0c;还在设计模式、版本控制等方面发挥着重要作用。本文将通过几个生动的示例&#xff0c;带您…