当前位置: 首页 > news >正文

[RTOS 学习记录] 预备知识:C语言结构体

这篇文章是我阅读《嵌入式实时操作系统μCOS-II原理及应用》后的读书笔记,记录目的是为了个人后续回顾复习使用。

文章目录

  • 结构体
    • 结构体基础
      • 声明和定义结构体类型
      • 声明和定义结构体变量
      • 初始化结构体变量
        • 初始化各个成员
        • 使用列表符号初始化
      • 使用结构体变量
      • 综上
    • 结构体的简写表示法
      • 压缩表示法
      • 匿名结构体
      • typedef
        • typedef 关键字
        • typedef 用于结构体
        • typedef 用于匿名结构体
    • 嵌套结构体
    • 结构体指针
      • 声明和定义
      • 初始化
      • 指针使用
    • 结构体数组
    • 位域
      • 位域基础
      • 缺失位
      • 示例
    • 紧凑成员
  • 结构体的应用
    • 基本应用
    • 结构体嵌套
      • 程序结构
      • 注意点和重要知识点
    • 结构类型的函数指针成员
    • 结构体的仿类类型
    • 嵌套结构类型指针
    • 基类函数钩子
    • 实现多态特性
    • 计算外层对象指针的宏定义

《嵌入式实时操作系统μC/OS-Ⅱ原理及应用》这本书的前言部分有提到:

C 指针看起来像是一个复习的内容,其实是要重点强调 C 指针中的函数指针,因为这种数据类型在操作系统软件中使用的频率太高了,而高校的 C 语言教学又大多不把它当作重点,所以致使相当一部分高校学生甚至不知道函数指针为何物。除了 C 指针之外,C 语言中的关键字 typedef 及其常用方法也是由于上述原因而被初学者忽视,从而造成了学习上的困难,因此在第 2 章也增加了这方面的内容。当然,因为本书的宗旨不是介绍 C 语言,所以仅依靠本书的寥寥数语并不能真正使读者完全掌握函数指针,但起码能使读者知道基础的欠缺之所在,从而主动去查找和阅读文献。

之前,我们已经复习了 C 语言指针的基础知识。

因此,接下来我们需要复习 C 语言中的结构体这部分的知识内容,为接下来的实时操作系统的学习打基础。


下方内容翻译自 Embeetle 的 Embedded C Tutorial,在翻译的过程中,我把自己上机实验的结果,以截图的方式插入到相应的位置。

结构体

结构体是一种复合数据类型,它用于将某些(可能不同)类型的成员组合成一种单一类型。

结构体是一种用户定义的数据类型,允许将不同类型的数据组合在一起。结构体中的各个元素称为成员

结构体可以包含任意数量的成员,这些成员可以是任何数据类型。它有点类似于数组——但数组只能包含相同类型的数据。

结构体基础

请记住上文结构体中的定义:

结构体是一种用户定义的数据类型,允许将不同类型的数据组合在一起。结构体中的各个元素称为成员

严格意义上来说,“结构体”或“结构”指的是数据类型。然而,它也常用于代指由数据类型实例化出来的变量,这时就容易引起混淆!

为了严谨地避免这种歧义,我们将从不单独使用“结构体”这个术语。我们将始终使用“结构体类型”或“结构体变量”组合词:

  • 结构体类型本质上是一种数据类型。数据类型仅在编译时存在!因此,仅仅指定结构体类型不会占用任何内存。
  • 一旦指定了结构体类型,我们就可以用它实例化出结构体变量。每个结构体变量都会消耗一部分内存——就像其他变量一样(整数、浮点数等)。

将其比作有一个食谱并用它实际烹饪菜肴:
recipe_dish

结构体类型是食谱,结构体变量是菜肴。只有菜肴才会占用餐桌上的空间。

我们将首先看看如何声明和定义结构体类型。在第 2 节中,我们将开始由这种类型实例化出变量。

声明和定义结构体类型

指定结构体类型分为两个步骤:首先声明类型,然后定义它。注意,此时没有创建任何变量!我们在此仅使用“声明”和“定义”术语来指代结构体类型本身:

结构体类型的声明只是告知编译器有一个特定的结构体数据类型存在及其名称。

结构体类型的定义 完整地指定了结构体数据类型。定义后,编译器了解了所有关于它的信息:

  • 结构体的确切内存布局及其所有成员。
  • 结构体实例在内存中占用的空间(当我们声明并定义一个结构体变量时,就会创建一个“结构体实例”。我们将在第 2 节中进行学习了解)。

我们来看一个例子:

// 声明数据类型 'struct Point'
struct Point;// 定义数据类型 'struct Point'
struct Point
{int x;int y;
};

在声明之后,编译器知道存在一种结构体数据类型 struct Point。没错,关键词 struct 是该数据类型名称的一部分!在定义之后,编译器也知道了关于该结构体数据类型的所有细节。注意,此时还没有实例化该结构体数据类型的变量,因此尚未分配任何内存。

 {..}   在同一行上声明和定义 

通常,结构体类型的显式声明会被省略,这样的话,定义就可以同时起到声明和定义类型的双重作用:

// 声明并定义数据类型 'struct Point'
struct Point
{int x;int y;
};

在某些情况下,我们必须事先显式声明:

// 声明数据类型 'struct Foo'
struct Foo;// 声明数据类型 'struct Bar'
struct Bar;// 定义数据类型 'struct Bar'
struct Bar {struct Foo *foo;...
};// 定义数据类型 'struct Foo'
struct Foo {struct Bar *bar;...
};

在下一章中,我们将实例化结构体类型(从中创建变量)。

声明和定义结构体变量

最后,让我们创建变量!假设我们已经声明并定义了数据类型 struct Point 现在我们来声明和定义变量:

// 声明变量 a 和 b
extern struct Point a;
extern struct Point b;// 定义变量 a 和 b
struct Point a;
struct Point b;

再次强调,数据类型不是 Point 而是 struct Point!如果记住这一点,那么声明和定义结构体变量其实并没有什么特别之处。

就像普通变量一样,我们应该记住声明定义之间的区别。我们在这里重复一遍:

变量的声明告知编译器一个特定变量存在及其名称、类型和大小(对于结构体变量,数据类型显然是我们之前声明并定义的结构体类型(见第 1 章)),编译器随后知道足够的信息来与变量交互。然而,此时不会进行任何内存的分配。

