深入理解可变参数

目录

1.C语言方式

1.1.宏介绍

1.2.原理详解

1.3.案例分析

1.4.其他实例

2.C++之std::initializer_list

2.1.简介

2.2.原理详解

2.3.案例分析

3.C++之可变参数模版

3.1.简介

3.2.可变参数个数

3.3.递归包展开

3.4.逗号表达式展开

3.5.Lambda 捕获

3.6.转发参数包

4.总结


1.C语言方式

1.1.宏介绍

C语言中的可变参数是指函数可以接受可变数量的参数。这些参数的数量在编译时是未知的。在这些可变参数中的参数类型可以相同,也可以不同;可变参数的每个参数并没有实际的名称与之相对应,用起来是很灵活;在头文件stdarg.h中,涉及到的宏有:
va_list :   是指向参数的指针 ,通过指针运算来调整访问的对象
va_start :获取可变参数列表的第一个参数的地址
va_arg : 获取可变参数的当前参数,返回指定类型并将指针指向下一参数
va_end : 清空va_list可变参数列表

1.2.原理详解

函数的参数是存放在栈中,地址是连续的,所以可以通过相对位置去访问,这也是可变参数的访问方式;变长参数的实现需要依赖于C语言默认的cdecl调用惯例的自右向左压栈传递方式;可变参数是由1.1介绍的几个宏来实现,但是由于硬件平台的不同,编译器的不同,宏的定义也不相同,下面是AMD CPU x64平台下的定义:

typedef char* va_list;

va_list的定义

//[1]
#ifdef __cplusplus
#define _ADDRESSOF(v) (&reinterpret_cast<const char &>(v))
#else
#define _ADDRESSOF(v) (&(v))
#endif

//[2]
#define va_start _crt_va_start
#define va_arg   _crt_va_arg
#define va_end   _crt_va_end
#define va_copy(destination, source) ((destination) = (source))

//[3]
#define _PTRSIZEOF(n) ((sizeof(n) + sizeof(void*) - 1) & ~(sizeof(void*) - 1))//系统内存对齐
#define _ISSTRUCT(t)  ((sizeof(t) > sizeof(void*)) || (sizeof(t) & (sizeof(t) - 1)) != 0)
#define _crt_va_start(v,l)	((v) = (va_list)_ADDRESSOF(l) + _PTRSIZEOF(l))
#define _crt_va_arg(v,t)	_ISSTRUCT(t) ?						\
				 (**(t**)(((v) += sizeof(void*)) - sizeof(void*))) :	\
				 ( *(t *)(((v) += sizeof(void*)) - sizeof(void*)))
#define _crt_va_end(v)		((v) = (va_list)0)
#define _crt_va_copy(d,s)	((d) = (s))

从上面的源码可以看出:
1) va_list  v; 定义一个指向char类型的指针v。
2) va_start(v,l) ;执行 v = (va_list)&l + _PTRSIZEOF(l) ,v指向参数 l 之后的那个参数的地址,即 v指向第一个可变参数在堆栈的地址。
3) va_arg(v,t) , ( (t )((v += _PTRSIZEOF(t)) - _PTRSIZEOF(t)) ) 取出当前v指针所指的值,并使 v 指向下一个参数。 v+=sizeof(t类型) ,让v指向下一个参数的地址。然后返回 v - sizeof(t类型) 的t类型指针,这正是第一个可变参数在堆栈里的地址。然后 用取得这个地址的内容。
va_end(v) ; 清空 va_list v。

1.3.案例分析

#include <iostream>
#include <stdarg.h>

void printValues(const char* format, ...) {
	va_list args;  // 定义一个va_list类型的变量
	va_start(args, format);  // 初始化args

	for (const char* arg = format; *arg != '\0'; ++arg) {
		if (*arg == '%') {
			++arg;
			switch (*arg) {
			case 'd':  // 对于整数
				std::cout << va_arg(args, int);
				break;
			case 's':  // 对于字符串
				std::cout << va_arg(args, char*);
				break;
			default:
				std::cout << "Invalid format specifier: " << *arg;
			}
		}
		else {
			std::cout << *arg;
		}
	}
	va_end(args);  // 清理va_list变量
}

int main() {
	printValues("say self info: %s, age %d\n", "xiao", 45);  //输出: say self info xiao, age 45
	return 0;
}

