深入探究C++中的仿函数和迭代器——提升你的STL技能

news2024/12/24 10:48:22

在这里插入图片描述

📖作者介绍:22级树莓人(计算机专业),热爱编程<目前在c++阶段>——目标Windows,MySQL,Qt,数据结构与算法,Linux,多线程,会持续分享学习成果和小项目的
📖作者主页:热爱编程的小K
📖专栏链接:c++

🎉欢迎各位→点赞👏 + 收藏💞 + 留言🔔​
💬总结:希望你看完之后,能对你有所帮助,不足请指正!共同学习交流 🐾

在这里插入图片描述

文章目录

      • 一、仿函数
        • 1、介绍
        • 2、为什么要有仿函数?
        • 3、核心
          • A、仿函数的调用
          • B、应用
          • C、标准库仿函数
        • 4、仿函数优点
        • 5、仿函数作用
      • 二、迭代器
        • 1、分类
        • 2、辅助函数
        • 3、流型迭代器


一、仿函数

1、介绍

仿函数(Functor)又称为函数对象(Function Object)是一个能行使函数功能的类,仿函数是定义了一个含有operator()成员函数的对象,可以视为一个一般的函数,只不过这个函数功能是在一个类中的运算符operator()中实现,是一个函数对象,它将函数作为参数传递的方式来使用。

写一个简单类,除了维护类的基本成员函数外,只需要重载 operator() 运算符 。这样既可以免去对一些公共变量的维护,也可以使重复使用的代码独立出来,以便下次复用。

STL 中也大量涉及到仿函数,有时仿函数的使用是为了函数拥有类的性质,以达到安全传递函数指针、依据函数生成对象、甚至是让函数之间有继承关系、对函数进行运算和操作的效果。比如 STL 中的容器 set 就使用了仿函数 less ,而 less 继承的 binary_function,就可以看作是对于一类函数的总体声明,这是函数做不到的。

2、为什么要有仿函数?

1,假如客户有一个需求摆在我们的面前,编写一个函数:函数可以获得斐波拉契数列每项的值;每调用一次便返回一个值;函数可根据需要重复使用。我们之前在 C 语言中也讲过斐波拉契数列,相信这个很好实现了。那么我们就编写的程序如下

int fibonacci()
{
	static int a0 = 0;	//第一项
	static int a1 = 1;	//第二项

	int ret = a1;		//保存
	a1 = a0 + a1;
	a0 = ret;

	return ret;
}
int main()
{
	for (size_t i = 0; i < 5; i++)
	{
		cout << fibonacci() << " ";		//1 1 2 3 5
	}
	cout << endl;
	for (size_t i = 0; i < 5; i++)
	{
		cout << fibonacci() << " ";		//8 13 21 34 55
	}
	return 0;
}

我们就开心的完成任务了,于是交给客户了。过两天,客户又给打回来了。说是存在几个问题:函数一但调用就无法重来,静态局部变量处于函数内部,外界无法改变。函数为全局函数,是唯一的,无法多次独立使用。无法指定某个具体的数列项作为初始值。于是我们想着将静态局部变量改为去全局变量,再次重新调用时,便将全局变量重新初始化,重新如下

int a0 = 0;	//第一项
int a1 = 1;	//第二项
int fibonacci()
{
	int ret = a1;
	a1 = a0 + a1;
	a0 = ret;

	return ret;
}

int main()
{
	for (size_t i = 0; i < 5; i++)
	{
		cout << fibonacci() << " ";		//1 1 2 3 5 8
	}
	cout << endl;

	a0 = 0;
	a1 = 1;
	for (size_t i = 0; i < 5; i++)
	{
		cout << fibonacci() << " ";		//1 1 2 3 5 8
	}
	return 0;
}

是满足这个需求了,但是要在使用时需要重新初始化全局变量,客户肯定不干啊。所以这个解决方案不可行。于是乎,我们在 C++ 中一个吊炸天的技术来了:函数对象。

先来说说函数对象:

  • 使用具体的类对象取代函数;
  • 该类的对象具备函数调用的行为;
  • 构造函数指定具体数列项的起始位置;
  • 多个对象相互独立的求解数列项。

