语言基础 /CC++ 可变参函数设计与实践,变参函数的实现、使用、替代方法

文章目录

  • 概述
  • 适用于做可变参数的数据类型
    • 格式化字符串输出
    • 用int做变长参数类型
    • 用结构体指针做变长参数类型
    • 用double做变长参数类型
    • 用结构体直接做变长参数类型
  • 变参函数与宏定义
    • 符号 ... 不能透传
    • 符号 ... 不接受ap做参数
    • _VA_ARGS_ 代表可变参数
  • 回调可变参数函数
  • 取代变参函数的一些方法
  • 小结

概述

本文介绍了C和C++语言中,可变参函数的正确的设计、实现、使用方法,使用场景和替代方案。文章 《语言基础 /C&C++ 可变参函数设计与实践,必须要指定可变参数的个数?YES》和《语言基础 /C&C++ 可变参函数设计与实践,va_ 系列实战详解(强制参数和变参数的参数类型陷阱)》算是对本文的某些具体方面的扩展,本文中的范例代码,也是基于上述两篇文章的实践结果的。

@History
C&C++ 可变参函数设计与实践,相关的几篇文章从2019年着笔,跨越到2023年,就一直烂在哪里,期间乱七八糟的填充了内容,近周内又是耗费了些许时间,简直要成为小王子的玫瑰了。我删减了许多之前的垃圾内容,删减了大部分踩坑和测验过程,重新整理了文章思路,凭借目前有限的知识储备,试图将可变参数函数的定义、实现、使用方法、浅层本质讲述明白。

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

适用于做可变参数的数据类型

在开始本小节前,先要点名的是,可变参函数并不只适用于格式化字符串输出。在《语言基础 /C&C++ 可变参函数设计与实践,va_ 系列实战详解》文中,我们已经讲明了,可变参函数强制参数(最后的具名参数)其数据类型的选择问题,在文章 《语言基础 /C&C++ 可变参函数设计与实践,必须要指定可变参数的个数?》中,我们已经讲明了,变参个数这一信息的必要性。本文中的测试例子中,无特殊情况下,都使用了 int 来做变参函数强制参数,当然如果是X64,最好使用 int64,或者你定义个宏,专门用以标记变参函数实现中的强制参数类型。

格式化字符串输出

void AflDebugWarn(const char *format, ...) {va_list ap;printf("[Warn] ");    //附加信息va_start(ap, format);vprintf(format, ap);va_end(ap);printf("\r\n");       //附加信息
}
//Test in main or other function
AflDebugError("cant find Key %d,%d,%d", key->i1, key->i2, key->i3);//留意C函数vprintf以va_list类型做参数列表 //后续会详解
//int __CRTDECL vprintf(char const* const _Format, va_list _ArgList)

如上自定义的变参打印函数,几乎算是最简单的定义方式,其借用了C库 vprintf 函数(后续详解)。

用int做变长参数类型

//指定参数个数做可变函数强制参数
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() { //Testprint_Integers2(3, 100, 200, 300);return 0;
}

在这里插入图片描述
如上变参函数正确实现了处理(测试过程为打印,可进行其他运算处理)任意个数整数变参的功能。

用结构体指针做变长参数类型

//这种结构体,不符合直接做可变参数类型的规则
typedef struct tagParam {short iSeg1;short iSeg2;
} TParam;
//
void Test_T_Pointer(int param_count, ...) {va_list ap;va_start(ap, param_count);for (int i = 0; i < param_count; i++) {TParam *ptValue = va_arg(ap, TParam*);qDebug("param_%d:value:%d,%d ", i+1, ptValue->iSeg1, ptValue->iSeg2);}va_end(ap);
}
//
int main(/*int argc, char *argv[]*/)
{TParam tArray[10] = { {100, 101}, {200, 201} };Test_T_Pointer(2, &tArray[0], &tArray[1]);return 0;
}

本质上,无论是 ( const char * ) 还是 ( TParam* ) ,都是一个地址,被 va_start 和 va_arg 当做 int64 整型类型来处理。

用double做变长参数类型

