C++ Primer 第十六章 模板与泛型编程 重点解读

news2024/12/25 22:26:32

文章目录

  • 1 定义模板
    • 1.1 类模板成员函数的实例化:
    • 1.2 在类代码内简化模板类名的使用:
    • 1.3 令模板自己的类型参数成为友元(C++11)
    • 1.4 模板类型别名
      • 1.4.1 typedef
      • 1.4.2 为模板定义类型别名(C++11)
    • 1.5 函数模板与类模板的区别
    • 1.6 使用类的类型成员——`typename`
    • 1.7 成员模板
    • 1.8 控制实例化(C++11)
      • 1.8.1 实例化定义会实例化所有成员
    • 1.9 ⭐关于智能指针的删除器
      • 1.9.1 [在运行时绑定删除器](https://gcc.gnu.org/onlinedocs/libstdc++/libstdc++-api-4.6/a01034_source.html) —— `shared_ptr`
      • 1.9.1 在编译时绑定删除器 ——`unique_ptr`
  • 2 模板实参推断
    • 2.1 类型转换与模板类型参数
    • 2.2 函数模板显示实参
    • 2.3 尾置返回类型(C++11)与类型转换
    • 2.4 进行类型转换的标准库模板类
    • 2.5 引用折叠和右值引用参数
    • 2.6 理解 `std:move`
    • 2.7 在调用中使用`std::forward`保持类型信息
  • 3 重载与模板
    • 3.1 如何选择重载版本
    • 3.2 处理C风格字符串指针和字符串字面常量
  • 4 可变参数模板(C++11)
    • 4.1 编写可变模板参数函数模板
    • 4.2 包扩展
    • 4.3 转发参数包 —— `emplace`
  • 5 模板特例化
    • 5.1 类模板特例化
    • 5.2 类模板部分特例化(偏特化)
      • 5.2.1 类模板的全特化和偏特化
      • 5.2.2 通过类模板的偏特化实现`std::remove_reference`
      • 5.2.3 函数模板不支持偏特化
    • 5.3 间接的对函数模板进行偏特化
      • 5.3.1 借助类模板偏特化
  • ※ 5.3.2`Concepts`(C++20)
      • 5.3.3 使用concept间接实现函数模板的偏特化

1 定义模板

1.1 类模板成员函数的实例化:

如果一个成员函数没有被使用,则它不会被实例化。成员函数只有在被用到时才进行实例化

1.2 在类代码内简化模板类名的使用:

在一个类模板的作用域内,我们可以直接使用模板名而不必指定模板实参

1.3 令模板自己的类型参数成为友元(C++11)

template<typename T>
class Test2 {
	friend T;
private:
	int t2 = 0;
};


class Test1 {
public:
	void func() {
		Test2<Test1> t;
		cout << t.t2 << endl;    //访问Test2类的私有成员
	}
private:
	int t = 1;

};

int main() {
	Test1 t;
	t.func();
	return 0;
}

1.4 模板类型别名

1.4.1 typedef

  • typedef可以为一个实例化的模板定义别名,但是不能定义一个typedef引用一个模板
typedef Blob<string> StrBlob;   //ok
typedef Blob<T> TBlob  //error

1.4.2 为模板定义类型别名(C++11)

  • template<typename T> using twin = pair<T, T>;
    twin<string> authors;  //authors 的类型为pair<string, string>
    
  • template<typename T> using twin = pair<T, int>;
    twin<string> authors;    //authors 的类型为pair<string, int>
    
    

1.5 函数模板与类模板的区别

  • 默认参数:函数模板不允许由默认参数(C++11之后支持了函数模板的默认实参);类模板在模板参数列表中可以有默认参数
  • 特化:函数模板只能全特化;类模板可以全特化、偏特化
  • 调用方式:函数模板可以进行类型推导、隐式调用、显示调用;类模板只能显示调用
template<typename T> constexpr
auto func(const T& x, const T& y) -> decltype(x + y) {
	cout << typeid(T).name() << ": ";
	return x + y;
}

int main() {
	int x1 = 10, y1 = 20;
	cout << func<int>(x1, y1) << endl;    //显示

	double x2 = 2.2, y2 = 3.3;
	cout << func(x2, y2) << endl;	//隐式
}

1.6 使用类的类型成员——typename

string::size_type* t;

上述代码由于编译器有string的定义,从而知道size_type是一个类型

  • 在普通(非模板)代码中,编译器掌握类的定义,因此,他知道通过作用域访问的名字是类型还是static成员。
T::size_type * p;    
//1.p是一个size_type类型的指针
//2.表达式为size_type这个静态成员与p相乘

如果T是一个模板类型参数,编译器无法知道size_type是一个类型还是一个static成员,

  • 默认情况下,C++语言假定通过作用域运算符(::)访问的名字不是类型。因此,我们需要手动声明使用一个模板类型参数的类型成员——typename
template <typename T>
typename T::value_type top(const T& c) {
    if (!c.empty()) return c.back();
    else return typename T::value_type();    //当容器为空,返回容器的value_type的默认值
}

1.7 成员模板

  • 一个类可以包含本身是模板的成员函数。这种成员被称为成员模板

问:为什么成员模板不能是虚函数?

答:C++中的成员模板函数不能被声明为虚函数。这是因为模板函数只有在使用时才会被实例化而虚函数表的大小在编译期间就需要确定。如果允许成员模板函数为虚函数,那么编译器就必须知道我们一共为该成员模板函数实例化了多少个不同的版本,以便确定类的虚函数表的大小。这是不可能的,因为模板函数可以被实例化为任意数量的版本,这取决于模板参数的类型和数量。

  • 实例:定义unique_ptr的删除器

1.8 控制实例化(C++11)

在大系统中,在多个文件中实例化相同模板的额外开销可能非常严。在新标准中,我们可以通过显示实例化(explicit instantiation)来避免这种开销

//显式实例化的形式:
template [declaration];  	//实例化定义

extern template [declaration];   //实例化声明
//Application.cc

//这些模板类型必须在程序其他位置进行实例化
extern template class Blob<string>;
extern template int compare(const int&, const int&);
Blob<string> sa1, sa2;  	//实例化会出现在其他位置

//接收initializer_list的构造和拷贝构造函数在本文件中实例化
Blob<int> a1 = {0,1,2,3}		
Blob<int> a2(a1);

int i = compare(a1[0], a2[0]); 		//实例化出现在其他位置
//templateBuild.cc
//实例化文件必须为每个在其他文件中声明extern的类型和函数提供一个(非extern)的定义
template int compare(const int&, const int&);
template class Blob<string>;    //实例化模板的所有成员

1.8.1 实例化定义会实例化所有成员

一个类模板的实例化定义会实例化该模板的所有成员,包括内联的成员函数。因为当编译器遇到一个实例化定义时,他不了解程序使用哪些成员函数

1.9 ⭐关于智能指针的删除器

shared_ptr:

在这里插入图片描述

unique_ptr:

在这里插入图片描述

通过观察shared_ptrunique_ptr的模板可以发现,shared_ptr的删除器只需要在创建或reset指针时传递给它一个可调用对象即可;而删除器类型是一个unique_ptr对象的类型的一部分,用户必须在定义unique_ptr时以显示模板实参的形式提供删除器类型

1.9.1 在运行时绑定删除器 —— shared_ptr

  • shared_ptr必须能直接访问其删除器,删除器必须保存为一个指针或一个封装了指针的类(如function)
int main() {
	int* p = new int(1);
	shared_ptr<int> sp(p, [](int* p) { delete(p); });
	int* p2 = new int(2);
	sp.reset(p2, [](int* p) { 
		delete(p); 
		cout << "haha" << endl; 
		});
    //程序最后打印haha 
}

我们可以确定shared_ptr不是将删除器直接保存为一个成员,因为删除器的类型知道运行时才会确定

1.9.1 在编译时绑定删除器 ——unique_ptr

unqiue_ptr类中,删除器的类型是类类型的一部分,即unique_ptr有两个模板参数.

由于删除器的类型是unique_ptr类型的一部分,因此删除器成员的类型在编译时就确定了。从而删除器可以直接保存在unique_ptr对象中

通过在编译时绑定删除器,unique_ptr避免了间接调用删除器的运行时开销。

通过在运行时绑定删除器,shared_ptr使用户重载删除器更为方便

2 模板实参推断

2.1 类型转换与模板类型参数

template <typename T> T fobj(T t1, T t2) { return T(); }
template <typename T> T fref(const T& t1, const T& t2) { return T(); }
int main() {
	int x1 = 1;
	const int x2 = 2;
	fobj(x1, x2);   //调用fobj(int, int)
	fref(x1, x2);	//调用fref(const int&, const int&)

	int a[10], b[10];
	fobj(a, b);		//调用f(int*, int*)
	fref(a, b);		//error:没有与参数列表匹配的函数模板
}

不能用于函数模板的类型转换:

  1. 算数转换
  2. 派生类向基类的转换
  3. 用户定义的转换

2.2 函数模板显示实参

template<typename T1, typename T2, typename T3>
T1 sum(T2 x, T3 y) {
	return x + y;
}

int main() {
	cout << sum<int>(1.1, 1) << endl;    //2
	cout << sum<double>(1.1, 1) << endl;     //2.1
}

2.3 尾置返回类型(C++11)与类型转换

template <typename It>
auto fcn(It beg, It en) -> decltype(*beg) {
	return *beg;
}

解引用返回一个序列的左值,因此通过decltype推断的类型为beg表示的元素的类型的引用

2.4 进行类型转换的标准库模板类

  • 其中typeremove_reference模板类的一个类型成员
template<typename It>
//为了使用模板参数的成员,type是一个类的成员,而该类依赖于一个模板参数,因此我们必须使用typename
//type的类型为非引用的
auto func2(It beg, It end) -> typename remove_reference<decltype(*beg)>::type {
	reutrn *beg;
}

在这里插入图片描述

其他标准类型转换模板在书606页

习题:

template<typename It>
//为了使用模板参数的成员,必须用typename
auto func2(It beg, It end) ->  decltype(*beg + 0) {		//合法:左值引用 + 右值 -> 左值
	return *beg;    //返回序列中一个元素的拷贝
}

int main() {
	list<int> lt{ 1,2 };
	decltype(func2(lt.begin(), lt.end())) t = 10;
}

2.5 引用折叠和右值引用参数

template <typename T> void func(T&&);

int main() {
	int i = 0;
	func(i);    //看起来实参的类型为int& && 
}
  • 我们不能直接定义一个引用的引用, 但是可以通过类型别名模板类型参数间接定义。
  • 新标准中(C++11), 折叠规则拓展到右值引用
    • X& &X& &&X&& & --折叠–> X&
    • X&& && --折叠–> X&&
typedef int& ref_type;
typedef ref_type&& refs_type;
int main() {
	int x = 10;
	refs_type y = x;   //y是一个左值引用:int& && -> 引用折叠 -> int&
}

2.6 理解 std:move

//std::move是如何定义的:
template <typename T>
typename remove_reference<T>::type&& move(T&& t) {
	return static_cast<typename remove_reference<T>::type&&>(t);
}

//考虑以下两种情况  P611
int main() {
	string s1("lvalue s1"), s2;
    s = move(string("rvalue s"));	//右值移动
    s = move(s1);	//左值移动,移动之后,s1的值是不确定的
}

2.7 在调用中使用std::forward保持类型信息

3 重载与模板

3.1 如何选择重载版本

  • 当有多个重载模板对一个调用提供同样好的匹配时,因该选择最特例化的版本
template <typename T> string debug_rep(const T& t) {}

template <typename T> string debug_rep(T* t) {}

int main() {
	string s("ss");
	const string* sp = &s;
	debug_rep(sp);    //调用第二个,T被绑定到const string
}
  • 如果一个非模板函数和一个模板函数提供同样好的匹配,则选择非模板版本

3.2 处理C风格字符串指针和字符串字面常量

4 可变参数模板(C++11)

4.1 编写可变模板参数函数模板

 template <typename T>
 ostream& print(ostream& os, const T& t) {
	 return os << t;
 }

 template <typename T, typename... Args>
 ostream& print(ostream& os, const T& t, const Args&... args) {
	 os << t << ' ';
	 return print(os, args...);    //包拓展
 }

4.2 包扩展

  1. 函数参数包名 + ...
print(os, args...); 
  1. function(函数参数包名) + ...
print(os, debug_rep(args...));    //扩展中的模式会独立地应用于包中的元素

4.3 转发参数包 —— emplace

template <class... Args>
inline
void StrVec::emplace_back(Args&&... args) {
	chk_n_alloc();
	alloc.construct(first_free++, std::forward<Args>(args)...);
}

5 模板特例化

5.1 类模板特例化

namespace std {
    template<>
    struct hash<Sales_data> {
        //用来散列一个无需容器的类型必须要定义下列类型
        typedef size_t result_type;
        typedef Sales_data argument_type;    //默认情况下,此类型需要operator==
        size_t operator()(const Sales_data& s) const {
            return hash<string>() (s.bookNo) ^      //构造匿名对象调用operator()返回一个哈希值
                   hash<unsigned>() (s.units_sold) ^ 
                   hash<double>() (s.revenue);
        }
    };
}  //关闭std命名空间

5.2 类模板部分特例化(偏特化)

5.2.1 类模板的全特化和偏特化

我们只能部分特例化类模板,而不能部分特例化函数模板 —— P628

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

template <typename T>  //类模板的全特化
class add<T*> { 
public:
	T operator()(T* a, T* b) {
		cout << "pointer" << endl;
		return *a + *b;
	}
};

template<>
class add<int*> {   //类模板的偏特化
public:
	int operator()(int* a, int* b) {
		cout << "int pointer" << endl;
		return *a + *b;
	}
};

5.2.2 通过类模板的偏特化实现std::remove_reference

代码更具模板的匹配规则进行精确的匹配,当传入的是一个右值引用时,匹配第三个模板,此时T的类型为去掉右值引用性质之后的类型

template<class T> struct remove_reference {
	typedef T type;
};
template<class T> struct remove_reference<T&> {
	typedef T type;
};
template<class T> struct remove_reference<T&&> {
	typedef T type;
};

5.2.3 函数模板不支持偏特化

问:为什么函数模板不能支持偏特化,如果可以,发产生怎样的歧义?

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

template <typename T*>   //函数模板的偏特化,错误,产生歧义
T add(T* a, T* b) {
return *a + *b;
}

template<>
int add(int a, int b) {   //函数模板的全特化
	return a + b;
}

/*
template<>
class add<int*> {   //类模板的偏特化
public:
	int operator()(int* a, int* b) {
		cout << "int pointer" << endl;
		return *a + *b;
	}
};
*/

在这个例子中,如果我们尝试调用add(a, b),其中a和b都是指针类型,编译器将无法确定应该调用哪个函数。因为第一个函数模板可以通过类型推导将T推导为指针类型,而第二个函数模板是针对指针类型的偏特化版本。这就产生了歧义,编译器无法确定应该调用哪个函数。

5.3 间接的对函数模板进行偏特化

C++标准并不支持函数模板偏特化。然而在实际开发中,我们确实需要对一些函数模板进行偏特化。这是因为偏特化和函数重载容易出现歧义。

  1. 借助类模板偏特化:由于类可以进行偏特化处理,因此一种非常直观的方案就是使用Functor代替函数,并实现 operator ()。详细例子看5.2.1

  2. 使用Concepts:C++20提供了 Concepts 特性, Concepts 特性提出的动机是为了解决模板元编程过程中,编译器给出的报错信息冗余及编译器不能很好的给出准确的出错信息的问题。

5.3.1 借助类模板偏特化

template <typename T>
class add {
public:
	T operator()(T a, T b) {
		return a + b;
	}
};
template <typename T>  //全特化
class add<T*> {
public:
	T operator()(T* a, T* b) {
		cout << "pointer" << endl;
		return *a + *b;
	}
};
template<>
class add<int*> {   //偏特化
public:
	int operator()(int* a, int* b) {
		cout << "int pointer" << endl;
		return *a + *b;
	}
};

int main() {
	cout << add<int>()(10, 20) << endl;
	int x = 10, y = 20;
	cout << add<int*>()(&x, &y) << endl;
}

※ 5.3.2Concepts(C++20)

Concepts的提出就是为了解决上述问题,它通过将模板的类型约束抽象出来,然后在模板定义时再使用。这样成功解耦了模板类型约束和模板本身的一些类型逻辑

  • concept的定义形式
template < template-parameter-list > concept concept-name = constraint-expression;

其中,constraint-expression 是一个可以被evalbool的表达式或者编译期函数。在使用定义好的concept时,constraint-expression 会根据上面 template-parameter-list 传入的类型,执行编译期计算,判断使用该concept的模板定义是否满足。如果不满足,则编译期会给定一个具有明确语义的错误

template <typename T>
concept always_satisfied = true;

 /*		
 	将类型T抽象成integral类型,当模板实参使constraint-expression为true时,
 	才匹配template<inntegral T>的函数模板
 */
template <typename T>
concept integral = std::is_integral_v<T>;   

template <typename T>
concept signed_integral = integral<T> && std::is_signed_v<T>;

template <always_satisfied T>
T mul(T a, T b) {
    puts("always_satisfied");
    return a * b;
}

template <integral T>
T add(T a, T b) {
    puts("integral");
    return a + b;
}

template <signed_integral T>
T subtract(T a, T b) {
    puts("signed_integral");
    return a - b;
}

int main() {
    mul(1, 2);
    mul(1.0f, 2.0f);
    add(1, 2);
    subtract(1, 2);
}

5.3.3 使用concept间接实现函数模板的偏特化

  • 我们以5.2.2的代码为基准,使用concept编写函数模板的偏特化
//T是一个整数类型
template <typename T>
concept integral = std::is_integral_v<T>;

//T为一个整数类型的指针,特别注意,使用remove_pointer去除模板实参的指针性质
template <typename T>
concept integral_pointer = integral<typename std::remove_pointer<T>::type> && std::is_pointer_v<T>;

template <integral T>
void add(T a, T b) {
    cout << "integral: " << a + b << endl;
}

template <integral_pointer T>
void add(T a, T b) {

    cout << "integral_pointer: " << *a + *b << endl;
}

int main() {
    int x = 10, y = 20;
    add(&x, &y);
    add(10, 20);
     /*
    	output:
    	integral_pointer: 30
		integral: 30
    */
    
    float a = 1.1, b = 2.2;
    add(a, b);    //error:没有于参数列表匹配的重载函数,add实例参数类型为(float, float)
    add(&a, &b);    //error:没有于参数列表匹配的重载函数,add实例参数类型为(float*, float*)
}

template
concept integral_pointer = integral<typename std::remove_pointer::type> && std::is_pointer_v;

template
void add(T a, T b) {
cout << "integral: " << a + b << endl;
}

template <integral_pointer T>
void add(T a, T b) {

cout << "integral_pointer: " << *a + *b << endl;

}

int main() {
int x = 10, y = 20;
add(&x, &y);
add(10, 20);
/*
output:
integral_pointer: 30
integral: 30
*/

float a = 1.1, b = 2.2;
add(a, b);    //error:没有于参数列表匹配的重载函数,add实例参数类型为(float, float)
add(&a, &b);    //error:没有于参数列表匹配的重载函数,add实例参数类型为(float*, float*)

}


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

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

相关文章

如何撰写How-To文章:7个高效步骤(新手SEOer向导)

How-To类型的文章在各种领域都非常受欢迎&#xff0c;因为它们直接回应了读者的需求和问题。因此&#xff0c;很多网站也会针对一些信息型的关键词写很多How-To文章吸引流量。 “How-To”类文章也是我最喜欢的一种内容类型。因为它比起其他大部分内容类型来讲要容易写很多。 …

java开发之个微机器人的实现

简要描述&#xff1a; 二次登录 请求URL&#xff1a; http://域名地址/secondLogin 请求方式&#xff1a; POST 请求头Headers&#xff1a; Content-Type&#xff1a;application/jsonAuthorization&#xff1a;login接口返回 参数&#xff1a; 参数名必选类型说明wcId…

Vue框架学习笔记——列表渲染:v-for

文章目录 前文提要代码正文 前文提要 本人仅做个人学习记录&#xff0c;如有错误&#xff0c;请多包涵 主要学习链接&#xff1a;尚硅谷Vue2.0Vue3.0全套教程丨vuejs从入门到精通 代码正文 <body><div id"box"><ul><li v-for"(p,index)…

印刷生产ERP有哪些?印刷生产ERP系统哪一个好用

印刷生产行业发展历史悠久&#xff0c;不同规模的印刷生产工厂有差异化的经营策略和管理侧重点&#xff0c;同时在日常经营过程中也会遇到多样化的管理难题。 有些企业在车间排期、库存盘点、生产流程优化、资源整合、成本核算等方面存在不少管理问题。同时也有不少印刷生产企…

Java异常机制入门详解(上)

Java异常机制入门详解 基础知识 什么是异常&#xff1f; 在Java语言中&#xff0c;异常就是代表程序出现的问题。异常的引入不是让我们以后不出异常&#xff0c;而是程序出了异常之后&#xff0c;该如何处理。如如下两段代码&#xff0c;会各抛出一个异常&#xff1a; int[…

ssrf介绍、相关php函数及demo演示

SSRF系列 危害&#xff08;利用&#xff09; 漏洞判断 回显 延时 DNS请求 相关函数

W11+Ipv6+可道云+PHPstudy实现私人云盘搭建

W11Ipv6可道云PHPstudy实现私人云盘搭建 一、搭建原因二、搭建过程软件选择服务器环境管理软件私人云盘 可道云搭建小皮面板搭建 三、相关配置程序开机自启远程关机远程开机 四、相关参考 一、搭建原因 工位电脑上一些文件想备份到家里电脑&#xff0c;购买NAS又有点多余&…

如何使用内网穿透工具实现公网访问GeoServe Web管理界面

文章目录 前言1.安装GeoServer2. windows 安装 cpolar3. 创建公网访问地址4. 公网访问Geo Servcer服务5. 固定公网HTTP地址6. 结语 前言 GeoServer是OGC Web服务器规范的J2EE实现&#xff0c;利用GeoServer可以方便地发布地图数据&#xff0c;允许用户对要素数据进行更新、删除…

【AntDB 数据库】国产数据库发展之信创政策的加持

由于我国在信息技术领域起步较晚&#xff0c;国内大量的市场份额被国际IT巨头占据&#xff0c;甚至长期处于被垄断的地位&#xff0c;这也给了某些国家妄图通过挑起科技、贸易摩擦制衡我国发展的机会。为了解决可能存在的安全风险&#xff0c;在重要信息系统、关键基础设施中使…

云原生之深入解析Kubernetes策略引擎对比:OPA/Gatekeeper与Kyverno

一、前言 ① Kubernetes 策略 Kubernetes 的 Pod Security Policy&#xff0c;正如其名字所暗示的&#xff0c;仅是针对 Pod 工作的&#xff0c;是一种用来验证和控制 Pod 及其属性的机制。另外 PSP 只能屏蔽非法 Pod 的创建&#xff0c;无法执行任何补救/纠正措施。而 Gatek…

Redis持久化及常见问题解决

持久化缓存雪崩缓存穿透缓存击穿缓存预热 持久化 Redis的储存形式&#xff1a;一份在内存、一份在磁盘。内存的是最新的&#xff1b;磁盘里的会隔一段时间更新。 Redis持久化方式&#xff1a; RDB:快照方式&#xff1b;将某⼀个时刻的内存数据&#xff0c;以⼆进制的⽅式写⼊…

Hana Studio打开BW失败

Hana Studio打开BW失败 JCo initialization failed with java.lang.UnsatisfiedLinkError: D:\ycy\BW培训\HANA\configuration\org.eclipse.osgi\357\0.cp\lib\sapjco3.dll: Can’t find dependent libraries 这个提示应该是VC版本问题&#xff0c;按如下链接中的地址下载安装…

【头歌实训】分布式文件系统 HDFS

文章目录 第1关&#xff1a;HDFS的基本操作任务描述相关知识HDFS的设计分布式文件系统NameNode与DataNode HDFS的常用命令 编程要求测试说明答案代码 第2关&#xff1a;HDFS-JAVA接口之读取文件任务描述相关知识FileSystem对象FSDataInputStream对象 编程要求测试说明答案代码 …

【开源】基于Vue+SpringBoot的民宿预定管理系统

项目编号&#xff1a; S 058 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S058&#xff0c;文末获取源码。} 项目编号&#xff1a;S058&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 用例设计2.2 功能设计2.2.1 租客角色…

面试题之分布式事务篇

1.什么是分布式事务&#xff1f; 概述&#xff1a;在分布式系统上一次大的操作由不同的小操作组成&#xff0c;这些小的操作分布在不同的服务节点上&#xff0c;且属于不同的应用&#xff0c;分布式事务需要保证这些小操作要 么全部成功&#xff0c;要么全部失败。 如下所示&…

windows 系统读取 Linux Ext 分区硬盘

windows 系统读取 Linux Ext 分区硬盘 起因是需要处理一些在硬盘中的数据&#xff0c;硬盘插到我的电脑上后无法读盘&#xff0c;在 Windows 存储管理器中查看显示未分配&#xff0c;需要格式化&#xff0c;但是在 Ubuntu 系统的电脑中可以正常识别&#xff0c;试验了几次后发…

从印刷机到数据库:自动化如何提高生产力和改善生活

一文了解从自动化到数据库的前世今生。 作者&#xff1a;Kevin Kline 本文来源&#xff1a;https://www.dbta.com/&#xff0c;爱可生开源社区翻译。 本文约 2200 字&#xff0c;预计阅读需要 8 分钟。 自动化的历史 人类长期以来一直梦想着自动化的前景。我们最早的祖先将其想…

大数据集群增加数据盘,平衡数据盘HDFS Disk Balancer

大数据集群增加数据盘&#xff0c;平衡数据盘HDFS Disk Balancer 官网&#xff1a;https://hadoop.apache.org/docs/r3.3.6/hadoop-project-dist/hadoop-hdfs/HDFSDiskbalancer.html hdfs diskbalancer -execute /system/diskbalancer/nodename.plan.jsonhdfs diskbalancer -q…

手把手教你如何搭建SSM框架、图书商城系统案例

我 | 在这里 &#x1f575;️ 读书 | 长沙 ⭐软件工程 ⭐ 本科 &#x1f3e0; 工作 | 广州 ⭐ Java 全栈开发&#xff08;软件工程师&#xff09; &#x1f383; 爱好 | 研究技术、旅游、阅读、运动、喜欢流行歌曲 ✈️已经旅游的地点 | 新疆-乌鲁木齐、新疆-吐鲁番、广东-广州…

[PyTorch][chapter 4][李宏毅深度学习][Gradient Descent]

前言&#xff1a; 目录: 1: 梯度下降原理 2: 常见问题 3&#xff1a; 梯度更新方案 4&#xff1a; 梯度下降限制 一 梯度下降原理 机器学习的目标找到最优的参数,使得Loss 最小 为什么顺着梯度方向loss 就能下降了。主要原理是泰勒公式。 假设损失函数为 忽略二阶导数, 当 …