变量的定义为变量分配一个或多个内存单元,这发生在编译器将源文件转换为目标文件时,目标文件为每个定义的变量保留内存空间。大多数目标文件是可重定位的,这意味着它们从地址 0x0000 开始分配内存空间,链接器最终将所有的这些目标文件合并在一起,上下移动它们的基地址以使它们都能够塞进内存中,只有在那之后,绝对内存地址才会被知道。

初始化结构体变量

一旦声明和定义了结构体变量,就应该为其成员赋值;换句话说,结构体变量应该被初始化。这可以通过多种方式实现。

初始化各个成员

要初始化结构体变量,我们可以分别为每个成员赋值,这非常简单。假设结构体变量已经声明和定义,初始化如下所示:

// 初始化结构体变量 a
a.x = 3;
a.y = 5;
使用列表符号初始化

我们可以使用列表符号一次性为所有成员赋值,而不是分别赋值:

// 初始化结构体变量 a
a = (struct Point){.x = 3,.y = 5,
};

注意强制转换前缀,{..} 块内表达式的结果需要转换为数据类型 struct Point,然后才能赋值给变量 a。如果在定义变量 a 的同一条语句中对其进行初始化,则可以省略强制转换。

{..} 表达式中,.x.y 明确显示了哪个成员被赋予了什么值。但是,如果你愿意,也可以省略它们:

// 初始化结构体变量 a
a = (struct Point){3,5,
};

使用结构体变量

一旦我们的结构体变量 a 声明、定义并初始化后,我们就可以像使用其他任何变量一样使用它。然而,有一个特殊的特性:我们可以使用点表示法访问结构体变量内部的各个成员。

// 使用结构体变量 a
a.x = a.y + 7;

综上

我们从声明和定义一个结构体类型开始;然后,我们使用该类型声明和定义结构体变量,最终进行初始化。让我们将所有这些放在这个测试文件中:

// C 代码测试文件
// ================
#include <stdio.h>// 声明数据类型 'struct Point'
struct Point;// 定义数据类型 'struct Point'
struct Point
{int x;int y;
};// 声明变量 a 和 b
extern struct Point a;
extern struct Point b;// 定义变量 a 和 b
struct Point a;
struct Point b;int main()
{// 初始化结构体变量 aa.x = 3;a.y = 5;// 初始化结构体变量 b// (不同的方法)b = (struct Point){.x = 7,.y = 9,};printf("a.x = %d\n", a.x);printf("a.y = %d\n", a.y);printf("b.x = %d\n", b.x);printf("b.y = %d\n", b.y);
}

你会得到以下输出:

> gcc test.c -Wall && a.exe
a.x = 3
a.y = 5
b.x = 7
b.y = 9

运行结果如下图所示:
image-20240804090618937

我知道你现在在想什么,我都快闻到味儿了:
verbose_comic

确实,我们分步骤做了每件事:

  • 声明结构体数据类型;
  • 定义结构体数据类型;
  • 声明一个结构体变量;
  • 定义结构体变量;
  • 初始化结构体变量。

当然,在实际情况下这样详细地描述是不合理的,但这是一次极好的学习经验!在下一章节中,我们将看到简写符号。换句话说,我们将学习如何将上述几个步骤糅合进单个表达式中。

结构体的简写表示法

回顾我们在上一章节中如何创建结构体:

  1. 声明结构体数据类型;
  2. 定义结构体数据类型;
  3. 声明结构体变量;
  4. 定义结构体变量;
  5. 初始化结构体变量。

这显得非常冗长,让我们看看如何简化这一过程。

压缩表示法

首先,让我们从结构体数据类型的声明和定义开始:

// 声明数据类型 'struct Point'
struct Point;// 定义数据类型 'struct Point'
struct Point
{int x;int y;
};

首先,我们可以将声明和定义合并为一个表达式。只需省略声明部分,使定义同时具有声明的功能:

// 声明并定义数据类型 'struct Point'
struct Point
{int x;int y;
};

在结构体数据类型声明和定义之后,我们可以实例化变量。我们通常在单独的表达式中进行,但也可以将其合并为一个表达式:

// 声明并定义数据类型 'struct Point',然后
// 使用该数据类型声明并定义变量 a 和 b。
struct Point
{int x;int y;
} a, b;

如何初始化变量呢?这也可以实现!

// 声明并定义数据类型 'struct Point',然后
// 从中声明并定义变量 a 和 b,并初始化它们。
struct Point
{int x;int y;
} a = {3, 5}, b = {7, 9};

我们把它放在一个测试文件中:

// C 代码测试文件
// ================
#include <stdio.h>// 声明并定义数据类型 'struct Point',然后
// 从中声明并定义变量 a 和 b,并初始化它们。
struct Point
{int x;int y;
} a = {3, 5}, b = {7, 9};int main()
{printf("a.x = %d\n", a.x);printf("a.y = %d\n", a.y);printf("b.x = %d\n", b.x);printf("b.y = %d\n", b.y);
}

你会得到以下输出:

> gcc test.c -Wall && a.exe
a.x = 3
a.y = 5
b.x = 7
b.y = 9

运行结果如下图所示:
image-20240804092958501

匿名结构体

在某些情况下,我们只希望从一个结构体数据类型中实例化一个(或几个)变量,我们不打算以后再实例化更多的变量,那么给这个结构体数据类型命名就没有意义,只需省略名称:

// 声明并定义一个匿名结构体数据类型,然后
// 从中声明并定义变量 a 和 b,并初始化它们。
struct
{int x;int y;
} a = {3, 5}, b = {7, 9};

从这个结构体数据类型中,只有变量 a 和 b 存在。一旦表达式结束,就无法再创建新变量!

typedef

在处理结构体时,typedef 关键字经常被使用到。通常,该关键字用于为给定的数据类型创建一个额外的名称(别名)。让我们详细说明一下。

typedef 关键字

typedef 关键字并不会创建一个新数据类型,它只是为已有的数据类型创建了一个别名。因此,它常用于简化语法。以下是一个简单的例子:

// 将 'BYTE' 做为 'unsigned char' 的别名 
typedef unsigned char BYTE;

一旦 BYTE 被指定为 unsigned char 的别名,就可以这样使用:

// 使用 'BYTE' 作为 'unsigned char' 的缩写
BYTE b1, b2;
typedef 用于结构体

typedef 关键字还可以给结构体数据类型创建别名:

// 声明并定义数据类型 'struct Point'
struct Point
{int x;int y;
};// 指定 'POINT' 为 'struct Point' 的别名
typedef struct Point POINT;

