当前位置: 首页 > news >正文

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

文章目录

  • 1. 新增默认成员函数
    • 1.1 功能
    • 1.2 示例
  • 2. 类成员变量初始化
  • 3. 新关键字
    • 3.1 关键字default
    • 3.2 关键字delete
      • 补充
    • 3.3 关键字final和override
  • 4. 可变参数模板
    • 4.1 介绍
    • 4.2 定义方式
    • 4.3 展开参数包
      • 递归展开参数包
        • 优化
      • 初始化列表展开参数包
      • 逗号表达式展开参数包
        • 补充
  • 5. emplace接口
    • 5.1 区别
    • 5.2 使用方式
    • 5.3 原理
    • 5.4 意义

1. 新增默认成员函数

在C++11之前,一个类有6个默认成员函数,即构造函数、析构函数、拷贝构造函数、拷贝赋值函数、取地址重载函数和const取地址重载函数。

C++11新增了两个默认成员函数:

  • 移动构造函数
  • 移动赋值重载函数

而编译器默认生成的情况并非像其他6个默认成员函数一样,单纯未实现移动构造函数或移动赋值重载函数。编译器会在以下所有条件都满足时生成隐式的移动构造函数和移动赋值运算符:

  • 没有声明拷贝构造函数或拷贝赋值重载函数。
  • 没有声明移动构造函数或移动赋值重载函数。
  • 没有声明析构函数。

即:如果已经显式地声明移动构造函数或移动赋值重载函数,而没有实现拷贝构造函数或拷贝赋值函数,编译器也不会生成移动构造函数和移动赋值重载函数。

1.1 功能

默认生成的移动构造函数和移动赋值重载函数的功能是:

  • 移动构造函数:
    • 内置类型成员:值拷贝(浅拷贝);
    • 自定义类型成员:如果该成员实现了移动构造就调用它的移动构造,否则调用它的拷贝构造。
  • 移动赋值重载函数:
    • 内置类型成员:值拷贝(浅拷贝);
    • 自定义类型成员:如果该成员实现了移动赋值就调用它的移动赋值,否则调用它的拷贝赋值。

1.2 示例

下面用一个例子验证,首先实现一个简易的string类:

#include <iostream>
#include <string>
#include <cstddef>
using namespace std;namespace xy
{class string{public:// 构造函数string(const char* str = ""){_size = strlen(str);_capacity = _size;_str = new char[_capacity + 1];strcpy(_str, str);}void swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}// 拷贝构造函数string(const string& s):_str(nullptr), _size(0), _capacity(0){cout << "string(const string& s) -- 深拷贝" << endl;string tmp(s._str);swap(tmp);}// 移动构造string(string&& s):_str(nullptr), _size(0), _capacity(0){cout << "string(string&& s) -- 移动构造" << endl;swap(s);}// 拷贝赋值运算符重载string& operator=(const string& s){cout << "string& operator=(const string& s) -- 深拷贝" << endl;string tmp(s);swap(tmp);return *this;}// 移动赋值string& operator=(string&& s){cout << "string& operator=(string&& s) -- 移动赋值" << endl;string tmp(s);swap(tmp);return *this;}void push_back(char ch){if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size] = ch;_str[_size + 1] = '\0';_size++;}// +=运算符重载string& operator+=(char ch){push_back(ch);return *this;}void reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];strncpy(tmp, _str, _size + 1);delete[] _str;_str = tmp;_capacity = n;}}//析构函数~string(){_str = nullptr;_size = 0;_capacity = 0;}// 其他接口...private:char* _str;size_t _size;size_t _capacity;};
}

然后再实现一个简易的Person类,其中name成员的类型是上面实现的xy::string。

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& operator=(const Person& p){if (this != &p){_name = p._name;_age = p._age;}return *this;}// 析构函数~Person(){}
private:xy::string _name; // 姓名int _age;         // 年龄
};

其中,Person类只实现了拷贝构造函数、拷贝赋值函数和析构函数,并未实现移动构造函数和移动赋值重载函数。

现在,假如要用一个右值构造s2对象,编译器会为Person类生成一个默认的移动构造函数吗?

int main()
{Person s1("小明", 18);Person s2 = std::move(s1);return 0;
}

输出:

string(const string& s) -- 深拷贝

输出结果表明,编译器是调用Person的拷贝构造函数构造的s2对象,进而调用string的拷贝构造函数对name成员初始化,而并非生成一个默认的移动构造函数。

想让编译器为Person类生成默认移动构造函数,就需要将Person类中的拷贝构造、拷贝赋值和析构函数删去,再用右值构造对象时,编译器就会生成并调用Person类的默认移动构造函数。

注释掉要删去的函数,同样的测试代码的输出结果:

string(string&& s) -- 移动构造

虽然看起来生成默认移动构造函数和移动赋值重载函数的条件苛刻,但实际上析构函数和拷贝构造函数、赋值重载函数(需要析构,说明有资源被用来构造)都是成对出现的。实际上移动构造都是要自己写的,而且移动赋值的规则也是类似的。对于深拷贝的类,必须要自己写移动构造,因为拷贝的成本太大了。但是浅拷贝可以不用自己写。

