【C++】string类模拟实现上篇(附完整源码)

news2025/1/21 0:58:49

目录

      • 前言
      • 1. string的基本结构
      • 2. 构造函数、析构函数
        • 2.1 构造函数的实现
          • 2.1.1带参构造函数
        • 2.2析构函数
        • 2.3无参构造函数
        • 2.4无参和带参构造函数合并
      • 3. string的遍历
        • 3.1 operator[ ]
        • 3.2迭代器模拟实现 (简单实现)
        • 3.3 const迭代器模拟实现
      • 4. 数据的增删查改
        • 4.1 reserve
        • 4.2 push_back和append
        • 4.3 +=
        • 4.4 insert
        • 4.5 erase
      • 4.6 find
        • 4.7 substr
      • 5. 拷贝构造
        • 5.1 浅拷贝默认拷贝构造
        • 5.2 深拷贝拷贝构造函数
      • 6. 源码(上部分)
        • 6.1 string.h
        • 6.2 test.cpp
      • 7. 总结

前言

在上一篇文章中,我们详细介绍了string类一些常用接口的使用,那这篇文章,我们将对string进行一个模拟实现,帮助大家理解的更加深刻。

1. string的基本结构

在上篇文章中我们了解:

string的底层其实就是一个支持动态增长的字符数组。那确定它的结构,接下来我们就开始模拟实现它。

首先新建一个头文件string.h,定义一个string类:

class string
{
    public :
    //成员函数
    private :
        char*  _str;
        size_t _size;
        size_t _capacity;
};

这里string类的三个成员变量,一个字符指针_str指向开辟的动态数组,_size标识有效数据个数,_capacity记录容量的大小(不包含’\0’)。

但是因为标准库里已经有string类,为了避免冲突,我们需要定义一个命名空间,把我们自己实现的string类放到自己的命名空间里面

namespace w
{
    class string
{
    public :
    //成员函数
    private :
        char*  _str;
        size_t _size;
        size_t _capacity;

};
    
} 

2. 构造函数、析构函数

2.1 构造函数的实现

2.1.1带参构造函数

首先我们来模拟实现一个带参构造函数:

我们知道标准库里string类的构造函数有很多,这里我们只模拟实现最常用的:
在这里插入图片描述

在之前的文章中我们提到尽量使用初始化列表进行初始化,我们可以这样写:
在这里插入图片描述
但是这里你会发现程序报错了,因为如果像上图一样初始化,首先涉及到权限放大的问题(之前文章有讲过)char* strconst修饰,不能被修改,但是赋给_str_strchar* 类型的,可以修改。其次用常量字符串去初始化也不能被修改。

那怎么办呢? 我们这里不直接传参而是开空间,用strcpy去进行拷贝:

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

顺便这里我们提供一个接口用来返回字符串:

 const char* c_str()
        {
            return _str;
        }

我们在创建一个test.cpp文件用来测试我们写的接口:
在这里插入图片描述

2.2析构函数

这里我们直接顺便给出析构函数:

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

2.3无参构造函数

我们有的时候还会遇到这样的场景:
在这里插入图片描述
所以这里需要我们去实现一个无参的构造函数。

假设这里的无参构造函数我们这样实现:
在这里插入图片描述

那这样真的可行吗?
如果这里_str传空指针那么在刚刚实现的c_str函数就会返回空程序,程序会崩溃。并且在标准库里的c_str接口即使传空也是会有返回值的。

那这里应该怎么办呢? 我们可以这样写:

  string()
	        :_str(new char[1])
		    ,_size(0)
		    ,_capacity(0)
	    {
            _str[0] = '\0';
        }

这里我们给_str开辟一个空间,然后给这块空间给上'\0'。这样就不会出现上面的问题了

2.4无参和带参构造函数合并

我们之前讲过无参和带参的可以用全缺省