printValues函数调用的时候展开为:

void printValues(const char* format, const char* param1, int param2)

从上面的代码来分析一下这个示例:在windows中,栈由高地址往低地址生长,调用printValues函数时,其参数入栈情况如下:

当调用va_start(args, format)时:args指针指向情况对应下图:

        当调用va_arg(args, ...)时,它必须返回一个由va_list所指向的恰当的类型的数值,同时递增args,使它指向参数列表中的一个参数(即递增的大小等于与va_arg宏所返回的数值具有相同类型的对象的长度)。因为类型转换的结果不能作为赋值运算的目标,所以va_arg宏首先使用sizeof来确定需要递增的大小,然后把它直接加到va_list上,这样得到的指针再被转换为要求的类型。

        在上面的示例中,我们定义了一个名为printValues的函数,它接受一个格式字符串和一个可变数量的参数。我们使用va_list、va_start、va_arg和va_end这些宏来处理可变参数。在格式字符串中,我们使用%来指定参数的类型,例如%d表示整数,%s表示字符串。然后,我们使用va_arg宏来获取相应的参数值。最后,我们使用va_end宏来清理va_list变量。

1.4.其他实例

1) printf实现

#include <stdarg.h>

int printf(char *format, ...)
{
    va_list ap;
    int n;
     
    va_start(ap, format);
    n = vprintf(format, ap);
    va_end(ap);
    return n;    
}

2)定制错误打印函数error

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

void error(char *format, ...)
{
    va_list ap;
    va_start(ap, format);
    fprintf(stderr, "Error: ");
    vfprintf(stderr, format, ap);
    va_end(ap);
    fprintf(stderr, "\n");
    return;    
}

2.C++之std::initializer_list

        在C++中我们一般用()和=初始化参数或对象,还可以用{}来初始化参数或对象,比如数组的初始化int m[] = {1,4,5},除了数组,在STL里面很多标准的容器和自定义类型都用{} 进行初始化。

        自C++11标准开始就引入了列表初始化的概念,即支持使用{}对变量或对象进行初始化,且与传统的变量初始化的规则一样,也分为拷贝初始化和直接初始化两种方式。

2.1.简介

std::initializer_list<T> 类型对象是一个访问 const T 类型对象数组的轻量代理对象。
std::initializer_list 对象在这些时候自动构造:
1)用花括号初始化器列表列表初始化一个对象,其中对应构造函数接受一个 std::initializer_list 参数,如std::vector的构造函数  vector(initializer_list<_Ty> _Ilist, const _Alloc& _Al = _Alloc())
2)以花括号初始化器列表为赋值的右运算数,或函数调用参数,而对应的赋值运算符/函数接受 std::initializer_list 参数
3)绑定花括号初始化器列表到 auto ,包括在范围 for 循环中

initializer_list 可由一对指针或指针与其长度实现。复制一个 std::initializer_list 不会复制其底层对象。

注意:

a、底层数组不保证在原始 initializer_list 对象的生存期结束后继续存在。 std::initializer_list 的存储是未指定的(即它可以是自动、临时或静态只读内存,依赖场合)。

b、底层数组是 const T[N] 类型的临时数组,其中每个元素都从原始初始化器列表的对应元素复制初始化(除非窄化转换非法)。底层数组的生存期与任何其他临时对象相同,除了从数组初始化 initializer_list 对象会延长数组的生存期,恰如绑定引用到临时量(有例外,例如对于初始化非静态类成员)。底层数组可以分配在只读内存。

c、若声明了 std::initializer_list 的显式或偏特化则程序为谬构。

2.2.原理详解

源码面前无秘密,直接上源码:

template <class _Elem>
class initializer_list {
public:
    using value_type      = _Elem;
    using reference       = const _Elem&;
    using const_reference = const _Elem&;
    using size_type       = size_t;

    using iterator       = const _Elem*;
    using const_iterator = const _Elem*;

    constexpr initializer_list() noexcept : _First(nullptr), _Last(nullptr) {}  //1

    constexpr initializer_list(const _Elem* _First_arg, const _Elem* _Last_arg) noexcept
        : _First(_First_arg), _Last(_Last_arg) {}                               //2

    _NODISCARD constexpr const _Elem* begin() const noexcept {
        return _First;
    }

