0%

.\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\startup\arm目录下的文件为启动文件,复制到工程目录,新建Start文件夹,并粘贴启动文件。

.\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x
目录下的stm32f10x.h是STM32的外设寄存器描述文件,用于描述STM32有哪些寄存器和它对应的地址。
system_stm32f10x.csystem_stm32f10x.h主要是用来配置时钟的。
这三个文件粘贴到Start文件夹下。

因为STM32是内核和内核外围的设备组成的,且内核的寄存器描述文件和外围设备的描述文件不是在一起的,还需要添加内核寄存器描述文件。
.\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\CoreSupport目录下的core_cm3.ccore_cm3.h就是内核寄存器描述,还带了内核的配置函数。
这两个文件粘贴到Start文件夹下。

在Keil软件中,选择添加存在的文件,添加启动文件(startup_stm32f10x_md.s),和剩下的.c和.h文件。
Start目录下的文件
最后还要再工程选项里添加上Start文件夹的头文件路径,点击魔术棒,进入C/C++,在Include Paths栏中,把Start路径添加进去。

接下来新建User文件夹,在工程目录中新建User文件夹,然后再Keil中,Target点击右键,添加组,改个名字,叫User,在User右键,点击添加新文件,选择C文件,名字叫main。

⚠ 路径选择User文件夹,否则默认放在工程根目录。

右键选择插入头文件,选择stm32f10x.h

⚠ 最后一行必须为空行,否则会报警告。

📖点击扳手按钮,在Editor下,Encoding编码格式选为UTF-8,否则可能会出现乱码问题。

配置调试器:点击魔术棒按钮,Debug,默认的调试器为ULINK,改为STLINK。再点击右侧的Settings按钮,在Flash Download下,勾选Reset and Run。选择此项,下载程序后会立刻复位并运行,方便调试。

在工程目录下新建文件夹,LIbrary,来存放库函数。
.\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\STM32F10x_StdPeriph_Driver\src目录下,misc.c为内核的库函数,其他为内核外的外设库函数,全部复制到Library文件夹下。
再打开.\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\STM32F10x_StdPeriph_Driver\inc,该目录下的内容为库函数的头文件,全部复制到Library文件夹下。
在keil软件添加组,改名Library,添加存在的文件,添加Library下全部文件。

进入\STM32F10x_StdPeriph_Lib_V3.5.0\Project\STM32F10x_StdPeriph_Template目录,复制stm32f10x_conf.hstm32f10x_it.cstm32f10x_it.h。粘贴到User目录下。

stm32f10x_conf.h用于配置库函数头文件包含关系和检查函数定义。
it文件用来存放中断函数。

回到Keil软件,添加刚才的3个文件。

打开Keil工程选项(魔术棒),在C/C++中Define添加USE_STDPERIPH_DRIVER用来包含标准外设库。

在Include Paths中,添加User和Library文件夹。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include "stm32f10x.h"                  // Device header

int main(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);//用来使能或失能APB2的外设时钟
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);//配置端口模式,第一个是选择哪个GPIO,第二个是选择结构体
// GPIO_SetBits(GPIOC,GPIO_Pin_13);//将PC13口置为高电平
GPIO_ResetBits(GPIOC,GPIO_Pin_13);//将PC13口置为低电平
}

新建工程步骤
•建立工程文件夹,Keil中新建工程,选择型号
•工程文件夹里建立Start、Library、User等文件夹,复制固件库里面的文件到工程文件夹
•工程里对应建立Start、Library、User等同名称的分组,然后将文件夹内的文件添加到工程分组里
•工程选项,C/C++,Include Paths内声明所有包含头文件的文件夹
•工程选项,C/C++,Define内定义USE_STDPERIPH_DRIVER
•工程选项,Debug,下拉列表选择对应调试器,Settings,Flash Download里勾选Reset and Run

工程架构

在信捷PLC编程中,ZCP(区间比较)和CMP(单值比较)都是数据比较指令,但功能和应用场景有显著区别。以下是详细对比及实例说明:


1. 核心区别

指令功能输出结果数量适用场景
CMP比较源数据与单个目标值3种结果(>、=、<)判断数据是否超限、等于特定值
ZCP判断源数据是否在指定区间内3种结果(<下限、区间内、>上限)数据范围检测(如温度、压力控制)

2. 指令详解

(1) CMP 指令

  • 语法CMP S1 S2 D
    • S1:源数据(待比较值)
    • S2:目标值
    • D:连续3个位元件存储结果(D=小于, D+1=等于, D+2=大于)
  • 执行特点:每个扫描周期执行一次,结果实时更新。

