C++ Primer:模板与泛型编程

1. 模板函数

在C++中,模板与泛型编程是一种强大的编程范式,它允许程序员编写与类型无关的代码。这种类型无关的代码在编译时会被实例化,以支持特定的数据类型。下面是根据您提出的点,对模板函数及其相关概念的一个整理。

模板函数是允许程序员编写一个函数模板,该模板可以与多种数据类型一起工作,而不是仅限于一种数据类型。通过模板,我们可以定义一组在逻辑上相似但操作的数据类型不同的函数。

1.1 模板参数

模板参数是模板定义中用于表示类型或值的占位符。在函数模板中,我们通常使用类型参数来指定函数可以操作的类型。类型参数在模板声明中通过关键字classtypename后跟一个标识符来声明(在模板定义中两者可互换使用,但typename在某些上下文中更为清晰)。

template <typename T>
void print(T value) {std::cout << value << std::endl;
}

在上面的例子中,T是一个模板参数,它可以被任何类型所替代。

1.2 函数形参

函数形参是函数模板中除了模板参数以外的参数,它们定义了函数需要接收的实参的类型和数量。在模板函数中,形参的类型可以是模板参数所代表的类型,也可以是其他非模板类型。

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

在这个例子中,ab是函数add的形参,它们的类型由模板参数T决定。

1.3 成员模板

成员模板是类模板或普通类中的模板成员(可以是成员函数或成员变量)。成员模板允许我们在类的上下文中定义与类型无关的成员。

  • 类模板中的成员函数模板:这种成员模板允许成员函数自身也接受模板参数,从而进一步增强了类的灵活性。
template <typename T>
class Box {
public:T value;// 成员函数模板template <typename U>void compare(U other) {if (value < other) {std::cout << "Box value is less than other." << std::endl;} else {std::cout << "Box value is not less than other." << std::endl;}}
};

在这个例子中,Box是一个类模板,它有一个成员函数模板compare,该函数接受一个不同类型的参数other,并与Boxvalue成员进行比较。

  • 非模板类中的成员函数模板:虽然较少见,但即使在一个非模板类中,也可以定义成员函数模板,以提供对多种类型数据的操作。

模板和泛型编程是C++中一个非常重要的概念,它们极大地增强了C++的表达能力,使得代码更加灵活和可重用。通过上面的介绍,您应该对模板函数及其相关概念有了一定的了解。

2. 类模板

在C++中,模板(Templates)是泛型编程的基石,它允许程序员编写与类型无关的代码。类模板(Class Templates)是模板的一种形式,用于定义类,其中类或类的成员在定义时其类型可以是未指定的,直到类被实例化时才确定。下面是对类模板及其相关概念的详细整理,每个部分都包含例子。

2.1 与模板函数的区别
  • 模板函数处理的是函数级别的泛型编程,其函数体在编译时根据提供的类型参数实例化。
  • 类模板则是对类进行泛型化,可以定义类级别的泛型编程,类的所有成员(包括成员函数和成员变量)都可以是模板化的。

例子

// 模板函数例子
template<typename T>
T max(T a, T b) {return (a > b) ? a : b;
}// 类模板例子
template<typename T>
class Box {
public:T value;Box(T val) : value(val) {}void print() { std::cout << value << std::endl; }
};
2.2 模板类名的使用

使用模板类时,需要在类名后加上尖括号<>中的类型参数。

例子

Box<int> intBox(42);
intBox.print();  // 输出 42Box<std::string> stringBox("Hello");
stringBox.print();  // 输出 Hello
2.3 类模板的成员函数

类模板的成员函数可以在类内部定义(隐式模板),也可以在类外部定义(显式模板)。

内部定义

template<typename T>
class Box {
public:T value;Box(T val) : value(val) {}void print() { std::cout << value << std::endl; }
};

外部定义

template<typename T>
class Box {
public:T value;Box(T val) : value(val) {}void print();
};template<typename T>
void Box<T>::print() {std::cout << value << std::endl;
}
2.4 类型成员

类模板中可以包含类型成员,这些类型成员也可以是模板化的。