糟糕,我们又使用了冗长的方式!首先我们声明并定义了数据类型 struct Point,然后利用 typedef 关键字为结构体数据类型创建别名 POINT。这两个动作可以合并为一个表达式:

// 声明并定义数据类型 'struct Point',然后
// 指定 'POINT' 作为该数据类型的别名。
typedef struct Point
{int x;int y;
} POINT;

一旦结构体数据类型有了别名,就可以通过两种方式实例化变量——使用或不使用别名:

// 声明变量 a 和 b
extern struct Point a;
extern POINT b;// 定义变量 a 和 b
struct Point a;
POINT b;

我将别名写成大写而结构体名写成小写,并没有特别的原因,只要它们是不同的标识符就可以。

typedef 用于匿名结构体

我们使用 typedefstruct Point 指定了别名 POINT。之后,我们有两种方式从结构体中实例化变量——使用或不使用别名,这有点愚蠢。更合理的做法是使用 typedef 为匿名结构体指定一个别名:

// 声明并定义一个匿名结构体数据类型,然后
// 指定 'POINT' 作为该数据类型的别名。
typedef struct
{int x;int y;
} POINT;

这样做时,我们本质上为匿名结构体去掉了匿名。现在我们只能使用别名来实例化变量:

// 声明变量 a 和 b
extern POINT a;
extern POINT b;// 定义变量 a 和 b
POINT a;
POINT b;

很聪明,对吧?
img

无论如何,事实是大多数人更喜欢使用 typedef 的方式,这看起来更简洁。

嵌套结构体

结构体可以嵌套,例如:

// C 代码测试文件
// ================
#include <stdio.h>// 声明并定义一个匿名结构体数据类型,然后
// 指定 'Point' 作为该数据类型的别名。
typedef struct
{int x;int y;
} Point;// 声明并定义一个匿名结构体数据类型,然后
// 指定 'Line' 作为该数据类型的别名。
typedef struct
{Point a;Point b;
} Line;int main()
{Line line;line.a.x = 3;line.a.y = 8;line.b.x = 7;line.b.y = 9;printf("line.a.x = %d\n", line.a.x);printf("line.a.y = %d\n", line.a.y);printf("line.b.x = %d\n", line.b.x);printf("line.b.y = %d\n", line.b.y);
}

运行结果如下图所示:
image-20240804094854377

在这个例子中,结构体 Line 包含两个 Point 结构体作为其成员。要访问这些点的成员,只需再使用一级点号表示法即可。

结构体指针

从理论上讲,“结构”或“结构体”指的是数据类型,尽管有时也用来指代该数据类型的结构体变量(实例)。我们在结构体基础章节中已经提到过这种歧义性。

当谈到“结构体指针”时,我们实际上指的是“指向结构体实例的指针”,请记住这一点。

声明和定义

如果我们已经指定了数据类型 struct Point,那么我们可以像下面这样声明和定义一个指向该结构体实例的指针:

// 声明指针
struct Point *p;// 定义指针
extern struct Point *p;

通常,我们会有一个用别名引用的 typedef 结构体,如 POINT

// 声明指针
POINT *p;// 定义指针
extern POINT *p;

如你所见,指向结构体实例的指针的声明和定义与其他指针的用法没有区别。

初始化

要初始化指针,它需要设置为其他变量的地址——在当前这种情况下是结构体实例的地址。假设 a 是这样一个变量,那么:

// 初始化指针
p = &a;

指针使用

一旦指针被声明、定义和初始化后,它就可以像它指向的变量一样使用。为此,我们需要在指针前加上 *(解引用)操作符。在这方面,没有什么特别之处。

// 通过指针访问成员 x
(*p).x = 5; // 等效于 a.x = 5;

有一种简写表示法:

// 通过指针访问成员 x
p->x = 5; // 等效于 a.x = 5;

换句话说:

p->x 等效于 (*p).x

请参考以下示例:

// C 代码测试文件
// ================
#include <stdio.h>// 声明并定义数据类型 'struct Point'
struct Point
{int x;int y;
};// 指定 'POINT' 为 'struct Point' 的别名
typedef struct Point POINT;// 实例化结构体:声明并定义变量 a
POINT a;// 声明、定义并初始化指向 a 的指针
POINT *p = &a;int main()
{// 初始化结构体变量 aa.x = 3;a.y = 5;printf("a.x = %d\n", a.x);printf("a.y = %d\n", a.y);printf("p->x = %d\n", p->x);printf("p->y = %d\n", p->y);
}

这段代码展示了如何使用结构体指针访问结构体成员。

运行结果如下图所示:
image-20240804133951999

结构体数组

“结构体数组”实际上是指“结构体实例的数组”。事实上,这并没有什么特别之处,它只是一个数组,每个元素恰好是一个结构体实例。唯一特别的是,我们可以使用列表表示法来初始化数组:

// 声明、定义并初始化结构体数组
struct Point my_arr[3] = {{.x=4, .y=5}, // 初始化第一个元素{.x=6, .y=7}, // 初始化第二个元素{.x=8, .y=9}  // 初始化第三个元素
};

然后我们可以这样访问成员:my_arr[0].x

初始化甚至可以更简短:

// 声明、定义并初始化结构体数组
struct Point my_arr[3] = { {4, 5}, {6, 7}, {8, 9} };

位域

MCU 在其内存中保留了一部分用于特殊功能寄存器(SFRs)。这些寄存器的详细信息可以在 MCU 的数据手册中找到。例如,以下是 dsPIC33FJ256MC710A 的 PORTA 和 LATA 寄存器:
image-20240804135214932

它们在 RAM 中有固定的位置:地址分别是 0x02C20x02C4。由于这是一个 16 位处理器,它们中的每一个都是 16 位宽的。通过 LATA 寄存器,可以强制 MCU 的引脚电平是高或低;PORTA 寄存器用于感知它们的状态。但如何从(往)这些寄存器中读(写)单个位呢?

我们可以使用位掩码来实现。位掩码是一种巧妙的使用按位操作符(例如 &|)来操纵变量中某些位的方法,而不影响其他位。然而,这种方法很快会导致代码难以阅读。

更优雅的方法是使用位域

位域基础

位域是结构体中的无符号整数成员,占据指定数量的相邻位。例如:

typedef struct
{unsigned int lo: 1;unsigned int mid: 6;unsigned int hi: 1;
} FooBits;