我们来看几种写法:
在这里插入图片描述
能这样写吗?答案是肯定不能这样写类型就不匹配,一个是字符一个是字符串
在这里插入图片描述
能这样写吗? 答案是肯定不能。这样写strlen里的str就是空串了。

其实应该这样写:
在这里插入图片描述
这里我们直接给一个空串,常量字符串末尾是默认有"\0"

3. string的遍历

3.1 operator[ ]

我们知道在标准库中可以通过下标去访问字符串中的某一个字符,下面我们来实现对[]的重载。

首先我们需要实现size()接口:
在这里插入图片描述

接下来我们来实现一下[]的重载:
在这里插入图片描述
在这里插入图片描述
这里我们实现了两个版本普通版本对应普通对象,const版本对应const对象,且这两个函数构成函数重载

下面我们来验证一下:
在这里插入图片描述

3.2迭代器模拟实现 (简单实现)

除了[]可以遍历访问string对象,我们还可以用迭代器进行访问。

那迭代器我们说了大家可以理解成一个像指针一样的东西,但是不一定是指针。
我们最开始介绍了STL有好几个版本,不同的版本实现可能是不一样的。
那其实vs下string的迭代器呢就不是使用指针实现的,而G++下使用的SGI版本是指针实现的。
那这里我们模拟实现就使用指针来实现:
在这里插入图片描述

下面我们来验证一下:
在这里插入图片描述
在这里插入图片描述

同样的我们还可以使用范围for进行遍历:
在这里插入图片描述
范围for的底层就是用的迭代器。
大家可以理解成范围for的语法其实就跟我们之前学过的宏有点类似,它会被替换成迭代器,相当于把*it赋值给ch。范围for的底层就是无脑替换。

3.3 const迭代器模拟实现

这里我们再实现const版本给const对象使用:
在这里插入图片描述

4. 数据的增删查改

首先我们来实现一下push_back()append().这两个都是插入数据,既然插入数据那我们就必须考虑扩容的问题。
在这里插入图片描述
那这里如果扩容的话,我们一次扩多少呢?
对于push_back来说一次扩二倍没问题,但是append一次扩二倍有可能是不行的。
为什么?
如果当前的容量是10,现在追加一个长度为25的字符串,扩容到原来的两倍才
20,也是不够用的。

那这里我们通过string的另一个接口reserve,它可以改变容量为我们指定的大小,帮助我们扩容。
下面我们就先来实现一下reserve。

4.1 reserve

我们先来看一下reserve怎么实现:
在这里插入图片描述
这里当参数n的值小于_capacity,如果不加这个if判断这里就会缩容。但是我们知道,库里的接口是不会缩容的。所以需要加上这个条件判断。

4.2 push_back和append

那接下来有了reserve我们继续来实现push_backappend

在这里插入图片描述
push_back这里我们直接选择两倍扩。

在这里插入图片描述
这里append最少扩容到_size + len.

下面我们来实现一下:
在这里插入图片描述

4.3 +=

我们虽然有push_back和append但是我们更喜欢用重载的+=。当然+=的底层也是可以用push_back和append实现的。
在这里插入图片描述

下面我们来实现一下:
在这里插入图片描述

4.4 insert

对于insert我们主要实现库里的这两个版本:
在这里插入图片描述
在这里插入图片描述

首先我们来实现一下在pos位置插入n个字符:
逻辑其实是比较简单的。首先判断一下,是否需要扩容,然后就插入数据,如果往中间插就需要挪动数据。
在这里插入图片描述

这样写有没有问题呢? 我们来测试一下:
在这里插入图片描述
好像没什么问题啊。真的没问题吗?

我们来看一种特殊情况:当pos = 0 时插入数据:
在这里插入图片描述

程序这里挂了。那为什么呢?
这里当pos = 0时,end等于0时还会进入循环,end再- -会变成多少? 是-1吗?
在这里插入图片描述
这里end的类型是szie_t,无符号整型,所以end为0后再- -并不是-1,而是整型最大值,发生越界,循环也没正常结束,所以程序崩了。

