Template_C++

C++模板

C++提供了function template.

function template:实际上是建立一个通用函数,其函数类型和形参类型不具体制定,用一个虚拟的类型来代表。这个通用的函数就称为函数模版。

是不是可以这样理解,函数模版就是给了一种功能,能够适合多种数据的功能?

  • C++提供两种模版机制:函数模版和类模版。
  • 类属-类型参数化,又称参数模版。

总结:

  • 模版把函数或类要处理的数据类型参数化,表现为参数的多态性,称为类属。
  • 模版用于表达逻辑结构相同,但具体数据元素类型不同的数据对象的通用行为
1 函数模板

用函数模版,是为了实现泛型,减轻编程的工作量,增强函数的重要性

#include <iostream>template<typename T>
void Myswap(T& a, T& b){T temp = a;a = b;b = temp;
}int main(){int a = 10, b = 20;Myswap(a, b);std::cout << "a = " << a << " b = " << b << std::endl;double x = 1.5, y = 2.5;Myswap(x, y);std::cout << "x = " << x << " y = " << y << std::endl;char c = 'a', d = 'b';Myswap(c, d);std::cout << "c = " << c << " d = " << d << std::endl;return EXIT_SUCCESS;
}

在上述的代码中,我们写了一个基于模板的交换函数,可以用于交换int类型、double类型和char类型,如果不使用模板函数,就需要写三个交换函数。

void Myswap(T& a, T& b);
void Myswap(int& a   , int& b);
void Myswap(double& a, double& b);
void Myswap(char& a  , char& b);
1.1 函数模板和普通函数区别
  • 函数模板不允许自动类型转化,必须严格匹配类型;
  • 普通函数能够自动进行类型转化
#include <iostream>template<typename T>
T MyPlus(T a, T b){std::cout << "int TemplateT + T = " << a + b << std::endl;T ret = a + b;return ret;
}int MyPlus(int a, char b){std::cout << "int noTemplate char + int = " << a + b << std::endl;int ret = a + b;return ret;
}int main(){std::cout << "Hello World!" << std::endl;int a = 10;char b = 'a';MyPlus(a,a); // function template MyPlus<int>(a, a) is calledMyPlus(b,b); // function template MyPlus<char>(a, b) is calledMyPlus(a,b); // function MyPlus(int, char) is called// 普通函数可以隐式类型转换MyPlus(b,a); //function template MyPlus<char>(b, a) is calledreturn EXIT_SUCCESS;
}/*
Hello World!
int TemplateT + T = 20
int TemplateT + T = 194
int noTemplate char + int = 107
int noTemplate char + int = 107
*/
/* why not define template 
template<typename T1, typename T2>
T1 MyPlus(T1 a, T2 b) {std::cout << "T1,T2 Template T2 + T1 = " << a + b << std::endl;T1 ret = a + b;return ret;
}
MyPlus(a,b); // template<typename T1, typename T2> is called
MyPlus(b,a); // template<typename T1, typename T2> is called
*/
1.1.1 为什么函数模板不允许自动类型转换

在C++中,函数模板不允许自动类型转换的主要原因是为了保持类型安全和编译时的类型检查。以下是几个关键原因:

  1. 类型安全:函数模板的设计初衷是提供一种泛型编程的方式,使得代码可以处理多种数据类型。为了确保类型安全,编译器需要确保模板参数的类型是精确匹配的。如果允许自动类型转换,可能会导致意外的类型转换和潜在的类型错误。
  2. 编译时类型检查:C++是一种静态类型语言,编译器在编译时会进行严格的类型检查。函数模板参数的精确匹配确保了编译器可以在编译时发现类型不匹配的错误,而不是在运行时才发现问题。
  3. 避免二义性和不确定性:如果允许自动类型转换,可能会导致函数调用的二义性。例如,如果有多个函数模板可以匹配某个调用,编译器将难以确定应该选择哪个模板实例。通过要求精确匹配,可以避免这种二义性。
  4. 模板特化和重载:C++允许对模板进行特化和重载,这提供了更灵活的类型处理方式。如果允许自动类型转换,可能会干扰这些机制的正常工作,使得模板特化和重载的规则变得复杂和难以管理。

例如,考虑以下函数模板:

template <typename T>
T add(T a, T b) {return a + b;
}

如果允许自动类型转换,调用 add(5, 3.2) 可能会导致编译器尝试将 int 转换为 double 或反之,这不仅会增加编译器的复杂性,还可能导致意外的结果。

因此,为了保持类型安全、编译时类型检查和避免二义性,C++中的函数模板不允许自动类型转换。如果需要处理不同类型的参数,可以使用多个类型参数的模板、模板特化或重载函数来实现

1.1.2 自动类型转换

在C++中,自动类型转换(也称为隐式类型转换)是指编译器在需要时自动将一种数据类型转换为另一种数据类型的过程。这种转换通常发生在不同数据类型的变量进行运算或赋值时,以确保操作能够顺利进行。

C++中的自动类型转换遵循一定的规则和优先级,常见的自动类型转换包括:

  1. 整数提升:较小的整数类型(如 charshort)在表达式中会被自动提升为 int 类型。
  2. 整数和浮点数之间的转换:整数类型可以自动转换为浮点数类型,但可能会导致精度损失。
  3. 派生类到基类的转换:派生类对象可以自动转换为基类对象。
  4. 算术转换:在算术运算中,操作数会被转换为相同的类型,通常是表达式中最高级别的类型。

以下是一些C++中自动类型转换的示例:

#include <iostream>int main() {int a = 5;       // 整数类型double b = 3.2;  // 浮点数类型double c = a + b;  // 自动将整数 a 转换为浮点数类型,然后进行加法运算std::cout << c << std::endl;  // 输出 8.2char ch = 'A';  // 字符类型int i = ch;     // 自动将字符类型转换为整数类型std::cout << i << std::endl;  // 输出 65('A' 的 ASCII 值)return 0;
}

在这个示例中,整数 a 被自动转换为浮点数类型,以便与浮点数 b 进行加法运算。字符 ch 被自动转换为整数类型,以便赋值给整数变量 i

1.2 函数模板和普通函数在一起调用规则

在C++中,函数模板和普通函数可以共存,并且在调用时遵循一定的规则来确定调用哪个函数。这些规则包括函数模板实例化、普通函数的重载解析以及模板参数推导等。以下是一些关键规则:

  1. 普通函数的优先级:如果存在一个普通函数,其参数类型与调用参数完全匹配,那么编译器会优先选择这个普通函数,而不是实例化一个函数模板。
  2. 模板参数推导:如果调用参数与普通函数不匹配,编译器会尝试推导函数模板的参数类型,并实例化一个模板函数。
  3. 重载解析:如果存在多个函数模板或普通函数可以匹配调用参数,编译器会进行重载解析,选择最匹配的函数。

以下是一个示例,展示了函数模板和普通函数在一起调用的规则:

#include <iostream>// 普通函数
int add(int a, int b) {return a + b;
}// 函数模板
template <typename T>
T add(T a, T b) {return a + b;
}int main() {int a = 5,b = 3;double c = 3.2, d = 2.1;// 调用普通函数,因为参数类型完全匹配std::cout << add(a, b) << std::endl;  // 输出 8// 调用函数模板,因为普通函数不匹配 double 类型std::cout << add(c, d) << std::endl;  // 输出 5.3// 调用函数模板,因为普通函数不匹配 double 和 int 的组合std::cout << add(a, c) << std::endl;  // 输出 8.2(注意:这里会进行隐式转换)return 0;
}

在这个示例中:

  • add(a, b) 调用普通函数 int add(int, int),因为参数类型完全匹配。
  • add(c, d) 调用函数模板 T add(T, T),因为普通函数不匹配 double 类型。
  • add(a, c) 调用函数模板 T add(T, T),因为普通函数不匹配 intdouble 的组合。在这种情况下,编译器会尝试推导模板参数类型,但由于模板参数必须一致,a 会被隐式转换为 double 类型。

总结来说,函数模板和普通函数在一起调用时,编译器会根据参数类型匹配、模板参数推导和重载解析等规则来确定调用哪个函数。普通函数如果参数类型完全匹配,会优先被调用;否则,编译器会尝试实例化函数模板。

1.3 模板的编译过程

?为什么函数模板可以和普通函数放在一起?