例子

template<typename T, typename Alloc = std::allocator<T>>
class Vec {
public:using value_type = T;using allocator_type = Alloc;// 其他成员...
};
2.5 类模板和友元

友元关系对于类模板可以很复杂,因为友元可以是另一个模板或者非模板。

例子

template<typename T>
class Box;template<typename T>
void printBox(const Box<T>& b);template<typename T>
class Box {T value;friend void printBox<>(const Box<T>&);
public:Box(T val) : value(val) {}
};template<typename T>
void printBox(const Box<T>& b) {std::cout << b.value << std::endl;
}

注意:在某些编译器中,友元模板函数的声明可能需要额外的语法(如使用template<>),但上述简洁形式在现代编译器中通常是可接受的。

2.6 模板类型别名

使用using关键字可以定义模板类型别名,简化模板类型的书写。

例子

template<typename T>
using Ptr = T*;Ptr<int> p = new int(10);  // 相当于 int* p = new int(10);
2.7 类模板的 static 成员

类模板的static成员对于所有相同类型参数的实例是共享的。

例子

template<typename T>
class Box {
private:static int count;
public:Box() { ++count; }~Box() { --count; }static int getCount() { return count; }
};template<typename T>
int Box<T>::count = 0;int main() {Box<int> b1, b2;std::cout << Box<int>::getCount() << std::endl;  // 输出 2b1.~Box();  // 显式调用析构函数,仅为演示std::cout << Box<int>::getCount() << std::endl;  // 输出 1return 0;
}

通过这些例子和解释,你应该对C++中的类模板及其相关概念有了更深入的理解。

3. 模板编译

3.1 实例化声明

实例化声明(Instantiation Declaration)是模板使用的一个阶段,它指的是在编译过程中,编译器根据模板定义和特定的类型参数生成具体类型实例的过程。这个过程是隐式的,即用户不需要显式地编写实例化代码,编译器会根据上下文自动完成。

例子

假设我们有一个函数模板compare,用于比较两个值:

template <typename T>
int compare(const T& a, const T& b) {if (a < b) return -1;if (b < a) return 1;return 0;
}

当我们在代码中调用compare(1, 2)时,编译器会自动实例化一个int类型的compare函数版本。这个过程就是实例化声明,它不需要用户手动干预。

3.2 实例化定义

实例化定义(Instantiation Definition)是模板编译的另一个关键阶段,它指的是编译器实际生成模板具体类型实例的代码的过程。这个过程可能是隐式的,也可能是显式的。隐式实例化定义发生在编译器自动根据模板定义和类型参数生成具体类型实例时;而显式实例化定义则是用户通过特定的语法告诉编译器生成某个具体类型的实例。

隐式实例化定义例子

继续上面的compare函数模板例子,当调用compare(1.0, 2.0)时,编译器会自动实例化一个double类型的compare函数版本,这就是隐式实例化定义。

显式实例化定义例子

用户可以通过extern template声明来显式地实例化模板的某些部分,但这主要用于控制模板实例的生成,以减少编译时间和优化链接过程。不过,更常见的显式实例化定义是直接通过模板实例化语法来完成的,如:

template int compare<int>(const int&, const int&); // 显式实例化int类型的compare函数

但请注意,上面的显式实例化定义语法在C++标准中并不直接支持用于函数模板的实例化(主要用于类或模板成员函数的实例化)。实际上,对于函数模板,我们通常不需要显式实例化定义,因为编译器会根据调用自动进行实例化。然而,为了说明概念,我们可以将其理解为一种“指导编译器生成特定类型实例”的方式。

在类模板的上下文中,显式实例化定义更加常见,例如:

template class SortedArray<int>; // 显式实例化SortedArray模板的int类型版本

这会告诉编译器生成SortedArray<int>类型的具体类定义,即使该类在程序的其他部分没有被直接使用。

模板编译中的实例化声明和实例化定义是模板使用的核心过程。实例化声明是隐式的,由编译器根据上下文自动完成;而实例化定义可以是隐式的,也可以是显式的,其中显式实例化定义主要用于控制模板实例的生成,优化编译和链接过程。通过理解这些过程,可以更好地掌握C++模板编程的精髓。