    _NODISCARD constexpr const _Elem* end() const noexcept {
        return _Last;
    }

    _NODISCARD constexpr size_t size() const noexcept {
        return static_cast<size_t>(_Last - _First);
    }

private:
    const _Elem* _First;
    const _Elem* _Last;
};

// FUNCTION TEMPLATE begin
template <class _Elem>
_NODISCARD constexpr const _Elem* begin(initializer_list<_Elem> _Ilist) noexcept {
    return _Ilist.begin();
}

// FUNCTION TEMPLATE end
template <class _Elem>
_NODISCARD constexpr const _Elem* end(initializer_list<_Elem> _Ilist) noexcept {
    return _Ilist.end();
}

        从上面的STL的std::initializer_list源码来看,std::initializer_list是一个模版类,定义了指向该类对象首端、尾端的迭代器(即常量对象指针const T*),实际上就是对{}表达式内容的简单封装,当使用{}时,就会调用 initializer_list(const _Elem* _First_arg, const _Elem* _Last_arg) 构造出std::initializer_list。

        当得到了一个std::initializer_list对象后,再来寻找标准容器中以std::initializer_list为形参的构造函数,并调用该构造函数对容器进行初始化

2.3.案例分析

示例1:

class IMessageField1 {};

//1
void  addMessageField(std::initializer_list<IMessageField1*> t)
{
	std::vector<IMessageField1*>  pTest(t);
}

#if  0
//2
void  addMessageField(std::vector<IMessageField1*> t)
{
	std::vector<IMessageField1*>  pTest(t);
}
#endif

void  main()
{
	//[1]
	std::unique_ptr<IMessageField1> a(new IMessageField1);
	std::unique_ptr<IMessageField1> b(new IMessageField1);
	std::unique_ptr<IMessageField1> c(new IMessageField1);
	std::unique_ptr<IMessageField1> d(new IMessageField1);
	std::unique_ptr<IMessageField1> e(new IMessageField1);
	addMessageField({ a.get(), b.get(), c.get(), d.get(), e.get() });
}

   上面代码1和2的方式都可以实现功能,2的方式实际上也是先临时生成一个std::initializer_list,再调用std::vector的构造函数临时生成一个std::vector,最后再用刚生成的std::vector初始化pTest,相比1的方式,多了几重复制,效率比较低,一般采用1的方式实现功能。

示例2:

#include <iostream>
#include <vector>
#include <initializer_list>
 
template <class T>
struct S {
    std::vector<T> v;
    S(std::initializer_list<T> l) : v(l) {
         std::cout << "constructed with a " << l.size() << "-element list\n";
    }
    void append(std::initializer_list<T> l) {
        v.insert(v.end(), l.begin(), l.end());
    }
    std::pair<const T*, std::size_t> c_arr() const {
        return {&v[0], v.size()};  // 在 return 语句中复制列表初始化
                                   // 这不使用 std::initializer_list
    }
};
 
template <typename T>
void templated_fn(T) {}
 
int main()
{
	int a1[] = { 1,2,3,4,5,6 }; //数组拷贝初始化
	int a2[]{ 5,6,7,8,9,0 };   //数组直接初始化

    S<int> s = {1, 2, 3, 4, 5}; // 复制初始化
    s.append({6, 7, 8});      // 函数调用中的列表初始化
 
    std::cout << "The vector size is now " << s.c_arr().second << " ints:\n";
 
    for (auto n : s.v)
        std::cout << n << ' ';
    std::cout << '\n';
 
    std::cout << "Range-for over brace-init-list: \n";
 
    for (int x : {-1, -2, -3}) // auto 的规则令此带范围 for 工作
        std::cout << x << ' ';
    std::cout << '\n';
 
    auto al = {10, 11, 12};   // auto 的特殊规则
 
    std::cout << "The list bound to auto has size() = " << al.size() << '\n';
 
//    templated_fn({1, 2, 3}); // 编译错误!“ {1, 2, 3} ”不是表达式,
                             // 它无类型,故 T 无法推导
    templated_fn<std::initializer_list<int>>({1, 2, 3}); // OK
    templated_fn<std::vector<int>>({1, 2, 3});           // 也 OK
}

输出:

constructed with a 5-element list
The vector size is now 8 ints:
1 2 3 4 5 6 7 8
Range-for over brace-init-list: 
-1 -2 -3 
The list bound to auto has size() = 3

示例3:

struct MyTest{
	explicit  X(int a, int b) :a(a), b(b) { std::cout << "MyTest(int a,int b)\n"; }

	int a{};
	int b{};
};

int main() {
	MyTest x{ 1,2 }; //OK
	MyTest x2( 1,2 ); //OK
	MyTest x3 = { 1,2 }; //Error
}

MyTest x3 ={1,2}; 参考复制初始化的规则:复制列表初始化(考虑 explicit 和非 explicit 构造函数,但只能调用非 explicit 构造函数)

3.C++之可变参数模版

3.1.简介

一个可变参数模板就是一个可以接受可变参数的模版函数或模板类;参数的类型是一种模板,是可经推导的,可以是任意存在的类型(系统类型或自定义类型);参数数目可变的,可以包括零个、一个或多个;可变数目的参数被称为参数包(parameter packet)。存在两种参数包:模板参数包(template parameter packet),表示零个或多个模板参数;函数参数包function parameterpacket),表示零个或多个函数参数。如:

template<typename... Arguments> class vtclass;

vtclass< > vtinstance1;
vtclass<int> vtinstance2;
vtclass<float, bool> vtinstance3;
vtclass<long, std::vector<int>, std::string> vtinstance4;

3.2.可变参数个数

利用sizeof...()计算可变参数的大小,如:

template<class... Types>
struct count
{
    static const std::size_t value = sizeof...(Types);
};

3.3.递归包展开

C++的包展开是通过 args... 的形式,后面... 就意味着展开包,需要两个函数:递归终止函数 和 递归函数过程就是参数包在展开的过程中递归调用自己,每调用一次参数包中的参数就会少一个,直到所有的参数都展开为止,当没有参数时,则调用递归终止函数终止递归过程。如下:

#include <iostream>

using namespace std;

void print() {
    cout << endl;
}

template <typename T> 
void print(const T& t) {  //边界条件
    cout << t << endl;
}

template <typename First, typename... Rest> 
void print(const First& first, const Rest&... rest) {
    cout << first << ", ";
    print(rest...); //打印剩余参数,注意省略号必须有
}

int main()
{
    print(); // calls first overload, outputting only a newline
    print(1); // calls second overload

    // these call the third overload, the variadic template,
    // which uses recursion as needed.
    print(10, 20);   //输出: 10, 20
    print(100, 200, 300); //输出:100, 200, 300
    print("first", 2, "third", 3.14159); //输出: first, 2, third, 3.14159
}

3.4.逗号表达式展开

逗号表达式是会从左到右依次计算各个表达式,并将最后一个表达式的值作为返回值返回;我们将最后一个表达式设为整型值,所以最后返回的是一个整型;将处理参数个数的动作封装成一个函数,将该函数作为逗号表达式的第一个表达式;…代表参数包,列表展开。如:

template <class T>
void printArg(T t) {
	cout << t << endl;
}

//展开参数包
template <class ...Args>
void expand(Args... args) {
	int arr[] = { (printArg(args), 0)... };
}
int main()
{
	expand(1);
	expand(1, 'A');
	expand(1, "hello", 3);
	return 0;
}

函数执行expand(1, "hello", 3);的时候,调用expand,数组arr初始化会展开args参数,变化为:

int arr[] = {(printArg(1), 0), (printArg("hello"), 0), (printArg(3), 0)};

根据逗号表达式的规则,arr[] 还是 {0,0,0};

另外,还可以利用std::initializer_list 可以接收任意长度的初始化列表来展开包,如:

template<class F, class... Args>
void expand(const F& f, Args&&...args) {
	std::initializer_list<int>{(f(std::forward< Args>(args)), 0)...};
}

int main()
{
    expand([](int i) { cout << i << endl; }, 23, 44, 2423);
    return 0;
}

3.5.Lambda 捕获

包展开可以在 lambda 表达式的捕获子句中出现:

template<class... Args>
void f(Args... args)
{
    auto lm = [&, args...] { return g(args...); };
    lm();
}

3.6.转发参数包