编译过程:预处理(Pre-processing)->编译(Compiling)-> 汇编(Assembling)-> 链接(Linking)

image-20240716105436980

函数模板机制理论

  • 编译器并不是把函数模板处理成能够处理成任何类型的函数
  • 函数模板通过具体类型产生不同的函数
  • 编译器对函数模板进行两次编译,在声明的地方对模板代码本身进行编译,在调用的地方对参数替换后的代码进行编译。

局限性:函数模板很有可能无法处理某些类型,比如两数相加的模板就无法处理两个数组相加。此时可以使用模板重载的方式解决。

1.4 函数模板重载

在C++中,函数模板可以像普通函数一样进行重载。函数模板重载允许定义多个具有相同名称但参数类型或数量不同的模板函数。编译器会根据调用时的参数类型和数量来选择合适的模板实例。

以下是一些函数模板重载的示例:

#include <iostream>// 函数模板 1:接受两个相同类型的参数
template <typename T>
T add(T a, T b) {return a + b;
}
// 函数模板 2:接受三个相同类型的参数
template <typename T>
T add(T a, T b, T c) {return a + b + c;
}
// 普通函数:接受两个不同类型的参数
double add(int a, double b) {return a + b;
}
int main() {int a = 5,b = 3,c = 2;double d = 3.2;// 调用函数模板 1std::cout << add(a, b) << std::endl;  // 输出 8// 调用函数模板 2std::cout << add(a, b, c) << std::endl;  // 输出 10// 调用普通函数std::cout << add(a, d) << std::endl;  // 输出 8.2return 0;
}

在这个示例中:

  1. add(a, b) 调用第一个函数模板 T add(T, T),因为参数类型相同。
  2. add(a, b, c) 调用第二个函数模板 T add(T, T, T),因为参数类型相同且数量为三个。
  3. add(a, d) 调用普通函数 double add(int, double),因为参数类型不同且数量为两个。

函数模板重载的规则与普通函数重载类似,编译器会根据参数类型和数量进行匹配,选择最合适的函数模板实例或普通函数。如果存在多个匹配的模板实例,编译器会进行重载解析,选择最匹配的实例。

需要注意的是,函数模板的重载并不意味着编译器会自动进行类型转换。模板参数必须精确匹配,否则编译器会报错。如果需要处理不同类型的参数,可以使用多个类型参数的模板或模板特化来实现。

1.5 函数模板特化

在C++中,template<> 语法用于模板特化。模板特化允许为特定类型提供专门的实现。你提到的 template<> void mySwap<persion>(persion &p1, persion &p2) 是一个函数模板的显式特化,用于处理 persion 类型的交换操作。

假设你有一个通用的 mySwap 函数模板,用于交换两个对象:

template <typename T>
void mySwap(T &a, T &b) {T temp = a;a = b;b = temp;
}

如果你希望为 persion 类型提供一个专门的实现,可以使用模板特化:

#include <iostream>
#include <string>// 定义 persion 类
class persion {
public:persion(const std::string& name, int age) : name(name), age(age) {}void print() const {std::cout << "Name: " << name << ", Age: " << age << std::endl;}std::string name;int age;// 友元声明,允许 mySwap 函数访问私有成员void mySwap(persion &p1, persion &p2);
};// 通用的 mySwap 函数模板
template <typename T>
void mySwap(T &a, T &b) {T temp = a;a = b;b = temp;
}// 针对 persion 类型的 mySwap 函数模板特化
template<> void mySwap<persion>(persion &p1, persion &p2) {using std::swap;swap(p1.name, p2.name);swap(p1.age, p2.age);
}int main() {persion p1("Alice", 30);persion p2("Bob", 25);std::cout << "Before swap:" << std::endl;p1.print(); p2.print();mySwap(p1, p2);std::cout << "After swap:" << std::endl;p1.print(); p2.print();return 0;
}
/*
Before swap:
Name: Alice, Age: 30
Name: Bob, Age: 25
After swap:
Name: Bob, Age: 25
Name: Alice, Age: 30
*/

在这个示例中:

  1. 定义了一个通用的 mySwap 函数模板,用于交换两个对象。
  2. 定义了一个 persion 类,包含 nameage 成员变量。
  3. 使用 template<> 语法为 persion 类型提供了一个专门的 mySwap 实现,交换 persion 对象的 nameage 成员变量。

