网站建设实训设备,池州微信网站建设,中国外贸订单网,知名企业网站大全C提高编程
本阶段主要针对C泛型编程和STL技术做详细讲解#xff0c;探讨C更深层的使用
泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。 模板
1.模板的概念
模板就是建立通用的模具#xff0c;大大提高复用性
例如#xff1a; 2.函数模板
C另一种编程思想称…C提高编程
本阶段主要针对C泛型编程和STL技术做详细讲解探讨C更深层的使用
泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。 模板
1.模板的概念
模板就是建立通用的模具大大提高复用性
例如 2.函数模板
C另一种编程思想称为泛型编程主要利用的技术就是模板
C提供两种模板机制函数模板和类模板
1.函数模板语法
函数模板作用
建立一个通用函数其函数返回值类型和形参类型可以不具体制定用一个虚拟的类型来代表。
语法
templatetypename T 函数声明或定义
解释
template —— 声明创建模板
typename —— 表面其后面的符号是一种数据类型可以用class替代
T —— 通过的数据类型名称可以替换通常为大写字母也可以自己定义
示例
#includeiostream
using namespace std;
//函数案例
//两个整型交换的函数
void swapInt(int a, int b)//使用C引用的知识
{int temp a;a b;b temp;
}
//两个浮点型交换的函数
void swapDouble(double a, double b)//使用C引用的知识
{double temp a;a b;b temp;
}
int main()
{int a 10;int b 20;swapInt(a, b);cout a a b b endl;double c 1.1;double d 2.2;swapDouble(c, d);cout c c d d endl;return 0;
}
上面的是函数实现数据交换的方式接下来展示的是函数模板
#includeiostream
using namespace std;
templatetypename T//声明一个模板告诉编译器后面代码中紧跟着的T不要报错T是通用的数据类型
void mySwap(T a, T b)
{T temp a;a b;b temp;
}
int main()
{int a 10;int b 20;//利用函数模板交换//两种方式使用函数模板//1.自动类型推导mySwap(a, b);cout a a b b endl;//2.显示指定类型double c 1.1;double d 2.2;mySwapdouble(c, d);cout c c d d endl;return 0;
}
总结 函数模板利用关键字template 使用函数模板有两种方式自动类型推导、显示指定类型 模板的目的是为了提高复用性将类型参数化
2.函数模板注意事项
注意事项 自动类型推导必须推导出一致的数据类型T才可以使用 模板必须要确定出T的数据类型才可以使用
示例
#includeiostream
using namespace std;
//函数模板注意事项
templatetypename T//typename可以替换成class有的程序员将typename作为函数模板class作为类模板作为区分
void mySwap(T a, T b)
{T temp a;a b;b temp;
}
//1.自动类型推导必须要推导出一致的数据类型T
void test01()
{int a 10;int b 20;char c c;mySwap(a, c);//error推导不出一致的T类型cout a a c c endl;
}
//2.模板必须要确定出T的数据类型才可以使用
templateclass T
void func()
{cout func函数的调用 endl;
}
void test02()
{funcint();
}
int main()
{//test01();test02();return 0;
}
3.函数模板案例
案例描述 利用函数横板封装一个排序的函数可以对不同数据类型数组进行排序 排序规则从大到小排序算法为选择排序 分别利用char数组和int数组进行测试
示例
#includeiostream
using namespace std;
//实现通用 对数组进行排序的函数
//规则 从大到小
//算法 选择算法
//测试 char 数组 int
//交换函数
templatetypename T
void mySwap(T a, T b)
{T temp a;a b;b temp;
}
//排序算法
templatetypename T
void mySort(T arr[], int len)
{for(int i 0;i len; i){int max i;//认定最大值的下标for(int j i;j len; j){//认定的最大值比遍历出的数值要小说明j下标的元素才是真正的最大值if(arr[max] arr[j]){max j;//更新最大值坐标}}if(max ! i){//交换max和i元素mySwap(arr[max],arr[i]);}}
}
//提供打印数组模板
templatetypename T
void printArray(T arr[], int len)
{for(int i 0;i len; i){cout arr[i] ;}cout endl;
}
void test01()
{//测试char数组char charArr[] badcfe;int num sizeof(charArr) / sizeof(char);mySort(charArr, num);printArray(charArr, num);
}
void test02()
{//测试int数组int intArr[] { 7,5,1,3,9,2,4,6,8 };int num sizeof(intArr) / sizeof(int);mySort(intArr, num);printArray(intArr, num);
}
int main()
{test01();test02();return 0;
}
4.普通函数与函数模板的区别
普通函数与通数模板区别 普通函数调用时可以发生自动类型转换隐式类型转换 函数模板调用时如果利用自动类型推导不会发生隐式类型转换 如果利用显示指定类型的方式可以发生隐式类型转换
示例
#includeiostream
using namespace std;
//普通函数调用可以发生隐式类型转换
//函数模板调用如果利用自动类型推导不会发生隐式类型转换
//函数模板调用如果利用显示指定类型的方式可以发生隐式类型转换
//普通函数
int myAdd1(int a, int b)
{return a b;
}
void test01()
{int a 10;int b 20;char c c;//ASCLL码中a - 97c - 99int ret myAdd1(a,c);cout ret endl;//109会隐式地进行计算
}
//函数模板
templatetypename T
T myAdd2(T a, T b)
{return a b;
}
void test02()
{int a 1;char b b;//自动类型推导cout myAdd2(a,b) endl;//error出现了两种类型无法进行运算//显示指定类型cout myAdd2int(a, b) endl;//99 可以执行运行
}
int main()
{test01();test02();return 0;
}
总结建议使用显示指定类型的方式调用函数模板因为可以自己确定通用类型T
5.普通函数与函数模板的调用规则
调用规则如下 如果函数模板和普通函数都可以实现优先调用普通函数 可以通过空模板参数列表来强制阀用函数模板 函数模板也可以发生重载 如果函数模板可以产生更好的匹配优先调用函数模板
示例
#includeiostream
using namespace std;
//普通函数与函数模板调用规则
//1.如果函数模板和普通函数都可以实现优先调用普通函数
//2.可以通过空模板参数列表来强制阀用函数模板
//3.函数模板也可以发生重载
//4.如果函数模板可以产生更好的匹配优先调用函数模板
void myPrint(int a, int b)
{cout 调用普通函数 endl;
}
templatetypename T
void myPrint(T a, T b)
{cout 调用模板 endl;
}
//void myPrint(T a, T b, T c)
//{//cout 调用模板 endl;
//}
void test01()
{int a 10;int b 10;int c 10;myPrint(a, b);//优先调用普通函数有声名//通过空模板参数列表强制调用函数模板myPrint(a, b);//函数模板也可以发生重载//myPrint(a, b, c);//如果函数模板产生更好的匹配优先调用模板函数char c1 a;char c2 b;myPrint(c1, c2);
}
int main()
{test01();return 0;
}
总结既然提供提供了函数模板最好就不要提供普通函数否则容易出现二义性。
6.模板的局限性
局限性
模板的通用性并不是万能的
例如
templatetypename T
void f(T a, T b)
{a b;
}
在上述代码中提供的赋值操作如果传入的a和b是一个数组就无法实现。
templateclass T
void f(T a, T b)
{if(a b){ ... }
}
在上述代码中如果T的数据类型传入的是像Person这样的自定义数据类型也无法正常运行
因此C为了解决这种问题提供模板的重载可以为这些特定的类型提供具体化的模板
示例
#includeiostream
#includestring
using namespace std;
//模板局部性
//模板并不是万能的有些特定数据类型需要用具体化方法做特殊实现
class Person
{
public:Person(string name, int age){this-m_Name name;this-m_Age age;}//姓名string m_Name;//年龄int m_Age;
};
//对比两个数据是否相等函数
templatetypename T
bool myCompare(T a, T b)
{if(a b){return true;}else{return false;}
}
//利用具体化Person的版本实现代码具体化优先使用
template bool myCompare(Person p1, Person p2)
{if(p1.m_Name p2.m_Name p1.m_Age p2.m_Age){return true;}else{return false;}
}
void test01()
{int a 10;int b 10;bool ret myCompare(a, b);if(ret){cout a b endl;}else{cout a ! b endl;}
}
void test02()
{Person p1(Tom,10);Person p2(Tom,10);bool ret myCompare(p1,p2);if(ret){cout p1 p2 endl;}else{cout p1 ! p2 endl;}
}
int main()
{test01();test02();return 0;
}
总结 利用具体化的模板可以解决自定义类型的通用化 学习模板并不是为了写模板而是在STL能够运用系统提供的模板
类模板
1.类模板语法
类模板作用 建立一个通用类类中的成员数据类型可以不具体制定用一个虚拟的类型来代表。
语法
templatetypename T
类
解释 template——声明创建模板 typename——表面其后面的符号是一种数锯类型可以用class代替 T——通用的数据类型名称可以替换通常为大写字母
示例
#includeiostream
#includestring
using namespace std;
//类模板
templateclass NameType, class AgeType
class Person
{
public:Person(NameType name,AgeType age){this-m_Name name;this-m_Age age;}void showPerson(){cout name: this-m_Name age: this-m_Age endl;}NameType m_Name;AgeType m_Age;
};
void test01()
{Personstring,int p1(zhangsan,18);p1.showPerson();
}
int main()
{test01();return 0;
}
2.类模板与函数模板区别
类模板与函数模板区别主要有两点 类模板没有自动类型推导的使用方式 类模板在模板参数列表中可以有默认参数
示例
#includeiostream
#includestring
using namespace std;
//类模板与函数模板区别
templateclass NameType, class AgeType int//templateclass NameType, class AgeType int
//类模板在模板参数列表中可以有默认参数但是函数模板无法调用默认参数
class Person
{
public:Person(NameType name, AgeType age ){this-m_Name name;this-m_Age age;}void showPerson(){cout name: this-m_Name age: this-m_Age endl;}NameType m_Name;AgeType m_Age;
};
//1.类模板没有自动类型推导使用方式
void test01()
{//Person p(zhangsan,18);error无法使用自动类型推导Personstring,intp(zhangsan,18);//只能使用显示指定函数p.showPerson();
}
//2.类模板在模板参数列表中可以有默认参数
void test02()
{Personstring,intp(lisi,2);p.showPerson();
}
int main()
{test01();test02();return 0;
}
3.类模板
类模板中成员函数和普通类中成员函数创建时机是有区别的 普通类中的成员函数开始就可以创建 类模板中的成员函数在调用时才创建
示例
#includeiostream
using namespace std;
//类模板中成员函数创建时机
//类模板中成员函数在调用时采取创建
class Person1
{
public:void showPerson1(){cout Person1 show endl;}
};
class Person2
{
public:void showPerson2(){cout Person2 show endl;}
};
templateclass T
class MyClass
{
public:T obj;//类模板中的成员函数void func1(){obj.showPerson1();}void func2(){obj.showPerson2();}
};
void test01()
{MyClassPerson1 m1;//要确定调用的成员类型m1.func1();MyClassPerson2 m2;m2.func2();
}
int main()
{test01();return 0;
}
总结类模板中的成员函数并不是开始就创建的在调用时才去创建
4.类模板对象做函数参数
学习目标
类模板实例化出的对象向函数传参的方式。
一共有三种传入方式 指定传入的类型——直接显示对象的数据类型 参数模板化——将对象中的参数变为模板进行传递 整个类模板化——将这个对象类型模板化进行传递
示例
#includeiostream
#includestring
using namespace std;
//类模板对象做函数参数
templateclass T1, class T2
class Person
{
public:Person(T1 name, T2 age){this-m_Name name;this-m_Age age;}void showPerson(){cout 姓名 this-m_Name 年龄 this-m_Age endl;}T1 m_Name;T2 m_Age;
};
//1.指定传入形式
void printPerson1(Personstring,intp)
{p.showPerson();
}
void test01()
{Personstring,int p(zhangsan,18);//1.指定传入形式printPerson1(p);
}
//2.参数模板化
templateclass T1, class T2
void printPerson2(PersonT1,T2p)
{p.showPerson();cout T1的类型 typeid(T1).name() endl;cout T2的类型 typeid(T2).name() endl;
}
void test02()
{Personstring,int p(lisi,20);printPerson2(p);
}
//3.整个类模板化
templateclass T
void printPerson(T p)
{p.showPerson();cout T的数据类型 typeid(T).name() endl;
}
void test03()
{Personstring,int p(wangwu,22);printPerson(p);
}
int main()
{test01();test02();test03();return 0;
}
姓名zhangsan 年龄18
姓名lisi 年龄20
T1的类型NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
T2的类型i
姓名wangwu 年龄22
T的数据类型6PersonINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEiE
总结 通过类模板的对象可以有三种方式向函数中进行传参 使用比较广泛的是第一种指定传入的类型
typeid的知识补充
在c中typeid用于返回指针或引用所指对象的实际类型。
typeid就是C中查看数据类型的函数语法一般为typeid(A).name()
由第二的T1返回值知string的原名是很长的。
示例
#includeiostream
using namespace std;
class Base {};
class Derived
{
public:
};
int main()
{Base b, *pb;pb NULL;Derived d;
cout typeid(int).name() endl typeid(unsigned).name() endl typeid(long).name() endl typeid(unsigned long).name() endl typeid(char).name() endl typeid(unsigned char).name() endl typeid(float).name() endl typeid(double).name() endl typeid(string).name() endl typeid(Base).name() endl typeid(b).name()endl typeid(pb).name()endl typeid(Derived).name() endl typeid(d).name()endl typeid(type_info).name() endl;return 0 ;
}
vscode上面输出类型就是如下可见
i
j
l
m
c
h
f
d
NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
4Base
4Base
P4Base
7Derived
7Derived
St9type_info
5.类模板与继承
当类模板碰到继承时需要注意一下几点 当子类继承的类是一个类模板时子类在声明的时候要指定出父类中T的类型 如果不指定编译无法给子类分配内存 如果想灵活指定出父类中T的类型子类也需变为类模板
示例
#includeiostream
#includestring
using namespace std;
//类模板与继承
templateclass T
class Base
{T m;
};
class S1 :public Baseint//必须要知道父类中的T类型才能继承给子类
{
};
void test01()
{S1 s1;
}
//想灵活指定父类中T类型子类也需要变类模板
templateclass T1, class T2
class S2 :public BaseT2
{
public:S(){cout T1类型 typeid(T1).name() endl;cout T2类型 typeid(T2).name() endl;}T1 obj;
};
void test02()
{S2int,char s2;s2.S();
}
int main()
{test01();test02();return 0;
}
总结如果父类是类模板子类需要指定出父类中的数据类型
6.类模板成员函数类外实现
学习目标够掌控类模板中的成员函数类外实现
示例
#includeiostream
using namespace std;
//类模板成员函数类外实现
templateclass T1, class T2
class Person
{
public:Person(T1 name, T2 age);void showPerson();T1 m_Name;T2 m_Age;
};
//构造函数类外实现
templateclass T1, class T2
PersonT1,T2::Person(T1 name, T2 age)
{this-m_Name name;this-m_Age age;
}
//成员函数类外实现
templateclass T1, class T2
void PersonT1,T2::showPerson()//体现是类模板
{cout 姓名 this-m_Name 年龄 this-m_Age endl;
}
void test01()
{Personstring,int p(Tom,20);p.showPerson();
}
int main()
{test01();return 0;
}
总结类模板中成员函数类外实现时需要加上模板参数列表
7.类模板分文件编写
学习目标
掌握类模板成员函数分文件编写产生的问题以及解决方式
问题
类模板中成员函数创建时机是在调用阶段导致分文件编写时链接不到
解决
解决方法一直接包含.cpp文件
解决方法二将声明和实现写到同一文件中并更改后缀为.hpphpp是约定的名称并不是强制。
示例
#includeiostream
#includestring
using namespace std;
//类模板分文件编写问题以及解决
templateclass T1, class T2
class Person
{
public:Person(T1 name, T2 age);void showPerson();T1 m_Name;T2 m_Age;
};
//类外实现
templateclass T1, class T2
PersonT1,T2::Person(T1 name, T2 age)
{this-m_Name name;this-m_Age age;
}
templateclass T1, class T2
void PersonT1,T2::showPerson()
{cout 姓名 this-m_Name 年龄 this-m_Age endl;
}
void test01()
{Personstring,intp(zhangsan,18);p.showPerson();
}
int main()
{test01();return 0;
}
分文件方法一
person.h
#pragma once
#includeiostream
#includestring
using namespace std;
templateclass T1, class T2
class Person
{
public:Person(T1 name, T2 age);void showPerson();T1 m_Name;T2 m_Age;
};
person.cpp
#includeperson.h
templateclass T1, class T2
PersonT1,T2::Person(T1 name, T2 age)
{this-m_Name name;this-m_Age age;
}
templateclass T1, class T2
void PersonT1,T2::showPerson()
{cout 姓名 this-m_Name 年龄 this-m_Age endl;
}
main.cpp
#includeiostream
using namespace std;
//第一种方式
#includeperson.cpp//person.h调用的话是不会创建里面的东西
//第二种方式将.h和.cpp中的内容写到一起将后缀名改为.hpp
void test01()
{Personstring,intp(zhangsan,18);p.showPerson();
}
int main()
{test01();return 0;
}
分文件方法二
person.hpp
#pragma once
#includeiostream
#includestring
using namespace std;
templateclass T1, class T2
class Person
{
public:Person(T1 name, T2 age);void showPerson();T1 m_Name;T2 m_Age;
};
templateclass T1, class T2
PersonT1,T2::Person(T1 name, T2 age)
{this-m_Name name;this-m_Age age;
}
templateclass T1, class T2
void PersonT1,T2::showPerson()
{cout 姓名 this-m_Name 年龄 this-m_Age endl;
}
main.cpp
#includeiostream
using namespace std;
//第一种方式
#includeperson.hpp//person.h调用的话是不会创建里面的东西
//第二种方式将.h和.cpp中的内容写到一起将后缀名改为.hpp
void test01()
{Personstring,intp(zhangsan,18);p.showPerson();
}
int main()
{test01();return 0;
}
8.类模板与友元
学习目标掌握类模板配合友元函数的类内和类外实现
全局函数类内实现——直接在类内声明友元即可
全局函数类外实现——需要提前让编译器知道全局函数的存在
//提前让编译器知道有Person类的存在
templateclass T1, class T2
class Person;
//类外实现先让编译器看到了解到有这么一个代码
templateclass T1, class T2
void printPerson2(PersonT1,T2 p)
{cout 类外实现的内容 姓名 p.m_Name 年龄 p.m_Age endl;
}
示例
#includeiostream
#includestring
using namespace std;
//通过全局函数打印Person的信息
//提前让编译器知道有Person类的存在
templateclass T1, class T2
class Person;
//类外实现先让编译器看到了解到有这么一个代码
templateclass T1, class T2
void printPerson2(PersonT1,T2 p)
{cout 类外实现的内容 姓名 p.m_Name 年龄 p.m_Age endl;
}
templateclass T1, class T2
class Person
{//全局函数 类内实现friend void printPerson(PersonT1,T2 p){cout 类内实现的内容 姓名 p.m_Name 年龄 p.m_Age endl;}//全局函数 类外实现//加一个空模板参数列表//如果全局函数 是类外实现 需要让编译器提前知道这个函数的存在。friend void printPerson2(PersonT1,T2 p);public:Person(T1 name, T2 age){this-m_Name name;this-m_Age age;}private:T1 m_Name;T2 m_Age;
};
void test01()
{Personstring,intp(Tom,20);printPerson(p);
}
void test02()
{Personstring,intp(Jerry,18);printPerson2(p);
}
int main()
{test01();test02();return 0;
}
总结建议全局函数做类内实现用法简单而且编译器可以直接识别。
类模板案例
案例描述实现一个通用的数组类要求如下 可以对内置数据类型以及自定义数据类型的数据进行存储 将数组中的数据存储到堆区 构造函数中可以传入数组的容量 提供对应的拷贝构造函数以及operator防止浅拷贝问题 提供尾插法和尾删除法对数组中的数据进行增加和删除 可以通过下标的方式访问数组中的元素 可以获取数组中当前元素个数和数组的容量
函数实现
MyArray.hpp
#pragma
#includeiostream
#includestring
using namespace std;
templateclass T
class MyArray
{
public://有参构造 参数 容量MyArray(int capacity){//cout MyArray有参构造的调用 endl;this-m_Capacity capacity;this-m_Size 0;this-pAddress new T[this-m_Capacity];}//拷贝构造MyArray(const MyArray arr){//cout MyArray拷贝构造的调用 endl;this-m_Capacity arr.m_Capacity;this-m_Size arr.m_Size;//this-pAddress arr.pAddress//深拷贝this-pAddress new T[arr.m_Capacity];//将arr中的数据都拷贝过来for(int i 0;i this-m_Size; i){this-pAddress[i] arr.pAddress[i];}}//operator 防止浅拷贝问题 a b cMyArray operator(const MyArray arr){//cout MyArray的operator的调用 endl;//先判断原来堆区是否有数据如果有先释放if(this-pAddress ! NULL){delete[] this-pAddress;this-pAddress NULL;this-m_Capacity 0;this-m_Size 0;}//深拷贝this-m_Capacity arr.m_Capacity;this-m_Size arr.m_Size;this-pAddress new T[arr.m_Capacity];for(int i 0;i this-m_Size; i){this-pAddress[i] arr.pAddress[i];}return *this;}//尾插法void Push_Back(const T val){//判断容量是否等于大小if(this-m_Capacity this-m_Size){return;}this-pAddress[this-m_Size] val;//在数组末尾插入数据this-m_Size;//更新数组大小}//尾删法void Pop_Back(){//让用户访问不到最后一个元素即为尾删逻辑删除if(this-m_Size 0){return;}this-m_Size--;//更新数组大小}//通过下标方式访问数组中的元素 arr[0] 100 赋值操作T operator[](int index){return this-pAddress[index];}//返回数组容量int getCapacity(){return this-m_Capacity;}//返回数组大小int getSize(){return this-m_Size;}//析构函数~MyArray(){//cout MyArray析构函数的调用 endl;if(this-pAddress ! NULL){delete[] this-pAddress;this-pAddress NULL;}}
private:T * pAddress;//指针指向堆区开辟的真实数组int m_Capacity;//数组容量int m_Size;//数组大小
};
main.cpp
#includeiostream
#includestring
using namespace std;
#includeMyArray.hpp
void test01()
{//测试构造函数、析构函数、operator的调用使用完课注释掉MyArrayintarr1(5);MyArrayintarr2(arr1);MyArrayintarr3(100);arr3 arr1;
}
void printIntArray(MyArrayintarr)
{for(int i 0;i arr.getSize(); i){cout arr[i] ;}cout endl;
}
void test02()
{//测试尾插法、尾删法和其余函数调用MyArrayintarr1(5);for(int i 0;i 5; i){//利用尾插法arr1.Push_Back(i);}cout arr1的打印输出为 endl;printIntArray(arr1);cout arr1的容量为 arr1.getCapacity() endl;cout arr1的大小为 arr1.getSize() endl;MyArrayintarr2(arr1);//拷贝构造函数cout arr2的打印输出为 endl;printIntArray(arr2);cout arr2的容量为 arr2.getCapacity() endl;cout arr2的大小为 arr2.getSize() endl;
}
//测试自定义数据类型
class Person
{
public:Person();Person(string name, int age){this-m_Name name;this-m_Age age;}string m_Name;int m_Age;
};
void printPersonArray(MyArrayPerson arr)
{for(int i 0;i arr.getSize(); i){cout 姓名 arr[i].m_Name 年龄 arr[i].m_Age endl;}
}
void test03()
{MyArrayPersonarr(10);Person p1(zhangsan,18);Person p2(lisi,19);Person p3(wangwu,20);Person p4(zhaoliu,21);Person p5(tangqi,22);//将数据插入到数组中arr.Push_Back(p1);arr.Push_Back(p2);arr.Push_Back(p3);arr.Push_Back(p4);arr.Push_Back(p5);//打印数组printPersonArray(arr);//打印数组容量cout 数组容量 arr.getCapacity() endl;//打印数组大小cout 数组大小 arr.getSize() endl;
}
int main()
{test01();test02();test03();return 0;
} 今天我们全面学习的模板和类模板这一块的知识就到此为止啦。掌握和运用还需要持续不断的训练和磨合加深对模板板块的理解这在未来对我们的提升自己编码水平和能力都是非常重要的。通过学长参加招聘会我了解到今年春招情况惨淡大部分公司都对软件开发部门进行了比较具有规模性的裁员。应对这样残酷的现实我们只能不断提高自己踏实掌握技术才是硬道理
码字不易希望大家多多支持