在C++11标准下,我们可以组合使用可变参数模板与std::forword机制来编写函数,实现将其实参不变地传递给其他函数,关于std::forward的详解讲解,可参考我的博客:C++之std::forward_c++ std::forward-CSDN博客

借助std::forward<Args>(args)... 就可以实现参数的完美转发了,如STL中map的插入函数emplace下:

template <class... _Valty>
iterator emplace(_Valty&&... _Val)
{
    return _Mybase::emplace(_STD forward<_Valty>(_Val)...).first;
}

4.总结

纸上得来终觉浅,绝知此事要躬行。

参考

形参包 (C++11 起) - cppreference.com

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

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

相关文章

LN和BN

假设batch为2&#xff0c;&#xff08;2&#xff0c;3&#xff0c;256&#xff0c;256&#xff09;这样的样本 LN比较直观就是在每个独立的样本上计算均值和方差&#xff0c;然后归一化。&#xff08;2&#xff0c;3&#xff0c;256&#xff0c;256&#xff09; 归一化是将数…

十、基本对话框大集合(Qt5 GUI系列)

目录 一、设计需求 二、实现代码 三、代码解析 四、总结 一、设计需求 Qt提供了很多标准的对话框。例如标准文件对话框(QFileDialog)、标准颜色对话框(QColorDialog)、标准字体对话框 (QFontDialog)、标准输入对话框 (QInputDialog) 及消息对话框 (QMessageBox)。本文展示各…

JVM之内存模型带参数

Spring Boot程序的JVM参数设置格式(Tomcat启动直接加在bin目录下catalina.sh文件里)&#xff1a; java ‐Xms2048M ‐Xmx2048M ‐Xmn1024M ‐Xss512K ‐XX:MetaspaceSize256M ‐XX:MaxMetaspaceSize256M ‐jar xxxxxx.jar-Xss&#xff1a;每个线程的栈大小 -Xms&#xff1a;设置…

http 503 错误

503错误是一种HTTP状态码&#xff0c;表示你请求的网站或服务暂时不可用&#xff0c;通常是因为服务器过载或维护&#xff0c;你可能会看到类似这样的提示&#xff1a;503 Service Unavailable、503 Service Temporarily Unavailable、HTTP Server Error 503、HTTP Error 503 I…

数据分析求职-简历准备

简历在整个求职过程中的重要性不言而喻&#xff0c;今天咱们来聊求职过程中简历准备的那些事儿~ 1. 简历究竟有啥用 求职的流程简单说就是&#xff1a;网申->笔试->面试->offer 其中网申环节&#xff0c;简历100%决定了你的通过与否&#xff0c;这个点大家都知道。…

计算机基础面试题 |09.精选计算机基础面试题

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

谈谈我眼中的用户分层分析!

大家好&#xff0c;我是阿粥。 用户分层是几乎所有品牌都会思考的一个重要命题。 合理的分层能够帮助品牌有效提升用户的忠诚度。 这篇文章&#xff0c;我会带大家熟悉用户分层的基本概念、价值以及分层分析的思路。内容略干&#xff0c;在看的时候可以喝两口水。 同样&#xf…

css-、串联选择器和后代选择器的用法

& &表示嵌套的上一级&#xff0c;这是sass的语法&#xff0c;代表上一级选择器 .btn {&.primary {background-color: #007bff;color: #fff;} } 编译出来的结果是同一个元素&#xff0c;有两个类名&#xff0c;两个类名之间没有空格&#xff1a; .btn.primary {…

分布式【zookeeper面试题23连问】

1. ZooKeeper是什么&#xff1f; ZooKeeper是一个分布式的&#xff0c;开放源码的分布式应用程序协调服务&#xff0c;是Google的Chubby一个开源的实现&#xff0c;它是集群的管理者&#xff0c;监视着集群中各个节点的状态根据节点提交的反馈进行下一步合理操作。最终&#x…

花了一小时,拿python手搓了一个考研背单词软件

听说没有好用的电脑端背单词软件&#xff1f;只好麻烦一下&#xff0c;花了一小时&#xff0c;拿python手搓了一个考研背单词软件。 代码已经开源在我的github上&#xff0c;欢迎大家STAR&#xff01; 其中&#xff0c;数据是存放在sqlite中&#xff0c;形近词跳转是根据jaro …

开源游戏引擎:创造无限可能 | 开源专题 No.56

