语言基础 /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() { //Test
    print_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 double
        qDebug("test_float2_param%d:%2.3f ", i+1, value);
    }
    va_end(argptr);
}

int main() {
	//8, 4, 4, 8
    qDebug("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_X64
    void __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 >=1600
	    ret = vsprintf_s(outContext, 1024, format, _ArgListap);
	#else
	    ret = vsprintf(outContext, format, _ArgListap);
	#endif
    assert(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.mfbz.cn/a/610632.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【数据库原理及应用】期末复习汇总高校期末真题试卷05

试卷 一、选择题 1.( )是存储在计算机内有结构的数据的集合。 A.数据库系统 B.数据库 C.数据库管理系统 D.数据结构 2.数据库的三级模式结构中&#xff0c;数据库对象—视图是( ) A.外模式 B.内模式 C.存储模式 D.模式 3.在下列关于关系表的陈述中&#xff0c;错误的是(…

Zabbix监控中文乱码问题解决方法

一、问题描述 1.查看Zabbix仪表盘 在Zabbix的监控仪表盘界面&#xff0c;字体显示为“方框”&#xff0c;无法查看到具体的性能指标名称。 2.问题分析 Zabbix的web端没有中文字库&#xff0c;导致切换到中文页面&#xff0c;中文成了乱码这个问题&#xff0c;我们最需要把中文…

使用LlamaIndex构建能对文档进行推理;大模型自动执行基于浏览器的工作流;ElevenLabs宣布进军音乐创作领域

✨ 1: Building Agentic RAG with LlamaIndex 由Jerry Liu教授、专注于使用LlamaIndex构建能对文档进行推理和回答复杂问题的代理研究型RAG的新课程 我很高兴向大家介绍“使用&#xff08;RAG&#xff09;与Llamalndex构建主动性研究助理代理”的课程&#xff0c;这是由Llama…

XSS-Labs 靶场通过解析(下)

前言 XSS-Labs靶场是一个专门用于学习和练习跨站脚本攻击&#xff08;XSS&#xff09;技术的在线平台。它提供了一系列的实验场景和演示&#xff0c;帮助安全研究人员、开发人员和安全爱好者深入了解XSS攻击的原理和防御方法。 XSS-Labs靶场的主要特点和功能包括&#xff1a;…

关联系统-整车控制器VCU

整车驱动原理 如上图所示&#xff0c;电池组输出直流电给DC/AC逆变器&#xff0c;逆变器将直流电转化为交流电输入给电机&#xff0c;电机在电磁力的作用下转动&#xff0c;通过传动机构将驱动力输送到车轮&#xff0c;其中整车控制器VCU可以根据用户油门/刹车的输入控制输出功…

国际化业务、全球化团队沟通难?浅析跨文化沟通的挑战和应对措施

在全球化背景下&#xff0c;发展出海业务相比以往更具有巨大的前景和潜力&#xff0c;是企业寻找“第二增长点”和提升综合实力的优先选择。近几年“中企出海”大热&#xff0c;中国企业在世界各地开展业务拓展国际市场&#xff0c;获得更加国际化的营商经验与客户资源。与此同…

【数据结构】单链表和双链表

文章目录 一、链表的概念及结构二、链表的分类三、无头单向非循环链表1.单链表创建2.尾插和头插3.尾删和头删4.打印5.查找6.插入7.删除8.销毁 四、带头双向循环链表1.双链表的创建2.初始化3.判断链表是否为空4.尾插和头插5.尾删和头删6.查找7.插入8.删除9.销毁 五、总结链表和顺…

《窄门》安德烈·纪德

究竟会不会有这样一种爱情&#xff0c;即使毫无希望&#xff0c;一个人也可以将它长久地保持在心中&#xff1b;即使生活每天吹它&#xff0c;也始终无法把它吹灭……&#xff1f; 在《窄门》中&#xff0c;纪德将爱情中的神秘主义体验推向极致&#xff0c;为我们讲述了一段纯…

C语言(指针)3

Hi~&#xff01;这里是奋斗的小羊&#xff0c;很荣幸各位能阅读我的文章&#xff0c;诚请评论指点&#xff0c;关注收藏&#xff0c;欢迎欢迎~~ &#x1f4a5;个人主页&#xff1a;小羊在奋斗 &#x1f4a5;所属专栏&#xff1a;C语言 本系列文章为个人学习笔记&#x…

YOLOv9改进策略 | 添加注意力篇 | 利用YOLO-Face提出的SEAM注意力机制优化物体遮挡检测(附代码 + 修改教程)

一、本文介绍 本文给大家带来的改进机制是由YOLO-Face提出能够改善物体遮挡检测的注意力机制SEAM&#xff0c;SEAM&#xff08;Spatially Enhanced Attention Module&#xff09;注意力网络模块旨在补偿被遮挡面部的响应损失&#xff0c;通过增强未遮挡面部的响应来实现这一目…

链表第4/9题--翻转链表--双指针法

LeetCode206&#xff1a;给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[5,4,3,2,1]示例 2&#xff1a; 输入&#xff1a;head [1,2] 输出&#xff1a;[2,1]示例…

鸿蒙OpenHarmony开发板解析:【特性配置规则】

特性 特性配置规则 下面介绍feature的声明、定义以及使用方法。 feature的声明 开发前请熟悉鸿蒙开发指导文档&#xff1a;gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md点击或者复制转到。 在部件的bundle.json文件中通过feature_list来声明部件的feature列…

生信技能45 - 基于docker容器运行生信软件

1. 获取docker镜像 以运行xhmm CNV分析软件为例。 # 搜索仓库镜像 sudo docker search xhmm# 拉取镜像 sudo docker pull ksarathbabu/xhmm_v1.0# 启动镜像,非后台 sudo docker run -it ksarathbabu/xhmm_v1.0 /bin/bash # -i: 交互式操作。 # -t: 终端。 # ksarathbabu/xhmm…

爆爽,英语小白怒刷 50 课!像玩游戏一样学习英语~

重点!!!(先看这) 清楚自己学英语的目的, 先搞清楚目标&#xff0c;再行动自身现在最需要的东西&#xff1a;词汇量&#xff1f;口语&#xff1f;还是阅读能力&#xff1f;找对应的书籍,学习资料往兴趣靠拢&#xff1a;网上有大量的推荐美剧学习、小说学习&#xff0c;不要被他…

机器学习算法应用——K近邻分类器(KNN)

K近邻分类器&#xff08;KNN&#xff09;&#xff08;4-2&#xff09; K近邻分类器&#xff08;K-Nearest Neighbor&#xff0c;简称KNN&#xff09;是一种基本的机器学习分类算法。它的工作原理是&#xff1a;在特征空间中&#xff0c;如果一个样本在特征空间中的K个最相邻的样…

【一刷《剑指Offer》】面试题 17:合并两个排序的链表

力扣对应题目链接&#xff1a;21. 合并两个有序链表 - 力扣&#xff08;LeetCode&#xff09; 核心考点&#xff1a;链表合并。 一、《剑指Offer》内容 二、分析题目 这道题的解题思路有很多&#xff1a; 可以一个一个节点的归并。可以采用递归完成。 三、代码 1、易于理解的…

Linux-基础命令第三天

1、命令&#xff1a;wc 作用&#xff1a;统计行数、单词数、字符数 格式&#xff1a;wc 选项 文件名 例&#xff1a; 统计文件中的行数、单词数、字符数 说明&#xff1a;59代表行数&#xff0c;111代表单词数&#xff0c;2713代表字符数&#xff0c;a.txt代表文件名 选项…

c语言查找字符串中指定字符串的个数

目录 一、测试思路二、方式1三、方式2 一、测试思路 使用C语言来查找一个字符串中指定数量的子字符串&#xff0c;使用 strncmp 函数或者 memcmp 函数&#xff0c;遍历主字符串并计数子字符串出现的次数。或者使用 strstr 函数&#xff0c; strstr 函数是 C 语言标准库 <str…

Java 集合-List

集合主要分为两组(单列集合, 双列集合) Connection 接口有两个重要的子接口LIst 和 Set, 它们的实现子类都是单列集合, Map 接口的实现子类是双列集合, 存放的是 K-V Connection 接口 Collection 接口和常用方法 下面以 ArrayList 演示一下 add: 添加单个元素remove: 删除指…

基于GIS地理技术+智慧巡检解决方案(Word原件)

传统的巡检采取人工记录的方式&#xff0c;该工作模式在生产中存在很大弊端&#xff0c;可能造成巡检不到位、操作失误、观察不仔细、历史问题难以追溯等现象&#xff0c;使得巡检数据不准确&#xff0c;设备故障隐患得不到及时发现和处理。因此建立一套完善的巡检管理系统是企…
最新文章