那怎么解决呢?把end改成int可行吗?

这里也是不可行的。end和pos比较,end变成int,但是pos是size_t类型,这里是会发生整型提升(C语言知识)那我们应该如何解决呢?

这里解决方法有很多,我们采用其中一种利用我们之前文章中提到的npos解决:
在这里插入图片描述

在这里插入图片描述

我们再来测试一下:
在这里插入图片描述

刚才是插入一个字符,现在我们再来实现插入字符串的。那么逻辑和上面其实是一样的。只不过上面我们只需要挪出n个空间就可以了,那这里我们需要挪动数据腾出strlen(str)个空间。
在这里插入图片描述

下面我们来测试一下:
在这里插入图片描述

4.5 erase

那么接下来我们来实现一下erase,从pos位置删除len个字符:
在这里插入图片描述

对于erase首先第一种情况就是pos+len小于字符串的长度,那我们需要把pos位置开始的后len个字符删掉,但是仍然保留后续字符。那这里就是挪动后面的数据,把需要删除的覆盖掉就行
那其它情况就是len比较大,pos+len直接大于等于字符串的长度,那就把pos后面的全部删掉。或者没有传pos这个参数,缺省值npos,那也要把后面的全删,所以这两种情况可以统一处理。这里只需要把pos位置给成“\0”就行了。
在这里插入图片描述

我们来测试一下:
在这里插入图片描述

当然为了和标准库里的一致我们这里也使用引用返回:
在这里插入图片描述

4.6 find

下面我们来实现一下find。find的实现其实很简单,遍历去找,找到了就返回下标,找不到就返回npos
在这里插入图片描述

当然find还支持从pos位置开始查找一个字符串:在这里我们复用C语言中的strstr去查找。在这里插入图片描述

下面我们来测试一下:
在这里插入图片描述

4.7 substr

下面我们再来实现一下substr。它的逻辑也是很简单的。

这里稍微需要注意的是我们需要条件判断当截取的字串足够长,我们截取的长度就是pos位置一直到字符串的末尾。
在这里插入图片描述

5. 拷贝构造

我们现在先来写一段这样的代码:
在这里插入图片描述
这里有一个拷贝构造,s2是s1拷贝构造而来的。

5.1 浅拷贝默认拷贝构造

在之前类和对象的文章中,我们知道,拷贝构造函数我们自己不写编译器是会默认生成的,这里我们直接运行上面的代码:
在这里插入图片描述

这里程序出错发生了一个经典的浅拷贝的问题。在之前的文章中我们也有讲过若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数拷贝对象 按内存存储字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝,就会出现问题。

5.2 深拷贝拷贝构造函数

这里就需要我们自己去实现拷贝构造函数,完成深拷贝:
在这里插入图片描述
在这里插入图片描述
下面我们来测试一下:
在这里插入图片描述

6. 源码(上部分)

6.1 string.h