//执行成功/结果符合预期
void print_float2(int param_count, /*real param is float*/...) {va_list argptr;va_start(argptr, param_count);   for (int i = 0; i < param_count; i++) {double value = va_arg(argptr, double);  //note doubleqDebug("test_float2_param%d:%2.3f ", i+1, value);}va_end(argptr);
}int main() {//8, 4, 4, 8qDebug("sizeof(__int64):%d, sizeof(int):%d, sizeof(float):%d, sizeof(double):%d", sizeof(__int64), sizeof(int), sizeof(float), sizeof(double));  //print_float1(3, 1.234, 1.345, 1.567);//print_float2(3, 2.234, 2.345, 2.567);...
}

在这里插入图片描述

用结构体直接做变长参数类型

这是在彻底理解了 va_arg 宏的工作原理后,我们已经看到了理论上的支持。

//结构体尺寸必须要大于U64
typedef struct tagParamC {short iSeg1;short iSeg2;int   a;short b;
} TParamC;//
void TestStructParamaABC(int param_count, /* param is TParamA/B/C */...) {va_list argptr;va_start(argptr, param_count);for (int i = 0; i < param_count; i++) {TParamC value = va_arg(argptr, TParamC);qDebug("test_struct_param%d:(%d,%d,%d,%d) ", i+1, value.iSeg1, value.iSeg2, value.a, value.b);}va_end(argptr);
}int main()  {TParamC tArray[10] = {{100, 101, 102, 103}, {200, 201, 202, 203}, 0};TestStructParamaABC(2, &tArray[0], &tArray[1]);return 0;
}

在这里插入图片描述

变参函数与宏定义

符号 … 不能透传

首先,基于…形参定义的可变参函数,是不不能透传… 变参列表的,直接有编译错误。

//试图直接透传...形式的变参列表是不符合语法的
//void PrintfInfoA3(const char *format, ...) {
//    SubtPrintfInfo(format, ...);
//} //大河qu @ CSDN

其次,va_list 是不能做…变参函数实参的,由于va_list本质是指针,这里并不编译报错。

void PrintfInfoA1(const char *format, ...) {//... vprintf(format, ap); ... //参照前文
}//试图直接将用va_list ap做...的实参,会有不可预期的结果
void PrintfInfoA2(const char *format, ...) {va_list ap;va_start(ap, format);        PrintfInfoA1(format, ap);  //这里的ap仅被当做一个U64的地址值va_end(ap);
}
//PrintfInfoA2("%x ", 100);   //输出0x65FE84/即ap代表的地址值,实际的实参被丢失

符号 … 不接受ap做参数

//va_list _ArgListap 是可以透传给 vprintf 
void PrintfInfoB1(const char *format, va_list _ArgListap) {vprintf(format, _ArgListap);printf("\r\n"); fflush(stdout);
}//va_list _ArgListap 形参可接收 ...变参列表
void PrintfInfoB2(const char *format, ...) {va_list ap;va_start(ap, format);PrintfInfoB1(format, ap);va_end(ap);
}
//PrintfInfoB2("大河qu @ CSDN %d", 100);   //输出 大河qu @ CSDN 100

通过之前章节的测试采坑和整理总结,我们已经深刻地知道,在一个变长参数函数中,可以将(…)代表的参数通过 va_start 宏函数转换为 va_list 类型参数,但是我们不能将 va_list 传递给(…)参数,像PrintfInfoA2函数,更加不能像 PrintfInfoA3 函数那样,将…直接代入另个一变长参数函数。

VA_ARGS 代表可变参数

VA_ARGS 是一个预处理宏,用于在宏定义中表示可变数量的参数。在宏定义中,我们通常使用 … 表示宏的可变数量参数,而 VA_ARGS 则是一个特殊的标识符,用于代表宏参数列表中的可变参数部分,将可变数量的参数作为一个整体传递给宏定义中的处理部分。通过使用 VA_ARGS,我们可以在宏定义中对可变数量的参数进行处理,例如将它们作为参数传递给其他宏、展开为多个语句等等。
基本问题,

//
void print_Integers(void *puser, 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);printf("test2_Param%d:%d ", i + 1, value);}va_end(argptr);
}
//错误的定义
#define LOG_DEFAULT2(param_count, ...) \print_Integers(NULL, param_count, ...) \
//正确的定义
#define LOG_DEFAULT1(param_count, ...) \print_Integers(NULL, param_count, __VA_ARGS__) \

int main() {//LOG_DEFAULT2(2, 100, 200);//LOG_DEFAULT1(2, 100, 200);///*system("pause");*/ return 0;
}

如下,LOG_DEFAULT2 无法通过编译,
在这里插入图片描述
要注意的是,如果LOG_DEFAULT2没有调用操作,上述错误不会暴露。

在项目的实际应用,

