语言基础 /CC++ 可变参函数设计与实践,必须要指定可变参数的个数?YES

news2024/10/5 15:35:42

文章目录

  • 概述
  • 语法符号 ...
  • 变参函数的强制参数
  • 没必要指定变参首元素
  • 自以为是,找到了不定义'变参个数'的方法
  • 函数 printf 和 vprintf 隐式的指明了变参个数
  • 宏函数 va_arg 透析
  • 小节

概述

本文重点分析论证了,在可变参函数参数表中指定变参个数的必要性,以及指定变参列表首元素的不必要性,是对C&C++ 可变参函数设计与实践系列文章的进一步扩展。在整理《语言基础 /C&C++ 可变参函数设计与实践》相关文章内容的时候,发现,在变参函数实践过程中,存留了两个问题:

1、变参函数的形参列表中,一定要指定可变参数个数吗?函数 printf 和 vprintf 没有指定变参个数?
2、变参函数的形参列表中,一定要指定用户变参数列表的首个元素吗?

@History
如上两个问题并不大,也不很关键,不影响我对变参函数的实现和使用过程,但每每想起来,就觉得不舒服。本来是将 “可变参函数,强制参数不可以是char和short等类型”、“可变参函数,必须要指定可变参数的个数?”、“回调变参函数和替代变参函数方案” 等相关内容塞到一篇文章中的,奈何篇幅太长,故拆分出此篇文章。

转载请标明原文链接,
https://blog.csdn.net/quguanxin/category_6223029.html

语法符号 …

先要说明的一点是,…省略号是什么? 它是一种语法结构(符号)。

//指定参数个数做可变函数强制参数
void print_Integers2(int param_count, ...) {
    va_list argptr;
    va_start(argptr, param_count);     //初始化变参列表
    for (int i = 0; i < param_count; i++) {
        int value = va_arg(argptr, int);
        //do something .., or save first..
        qDebug("test2_Param%d:%d ", i+1, value);  
    }
    va_end(argptr);                    //清空变参列表
}

int main() { //Test
    print_Integers2(3, 100, 200, 300);
    return 0;
}

在这里插入图片描述
如上述函数定义中,省略号 … 它并不是关键字或占位符,而是一种特殊的语法结构,用于表示函数可以接受可变数量的参数(列表)。这里,省略号 …语法结构符号,其作用是告诉编译器,在函数体内可以通过某些手段(如 <stdarg.h> 头文件中的宏和类型)来访问和处理可变参数列表中的参数。

变参函数的强制参数

在代表变参列表的语法符号… 前,要至少有一个确定的形参,当然,你可以在变参函数形参类表中添加多个确定的参数。紧挨着…的那个确定参数,也即最后一个确定参数,一般称为变参函数的强制参数,如上文 print_Integers2 函数中的 param_count,如下文 print_Integers1函数中的 param_one。

在QtCreator+Mingw下,我们测试一个没有强制参数的变参函数实现,
在这里插入图片描述
如上,va_start 宏,并不能接受 null 实参,那会导致编译器崩溃,故强制参数是 va_start 宏函数的钢需,强制要求存在。在《语言基础 /C&C++ 可变参函数设计与实践,强制参数不可以是char和short等类型!》中,我们讲解了强制参数的类型问题,这里不再赘述。

没必要指定变参首元素

在很长一段时间内,我自定义的变参函数都是同时指定参数个数和用户参数列表的第一个元素做确定形参,如,

//某项目中抽出来的伪代码 /Here功能上...是与xBoardType相同数据类型的参数
bool BuildDynamicDevModel(THandleBoard *ptUserDt, unsigned int xBoardTypeCount, unsigned int xBoardTypeOne, ...)
{
    //存储变参列表的变量/首个元素直接赋值,其他置零
    unsigned int ParamList[/*MAX_COUNT*/10] = {xBoardTypeOne, 0};
    //参数序号
    unsigned char u8Index = 0;

    va_list args;  //
    va_start(args, xBoardTypeCount);    //初始化变参列表,可变参数列表的起始位置
    for (u8Index = 0; u8Index < xBoardTypeCount; u8Index++) {
        // //warning if short or unsigned char /+1: for skip u8BoardTypeOne
        ParamList[u8Index + 1] = va_arg(args, int);
    }
    va_end(args);                      //清理va_list参数列表

    //do someting by ParamList...
}

脱离项目,我们单独看一个完整的测例,