4. 模板参数

4.1 默认模板实参

默认模板实参允许为模板参数指定默认值,这样在使用模板时,如果没有为这些参数提供具体的值,就会使用默认值。这个功能在定义模板时非常有用,特别是当模板的某些参数在大多数情况下都有相同的值时。

例子
#include <iostream>
#include <vector>// 定义一个模板,具有默认模板实参
template<typename T, int N = 10>
class Buffer {
public:T elem[N]; // 使用模板参数N作为数组大小// 初始化数组Buffer() {for (int i = 0; i < N; ++i) {elem[i] = T(); // 使用T的默认构造函数}}// 打印数组void print() const {for (int i = 0; i < N; ++i) {std::cout << elem[i] << " ";}std::cout << std::endl;}
};int main() {// 使用默认模板实参Buffer<int> b1;b1.print(); // 打印10个0// 明确指定模板实参Buffer<double, 5> b2;b2.print(); // 打印5个0.0return 0;
}
4.2 模板实参推断

模板实参推断是编译器根据函数调用或模板实例化时提供的参数类型,自动推导出模板参数类型的过程。这个过程极大地简化了模板的使用,使得程序员无需显式指定模板参数的类型。

例子

首先,定义一个简单的模板函数:

#include <iostream>
#include <vector>// 模板函数,用于打印容器的所有元素
template<typename Container>
void printContainer(const Container& c) {for (const auto& elem : c) {std::cout << elem << " ";}std::cout << std::endl;
}int main() {std::vector<int> vec = {1, 2, 3, 4, 5};// 模板实参自动推断为std::vector<int>printContainer(vec);// 对于基本类型数组,模板实参推断不会工作,因为数组类型在模板实参推断中有特殊规则// 下面的代码会导致编译错误,除非使用显式模板实参或使用std::begin/std::end// int arr[] = {1, 2, 3, 4, 5};// printContainer(arr); // 错误// 使用std::begin和std::end绕过这个问题printContainer(std::begin(arr), std::end(arr)); // 假设修改printContainer以接受迭代器return 0;
}// 注意:上面的例子在最后使用了迭代器而不是直接传递数组,因为直接传递数组给模板函数通常不会按预期工作。
// 正确的做法是为printContainer提供迭代器版本,或者使用std::array或std::vector等容器。// 迭代器版本的printContainer可能如下:
template<typename Iter>
void printContainer(Iter begin, Iter end) {for (Iter it = begin; it != end; ++it) {std::cout << *it << " ";}std::cout << std::endl;
}

在上面的例子中,printContainer函数能够自动推断出Container的类型,这是基于传递给函数的容器对象的类型。然而,对于原生数组,直接传递通常会导致编译错误,因为数组名在大多数上下文中会退化为指向数组首元素的指针,而不是传递整个数组。为了处理数组,我们可以使用std::beginstd::end来获取数组的迭代器,或者将数组封装在如std::arraystd::vector这样的容器中。

5. 重载与模板

5.1 模板函数与非模板函数的重载

模板函数可以与非模板函数共存,并基于函数调用时提供的参数类型进行重载解析。编译器会首先尝试将函数调用与任何非模板函数匹配。如果没有找到完全匹配的非模板函数,编译器将尝试使用模板函数进行匹配,并通过模板实参推断来确定模板参数的具体类型。

例子

#include <iostream>
#include <string>// 非模板函数
void print(const std::string& s) {std::cout << "Printing string: " << s << std::endl;
}// 模板函数
template<typename T>
void print(T value) {std::cout << "Printing generic type: " << value << std::endl;
}int main() {int num = 42;std::string str = "Hello, world!";// 调用非模板函数print(str); // 输出:Printing string: Hello, world!// 调用模板函数print(num); // 输出:Printing generic type: 42return 0;
}

在这个例子中,print函数被重载了:一个版本接受std::string类型,另一个版本是模板函数,可以接受任何类型。根据传入的参数类型,编译器会选择合适的函数进行调用。