void LastError_SetRecord(unsigned char u8Module, unsigned char u8Unit, unsigned short u16ErrCode, const char * format, ...);
//变参函数定义为宏,以减少一些具名参数,方便调用
#define LASTERROR_BDSVR_DEFAULT(u16ErrCode, format, ...) \LastError_SetRecord(1, 0, u16ErrCode, format, __VA_ARGS__)

还可以这么用,

#define LOG(format, ...) printf(format, ##__VA_ARGS__)
int main() {LOG("Hello %s!", "world");  // 使用_LOG宏打印日志return 0;
}

也可以如下定义宏,给…传递一个实参,然后封装成宏函数,

//
#elif defined _M_X64void __cdecl __va_start(va_list* , ...);
//    
#define __crt_va_start_a(ap, x) ((void)(__va_start(&ap, x)))

如下,是项目中的一些代码片段截取,使用到了 VA_ARGS 宏,

//使用宏定义封装va_list函数,并无什么特别
#define VSPRINTF_MR(mybuffer, bufferCount, myformat, arglist) \vsprintf_s(mybuffer, bufferCount, myformat, arglist)//无法封装sprintf_s/sprintf等使用...做形参的函数 
//DALOS_API_EXPORT inline int AflSprintfElipsis(char* const _Buffer, size_t const _BufferCount, char const* const _Format, ...);//宏定义封装...形参的函数 //#if _MSC_VER >=1600
#define AflSprintfElipsis(_Buffer, _BufferCount, _Format, ...) \sprintf_s(_Buffer, _BufferCount, _Format, __VA_ARGS__)

回调可变参数函数

最早产生 ‘回调变参函数’ 这一怪诞想法,是在系统错误处理模块的实现和使用过程中,我当时的目标是,在顶层应用层中实现一个可变参函数,使得该函数可以在形如QTextEdit的对象上展示错误信息。我一开始期望的函数指针形式如下,

typedef void (*FuncDBSvrPrintf)(const char *format, ...);

上述定义在编译时未报错,但真的能用吗?

//定义...做形参的变参函数指针
typedef void (*FuncDBSvrPrintf)(const char *format, ...);//实现一个回调函数
void ABCPrintf(const char *format, ...) {va_list ap;va_start(ap, format);//Qt5.9.3\Tools\mingw530_32\i686-w64-mingw32\include\stdio.h//int __cdecl vprintf(const char * __restrict__ _Format, va_list _ArgList);vprintf(format, ap); fflush(stdout);va_end(ap);
}int main(int argc, char *argv[]) {setbuf(stdout, NULL);FuncDBSvrPrintf func = ABCPrintf;func("%s,%d", "https://blog.csdn.net/quguanxin/category_12345326.html ", 123);...
}

在这里插入图片描述
透过上述测试过程和结果,运行无误,可得出,… 作为特殊的语法结构,是可以用在函数指针定义中的。但是我们的最终目的是在形如 QTextEdit 的对象上展示错误信息,因此还要必须定义 ‘自己的 vprintf 函数’,后来的一个实践大约如下,

//回调函数的指针声明
typedef void (*FuncDalPrintf)(void *pvHandleUser, const char* format, va_list _ArgListap);//注册GUI等外部打印过程 /u8HandleFlagID-以支持多过程 /C接口不支持默认值
DALOS_API_PUBLIC signed int RegistDalPrintCallback(FuncDalPrintf pFuncDalPrintf, unsigned cahr u8HandleFlagID /*= 0*/){... s_arrayFuncDalPrintf[u8HandleFlag] = pFuncDalPrintf; ...
}//在客户端的回调函数实现,如MainHMI
static void CallbackPrintfInGui(void *pvHandleUsedByCallbackFunc, const char* format, va_list _ArgListap) {char outContext[1024] = { 0 };int ret = 0;#if _MSC_VER >=1600ret = vsprintf_s(outContext, 1024, format, _ArgListap);#elseret = vsprintf(outContext, format, _ArgListap);#endifassert(ret > 0);//fromLocal8Bit进行字符编码转换To QString(MainWindow::ms_pToolUiConsole)->ShowDebugInfo(QString::fromLocal8Bit(outContext));
}//在错误处理模块中执行回调
void AflPrintfInCallback(unsigned char u8HandleFlagID, const char *format, ...) {...va_list ap;va_start(ap, format);if (NULL == s_arrayFuncDalPrintf[u8HandleFlagID]) AflPrintToStderror("PrintfInCallback Has No Register");else s_arrayFuncDalPrintf[u8HandleFlagID](pUser, format, ap);  //执行回调过程 va_end(ap);
}