通过这种方式,你可以为特定类型提供定制的交换操作,而不影响其他类型的交换行为。

类模板

类模板和函数模板的定义和使用类似。

类模板用于实现类所需数据的类型参数化

在C++中,类模板(class template)是一种强大的工具,用于实现类所需数据的类型参数化。类模板允许你定义一个通用的类,其中的数据类型可以作为参数传递,从而使得类可以处理多种不同类型的数据。

以下是一个简单的类模板示例,展示了如何使用类模板实现类型参数化:

#include <iostream>
#include <vector>// 定义一个类模板,用于存储和管理数据
template <typename T>
class Container {
public:// 添加元素到容器中void add(const T& item) {data.push_back(item);}// 打印容器中的所有元素void print() const {for (const T& item : data) {std::cout << item << " ";}std::cout << std::endl;}private:std::vector<T> data; // 使用 vector 存储数据
};int main() {// 创建一个存储 int 类型数据的 Container 实例Container<int> intContainer;intContainer.add(1);intContainer.add(2);intContainer.add(3);intContainer.print(); // 输出: 1 2 3// 创建一个存储 double 类型数据的 Container 实例Container<double> doubleContainer;doubleContainer.add(1.1);doubleContainer.add(2.2);doubleContainer.add(3.3);doubleContainer.print(); // 输出: 1.1 2.2 3.3// 创建一个存储 std::string 类型数据的 Container 实例Container<std::string> stringContainer;stringContainer.add("Hello");stringContainer.add("World");stringContainer.print(); // 输出: Hello Worldreturn 0;
}
/*
1 2 3 
1.1 2.2 3.3
Hello World
*/

在这个示例中:

  1. 定义了一个名为 Container 的类模板,其中的 T 是一个类型参数。
  2. Container 类包含两个成员函数:add 用于添加元素到容器中,print 用于打印容器中的所有元素。
  3. 使用 std::vector<T> 来存储数据,这样可以方便地管理动态数组。
  4. main 函数中,创建了三个不同类型的 Container 实例:一个用于存储 int 类型数据,一个用于存储 double 类型数据,一个用于存储 std::string 类型数据。

通过这种方式,类模板允许你编写一次代码,然后使用不同的类型参数来实例化类,从而实现类型参数化。这大大提高了代码的复用性和灵活性。

类模板做函数参数

在C++中,类模板可以作为函数参数,这使得函数能够处理不同类型的类模板实例。你可以通过模板参数来传递类模板实例,或者直接在函数参数中使用类模板。

以下是一些示例,展示了如何将类模板作为函数参数:

示例 1:通过模板参数传递类模板实例

#include <iostream>
#include <vector>// 定义一个类模板
template <typename T>
class Container {
public:void add(const T& item) {data.push_back(item);}void print() const {for (const T& item : data) {std::cout << item << " ";}std::cout << std::endl;}
private:std::vector<T> data;
};// 定义一个函数模板,接受一个 Container 实例
template <typename T>
void processContainer(const Container<T>& container) {container.print();
}
int main() {Container<int> intContainer;intContainer.add(1); intContainer.add(2); intContainer.add(3);processContainer(intContainer); // 输出: 1 2 3Container<double> doubleContainer; doubleContainer.add(1.1); doubleContainer.add(2.2); doubleContainer.add(3.3);processContainer(doubleContainer); // 输出: 1.1 2.2 3.3return 0;
}

示例 2:直接在函数参数中使用类模板

// 定义一个函数,接受一个 Container 实例
void processIntContainer(const Container<int>& container) {container.print();
}
void processDoubleContainer(const Container<double>& container) {container.print();
}

在这两个示例中,我们展示了如何将类模板实例作为函数参数传递。第一个示例通过模板参数传递类模板实例,使得函数可以处理不同类型的 Container 实例。第二个示例直接在函数参数中使用类模板,为每种类型定义了一个单独的函数。

通过这种方式,你可以编写灵活的函数,处理不同类型的类模板实例

类模板派生普通类