5.2 模板函数之间的重载

模板函数之间也可以进行重载,这要求模板函数之间在参数数量或类型上存在差异,以便编译器能够根据函数调用时的参数来区分它们。

例子

#include <iostream>// 第一个模板函数,接受一个参数
template<typename T>
void func(T x) {std::cout << "Function with one parameter called." << std::endl;
}// 第二个模板函数,接受两个参数,且两个参数类型相同
template<typename T>
void func(T x, T y) {std::cout << "Function with two identical parameters called." << std::endl;
}// 第三个模板函数,接受两个参数,但参数类型可能不同
template<typename T, typename U>
void func(T x, U y) {std::cout << "Function with two different parameters called." << std::endl;
}int main() {func(42);           // 调用第一个模板函数func(1.0, 2.0);     // 调用第二个模板函数func('a', 3.14);    // 调用第三个模板函数return 0;
}

在这个例子中,func函数被重载了三次:一次接受一个参数,一次接受两个相同类型的参数,另一次接受两个可能不同类型的参数。根据函数调用时提供的参数数量和类型,编译器会选择合适的模板函数进行实例化。

5.3 模板特化与重载的交互

模板特化允许为模板的特定类型提供定制化的实现。当模板特化与模板函数或其他模板特化共存时,重载解析规则同样适用。

例子

#include <iostream>// 通用模板函数
template<typename T>
void specialFunc(T x) {std::cout << "Generic version called." << std::endl;
}// 模板特化
template<>
void specialFunc<int>(int x) {std::cout << "Specialized for int called." << std::endl;
}// 另一个模板函数,可以与上面的模板函数共存
template<typename T>
void anotherFunc(T x) {std::cout << "Another function called." << std::endl;
}int main() {specialFunc(42);      // 调用特化版本specialFunc(3.14);    // 调用通用模板版本anotherFunc(42);      // 调用另一个模板函数return 0;
}

在这个例子中,specialFunc模板函数有一个针对int类型的特化版本。当调用specialFunc并传入int类型的参数时,会调用特化版本;如果传入其他类型的参数,则会调用通用模板版本。同时,anotherFunc是另一个模板函数,与specialFunc共存且不受其特化的影响。

通过这些例子,我们可以看到模板与重载之间的交互为C++提供了极大的灵活性和表达能力。

6. 可变参数模板

可变参数模板是C++11中引入的一个新特性,它允许我们定义一个可以接受可变数目参数的模板函数或模板类。这对于编写能够处理任意数量和类型参数的函数或类非常有用。

6.1 定义可变参数模板

可变参数模板通过模板参数包(Template Parameter Pack)和函数参数包(Function Parameter Pack)来实现。模板参数包用于在模板定义中声明可变数量的类型参数,而函数参数包则用于在函数定义中声明可变数量的函数参数。

模板参数包使用typename...class...来声明,后跟一个名称,表示这个包可以包含零个或多个类型。

函数参数包使用类型名称后跟省略号(…)来声明,表示这个函数可以接受零个或多个该类型的参数。

例子:可变参数模板函数
#include <iostream>// 定义一个可变参数模板函数,用于打印任意数量和类型的参数
template<typename T, typename... Args>
void print(std::ostream& os, const T& first, const Args&... rest) {os << first; // 打印第一个参数if constexpr (sizeof...(Args) > 0) { // 如果Args不为空os << ", "; // 在参数之间添加逗号print(os, rest...); // 递归调用,打印剩余参数}
}// 特化版本,用于处理参数包为空的情况,防止无限递归
template<typename T>
void print(std::ostream& os, const T& last) {os << last;
}int main() {std::cout << "Printing with variadic templates: ";print(std::cout, 1, 3.14, "Hello, world!"); // 输出: 1, 3.14, Hello, world!return 0;
}
6.2 参数包的扩展

参数包的扩展是将参数包分解为单独的参数,并对每个参数执行某种操作的过程。在C++中,这通常通过递归函数或逗号表达式与初始化列表来实现。