2. 类成员变量初始化

默认构造函数只会针对自定义类型调用其构造函数初始化,而不会对内置类型处理,C++11允许在变量声明时赋予缺省值(非静态)。

class Person
{
private:string _name = "小明";int _age = 18;static int _s;
};

其中_s是静态成员变量,只能在类的外部初始化。

注意:在声明成员变量时,赋予缺省值并不是初始化。

3. 新关键字

3.1 关键字default

C++11关键字default是一种新的函数声明方式,它可以让编译器为某些特殊成员函数生成默认的实现。例如,如果你想要一个默认的构造函数或拷贝构造函数,你可以在函数声明后面加上=defaut。这样做有以下好处:

  • 可以避免不必要的初始化或赋值操作
  • 可以让类具有合成版本的特殊成员函数的特性,例如平凡性、可移动性等
  • 可以提高代码的可读性和一致性

使用default关键字强制生成某个默认成员函数,如构造函数:

class T
{
public:T() = default; // 强制生成构造函数// 构造函数T(const int t): _t(t){}
private:int _t;
};

除了构造函数之外,其他默认成员函数都可以用default关键字强制生成。

3.2 关键字delete

C++11关键字delete是一种新的函数声明方式,它可以让你禁用某些特殊成员函数或自定义函数。例如,如果你想要禁止一个类被拷贝或赋值,你可以在拷贝构造函数和赋值运算符后面加上=delete。这样做有以下好处:

  • 可以防止不合理或危险的操作
  • 可以提高编译时的错误检测
  • 可以避免不必要的代码生成

一般将被=delete修饰的函数称为删除函数。

禁止一个类被拷贝或赋值,你可以在拷贝构造函数和赋值运算符后面加上=delete

class T
{
public:// 构造函数T(const int t): _t(t){}
private:T(const T& t) = delete;T& operator=(const T&) = delete;
private:int _t;
};

还可以:假设你有一个类A,它有一个虚函数f(),你想要禁止它的子类B和C重写这个函数。你可以在A中声明f()=delete,这样B和C就不能再定义自己的f()了。代码如下:

class A 
{
public:virtual void f() = delete; // 禁用f()
};class B : public A 
{
public:void f() override {} // 编译错误,不能重写被删除的函数
};class C : public A 
{
public:void f() {} // 编译错误,不能定义同名的函数
};

补充

关于delete关键字,还有以下用途:

  • 释放程序动态申请的内存空间,例如 delete ptr; delete[] arr;
  • 删除对象的某个属性,例如 delete obj.name; delete obj['name'];
  • 删除数组的某个元素,例如 var arr = [1, 2, 3]; delete arr[0];

3.3 关键字final和override

  • override是C++11中的一个关键字,它用来修饰派生类中重写的虚函数,表示该函数确实是要重写基类的虚函数。这样可以避免因为函数签名不匹配而导致的重写失败或者隐藏基类函数的问题。使用方法如下:
class Base
{public:virtual void foo();virtual void bar() final; // 基类中用final修饰的虚函数不能被派生类重写
};class Derived : public Base
{public:void foo() override; // 正确,重写了基类的虚函数void bar() override; // 错误,试图重写被final修饰的虚函数
};
  • final:阻止类的进一步派生和虚函数的进一步重写,例如:
// 1. final修饰类
class A final 
{ 
public:virtual void f() {} // 阻止A被继承
};class B : public A 
{ 
public:void f() override {} // 错误,A是final类
};
// 2. final修饰虚函数
class C 
{
public:virtual void g() final {} // 阻止g被重写
};class D : public C
{
public:void g() override {} // 错误,g是final函数
};

delete关键字的应用:

使用关键字delete定义一个只能在堆上(栈上)生成对象的类。

  • 只能在堆上生成对象的类:将构造函数和析构函数设为私有或delete析构函数,然后提供一个静态成员函数来调用newdelete操作符,如下:
class HeapOnly
{
public:HeapOnly() // 构造函数 {_str = new char[5];} ~HeapOnly() = delete; // delete析构函数void Destory(){delete[] _str;operator delete(this);}
private:char* _str;
};
int main()
{HeapOnly* ptr = new HeapOnly;ptr->Destory();return 0;
}

这段代码定义了一个名为HeapOnly的类,它只能在堆上创建对象,不能在栈上创建对象。它的构造函数分配了一个长度为5的字符数组,并把指针赋给_str成员变量。它的析构函数被delete修饰符删除了,这意味着它不能被隐式调用,也就是说,当对象离开作用域时,不会自动调用析构函数释放资源。

因此,这个类提供了一个名为Destory的成员函数,用来手动释放资源和删除对象。这个函数先调用delete[]操作符释放_str指向的字符数组,然后调用operator delete(this)来释放对象占用的内存。

在main函数中,使用new操作符在堆上创建了一个HeapOnly类型的对象,并把指针赋给ptr变量。然后调用ptr->Destory()来销毁对象。它用指针ptr接受new出来的对象是为了能够访问对象的成员函数和变量。如果不用指针,就无法操作对象。因为这个类的析构函数被delete了,所以不能用引用或者值来接受对象,否则会导致编译错误。