(2) ZCP 指令

  • 语法ZCP S1 S2 S3 D
    • S1:区间下限
    • S2:区间上限(需满足 S1 ≤ S2
    • S3:源数据
    • D:连续3个位元件存储结果(D=小于下限, D+1=区间内, D+2=大于上限)
  • 执行特点:仅在使能条件ON时执行一次(需配合上升沿触发)。

3. 实例演示

场景1:CMP 指令应用(速度监控)

1
2
3
4
5
|   X0      CMP K100 D0 M0  |  
|----|------[CMP 100 D0 M0]-|
| |--[M0]----(Y0) | // D0 < 100 时Y0亮
| |--[M1]----(Y1) | // D0 = 100 时Y1亮
| |--[M2]----(Y2) | // D0 > 100 时Y2亮
  • 功能:监控D0的值:
    • D0 < 100M0=ON → Y0输出
    • D0 = 100M1=ON → Y1输出
    • D0 > 100M2=ON → Y2输出

场景2:ZCP 指令应用(温度区间控制)

1
2
3
4
5
|   X1      ZCP K50 K80 D10 M10  |  
|----|------[ZCP 50 80 D10 M10]-|
| |--[M10]---(Y3) | // D10 < 50 → 温度过低
| |--[M11]---(Y4) | // 50≤D10≤80 → 温度正常
| |--[M12]---(Y5) | // D10 > 80 → 温度过高
  • 功能:检测D10(温度值):
    • <50℃M10=ON → 启动加热(Y3)
    • 50~80℃M11=ON → 维持状态(Y4)
    • >80℃M12=ON → 启动冷却(Y5)

4. 关键注意事项

  1. 结果元件占用
    • CMP/ZCP 的输出会占用连续3个位元件(如指定M0则实际使用M0~M2)。
  2. 指令触发方式
    • CMP:每个扫描周期执行,适合实时监控。
    • ZCP:需配合上升沿触发(如LDP X1),避免多次执行。
  3. 区间有效性
    • ZCP 要求 S1 ≤ S2,否则 M11(区间内)永远为OFF。

总结

  • CMP:用于单点比较(如阈值报警、数值匹配)。
  • ZCP:用于区间判断(如工艺参数范围控制)。
    根据实际需求选择指令,可高效实现精准的逻辑控制。

在信捷PLC编程中,LD X0LDD X0 的区别主要体现在 指令类型执行方式 上。以下是详细对比:

📌 1. LD X0(普通取指令)

  • 功能
    读取输入继电器 X0扫描周期开始时的状态(即上一个扫描周期结束时的状态)。
  • 执行特点
    • 在程序扫描周期内执行,读取的是 内部映像寄存器 的值(非实时物理输入)。
    • 响应速度较慢,适用于一般逻辑控制。
  • 应用场景
    普通开关控制、非实时性要求的逻辑。

⚡ 2. LDD X0(立即取指令)

  • 功能
    直接读取 物理输入点 X0 的实时状态,不受扫描周期影响。
  • 执行特点
    • 绕过内部映像寄存器,立即读取当前物理输入
    • 响应速度更快(一个扫描周期内可多次响应),适用于高速信号处理。
  • 应用场景
    高速计数、精确定时、脉冲捕捉等需要实时响应的场合。

📖 手册依据(附件内容)

在提供的用户手册中:

  • LD 属于基本顺控指令(第3章),定义为普通取正操作(3-2节)。
  • LDD 归类为 立即执行指令(3-6节),描述为“直接从触点上读取状态”,强调其实时性。

💡 关键区别总结

指令读取来源响应速度适用场景
LD X0内部映像寄存器较慢普通逻辑控制
LDD X0物理输入点(实时)更快高速信号、实时控制

⚠️ 注意事项

  • LDD 指令会轻微增加程序执行时间,仅在必要时使用(如高速输入)。
  • 普通输入信号(如按钮、传感器)通常用 LD 即可满足需求。

若需进一步优化高速响应,可结合手册中 高速计数功能(第5章)或 精确定时指令(第10章)使用。

本题要求实现一个函数,判断任一给定整数N是否满足条件:它是完全平方数,又至少有两位数字相同,如144、676等。

函数接口定义:

1
int IsTheNumber ( const int N );

其中N是用户传入的参数。如果N满足条件,则该函数必须返回1,否则返回0。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <stdio.h>
#include <math.h>
int IsTheNumber ( const int N );
int main()
{
int n1, n2, i, cnt;
scanf("%d %d", &n1, &n2);//输入两个数字,统计这两个数字之间符合要求的数字
cnt = 0;
for ( i=n1; i<=n2; i++ ) {
if ( IsTheNumber(i) )
cnt++;
}
printf("cnt = %d\n", cnt);
return 0;
}
/* 你的代码将被嵌在这里 */
int IsTheNumber ( const int N )
{
    int n = N;
    int perfect_square = 0;//是否是完全平方数
    int same = 0;//是否有2个数字相同
    //先看看这个数是不是完全平方数
    for (int i = 0; i*i <= n; i++)
    {
        if ( i * i == N)
        {
            perfect_square = 1;
            break;
        }
    }
    //得到数字的长度,看看是几位数
    int length = 0;//数字的长度
    while (n > 0)
    {
        length++;
        n = n / 10;
    }
    n = N;
    int input[5];//这个字符串用来存储输入的数字,因为输入是int型,5位已经足够
    for (int i = 0; i < length; i++)
    {
        int temp = n % 10;//得到这个数字的个位
        input[i] = temp;
        n = n / 10;//把这个数字的个位去掉
    }
    //判断这个数字是否有2个相同数字
    if (input[0]==input[1]||input[0]==input[2]||input[0]==input[3]||input[0]==input[4]||
        input[1]==input[2]||input[1]==input[3]||input[1]==input[4]||
        input[2]==input[3]||input[2]==input[4]||
        input[3]==input[4])
    {
        same = 1;
    }
   //如果两个都符合,返回1,否则返回0
    if (same == 1 && perfect_square == 1)
    {
        return 1;
    }else{
        return 0;
    }
}

改进的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int IsTheNumber ( const int N )
{
int n = N;
// 判断是否完全平方数 (优化版)
int root = (int)sqrt(N);
if(root * root != N)
return 0;

// 数字频率统计
int digitCount[10] = {0}; // 0-9的数字计数器
while(n > 0) {
int digit = n % 10;
digitCount[digit]++;
if(digitCount[digit] >= 2)
return 1; // 发现重复立即返回
n /= 10;
}
return 0;
}
1
2
3
int root = (int)sqrt(N);
if(root * root != N)
return 0;
  • 裁判程序已经有math.h了,所以应该使用 sqrt() 计算平方根整数部分
  • 检查 root*root == N 替代低效的循环
1
digitCount[digit]++;

当出现对应数字时,把这个数字的计数器直接加1,可以避免我的代码中复杂的判断逻辑。

1
2
if(digitCount[digit] >= 2) 
return 1; // 发现重复立即返回

当一个数字已经出现2次了,后面的就不用管了,已经符合条件了。

链表
本题要求实现一个函数,求单链表L结点的阶乘和。这里默认所有结点的值非负,且题目保证结果在int范围内。

函数接口定义:

1
int FactorialSum( List L );

其中单链表List的定义如下:

1
2
3
4
5
6
typedef struct Node *PtrToNode;
struct Node {
int Data; /* 存储结点数据 */
PtrToNode Next; /* 指向下一个结点的指针 */
};
typedef PtrToNode List; /* 定义单链表类型 */

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <stdio.h>
#include <stdlib.h>
typedef struct Node *PtrToNode;//这句的意思是把指向Node这个结构体的指针起名叫PtrToNode
struct Node {
    int Data; /* 存储结点数据 */
    PtrToNode Next; /* 指向下一个结点的指针 */
};
typedef PtrToNode List; /* 定义单链表类型 */
int FactorialSum( List L );
int main()
{
    int N, i;
    List L, p;
    scanf("%d", &N);
    L = NULL;//这是表头指针L,初始时为NULL表明链表为空
    for ( i=0; i<N; i++ ) {
        p = (List)malloc(sizeof(struct Node));
        scanf("%d", &p->Data);
        p->Next = L;  L = p;
    }
    printf("%d\n", FactorialSum(L));
    return 0;
}
/* 你的代码将被嵌在这里 */
int Factorial(int n)//求阶乘
{
    if( n == 0 || n == 1 )
    {
        return 1;
    }else{
        int sum = 1;
        for (int i = 1; i <= n; i++)
        {
            sum = sum * i;
        }
        return sum;
    }
}
int FactorialSum( List L )
{
    int sum = 0;
    int temp = 0;//暂时存一下一个数字的阶乘
    while (L != NULL)
    {
        temp = Factorial(L->Data);//得到这一次循环的阶乘
        sum = sum + temp;
        L = L->Next;
    }
    return sum;
}

1
typedef struct Node *PtrToNode;
  • 定义一个新的类型别名:PtrToNode 是指向 struct Node 结构体的指针类型。
  • 后续可以直接使用 PtrToNode p; 来声明一个指向结构体的指针变量。

1
typedef PtrToNode List; /* 定义单链表类型 */
  • 再次使用 typedef,把 PtrToNode 类型(也就是指向结构体的指针)起个别名叫 List
  • 这样我们就可以直接写 List L; 来表示一个链表头指针。

1
p = (List)malloc(sizeof(struct Node)); 
  • 使用 malloc() 动态分配一个 struct Node 大小的内存空间,并将返回的指针强制转换为 List 类型,赋值给 p
  • 这就创建了一个新的链表节点。

1
p->Next = L; L = p; 
  • 将新节点插入到链表头部: - p->Next = L;:让新节点的 Next 指向原来的第一个节点;
  • L = p;:更新链表头指针 L,让它指向新插入的节点。
  • 这样做每次插入都在链表头部,形成的是逆序链表(后面输入的节点在前面)。

while (L != NULL)

只要节点存在(即指针不是 NULL),就继续处理。
这里不能写成while (L->Data != NULL),因为:

  • L->Data 是一个 int 类型,而不是指针类型。
  • NULL 是一个空指针常量(通常是 (void*)0),不能用来判断 int 是否为空。
  • 所以这个判断条件是语法错误 + 逻辑错误

逻辑结构上相邻的数据,在实际存储时,这些数据所在的内存地址并不连续,而是随机分布在内存中的各个位置,这种存储模式就是链式存储,所生成的表就是链表。

为了保持数据之间的关系,每个数据在存储时配有一个指针,指向它后面的元素,这样,数据的顺序就由指针联系起来了。

链表中每个元素由两部分构成,一是其本身存储的信息,二是指向后续的指针。

参考:【动画演示】链表详解及其底层机制 C语言

一个采购员去银行兑换一张y元f分的支票,结果出纳员错给了f元y分。采购员用去了n分之后才发觉有错,于是清点了余额尚有2y元2f分,问该支票面额是多少?

输入格式:

输入在一行中给出小于100的正整数n。

输出格式:

在一行中按格式y.f输出该支票的原始面额。如果无解,则输出No Solution

分析:

  • 采购员实际得到的金额:100f + y 分。
  • 采购员用去了 n 分,所以剩下的金额是 100f + y - n 分。
  • 剩下的金额应该等于 200y + 2f 分。
  • 这个问题的方程表示为:98f−199y=n

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <stdio.h>
int main()
{
    int y = 0;
    int f = 0;
    int n = 0;
    int found = 0;//找到解标志
    scanf("%d",&n);
    for (f=1; f<100; f++)
    {
        for (y=0; y<=99; y++)
        {
            if (98*f-199*y==n)
            {
                found = 1;
                break;//找到了解,退出内层循环
            }
        }
        if (found)//退出外层循环
        {
            break;
        }
    }
    if (found)
    {
        printf("%d.%d",y,f);//打印
    }else
    {
        printf("No Solution");//没有解
    }
}

根据下面关系式,求圆周率的值,直到最后一项的值小于给定阈值。
$$
\frac{\pi}{2} = 1 + \frac{1}{3} + \frac{2!}{3 \times 5} + \frac{3!}{3 \times 5 \times 7} + \cdots + \frac{n!}{3 \times 5 \times 7 \times \cdots \times (2n + 1)} + \cdots
$$

输入格式:

输入在一行中给出小于1的阈值。

输出格式:

在一行中输出满足阈值条件的近似圆周率,输出到小数点后6位。

输入样例:

1
0.01

输出样例:

1
3.132157

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <stdio.h>
double factorial(int n);//计算阶乘
int main()
{
    float threshold = 0;//阈值
    float input = 0;//输入
    float sum = 0;//结果
    float numerator = 0;//分子
    float denominator = 1;//分母
    scanf("%f",&input);
    int i = 1;
    do {
        int x = 1;
        int j = 1;//计算分母循环用
        for (j = 1; j<=i;j++)//计算分母
        {
            denominator = denominator * x;
            x = x + 2;
        }
        numerator = factorial(i-1);//计算分子
        threshold = numerator/denominator;//计算阈值(当前的最后一项)
        sum = sum + threshold;//计算和
        i++;
        denominator = 1;
    } while (threshold > input);
    sum = sum * 2;
    printf("%.6f",sum);
}
double factorial(int n)//计算阶乘
{
    double result = 1;
    int i = 0;
    for (i = 1; i <= n; i++)
    {
        result = result * i;
    }
    return result;
}

《C程序设计语言》练习4-2
对atof函数进行扩充,使它可以处理形如
123.45e-6
的科学表示法,其中,浮点数后面可能会紧跟一个e或E以及一个指数(可能有正负号)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
double atof(char s[])
{
    double val,power;
    int i,sign;
    for (i=0; isspace(s[i]); i++)
    {
        ;//去除数字前边的空格
    }
    sign = (s[i] == '-') ? -1 : 1;//把这个数字前边的符号保存在sign里
    if (s[i] == '+' || s[i] == '-')
    {
        i++;//跳过符号位
    }
    for (val = 0.0; isdigit(s[i]); i++)
    {
        val = 10.0* val + (s[i] - '0');//得到小数点前边的数字
    }
    if (s[i] == '.')
    {
        i++;//跳过小数点
    }
    for (power = 1.0; isdigit(s[i]); i++)
    {
        val = 10.0 * val + (s[i] - '0');//得到小数点后边的数字,一并放在val中
        power *= 10.0;//之后要除掉的倍数,因为之前多乘了。
    }
    return sign * val / power;//返回符号*值/小数点后位数
}

思路

  1. 提供的atof函数已经可以处理e之前的部分,需增加对e后部分的处理。
  2. 跳过e符号,获得符号位Esign,最后得到指数j。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <stdio.h>
#include <ctype.h>
#include <math.h>
double atof(char s[]);
int main() {
    char s[] = "3.1415E10";
    double ret = atof(s);
    return 0;
}
double atof(char s[])
{
    double val,power;
    int i,sign;
    int Esign = 0,j = 0;
    for (i=0; isspace(s[i]); i++)
    {
        ;
    }
    sign = (s[i] == '-') ? -1 : 1;
    if (s[i] == '+' || s[i] == '-')
    {
        i++;
    }
    for (val = 0.0; isdigit(s[i]); i++)
    {
        val = 10.0* val + (s[i] - '0');
    }
    if (s[i] == '.')
    {
        i++;
    }
    for (power = 1.0; isdigit(s[i]); i++)
    {
        val = 10.0 * val + (s[i] - '0');
        power *= 10.0;
    }
    if (s[i] == 'e' || s[i] == 'E')//处理科学计数法
    {
        i++;//跳过e
        Esign = (s[i] == '-') ? -1 : 1;//获得符号位
        if (s[i] == '+' || s[i] == '-')
        {
            i++;//跳过符号
        }
        for (j=0; isdigit(s[i]); i++)
        {
            j = 10 * j + (s[i] - '0');//获得指数
        }
    }
    return sign * val / power * pow(10, Esign*j);
}

《C程序设计语言》练习4-1
编写函数strrindex(s,t),它返回字符串t在s中最右边出现的位置。如果s中不包含t,则返回-1。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <stdio.h>
#include <string.h>
int strrindex(char s[],char t[]);
int main()
{
    char s[] = "abqqqcdqqefqgqhijkqqqqlmnopqqqqrsqqqqtqrqrqrscsdaadzcqraaaaqqqqqr";
    char t[] = "qr";
    int Locate = strrindex(s,t);
    return 0;
}
int strrindex(char s[],char t[])
{
    int i = 0;
    int ret = -1;
    int tLenth = strlen(t);//字符串t的长度
    for (i = 0; s[i] != '\0'; i++)
    {
        if (s[i] == t[0])//s[i]和字符串t的首元素相等了
        {
            int j = 0;
            for (j = 0; j < tLenth; j++)
            {
                static int Count = 0;//记录元素相等的个数,和t的长度相等时,代表找到了
                if (s[i+j] == t[j])//其他元素也相等
                {
                    Count++;
                    if (Count == tLenth)//记录的相等个数和t的长度相等
                    {
                        ret = i;
                        Count = 0;//记录清零,为了后面查找准备
                    }
                }
                else//出现了不相等的元素
                {
                    Count = 0;//记录清零
                    break;
                }
            }
        }
    }
    return ret;
}

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <stdio.h>
#include <string.h>
int strrindex(char s[], char t[]);
int main()
{
    char s[] = "abqqqcdqqefqgqhijkqqqqlmnopqqqqrsqqqqtqrqrqrscsdaadzcqraaaaqqqqqr";
    char t[] = "qraaa";
    int Locate = strrindex(s, t);
    return 0;
}
int strrindex(char s[], char t[])
{
    int sLength = strlen(s);
    int tLength = strlen(t);
    int i, j
    for (i = sLength - tLength; i >= 0; i--) //从后往前找
    {
        for (j = 0; j < tLength; j++)
        {
            if (s[i + j] != t[j]) //不匹配的时候
            {
                break;
            }
        }
        if (j == tLength) //找到了
        {
            return i;  // 找到最后一次出现的位置
        }
    }
    return -1// 如果未找到
}