递归方式:如上例所示,通过递归调用函数本身,每次传递除第一个参数外的剩余参数包。

逗号表达式与初始化列表:利用C++11的初始化列表和逗号运算符,可以在不显式递归的情况下扩展参数包。

6.3 转发参数包

可变参数模板经常与完美转发(Perfect Forwarding)结合使用,以确保参数在传递给其他函数时保持其原始值类别(左值或右值)。这通常通过std::forward和模板参数包中的右值引用来实现。

例子:转发参数包

#include <utility> // for std::forward
#include <vector>template<typename... Args>
void wrapper(std::vector<int>& vec, Args&&... args) {vec.emplace_back(std::forward<Args>(args)...); // 转发参数包到emplace_back
}int main() {std::vector<int> vec;wrapper(vec, 1, 2, 3, 4); // vec现在包含{1, 2, 3, 4}return 0;
}

在这个例子中,wrapper函数接受一个std::vector<int>和一个可变数量的参数。它使用std::forward将参数包转发给emplace_back,从而保持参数的原始值类别。

总结

可变参数模板是C++中非常强大的一个特性,它允许我们编写能够处理任意数量和类型参数的函数和类。通过模板参数包和函数参数包,我们可以灵活地定义和使用这些模板。此外,结合完美转发,我们可以确保参数在传递过程中保持其原始特性,从而编写出更加高效和通用的代码。

7.1 完全特例化(Full Specialization)

在C++中,模板特例化(Template Specialization)是模板编程的一个重要概念,它允许程序员为模板类或模板函数提供特定类型或值的定制实现。这在进行性能优化、处理特殊数据类型或当模板的通用实现不适用于特定情况时特别有用。模板特例化可以分为完全特例化和偏特例化两种形式。

完全特例化是指为模板的所有模板参数提供具体的类型或值。这允许你为特定的类型组合提供定制的实现。

例子

假设我们有一个通用的模板类,用于打印容器的大小,但我们想为std::string提供一个特别的实现,因为std::string的大小不是通过size()成员函数获得的,而是通过length()size()std::string同时提供了这两个成员函数)。然而,为了演示,我们可以假装std::string没有size()函数。