在这个例子中,指定了一个匿名结构体,并给它取名为 FooBits。结构体有三个位域:lomidhi。它们每一个都是无符号整数类型,并且指定了各自的位数。无符号整数类型甚至可以写成 unsigned

typedef struct
{unsigned lo: 1;unsigned mid: 6;unsigned hi: 1;
} FooBits;

现在用这个结构体实例化一个变量:

// 声明并定义来自结构体 'FooBits' 的变量 'foo'
FooBits foo;// 初始化位域
foo.lo = 1;
foo.mid = 8;
foo.hi = 0;

本质上,这与普通的结构体变量非常相似。我们可以使用点号表示法来访问位域成员。结构体变量也可以使用列表表示法进行初始化(如果结构体变量的定义和初始化在同一条语句中进行,则可以省略类型转换 (FooBits)):

// 声明并定义来自结构体 'FooBits' 的变量 'foo'
FooBits foo;// 初始化位域
foo = (FooBits){1, 8, 0};

缺失位

许多 MCU 的寄存器都有未使用的位。本章节开头的例子显示了 PORTA 和 LATA 寄存器中第 8 位和第 11-13 位是未使用的。这些未使用的位域在结构体定义中为简化起见不命名:

typedef struct
{unsigned lo: 1; // 位域命名为 'lo'unsigned : 6;   // 匿名位域unsigned hi: 1; // 位域命名为 'hi'
} FooBits;

在这个例子中,位域 lohi 之间的 6 位是不可达的——这正是我们所需要的。初始化结构体变量时,也应忽略这些位:

// 声明并定义来自结构体 'FooBits' 的变量 'foo'
FooBits foo;// 初始化位域,忽略匿名位域
foo = (FooBits){1, 0};

示例

这是 Microchip 定义的结构体 LATAbits 的样子:

// 声明变量 LATA
extern volatile uint16_t LATA __attribute__((__sfr__));// 指定类型 'struct tagLATABITS' 并提供别名
// 'LATABITS'。
typedef struct tagLATABITS {uint16_t LATA0:1;uint16_t LATA1:1;uint16_t LATA2:1;uint16_t LATA3:1;uint16_t LATA4:1;uint16_t LATA5:1;uint16_t LATA6:1;uint16_t LATA7:1;uint16_t :1;uint16_t LATA9:1;uint16_t LATA10:1;uint16_t :3;uint16_t LATA14:1;uint16_t LATA15:1;
} LATABITS;// 声明变量 'LATAbits' 为该结构体的实例。
extern volatile LATABITS LATAbits __attribute__((__sfr__));

Microchip 在这里声明了(但没有定义!)两个变量:

  • 类型为 uint16_t(16 位整数)的 LATA
  • 类型为 LATABITS(带位域的结构体,总共 16 位)的 LATAbits

由于它们未被定义,编译器不会为它们分配内存地址,extern 关键字处理这一点。更进一步的是,__attribute__((__sfr__)) 编译器指令暗示编译器地址分配将在构建过程的最后阶段:链接步骤中进行。链接脚本提供绝对地址:

PORTA        = 0x2C2;
_PORTA       = 0x2C2;
_PORTAbits   = 0x2C2;
LATA         = 0x2C4;
_LATA        = 0x2C4;
_LATAbits    = 0x2C4;

变量 LATALATAbits 都映射到相同的地址 0x2C4。变量 LATA 是一个 16 位整数,我们可以整体操作。LATAbits 总共也是 16 位,但它的数据类型是结构体,因此它提供了对各个位的细粒度访问!

紧凑成员

当你看到这样的代码:

// 指定匿名结构体类型,并通过别名 'Foo' 提供
typedef struct
{char a;char b;
} Foo;// 声明、定义并初始化一个结构体变量
Foo foo = {'A', 'B'};

你可能认为变量 foo 在内存中的样子如下:
image-20240804141936487

然而,这并不一定正确!编译器通常会根据 CPU 的位数对成员进行对齐。换句话说,在一个 16 位的 MCU 上,我们可能会得到这样的情况:
image-20240804142140763

变量 foo 现在消耗了两个 16 位的内存单元,每个内存单元的下半部分用于存储 char 类型的数据。

在大多数情况下,这无关紧要。但是当这很重要时,我们需要使用编译器指令来紧凑成员:

typedef struct
{char a;char b __attribute__((__packed__)); // 成员 'b' 紧跟在 'a' 之后
} Foo;

在上面的例子中,成员 b 被紧凑存放:它紧跟在前一个成员后面,没有任何填充。

如果有多个成员需要紧凑存放,我们可以将编译器指令放在结构体的顶部:

typedef struct __attribute__((__packed__))
{char a;char b;char c;char d;
} Foo;

注意嵌套结构体!例如:

typedef struct
{uint8_t x;uint8_t y;
} Point;typedef struct __attribute__((__packed__))
{Point a;Point b;
} Line;

结构体 Line 是紧凑的,但这并不意味着结构体 Point 也是紧凑的!换句话说:成员 ab 之间不会有填充,但成员 xy 之间仍然可能有填充。

上方内容翻译自 Embeetle 的 Embedded C Tutorial


结构体的应用

RTOS 中的各种资源的控制块是结构体类型对象,因此有“没有结构体类型就没有操作系统”的说法,由此可见,C 语言的结构体类型在 RTOS 中作用巨大。

基本应用

为下图所示的个人基本信息表声明一个结构体类型,然后定义这个类型的对象并为其成员赋初值,最后输出它们
image-20240804150420367

测试用程序代码如下:

#include <stdio.h>
#include <stdlib.h>struct personal
{char *name;char *sex;char *birthday;char *nationality;char *health;int age;char *height;char *email;char *address;
};int main(void)
{struct personal ps1;ps1.name = "ZhangSan";ps1.sex = "male";ps1.birthday = "5/1/2000";ps1.nationality = "han";ps1.health = "good";ps1.age = 24;ps1.height = "180";ps1.email = "ZhangSan@qq.com";ps1.address = "BeiJing";printf("Name: \t%s\nSex: \t%s\nAge: \t%d\n", ps1.name, ps1.sex, ps1.age);exit(0);
}

运行结果如下图所示:
image-20240804151602767

这段代码首先包含了两个头文件 #include <stdio.h>#include <stdlib.h>,分别用于输入输出操作和内存管理。

接着定义了一个名为 personal 的结构体,其中包含了成员变量 namesexbirthdaynationalityhealthageheightemailaddress,用于存储个人信息。