godotengine/godot Stars: 62.6k License: MIT Godot Engine 是一个功能强大的跨平台游戏引擎&#xff0c;可用于创建 2D 和 3D 游戏。它提供了一套全面的常见工具&#xff0c;让用户可以专注于制作游戏而不必重复造轮子。该引擎支持将游戏一键导出到多个平台上&#xff0c;包…

阿里云服务器Valheim端口2456、2457和2458放行设置

使用阿里云服务器搭建Valheim英灵神殿需要开启2456-2458端口&#xff0c;阿里云服务器默认只开放了22核3389端口&#xff0c;开通2456端口是在安全组中配置的&#xff0c;阿里云服务器网aliyunfuwuqi.com来详细说下阿里云服务器安全组开通端口流程&#xff1a; 阿里云服务器安…

超简单|配图详细| 双系统Ubuntu22.04 系统磁盘扩容

文章目录 1. 打开磁盘工具2. 格式化空闲分区3. 挂载该分区4. 数据迁移与备份5. 卸载原分区6. 挂载新的/home分区7. 重启系统8. 删除原来的数据8.1 直接格式化分区8.2 没有单独的/home分区1. 查看设备名2. 重新挂载该分区3. 删除原始分区/home目录中的内容4. 卸载原分区5. 重启 …

项目初始化脚手架搭建

项目初始化脚手架搭建 仓库地址 easy-web: 一个快速初始化SpringBoot项目的脚手架 (gitee.com) 目前这个项目还是个单体项目&#xff0c;后续笔者有时间可能会改造成父子工程项目&#xff0c;将通用模块抽象出来&#xff0c;有兴趣的小伙伴也可以自行 CV 改造。 1、项目初始化…

嵌入式-stm32-基于HAL库的感应开关盖垃圾桶项目(开源)

嵌入式-stm32-感应开关盖垃圾桶项目&#xff08;开源&#xff09; 网盘资料 《嵌入式-stm32-基于HAL库的感应开关盖垃圾桶项目&#xff08;开源&#xff09;》 目录 一&#xff1a;项目概述 二&#xff1a;材料准备 三&#xff1a;细节分析&#xff08;重点&#xff09; 四&…

有详细一些的考研数学真题解析吗?

考研数学真题解析可以写得很详细&#xff0c;但是纸质资料可能受限于篇幅与排版等原因&#xff0c;没有把过程写得很详细。 但是&#xff0c;如果解析步骤不够详细的话&#xff0c;可能读者在看的时候就会因为其中某一个被省略的步骤而“卡壳”&#xff0c;进而需要花费很多额…

CAAC无人机操作证考证报名流程及白底证件照片制作方法

在这个无人机技术日新月异的时代&#xff0c;拥有一张CAAC民用无人机操作证不仅意味着你能够合法地在天空翱翔&#xff0c;也象征着你对飞行技术的尊重和对规章制度的遵守。如果你怀揣着成为无人机飞行员的梦想&#xff0c;那么&#xff0c;让我们一起揭开CAAC民用无人机操作证…

9.java——(杂例)组合,代理,向上转型static,fianl,关键字(有道云笔记复制粘贴,大家整体性的把握)

组合——内部有类&#xff08;心中有对象&#xff01;&#xff01;&#xff01;&#xff09;&#xff08;足球 和足球运动员梅西和脚下的足球一样&#xff09; has和is的区别&#xff0c;has是组合&#xff0c;是有&#xff0c;持有的意思&#xff1b;is是继承&#xff0c;是…

Vue开发中使用Element UI过程中遇到的问题及解决方案Missing required prop: “value”

一、vue中使用el-table的typeindex有时不显示序号 Table 表格 用于展示多条结构类似的数据&#xff0c;可对数据进行排序、筛选、对比或其他自定义操作。 当el-table元素中注入data对象数组后&#xff0c;在el-table-column中用prop属性来对应对象中的键名即可填入数据&…

类加载器的分类

类加载器的任务就是把 java字节码文件内容 加载到 JVM 中去 分类 类加载器一般分为两类&#xff1a; 启动类加载器&#xff1a; 这个类加载器使用C实现 是虚拟机自身的一部分&#xff0c;启动类加载器加载的内容 包括java.lang.Object、java.lang.String等在内的Java核心类库。…