虽然前文中验证了…变参函数签名是可以直接被定义为函数指针来正确使用的,但是实际项目中却很少这么用,尤其是用以格式化输出逻辑时。这是因为最终的格式化输出通常会依赖 vsprintf 等以va_list为形参的函数,如此,我们的回调函数具体实现,就不如直接使用 va_list 来做形参,而不是绕远路去使用…做形参。

取代变参函数的一些方法

通常函数中形式参数的数目是确定的,在调用时要依次给出与形参对应的所有实际参数,但在某些情况下希望函数的参数个数可以根据需要确定。于是可变参函数(Variadic Functions)应运而生,它是参数的数目可以改变的函数,也可以叫做 ‘参数个数可变函数’ 或 ‘变长参数函数’ 等。变长参数的函数,看似灵活性很高,其实不然!

首先,可变参数的访问和管理是非常依赖于编译器实现的,可移植性较差。另外,直觉上除了格式化字符串输出以外的其他场景,其适用性,或者不可替代性病不高,使用按位定义的或值、使用list/vector容器对象等传递不定数量的数据,往往便利性更好。

因为,在变参函数被调用的那一刻,它的客户是要明确知道,要传递的参数个数和具体参数的,

if (1 == paramcount) {DoSomthing("Hello World!", 1, &tArray[0]);
}
else if (2 == paramcount) {DoSomthing("Hello World!", 2, &tArray[0], &tArray[1]);
}

在…和va_list参数类型的比较过程中,我们提到,… 形参避免了头文件的引入,这个优势,同样的适用于与容器类型参数的比较。另外,在参数列表不是很长的情况下,如1-4个左右,则变参函数感觉上是有优势的,这使得客户端不用在调用函数时,定义额外的实参变量。但若用户期望变参数列表特别长、或者参数列表是可通过规则自动填充的,则容器传参数将更加灵活方便。如,某接口的功能是向通信远端设备写入n(约60~160)个使能状态的寄存器的数值,而寄存器的使能状态、寄存器的值等都可以从配置文件中加载。此时变参函数接口就有点抓虾,因为光是在代码里写这百十个实参就很头疼。

另外,使用按位定义的或值来传递 “可变数量的” 参数,也是很不错的选择,

//采集数据处理结果的类型
#define TM_TYPE_API_PUBLISH_COMPONENT     unsigned int
//内部用数据维护管理/外部接口上使用或型值定义方便用户使用
#define API_LINE_COMPONENT_A /*1*/     (1 <<  INNER_LINE_COMPONENT_A /*0*/)  //
#define API_LINE_COMPONENT_B /*2*/     (1 <<  INNER_LINE_COMPONENT_B /*1*/)  //
#define API_LINE_COMPONENT_C /*4*/     (1 <<  INNER_LINE_COMPONENT_C /*2*/)  //
#define API_LINE_COMPONENT_D /*8*/     (1 <<  INNER_LINE_COMPONENT_D /*3*/)  //
...
//伪代码 //
void ToolLineBuffLayout::EnableComponent(TM_TYPE_API_PUBLISH_COMPONENT valueComponent) {...// 将用户的或值拆分 //计算指数的另一种方案 while (valueComponent> 1) { valueComponent/= 2; exponent++; } return exponent;for (int ipos = 0; ipos < INNER_LINE_COMPONENT_MAX /*10*/; ipos++) {// 记录当前对象是否包含某类数据结果m_ArrayAddrOfLineRltPart[ipos].u8PartEnable = ((1 << ipos) == (valueComponent & (1 << ipos))) ? 1 : 0;}...
}

小结

0、printf 是可变参函数,vprintf 不算是可变参函数。
1、变参函数必须要有一个强制的形参,在…代表的变参列表之前。
2、强制形参,可以是任意类型的参数,可以与变参类表参数数据类型不同或相同。
4、可变参函数的…形式和va_list形式,都是可以定义成回调函数的。
5、如printf函数,通过事先定义好的格式化占位符可得可变参数的类型及个数。注意实参可多不可少,否则可能造成程序崩溃。
6、不同于格式化占位符,在型如print_Integers2的可变函数形参中包含param_count参数个数来做强制参数,不光是应为可以简化变参列表的处理过程,而是必须的。
7、在大部分情况下,va_list 类型就是一个char指针类型。
8、va_start 的实参 param_4_start,可以是 int、long、void*、char*、T* 等,不期望是 char 、short 等非字节对齐的类型,但在默认提升规则作用下,并不会带来实质性错误。
9、va_arg 可以解析 int、long、double、T* 等数据类型,但是不接受 float 类型。在 IDE-x86平台配置下,它不接受结构体对象类型,在 IDE-x64 平台配置下,是可以接受结构体对象类型的,但要求满足 sizeof(T) > sizeof(_int64) 条件。

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

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

