用法目录:
struct
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//对结构体变量初始化
struct Student
{
char id[5];
char name[10];
int age;
char sex[3];
}s2={"0002","Mary",15,"女"};
int main()
{
struct Student s1={"0001","Tom",10,"男"};
struct Student s3;
strcpy(s3.id,"0003");
strcpy(s3.name,"John");
s3.age=13;
strcpy(s3.sex,"男");
system("PAUSE");
return 0;
}
struct 是C语言中用户用来自定义数据类型的,其只能是一些变量的集合,虽然可以封装数据但不能隐藏数据,且成员不可以是函数。struct用法和用int定义整型变量一样,struck就是在程序编辑初要声明的结构体变量。
typedef
typedef struct k//建立int型数据的二叉树
{
int data;
struct k *l,*r;
}btree;
typedef就是用来定义一种类型的新别名
表示:
struct k{……}:定义一个结构体包含data以及l、r;
Typedef struct k btree:将k结构体定义别名btree,即用btree即可定义k结构体类型;
struct k *l,*r:定义指针l、r,可指向struct类型。
注意:经常将typedef与struct同时使用。
malloc与free
ElemType *p=(ElemType *)malloc(sizeof(ElemType)*InitSize);//申请空间
free(p); //释放空间
malloc是c与c++中用来动态申请空间
free是c与c++中用来释放空间
注意:经常将malloc与free同时使用。
*与&
对于 * 与&分为两种情况:
1.声明时 * 是声明指针,&是声明引用(引用就是某个目标变量的别名)。
2.使用时 * 是解引用,&是取地址。
void fun(int *p,char &x) //*是定义指针
{
*p=……; //*是解引用
}
函数定义时形参里的 * 是定义指针,函数里面使用 * 是解引用。
1.声明引用
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
int a=3;
int &b=a;
int c=a;
cout<<"a:"<<a<<endl;
cout<<"b:"<<b<<endl;
cout<<"c:"<<c<<endl;
b=10;
cout<<"a:"<<a<<endl;
cout<<"b:"<<b<<endl;
cout<<"c:"<<c<<endl;
cout<<"&a:"<<&a<<endl;
cout<<"&b:"<<&b<<endl;
cout<<"&c:"<<&c<<endl;
}
输出:
a:3
b:3
c:3
a:10
b:10
c:3
&a:0x7ffee05caacc
&b:0x7ffee05caacc
&c:0x7ffee05caabc
int &b=a; 是指将a取个别名叫b,改变b的值也就是改变a的值,并不是声明一个新变量b,不占存储单元。
2.声明指针
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
int *b,*c,*d;
int a=10;
b=&a; //将a的地址存入指针b,即b指向a
c=(int *)a;
printf("a=%d,b=%d,c=%d\n",a,b,c);
*b=*b+10; //将b中地址所指向值+10,即a+10
c=c+10; //c为int指针型每一个占4字节,则c的值增加40
printf("a=%d,b=%d,c=%d\n",a,b,c);
d=(int *)malloc(sizeof(int));
*d=10;
printf("d=%d,*d=%d",d,*d);
}
输出:
a=10,b=-435508412,c=10
a=20,b=-435508412,c=50
d=-1908407440,*d=10
创建一个指针可指向同类型数据的地址(&取地址或molloc申请空间的地址)
3.解引用
引用其实就是引用该变量的地址,“解"就是把该地址对应的东西解开,解出来,就像打开一个包裹一样,那就是该变量的值了,所以称为"解引用”。
4.取地址
取该变量的地址。
pair
pair是c++中的一种模板类型,每个pair中能且只能存储两个值,这两个值的种类无限制,可以为int、string等基本数据类型,也可以为用户自定义struct结构体。
1.pair的定义
pair<string,int> p; //定义pair变量p,分别为string与int型
pair<int ,int > p; //定义pair变量p,分别为int与int型
pair<double,int> p; //定义pair变量p,分别为double与int型
2.pair的赋值
pair<int ,int > p (5,6); //直接赋值
pair<int ,int > p1= make_pair(5,6); //调用函数赋值
pair<int ,int > p1(2,3);
pair<int ,int > p2(p1); //用已有的对象初始化,p2.first=p1.first,p2.second=p1.second
pair<int, int> p4; //没有显示初始化,自动执行默认初始化操作。p4为(0,0)
p2 = pair<int, int> (1, 4);//直接赋值操作
p2 = p1; //赋值操作
3.pair内容的读取
每个pair都有两个属性first以及second,调用方式如下:
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
int main()
{
pair<int,int>p(2,3);
cout<<p.first<<endl;
cout<<p.second;
}
输出如下:
2
3
注意:需定义多个pair时,可用typedef将pair换名:
typedef pair<int,int>PII;
PII p1;
PII p2(2,3);
pair常用作于在二维数组中记录点的坐标
vector
vector被称为容器,使用时需要加入头文件:#include <vector>;
注意:vector可代替数组使用,但消耗的时间比数组长很多。
1.vector的初始化
vector<int> a(10);
vector<int> a[10];
//定义了10个整型元素的向量(尖括号中为元素类型名,它可以是任何合法的数据类型),但没有给出初值
vector<int> a(10,1); //定义了10个整型元素的向量,且给出每个元素的初值为1
vector<int> a(b); //用b向量来创建a向量,整体复制性赋值,b也为vector<int>型
vector<int> a(b.begin(),b.begin+3); //定义了a值为b中第0个到第2个(共3个)元素
int b[7]={1,2,3,4,5,9,8};
vector<int> a(b,b+7); //从数组中获得初值
//二维vector的初始化与赋值
vector<vector<typename>> name(行数, vector<typename>(每行的列数));
name[i][j]=x;
//二维vector的赋值
vector<vector<typename>> name;
vector<typename> b;
name.push_back(b);
2.vector的各种属性操作
a.assign //分配属性,即对a进行赋值,方式与初始化中的方式一样,eg:a.assign(b.begin(), b.begin()+3);a.assign(4,2)等
a.back(); //返回a的最后一个元素
a.front(); //返回a的第一个元素
a[i]; //返回a的第i个元素,当且仅当a[i]存在
a.clear(); //清空a中的元素
a.empty(); //判断a是否为空,空则返回ture,不空则返回false
a.pop_back(); //删除a向量的最后一个元素
a.erase(a.begin()+1,a.begin()+3); //删除a中第1个(从第0个算起)到第2个元素
a.push_back(5); //在a的最后一个向量后插入一个元素,其值为5
a.insert(a.begin()+1,5); //在a的第1个元素(从第0个算起)的位置插入数值5,如a为1,2,3,4,插入元素后为1,5,2,3,4
a.insert(a.begin()+1,3,5); //在a的第1个元素(从第0个算起)的位置插入3个数,其值都为5
a.insert(a.begin()+1,b+3,b+6); //b为数组,在a的第1个元素(从第0个算起)的位置插入b的第3个元素到第5个元素(不包括b+6),如b为1,2,3,4,5,9,8 ,插入元素后为1,4,5,9,2,3,4,5,9,8
a.size(); //返回a中元素的个数;
a.capacity(); //返回a在内存中总共可以容纳的元素个数
a.resize(10); //将a的现有元素个数调至10个,多则删,少则补,其值随机,对新加入的进行值更改
a.resize(10,2); //将a的现有元素个数调至10个,多则删,少则补,其值为2,对新加入的进行值更改
a.reserve(100); //将a的容量(capacity)扩充至100,也就是说现在测试a.capacity();的时候返回值是100
a.swap(b); //b为向量,将a中的元素和b中的元素进行整体性交换
a==b; //b为向量,向量的比较操作还有!=,>=,<=,>,<
map
使用时需要引入#include <map>头文件
map是一种关联容器,可以自动建立key-value的对应,key 和 value可以是任意你需要的类型,包括自定义类型。
1.map的初始化
map<int, string> mapStudent;// 定义一个map对象
mapStudent.insert(pair<int, string>(000,"student_zero"));// 用insert函數插入pair
mapStudent.insert(map<int, string>::value_type(001, "student_one"));// 用insert函数插入value_type数据
mapStudent[123] = "student_first";
mapStudent[456] = "student_second";//用数组方式插入
注意:当map中有这个关键字时,insert操作是不能在插入数据的,但数组方式会将原来key对应的value覆盖。
2.map的各种属性操作
mapStudent.first;//map中的key值
mapStudent.second;//map中的value值
mapStudent.size();//返回map中的元素个数
mapStudent.begin();//返回指向map头部的迭代器
mapStudent.clear();//删除所有元素
mapStudent.count(key);//返回指定元素出现的次数, (因为key值不会重复,所以只能是1 or 0)
mapStudent.empty();//如果map为空则返回true
mapStudent.end();//返回指向map末尾的迭代器
mapStudent.erase(key);//删除指定元素,成功删除返回1,否则返回0
map与unordered_map只有一点不同,即 map 容器中存储的数据使用红黑树进行排序,是有序的,也就是说map中的数据按key的值由小到大自动排序,而unordered_map 容器中是无序的。
unordered_set
unordered_set是一种关联容器,用于构建哈希表,采取的处理内部冲突方式为链地址法。
1.unordered_set的初始化
unordered_set<int> c1;
unordered_set<int> c2;
c2 = c1;
2.unordered_set的各种属性操作
c1.empty();//判断是否为空,为空返回1,否则返回0
c1.size();//获取哈希表中总共的元素类数
c1.max_size();//获取哈希表所能存储的最大存储类数
c1.count(i);//查找哈希表中是否含有i,若有返回1,否则返回0
c1.insert(i);//将i插入到哈希表中
c1.clear();//将哈希表中的元素全部删除
3.unordered_set的篮子操作
哈希表的篮子数就是指哈希表的最大存储类数,会随着元素类数增加而增加,为质数。
c1.bucket_count();//返回哈希表的篮子数
c1.bucket(i);//返回元素i所在的篮子序号
注意:一般编写代码时不会使用篮子操作。
stack
stack用于构建栈,栈的基本特性为:栈顶进栈顶出,先进后出。
1.栈的初始化
stack<typename> st; //创建一个栈对象
2.栈的基本操作
st.push(i);//将i压入栈
st.top();//取栈顶元素
st.pop();//将栈顶元素出栈
st.empty(); //判断栈是否为空
st.size();//返回栈的元素个数
注意:使用top、pop函数前,要使用empty函数判断栈是否为空,否则可能导致空指针错误。
queue
queue用于构建队列,队列的基本特性为:队尾进队头出,先进先出。
1.队列的初始化
queue<typename> name;
2.队列的基本操作
name.push(x);//将x压入队列
name.front();//获得队列的队头元素
name.back();//获得队列的队尾元素
name.pop();//使队头元素出队
name.empty();//判断队列是否为空
name.size();//返回队列的元素个数
注意:使用front、back、pop函数前,要使用empty函数判断队列是否为空,否则可能导致空指针错误。
priority_queue
优先队列priority_queue,优先级高的元素先出队列,并非按照先进先出的要求,类似一个堆。
其初始化方式为:priority_queue<Type, Container, Functional>, 其中Type为数据类型,Container为保存数据的容器,Functional为元素比较方式。
priority_queue(),默认比较方式为operator<> ,按照从小到大排列。所以top()返回的是最大值。使用greater<>后,数据从大到小排列,top()返回的就是最小值。
priority_queue()的后面两个参数缺省的话,优先队列就是大顶堆,队头元素最大。如果使用了第三个参数,那第二个参数不能省,用作保存数据的容器。
1.的初始化
priority_queue<int,vector<int> , greater<>> pq;
2.的基本操作
empty();//判断一个队列是否为空
pop();//删除队顶元素
push();//加入一个元素
size();//返回优先队列中拥有的元素个数
top();//返回优先队列的队顶元素
list
list的底层是双向循环链表结构,每个元素存储在互不相关的独立结点中,在结点中通过指针指向前一个和后一个元素。list可在任意位置进行插入、移除数据,且效率高。但list不支持随机访问。
1.的初始化
list<int> a;//构造了一个空的list
list<int> a(4,100);//a中存放4个值为100的数
list<int> a(l1.begin(),l1.end());//用l1的左闭右开区间构造a
list<int> a(l1);//用l1构造a
2.的基本操作
a.empty();//检测a是否为空,是空则返回true
a.size();//返回a中有效结点的个数
a.front();//返回a中的第一个结点值的引用
a.back();//返回a中的最后一个结点值的引用
a.clear();//清空a
a.swap(b);//交换a,b的元素
a.insert(pos,4);//在pos前插入4
a.insert(pos,5,5);//在pos前插入5个值为5的元素
vector<int> v(7,8,9);
a.insert(pos,v.begin(),v.end());//pos前插入v中对应的元素
a.pop_back();//删除a最后一个元素
a.push_bacK(val);//在a的最后插入值为val的元素
a.pop_front();//删除a的第一个元素
a.push_front(val);//在a的前面插入值为val的元素
1.的初始化
2.的基本操作
重要关键字
使用以下算法时需加入头文件#include<algorithm>
min与max
功能:返回两个值中的最小(min)、最大(max)值,用法如下:
min(a,b)//返回a与b之间的最小值
max(a,b)//返回a与b之间的最大值
//可通过以下方法找出三个及三个以上值的最大、最小值
min(a,min(b,c))//返回a、b、c之间的最小值
max(a,max(b,c))//返回a、b、c之间的最大值
sort
功能:对输入的内容进行排序,用法如下:
sort(a.begin(),a.end()); //对a中的从a.begin()(包括它)到a.end()(不包括它)的元素进行从小到大排列
//通过cmp函数设置排序方式
typedef struct //结构体
{
int value=MAX;
int l;
int r;
int kk=-1; //用于并查集
}Edge;
int cmp(Edge a,Edge b) //cmp函数
{
return a.value< b.value; //<指从小到大排序,>指从大到小排序
}
sort(edge+1,edge+m+1,cmp); //用法
reverse
功能:对输入的内容进行倒置,用法如下:
reverse(a.begin(),a.end()); //对a中的从a.begin()(包括它)到a.end()(不包括它)的元素倒置
copy
功能:将部分内容复制到指定位置,用法如下:
copy(a.begin(),a.end(),b.begin()+1); //把a中的从a.begin()(包括它)到a.end()(不包括它)的元素复制到b中,从b.begin()+1的位置(包括它)开始复制,覆盖掉原有元素
find
功能:找到某指定元素的位置并返回,用法如下:
find(a.begin(),a.end(),10); //在a中的从a.begin()(包括它)到a.end()(不包括它)的元素中查找10,若存在返回其在向量中的位置
abs
功能:返回某值的绝对值,用法如下:
abs(a);//返回a的绝对值
pow
功能:返回a的b次方,用法如下:
pow(a,b);//返回a的b次方,a为double类型。
注意:使用pow函数时需引入头文件#include <math.h>
define
功能:主要用于代替,用法如下:
#define MAX 50;//定义MAX指代常量50
#define x first;//定义x指代first,first具有特定意义时eg:a.x可用于指代a.first.
注意:define还有其他作用,此处不列举。
auto
功能1:在声明变量的时候根据变量初始值的类型自动为此变量选择匹配的类型,类似于C#中的var,用法如下:
double a = 10.1;
auto au_a = a;//自动类型推断,au_a为double类型
cout << typeid(au_a).name() << endl;
输出如下:
double
注意:编程中不建议如此使用。
功能2:for循环的另一种用法,用法如下:
int arr[10];
for(int i=0;i<10;i++)
{
arr[i]=i;
}
for(auto &a:arr)//可理解为定义一个auto类型的a,将arr数组从头开始赋值给a
{
std::cout << a;//未使用using namespace std引入库时,不可直接使用cout
}
输出如下:
0123456789
memset
功能:将某一块内存中的内容全部设置为指定的值,常用于新申请的内存初始化,赋值单位为字节,用法如下:
memset(起始位置,赋的指定值,赋值范围);
//使用正确,因为char型变量就为一字节,memset也是按字节为单位赋值
char arr[10];
memset(arr,'a',sizeof(arr));
//使用错误,int型变量占四字节,memset会将每个字节改为1,最后arr数组每个数据为16843009
int arr[10][10];
memset(arr,1,sizeof(arr));
//一般使用如下用法将int型数组赋值为无穷大
int arr[10][10];
memset(arr,0x3f,sizeof(arr));
sqrt
功能:求某个数的算术平方根,用法如下:
√x = sqrt(x);
double x=sqrt(double x);
float x=sqrt(float x);
long double x= sqrt(long double x);
注意:sqrt()函数只能求正数的算术平方根,不可以传入负数。另外,sqrt函数只能对float以及double类型数据进行操作,所以经常需要强制类型转换。
功能:用法如下:
常用自定义函数
将b进制数转化为十进制
int get(string s, int b) // 将b进制的数转化成十进制
{
int res = 0;
for (auto &c: s)
res = res * b + c - '0';
return res;
}
该算法又称为秦九韶算法。
求计算方法C(n,m)
//C(n,m)一定是整数
//C(n,m)=C(m-1,n-1)+C(m,n-1)
int Combination(int n, int m)
{
if(m==1)
return n;
if(m==0)
return 1;
if(m>n/2)
return Combination(n,n-m);
return Combination(n-1,m-1)+Combination(n-1,m);
}
但是单纯采用以上递归方式求C(n,m),会造成很多重复计算使得耗时过长,可采用空间换时间将已计算的结果记录下来,代码如下:
//C(n,m)一定是整数
//C(n,m)=C(m-1,n-1)+C(m,n-1)
int a[n][m]; //将a数组全部赋值-1
int Combination(int n, int m)
{
if(m==1)
return n;
if(m==0)
return 1;
if(m>n/2)
return a[n-1][n-1-m]!=-1?a[n-1][n-1-m]:Combination(n,n-m);
return a[n-1][m-1]=((a[n-2][m-2]!=-1?a[n-2][m-2]:Combination(n-1,m-1))+(a[n-2][m-1]!=-1?a[n-2][m-1]:Combination(n-1,m)));
}
返回树的高度
int getLen(TreeNode *root){
if(root==NULL)
return 0;
return max(getLen(root->left),getLen(root->right))+1;
}
判断是否为素数
//判断x是否为素数
bool check(int x)
{
if(x<2)
return false;
for(int i=2;i<=(int)sqrt(x);i++)
if(x%i==0)
return false;
return true;
}
基础知识
差分
差分一般用于解决:对数组的一块连续范围进行相同操作, 时间复杂度远小于暴力法。
一、题目描述
二、分析与代码
分析
使用差分解题:
1.构建差分数组,初值全部设置为0
2.对于要修改值的范围,对差分数组该范围的第一个与最后一个的后一个修改即可,eg:对数组中的第a个到第b个加上c,只需要使差分数组第a个加上c,第b+1个减去c即可
3.计算出原数组修改值,方法:将差分数组从第一个元素开始,前一个元素加到后一个元素上,差分数组的最终值就是原数组的修改值
4.将修改后的差分数组按下标加到原数组上,原数组则是按要求修改后的数组
具体代码
#include <iostream>
#include <cstdio>
const int N = 1e5 + 10;
using namespace std;
int main()
{
int n,m;
scanf(" %d %d",&n,&m);
int num[N],count[N];
int i;
for(i=0;i<n;i++) //设置差分数组
count[i]=0;
for(i=0;i<n;i++)
scanf(" %d",&num[i]);
int l,r,c;
while(m--) //构建差分数组
{
scanf(" %d %d %d",&l,&r,&c);
count[l-1]=count[l-1]+c;
count[r]=count[r]-c;
}
for(i=1;i<n;i++) //计算修改值
count[i]=count[i]+count[i-1];
for(i=0;i<n;i++) //将元素组与修改值相加
cout<<num[i]+count[i]<<" ";
}
注意:在代码运行时可能需要进行其他操作,所以当数组范围不够大时可能出现奇奇怪怪的错误,例如数组内容被修改。一般开数组用以下方法:
const int N = 1e5 + 10;
int num[N];
kmp
kmp一般用于解决:字符串匹配,例如 找出模式串a在主串b中的位置。
强调:
字符串匹配一般有如下两种方式:
1.朴素法
2. kmp算法
一、题目描述
二、分析与代码
分析
该题就是使用kmp算法查找出主串中具有的模式串的数量。首先应该求出模式串的next数组,求next数组具有两种思路代码:
1.对每个字母分别求其前面字符组成串的最大相同前后缀长度,得出相应的next数组值。
//模式串ch下标从1开始
void get_next()
{
Next[1]=0;
if(ch[2]=='\0') //如果模式串只有一个字符
return;
Next[2]=1;
int i,len,j;
for(i=3;ch[i]!='\0';i++)
{
for(len=i-2;len>=1;len--)
{
for(j=1;j<=len;j++)
if(ch[j]!=ch[i+j-len-1])
break;
if(j==len+1)
{
Next[i]=j;
break;
}
}
if(len<1)
Next[i]=1;
}
}
注意:该种方法求next数组在比赛中一般都会超时。
2.改进如下:
void get_next()
{
Next[1]=0;
if(ch[2]=='\0') //如果模式串只有一个字符
return;
Next[2]=1;
for(int i=3,k=1;ch[i]!='\0';i++) //k表示最大相同前后缀长度
{
while(k>1&&ch[i-1]!=ch[k]) //如果下标i-1字符与下标k字符不一致,通过next数组回溯
k=Next[k];
if(ch[i-1]==ch[k]) //如果下标i-1字符与下标k字符一致,则k值可加1
k++;
Next[i]=k;
}
}
改进后求next数组的时间复杂度大大降低。
具体代码
#include <iostream>
using namespace std;
const int N=1005;
char str[N];
char ch[N];
int Next[N];
void get_next()
{
Next[1]=0;
if(ch[2]=='\0')
return;
Next[2]=1;
//cout<<0<<" "<<1<<" ";
for(int i=3,k=1;ch[i]!='\0';i++)
{
while(k>1&&ch[i-1]!=ch[k])
k=Next[k];
if(ch[i-1]==ch[k])
k++;
Next[i]=k;
//cout<<Next[i]<<" ";
}
//cout<<endl;
}
int main()
{
char x;
int i,j;
int _count;
while(1)
{
scanf("%c",&x);
i=1;
while(x!=' ')
{
str[i++]=x;
scanf("%c",&x);
if(str[1]=='#'&&x=='\n')
return 0;
}
str[i]='\0';
scanf("%c",&x);
i=1;
while(x!='\n')
{
ch[i++]=x;
scanf("%c",&x);
}
ch[i]='\0';
get_next();
_count=0;
i=1;
while(1)
{
j=1;
while(ch[j]!='\0'&&str[i]!='\0')
{
if(str[i]!=ch[j])
{
j=Next[j];
if(j==0)
{
i++;
j++;
}
}
else
{
i++;
j++;
}
}
if(ch[j]=='\0')
{
_count++;
//cout<<"i="<<i<<" "<<"j="<<j<<endl;
}
if(str[i]=='\0')
break;
}
cout<<_count<<endl;
}
}
前缀和
一般用于解决:一维或二维数组连续的一段的数值和的基本操作。
一、题目描述
二、分析与代码
分析
1、求该二维数组的前缀和;
2、按照划定矩阵的类型,将其划分为四种情况,并通过前缀和输出对应的矩阵和。
具体代码
class NumMatrix {
public:
vector<vector<int>> sum;
NumMatrix(vector<vector<int>>& matrix) {
int m=matrix.size(),n;
if(m>0)
{
n=matrix[0].size();
vector<int> a;
a.push_back(matrix[0][0]);
for(int i=1;i<n;i++)
a.push_back(a[i-1]+matrix[0][i]);
sum.push_back(a);
for(int i=1;i<m;i++)
{
a.clear();
a.push_back(matrix[i][0]+sum[i-1][0]);
for(int j=1;j<n;j++)
a.push_back(sum[i-1][j]+a[j-1]+matrix[i][j]-sum[i-1][j-1]);
sum.push_back(a);
}
}
//输出所求二维前缀和
/*
for(int i=0;i<m;i++)
{
for(int j=0;j<n;j++)
cout<<sum[i][j]<<' ';
cout<<endl;
}
*/
}
int sumRegion(int row1, int col1, int row2, int col2) {
if(row1==0&&col1==0)
return sum[row2][col2];
if(row1==0)
return sum[row2][col2]-sum[row2][col1-1];
if(col1==0)
return sum[row2][col2]-sum[row1-1][col2];
return sum[row2][col2]+sum[row1-1][col1-1]-sum[row2][col1-1]-sum[row1-1][col2];
}
};
/**
* Your NumMatrix object will be instantiated and called as such:
* NumMatrix* obj = new NumMatrix(matrix);
* int param_1 = obj->sumRegion(row1,col1,row2,col2);
*/
一般用于解决:。
一、题目描述
二、分析与代码
分析
具体代码