在C++中,可以从类模板派生出普通类。这种情况下,普通类会固定使用类模板中的某个特定类型。以下是一个示例,展示了如何从类模板派生出普通类:

#include <iostream>
#include <vector>// 定义一个类模板
template <typename T>
class BaseContainer {
public:void add(const T& item) {data.push_back(item);}void print() const {for (const T& item : data) {std::cout << item << " ";}std::cout << std::endl;}
protected:std::vector<T> data;
};// 从类模板派生出一个普通类
class IntContainer : public BaseContainer<int> {
public:void addMultiple(int count, int value) {for (int i = 0; i < count; ++i) {add(value);}}
};int main() {IntContainer intContainer;intContainer.add(1); ntContainer.add(2); ntContainer.add(3);intContainer.print(); // 输出: 1 2 3intContainer.addMultiple(3, 4);intContainer.print(); // 输出: 1 2 3 4 4 4return 0;
}

在这个示例中:

  1. 定义了一个名为 BaseContainer 的类模板,其中的 T 是一个类型参数。
  2. BaseContainer 类包含两个成员函数:add 用于添加元素到容器中,print 用于打印容器中的所有元素。
  3. BaseContainer 类模板派生出一个普通类 IntContainer,并固定使用 int 类型。
  4. IntContainer 类添加了一个新的成员函数 addMultiple,用于添加多个相同值的元素。

通过这种方式,你可以从类模板派生出普通类,并固定使用某个特定类型。这使得代码更加简洁和易于维护。

类模板派生类模板

类模板派生类模板是指从一个类模板派生出另一个类模板。这种技术在C++中非常有用,可以实现代码的重用和泛化。下面是一个简单的示例,展示了如何从一个类模板派生出另一个类模板。

#include <iostream>// 基类模板
template <typename T>
class Base {
public:Base(T value) : data(value) {}void display() const {std::cout << "Base: " << data << std::endl;}
protected:T data;
};// 派生类模板
template <typename T>
class Derived : public Base<T> {
public:Derived(T value) : Base<T>(value) {}void display() const {std::cout << "Derived: " << this->data << std::endl;}
};int main() {Base<int> base(10);base.display();Derived<int> derived(20);derived.display();return 0;
}

在这个示例中,我们定义了一个基类模板 Base,它包含一个类型为 T 的数据成员 data 和一个显示数据的成员函数 display。然后,我们定义了一个派生类模板 Derived,它继承自 Base 模板,并重写了 display 函数。

main 函数中,我们创建了一个 Base<int> 对象和一个 Derived<int> 对象,并分别调用它们的 display 函数来显示数据。

通过这种方式,我们可以灵活地使用类模板派生类模板,实现更复杂和通用的代码结构。

类模板类内实现

在C++中,类模板的成员函数可以在类内实现,也可以在类外实现。类内实现通常用于简单的成员函数,这样可以使得代码更加紧凑和易读。下面是一个示例,展示了如何在类模板中实现成员函数。

#include <iostream>template <typename T>
class MyClass {
public:// 构造函数MyClass(T value) : data(value) {}// 成员函数在类内实现void display() const {std::cout << "Data: " << data << std::endl;}private:T data;
};int main() {MyClass<int> obj(10);obj.display();MyClass<double> obj2(3.14);obj2.display();return 0;
}

在这个示例中,我们定义了一个类模板 MyClass,它包含一个类型为 T 的数据成员 data 和一个显示数据的成员函数 displaydisplay 函数在类内实现,直接在类定义中给出其实现。

main 函数中,我们创建了一个 MyClass<int> 对象和一个 MyClass<double> 对象,并分别调用它们的 display 函数来显示数据。

类内实现的优势在于代码更加紧凑,适合简单的成员函数。如果成员函数较为复杂,或者需要在多个地方使用相同的实现,可以考虑在类外实现成员函数。

类模板类外实现

在C++中,类模板的成员函数也可以在类外实现。这种做法通常用于成员函数较为复杂,或者需要在多个地方使用相同的实现。下面是一个示例,展示了如何在类模板中实现成员函数。