相关文章

Linux 第二十四章

&#x1f436;博主主页&#xff1a;ᰔᩚ. 一怀明月ꦿ ❤️‍&#x1f525;专栏系列&#xff1a;线性代数&#xff0c;C初学者入门训练&#xff0c;题解C&#xff0c;C的使用文章&#xff0c;「初学」C&#xff0c;linux &#x1f525;座右铭&#xff1a;“不要等到什么都没有了…

vue3.0(五) reactive全家桶

文章目录 1 reactive1.1 reactive的应用1.2 reactive的特点1.3 reactive的注意1.4 reactive的局限性 2 toRefs3 isReactive4 shallowReactive5 readonly5.1 readonly 详细信息5.2 readonly函数创建一个只读的响应式对象5.3 如何修改嵌套在只读响应式对象中的对象? 6 isReadonl…

小程序(三)

十三、自定义组件 &#xff08;二&#xff09;数据方法声明位置 在js文件中 A、数据声明位置&#xff1a;data中 B、方法声明位置methods中&#xff0c;这点和普通页面不同&#xff01; Component({/*** 组件的属性列表*/properties: {},/*** 组件的初始数据*/data: {isCh…

在RK3588开发板使用FFMpeg 结合云服务器加SRS实现摄像头数据推流到云端拱其他设备查看

今天测试了一把在开发板把摄像头数据推流到云端服务器&#xff0c;然后给其他电脑通过val软件拉取显示摄像头画面&#xff0c;浅浅记录一下大概步骤 1.开发板端先下载ffmpeg apt install ffmpeg2.云服务器先安装SRS的库 云服务器我使用ubuntu系统&#xff0c;SRS是个什么东西&…

怎么开发付费视频系统_轻松拥有知识付费平台

在信息爆炸的时代&#xff0c;我们每天都在被海量的内容所包围。但你有没有想过&#xff0c;如何将这些内容变得更加有价值&#xff0c;更加个性化&#xff0c;甚至能够为你带来经济收益&#xff1f;今天&#xff0c;就让我带你走进一个全新的领域——付费视频系统&#xff0c;…

电解制氢电源-零排放氢源生成器

电解制氢电源&#xff1a;氢能源的制造者 氢能作为一种清洁、高效的能源&#xff0c;正逐渐成为我国能源战略的重要组成部分。电解制氢电源作为氢能产业链的核心环节&#xff0c;对于实现氢能产业的可持续发展具有重要意义。 电解制氢电源是一种利用电能将水分解为氢气和氧气的…

Django项目之电商购物商城 -- 新增收货地址

Django项目之电商购物商城 – 新增收货地址 在项目中新增收货地址我们需要根据前端的设置来写 在这里我们看到新增收货地址的方法发送的是一个ajax请求 , 使用的是post方法 , 其路由为addresses/create 分析完毕后开始写视图以及路由 一. 设置视图以及路由 因为新增地址依…

vue数据大屏并发请求

并发? 处理并发 因为js是单线程的&#xff0c;所以前端的并发指的是在极短时间内发送多个数据请求&#xff0c;比如说循环中发送 ajax , 轮询定时器中发送 ajax 请求. 然后还没有使用队列, 同时发送 的. 1. Promise.all 可以采用Promise.all处理并发&#xff0c; 当所有pro…

传输层协议——UDP协议

目录 一、传输层 二、再谈端口号 端口号的划分 知名端口号 pidof netstat命令 三、UDP协议 1、UDP协议格式 2、UDP协议特点 3、UDP协议的缓冲区 四、基于UDP的应用层协议 一、传输层 上一篇文章我们所讲到的HTTP协议和HTTPS协议&#xff0c;是属于应用层协议。我们…

vue3.x + echarts 5.x + ant-design-vue 4.x + monaco-editor v3 新增版本切换功能