main 函数中,创建了一个结构体变量 ps1,并为其赋值:name 为 “ZhangSan”,sex 为 “male”,birthday 为 “5/1/2000”,nationality 为 “han”,health 为 “good”,age 为 24,height 为 “180”,email 为 “ZhangSan@qq.com”,address 为 “BeiJing”。

最后通过 printf 函数打印出 ps1 结构体变量中的 namesexage 信息,并通过 exit(0) 退出程序。

结构体嵌套

在上面个人基本信息表的基础上增加如下图所示的学习经历表,然后将这两个表格组合成一个学生信息表

image-20240804153859994

测试用程序代码如下:

#include <stdio.h>
#include <stdlib.h>//声明个人基本信息表,即声明结构类型
struct personal
{char *name;char *sex;char *birthday;char *nationality;char *health;int age;char *height;char *email;char *address;
};//声明学习经历表
struct studyExperience
{char *school;char *university;
};//声明学生信息表
struct studentMessage
{struct personal ps1Tab;struct studyExperience stuExp;
};int main(void)
{//定义个人基本信息表对象并初始化struct personal ps1 ={"ZhangSan","male","5/1/2000","han","good",24,"180","ZhangSan@qq.com","BeiJing"};//读取并输出结构体部分成员的值printf("Name: \t%s\nSex: \t%s\nAge: \t%d\n\n", ps1.name, ps1.sex, ps1.age);//定义学习经历表对象并初始化struct studyExperience se ={"zhongshan 1 school","guangdong university"};//读取并输出结构体成员的值printf("School: \t\t%s\nUniversity: \t%s\n\n", se.school, se.university);//定义学生信息表对象并初始化struct studentMessage sm ={ps1,se};//输出部分成员值printf("Name: \t\t%s\nSchool: \t%s\n", sm.ps1Tab.name, sm.stuExp.university);exit(0);
}

运行结果如下图所示:
image-20240804154225767

这个C语言程序定义了几个结构体类型来存储个人信息和学习经历,并在main函数中创建了这些结构体的实例,对它们进行了初始化,并打印了一些成员的值。以下是对程序的详细解释及需要注意的细节和知识点:

程序结构

  1. 结构体声明

    • struct personal:定义了一个结构体来存储个人基本信息,包括姓名、性别、生日、国籍、健康状况、年龄、身高、电子邮件和地址。这些成员中的一些(如name, sex, birthday等)是char*类型,即指向字符的指针。

    • struct studyExperience:定义了一个结构体来存储学习经历,包括学校和大学,这两个成员都是char*类型。

    • struct studentMessage:定义了一个结构体来存储学生的信息,它包含两个结构体成员,分别是personalstudyExperience类型的实例。

  2. main函数

    • 个人信息表对象初始化

      struct personal ps1 = {"ZhangSan","male","5/1/2000","han","good",24,"180","ZhangSan@qq.com","BeiJing"
      };
      

      这部分定义了一个personal类型的结构体对象ps1,并为各个成员赋了初值。需要注意的是,虽然这些值是字符串常量,但ps1结构体的成员是char*类型,这意味着它们是指向这些字符串常量的指针。这样做的好处是节省内存,但如果你需要修改这些字符串,你需要在堆上分配内存并使用strdup等函数复制字符串。

    • 学习经历表对象初始化

      struct studyExperience se = {"zhongshan 1 school","guangdong university"
      };
      

      这部分定义了一个studyExperience类型的结构体对象se,并为学校和大学赋初值。

    • 学生信息表对象初始化

      struct studentMessage sm = {ps1,se
      };
      

      这部分定义了一个studentMessage类型的结构体对象sm,它包含了ps1se两个结构体的实例,分别对应个人信息和学习经历。

  3. 输出结构体成员

    printf("Name: \t%s\nSex: \t%s\nAge: \t%d\n\n", ps1.name, ps1.sex, ps1.age);
    printf("School: \t\t%s\nUniversity: \t%s\n\n", se.school, se.university);
    printf("Name: \t\t%s\nSchool: \t%s\n", sm.ps1Tab.name, sm.stuExp.university);
    

    这些printf语句用于打印结构体成员的值。注意,%s格式说明符用于打印字符串,%d用于打印整数。

注意点和重要知识点

  1. 字符串和指针

    • 在这个程序中,字符串成员是用char*来声明的。这意味着这些成员存储的是指向字符串的指针,而不是实际的字符串数据。如果在程序中修改这些字符串数据,需要分配动态内存并使用strdup函数来复制字符串。
  2. 结构体成员的初始化

    • 结构体成员的初始化需要保持数据类型一致。例如,对于char*类型的成员,需要确保字符串常量的生命周期足够长,以防止在结构体使用期间字符串数据被修改或释放。
  3. 内存管理

    • 如果要在结构体中存储动态分配的内存,记得在程序结束之前释放这些内存,避免内存泄漏。
  4. 结构体的嵌套

    • studentMessage结构体通过将personalstudyExperience结构体作为成员实现了嵌套。结构体嵌套是组织和管理复杂数据的有效方法。
  5. exit函数

    • exit(0);用于正常终止程序。在许多简单程序中,它的作用类似于return 0;,表示程序正常结束。

结构类型的函数指针成员

测试用程序代码如下:

/** 结构类型的函数指针成员*/
#include <stdio.h>
#include <stdlib.h>struct studentMessage;  //对象前置声明
void showMessage(struct studentMessage x);  //函数前置声明
//声明个人基本信息表,即声明结构类型
struct personal
{char *name;char *sex;char *birthday;char *nationality;char *health;int age;char *height;char *email;char *address;
};
//声明学习经历表
struct studyExperience
{char *school;char *university;
};
//声明学生信息表
struct studentMessage
{struct personal *ps1Tab;struct studyExperience *stuExp;void (*disPer)(struct studentMessage x);
};int main(void)
{//定义个人基本信息表对象并初始化struct personal ps1 ={"ZhangSan","male","5/1/2000","han","good",24,"180","ZhangSan@qq.com","BeiJing"};//读取并输出结构体部分成员的值printf("Name: \t%s\nSex: \t%s\nAge: \t%d\n\n", ps1.name, ps1.sex, ps1.age);//定义学习经历表对象并初始化struct studyExperience se ={"zhongshan 1 school","guangdong university"};//读取并输出结构体成员的值printf("School: \t\t%s\nUniversity: \t%s\n\n", se.school, se.university);//定义学生信息表对象并初始化struct studentMessage sm ={.ps1Tab = &ps1,.stuExp = &se,.disPer = showMessage};//通过结构类型对象的函数指针调用了函数 showMessagesm.disPer(sm);exit(0);
}/** 打印信息 */
void showMessage(struct studentMessage x)
{//输出部分成员值printf("Name: \t%s\nSchool: %s\n", x.ps1Tab->name, x.stuExp->school);
}