//'不太优雅的'惯用格式?
void print_Integers1(short param_count, int param_one, ...) {
    qDebug("test1_Param1:%d ", param_one);
    va_list argptr;
    va_start(argptr, param_one);       //start
    for (int i = 0; i < param_count-1; i++) {
        param_one = va_arg(argptr, int);
        qDebug("test1_Param%d:%d ", i+1, param_one);
    }
    va_end(argptr);                    //End
}

也记不清楚是从哪里习得的上述模式,或者是如何自己摸索出来的,它们是准确无误运行的。只是但每每看到上述样式的变参函数签名,一股莫名的拙劣感就迎面而来。最根本的,由于变参表首元素–变参param_one的引入,使得变参类表的处理过程略显繁琐,又是加1又是减1的。那么最初的我,为什么要加param_one这个参数呢?
回顾历史,
我当时误认为,va_start 是需要变参的首元素来进行初始化的,而不能以其他的参数来初始化,尤其是当变参为结构体指针等类型的时候。但实际上我担心过火了,任意合法数据类型(如int、long等)的强制参数,都可以作为va_start的实参,且没有必要与变参元素是相同的类型。这个已经验证过多次了,上节中的 print_Integers2 函数就是一个很好的例子,此处不再赘述。

承上启下,
截止前文,我们已经消除了概要中的第2个问题,即,变参列表中,没有必要指定变参表的首元素。而且通过前文中我们也明白了变参函数强制参数的功能和必要性,一个变参函数不能只包含…这个语法结构符号。

自以为是,找到了不定义’变参个数’的方法

在很久前的一次记录中,我定义了函数,

void TestVaridicFuncOfStruct(void *pvUserDt, TParam *args, ...) {
	...
}

可能是当时不知道哪里来的小幸运,其执行过程竟然没有出现任何异常,那导致我在一段时间内误以为是发现了新大陆,以为我找到了不用添加 ’ 变参个数形参’ 的方法。但后来的测试,这种幸运没有再出现过,我都怀疑自己当时是看花眼了,从而自己骗了自己许久。

//只携带参数列表首元素,做普通形参
void print_Integers3(int param_one, ...) {
    short index = 0;
    qDebug("test3_Param_%d:%d ", ++index, param_one);
    //
    va_list argptr;
    va_start(argptr, param_one);  //初始化变参列表
    do {
        int param_other = va_arg(argptr, int);
        if (0 == param_other) break;
        qDebug("test3_Param_%d:%d ", ++index, param_other);
       } while (true);
    va_end(argptr);               //清空变参列表
    qDebug("test3_Param_End");
}

int main() {
    //首元素是单独的形参
    print_Integers1(3, 100, 200, 300);
    //所有变参统一使用...定义
    print_Integers2(3, 100, 200, 300);
    //不传递变参个数
    print_Integers3(100, 200, 300);
	...
}

在这里插入图片描述
如上 print_Integers3 的处理结果,并没有按照预期输出结果,期望的是只遍历出3个实参,而是出现了第4个假实参。由于变参类型只是普通的int类型,上述异常并未导致程序崩溃。

我们看点更危险的,

void TestVaridicFuncOfStruct2(void *pvUserDt, ...) {
    va_list ap;
    int param_index = 0;  //参数编号
    va_start(ap, pvUserDt);
    do {
        TParam *args = va_arg(ap, TParam*);
        if (args == 0) break;
        qDebug("argIndex[%d] argAddr[0x%.16llx], argValue[%d-%d] ",
               param_index, (unsigned long long)args, args->iSeg1, args->iSeg2);
        param_index++;
    } while (param_index < MAXARGS);
    va_end(ap);
}

程序输出,
在这里插入图片描述
param_index == 2 时已是异常,其==3时,如下,程序直接崩溃,
在这里插入图片描述

通过上述两个测试用例和运行结果,基本可得,变参数目这一信息对于变参列表的正确解析似乎是必要的。

函数 printf 和 vprintf 隐式的指明了变参个数

//
int __CRTDECL  printf(char const* const format, ...);
//
int __CRTDECL vprintf(const char* format, va_list argptr);

如上,printf 和 vprintf 函数都是 C 标准库中的函数,都用于根据提供的格式化字符串和参数列表进行输出,它们的第一个参数同是格式化字符串 format,用于指定输出的格式,不同的是可变参数列表的形式。在大多数实现中,printf函数的底层实现可能使用了vprintf函数,即,printf函数会将format字符串和可变参数列表传递给vprintf函数来进行实际的输出,但具体实现会因编译器和操作系统而异。