同样函数对象也是通过函数调用操作符(),便是重载操作符了。它只能通过类的成员函数重载,可以定义不同参数的多个重载函数。

下来我们来看看最终的解决方案

class Fibonacci
{
public:
	Fibonacci() :_a0(0), _a1(1) {}
	Fibonacci(int n) :_a0(0), _a1(1) 
	{
		for (int i = 0; i < n; i++)
		{
			int ret = _a1;
			_a1 = _a0 + _a1;
			_a0 = ret;
		}
	}
	int operator()()
	{
		int ret = _a1;
		_a1 = _a0 + _a1;
		_a0 = ret;
		return ret;
	}
private:
	int _a0;
	int _a1;
};


int main()
{
	Fibonacci fib;
	for (size_t i = 0; i < 5; i++)
	{
		cout << fib() << " ";		//1 1 2 3 5 8
	}
	cout << endl;

	Fibonacci fib1(9);
	for (size_t i = 0; i < 5; i++)
	{
		cout << fib1() << " ";		//55 89 144 233 377
	}
	return 0;
}

我们看到已经实现了所有需求,并且随时想从哪个数开始都行。

2,比如,有一个简单需求:统计一个vector<int>中,元素等于3的数量。解决方法可能会是:

int equal_count(const vector<int>::iterator& first,const vector<int>::iterator& last,const int& val)
{
	int size = 0;
	for (auto it = first; it != last; it++)
	{
		if (*it == val)
		{
			size++;
		}
	}
	return size;
}
int main()
{
	vector<int> v = { 1,2,3,4,5,6,7,8,1,3,1,4,3,2,1 };
	//统计v中元素等于3的元素个数
	size_t size = equal_count(v.begin(), v.end(), 3);
	cout << size << endl;	//output:3

	return 0;
}

其实,统计容器中某个元素的数量,C++中有一个函数count

size_t count(const Iter First, const Iter Last, const Ty& Val);

对于上面的统计元素个数没有拓展性。比如:统计v中元素大于于3的元素个数呢?为此我们必须再设计一个greater_count函数:

int great_count(const vector<int>::iterator& first, const vector<int>::iterator& last, const int& val)
{
	int size = 0;
	for (auto it = first; it != last; it++)
	{
		if (*it > val)
		{
			size++;
		}
	}
	return size;
}

这样写就很麻烦,我只需要改变一下规则,就需要多一个函数,咱们可以把里面的比较规则,写成一个函数(可调用的对象),通过传参实现比较。

//using FunType = bool (*)(int, int);
template<typename FunType>
int count_if(const vector<int>::iterator& first, const vector<int>::iterator& last,FunType cmp,const vector<int>::value_type& val)
{
	int size = 0;
	for (auto it = first; it != last; it++)
	{
		if (cmp(*it,val))
		{
			size++;
		}
	}
	return size;
}
bool equal(int a, int b)
{
	return a == b;
}
bool great(int a, int b)
{
	return a > b;
}
int main()
{
	vector<int> v = { 1,2,3,4,5,6,7,8,1,3,1,4,3,2,1 };
	//统计v中元素等于3的元素个数
	size_t size;
	size = count_if(v.begin(), v.end(), equal,3);
	cout << size << endl;
	//统计v中元素大于3的元素个数
	size = count_if(v.begin(), v.end(), great,3);
	cout << size << endl;
	
	return 0;
}

这样是不是就轻松很多了,但是这里的统计元素3,我们要通过count_if传到比较函数里面去,非常的丑陋对不对。有一种写法,可以不通过参数传进去。

首先,删掉count_if中的最后一个参数val。

然后,把equal和great稍加修改一下。

template<typename FunType>	
int count_if(const vector<int>::iterator& first, const vector<int>::iterator& last, FunType cmp)
{
	int size = 0;
	for (auto it = first; it != last; it++)
	{
		if (cmp(*it))
		{
			size++;
		}
	}
	return size;
}
bool equal(int a)
{
	return a == 3;
}
bool great(int a)
{
	return a > 3;
}
int main()
{
	vector<int> v = { 1,2,3,4,5,6,7,8,1,3,1,4,3,2,1 };
	//统计v中元素等于3的元素个数
	size_t size;
	size = count_if(v.begin(), v.end(), equal);
	cout << size << endl;
	//统计v中元素大于3的元素个数
	size = count_if(v.begin(), v.end(), great);
	cout << size << endl;
	
	return 0;
}

