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

文章目录

  • 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;    //2cout << 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: 30integral: 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.rhkb.cn/news/210507.html

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

相关文章

【Vulnhub 靶场】【CEREAL: 1】【困难】【20210529】

1、环境介绍 靶场介绍&#xff1a;https://www.vulnhub.com/entry/cereal-1,703/ 靶场下载&#xff1a;https://download.vulnhub.com/cereal/Cereal.ova 靶场难度&#xff1a;困难 发布日期&#xff1a;2021年5月29日 文件大小&#xff1a;1.1 GB 靶场作者&#xff1a;Thomas…

外包干了4年,技术退步明显...

先说情况&#xff0c;大专毕业&#xff0c;18年通过校招进入湖南某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测试&#xf…

Rocky Linux 9.3 为 PowerPC 64 位带回云和容器镜像

RHEL 克隆版 Rocky Linux 9.3 今天发布了&#xff0c;作为红帽企业 Linux 发行版 CentOS Stream 和 Red Hat Enterprise Linux 的免费替代版本&#xff0c;现在可供下载。 Rocky Linux 9.3 是在 Rocky Linux 9.2 发布 6 个月之后发布的&#xff0c;它带回了 PowerPC 64 位 Lit…

如何正确使用高防IP?高防IP使用教程

随着网络攻击的日益猖獗&#xff0c;越来越多的企业开始重视网络安全&#xff0c;并选择使用高防IP来保护自己的服务器。但是&#xff0c;使用高防IP并不是简单的开通服务就能够达到防护效果的&#xff0c;它需要正确的使用方法才能够发挥出应有的作用。 本文将向大家介绍如何正…

公有云迁移研究——AWS DMS

大纲 1 什么是DMS2 DMS的作用3 DMS在迁移的时候都做些什么4 在使用DMS的时候我们需要做些什么5 操作5.1 创建两个数据库终端节点5.2 创建迁移任务 6 可能遇到的问题7 总结 在本地机房或其他云往AWS上做迁移时&#xff0c;往往会遇到数据库迁移的任务。如果数据量不是特别大&…

一篇吃透大厂面试题,2024找工作一帆风顺。

&#x1f3c6;作者简介&#xff0c;普修罗双战士&#xff0c;一直追求不断学习和成长&#xff0c;在技术的道路上持续探索和实践。 &#x1f3c6;多年互联网行业从业经验&#xff0c;历任核心研发工程师&#xff0c;项目技术负责人。 &#x1f389;欢迎 &#x1f44d;点赞✍评论…

MySQL -DDL 及表类型

DDL 创建数据库 CREATE DATABASE [IF NOT EXISTS] db_name [create_specification [, create_specification] ...] create_specification:[DEFAULT] CHARACTER SET charset_name [DEFAULT] COLLATE collation_name 1.CHARACTER SET&#xff1a…

Python查看文件列表

os.listdir 是 Python 的一个内置函数&#xff0c;用于列出指定目录中的所有文件和子目录。它接受一个字符串参数&#xff0c;即要列出内容的目录的路径。 列出当前工作目录中的所有文件和子目录 files_and_dirs os.listdir() print(files_and_dirs) 列出指定目录中的所…

支付宝沙箱支付

1. 二维码 1.1 什么是二维码&#xff1a; ​ 二维码又称QR Code&#xff0c;QR全称Quick Response&#xff0c;是一个近几年来移动设备上超流行的一种编码方式&#xff0c;它比传统的Bar Code条形码能存更多的信息&#xff0c;也能表示更多的数据类型。 ​ 二维条码/二维码&…

LeetCode437.路径总和III

看完题目我就拿直接用递归写了如下代码&#xff1a; class Solution {private int ans;public int pathSum(TreeNode root, int targetSum) {ans 0;dfs(root, targetSum, 0);return ans;}public void dfs(TreeNode root, int targetSum, int sum){if(root null)return;sum r…

深入解析JVM内存结构:Metaspace、堆与垃圾收集器