运行结果如下图所示:
image-20240805215309238

结构体的仿类类型

测试用程序代码如下:

/** 结构体的仿类类型*/
#include <stdio.h>
#include <stdlib.h>struct studentMessage;  //对象前置声明
void showMessage(struct studentMessage x);  //函数前置声明
void setAge(struct studentMessage x, int y);  //设置 age 值的函数前置声明
int getAge(struct studentMessage x);  //获取 age 值的函数前置声明
//声明个人基本信息表,即声明结构类型
struct personal
{char *name;char *sex;char *birthday;char *nationality;char *health;int age;char *height;char *email;char *address;
};
//声明学习经历表
struct studyExperience
{char *school;char *university;
};
//声明学生信息表
struct studentMessage
{struct personal *ps1Tab;struct studyExperience *stuExp;void (*disPer)(struct studentMessage x);void (*set_age)(struct studentMessage x, int y);  //set 函数指针int (*get_age)(struct studentMessage x);  //get 函数指针
};int main(void)
{//定义个人基本信息表对象并初始化struct personal ps1 ={"ZhangSan","male","5/1/2000","han","good",24,"180","ZhangSan@qq.com","BeiJing"};//读取并输出结构体部分成员的值printf("Name: \t%s\nSex: \t%s\nAge: \t%d\n\n", ps1.name, ps1.sex, ps1.age);//定义学习经历表对象并初始化struct studyExperience se ={"zhongshan 1 school","guangdong university"};//读取并输出结构体成员的值printf("School: \t\t%s\nUniversity: \t%s\n\n", se.school, se.university);//定义学生信息表对象并初始化struct studentMessage sm ={.ps1Tab = &ps1,.stuExp = &se,.disPer = showMessage,  //为函数指针赋值.set_age = setAge,.get_age = getAge};//通过结构类型对象的函数指针调用了函数 showMessagesm.disPer(sm);//调用 set_age 函数sm.set_age(sm, 35);//调用 get_age 函数并输出printf("Age: \t%d\n", sm.get_age(sm));exit(0);
}/** 打印信息 */
void showMessage(struct studentMessage x)
{//输出部分成员值printf("Name: \t%s\nSchool: %s\n", x.ps1Tab->name, x.stuExp->school);
}
/** 设置 age 值的函数*/
void setAge(struct studentMessage x, int y)
{x.ps1Tab->age = y;
}
/** 获取 age 值的函数*/
int getAge(struct studentMessage x)
{return x.ps1Tab->age;
}

运行结果如下图所示:
image-20240805215625093

嵌套结构类型指针

测试用程序代码如下:

/** 嵌套结构类型指针*/
#include <stdio.h>
#include <stdlib.h>//声明了一个只有一个成员的结构类型(猫猫类型)
struct cat
{char *color;  //猫猫的颜色
};
//声明嵌套结构类型(小猫类型),其中内嵌了 struct cat 对象
struct childCat
{struct cat c; //内嵌对象char *color;  //猫咪的颜色
};int main(void)
{//定义一个包含有内嵌对象的外层对象struct childCat chi;chi.c.color = "white";  //为内嵌对象 color 成员赋值chi.color = "black";    //为外层对象 color 成员赋值//把首地址赋予了内嵌对象类型指针struct cat *p = (struct cat *)&(chi);//输出内嵌对象的 color 的值printf("Cat color: %s\n", p->color);exit(0);
}

运行结果如下图所示:
image-20240805215745181

基类函数钩子

测试用程序代码如下:

/** 基类函数钩子*/
#include <stdio.h>
#include <stdlib.h>//内嵌类型(基类)
struct cat
{char *color;  //猫猫的颜色void (*_show)(struct cat *p); //函数钩子
};
//外层类型(派生类)
struct childCat
{struct cat c; //内嵌对象char *color;  //猫咪的颜色
};/** 基类类型的 show 函数*/
void showCat(struct cat *p)
{printf("Cat color: %s\n", p->color);
}/** 派生类类型的 show 函数*/
void showChildCat(struct cat *p)
{//将 p 转换为派生类类型printf("Child Cat color: %s\n", ((struct childCat *)p)->color);
}int main(void)
{//定义基类对象并初始化struct cat stCat;stCat.color = "white";stCat._show = showCat;   //为钩子挂接函数 showCat//定义派生类对象并初始化struct childCat stChildCat;stChildCat.color = "black";  //为外层对象 color 成员赋值stChildCat.c._show = showChildCat;   //为钩子挂接函数 showChildCat//使用对象调用钩子stCat._show(&stCat);stChildCat.c._show((struct cat *)&stChildCat);//定义两个基类类型的指针变量struct cat *p1, *p2;p1 = &stCat;p2 = (struct cat *)&stChildCat;//使用基类类型的指针调用钩子(调用界面完全相同)p1->_show(&stCat);p2->_show((struct cat *)&stChildCat);exit(0);
}

程序运行结果如下图所示:

image-20240805215840003

实现多态特性

测试用程序代码如下:

/** 实现多态特性*/
#include <stdio.h>
#include <stdlib.h>//内嵌类型(基类)
struct cat
{char *color;  //猫猫的颜色void (*_show)(struct cat *p); //函数钩子
};
//外层类型(派生类)
struct childCat
{struct cat c; //内嵌对象char *color;  //猫咪的颜色
};/** 基类类型的 show 函数*/
void showCat(struct cat *p)
{if (NULL == p)  //指针为空返回return;printf("Cat color: %s\n", p->color);
}/** 派生类类型的 show 函数*/
void showChildCat(struct cat *p)
{if (NULL == p)  //指针为空返回return;//将 p 转换为派生类类型printf("Child Cat color: %s\n", ((struct childCat *)p)->color);
}/** 同一个函数因接收对象的类型不同,其 show 的结果也不同*/
void show(struct cat *_cat)
{if (NULL == _cat)  //指针为空返回return;//调用钩子上的函数_cat->_show(_cat);
}int main(void)
{//定义基类对象并初始化struct cat stCat;stCat.color = "white";stCat._show = showCat;   //为钩子挂接函数 showCat//定义派生类对象并初始化struct childCat stChildCat;stChildCat.color = "black";  //为外层对象 color 成员赋值stChildCat.c._show = showChildCat;   //为钩子挂接函数 showChildCat//以 struct childCat * 类型对象为实参调用 show()show((struct cat *)&stChildCat);//以 struct cat * 类型对象为实参调用 show()show(&stCat);exit(0);
}

