C++进阶--C++11新的类功能和可变参数模板

C++11新的类功能和可变参数模板

  • 一、默认成员函数
  • 二、类成员变量初始化
  • 三、强制生成默认函数的关键字default
  • 四、禁止生成默认函数的关键字delete
  • 五、继承和多态中final与override关键字
  • 六、可变参数模板的概念
  • 七、可变参数模板的定义方式
  • 八、参数包的展开方式
    • 8.1 递归展开参数包
    • 8.2 逗号表达式展开参数包
  • 九、STL容器中的emplace相关接口函数
    • 9.1 emplace版本的插入接口
    • 9.2 emplace系列接口的使用方式
    • 9.3 emplace系列接口的工作流程
    • 9.4 emplace系列接口的意义

一、默认成员函数

   在C++11之前,有6个默认成员函数:

1.构造函数
2.析构函数
3.拷贝构造函数
4.拷贝赋值重载
5.取地址重载
6.const取地址重载
   最重要的是前面4个,后面两个用户不大。默认成员函数就是我们不写编译器会生成一个默认的。
   C++11新增了两个:移动构造函数和移动赋值运算符重载。
针对移动构造函数和移动赋值运算符重载有一些需要注意的点如下:

  • 如果你没有自己实现移动构造函数,且没有实现析构函数、拷贝构造、拷贝赋值重载中的任意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。
  • 如果你没有自己实现移动赋值重载函数,且没有实现析构函数、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造完全类似)
  • 如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。

   要验证默认生成的移动构造和移动赋值,需要模拟实现一个简化版的string类,类当中只编写了几个我们需要用到的成员函数。