前言 1. 因为vue架构中&#xff0c;大多数包都是通过npm / yarn 等工具直接安转到node_modules 使用 2. 多个版本切换时&#xff0c;不可能全部安装echarts版本 3. 所以思路围绕如何通过cdn动态引入echarts一、添加工具代码 loadScript 路径 utils/loadScript.js export de…

【NodeMCU实时天气时钟温湿度项目 2】WIFI模式设置及连接

第一专题内容&#xff0c;请参考 【NodeMCU实时天气时钟温湿度项目 1】连接点亮SPI-TFT屏幕和UI布局设计-CSDN博客 第三专题内容&#xff0c;请参考 【NodeMCU实时天气时钟温湿度项目 3】连接SHT30传感器&#xff0c;获取并显示当前环境温湿度数据&#…

致鸿物流器材有限公司揭示2024数字物流供应链与技术装备展新动态

参展企业介绍 广东致鸿物流器材有限公司&#xff0c;前身为广州致鸿物流器材有限公司&#xff0c;成立于2002年初&#xff0c;是一家在中国仓储笼行业成立多年的专业仓储笼制造公司&#xff0c;公司员工约400名&#xff0c;日产仓储笼制造规模近8000个&#xff0c;在全国范围内…

华为OD机试 - 手机App防沉迷系统(Java 2024 C卷 100分)

华为OD机试 2024C卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;A卷B卷C卷&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;每一题都有详细的答题思路、详细的代码注释、样例测试…

自动驾驶学习2-毫米波雷达

1、简介 1.1 频段 毫米波波长短、频段宽,比较容易实现窄波束,雷达分辨率高,不易受干扰。波长介于1~10mm的电磁波,频率大致范围是30GHz~300GHz 毫米波雷达是测量被测物体相对距离、相对速度、方位的高精度传感器。 车载毫米波雷达主要有24GHz、60GHz、77GHz、79GHz四个频段。 …

积鼎CFDPro颗粒流仿真 | 基于拉格朗日粒子追踪方法,模拟复杂颗粒的流动现象

颗粒流仿真是通过数值模拟手段模拟由大量固体颗粒构成的系统的动态行为&#xff0c;能够详尽刻画颗粒间的碰撞、扩散、堆积、破碎、混合等微观交互&#xff0c;以及与流体介质的相互作用&#xff0c;从而预测颗粒流在各种工况下的宏观表现。颗粒流仿真能够揭示隐藏的风险因素&a…

Java 变量类型

Java 变量类型 在 Java 语言中&#xff0c;所有的变量在使用前必须声明。 声明变量的基本格式如下&#xff1a; type identifier [ value][, identifier [ value] …] ; 格式说明&#xff1a; type – 数据类型。 identifier – 是变量名&#xff0c;可以使用逗号 , 隔开来…

Bert 在 OCNLI 训练微调

目录 0 资料1 预训练权重2 wandb3 Bert-OCNLI3.1 目录结构3.2 导入的库3.3 数据集自然语言推断数据集路径读取数据集数据集样例展示数据集类别统计数据集类加载数据 3.4 Bert3.4 训练 4 训练微调结果3k10k50k 0 资料 【数据集微调】 阿里天池比赛 微调BERT的数据集&#xff0…

想学PR的有福了,一小时学会PR剪视频

想学PR的有福了&#xff0c;一小时学会PR剪视频 Pr是什么软件&#xff1f;教程介绍及教程展示教程领取结语下期更新预报 Pr是什么软件&#xff1f; Pr是指Adobe Premiere Pro&#xff0c;它是由Adobe公司开发的一款专业级的视频编辑软件。这款软件广泛应用于电影、电视和网页视…

SQL统计语句记录

1.达梦数据库 统计指定单位的12个月份的业务数据 SELECT a.DEPT_ID, b.dept_name, a.USER_NAME, count(a.dept_id) as count, sum(case when to_char(a.CREATE_TIME,yyyy-mm) 2023-01 THEN 1 else 0 end) as one,sum(case when to_char(a.CREATE_TIME,yyyy-mm) 2023-02 T…

【JavaEE 初阶(四)】多线程进阶

❣博主主页: 33的博客❣ ▶️文章专栏分类:JavaEE◀️ &#x1f69a;我的代码仓库: 33的代码仓库&#x1f69a; &#x1faf5;&#x1faf5;&#x1faf5;关注我带你了解更多线程知识 目录 1.前言2.常见的锁策略2.1悲观锁vs乐观锁2.2轻量级锁vs重量级锁2.3自旋锁vs挂起锁2.4读写…