#include <iostream>
using namespace std;
namespace w
{
    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;
		}

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

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


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

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

        size_t size() const
        {
            return _size;
        }

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

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

          void reserve(size_t n)
		{
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];
				strcpy(tmp, _str);
				delete[] _str;
				_str = tmp;
				_capacity = n;
			}
		}
		void push_back(char ch)
		{
			if (_size == _capacity)
			{
				// 2倍扩容
				reserve(_capacity == 0 ? 4 : _capacity * 2);
			}

			_str[_size] = ch;

			++_size;
			_str[_size] = '\0';
		}

		void append(const char* str)
		{
			size_t len = strlen(str);
			if (_size + len > _capacity)
			{
				// 至少扩容到_size + len
				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 insert(size_t pos, size_t n, char ch)
		{
			assert(pos <= _size);

			if (_size +n > _capacity)
			{
				// 至少扩容到_size + len
				reserve(_size + n);
			}

			// 添加注释最好
			size_t end = _size;
			while (end >= pos && end != npos)
			{
				_str[end + n] = _str[end];
				--end;
			}

			for (size_t i = 0; i < n; i++)
			{
				_str[pos + i] = ch;
			}

			_size += n;
		}

        	void insert(size_t pos, const char* str)
		{
			assert(pos <= _size);

			size_t len = strlen(str);
			if (_size + len > _capacity)
			{
				// 至少扩容到_size + len
				reserve(_size + len);
			}

			// 添加注释最好
			size_t end = _size;
			while (end >= pos && end != npos)
			{
				_str[end + len] = _str[end];
				--end;
			}

			for (size_t i = 0; i < len; i++)
			{
				_str[pos + i] = str[i];
			}

			_size += len;
		}

        string& erase(size_t pos, size_t len = npos)
		{
			assert(pos <= _size);

			if (len == npos || pos + len >= _size)
			{
				_str[pos] = '\0';
				_size = pos;

				_str[_size] = '\0';
			}
			else
			{
				size_t end = pos + len;
				while (end <= _size)
				{
					_str[pos++] = _str[end++];
				}
				_size -= len;
			}

            return *this;
		}

        size_t find(char ch, size_t pos = 0)
		{
			assert(pos < _size);

			for (size_t i = pos; i < _size; i++)
			{
				if (_str[i] == ch)
				{
					return i;
				}
			}

			return npos;
		}

		size_t find(const char* str , size_t pos = 0)
		{
			assert(pos < _size);

			const char* ptr = strstr(_str + pos, str);
			if (ptr)
			{
				return ptr - _str;
			}
			else
			{
				return npos;
			}
		}

        string substr(size_t pos = 0, size_t len = npos)
		{
			assert(pos < _size);

			size_t n = len;
			if (len == npos || pos + len > _size)
			{
				n = _size - pos;
			}

			string tmp;
			tmp.reserve(n);
			for (size_t i = pos; i < pos + n; i++)
			{
				tmp += _str[i];
			}

			return tmp;
		}

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

    public:
		const static size_t npos;

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


6.2 test.cpp

#include "Mystring.h"

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

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

    w ::string::iterator it = s1.begin();
    while (it != s1.end())
    {
        cout << *it << " ";
        ++it;
    }
    cout <<endl;
    
    for (auto ch : s1)
    {
        cout << ch <<" ";
    }
    cout <<endl;
}

void test_string2()
{

	w::string s1("hello world");
	cout << s1.c_str() << endl;

	s1.push_back(' ');
	s1.push_back('#');
	s1.append("hello");
	cout << s1.c_str() << endl;

    w::string s2("hello world");
	cout << s2.c_str() << endl;

	s2 += ' ';
	s2 += '#';
	s2 += "hello code";
	cout << s2.c_str() << endl;

}

void test_string3()
{
	w::string s1("helloworld");
	cout << s1.c_str() << endl;

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

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

    w::string s2("helloworld");
	s2.insert(5, "%%%%%");
	cout << s2.c_str() << endl;
	
}

void test_string4()
{
	w::string s1("helloworld");
	cout << s1.c_str() << endl;

	s1.erase(5, 3);
	cout << s1.c_str() << endl;

	s1.erase(5, 30);
	cout << s1.c_str() << endl;

	s1.erase(2);
	cout << s1.c_str() << endl;
}

void test_string5()
{
	w::string s1("helloworld");
	cout << s1.find('w',2) << endl;

	
}

void test_string6()
{
	w::string s1("hello world");
	w::string s2(s1);

	cout << s1.c_str() << endl;
	cout << s2.c_str() << endl;

}




int main()
{
    test_string6();
    return 0;
}

7. 总结

文章篇幅有限,剩余内容将在下篇进行讲解。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

最经典的解析LSA数据库(第六课)

初步认识OSPF的大致内容(第三课)_IHOPEDREAM的博客-CSDN博客 1 OSPF 工作过程 建立领居表 同步数据库 今天来 说一说数据库概念 计算路由表 2 什么是数据库&#xff1f; 数据库是一个组织化的数据集合&#xff0c;用于存储、管理和检索数据。它是一个可访问的集合&#x…

[SICTF 2023 #Round2] Crypto,PWN,Reverse

似乎很久没写了。 周五到周日&#xff0c;两天的这个比赛&#xff0c;有些东西还真是头回用&#xff0c;值得纪录一下。 Crypto 密码这块这届还是比较简单的&#xff0c;没有复杂的题&#xff0c;但量大分多。 【签到】古典大杂烩 给了一堆emoji的图 &#x1f429;&#x…

英国私校的艺术奖学金有哪些?申请要求和申请流程详解!

众所周知&#xff0c;英国私校不仅学术拔尖&#xff0c;在对学生艺术方面的培养也是毫不逊色的。几乎打开每一所英国私校的官网&#xff0c;都可以看到学校罗列的提供的各类课外艺术活动的精彩照片。      每个英国私校除了课后开设的五花八门的兴趣课外&#xff0c;还有各…

【项目 计网12】4.32UDP通信实现 4.33广播 4.34组播 4.35本地套接字通信

文章目录 4.32UDP通信实现udp_client.cudp_server.c 4.33广播bro_server.cbro_client.c 4.34组播multi_server.cmulti_client.c 4.35本地套接字通信ipc_server.cipc_client.c 4.32UDP通信实现 udp_client.c #include <stdio.h> #include <stdlib.h> #include <…

2023-09-10 LeetCode每日一题(课程表 II)

2023-09-10每日一题 一、题目编号 210. 课程表 II二、题目链接 点击跳转到题目位置 三、题目描述 现在你总共有 numCourses 门课需要选&#xff0c;记为 0 到 numCourses - 1。给你一个数组 prerequisites &#xff0c;其中 prerequisites[i] [ai, bi] &#xff0c;表示在…

hive葵花宝典:hive函数大全

文章目录 版权声明函数1 函数分类2 查看函数列表3 数学函数取整函数: round指定精度取整函数: round向下取整函数: floor向上取整函数: ceil取随机数函数: rand幂运算函数: pow绝对值函数: abs 4 字符串函数字符串长度函数&#xff1a;length字符串反转函数&#xff1a;reverse…

表情识别-情感分析-人脸识别(代码+教程)

表情识别 面部情绪识别&#xff08;FER&#xff09;是指根据面部表情识别和分类人类情绪的过程。通过分析面部特征和模式&#xff0c;机器可以对一个人的情绪状态作出有根据的推断。这个面部识别的子领域高度跨学科&#xff0c;涉及计算机视觉、机器学习和心理学等领域的知识。…

解析Spring Boot中的Profile:配置文件与代码的双重掌控

目录 创建一个spring boot 项目spring boot 中的配置体系配置文件与 Profile代码控制与Profile 创建一个spring boot 项目 基于 Spring Boot 创建 Web 应用程序的方法有很多,我们选择在idea中直接进行创建&#xff0c;服务器URL选择Spring Initializer 网站&#xff0c;类型选…

libnetcdf.so.19: cannot open shared object file: No such file or directory

Linux编译程序时出现问题 在linux系统上&#xff0c;编译一个工具包后&#xff0c;在运行该工具包时&#xff0c;出现以下报错&#xff1a; libnetcdf.so.19: cannot open shared object file: No such file or directory仔细分析报错信息可以发现&#xff1a;在运行该工具包…

UG\NX二次开发 判断向量在指定的公差内是否为零,判断是否是零向量 UF_VEC3_is_zero

文章作者:里海 来源网站:王牌飞行员_里海_里海NX二次开发3000例,里海BlockUI专栏,C\C++-CSDN博客 简介: UG\NX二次开发 判断向量在指定的公差内是否为零,判断是否是零向量 UF_VEC3_is_zero 效果: 代码: #include "me.hpp"void ufusr(char* param, int* retco…

Pytorch实现基于LSTM的情感分析

文章目录 本文参考导入必要的包介绍torchnet做数据的导入给必要的参数命名加载文本数据数据前处理模型训练验证 本文参考 PyTorch深度学习项目实战100例 https://weibaohang.blog.csdn.net/article/details/127154284?spm1001.2014.3001.5501 这段代码是一个基于PyTorch实现…

LeetCode(力扣)37. 解数独Python

LeetCode37. 解数独 题目链接代码 题目链接 https://leetcode.cn/problems/sudoku-solver/description/ 代码 class Solution:def solveSudoku(self, board: List[List[str]]) -> None:"""Do not return anything, modify board in-place instead."…

使用数据库表快速生成代码

这里使用的EasyCode插件&#xff0c;直接下载即可&#xff0c;这里需要有数据库的技术与使用idea&#xff0c;会使用起来更流畅&#xff01; 使用idea连接数据库 右键选择表 勾选你所需要的添加&#xff0c; 鄙人一般除了debug&#xff0c;其他都会勾选上 点击确定&#xff0c;…

【电源专题】不合理接地引发的典型问题及地环路隔离的方法

在文章:【电源专题】接地的类型 中我们讲到因为历史的原因接地在不同时期的概念是不同的。到了如今大规模的集成电路时代,在单板中接地其实是想要一个参考电位,一个等势点。 但是理想终究是理想,在现实接地中,往往因为接地平面的阻抗不是0,而电源电流过大、信号频率过高…

目标检测笔记(十五): 使用YOLOX完成对图像的目标检测任务(从数据准备到训练测试部署的完整流程)

文章目录 一、目标检测介绍二、YOLOX介绍三、源码获取四、环境搭建4.1 环境检测 五、数据集准备六、模型训练七、模型验证八、模型测试 一、目标检测介绍 目标检测&#xff08;Object Detection&#xff09;是计算机视觉领域的一项重要技术&#xff0c;旨在识别图像或视频中的…

Linux权限的概念和管理

Linux权限的概念和管理 1. Linux权限的概念2. Linux权限管理2.1 文件访问者的分类&#xff08;人&#xff09;2.2 文件类型和访问权限&#xff08;事物属性&#xff09;2.2.1 文件类型2.2.2 基本权限 2.3 文件权限值的表示方法2.4文件访问权限的相关设置方法1. chmod&#xff0…

C++的运算符重载介绍

所谓重载,就是赋予新的含义。函数重载(Function Overloading)可以让一个函数名有多种功能,在不同情况下进行不同的操作。运算符重载(Operator Overloading)也是一个道理,同一个运算符可以有不同的功能。 实际上,我们已经在不知不觉中使用了运算符重载。例如,+号可以对…

IDEA控制台取消悬浮全局配置SpringBoot配置https

IDEA控制台取消悬浮 idea 全局配置 SpringBoot(Tomcat) 配置https&#xff0c;同时支持http 利用JDK生成证书 keytool -genkey -alias httpsserver -keyalg RSA -keysize 2048 -keystore server.p12 -validity 3650配置类 Configuration public class TomcatConfig {Value(&quo…

【golang】调度系列之m

调度系列 调度系列之goroutine 上一篇中介绍了goroutine&#xff0c;最本质的一句话就是goroutine是用户态的任务。我们通常说的goroutine运行其实严格来说并不准确&#xff0c;因为任务只能被执行。那么goroutine是被谁执行呢&#xff1f;是被m执行。 在GMP的架构中&#xff…

PC首页资源加载速度由8s降到2s的优化实践

随着需求的不断开发&#xff0c;前端项目不断膨胀&#xff0c;业务提出&#xff1a;你们的首页加载也太慢啦&#xff0c;我都需要7、8秒才能看到内容&#xff0c;于是乎主管就让我联合后端开启优化专项&#xff0c;目标是3s内展示完全首页的内容。 性能指标 开启优化时&#…