下文以 GNU C 库实现为例(可在此 链接A / 链接B 页面下载源码),观察 printf 和 vprintf 函数的实现,
在这里插入图片描述
将下载到的 glibc-2.9.tar.gz 解压后,分别找到 printf 和 vprintf 的实现如下,

// file : printf.c
#undef printf
/* Write formatted output to stdout from the format string FORMAT.  */
int __printf (const char *format, ...) {
  va_list arg;
  int done;

  va_start (arg, format);
  done = vfprintf (stdout, format, arg);
  va_end (arg);

  return done;
}

// file : vprintf.c
#undef	vprintf
/* Write formatted output to stdout according to the format string FORMAT, using the argument list in ARG.  */
int __vprintf (const char *format, __gnuc_va_list arg) {
  return vfprintf (stdout, format, arg);
}

因此在glibc中,printf 函数的最终依托 vfprintf 函数,其实现比较庞杂,下文只截取小部分,

/* The function itself.  */
int vfprintf (FILE *s, const CHAR_T *format, va_list ap) {
	...
  /* This table maps a character into a number representing a class.  In each step there is a destination label for each class.  */
  static const int jump_table[] =   {
    ...
    /* '0' */  5, /* '1' */  8, /* '2' */  8, /* '3' */  8,
    /* '4' */  8, /* '5' */  8, /* '6' */  8, /* '7' */  8,
    /* '8' */  8, /* '9' */  8,            0,            0,
	       0,            0,            0,            0,
	       0, /* 'A' */ 26,            0, /* 'C' */ 25,
	       0, /* 'E' */ 19, /* F */   19, /* 'G' */ 19,
	    ...
  };

#define NOT_IN_JUMP_RANGE(Ch) ((Ch) < L_(' ') || (Ch) > L_('z'))
#define CHAR_CLASS(Ch) (jump_table[(INT_T) (Ch) - L_(' ')])
...

#define STEP0_3_TABLE							      \
    /* Step 0: at the beginning.  */				  \
    static JUMP_TABLE_TYPE step0_jumps[30] = {		  \
      ...
      REF (width),		    /* for '1'...'9' */	       \
      REF (mod_long),		/* for 'l' */		       \
	  ...
      REF (form_float),		/* for 'E', 'e', 'F', 'f', 'G', 'g' */	      
      ...
    };									      \
    /* Step 1: after processing width.  */				      \
    static JUMP_TABLE_TYPE step1_jumps[30] =				  \
		...
    /* Step 2: after processing precision.  */				      \
    static JUMP_TABLE_TYPE step2_jumps[30] =				      \
		...
    /* Step 3a: after processing first 'h' modifier.  */		  \
    static JUMP_TABLE_TYPE step3a_jumps[30] =				      \
    ...
    /* Step 3b: after processing first 'l' modifier.  */		  \
    static JUMP_TABLE_TYPE step3b_jumps[30] =				      \
		....
#define STEP4_TABLE								      \
    /* Step 4: processing format specifier.  */				      \
    static JUMP_TABLE_TYPE step4_jumps[30] =				      \
     ....

不再细究,有兴趣可直接研读 glibc - vfprintf.c 文件 。vfprintf实现思路还是读明白了一丁点的,当 vfprintf 函数遇到一个特定的格式化输出类型时,它会通过格式化字符串中的格式指示符来索引跳转表,并根据对应的索引值来获取变参列表中的实参和相应的处理函数。无论 vfprintf 的实现多么精妙绝伦,本质上,它都是解析 format 格式化字符串中的占位符,包括其个数、类型、对应的实参等,我们也可以根据这种思路,实现类似的可变参函数,甚至是是简单的自定义语义的某种功能。

伪代码版本的 vfprintf 实现,
如上,也感受到了,实际的 printf/vprintf 函数处理了非常多的格式化符号、选项和功能,性能优化措施和不一般的复杂性。现在的我是真的有些吃不消,于是写了如下示例,来简要叙述格式化字符串是如何表达参数个数这一信息的,并不做其他深究。

#include <stdio.h>
#include <stdarg.h>

int my_printf(const char* format, ...)
{
    va_list argptr;
    va_start(argptr, format);

    int count = 0;
    const char* p = format;
    while (*p != '\0') {
        if (*p == '%') {
            p++; // 跳过 '%'

            switch (*p) {
                case 'd': {
                    int value = va_arg(argptr, int);
                    printf("%d", value);
                    count++;
                    break;
                }
                case 's': {
                    char* str = va_arg(argptr, char*);
                    printf("%s", str);
                    count++;
                    break;
                }
                // 其他格式化符号的处理...

                default:
                    putchar(*p);
                    break;
            }
        }
        else {
            putchar(*p);
        }

        p++;
    }

    va_end(argptr);
	//
    return count;
}

int main() {
    int a = 10;
    char* str = "Hello";
    my_printf("Value: %d, String: %s\n", a, str);
    return 0;
}

通过上述简单的示例程序,可知,在逐字符解析 format 格式化字符串的过程中,就很容易得到了期望的实参的个数,在此我们姑且称之为隐式声明了变参数个数。也很容易理解到:如果实参类型与格式字符不匹配可能带来程序异常;如果实际参数多于格式化字符个数,则多出来的被舍弃;如果实际参数少于格式化字符个数,则程序行为是未定义的,如访问的内存溢出到了变参实参栈外…

宏函数 va_arg 透析

在前文中,从实践层面上就基本验证了,对于变参函数的实现,指定变参数的个数是必要的,无论是显示的还是隐式的。因为变参函数的实现过程中,我们并不能通过判断 NextParam 值为 0 或 NULL 等来有效结束变参列表的遍历过程。接下来,我们透过对 va_arg 宏函数的分析,从理论上,谈谈为啥要指定变参个数…

我们早知道 va_arg 函数 是C语言标准库中定义的一个宏,用于在可变参数函数中访问可变参数列表中的下一个参数。以 VS2015 为例,截取 va_arg 函数在部分OS类型下的定义。
在这里插入图片描述
在这里插入图片描述
如上定义在 Microsoft Visual Studio 14.0\VC\include\vadefs.h 文件下,并最终通过 Microsoft Visual Studio 14.0\VC\include\stdarg.h 头文件导出给用户使用。只关注 va_arg 定义,如下,

//river.qu/Microsoft Visual Studio 14.0\VC\include\vadefs.h //#elif defined _M_IX86
#define _INTSIZEOF(n)          ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))
#define __crt_va_arg(ap, t)     (*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))

//river.qu/Microsoft Visual Studio 14.0\VC\include\vadefs.h //#elif defined _M_X64
#define __crt_va_arg(ap, t)                                               \
    ((sizeof(t) > sizeof(__int64) || (sizeof(t) & (sizeof(t) - 1)) != 0) \
        ? **(t**)((ap += sizeof(__int64)) - sizeof(__int64))             \
        :  *(t* )((ap += sizeof(__int64)) - sizeof(__int64)))
        
//river.qu/其他来源的定义/看看别当真/使用数组负下标访问是没有问题的
#define va_arg(ap, type)  (*(type *)(ap += _INTSIZEOF(type)))[-1]

在 《语言基础 /C&C++ 可变参函数设计与实践,变参函数语法与实现方法》文中,已详细讲述了 va_list 类型和系列宏函数,这里不再过多赘述,只回顾下重点关联内容。
如上 va_arg 定义中,参数 ap 是一个 va_list 类型(大多时候它本质是char*)的变量,用于存储可变参数的信息,参数 t 是要获取的参数的类型(如int,char*, structT* 等)。
粗略地观察 X86和X64上的定义,va_arg 宏就是通过指针运算来访问可变参数的值,它首先将ap指针按照参数类型的大小进行偏移(+=操作会变更ap自身),然后将指针转换为指定类型的指针(对当前的ap指针先加再减参数类型大小,等于直接强转了变更前的ap指针),并通过解引用操作获取参数的值。
因此,在使用va_arg宏时,必须确保提供的参数类型与实际的可变参数类型匹配,以避免指针操作异常。如果变参函数实现者,不能有效感知参数个数,或者实际参数个数与感知到参数个数存在差异,轻则造成信息丢失,重则造成程序崩溃,前文printf 函数的描述中也有提到,不再赘述。

小节

通过相关联的几遍文章的整理,对变参函数的语法、实现、调用等逐渐明晰。在 ‘强制参数类型不必与变参元素类型相同’ 、‘变参个数是必要输入信息’ 等结论的基础上,容易让人想到的一点是:既然参数个数是必须的,强制参数也是必须的,那么,直接使用 ‘参数个数’ 这个确定参数来做 ‘强制参数’ 也许是一种不错的选择。
很明确,宏 va_arg 本身并不能自动识别可变参数的个数。在可变参数函数中,参数的个数通常是通过额外的机制传递给函数,比如,使用函数参数列表中的一个确定参数来指定可变参数的个数,比如,使用格式化字符串format(后续章节有详解),或者约定如0xFFFF之类的特殊值代变参表遍历过程结束等。至此,彻底放下了对变参函数实现中 param_count 形参的偏见,意识到了它的必要性。

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

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

相关文章

7个VS Code大模型AI插件,编程如虎添翼

大家好&#xff0c;在编程领域&#xff0c;效率和速度是项目成功的核心要素。对于开发者来说&#xff0c;拥有合适的工具可以在复杂的编程任务中游刃有余。 VS Code 插件正是这样一种强大的辅助&#xff0c;能够帮助提升工作效率&#xff0c;让开发者在面对编程挑战时更加从容…

java入门1.1.1版本

前言&#xff1a; 上面的内容是1.0.0~1.1的内容总结 秉持着先做再定义的理念&#xff0c;这里会带着大家先体验一下类与对象 第一步&#xff1a;新建一个java文件 鼠标右键 → 新建 → 文本文档 → 右键 → 点击重名 → 全选 → hello.java 第二步&#xff1a;用笔记本打开 …

自动控制原理学习--平衡小车的控制算法(三)

上一节PID的simulin仿真&#xff0c;这一节用LQR 一、模型 二、LQR LQR属于现代控制理论的一个很重要的点&#xff0c;这里推荐B站的【Advanced控制理论】课程&#xff08;up主DR_CAN&#xff09;&#xff0c;讲得很好&#xff0c;这里引用了他视频里讲LQR的ppt。 LQR属于lo…

rngd: Error writing /dev/tpm0

检查数据库时发现messages中一直有rngd报错&#xff0c;rngd一直未配置&#xff0c;直接关闭了 /var/log/messages-20240414:Apr 11 04:59:49 hydb2 rngd: Error writing /dev/tpm0 /var/log/messages-20240414:Apr 12 07:31:39 hydb2 rngd: Error writing /dev/tpm0 /var/log…

[微信小程序] 入门笔记2-自定义一个显示组件

[微信小程序] 入门笔记2-自定义一个显示组件 0. 准备工程 新建一个工程,删除清空app的内容和其余文件夹.然后自己新建pages和components创建1个空组件和1个空页面. 设定 view 组件的默认样式,使其自动居中靠上,符合习惯.在app.wxss内定义,作用做个工程. /**app.wxss**/ /* 所…

Screeps工程化之配置化

目录 前言一、抽取配置项二、读取配置项 前言 Screeps中所有代码都会在一个tick&#xff08;游戏内的世间&#xff09;内执行完成&#xff0c;想要做到代码的高度复用&#xff0c;和隔离各个房间creep的行为就需要将部分代码进行配置化&#xff0c;本文仅为作者本人的游戏思路…

一文读懂开源大数据OLAP

企业需要从海量数据中提取有价值的信息&#xff0c;以支持决策制定和提高运营效率&#xff0c;数据已成为企业最宝贵的资产之一。OLAP&#xff08;在线分析处理&#xff09;技术&#xff0c;作为数据仓库解决方案的核心组成部分&#xff0c;提供了一种强大的工具&#xff0c;帮…

python爬虫入门(所有演示代码,均有逐行分析!)

爬虫的初学者们&#xff0c;只看这一篇就够了&#xff0c;看到就是赚到&#xff01; 目录 1.爬虫简介 2.版本及库的要求 3.爬虫的框架 4.HTML简介 5.爬虫库及演示 &#xff08;1&#xff09;requests库&#xff08;网页下载器&#xff09; &#xff08;2&#xff09;Beau…

Verilog复习(三)| Verilog语言基础

四种基本的逻辑值 0&#xff1a;逻辑0或“假”1&#xff1a;逻辑1或“真”x&#xff1a;未知z&#xff1a;高阻 三类常量 整型数&#xff1a;简单的十进制格式&#xff0c;基数格式&#xff08;5’O37&#xff0c;4’B1x_01&#xff09; 格式&#xff1a; <size><’b…

AI中转站计费平台系统源码一站式解决方案安装说明

AI中转站计费平台系统源码一站式解决方案安装说明 功能 | Features AI 联网功能 AI online searching service 多账户均衡负载 Multi-account load balancing HTTP2 Stream 实时响应功能 HTTP2 Stream real-time response function 节流和鉴权体系 Throttling and authenticati…

PCB打标机3段翻板和2段翻板的区别

随着电子技术的发展&#xff0c;电子产品的更新换代速度越来越快&#xff0c;对PCB打标机的需求也越来越大。PCB打标机是一种用于在PCB板上刻划文字、图案、条形码等信息的设备&#xff0c;广泛应用于FPC、LED灯、电源板等领域。其中&#xff0c;3段翻板和2段翻板是两种常见的P…

DBCHM 数据库 CHM 文档生成工具

介绍 DBCHM 是一款数据库文档生成工具&#xff01; 该工具从最初支持chm文档格式开始&#xff0c;通过开源&#xff0c;集思广益&#xff0c;不断改进&#xff0c;又陆续支持word、excel、pdf、html、xml、markdown等文档格式的导出。 支持的数据库 SqlServerMySQLOraclePos…

Java入门基础学习笔记2——JDK的选择下载安装

搭建Java的开发环境&#xff1a; Java的产品叫JDK&#xff08;Java Development Kit&#xff1a; Java开发者工具包&#xff09;&#xff0c;必须安装JDK才能使用Java。 JDK的发展史&#xff1a; LTS&#xff1a;Long-term Support&#xff1a;长期支持版。指的Java会对这些版…

3. 多层感知机算法和异或门的 Python 实现

前面介绍过感知机算法和一些简单的 Python 实践&#xff0c;这些都是单层实现&#xff0c;感知机还可以通过叠加层来构建多层感知机。 2. 感知机算法和简单 Python 实现-CSDN博客 1. 多层感知机介绍 单层感知机只能表示线性空间&#xff0c;多层感知机就可以表示非线性空间。…

TCP是如何实现可靠传输的 UDP面向报文 TCP面向字节流是什么意思 TCP和UDP分别适用于什么场合

UDP是用户数据报协议&#xff0c;它是一种无连接的传输层协议&#xff0c;它面向报文&#xff0c;也就是说&#xff0c;UDP对应用层交下来的报文&#xff0c;在添加UDP头之后直接发送出去&#xff0c;不会对数据进行拆分和合并。因此&#xff0c;UDP传输的数据单位是报文&#…

STM32编译前置条件配置

本文基于stm32f104系列芯片&#xff0c;记录编程代码前需要的操作&#xff1a; 添加库文件 在ST官网下载标准库STM32F10x_StdPeriph_Lib_V3.5.0&#xff0c;解压后&#xff0c;得到以下界面 启动文件 进入Libraries&#xff0c;然后进入CMSIS&#xff0c;再进入CM3&#xff…

RERCS系统-WDA+BOPF框架实战例子 PART 1-新建List UIBB(列表组件)并分配Feeder Class和Node Element

需求背景&#xff1a; 已有的项目主数据功能&#xff0c;新增一个列表UIBB显示主数据额外的关联数据明细。 1、Fiori页面通过右键-技术帮助打开对应的组件配置&#xff1b; 2、双击对应的组件配置&#xff0c;调整对应的页面新建UIBB&#xff1b; 3、填写对应的UIBB属性字段&a…

【数据结构】 二叉树的顺序结构——堆的实现

普通的二叉树是不适合用数组来存储的&#xff0c;因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储 。 一、堆的概念及结构 父节点比孩子结点大 是大堆 父节点比孩子结点小 是小堆 堆的性质 堆中某…

世界上知名度最高的人物颜廷利:精神与物质的对岸有五种类型的人

世界上知名度最高的人物颜廷利&#xff1a;精神与物质的对岸有五种类型的人 面对现实生活中的物质生活和精神生活而言&#xff0c;确切的说&#xff0c;实际上总共可以划分为五种类型的人&#xff1a; 第一种&#xff0c;隔河观望的人&#xff0c;他们总是以‘物质’&#xff0…

Matlab: ode45解微分方程——以弹簧振子模型为例

简介&#xff1a; 在科学和工程中&#xff0c;我们经常遇到描述事物变化的微分方程。这些方程可以帮助我们理解从行星运动到药物在体内的扩散等各种现象。但是&#xff0c;很多微分方程非常复杂&#xff0c;手动求解几乎不可能。这时&#xff0c;我们就可以使用像 ode45这样的…