链接:https://blog.csdn.net/niaolianjiulin/article/details/76165609

4. 可变参数模板

4.1 介绍

可变参数模板是指一种可以定义0到任意个模板参数的模板,它可以用来实现高度泛化的函数或类。可变参数模板的语法是使用省略号(…)来表示一个或多个参数,例如:

template<class ...Args> 
返回类型 函数名(Args... args) 
{ //函数体 
}template<class ...Types> 
class 类名 
{ //类体 
}

本文主要就函数模板可变参数进行介绍。

  • 可变参数模板需要使用递归的方式来展开和处理参数。可以使用sizeof…操作符来获取可变参数的个数。

  • 第一行:模板参数Args(这个名字可以任意)前面有省略号,表示它是一个可变模板参数,我们把带省略号的参数称为参数包,参数包里面包含0到N(N ≥ 0)个模板参数

  • 第二行:args是一个函数形参参数包。

可变参数是指一种可以接受不同数量和类型的参数的方法或函数。一个熟悉的函数的参数就是可变参数:printf。它不仅可以接受不同数量的参数,还能接受不同类型的参数,它的底层是数组实现的(在此不讨论底层实现)。模版参数类似,只不过穿的不是对象,而是一个模板。

4.2 定义方式

可变参数模板需要使用递归的方式来展开和处理参数。可以使用sizeof…操作符来获取可变参数的个数。

例如:

#include <iostream>
using namespace std;
template<class ...Args>
void ShowList(Args... args)
{cout << sizeof...(args) << endl;
}int main()
{ShowList();ShowList(1, "11");ShowList("11", 1);ShowList("11", 1, '1');return 0;
}

输出:

0
2
2
3

注意sizeof的格式是sizeof...(args)

4.3 展开参数包

C++11语法并未支持直接展开参数包,例如这样是不被语法支持的:

template<class ...Args>
void ShowList(Args... args)
{for (int i = 0; i < sizeof...(args); i++){cout << args[i] << " "; // 错误}cout << endl;
}

展开函数的参数包,有以下几种方式:

  • 递归
  • 逗号表达式
  • 初始化列表

递归展开参数包

递归展开参数包,利用函数模板的重载和特化,将参数包中的每个元素依次提取出来并处理。要使用递归展开参数包,需要定义一个基本情况和一个递归情况,分别处理空参数包和非空参数包。例如:

// 基本情况:当参数包为空时,调用这个函数,终止递归
void ShowList() 
{cout << endl;
}// 递归情况:当参数包非空时,调用这个函数
template<class T, class... Args>
void ShowList(T head, Args... rest) 
{cout << head << " ";ShowList(rest...); // 递归调用
}

image-20230228151454809

int main()
{ShowList();ShowList(1, "11", 'A');return 0;
}

输出:

1 11 A 

其中,无参的递归函数表示终止状态,即无参。理解递归展开参数包的核心是知道模板参数T每次都会取到1个参数包Args中第一个参数,rest就是剩下的参数。

注意,这个递归不是运行时进行的,因为是模板参数,所以它是编译时进行的。

优化

终止的递归函数和展开递归函数分开不是很美观,可以考虑把它们包在一起,另外用一个函数调用它们:

void ShowListArg()
{cout << endl;
}template<class T, class... Args>
void ShowListArg(T head, Args... rest)
{cout << head << " ";ShowListArg(rest...);
}
// 供外部调用
template<class ...Args>
void ShowList(Args... args)
{ShowListArg(args...);
}

注意,不可以使用sizeof判断参数包中参数个数,因为模板的推演是编译时运行,是在每次递归的函数接收到参数包以后才能知道参数包中的个数,所以不能用这样的方式终止递归:

if (sizeof...(args) == 0)
{return;
}

其中的if判断语句是编译后生成二进制文件后机器才会执行的语句,即运行时(runtime),函数模板的推演则是编译时(compile-time)。

初始化列表展开参数包

数组可以用列表初始化:

int a[] = {1,2,3,4};

如果参数包中的类型都相同且在其外部是明确的,那么也可以用类似的方式用参数包初始化数组,遍历数组即可展开参数包:

template<class ...Args>
void ShowList(Args... args)
{int arr[] = { args... }; // 列表初始化for (auto e : arr){cout << e << " "; // 打印}cout << endl;
}
int main()
{ShowList(1);ShowList(1, 2);ShowList(1, 2, 3);return 0;
}

输出:

1 
1 2 
1 2 3

但是,C++中的数组是相同类型元素的集合,且不能传入0个参数,所以使用初始化列表展开参数包有很大的局限性,需要搭配逗号表达式展开参数包。

逗号表达式展开参数包

逗号表达式是一种用逗号运算符连接两个或多个表达式的语法,它的运算过程是从左到右依次计算每个表达式,并且整个逗号表达式的值为最后一个表达式的值(并返回)。例如,a = 3 + 5, b = 6 + 8是一个逗号表达式,它会先计算a = 3 + 5,然后计算b = 6 + 8,并且整个逗号表达式的值为14(即b = 6 + 8的值)。

例如,下面的代码定义了一个函数模板,它可以接受任意数量和类型的参数,并将它们打印出来:

template<class T>
void PrintArg(T t)
{cout << t << " ";
}template<class... Args>
void ShowList(Args... args)
{int arr[] = { (PrintArg(args), 0)... };cout << endl;
}
int main()
{ShowList(1);ShowList(1, 2);ShowList(1, 2, 3);return 0;
}

输出:

1 
1 2 
1 2 3 
  • 返回0,形成了一个由0组成的初始化列表,确保逗号表达式返回的是一个整型值,以符合数组元素的类型,此步骤是为了符合语法。
  • 将打印功能封装为一个函数,并作为逗号表达式中的第一个表达式,这样逗号表达式就会从左到右先调用函数。然后将最后一个整型值作为返回值初始化整型数组。
  • 初始化列表会按照顺序执行每个逗号表达式,并丢弃其结果。这样就实现了对参数包中每个元素的操作。

注意:

可变参数的省略号要加在逗号表达式外面,表示需要将逗号表达式展开。如果将省略号加在args的后面,那么参数包将会被展开之后再整体传入PrintArg函数,代码中的{(PrintArg(args), 0)...}将会展开为{(PrintArg(arg1), 0), (PrintArg(arg2), 0)等等...}

此时已经可以传入不同类型的模板参数,但是依然不能传入0个参数,原因是数组长度不为0,可以增加一个无参版本的ShowList以供匹配。

// 无参调用
void ShowList()
{cout << endl;
}

补充

初始化列表展开参数包最大的问题就是不能传入不同类型的模版参数,原因是数组类型必须是相同的,但实际上传入的模板参数类型与数组元素类型之间并没有制约关系,所以将打印函数的返回值改为数组元素类型(例如int),即可满足语法,但个人认为这样做有点怪。

// 无参调用
void ShowList()
{cout << endl;
}
// 打印函数
template<class T>
int PrintArg(T t)
{cout << t << " ";return 0;   // 返回整型
}template<typename... Args>
void ShowList(Args... args)
{int arr[] = { PrintArg(args)... };cout << endl;
}
int main()
{ShowList(1);ShowList(1, 2);ShowList(1, 2, 3);return 0;
}

输出:

1 
1 2 
1 2 3 

5. emplace接口

STL的emplace(动词,安放)接口是C++11标准新增加的一组成员函数,用于在容器中构造而不是拷贝元素,从而提高插入性能和避免内存拷贝和移动。不同的容器有不同的emplace接口,例如vector有emplace()和emplace_back(),map有emplace()和emplace_hint()等。

注意:emplace版本的插入接口支持模板的可变参数,参数类型都带有&&,表示万能引用,而不是右值引用,即可以传入左值对象或者右值对象。

5.1 区别

例如vector的emplace_back(),对应的是push_back(),它们使用方式类似,不同之处:

  • emplace_back()可以直接在vector的末尾构造元素,而不需要创建临时对象或者移动或拷贝元素,这样可以节省内存和提高效率。
  • emplace_back()可以自动进行类型转换,而push_back()需要显式地调用构造函数。
  • emplace_back()和push_back()都可以传入左值或右值对象,前者不能使用初始化列表而后者可以。
  • emplace_back()可以接受多个参数,即可以传入用于构造元素的参数包,而push_back()只能接受一个对象或者一个右值引用。

5.2 使用方式

#include <vector>
int main()
{vector<pair<int, string>> v;pair<int, string> kv(2, "world");v.emplace_back(make_pair<int, string>(1, "hello")); // 右值v.emplace_back(kv); // 左值v.emplace_back(3, "!!"); // 参数包for(auto e: v){cout << e.second << " ";}cout << endl;return 0;
}

输出:

hello world !! 

为了使用参数包,vector的元素是pair。

5.3 原理

emplace接口直接在容器中构造新元素(类型可能是一个对象),而不进行传值或传引用操作,进而避免拷贝或移动带来的开销。

  1. 首先通过空间配置器获取内存空间,不调用构造函数初始化;
  2. 然后调用allocator_traits::construct函数对这块空间进行初始化,通过完美转发,接收空间的地址和用户传入的参数;
  3. allocator_traits::construct函数中使用定位new调用构造函数初始化这块空间,通过完美转发接收用户传入的参数;
  4. 将初始化好的新元素插入到容器中。

emplace接口的可变模板参数类型是万能引用,即左值引用、右值引用和参数包都能接收,对于不同类型的参数:

  • 参数是左值:首先调用构造函数实例化出一个左值对象,然后用定位new调用构造函数初始化申请的空间时,匹配拷贝构造函数
  • 参数是右值:首先调用构造函数实例化出一个右值对象,然后用定位new调用构造函数初始化申请的空间时,匹配移动构造函数
  • 参数是参数包:直接调用函数插入,用定位new调用构造函数初始化申请的空间时,匹配构造函数

注意:一般需要深拷贝的类才需要移动构造,否则使用左值和右值在效率上是没有区别的,都是调用一个构造函数和一次拷贝构造函数。

关于定位new:

定位new是一种特殊的new表达式,它可以在已分配的原始内存空间中调用构造函数初始化一个对象。这样可以避免常规new申请空间的开销,或者满足一些特殊的需求,比如内存池或硬件访问。

5.4 意义

emplace接口(统称)最大的意义就是它支持传入参数包,直接在容器的内部根据参数包构造新元素,这样就能减少一次拷贝或移动资源带来的开销。参数包就像一张图纸,C++11之前STL的插入接口都是在仓库门口(容器)卸货(元素),现在有了参数包,直接用图纸在仓库里建厂。要知道,可变参数模板的参数包的大小是由传入的实参个数决定的,远比拷贝或资源转移带来的开销小。

当一个类是深拷贝时,emplace接口的意义重大,当其他情况时,效率和普通的push接口相差不大。

相关文章:

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

文章目录1. 新增默认成员函数1.1 功能1.2 示例2. 类成员变量初始化3. 新关键字3.1 关键字default3.2 关键字delete补充3.3 关键字final和override4. 可变参数模板4.1 介绍4.2 定义方式4.3 展开参数包递归展开参数包优化初始化列表展开参数包逗号表达式展开参数包补充5. emplace…...

【Java学习笔记】15.Java 日期时间(1)

Java 日期时间 java.util 包提供了 Date 类来封装当前的日期和时间。 Date 类提供两个构造函数来实例化 Date 对象。 第一个构造函数使用当前日期和时间来初始化对象。 Date( )第二个构造函数接收一个参数&#xff0c;该参数是从 1970 年 1 月 1 日起的毫秒数。 Date(long …...

在ROS2中,通过MoveIt2控制Gazebo中的自定义机械手

目前的空余时间主要都在研究ROS2&#xff0c;最终目的是控制自己用舵机组装的机械手。 由于种种原因&#xff0c;先控制Gazebo的自定义机械手。 先看看目前的成果 左侧是rviz2中的moveit组件的机械手&#xff0c;右侧是gazebo中的机械手。在moveit中进行路径规划并执行后&#…...

Java-线程池 原子性 类

Java-线程池 原子性 类线程池构造方法调用Executors静态方法创建调用方法直接创建线程池对象原子性volatile-问题出现原因:volatile解决原子性AtomicInteger的常用方法悲观锁和乐观锁synchronized(悲)和CAS(乐)的区别并发工具类Hashtable集合ConcurrentHashMap原理:CountDownLa…...

力扣sql简单篇练习(二十五)

力扣sql简单篇练习(二十五) 1 无效的推文 1.1 题目内容 1.1.1 基本题目信息 1.1.2 示例输入输出 1.2 示例sql语句 # Write your MySQL query statement below SELECT tweet_id FROM Tweets WHERE CHAR_LENGTH(content)>151.3 运行截图 2 求关注者的数量 2.1 基本题目内…...

计算机网络:OSPF协议和链路状态算法

OSPF协议 开放最短路经优先OSPF协议是基于最短路径算法SPF,其主要特征就是使用分布式的链路状态协议OSPF协议的特点&#xff1a; 1.使用泛洪法向自治系统中的所有路由器发送信息&#xff0c;即路由器通过输出端口向所有相邻的路由器发送信息&#xff0c;而每一个相邻的路由器又…...

利用表驱动法+策略模式优化switch-case

1.前言 我有一个需求&#xff1a;有四个系统需要处理字段&#xff0c;一开始利用switch-case进行区分编码&#xff0c;后期字段处理越来越多&#xff0c;导致switch-case代码冗余&#xff0c;不太好&#xff0c;然后想通过java单继承多实现的性质进行优化。 2.实现 2.1定义S…...

SpringBoot创建和使用

目录 什么是SpringBoot SpringBoot的优点 SpringBoot项目的创建 1、使用idea创建 2、项目目录介绍和运行 Spring Boot配置文件 1、配置文件 2、配置文件的格式 3、properties 3.1、properties基本语法 3.2、读取配置文件 3.3、缺点 4、yml 4.1、优点 4.2、yml基本…...

which、whereis、locate文件查找命令

Linux下查找文件的命令有which、whereis、locate和find&#xff0c;find命令因要遍历文件系统&#xff0c;导致速度较慢&#xff0c;而且还会影响系统性能&#xff0c;而且命令选项较多&#xff0c;就单独放一篇介绍&#xff0c;可参见find命令——根据路径和条件搜索指定文件_…...

Uipath Excel 自动化系列14-SaveExcelFile(保存Excel)

活动描述 SaveExcelFile 保存Excel:保存工作簿&#xff0c;在修改 Excel 文件的用户界面自动化活动之后使用此活动&#xff0c;以保存对文件的更改 SaveExcelFile As 另存Excel : 将workbook 另存为文件 SaveExcelFile As PDF &#xff1a;将Excel 另存为PDF文件。该三个活…...

MyBatis学习

MyBatis优点 轻量级&#xff0c;性能出色 SQL 和 Java 编码分开&#xff0c;功能边界清晰。Java代码专注业务、SQL语句专注数据 开发效率稍逊于HIbernate&#xff0c;但是完全能够接受 补充&#xff1a;POJO 一&#xff1a;什么是POJO POJO的名称有多种&#xff0c;pure old…...

高速PCB设计指南系列(二)

第三篇 高速PCB设计 &#xff08;一&#xff09;、电子系统设计所面临的挑战   随着系统设计复杂性和集成度的大规模提高&#xff0c;电子系统设计师们正在从事100MHZ以上的电路设计&#xff0c;总线的工作频率也已经达到或者超过50MHZ&#xff0c;有的甚至超过100MHZ。目前…...

uniapp项目打包上线流程

平台&#xff1a;h5小程序app &#xff08;安卓&#xff09;小程序打包上线流程第一步&#xff1a;登录小程序公众平台第二步&#xff1a;hbuilderx打包小程序1.在mainfest.json文件中进行相关配置2.需要将项目中的网络请求改为https协议做为生产环境&#xff08;配置项目的环境…...

垃圾回收:垃圾数据如何自动回收

有些数据被使用之后&#xff0c;可能就不再需要了&#xff0c;我们把这种数据称为垃圾数据。如果这些垃圾数据一直保存在内存中&#xff0c;那么内存会越用越多&#xff0c;所以我们需要对这些垃圾数据进行回收&#xff0c;以释放有限的内存空间 不同语言的垃圾回收策略 通常…...

苹果笔不用原装可以吗?Apple Pencil平替笔推荐

近些年来&#xff0c;不管是学习还是画画&#xff0c;都有不少人喜欢用ipad。而ipad的用户&#xff0c;也是比较重视它的实用价值&#xff0c;尤其是不少人都想要好好利用来进行学习记笔记。事实上&#xff0c;有很多替代品都能替代Apple Pencil&#xff0c;仅仅用于记笔记就没…...

uniCloud基础使用-杂文

获取openID云函数use strict; exports.main async (event, context) > {//event为客户端上传的参数console.log(event : , event)// jscode2session 微信小程序登录接口&#xff0c;获取openidconst {code} event;// 云函数中如需要请求其他http服务&#xff0c;则使用uni…...

vector的模拟实现

文章目录vector的模拟实现vector 结构定义1. vector的迭代器的实现2. vector四个默认成员函数2.1 构造函数2.1.1 无参2.1.2 n个val初始化2.1.3 迭代器初始化2.2 析构函数2.3 拷贝构造函数2.3.1 传统写法2.3.2 现代写法2.4 赋值重载运算符3. 管理数组相关接口3.1 reserve3.2 res…...

【无标题】compose系列教程-4.相对布局ConstraintLayout的使用

相对布局在Compose中被称为ConstraintLayout&#xff0c;它可以让您以相对于其他元素的方式放置元素。 以下是使用ConstraintLayout实现相对布局的示例代码&#xff1a; Composable fun ConstraintLayoutExample() { ConstraintLayout(modifier Modifier.fillMaxSize()…...

JavaEE简单示例——Bean管理

简单介绍&#xff1a; 在这一章节我们会比较详细的介绍我们在之前的测试类中以及Bean管理XML配置文件中所使用到的类和方法&#xff0c;以及XML中配置的属性所代表的详细含义。以及之前我们反复提到但是一直没有详细的讲解的一个东西&#xff1a;容器。我们可以大致的有一个概…...

react+antdpro+ts实现企业级项目四:注册页面实现及useEmotionCss的介绍

创建文件路径并注册register路由 在pages/User下创建Register文件夹并创建index.tsx文件 然后在config/routes创建register注册路由。注册完后&#xff0c;当在登陆页面点击注册按钮时就可以跳转到此注册页面而不会报404了。 export default [{path: /user,layout: false,rou…...

Shifu基础功能:数据采集

数据采集 我们可以通过HTTP/gRPC与deviceShifu进行通信&#xff0c;deviceShifu会将我们发送的请求转换成设备所支持协议的形式&#xff0c;并发送给设备。 当设备接收到指令之后&#xff0c;数据会传输到deviceShifu中&#xff0c;之后deviceShifu将数据作为我们请求的返回值…...

代码随想录算法训练营day54 | 动态规划之子序列 392.判断子序列 115.不同的子序列

day54392.判断子序列1.确定dp数组&#xff08;dp table&#xff09;以及下标的含义2.确定递推公式3.dp数组如何初始化4.确定遍历顺序5.举例推导dp数组115.不同的子序列1.确定dp数组&#xff08;dp table&#xff09;以及下标的含义2.确定递推公式3.dp数组如何初始化4.确定遍历顺…...

MCAL知识点(三):Port与Dio配置

目录 1、概述 2、 Port的EB-tresos配置 2.1、创建模块 2.2 General配置 2.3、PortCnfigSet 2.4、Port的属性...

初识C++需要了解的一些东西(1)

目录&#x1f947;命名空间&#x1f3c5;存在原因&#x1f3f5;命名空间定义&#x1f3a7;命名空间的3种使用方式&#x1f3c6;C输入和输出&#x1f31d;缺省参数&#x1f31c;缺省参数概念⭐️缺省参数分类☀️函数重载&#x1f525;引用&#x1f31a;引用概念&#x1f313;引…...

友元函数的使用大全

概述 我们知道&#xff0c;C的类具有封装和信息隐藏的特性。一般情况下&#xff0c;我们会封装public的成员函数供用户调用&#xff0c;而将成员变量设置为private或protected。但在一些比较复杂的业务情况下&#xff0c;可能需要去访问对象中大量的private或protected成员变量…...

QT学习笔记-QT多项目系统中如何指定各项目的编译顺序

QT学习笔记-QT多项目系统中如何指定各项目的编译顺序背景环境解决思路具体操作背景 为了更好的复用程序功能以及更优雅的管理程序&#xff0c;有经验的程序员通常要对程序进行分层和模块化设计。在QT/C这个工具中同样可以通过创建子项目的方式对程序进行模块化&#xff0c;在这…...

JWT令牌解析及刷新令牌(十一)

写在前面&#xff1a;各位看到此博客的小伙伴&#xff0c;如有不对的地方请及时通过私信我或者评论此博客的方式指出&#xff0c;以免误人子弟。多谢&#xff01;如果我的博客对你有帮助&#xff0c;欢迎进行评论✏️✏️、点赞&#x1f44d;&#x1f44d;、收藏⭐️⭐️&#…...

Hibernate学习(一)

Hibernate学习&#xff08;一&#xff09; Hibernate框架的概述&#xff1a; 一&#xff1a;什么是框架&#xff1a;指软件的半成品&#xff0c;已经完成了部分功能。 二&#xff1a;EE的三层架构&#xff1a; 1.EE的三层经典架构&#xff1a; 我在这里主要学的是ssh框架。 三…...

Go的 context 包的使用

文章目录背景简介主要方法获得顶级上下文当前协程上下文的操作创建下级协程的Context场景示例背景 在父子协程协作过程中, 父协程需要给子协程传递信息, 子协程依据父协程传递的信息来决定自己的操作. 这种需求下可以使用 context 包 简介 Context通常被称为上下文&#xff…...

微服务为什么要用到 API 网关?

本文介绍了 API 网关日志的价值&#xff0c;并以知名网关 Apache APISIX 为例&#xff0c;展示如何集成 API 网关日志。 作者程小兰&#xff0c;API7.ai 技术工程师&#xff0c;Apache APISIX Contributor。 原文链接 什么是微服务 微服务架构&#xff08;通常简称为微服务&a…...

SWUST OJ 1042: 中缀表达式转换为后缀表达式【表达式转逆波兰表达式】

题目描述 中缀表达式是一个通用的算术或逻辑公式表示方法&#xff0c;操作符是以中缀形式处于操作数的中间&#xff08;例&#xff1a;3 4&#xff09;&#xff0c;中缀表达式是人们常用的算术表示方法。后缀表达式不包含括号&#xff0c;运算符放在两个运算对象的后面&#…...

Matlab基础知识

MATLAB批量读入文件和导出文件一、 批量读入文件1.若文件名称有序&#xff0c;则按照文件名称规律循环读取文件(1)读入不同的excelfor i1:1:10strstrcat(F:\数据\v,int2str(i),.xlsx); %连接字符串形成文件名Axlsread(str); end注&#xff1a;变量i为整数时&#xff0c;可以用i…...

动手学深度学习【2】——softmax回归

动手学深度学习网址&#xff1a;动手学深度学习 注&#xff1a;本部分只对基础知识进行简单的介绍并附上完整的代码实现&#xff0c;更多内容可参考上述网址。 前言 前面一节我们谈到了线性回归&#xff0c;它解决的是预测某个值的问题。但是在日常生活这&#xff0c;除了预测…...

深入理解Activity的生命周期

之前学习安卓的时候只是知道生命周期是什么&#xff0c;有哪几个&#xff0c;但具体的详细的东西却不知道&#xff0c;后来看过《Android开发艺术探索》和大量博客之后&#xff0c;才觉得自己真正有点理解生命周期&#xff0c;本文是我对生命周期的认识的总结。废话少说先上图。…...

Go语言刷题常用数据结构和算法

数据结构 字符串 string 访问字符串中的值 通过下标访问 s1 : "hello world"first : s[0]通过切片访问 s2 : []byte(s1) first : s2[0]通过for-range循环访问 for i, v : range s1 {fmt.Println(i, v) }查询字符是否属于特定字符集 // 判断字符串中是否包含a、b、…...

深入vue2.x源码系列:手写代码来模拟Vue2.x的响应式数据实现

前言 Vue响应式原理由以下三个部分组成&#xff1a; 数据劫持&#xff1a;Vue通过Object.defineProperty()方法对data中的每个属性进行拦截&#xff0c;当属性值发生变化时&#xff0c;会触发setter方法&#xff0c;通知依赖更新。发布-订阅模式&#xff1a;Vue使用发布-订阅…...

Linux线程控制

本篇我将学习如何使用多线程。要使用多线程&#xff0c;因为Linux没有给一般用户直接提供操作线程的接口&#xff0c;我们使用的接口&#xff0c;都是系统工程师封装打包成原生线程库中的。那么就需要用到原生线程库。因此&#xff0c;需要引入-lpthread&#xff0c;即连接原生…...

【LeetCode】剑指 Offer(20)

目录 题目&#xff1a;剑指 Offer 38. 字符串的排列 - 力扣&#xff08;Leetcode&#xff09; 题目的接口&#xff1a; 解题思路&#xff1a; 代码&#xff1a; 过啦&#xff01;&#xff01;&#xff01; 写在最后&#xff1a; 题目&#xff1a;剑指 Offer 38. 字符串的…...

FutureTask中的outcome字段是如何保证可见性的?

最近在阅读FutureTask的源码是发现了一个问题那就是源码中封装结果的字段并没有使用volatile修饰&#xff0c;源码如下&#xff1a;public class FutureTask<V> implements RunnableFuture<V> {/*** 状态变化路径* Possible state transitions:* NEW -> COMPLET…...

直播回顾 | 聚焦科技自立自强,Bonree ONE 助力国产办公自动化平稳替代

3月5日&#xff0c;两会发布《政府工作报告》&#xff0c;强调科技政策要聚焦自立自强。 统计显示&#xff0c;2022年金融信创项目数同比增长300%&#xff0c;金融领域信创建设当前已进入发展爆发期&#xff0c;由国有大型银行逐渐向中小型银行、非银金融机构不断扩展。信创云…...

深入理解Linux进程

进程参数和环境变量的意义一般情况下&#xff0c;子进程的创建是为了解决某个问题。那么解决问题什么问题呢&#xff1f;这个就需要进程参数和环境变量来进行决定的。子进程解决问题需要父进程的“数据输入”(进程参数 & 环境变量)设计原则&#xff1a;3.1 子进程启动的时候…...

Vue3之组件间的双向绑定

何为组件间双向绑定 我们都知道当父组件改变了某个值后&#xff0c;如果这个值传给了子组件&#xff0c;那么子组件也会自动跟着改变&#xff0c;但是这是单向的&#xff0c;使用v-bind的方式&#xff0c;即子组件可以使用父组件的值&#xff0c;但是不能改变这个值。组件间的…...

Java语法基础(一)

目录 代码注释方法 编码规范 基本数据类型及取值范围 变量和常量的声明与赋值 变量 常量 标识符 基本数据类型的使用 整数类型的使用 浮点类型的使用 布尔类型的使用 字符类型的使用 代码注释方法 单行注释&#xff1a;使用“//”进行单行注释多行注释&#xff1a;使…...

优思学院|零质量控制是什么概念?

零质量控制&#xff08;Zero Quality Control&#xff09;是指一个理想的系统&#xff0c;可以生产没有任何缺陷的产品&#xff0c;因此不需要频繁的检查&#xff0c;从而节省时间和金钱。那些追求过程优化并致力于持续过程改进的组织将零质量控制&#xff08;Zero Quality Con…...

2023-03-09 CMU15445-Query Execution

摘要: CMU15445, Project #3 - Query Execution 参考: Project #3 - Query Execution | CMU 15-445/645 :: Intro to Database Systems (Fall 2022) https://github.com/cmu-db/bustub 要求: OVERVIEW At this point in the semester, you have implemented the internal co…...

vuedraggable的使用

Draggable为基于Sortable.js的vue组件&#xff0c;用以实现拖拽功能。 特性 支持触摸设备 支持拖拽和选择文本 支持智能滚动 支持不同列表之间的拖拽 不以jQuery为基础 和视图模型同步刷新 和vue2的国度动画兼容 支持撤销操作 当需要完全控制时&#xff0c;可以抛出所有变化 可…...

双馈风力发电机-900V直流混合储能并网系统MATLAB仿真

MATLAB2016b主体模型&#xff1a;双馈感应风机模块、采用真实风速数据。混合储能模块、逆变器模块、转子过电流保护模块、整流器控制模块、逆变器控制模块。直流母线电压&#xff1a;有功、无功输出&#xff08;此处忘记乘负一信号输出&#xff09;&#xff0c;所以是负的。蓄电…...

leader选举过程

启动electionTimer&#xff0c;进行leader选举。 一段时间没有leader和follower通信&#xff0c;就会超时&#xff0c;开始选举leader过程。有个超时时间&#xff0c;如果到了这个时间&#xff0c;就会触发一个回调函数。具体如下: private void handleElectionTimeout() {boo…...

建造者模式

介绍 Java中的建造者模式是一种创建型设计模式,它的主要目的是为了通过一系列简单的步骤构建复杂的对象,允许创建复杂对象的不同表示形式,同时隐藏构造细节.它能够逐步构建对象,即先创建基本对象,然后逐步添加更多属性或部件,直到最终构建出完整的对象. 该模式的主要思想是将…...

IO与NIO区别

一、概念 NIO即New IO,这个库是在JDK1.4中才引入的。NIO和IO有相同的作用和目的,但实现方式不同,NIO主要用到的是块,所以NIO的效率要比IO高很多。在Java API中提供了两套NIO,一套是针对标准输入输出NIO,另一套就是网络编程NIO。 二、NIO和IO的主要区别 下表总结了Java I…...