程序运行结果如下图所示:
image-20240805220106669

计算外层对象指针的宏定义

测试用程序代码如下:

/** 计算外层对象指针的宏定义*/
#include <stdio.h>
#include <stdlib.h>//offsetof 和 container_of 的定义
#define offsetof(TYPE, MEMBER)  ((size_t)&((TYPE*)0)->MEMBER)
#define container_of(ptr, type, member) ({\const typeof(((type *)0)->member) *__mptr = (ptr); \(type *)((char *)__mptr - offsetof(type, member));})//定义了结构 struct parentStruct (本例中,它为内嵌对象的结构类型)
struct parentStruct
{int a;
};
//定义了结构 struct childStruct (本例中,它为外层对象的结构类型)
struct childStruct
{int b;struct parentStruct parent;
};/** 参数为内嵌结构类型指针的函数 show()*/
void show(struct parentStruct *p)
{//使用宏 container_of 获得外层对象指针struct childStruct *pChild = container_of(p, struct childStruct, parent);printf("a == %d\nb == %d\n", p->a, pChild->b);
}int main(void)
{//定义外层对象并初始化struct childStruct child;child.b = 53;child.parent.a = 77;  //给内嵌对象成员 a 赋值show(&child.parent);exit(0);
}

运行结果如下图所示:
image-20240805220331904

相关文章:

[RTOS 学习记录] 预备知识:C语言结构体

这篇文章是我阅读《嵌入式实时操作系统μCOS-II原理及应用》后的读书笔记&#xff0c;记录目的是为了个人后续回顾复习使用。 文章目录 结构体结构体基础声明和定义结构体类型声明和定义结构体变量初始化结构体变量初始化各个成员使用列表符号初始化 使用结构体变量综上 结构体…...

sqli-labs注入漏洞解析--less-9/10

第九关&#xff1a; 这一关相比第八关&#xff0c;第八关他正确显示you are in,错误不显示you are in,但是第九关你不管是输入正确或者错误都显示 you are in &#xff0c;这个时候布尔盲注就不适合我们用&#xff0c;所以我们的换一下思路,布尔盲注适合页面对于错误和正确结果…...

文心智能体平台:食尚小助,提供美食推荐和烹饪指导

文章目录 前言文心智能体平台介绍创建自己的智能体我的文心智能体体验地址总结 前言 在快节奏的现代生活中&#xff0c;许多人都希望能够享受美味的食物&#xff0c;但往往缺乏时间和精力来自己动手烹饪。为了解决这一问题&#xff0c;文心智能体平台推出了“食尚小助”智能体…...

工作中,如何有效解决“冲突”?不回避,不退让才是最佳方式

职场里每个人都在争取自己的利益&#xff0c;由于立场的不同&#xff0c;“冲突”不可避免。区别在于有些隐藏在暗处&#xff0c;有些摆在了台面上。 隐藏在“暗处”的冲突&#xff0c;表面上一团和气&#xff0c;实则在暗自较劲&#xff0c;甚至会有下三滥的手段&#xff1b;…...

Qt读写配置(ini)文件

本文介绍Qt读写配置&#xff08;ini&#xff09;文件。 1.配置文件&#xff08;ini&#xff09;简介 配置文件&#xff08;ini&#xff09;也叫ini文件&#xff08;Initialization File&#xff09;&#xff0c;即初始化文件。它由节名&#xff0c;键名&#xff0c;键值构成。…...

Python笔试面试题AI答之面向对象(2)

文章目录 6.阐述 Python自省&#xff08;机制与函数&#xff09; &#xff1f;7.简述Python中面向切面编程AOP和装饰器&#xff1f;面向切面编程&#xff08;AOP&#xff09;基本概念核心原理应用场景Python中的实现方式 装饰器&#xff08;Decorator&#xff09;基本概念语法应…...

Python学习计划——12.1选择一个小项目并完成

在这节课中&#xff0c;我们将选择一个小项目并完成它。为了综合运用前面所学的知识&#xff0c;我们选择构建一个简单的Web应用&#xff0c;该应用将包含数据分析和展示功能。我们将使用Flask框架和Pandas库来处理数据&#xff0c;并将结果展示在Web页面上。 项目&#xff1a…...

uniapp 多渠道打包实现方案

首先一个基础分包方案&#xff1a; 包不用区分渠道&#xff0c;只是通过文件名进行区分&#xff0c;公共代码逻辑可以通过mixins进行混入。 这样分包后就需要在打包时只针对编译的渠道包文件进行替换打包&#xff0c;其他渠道包的文件不打包进去&#xff0c;通过工具类实现…...

请你学习:前端布局3 - 浮动 float

1 标准流&#xff08;也称为普通流、文档流&#xff09; 标准流&#xff08;也称为普通流、文档流&#xff09;是CSS中元素布局的基础方式&#xff0c;它决定了元素在页面上的默认排列方式。这种布局方式遵循HTML文档的结构&#xff0c;不需要额外的CSS样式来指定元素的位置。…...

PyCharm 2024.1 总结和最新变化

​ 您好&#xff0c;我是程序员小羊&#xff01; 前言 PyCharm 2024.1 是 JetBrains 最新发布的Python集成开发环境&#xff08;IDE&#xff09;&#xff0c;旨在提供更强大的功能和更好的用户体验。以下是对这个版本的总结和最新变化的介绍 智能代码建议和自动完成&#xff1a…...

RGB红绿灯——Arduino

光的三原色 牛顿发现光的色散奥秘之后&#xff0c;进一步计算发现&#xff1a;七种色光中只有红、绿、蓝三种色光无法被分解&#xff0c;而其他四种颜色的光均可由这三种色光以不同比例相合而成。于是红、绿、蓝被称为“三原色光”或“光的三原色”。后经证实&#xff1a;红、绿…...

浅谈用二分和三分法解决问题(c++)

目录 问题引入[NOIP2001 提高组] 一元三次方程求解题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 提示思路分析AC代码 思考关于二分和三分例题讲解进击的奶牛题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 思路AC代码 平均数题目描述输入格式输出格式样例 …...