或者使用lambda表达式

vector<int> v = { 1,2,3,4,5,6,7,8,1,3,1,4,3,2,1 };
//统计v中元素等于3的元素个数
size_t size;
//size = equal_count(v.begin(), v.end(), 3);
size = count_if(v.begin(), v.end(), [](auto val) 
	{
			return val == 3; 
	});
cout << size << endl;
//统计v中元素大于3的元素个数
size = count_if(v.begin(), v.end(), [](auto val)
	{
		return val > 3;
	});
cout << size << endl;

其实lamda表达式出现之后(C++11),仿函数(C++98)的作用已经被削弱了,使用lamda会让我们使用STL方便许多。

对于count_if 里有和我们写的一模一样的函数,以后直接使用即可

那么使用仿函数,怎么实现上述功能呢?

struct Equal
{
	bool operator()(int val)
	{
		return val == 3;
	}
};
struct Great
{
	bool operator()(int val)
	{
		return val > 3;
	}
};
int count_if(const vector<int>::iterator& first, const vector<int>::iterator& last, FunType cmp);
int main()
{
	vector<int> v = { 1,2,3,4,5,6,7,8,1,3,1,4,3,2,1 };
	//统计v中元素等于3的元素个数
	size_t size;
	size = count_if(v.begin(), v.end(), Equal());
	cout << size << endl;
	//统计v中元素大于3的元素个数
	size = count_if(v.begin(), v.end(), Great());
	cout << size << endl;
	
	return 0;
}

可以继续升级~

struct Equal
{
	Equal(int usrVal) :_usrVal(usrVal) {}
	bool operator()(int val)
	{
		return val == _usrVal;
	}
	int _usrVal;
};
struct Great
{
	Great(int usrVal) :_usrVal(usrVal) {}
	bool operator()(int val)
	{
		return val > _usrVal;
	}
	int _usrVal;
};
int count_if(const vector<int>::iterator& first, const vector<int>::iterator& last, FunType cmp);
int main()
{
	vector<int> v = { 1,2,3,4,5,6,7,8,1,3,1,4,3,2,1 };
	//统计v中元素等于3的元素个数
	size_t size;
	size = count_if(v.begin(), v.end(), Equal(2));
	cout << size << endl;
    
	//统计v中元素大于3的元素个数
	size = count_if(v.begin(), v.end(), Great(5));
	cout << size << endl;
	
	return 0;
}

3、核心

仿函数是让类名模仿函数调用的行为---->函数名(参数) ,让类名能够 : 类名(参数) 方式使用

自己写仿函数关键点在于重载()运算符,所谓的模仿函数的行为,本质先构造一个无名对象,然后通过对象隐式调用重载函数

  • 自己写仿函数
  • 标准库中的仿函数(不需要记,自己会写了,自己创造)

仿函数一般有两个作用:

  • 充当比较准则
  • 充当算法或者容器构建的参数
A、仿函数的调用

必须加上{},要不然解析不了,分不清楚是构造函数还是啥
sum<int>{}(2, 3)

template <class _Ty> class sum 
{
public:
	int operator()(const _Ty& one, const _Ty& two) const 
	{
		return one + two;
	}
};
void testOne() 
{
	//调用
	sum<int> k;
	cout << k.operator()(2, 3) << endl;
	cout << k(2, 3) << endl;
	//必须加上{},要不然解析不了,分不清楚是构造函数还是啥
	cout << sum<int>{}(2, 3) << endl;
}
B、应用

冒泡排序函数准则的写入,使用结构体和类分别进行书写,方便理解