#include <iostream>
#include <vector>
#include <string>// 通用模板类
template<typename Container>
class ContainerSize {
public:static void printSize(const Container& c) {std::cout << "Size: " << c.size() << std::endl;}
};// std::string的完全特例化
template::<>cout<<class " ContainerLengthSize:< "std <<:: sstring> {
public:static void printSize(const std::string& s) {
.        stdlength() << std::endl; // 使用length()而不是size()}
};int main() {std::vector<int> vec = {1, 2, 3, 4, 5};ContainerSize<std::vector<int>>::printSize(vec); // 输出: Size: 5std::string str = "Hello, World!";ContainerSize<std::string>::printSize(str); // 输出: Length: 13return 0;
}

7.2 偏特例化(Partial Specialization)

偏特例化允许你为模板的一部分(而不是全部)模板参数提供具体的类型或值。这仅适用于模板类(不适用于模板函数)。

例子

假设我们有一个模板类,它接受两个类型参数,但我们只想为第一个类型为指针的类型组合提供定制实现。

#include <iostream>// 通用模板类
template<typename T1, typename T2>
class PairOperations {
public:static void perform() {std::cout << "Performing generic operations." << std::endl;}
};// 偏特例化,第一个类型为指针
template<typename T2>
class PairOperations<void*, T2> {
public:static void perform() {std::cout << "Performing special operations for void* and " << typeid(T2).name() << std::endl;}
};int main() {PairOperations<int, double>::perform(); // 输出: Performing generic operations.PairOperations<void*, double>::perform(); // 输出: Performing special operations for void* and dreturn 0;
}

注意:在上面的偏特例化例子中,我使用了void*作为第一个类型的特例化条件,但这仅仅是为了演示。在实际应用中,你可能会选择更具体的类型或类型组合进行偏特例化。

另外,请注意typeid(T2).name()的使用,它用于获取T2的类型名称,但请注意,由于类型名称的实现依赖于编译器,因此得到的名称可能在不同编译器之间有所不同,且可能包含编译器特定的前缀或后缀。

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

【设计模式-备忘录】

备忘录模式&#xff08;Memento Pattern&#xff09;是一种行为型设计模式&#xff0c;用于保存对象的内部状态&#xff0c;以便在将来某个时间可以恢复到该状态&#xff0c;而不暴露对象的内部实现细节。备忘录模式特别适合在需要支持撤销&#xff08;Undo&#xff09;操作的应…

Anthropic介绍Contextual Retrieval

人工智能模型要想在特定环境中发挥作用&#xff0c;往往需要获取背景知识。 例如&#xff0c;客户支持聊天机器人需要了解具体的业务&#xff0c;而法律分析机器人则需要了解大量的过往案例。 开发人员通常使用检索增强生成&#xff08;RAG&#xff09;来增强人工智能模型的知…

LEAN 赋型唯一性(Unique Typing)之 Church-Rosser 定理 (Church-Rosser Theorem)及 赋型唯一性的证明

有了并行K简化的概念及其属性&#xff0c;以及其在LEAN类型理论中的相关证明&#xff0c;就可以证明&#xff0c;在K简化下的Church-Rosser 定理。即&#xff1a; 其过程如下&#xff1a; 证明如下&#xff1a; 其中的 lemma 4.9 和 4.10 &#xff0c;及 4.8 是 这整个证明过程…

ImportError: /lib/x86 64-linux-gnu/libm.so.6:version GLIBc 2.29‘ not found

一、概述 在编译时出现一些问题&#xff0c;在网上搜索之后&#xff0c;对问题进行整理记录。 二、具体解决方法 &#xff08;一&#xff09;问题 如图所示&#xff0c;在编译过程中出现如下的问题。 &#xff08;二&#xff09;问题分析 通过在网络查询&#xff0c;在github…

后端-navicat查找语句(单表与多表)

表格字段设置如图 语句&#xff1a; 1.输出 1.输出name和age列 SELECT name,age from student 1.2.全部输出 select * from student 2.where子语句 1.运算符&#xff1a; 等于 >大于 >大于等于 <小于 <小于等于 ! <>不等于 select * from stude…

传统软件在定制化方面有哪些优势,SaaS 软件如何克服这一劣势?

一、传统软件在定制化优势 传统软件在定制化方面的优势主要体现在以下几个方面&#xff1a; 个性化需求满足&#xff1a;传统软件可以根据客户的特定需求进行个性化定制&#xff0c;提供定制化的解决方案&#xff0c;满足客户的业务流程和功能需求。灵活性和扩展性&#xff1a…

使用 Vue 3、Vite 和 TypeScript 的环境变量配置

使用 Vue 3、Vite 和 TypeScript 的环境变量配置 在开发现代前端应用时&#xff0c;环境变量是一个非常重要的概念。它可以帮助我们根据不同的环境&#xff08;开发、测试、生产&#xff09;配置不同的行为&#xff0c;比如 API 请求地址、调试选项等。在 Vue 3、Vite 和 Type…

一个.NET开发且功能强大的Windows远程控制系统

项目介绍 SiMayRemoteMonitorOS是一个基于Windows的远程控制系统&#xff0c;完全采用C#.NET开发&#xff0c;遵循AGPL-3.0开源协议。 核心功能 远程桌面&#xff1a;基于逐行扫描算法&#xff0c;提供流畅的远程桌面体验&#xff0c;支持多屏幕切换&#xff0c;以及全屏监控…

【C++】类和对象(下):再探构造函数、类型转换、static成员、友元、内部类、匿名对象、拷贝对象时编译器的优化

这篇博文是C中类和对象的最后一些知识&#xff0c;包括再探构造函数、类型转换、static成员、友元、内部类、匿名对象、拷贝对象时编译器的优化这些知识点。 1.再探构造函数 之前我们实现构造函数时&#xff0c;初始化成员变量主要是使用函数体内赋值&#xff0c;构造函数初始化…

neo4j:ubuntu环境下的安装与使用

一、neo4j安装 1. 下载安装包 进入网站&#xff1a;https://neo4j.com/deployment-center/#community 在上图中选择下载即可&#xff08;社区版免费&#xff09; 注意&#xff1a;neo4j的版本要和电脑安装的jdk版本对应&#xff0c;jdk版本使用java --version查看&#xff1a;…

计算机视觉的应用34-基于CV领域的人脸关键点特征智能提取的技术方法

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下计算机视觉的应用34-基于CV领域的人脸关键点特征智能提取的技术方法。本文主要探讨计算机视觉领域中人脸关键点特征智能提取的技术方法。详细介绍了基于卷积神经网络模型进行人脸关键点提取的过程&#xff0c;包括使…

长列表加载性能优化

一、长列表优化概述 列表是应用开发中最常见的一类开发场景&#xff0c;它可以将杂乱的信息整理成有规律、易于理解和操作的形式&#xff0c;便于用户查找和获取所需要的信息。应用程序中常见的列表场景有新闻列表、购物车列表、各类排行榜等。随着信息数据的累积&#xff0c;特…

基于SpringBoot的漫画网设计与实现

博主介绍&#xff1a;专注于Java vue .net php phython 小程序 等诸多技术领域和毕业项目实战、企业信息化系统建设&#xff0c;从业十五余年开发设计教学工作 ☆☆☆ 精彩专栏推荐订阅☆☆☆☆☆不然下次找不到哟 我的博客空间发布了1000毕设题目 方便大家学习使用 感兴趣的…

Java 每日一刊(第14期):抽象类和接口

“抽象是所有能力的精髓。” 前言 这里是分享 Java 相关内容的专刊&#xff0c;每日一更。 本期将为大家带来以下内容&#xff1a; 抽象类接口抽象类和接口的区别什么时候用抽象类&#xff0c;什么时候用接口抽象类可以实现接口接口中的常量其实是 public static final标记…

C语言图形编程:构建视觉效果与应用

引言 在计算机科学的领域中&#xff0c;C语言凭借其简洁、高效以及对底层硬件的强大控制能力&#xff0c;一直是系统级编程的首选语言之一。尽管近年来出现了许多高级语言&#xff0c;但C语言仍然在多个领域占据着重要地位&#xff0c;特别是在图形编程方面。本文将深入探讨如…

粒子向上持续瀑布动画效果(直接粘贴到记事本改html即可)

代码&#xff1a; 根据个人喜好修改即可 <!DOCTYPE html> <html lang"zh"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>宽粒子向上…

MOSFET是什么,终于有了一点点感知

目录 MOSFET是什么&#xff1f;FETMOS MOSFET和功率MOSFETMOSFET功率MOSFET MOSFET是什么&#xff1f; 英文是metal-oxide-semiconductor-field-effect-transistor&#xff0c;金属氧化物半导体场效应晶体管。 可以分开来看一下&#xff0c;MOS和FET FET 其中&#xff0c;FE…

图片类型转化---模拟某wps

文件上传功能的深入探讨 文件上传是Web应用程序中常见的功能&#xff0c;它允许用户将本地文件通过Web界面发送到服务器。在Flask中&#xff0c;这通常是通过处理表单数据来实现的。表单必须设置enctype为multipart/form-data&#xff0c;这样浏览器才能将文件作为多部分消息发…

Linux常用命令(部分学习待继续补充)

pwd print working directory 打印当前的工作目录 / 根目录 ls list 列出当前目录下的所有文件 ls / ls -h(human) ls -l(long) cd change directory 更改目录 cd … 回到上一级目录 ls list ls -l 会列出文件的详细信息 第一个字符是-表示普通文件 d表示是一个目录 rwx read …

keil 下载安装 保姆级教程

一.前言 最近被安排开发一个单片机的项目&#xff0c;回头想了一下&#xff0c;自己上次弄单片机的时候&#xff0c;还都是在大学期间&#xff0c;到现在也有三四年没有碰过了&#xff0c;大部分的知识点都忘了&#xff0c;所以又重新的把以前的笔记和资料&#xff0c;拿出来温…