Cocos Creator2D游戏开发(9)-飞机大战(7)-爆炸效果

这个爆炸效果我卡在这里好长时间,视频反复的看, 然后把代码反复的测试,修改,终于给弄出来 视频中这段,作者也是修改了好几次, 跟着做也走了不少弯路; 最后反正弄出来了; 有几个坑; ① 动画体创建位置是enemy_prefab ② enemy_prefab预制体下不用放动画就行; ③ 代码中引用Anima…...

终于有人把华为认证全部说清楚了

在信息技术领域&#xff0c;华为认证好比一座金字招牌&#xff0c;吸引着无数技术专业人士的青睐。 市场上关于华为认证的声音纷繁复杂&#xff0c;存在不少争议&#xff0c;让人难以辨别真伪。 今天就来好好讲讲华为认证&#xff0c;从头到尾都帮你盘盘清楚。 01 华为认证是…...

【知识】pytorch中的pinned memory和pageable memory

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 目录 概念简介 pytorch用法 速度测试 反直觉情况 概念简介 默认情况下&#xff0c;主机 &#xff08;CPU&#xff09; 数据分配是可分页的。GPU 无…...

【系统架构设计】数据库系统(五)

数据库系统&#xff08;五&#xff09; 数据库模式与范式数据库设计备份与恢复分布式数据库系统数据仓库数据挖掘NoSQL大数据 数据库模式与范式 数据库设计 备份与恢复 分布式数据库系统 数据仓库 数据挖掘 对数据挖掘技术进行支持的三种基础技术已经发展成熟&#xff0c…...

如何对人工智能系统进行测试|要点,方法及流程

当今社会&#xff0c;人工智能发展非常快。现在人工智能的发展已经渗透到了我们生活的方方面面&#xff0c;自动驾驶、或者我们手机里经常用到的一些应用都或多或少涉及到了一些人工智能的功能&#xff0c;比如说美图秀秀、新闻推荐、机器翻译以及个性化的购物推荐等等都涉及到…...

CVE-2023-37569~文件上传【春秋云境靶场渗透】

# 今天我们拿下CVE-2023-37569这个文件上传漏洞# 经过简单账号密码猜测 账号&#xff1a;admin 密码&#xff1a;password# 找到了文件上传的地方# 我们直接给它上传一句话木马并发现上传成功# 上传好木马后&#xff0c;右键上传的木马打开发现上传木马页面# 直接使用蚁剑进行连…...

MySQL简介 数据库管理与表管理

文章目录 1 MySQL的优势2 MySQL数据类型1 数字类型2 日期和时间类型3 字符串类型 3 数据库管理4 数据表管理参考 1 MySQL的优势 性能优化&#xff1a;通过优化存储引擎&#xff08;InnoDB&#xff0c;MyISAM&#xff09;和查询优化。解决大规模数据处理和查询优化开源&#xf…...

PHP 函数性能优化的技巧是什么?

本文由 ChatMoney团队出品 本文将详细介绍 PHP 函数性能优化的技巧。通过分析 PHP 函数的执行过程和性能瓶颈&#xff0c;提供一系列实用的优化方法&#xff0c;并结合代码示例&#xff0c;帮助读者提升 PHP 代码的执行效率。文章内容将涵盖变量作用域、递归算法、循环优化、内…...

Docker 离线安装指南

参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性&#xff0c;不同版本的Docker对内核版本有不同要求。例如&#xff0c;Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本&#xff0c;Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...

C++_核心编程_多态案例二-制作饮品

#include <iostream> #include <string> using namespace std;/*制作饮品的大致流程为&#xff1a;煮水 - 冲泡 - 倒入杯中 - 加入辅料 利用多态技术实现本案例&#xff0c;提供抽象制作饮品基类&#xff0c;提供子类制作咖啡和茶叶*//*基类*/ class AbstractDr…...

Flask RESTful 示例

目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题&#xff1a; 下面创建一个简单的Flask RESTful API示例。首先&#xff0c;我们需要创建环境&#xff0c;安装必要的依赖&#xff0c;然后…...

基于Flask实现的医疗保险欺诈识别监测模型

基于Flask实现的医疗保险欺诈识别监测模型 项目截图 项目简介 社会医疗保险是国家通过立法形式强制实施&#xff0c;由雇主和个人按一定比例缴纳保险费&#xff0c;建立社会医疗保险基金&#xff0c;支付雇员医疗费用的一种医疗保险制度&#xff0c; 它是促进社会文明和进步的…...

mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包

文章目录 现象&#xff1a;mysql已经安装&#xff0c;但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时&#xff0c;可能是因为以下几个原因&#xff1a;1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...

【开发技术】.Net使用FFmpeg视频特定帧上绘制内容

目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法&#xff0c;当前调用一个医疗行业的AI识别算法后返回…...

【无标题】路径问题的革命性重构:基于二维拓扑收缩色动力学模型的零点隧穿理论

路径问题的革命性重构&#xff1a;基于二维拓扑收缩色动力学模型的零点隧穿理论 一、传统路径模型的根本缺陷 在经典正方形路径问题中&#xff08;图1&#xff09;&#xff1a; mermaid graph LR A((A)) --- B((B)) B --- C((C)) C --- D((D)) D --- A A -.- C[无直接路径] B -…...

【JVM】Java虚拟机(二)——垃圾回收

目录 一、如何判断对象可以回收 &#xff08;一&#xff09;引用计数法 &#xff08;二&#xff09;可达性分析算法 二、垃圾回收算法 &#xff08;一&#xff09;标记清除 &#xff08;二&#xff09;标记整理 &#xff08;三&#xff09;复制 &#xff08;四&#xff…...

BLEU评分:机器翻译质量评估的黄金标准

BLEU评分&#xff1a;机器翻译质量评估的黄金标准 1. 引言 在自然语言处理(NLP)领域&#xff0c;衡量一个机器翻译模型的性能至关重要。BLEU (Bilingual Evaluation Understudy) 作为一种自动化评估指标&#xff0c;自2002年由IBM的Kishore Papineni等人提出以来&#xff0c;…...

elementUI点击浏览table所选行数据查看文档

项目场景&#xff1a; table按照要求特定的数据变成按钮可以点击 解决方案&#xff1a; <el-table-columnprop"mlname"label"名称"align"center"width"180"><template slot-scope"scope"><el-buttonv-if&qu…...