template <class _Ty, class _Pr> void bubble_Sort(_Ty array[], int size) 
{
	for (int i = 0; i < size; i++)
	{
		for (int j = 0; j < size - 1 - i; j++) 
		{
			if (_Pr{}(array[j], array[j + 1])) 
			{
				_Ty temp = array[j];
				array[j] = array[j + 1];
				array[j + 1] = temp;
			}
		}
	}
}
template <class _Ty> class my_Less 
{
public:
	bool operator()(const _Ty& one, const _Ty& two) const 
	{
		return one < two;
	}
};
template <typename _Ty> struct my_Greater
{
	bool operator()(const _Ty& one, const _Ty& two) const 
	{
		return one > two;
	}
};
void testTwo()
{
	int a[] = { 1,2,9,8,0,2,6,89,98,102,23 };
	bubble_Sort<int, my_Greater<int>>(a, 11);
	for (auto v : a) {
		cout << v << " ";
	}
	cout << endl;
	bubble_Sort<int, my_Less<int>>(a, 11);
	for (auto v : a) {
		cout << v << " ";
	}
	cout << endl;
}
C、标准库仿函数

我的评价是不用记,用到的时候自己写一个或者查一下,标准库的太多了,还比较难记

在这里插入图片描述

void test3() 
{
	//算术类
	cout << plus<int>{}(2, 3) << endl;
	cout << minus<int>{}(5, 2) << endl;
	cout << multiplies<int>{}(3, 2) << endl;
	//关系类
	cout << less<int>{}(4, 3) << endl;
	//逻辑类
	cout << logical_and<int>{}(3, 2) << endl;
	//位运算
	cout << bit_and<int>{}(2, 1) << endl;
}

4、仿函数优点

如果可以用仿函数实现,那么你应该用仿函数,而不要用CallBack(CallBack技术是一种编程技术,它允许将一个函数作为参数传递给另一个函数,以便在需要时执行该函数。这种技术通常用于事件处理程序和异步编程中。)。原因在于:

  • 仿函数可以不带痕迹地传递上下文参数。而CallBack技术通常使用一个额外的void*参数传递。这也是多数人认为CallBack技术丑陋的原因。

  • 仿函数技术可以获得更好的性能,这点直观来讲比较难以理解。

5、仿函数作用

仿函数通常有下面四个作用:

  • 作为排序规则,在一些特殊情况下排序是不能直接使用运算符<或者>时,可以使用仿函数。
  • 作为判别式使用,即返回值为bool类型。
  • 同时拥有多种内部状态,比如返回一个值得同时并累加。
  • 作为算法for_each的返回值使用。

二、迭代器

1、分类

迭代器是一个类中类,让类中类对象模仿指针的行为,迭代器通常是用来访问容器的

分类迭代器类型开始位置结束位置
正向迭代器容器名::iterator iter;begin()end()
反向迭代器容器名::reverse_iterator iter;rbegin()rend()
常正向迭代器容器名::const_iterator iter;cbegin()cend()
常反向迭代器容器名::const_reverse_iterator iter;crbegin()crend()

按照功能上来说分为三类:

  • 正向迭代器
  • 双向迭代器:list set map
  • 随机访问迭代器: array,vector,deque

注意点: stack与queue以及priority_queue 不支持迭代器访问

在这里插入图片描述

void testOne()
{
	vector<int> test = { 1,5,8,9,75,88 };
	for (vector<int>::reverse_iterator iter = test.rbegin(); iter != test.rend(); iter++) 
	{
		cout << *iter << " ";
	}
	cout << endl;
	for (vector<int>::const_reverse_iterator iter = test.crbegin(); iter != test.crend(); iter++)
	{
		cout << *iter << " ";
	}
	cout << endl;
}

2、辅助函数

  • advance(iter,n):移动
  • distance(beginPos ,endPos): 元素个数
  • iter_swap(first,second):交换

在这里插入图片描述

void testTwo() 
{
	list<int> data = { 1,2,3,4,5,6 };
	list<int>::iterator iter = data.begin();
	//cout << *(iter + 2) << endl; //错误,链表中没有这个操作
	advance(iter, 2);
	cout << *iter << endl;
	cout << distance(data.begin(), data.end()) << endl;
}

3、流型迭代器

输出流型迭代器

  • ostream_iterator object(ostream& out);
  • ostream_iterator object(ostream& out,const char* str);
  • object=value : 等效 cout<<value

输入流型迭代器

  • istream_iterator object; //End-of-stream
  • istream_iterator object(istream& in);
  • *object 等效 cin操作

在这里插入图片描述