namespace zl
{class string{public:typedef char* iterator;iterator begin(){return _str;}iterator end(){return _str + _size;}string(const char* str = ""):_size(strlen(str)), _capacity(_size){//cout << "string(char* str)" << endl;_str = new char[_capacity + 1];strcpy(_str, str);}// s1.swap(s2)void swap(string& s){::swap(_str, s._str);::swap(_size, s._size);::swap(_capacity, s._capacity);}// 拷贝构造string(const string& s):_str(nullptr){cout << "string(const string& s) -- 深拷贝" << endl;string tmp(s._str);swap(tmp);}//移动构造string(string&& s):_str(nullptr){cout << "string(string&& s) -- 移动构造" << endl;swap(s);}// 赋值重载string& operator=(const string& s){cout << "string& operator=(string s) -- 深拷贝" << endl;string tmp(s);swap(tmp);return *this;}// 移动构造/*string(string&& s):_str(nullptr), _size(0), _capacity(0){cout << "string(string&& s) -- 移动语义" << endl;swap(s);}*/// 移动赋值//s1=将亡值string& operator=(string&& s){cout << "string& operator=(string&& s) -- 移动拷贝" << endl;swap(s);return *this;}~string(){cout << "~string() -- 析构函数" << endl;delete[] _str;_str = nullptr;}char& operator[](size_t pos){assert(pos < _size);return _str[pos];}void reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}void push_back(char ch){if (_size >= _capacity){size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;reserve(newcapacity);}_str[_size] = ch;++_size;_str[_size] = '\0';}//string operator+=(char ch)string& operator+=(char ch){push_back(ch);return *this;}string operator+(char ch){string tmp(*this);tmp += ch;return tmp;}const char* c_str() const{return _str;}private:char* _str;size_t _size;size_t _capacity; // 不包含最后做标识的\0};
}

   然后再编写一个简单的Person类,Person类中的成员name的类型就是我们模拟实现的string类。

class Person
{
public:Person(const char* name = "", int age = 0):_name(name), _age(age){}Person(const char* name):Person(name,18){}private:zl::string _name;   //自定义类型int _age;           //内置类型
};

   虽然Person类当中没有实现移动构造和移动赋值,但拷贝构造、拷贝赋值和析构函数Person类都实现了,因此Person类中不会生成默认的移动构造和移动赋值。

int main()
{Person s1("张三",18);Person s2 = s1;Person s3 = std::move(s1);Person s4;s4 = std::move(s2);return 0;
}

   上述代码中用一个右值去构造s3对象,但由于Person类没有生成默认的移动构造函数,因此这里会调用Person的拷贝构造函数(拷贝构造既能接收左值也能接收右值),这时在Person的拷贝构造函数中就会调用string的拷贝构造函数对name成员进行深拷贝。
   如果要让Person类生成默认的移动构造函数,就必须将Person类中的拷贝构造、拷贝赋值和析构函数全部注释掉,这时用右值去构造s3对象时就会调用Person默认生成的移动构造函数。

  • Person默认生成的移动构造,对于内置类型成员_age会进行值拷贝,而对于自定义类型成员name,因为我们的string类实现了移动构造函数,因此它会调用string的移动构造函数进行资源的转移。
  • 而如果我们将是string类当中的移动构造函数注释掉,那么Person默认生成的移动构造函数,就会调用string类中的拷贝构造函数对name成员进行深拷贝。

说明

  • 我们在模拟实现的string类的拷贝构造、拷贝赋值、移动构造和移动赋值函数中都打印了一条提示语句,因此可以通过控制台输出判断是否调用了对应的函数。
  • 由于VS2013没有完全支持C++11,因此上述代码无法在VS2013当中验证,需要使用更新一点的编译器进行验证,比如VS2019。

二、类成员变量初始化

   默认生成的构造函数,对于自定义类型的成员会调用其构造函数进行初始化,但并不会对内置类型的成员进行处理。于是C++11支持非静态成员变量在声明时进行初始化赋值,默认生成的构造函数会使用这些缺省值对成员进行初始化。

class Person
{
public://...
private://非静态成员变量,可以在成员声明时给缺省值zl::string _name = "张三"; //姓名int _age = 20;             //年龄static int _n; //静态成员变量不能给缺省值
};

三、强制生成默认函数的关键字default

   C++11可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以使用default关键字显示指定移动构造生成。

class Person
{
public:Person(const char* name = "", int age = 0):_name(name), _age(age){}Person(const Person& p):_name(p._name),_age(p._age){}Person(Person&& p) = default;private:bit::string _name;int _age;
};int main()
{Person s1;Person s2 = s1;Person s3 = std::move(s1);return 0;
}

四、禁止生成默认函数的关键字delete

   如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且只声明不定义,这样只要其他人想要调用就会报错。
   在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。

class Person
{
public:Person(const char* name = "", int age = 0):_name(name), _age(age){}Person(const Person& p) = delete;private:bit::string _name;int _age;
};int main()
{Person s1;Person s2 = s1;Person s3 = std::move(s1);return 0;
}

五、继承和多态中final与override关键字

final修饰类

   被final修饰的类叫做最终类,最终类无法被继承。

class NonInherit final //被final修饰,该类不能再被继承
{//...
};

final修饰虚函数

   final修饰虚函数,表示该虚函数不能再被重写,如果子类继承后重写了该虚函数则编译报错。

//父类
class Person
{
public:virtual void Print() final //被final修饰,该虚函数不能再被重写{cout << "hello Person" << endl;}
};
//子类
class Student : public Person
{
public:virtual void Print() //重写,编译报错{cout << "hello Student" << endl;}
};

override修饰虚函数

   override修饰子类的虚函数,检查子类是否重写了父类的某个虚函数,如果没有重写则编译报错。

//父类
class Person
{
public:virtual void Print(){cout << "hello Person" << endl;}
};
//子类
class Student : public Person
{
public:virtual void Print() override //检查子类是否重写了父类的某个虚函数{cout << "hello Student" << endl;}
};

六、可变参数模板的概念

   可变参数模板是C++11新增的最强大的特性之一,它对参数高度泛化,能够让我们创建可以接受可变参数的函数模板和类模板。

  • 在C++11之前,类模板和函数模板中只能包含固定数量的模板参数,可变模板参数无疑是一个巨大的改进,但由于可变参数模板比较抽象,因此使用起来需要一定的技巧。
  • 在C++11之前其实也有可变参数的概念,比如printf函数就能够接收任意多个参数,但这是函数参数的可变参数,并不是模板的可变参数。

七、可变参数模板的定义方式

// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
template <class ...Args>
void ShowList(Args... args)
{}

说明

  • 模板参数Args前面有省略号,代表它是一个可变模板参数,我们把带省略号的参数称为参数包,参数包里面可以包含0到N(N≥0)个模板参数,而args则是一个函数形参参数包。
  • 模板参数包Args和函数形参参数包args的名字可以任意指定,并不是说必须叫做Args和args。

   现在调用ShowList函数时就可以传入任意多个参数了,并且这些参数可以是不同类型的。

int main()
{ShowList();ShowList(1);ShowList(1, 'A');ShowList(1, 'A',std::string("sort"));return 0;
}

   我们可以在函数模板中通过sizeof计算参数包中参数的个数。

template<class ...Args>
void ShowList(Args... args)
{cout << sizeof...(args) << endl; //获取参数包中参数的个数
}

   但是我们无法直接获取参数包中的每个参数,只能通过展开参数包的方式来获取,这是使用可变参数模板的一个主要特点,也是最大的难点。

特别注意,语法并不支持使用args[i]的方式来获取参数包中的参数

template<class ...Args>
void ShowList(Args... args)
{//错误示例:for (int i = 0; i < sizeof...(args); i++){cout << args[i] << " "; //打印参数包中的每个参数}cout << endl;
}

   因此要获取参数包中的各个参数,只能通过展开参数包的方式来获取,一般我们会通过递归或者逗号表达式来展开参数包。

八、参数包的展开方式

8.1 递归展开参数包

  • 给函数模板增加一个模板参数,这样就可以从接收到的参数包中分离出一个参数出来。
  • 在函数模板中递归调用该函数模板,调用时传入剩下的参数包。
  • 如此递归下去,每次分离出参数包中的一个参数,直到参数包中的所有参数都被取出来。

无参的递归终止函数

//递归终止函数
void ShowListArg()
{cout << endl;
}
//展开函数
template<class T, class ...Args>
void ShowListArg(T value, Args... args)
{cout << value << " "; //打印传入的若干参数中的第一个参数ShowListArg(args...); //将剩下参数继续向下传
}
//供外部调用的函数
template<class ...Args>
void ShowList(Args... args)
{ShowListArg(args...);
}

   这样一来,当递归调用ShowList函数模板时,如果传入的参数包中参数的个数为0,那么就会匹配到这个无参的递归终止函数,这样就结束了递归。

  • 但如果外部调用ShowList函数时就没有传入参数,那么就会直接匹配到无参的递归终止函数。
  • 而我们本意是想让外部调用ShowList函数时匹配的都是函数模板,并不是让外部调用时直接匹配到这个递归终止函数。
  • 最后通过编写外部调用函数,这是无论外部调用时传入多少个参数,最终匹配到的都是同一个函数了。

带参的递归终止函数

//递归终止函数
template<class T>
void ShowListArg(const T& t)
{cout << t << endl;
}
//展开函数
template<class T, class ...Args>
void ShowListArg(T value, Args... args)
{cout << value << " "; //打印传入的若干参数中的第一个参数ShowList(args...);    //将剩下参数继续向下传
}
//供外部调用的函数
template<class ...Args>
void ShowList(Args... args)
{ShowListArg(args...);
}

   这样一来,在递归调用过程中,如果传入的参数包中参数的个数为1,那么就会匹配到这个递归终止函数,这样也就结束了递归。但是需要注意,这里的递归调用函数需要写成函数模板,因为我们并不知道最后一个参数是什么类型的。
   但该方法有一个弊端就是,我们在调用ShowList函数时必须至少传入一个参数,否则就会报错。因为此时无论是调用递归终止函数还是展开函数,都需要至少传入一个函数。

8.2 逗号表达式展开参数包

   虽然我们不能用不同类型的参数取初始化一个整型数组,但我们可以借助逗号表达式。

  • 逗号表达式会从左到右依次计算各个表达式,并且将最后一个表达式的值作为返回值返回。
  • 将逗号表达式的最后一个表达式设置为一个整型值,确保逗号表达式返回的是一个整型值。
  • 将处理参数包中参数的动作封装成一个函数,将该函数的调用作为逗号表达式的第一个表达式。

这样一来,在执行逗号表达式时就会先调用处理函数处理对应的参数,然后再将逗号表达式中的最后一个整型值作为返回值来初始化整型数组。

//处理参数包中的每个参数
template<class T>
void PrintArg(const T& t)
{cout << t << " ";
}
//展开函数
template<class ...Args>
void ShowList(Args... args)
{int arr[] = { (PrintArg(args), 0)... }; //列表初始化+逗号表达式cout << endl;
}

说明

  • 我们这里要做的就是打印参数包中的各个参数,因此处理函数当中要做的就是将传入的参数进行打印即可。
  • 可变参数的省略号需要加在逗号表达式外面,表示需要将逗号表达式展开,如果省略号加在args的后面,那么参数包将会被展开后全部传入PrintArg函数,代码中的{ (PrintArg(args), 0)… }将会被展开成{ (PrintArg(arg1), 0), (PrintArg(arg2), 0), etc…}

这时调用ShowList函数时就可以传入多个不同类型的参数了,但调用时仍然不能传入0个参数,因为数组的大小不能为0,如果想要支持传入0个参数,也可以写一个无参的ShowList函数。

//支持无参调用
void ShowList()
{cout << endl;
}
//处理函数
template<class T>
void PrintArg(const T& t)
{cout << t << " ";
}
//展开函数
template<class ...Args>
void ShowList(Args... args)
{int arr[] = { (PrintArg(args), 0)... }; //列表初始化+逗号表达式cout << endl;
}

   实际上我们也可以不同逗号表达式,因为这里的问题就是初始化整型数组时必须用整型,那我们可以将处理函数的返回值设置为整型,然后用这个返回值去初始化整型数组也是可以的。

//支持无参调用
void ShowList()
{cout << endl;
}
//处理函数
template<class T>
int PrintArg(const T& t)
{cout << t << " ";return 0;
}
//展开函数
template<class ...Args>
void ShowList(Args... args)
{int arr[] = { PrintArg(args)... }; //列表初始化cout << endl;
}

九、STL容器中的emplace相关接口函数

9.1 emplace版本的插入接口

   C++11标准给STL中的容器增加emplace版本的插入接口,比如list容器的push_front、push_back和insert函数,都增加了对应的emplace_front、emplace_back和emplace函数。
   这些emplace版本的插入接口支持模板的可变参数,比如list容器的emplace_back函数的声明。

注意:emplace系列接口的可变模板参数类型都带有“&&”,这个表示的是万能引用,而不是右值引用。

9.2 emplace系列接口的使用方式

   emplace系列接口的使用方式与容器原有的插入接口的使用方式类似,但又有一些不同之处。
   以list容器的emplace_back和push_back为例;

  • 调用push_back函数插入元素时,可以传入左值对象或者右值对象,也可以使用列表进行初始化。
  • 调用emplace_back函数插入元素时,也可以传入左值对象或者右值对象,但不可以使用列表进行初始化。
  • 除此之外,emplace系列接口最大的特点就是,插入元素时可以传入用于构造元素的参数包。
int main()
{list<pair<int, string>> mylist;pair<int, string> kv(10, "111");mylist.push_back(kv);                              //传左值mylist.push_back(pair<int, string>(20, "222"));    //传右值mylist.push_back({ 30, "333" });                   //列表初始化mylist.emplace_back(kv);                           //传左值mylist.emplace_back(pair<int, string>(40, "444")); //传右值mylist.emplace_back(50, "555");                    //传参数包return 0;
}

9.3 emplace系列接口的工作流程

1.先通过空间配置器为新结点获取一块内存空间,注意这里只会开辟空间,不会自动调用构造函数对这块空间进行初始化。
2.然后调用allocator_traits::construct函数对这块空间进行初始化,调用该函数时会传入这块空间的地址和用户传入的参数(需要经过完美转发)。
3.在allocator_traits::construct函数中会使用定位new表达式,显示调用构造函数对这块空间进行初始化,调用构造函数时会传入用户传入的参数(需要经过完美转发)。
4.将初始化好的新结点插入到对应的数据结构当中,比如list容器就是将新结点插入到底层的双链表中。

9.4 emplace系列接口的意义

   由于emplace系列接口的可变模板参数的类型都是万能引用,因此既可以接收左值对象,也可以接收右值对象,还可以接收参数包。

1.如果调用emplace系列接口时传入的是左值对象,那么首先需要先在此之前调用构造函数实例化出一个左值对象,最终在使用定位new表达式调用构造函数对空间进行初始化时,会匹配到拷贝构造函数。
2.如果调用emplace系列接口时传入的是右值对象,那么就需要在此之前调用构造函数实例化出一个右值对象,最终在使用定位new表达式调用构造函数对空间进行初始化时,就会匹配到移动构造函数。
3.如果调用emplace系列接口时传入的是参数包,那就可以直接调用函数进行插入,并且最终在使用定位new表达式调用构造函数对空间进行初始化时,匹配到的是构造函数。

总结

  • 传入左值对象,需要调用构造函数+拷贝构造函数。
  • 传入右值对象,需要调用构造函数+移动构造函数。
  • 传入参数包,只需要调用构造函数。

   当然,这里的前提是容器中存储的元素所对应的类,是一个需要深拷贝的类,并且该类实现了移动构造函数。否则调用emplace系列接口时,传入左值对象和传入右值对象的效果都是一样的,都需要调用一次构造函数和一次拷贝构造函数。

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

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

相关文章

vue3教程,如何手动获取后端数据(入门到精通3,新人必学篇)

概述&#xff1a;没有后端数据的前端&#xff0c;就失去了灵魂&#xff0c;由于本人没有写后端数据&#xff0c;所有调用黑马的&#xff0c;往下看相信对你会有收获的。 目录 第一步&#xff1a;安装axios 第二步&#xff1a;编写后端访问地址 第三步&#xff1a;编写具体的…

如何更新github上fork的项目(需要一定git基础)

如何更新Fork的项目(需要一定git基础) 前言&#xff1a;本文记录一下自己在github上fork了大佬的开源博客项目https://github.com/tangly1024/NotionNext&#xff0c;如何使用git克隆以及自定义开发和同步合并原项目更新迭代内容的的步骤 如何更新fork的项目(进阶版) 首先你…

解决:ModuleNotFoundError: No module named ‘selenium’

解决&#xff1a;ModuleNotFoundError: No module named ‘selenium’ 文章目录 解决&#xff1a;ModuleNotFoundError: No module named selenium背景报错问题报错翻译报错位置代码报错原因解决方法方法一&#xff0c;直接安装方法二&#xff0c;手动下载安装方法三&#xff0…

数字图像处理(实践篇)三十七 OpenCV-Python 使用SIFT和BFmatcher对两个输入图像的关键点进行匹配实践

目录 一 涉及的函数 二 实践 三 报错处理 使用SIFT(尺度不变特征变换)算法

幻兽帕鲁服务器Palworld游戏怎么更新?

自建幻兽帕鲁服务器进入Palworld游戏提示“您正尝试加入的比赛正在运行不兼容的游戏版本&#xff0c;请尝试升级游戏版本”什么原因&#xff1f;这是由于你的客户端和幻兽帕鲁服务器版本不匹配&#xff0c;如何解决&#xff1f;更新幻兽帕鲁服务器即可解决。阿里云百科aliyunba…

git操作之本地代码修改后想回退成当前最新版本

这张图很关键&#xff0c;取自https://www.cnblogs.com/cblx/p/12467083.html 我们的vscode就是workspace&#xff0c;我们提交代码需要三步&#xff0c;add&#xff0c;commit&#xff0c;push&#xff0c;其中我们想拉取代码有两种方式&#xff0c;git pull或者git fetch/cl…

《【Python】如何设置现代 Python 日志记录 | Python 基础教程 | Python 冷知识 | 十分钟高手系列》学习笔记

《【Python】如何设置现代 Python 日志记录 | Python 基础》 2 PUT ALL HANDLERS/FILTERS ON THE ROOT&#xff1a;扁平化的设计有助于简化维护成本 5 STORE CONFIG IN JSON OR YAML FILE&#xff1a;使用配置文件可以将配置和代码解耦&#xff0c;减少代码量 日志设置示例 7 …

ubuntu 22安装配置并好安全加固后,普通用户一直登录不上

现象 ubuntu 22安装配置并好安全加固后&#xff0c;普通用户一直登录不上 排查报错 查看日志/var/log/auth.log发现报错 Jan 30 15:49:57 aiv-O-E-M sshd[62570]: PAM unable to dlopen(pam_tally2.so): /lib/security/pam_tally2.so: cannot open shared object file: No …

管理的四种风格

前言 管理的四种风格,一般的领导大概就是这几种管理模式,告知,辅导,参与,授权,还有就是乱搞式(神经病模式)。 一、告知式 告知式是指组织通过正式、明确的渠道,将信息传达给员工。这种方式通常用于传递基本的规章制度、工作流程、政策文件等。告知式的作用在于确保员…

Codeforces Round 921 (Div. 2)(A~C)

A. We Got Everything Covered! 找出一个字符串S&#xff0c;满足条件&#xff1a;所有可能的长度为n&#xff0c;使用前k个小写字母的字符串P都是S的子串。 其中字符串S的长度尽可能短。 这种构造题&#xff0c;构造S的时候尽可能在原有的基础上去扩展&#xff0c;如果能扩…

体验 AutoGen Studio - 微软推出的友好多智能体协作框架

体验 AutoGen Studio - 微软推出的友好多智能体协作框架 - 知乎 最近分别体验了CrewAI、MetaGPT v0.6、Autogen Studio&#xff0c;了解了AI Agent 相关的知识。 它们的区别 可能有人要问&#xff1a;AutoGen我知道&#xff0c;那Autogen Studio是什么&#xff1f; https://g…

pandas绘制饼图:百分比、定制标签、关闭图例、支持中文

matplotlib绘制饼图 import matplotlib.pyplot as pltplt.rc(font, family=SimHei, size=13) size = [25, 15

移动0元素

q:给定一个数组&#xff0c;将所有0元素移动到末尾&#xff0c;同时保证非0元素相对顺序 a:以下是使用Java实现将所有0元素移动到末尾&#xff0c;同时保证非0元素相对顺序的示例代码&#xff1a; public class MoveZeros {public static void moveZeros(int[] nums) {int no…

ES6 模块化、CommonJS 模块化的区别经典面试题

语法差异: ES6 模块化 使用 import 和 export 关键字来导入和导出模块。 javascriptCopy code// 导入模块 import { someFunction } from someModule; ​ // 导出模块 export function myFunction() {// code } CommonJS 模块化 使用 require 和 module.exports 或 exports 来导…

C++_list

目录 一、模拟实现list 1、list的基本结构 2、迭代器封装 2.1 正向迭代器 2.2 反向迭代器 3、指定位置插入 4、指定位置删除 5、结语 前言&#xff1a; list是STL(标准模板库)中的八大容器之一&#xff0c;而STL属于C标准库的一部分&#xff0c;因此在C中可以直接使用…

30个常用的lodash工具函数

chunk: 将数组拆分成指定大小的多个数组 function chunk(array, size) {const result [];for (let i 0; i < array.length; i size) {result.push(array.slice(i, i size));}return result; }compact: 过滤数组中的假值&#xff08;false、null、0、“”、undefined 和…

制作ubuntu-base-23.10-base-armhf的根文件系统rootfs

1、创建一台同版本的ubuntu23的虚拟机 2、下载 ubuntu-base-23.10-base-armhf.tar.gz 3、上传到虚拟机里&#xff0c;解压到rootfs文件夹下 tar -xf /opt/ubuntu-base-23.10-base-armhf.tar.gz -C /opt/rootfs4、安装 qemu&#xff0c;对任何机器运行操作系统的全系统仿真。…

npm 淘宝镜像正式到期

由于node安装插件是从国外服务器下载&#xff0c;如果没有“特殊手法”&#xff0c;就可能会遇到下载速度慢、或其它异常问题。 所以如果npm的服务器在中国就好了&#xff0c;于是我们乐于分享的淘宝团队干了这事。你可以用此只读的淘宝服务代替官方版本&#xff0c;且同步频率…

AsyncLocal是如何实现在Thread直接传值的?

一&#xff1a;背景 1. 讲故事 这个问题的由来是在.NET高级调试训练营第十期分享ThreadStatic底层玩法的时候&#xff0c;有朋友提出了AsyncLocal是如何实现的&#xff0c;虽然做了口头上的表述&#xff0c;但总还是会不具体&#xff0c;所以觉得有必要用文字图表的方式来系统…

百度智能小程序开发平台:SEO关键词推广优化 带完整的搭建教程

移动互联网的普及&#xff0c;小程序成为了众多企业和开发者关注的焦点。百度智能小程序开发平台为开发者提供了一站式的解决方案&#xff0c;帮助企业快速搭建并推广自己的小程序。本文将重点介绍百度智能小程序开发平台的SEO关键词推广优化功能&#xff0c;并带完整的搭建教程…