&#x1f680; 作者主页&#xff1a; 有来技术 &#x1f525; 开源项目&#xff1a; youlai-mall &#x1f343; vue3-element-admin &#x1f343; youlai-boot &#x1f33a; 仓库主页&#xff1a; Gitee &#x1f4ab; Github &#x1f4ab; GitCode &#x1f496; 欢迎点赞…

Mysql进阶-事务锁

前置知识-事务 事务简介 事务 是一组操作的集合&#xff0c;它是一个不可分割的工作单位&#xff0c;事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求&#xff0c;即这些操作要么同时成功&#xff0c;要么同时失败。 就比如: 张三给李四转账1000块钱&#xff0…

龙迅#LT8311X3 USB中继器应用描述!

1. 概述 LT8311X3是一款USB 2.0高速信号中继器&#xff0c;用于补偿ISI引起的高速信号衰减。通过外部下拉电阻器选择的编程补偿增益有助于提高 USB 2.0 高速信号质量并通过 CTS 测试。 2. 特点 • 兼容 USB 2.0、OTG 2.0 和 BC 1.2• 支持 HS、FS、LS 信令 • 自动检测和补偿 U…

Python+Requests模块添加cookie

请求中添加cookies 对于某些网站&#xff0c;登录然后从浏览器中获取cookies&#xff0c;以后就可以直接拿着cookie登录了&#xff0c;无需输入用户 名密码。 一、在参数中添加cookie 在发送请求时使用cookies 代码示例&#xff1a; import requests # 1&#xff0c;在参数…

文心一言 VS 讯飞星火 VS chatgpt (150)-- 算法导论12.2 6题

六、用go语言&#xff0c;考虑一棵二叉搜索树 T &#xff0c;其关键字互不相同。证明&#xff1a;如果 T 中一个结点 x 的右子树为空&#xff0c;且 x 有一个后继 y &#xff0c;那么 y 一定是 x 的最底层祖先&#xff0c;并且其左孩子也是 x 的祖先。(注意到&#xff0c;每个结…

深入浅出 Linux 中的 ARM IOMMU SMMU III

系统 I/O 设备驱动程序通常调用其特定子系统的接口为 DMA 分配内存&#xff0c;但最终会调到 DMA 子系统的 dma_alloc_coherent()/dma_alloc_attrs() 等接口。dma_alloc_coherent()/dma_alloc_attrs() 等接口通过 DMA IOMMU 的回调分配内存&#xff0c;并为经过 IOMMU 的 DMA 内…

微信小程序之猜数字和猜拳小游戏

目录 效果图 app.json 一、首页&#xff08;index3&#xff09;的代码 wxml代码 wxss代码 二、猜数字页面&#xff08;index&#xff09;代码 wxml代码 wxss代码 js代码 三.游戏规则页面&#xff08;logs&#xff09;代码 wxml代码 wxss代码 四.猜拳页面&#xff…

java--接口概述

1.认识接口 ①java提供了一个关键字interface&#xff0c;用这个关键字我们可以定义出一个特殊的结构&#xff1a;接口。 ②注意&#xff1a;接口不能创建对象&#xff1b;接口是用来被类实现(implements)的&#xff0c;实现接口的类称为实现类。 ③一个类可以实现多个接口(接…

Web安全-初识SQL注入(一)

1、初识SQL注入 1.1、什么是注入&#xff1f; 将不受信任的数据作为命令或查询的一部分发送到解析器时&#xff0c;会产生诸如SQL注入、NoSQL注入、OS 注入和LDAP注入的注入缺陷。攻击者的恶意数据可以诱使解析器在没有适当授权的情况下执行非预期命令或访问数据。 注入能导…

苍穹外卖项目笔记(5)——Redis

1 入门 1.1 Redis 简介 Redis 是一个基于内存的 key-value 结构数据库&#xff0c;官网链接&#xff08;中文&#xff09;&#xff1a;https://www.redis.net.cn 特点&#xff1a; 基于内存存储&#xff0c;读写性能高适合存储热点数据&#xff08;热点商品、资讯、新闻&am…