#include <iostream>template <typename T>
class MyClass {
public:// 构造函数MyClass(T value);// 成员函数声明void display() const;private:T data;
};// 构造函数在类外实现
template <typename T>
MyClass<T>::MyClass(T value) : data(value) {}// 成员函数在类外实现
template <typename T>
void MyClass<T>::display() const {std::cout << "Data: " << data << std::endl;
}int main() {MyClass<int> obj(10);obj.display();MyClass<double> obj2(3.14);obj2.display();return 0;
}

在这个示例中,我们定义了一个类模板 MyClass,它包含一个类型为 T 的数据成员 data 和一个显示数据的成员函数 displaydisplay 函数在类外实现,需要在类定义之外给出其实现。

main 函数中,我们创建了一个 MyClass<int> 对象和一个 MyClass<double> 对象,并分别调用它们的 display 函数来显示数据。

类外实现的优势在于可以将复杂的成员函数实现分离出来,使得类定义更加简洁。同时,相同的实现可以在多个地方使用,提高了代码的重用性。

类模板头文件和源文件分离问题

==注意:==类模板的声明和实现放到一个文件中,我们把这个文件命名为.hpp(这个是约定的规则,并不是标准,必须这么写)。

原因:

  • 类模板需要二次编译,在出现模板的地方编译一次,在调用模板的地方再次编译。
  • C++编译规则为独立编译。
模板类与友元函数

友元函数是可以访问类的私有变量(private)和保护变量(protected)。

在C++中,模板类可以声明友元函数,以便友元函数能够访问类的私有成员。友元函数可以是普通函数、类成员函数,甚至是另一个模板类的成员函数。下面是一个示例,展示了如何在模板类中声明友元函数。

#include <iostream>// 模板类
template <typename T>
class MyClass {
public:MyClass(T value) : data(value) {}// 声明友元函数friend void display(const MyClass<T>& obj);private:T data;
};// 友元函数的实现
template <typename T>
void display(const MyClass<T>& obj) {std::cout << "Data: " << obj.data << std::endl;
}int main() {MyClass<int> obj(10);display(obj);MyClass<double> obj2(3.14);display(obj2);return 0;
}

在这个示例中,我们定义了一个模板类 MyClass,它包含一个类型为 T 的数据成员 data。我们在类中声明了一个友元函数 display,该函数可以访问 MyClass 的私有成员 data

友元函数 display 的实现位于类外,并且需要使用模板参数 T 来匹配类模板。在 main 函数中,我们创建了一个 MyClass<int> 对象和一个 MyClass<double> 对象,并分别调用友元函数 display 来显示数据。

通过这种方式,模板类可以与友元函数协同工作,实现对私有成员的访问和操作。

template template parameters

在C++中,模板模板参数(template template parameters)允许你在定义模板时使用另一个模板作为参数。这种技术在需要参数化容器类型时特别有用。下面是一个示例,展示了如何使用模板模板参数。