void test3() 
{
	ostream_iterator<int> object(cout);
	object = 1234;
	cout << endl;
	vector<int> king = { 1,2,3,4,5,6,7,8 };
	copy(king.begin(), king.end(), ostream_iterator<int>(cout, " "));
	cout <<endl<< "输入型流迭代器:" << endl;
	vector<int> tql;
	istream_iterator<int> end;
	istream_iterator<int> test(cin);
	while(test != end) 
	{
		tql.push_back(*test);
		test++;
	}
	for (auto v : tql) {
		cout << v << " ";
	}
}

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

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

相关文章

若依/RuoYi-Vue,若依管理系统-启动步骤

若依RuoYi-Vue前后端项目启动流程_若依前端怎么启动_primary taste_mm的博客-CSDN博客若依官网&#xff1a;RuoYi 若依官方网站 |后台管理系统|权限管理系统|快速开发框架|企业管理系统|开源框架|微服务框架|前后端分离框架|开源后台系统|RuoYi|RuoYi-Vue|RuoYi-Cloud|RuoYi框架…

进驻Lidl利多超市利器—— EDI

Lidl利多超市是源自德国的跨国零售企业&#xff0c;成立于1973年&#xff0c;发展迅速&#xff0c;目前在欧洲拥有10,800多家门店&#xff0c;覆盖29个国家。Lidl的业务范围包括食品、饮料、家庭用品、家具、电器等多个品类。Lidl一直致力于提供高性价比的商品&#xff0c;以满…

FIT2CLOUD飞致云发布开源轻量级云管平台CloudExplorer Lite

2023年4月21日&#xff0c;中国领先的开源软件公司FIT2CLOUD飞致云正式发布开源轻量级云管平台项目CloudExplorer Lite。CloudExplorer Lite&#xff08;https://github.com/CloudExplorer-Dev&#xff09;脱胎于飞致云创始软件产品CloudExplorer多云管理平台&#xff0c;支持对…

图表示学习算法学习

struc2vec: Learning Node Representations from Structural Identity learning latent representations for the structural identity of nodes. &#xff1a; 从结构特征中学习节点潜在表示 node representation : 节点表示 structural identity : 结构特征 struct2Vec是一个…

《UVM实战》学习笔记——第七章 UVM中的寄存器模型1——寄存器模型介绍、前门/后门访问

文章目录 前言一、寄存器模型简介1.1 带寄存器配置总线的DUT1.2 参考模型如何读取寄存器的值1.3 寄存器模型的基本概念 二、简单的寄存器模型2.1 只有一个寄存器的寄存器模型2.2 将寄存器模型集成到验证平台2.3 在验证平台中使用寄存器模型 三、前门访问和后门访问3.1 前门访问…

2023年淮阴工学院五年一贯制专转本应用文写作考试大纲

2023年淮阴工学院五年一贯制专转本应用文写作考试大纲 一、考核对象 本课程的考核对象是五年一贯制高职专转本秘书学专业普通在校生考生。 二、考核目的 通过课堂教学&#xff0c;学生应当能够识记、理解和应用有关应用文写作的基本理论和基本技能。其中&#xff0c;识记指…

TortoiseSVN使用-TortoiseSVN更换或重置登录用户

文章目录 3.4.9 TortoiseSVN更换或重置登录用户 本人其他相关文章链接 3.4.9 TortoiseSVN更换或重置登录用户 1&#xff0c;打开SVN的settings 2&#xff0c;找到Saved Data栏&#xff0c;右侧Authentication data项点击清除按钮clear 3&#xff0c;再次打开SVN&#xff0c;会要…

AgentGPT已成气候

AgentGPT之前也有介绍过&#xff0c;它最主要的功能是在ChatGPT的功能基础上&#xff0c;允许你自己自定义配置部署&#xff0c;根据你给出的命令&#xff0c;它将尝试通过思考&#xff0c;和执行&#xff0c;不用重复的给它发送指令&#xff0c;直接给你汇总好结果。 安装步骤…

牛客网刷题总结

1.利用%符号获取特定位数的数字。 2.强制类型转换 &#xff08;将float转换为int &#xff09; 3.计算有关浮点型数据时&#xff0c;要注意你计算过程中所有的数据都是浮点型 4.0/3.0 ! 4/3 4.通过位操作符实现输出2的倍数&#xff08;对于位操作符不熟悉的小伙伴可以看看我…

StringBuffer类详解

StringBuffer 定义 1.java.lang.StringBuffer代表可变的字符序列&#xff0c;可以对字符串内容进行增删 2.很多方法与String相同&#xff0c;但StringBuffer是可变长度的。 3.StringBuffer是一个容器。 String和StringBuffer的不同 1.String保存的是字符串常量&#xff0c…

机器学习——用KNN解决非线性回归问题

问&#xff1a;k最近邻分类模型是非线性模型。 答&#xff1a;正确。k最近邻分类模型是非线性模型&#xff0c;因为它的决策边界是由最近邻居点的类别决定的&#xff0c;而最近邻居点的分布通常是不规则的&#xff0c;因此决策边界也就不是线性的。因此&#xff0c;k最近邻分类…

继续【Stable-Diffusion WEBUI】方方面面研究(内容索引)

文章目录 &#xff08;零&#xff09;前言&#xff08;一&#xff09;绘图&#xff08;1.1&#xff09;模型&#xff08;1.1.1&#xff09;基础模型&#xff08;Stable-diffusion模型&#xff09;&#xff08;1.1.2&#xff09;人物模型&#xff08;LoRA模型&#xff09; &…

我在公司彻夜撸码,老板天天开X6夜店蹦迪,到头来工资还拖欠

讲道理&#xff0c;我的学历远达不到BAT等名企大厂的要求&#xff0c;去不了好公司我认了&#xff0c;大专毕业的我在找工作的时候发现留给自己的机会并不多&#xff0c;最后去了一家不知名的小公司。入职后才发现这家公司其实就是个外包公司&#xff0c;里面的业务部门和制度相…

【RPA开发】Selenium 实现网页自动化

开发时有时会遇到网页爬取限制的情况&#xff0c;那么此时可以通过 Selenium 来解决这个问题&#xff0c;因为 Selenium 是模拟浏览器执行网页爬取&#xff0c;相比 Request/API 操作更安全&#xff0c;服务器会完全认为是用户在用浏览器进行操作&#xff0c;如此可以实现网页自…

centos7环境下:DolphinScheduler3.1.5简介和伪集群模式安装部署

centos7环境下&#xff1a;DolphinScheduler3.1.5简介和伪集群模式安装部署 DolphinScheduler简介 Apache DolphinScheduler是一个分布式、易扩展的可视化DAG工作流任务调度平台。致力于解决数据处理流程中错综复杂的依赖关系&#xff0c;使调度系统在数据处理流程中开箱即用…

Quartz定时任务

基本介绍 Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目&#xff0c;可以与J2EE与J2SE应用程序相结合也可以单独使用。Quartz可以用来创建简单或为运行十个、百个、甚至是好几万个Jobs这样复杂的日程序表&#xff0c;Jobs可以做成标准的Java组件或EJBs Qua…

android framework-ActivityManagerService(AMS)下

一、ActivityThread \frameworks\base\core\java\android\app\ActivityThread.java 1.1、main public static void main(String[] args) {Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");// Install selective syscall interceptionAnd…

java native 方法编写

目录 前言 1、创建 java native 方法 2、创建洞态链接库项目 3、加载 dll 文件 前言 Java 提供了调用 C 或 C 函数的方法&#xff0c;这种方法就是 native 方法&#xff0c;全称 Java Native Interface (JNI) 1、创建 java native 方法 1&#xff09;新建 java 类文件 …

【CMake】如何使用CMake构建一个工程

1.如何使用CMake构建一个工程 1. 使用 CMakelists.txt 构建工程 一个最简单 CMake 的项目是将某个源文件构建成为可执行文件&#xff0c;使用CMake 构建项目时&#xff0c;你需要创建一个 CMakeLists.txt 文件&#xff0c;通常情况下&#xff0c;下面三条命令在每个 CMakeLis…

四结4.20

这俩天实现了分离客服端和服务端&#xff0c; 先将对象序列化&#xff0c;通过socket从客户端发送到服务端&#xff0c;反序列化 进行数据库操作&#xff0c;最后向客户端返回结果&#xff0c;渲染javafx的画面 中途遇到俩种异常&#xff0c;因为一报错弹出一大段红色的英语…