#include <iostream>
#include <vector>
#include <deque>template <template <typename...> class Container, typename T>
class MyContainer {
public:MyContainer() {std::cout << "Constructor" << std::endl;}void display() {// 假设容器有一个 size() 方法和一个 operator[] 方法std::cout << "Size: " << container.size() << std::endl;for (size_t i = 0; i < container.size(); ++i) {std::cout << container[i] << " ";}std::cout << std::endl;}void push_back1(const T& value) {container.push_back(value);std::cout << "Push back " << value << std::endl;std::cout << "Size: " << container.size() << std::endl;std::cout << "Capacity: " << container.capacity() << std::endl;std::cout << "Front: " << container.front() << std::endl;}void dequeue1() {if (!container.empty()) {container.pop_front();}}
private:Container<T> container;
};int main() {// 使用 vector 作为容器类型MyContainer<std::vector, int> vecContainer;vecContainer.push_back1(1); vecContainer.push_back1(2);vecContainer.push_back1(3);vecContainer.display();// 使用 deque 作为容器类型MyContainer<std::deque, int> dequeContainer;dequeContainer.push_back1(1);dequeContainer.push_back1(2);dequeContainer.push_back1(3);dequeContainer.display();return 0;
}

在这个示例中,我们定义了一个模板类 MyContainer,它接受两个模板参数:一个模板模板参数 Container 和一个类型参数 TContainer 是一个模板类,而 T 是容器中元素的类型。

MyContainer 的构造函数中,我们使用模板模板参数 Container 创建了一个容器,并向其中添加了一个默认构造的 T 类型的元素。

main 函数中,我们创建了两个 MyContainer 对象,一个使用 std::vector 作为容器类型,另一个使用 std::deque 作为容器类型,并分别调用它们的 display 函数来显示容器中的元素。

通过这种方式,模板模板参数允许我们在定义模板时参数化容器类型,从而实现更灵活和通用的代码。

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

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

相关文章

pytorch学习(四)绘制loss和correct曲线

这一次学习的时候静态绘制loss和correct曲线&#xff0c;也就是在模型训练完成后&#xff0c;对统计的数据进行绘制。 以minist数据训练为例子 import torch from torch import nn from torch.utils.data import DataLoader from torchvision import datasets from torchvisi…

【zabbix6监控java-tomcat全流程】

目录 一、监控主机安装zabbix-server1、zabbix的安装2、配置数据库3、为zabbix server配置数据库4、启动服务,web界面安装 二、被监控主机安装tomcat1、安装JDK2、安装tomcat 三、zabbix的服务端安装zabbix-java-gateway四、被监控主机tomcat的配置五、web界面添加主机 一、监控…

使用 Web APi - MediaRecorder 获取麦克风资源,报错:Cannot find name ‘MediaRecorder‘ 的解决方法

目录 一、背景&#xff1a; 二、具体解决方法 一、背景&#xff1a; angular 调用 MediaRecorder 来使用麦克风获取声音&#xff0c;&#xff08;具体要求&#xff1a;angular 前端 按键调用 麦克风&#xff0c;松开按键生成音频文件&#xff09;代码如下&#xff08;来自通…

【树莓派3B+】控制引脚输出高低电平

前言一、安装RPI.GPIO库二、编写简单的输出高低电平的程序三、运行程序总结 前言 首先检查一下自己的板子有没有带库 我这个是有的。 ok&#xff0c;正式进入步骤 一、安装RPI.GPIO库 如果还没有安装RPi.GPIO库&#xff0c;可以通过以下命令在树莓派上安装&#xff1a; p…

Ubuntu20.04从零开搭PX4MavrosGazebo环境并测试

仅仅是个人搭建记录 参考链接&#xff1a; https://zhuanlan.zhihu.com/p/686439920 仿真平台基础配置&#xff08;对应PX4 1.13版&#xff09; 语雀 mkdir -p ~/tzb/catkin_ws/src mkdir -p ~/tzb/catkin_ws/scripts cd catkin_ws && catkin init catkin build cd…

数据结构day2

一、思维导图 内存分配 二、课后习题 分文件编译 //sys.h #ifndef TEST_H #define TEST_H #define MAX_SIZE 100//定义学生类型 typedef struct Stu {char name[20]; //姓名int age; //年龄double score; //分数 }stu;//定义班级类型 typedef struct Class {struct …

实战:详解Spring创建bean的流程(图解+示例+源码)

概叙 这篇主要总结Spring中bean的创建过程&#xff0c;主要分为加载bean信息–>实例化bean–>属性填充–>初始化阶段–>后置处理等步骤&#xff0c;且每个步骤Spring做的事情都很多&#xff0c;这块源码还是很值得我们都去看一看的。而Spring中Bean的声明周期其实…

GEO数据挖掘从数据下载处理质控到差异分析全流程分析步骤指南

0. 综合的教学视频介绍 GEO数据库挖掘分析作图全流程每晚11点在线教学直播录屏回放视频&#xff1a; https://www.bilibili.com/video/BV1rm42157CT/ GEO数据从下载到各种挖掘分析全流程详解&#xff1a; https://www.bilibili.com/video/BV1nm42157ii/ 一篇今年近期发表的转…

捷配总结的SMT工厂安全防静电规则

SMT工厂须熟记的安全防静电规则&#xff01; 安全对于我们非常重要&#xff0c;特别是我们这种SMT加工厂&#xff0c;通常我们所讲的安全是指人身安全。 但这里我们须树立一个较为全面的安全常识就是在强调人身安全的同时亦必须注意设备、产品的安全。 电气&#xff1a; 怎样预…

IDEA 调试 Ja-Netfilter

首先本地需要有两款IDEA 可以是相同版本&#xff0c;也可以是不同版本。反正要有两个&#xff0c;一个用来调试代码&#xff0c;一个启动。 移除原有ja-netfiler 打开你的ja-netfiler的vmoptions目录&#xff0c;修改其中的idea.vmoptions文件。移除最后一行-javaagent ...参…

分享 .NET EF6 查询并返回树形结构数据的 2 个思路和具体实现方法

前言 树形结构是一种很常见的数据结构&#xff0c;类似于现实生活中的树的结构&#xff0c;具有根节点、父子关系和层级结构。 所谓根节点&#xff0c;就是整个树的起始节点。 节点则是树中的元素&#xff0c;每个节点可以有零个或多个子节点&#xff0c;节点按照层级排列&a…

AI智能名片S2B2C商城小程序在社群去中心化管理中的应用与价值深度探索

摘要&#xff1a;随着互联网技术的飞速发展&#xff0c;社群经济作为一种新兴的商业模式&#xff0c;正逐渐成为企业与用户之间建立深度连接、促进商业增长的重要途径。本文深入探讨了AI智能名片S2B2C商城小程序在社群去中心化管理中的应用&#xff0c;通过详细分析社群去中心化…

【DGL系列】DGLGraph.out_edges简介

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 目录 函数说明 用法示例 示例 1: 获取所有边的源节点和目标节点 示例 2: 获取特定节点的出边 示例 3: 获取所有边的边ID 示例 4: 获取所有信息&a…

中国机器视觉行业上市公司市场竞争格局分析

中国机器视觉产业上市公司汇总&#xff1a;分布在各产业链环节 机器视觉就是用机器来代替人眼做测量和判断的系统&#xff0c;机器检测相较于人工视觉检测优势明显。目前&#xff0c;我国机器视觉产业的上市公司数量较多&#xff0c;分布在各产业链环节。具体包括&#xff1a;…

LeetCode-返回链表倒数第K个节点、链表的回文结构,相交链表

一、返回链表倒数第k个节点 . - 力扣&#xff08;LeetCode&#xff09; 本体思路参展寻找中间节点的方法&#xff0c;寻找中间节点是定义快慢指针&#xff0c;快指针每次走两步&#xff0c;慢指针每次走一步&#xff0c;当快指针为空或者快指针的下一个节点是空时&#xff0c;…

4000厂商默认账号密码、默认登录凭证汇总.pdf

获取方式&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1F8ho42HTQhebKURWWVW1BQ?pwdy2u5 提取码&#xff1a;y2u5

音视频开发入门教程(2)配置FFmpeg编译 ~共210节

在上一篇博客介绍了安装&#xff0c;音视频开发入门教程&#xff08;1&#xff09;如何安装FFmpeg&#xff1f;共210节-CSDN博客 感兴趣的小伙伴&#xff0c;可以继续跟着老铁&#xff0c;一起开始音视频剪辑功能&#xff0c;&#x1f604;首先查看一下自己的电脑是几核的&…

SCSA第七天

防火墙的可靠性 因为防火墙上不仅需要同步配置信息&#xff0c;还需要同步状态信息&#xff08;会话表等&#xff09;&#xff0c;所以&#xff0c;防火墙不能 像路由器那样单纯的靠动态协议来实现切换&#xff0c;需要用到双机热备技术。 1&#xff0c;双机 --- 目前双机热…

Golang面试题整理(持续更新...)

文章目录 Golang面试题总结一、基础知识1、defer相关2、rune 类型3、context包4、Go 竞态、内存逃逸分析5、Goroutine 和线程的区别6、Go 里面并发安全的数据类型7、Go 中常用的并发模型8、Go 中安全读写共享变量方式9、Go 面向对象是如何实现的10、make 和 new 的区别11、Go 关…

破解反爬虫策略 /_guard/auto.js(二)实战

这次我们用上篇文章讲到的方法来真正破解一下反爬虫策略&#xff0c;这两个案例是两个不同的网站&#xff0c;一个用的是 /_guard/auto.js&#xff0c;另一个用的是/_guard/delay_jump.js。经过解析发现这两个网站用的反爬虫策略基本是一模一样&#xff0c;只不过在js混淆和生成…