抽象之诗:C++模板的灵魂与边界

引言
在计算机科学的浩瀚长河中,C++模板如同一颗璀璨的星辰,以其独特的泛型编程方式为程序设计注入了灵魂。它是抽象的艺术,是类型的舞蹈,是效率与灵活性的交响乐。模板不仅是一种技术工具,更是一种哲学思考,它模糊了代码与数学、静态与动态、具体与抽象之间的界限。
本文将带领读者踏上一场关于C++模板的奇妙旅程。从基础语法到元编程的深水区,从历史背景到现代实践,这篇文章试图揭示模板背后那既精妙又深刻的逻辑与设计思想。让我们一起追溯这首「抽象之诗」,一窥C++模板的灵魂与边界。
由于之前已经介绍过模板的基础知识,在此处只做简要回顾,如有遗忘可移步复习。
https://blog.csdn.net/2303_81060385/article/details/141167597
一. 模板初阶回顾
1.1 模板的诞生——从类型的束缚中解放
C++模板的灵感来源于一种思想:程序的核心应当是逻辑,而非具体类型。在传统的程序设计中,我们常常为不同的数据类型书写类似的代码,而模板以一种优雅的方式将类型抽象化,使逻辑可以跨越不同的类型。
函数模板的初识:
template <typename T>
T max(T a, T b) {return (a > b) ? a : b;
}
这段代码展示了模板的灵魂——将类型延迟到编译期决定,从而让逻辑摆脱具体类型的束缚。模板让我们无需为每一种类型单独实现类似的功能,赋予了代码前所未有的灵活性。
1.2 模板的多面性——灵魂的多维投影
函数模板:函数的泛型化
模板赋予函数一种「多态性」,使其可以适配不同的类型,同时保持静态类型检查的安全性。
template <typename T>
T add(T a, T b) {return a + b;
}
编译器在调用时实例化该模板,根据参数类型生成特定的代码:
int x = add(1, 2); // 实例化为int版
double y = add(1.5, 2.5); // 实例化为double版
函数模板的意义在于,它让函数摆脱了具体类型的桎梏,使逻辑得以泛化,而无须妥协于动态类型语言的安全性。
类模板:面向对象与泛型的结合
类模板则是模板世界的另一个维度,它让我们能够以泛型方式定义类,从而适应不同的对象模型。
template <typename T>
class Stack {
private:std::vector<T> elements;
public:void push(const T& elem) { elements.push_back(elem); }T pop() {T elem = elements.back();elements.pop_back();return elem;}
};
类模板的出现,彻底改变了面向对象编程的格局,使抽象能力得以提升到一个全新的高度。它让类不仅可以操作不同的数据类型,还可以通过模板参数进一步调整行为。
二. 非类型模板参数
2.1 什么是非类型模板参数?
在模板编程中,除了类型参数(如 class T 或 typename T)外,还可以使用非类型模板参数。
非类型模板参数可以是常量,例如整数、枚举、指针等,它们在编译期间是已知的值。
代码示例如下:
template<class T, size_t N>
class Array {
public:T& operator[](size_t index) {return _array[index];}const T& operator[](size_t index) const {return _array[index];}size_t size() const { return N; }private:T _array[N];
};
在这个例子中,N 是一个非类型模板参数,表示数组的大小,它必须在编译时已知。
2.2 注意事项
-
允许的类型:非类型模板参数可以是整型、枚举、指针或者引用类型,但浮点数、类对象和字符串不允许作为非类型模板参数。
-
编译期确认:非类型模板参数必须在编译期确认。这意味着它的值在编译时必须是一个常量表达式。
2.3 使用场景
非类型模板参数最常用于需要对某些固定值进行编译期优化的场景。例如,在实现容器类时,可以通过非类型模板参数来指定容器的大小,从而在编译时确定内存分配的规模。
静态数组的实现:
template<typename T, size_t N>
class StaticArray {
public:T& operator[](size_t index) {return _array[index];}const T& operator[](size_t index) const {return _array[index];}private:T _array[N];
};int main() {StaticArray<int, 10> arr; // 创建一个大小为10的静态数组arr[0] = 1;arr[1] = 2;std::cout << arr[0] << ", " << arr[1] << std::endl;return 0;
}
在这个例子中,N 是数组的大小,编译器在编译时已经知道这个值,因此它能够直接优化内存分配和数组边界检查。
三. 模板特化
3.1 定义
模板特化是指在模板的基础上,针对某些特定的类型提供专门的实现。当模板的默认实现无法满足某些特定类型的需求时,就可以通过特化来处理。例如,针对指针类型的特殊处理。
3.2 分类
模板特化分为两种:
- 全特化:对模板中的所有参数进行特化。
- 偏特化:仅对模板中的部分参数进行特化或进一步限制。
3.3 函数模板特化
以下是一个函数模板特化的示例:
template<class T>
bool Less(T left, T right) {return left < right;
}// 针对指针类型的特化
template<>
bool Less<Date*>(Date* left, Date* right) {return *left < *right;
}int main() {Date d1(2022, 7, 7);Date d2(2022, 7, 8);std::cout << Less(d1, d2) << std::endl; // 正常比较日期Date* p1 = &d1;Date* p2 = &d2;std::cout << Less(p1, p2) << std::endl; // 使用特化版本,比较指针指向的内容return 0;
}
在这个例子中,函数 Less 针对 Date* 指针类型进行了特化,以正确处理指针类型的比较。
四. 类模板特化
4.1 全特化
全特化指的是对模板中的所有参数进行特化,适用于某些特定类型,完全替代原始的模板实现。
template<class T1, class T2>
class Data {
public:Data() { std::cout << "Data<T1, T2>" << std::endl; }
};template<>
class Data<int, char> {
public:Data() { std::cout << "Data<int, char>" << std::endl; }
};int main() {Data<int, int> d1; // 使用原始模板版本Data<int, char> d2; // 使用全特化版本
}
在这个例子中,Data<int, char> 这个类型的对象会调用全特化的版本,输出 “Data<int, char>”。
4.2 偏特化
偏特化允许对模板的一部分参数进行特化,而不需要对全部参数进行特化。它使得模板能够更灵活地处理复杂的类型组合。
示例1:部分参数的偏特化
template<class T1, class T2>
class Data {
public:Data() { std::cout << "Data<T1, T2>" << std::endl; }
};// 偏特化版本,将第二个模板参数特化为int
template<class T1>
class Data<T1, int> {
public:Data() { std::cout << "Data<T1, int>" << std::endl; }
};int main() {Data<int, char> d1; // 调用原始模板Data<int, int> d2; // 调用偏特化版本
}
这里,Data<int, int> 将调用偏特化版本,而 Data<int, char> 将调用原始模板版本。
示例2:指针类型的偏特化
template<class T1, class T2>
class Data {
public:Data() { std::cout << "Data<T1, T2>" << std::endl; }
};// 偏特化版本,将两个参数特化为指针类型
template<class T1, class T2>
class Data<T1*, T2*> {
public:Data() { std::cout << "Data<T1*, T2*>" << std::endl; }
};int main() {Data<int, int> d1; // 调用原始模板Data<int*, int*> d2; // 调用指针类型偏特化版本
}
通过类模板特化,可以实现对指针的排序,并确保比较的是指针指向的内容而不是地址。
五. 模板的分离编译
5.1 定义
分离编译指的是将程序分为多个源文件,每个源文件单独编译生成目标文件,最后将所有目标文件链接生成可执行文件。在模板编程中,分离编译有时会带来挑战,因为模板的实例化是在编译期进行的,编译器需要知道模板的定义和使用场景
5.2 问题
在模板的分离编译中,模板的声明和定义分离时会产生编译或链接错误。
这是因为模板的实例化是由编译器根据实际使用的类型生成的代码,如果在模板的定义和使用之间缺乏可见性,编译器无法正确地实例化模板。
具体示例如下:
// a.h
template<class T>
T Add(const T& left, const T& right);// a.cpp
template<class T>
T Add(const T& left, const T& right) {return left + right;
}// main.cpp
#include "a.h"int main() {Add(1, 2); // 使用模板函数Add(1.0, 2.0); // 使用模板函数return 0;
}
在这种情况下,由于模板的定义和使用是分离的,编译器在不同编译单元中无法找到模板的定义,从而导致链接错误。
5.3 解决方案
为了解决模板的分离编译问题,可以采取以下几种方法:
- 将模板的声明和定义放在同一个头文件中
将模板的定义和声明都放在头文件中,使得所有使用模板的编译单元都可以访问到模板的定义。
// a.h
template<class T>
T Add(const T& left, const T& right) {return left + right;
}
- 显式实例化模板
通过显式实例化,将模板的具体实现放在 .cpp 文件中。这样,编译器能够在实例化时找到模板的定义。
// a.cpp
template T Add<int>(const int& left, const int& right);
template T Add<double>(const double& left, const double& right);
这两种方法都能有效避免模板分离编译带来的问题,推荐将模板的定义和声明放在同一个文件中,通常使用 .hpp 或 .h 文件格式。
六. 模板总结
模板编程在C++中是一种非常强大的工具,通过泛型编程、模板特化和非类型模板参数等技术,可以编写高效、灵活的代码。模板编程的优缺点总结如下:
优点:
- 代码复用:模板能够极大提高代码的复用性,减少重复代码的编写。
- 灵活性:可以根据不同的数据类型生成特定的代码,增强了程序的适应性。
- STL基础:C++的标准模板库(STL)就是基于模板技术构建的,它为容器、算法和迭代器提供了高度泛型化的接口。
缺点:
- 代码膨胀:模板实例化时会生成不同版本的代码,可能导致二进制文件变大。
- 编译时间变长:由于模板的编译期实例化,可能会导致编译时间增加。
- 调试困难:模板编译错误信息往往非常复杂,难以阅读和调试。
七. 模板的精细雕琢——从特化到元编程
7.1 定义
模板元编程(Template Metaprogramming,简称TMP)是一种利用C++模板机制进行编译期计算和代码生成的编程技术。它主要用于在编译时生成代码,并避免运行时的计算,从而提升程序的效率。模板元编程的核心思想是通过模板递归实现逻辑运算、数学计算等操作。
7.2 编译器与运行期的区别
-
运行期计算是在程序执行过程中进行的,例如加法运算、条件判断等。
-
编译期计算则是在编译阶段就确定的,模板元编程可以在程序编译过程中进行某些计算,从而减少运行期的负担。C++模板系统可以进行编译期递归和选择。
7.3 模板元编程基础
模板元编程的基础主要是利用模板的递归和特化来进行编译期计算。一个简单的例子是使用模板递归来计算阶乘。
示例:使用模板元编程计算阶乘
// 基本模板
template<int N>
struct Factorial {static const int value = N * Factorial<N - 1>::value;
};// 特化版本,当N为1时终止递归
template<>
struct Factorial<1> {static const int value = 1;
};int main() {std::cout << "Factorial of 5: " << Factorial<5>::value << std::endl;return 0;
}
在这个例子中,Factorial<5> 会在编译期递归展开为 5 * 4 * 3 * 2 * 1,并计算出阶乘值。在运行时打印结果,编译器已经在编译阶段完成了计算。
7.4 使用模板元编程进行条件选择
模板元编程不仅可以用来进行数学运算,还可以用于条件选择(类似于 if-else 语句),从而在编译期决定代码的生成。例如,我们可以通过模板来选择某些代码块是否在编译时生成。
示例:编译期条件判断
template<bool Condition, typename TrueType, typename FalseType>
struct IfThenElse;template<typename TrueType, typename FalseType>
struct IfThenElse<true, TrueType, FalseType> {typedef TrueType type;
};template<typename TrueType, typename FalseType>
struct IfThenElse<false, TrueType, FalseType> {typedef FalseType type;
};int main() {// 当条件为 true 时,选择 int 类型IfThenElse<true, int, double>::type a = 10;// 当条件为 false 时,选择 double 类型IfThenElse<false, int, double>::type b = 3.14;std::cout << "a: " << a << ", b: " << b << std::endl;return 0;
}
在这个例子中,IfThenElse 模板类模拟了条件选择,在编译时根据布尔值 Condition 选择 TrueType 或 FalseType。如果条件为真,则选择 TrueType;否则,选择 FalseType。
7.5 TMP的实际应用
模板元编程可以用于很多实际场景中,例如计算多项式、矩阵运算、位操作等。它的主要优势在于可以减少运行时的计算开销,将复杂的逻辑提前到编译时处理,提升程序的效率。
八. 模板匹配规则与SFINAE
8.1 模板匹配规则
C++编译器在调用模板时,会根据传入的模板参数进行匹配。模板匹配的规则比较复杂,涉及到多个优先级和模板特化。
- 优先调用非模板函数
在匹配时,编译器会优先选择非模板函数,如果有完全匹配的非模板函数存在,编译器会选择该函数,而不是实例化模板。
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 = 1, b = 2;std::cout << Add(a, b) << std::endl; // 调用非模板版本return 0;
}
- 如果没有非模板函数,匹配模板实例
如果没有完全匹配的非模板函数存在,编译器将生成模板实例化版本。
template<typename T>
T Add(T a, T b) {return a + b;
}int main() {double x = 1.1, y = 2.2;std::cout << Add(x, y) << std::endl; // 调用模板实例化版本return 0;
}
8.2 SFINAE (Substitution Failure Is Not An Error)
SFINAE是 C++ 模板系统中的一个重要规则,全称为 “Substitution Failure Is Not An Error”(替换失败不是错误)。SFINAE 是指在模板实例化过程中,如果某些模板参数的替换失败,编译器不会直接报错,而是选择其他可行的模板。
template<typename T>
typename std::enable_if<std::is_integral<T>::value, T>::type
CheckType(T t) {return t * 2;
}template<typename T>
typename std::enable_if<!std::is_integral<T>::value, T>::type
CheckType(T t) {return t * 0.5;
}int main() {std::cout << CheckType(10) << std::endl; // 整数类型,输出20std::cout << CheckType(3.14) << std::endl; // 浮点数类型,输出1.57return 0;
}
在这个例子中,SFINAE 机制允许我们根据类型的不同选择不同的模板版本。在 CheckType 函数模板中,当传入的参数是整数类型时,编译器选择第一个版本,而当参数是浮点数类型时,选择第二个版本。
九. 常见问题分析
9.1 模板的代码膨胀问题
模板虽然提供了极大的灵活性,但它也会带来代码膨胀问题。因为模板实例化会生成多个版本的代码,所以在大规模使用模板时,可能会导致二进制文件体积增大。为了解决这个问题,可以考虑以下几种策略:
- 减少模板的实例化次数:通过显式实例化来控制模板的使用,避免重复生成相同功能的模板代码。
- 避免过度模板化:在设计模板时,尽量避免将所有逻辑都写成模板,只有在必要时才使用模板。
- 使用非类型模板参数:非类型模板参数可以减少模板的泛化程度,避免代码膨胀。
9.2 模板错误调试
模板编译错误通常会产生非常复杂的错误信息,难以调试。以下是一些常用的调试模板代码的方法:
- 分解模板代码:将复杂的模板逻辑分解为多个小的模板函数或类,逐步进行调试。
- 使用静态断言:在模板代码中插入 static_assert 来检查模板参数是否合法,提前发现问题。
- 阅读编译错误信息:虽然模板错误信息冗长,但可以从错误的上下文中找到模板参数替换的线索,从而定位问题。
小结
C++模板是一场从类型到抽象的奇幻旅程,它让我们在编程中不仅能触摸到逻辑与算法的肌理,还能领略数学与抽象的灵魂。这首「抽象之诗」见证了C++语言在泛型编程领域的巅峰成就,也为程序员打开了一个充满想象力与创造力的世界。
愿每一位程序员都能在模板的世界中找到属于自己的灵感,书写属于自己的代码诗篇。
本篇关于模板的介绍就暂告段落啦,希望能对大家的学习产生帮助,欢迎各位佬前来支持斧正!!!

相关文章:
抽象之诗:C++模板的灵魂与边界
引言 在计算机科学的浩瀚长河中,C模板如同一颗璀璨的星辰,以其独特的泛型编程方式为程序设计注入了灵魂。它是抽象的艺术,是类型的舞蹈,是效率与灵活性的交响乐。模板不仅是一种技术工具,更是一种哲学思考,…...
后端统一接口返回状态【初步模板】
后端统一接口返回状态【模板】 文章目录 后端统一接口返回状态【模板】1 .Result类编写2 .Constants类编写3 .更改Controller层下的类return格式 开发过程中,每个接口的返回格式设计都是一样的,这样可以大大提高开发效率。 项目结构如下图:分…...
呼入机器人:24小时客户服务的未来趋势
呼入机器人:24小时客户服务的未来趋势 作者:开源大模型智能呼叫中心系统FreeAICC,Github:https://github.com/FreeIPCC/FreeAICC 在当今快节奏的商业环境中,客户服务已成为企业竞争的核心要素之一。随着人工智能技术…...
whisper.cpp: PC端测试 -- 电脑端部署音频大模型
whisper.cpp: PC端测试 1.环境需要2.构建项目3.PC测试 1.环境需要 以下是经实验验证可行的环境参考,也可尝试其他版本。 (1)PC:Ubuntu 22.04.4 (2)软件环境:如下表所示 工具版本安装Anacond…...
WPF ControlTemplate 控件模板
区别于 DataTemplate 数据模板,ControlTemplate 是控件模板,是为自定义控件的 Template 属性服务的,Template 属性类型就是 ControlTemplate。 演示, 自定义一个控件 MyControl,包含一个字符串类型的依赖属性。 pub…...
序列化和反序列化(一)
因为通过这段时间的学习,发现,序列化和反序列化的考点和漏洞在平时遇到的还是比较多的,而且自己也没有特别去学习过这个知识点,所以在这里写一篇关于这里序列化和反序列话的博客,废话就停止在这里了。 在介绍具体的序列…...
Kubeadm+Containerd部署k8s(v1.28.2)集群(非高可用版)
Kubeadm+Containerd部署k8s(v1.28.2)集群(非高可用版) 文章目录 Kubeadm+Containerd部署k8s(v1.28.2)集群(非高可用版)一.环境准备1.服务器准备2.环境配置3.设置主机名4.修改国内镜像源地址5.配置时间同步6.配置内核转发及网桥过滤二.容器运行时Containerd安装(所有节点)…...
取子串(指针)
#include <stdio.h> #include <string.h>char* substr(char *s, int startloc, int len) {static char result[51]; // 定义一个足够大的静态数组来存储结果static char result1[] {N,U,L,L,\0};int i, j;// 检查startloc是否在字符串的范围内if (startloc < 1…...
Linux系列之如何更换Centos yum源?
系列博客专栏: JVM系列博客专栏SpringBoot系列博客 环境 Centos7Xshell7 问题描述 最近安装了一个虚拟机,准备用来学习,不过使用yum命令安装一些软件,不过使用这个命令时候,提示 Cannot find a valid baseurl fo…...
过滤器和拦截器的区别详解
文章目录 过滤器和拦截器的区别详解1. 来源不同2. 触发时机不同3. 实现原理不同4. 支持的项目类型不同5. 使用场景不同6. 核心区别总结**总结** 过滤器和拦截器的区别详解 在 Web 开发中,过滤器(Filter) 和 拦截器(Interceptor&a…...
centos使用mkisofs构建无人值守镜像(附官方学习文档)
安装mkisofs yum install -y mkisofs 挂载镜像并确认 并拷贝文件(/mnt 为我们的工作目录) 1.3 准备自动应答文件(保存为 ins.ks) 修改系统引导 实际上就是添加inst.ks 这个引导参数 传递应答文件 传统模式引导 UEFI模式引导 打包镜像 通用选项 -v:启用详细模式&a…...
Pyside6+qml+Qtcreator项目实战
Pyside6+qml+Qtcreator项目实战 B站视频教程,进去后记得点赞。最终运行效果: PYTHON and QT QUICK - Custom Buttons With QML And JavaScript - [MODERN GUI]-#012 开发环境: 1、Qt 6.7.2 2、Pyside6 3、Python 3.11.4 4、Windows 10 原视频是用的Qt5,我这里采用Qt6重新…...
秒鲨后端之MyBatis【1】环境的搭建和核心配置文件详解
别忘了请点个赞收藏关注支持一下博主喵!!!! ! ! Mybatis简介 MyBatis历史 MyBatis最初是Apache的一个开源项目iBatis, 2010年6月这个项目由Apache Software Foundation迁移到了Google Code。随着开发团队转投Google Code旗下ÿ…...
编译原理复习---目标代码生成
适用于电子科技大学编译原理期末考试复习。 1. 目标代码 是目标机器的汇编代码或机器码,在本课程中指的是类似于汇编代码的一种形式,由一条条的指令构成目标代码。 抽象机指令格式:OP 目的操作数,源操作数。 我们要做的&…...
Winnows基础(2)
Target 了解常见端口及服务,熟练cmd命令,编写简单的 .bat 病毒程序。 Trail 常见服务及端口 80 web 80-89 可能是web 443 ssl心脏滴血漏洞以及一些web漏洞测试 445 smb 1433 mssql 1521 oracle 2082/2083 cpanel主机管理系统登陆(国外用的…...
酒蒙子骰子小程序系统
酒蒙子流量变现小程序小游戏 后端tp8 前端uniapp 会员变现 分销推广 流量主 …...
网络安全防范
实践内容 学习总结 PDR,$$P^2$$DR安全模型。 防火墙(Firewall): 网络访问控制机制,布置在网际间通信的唯一通道上。 不足:无法防护内部威胁,无法阻止非网络传播形式的病毒,安全策略…...
重拾设计模式--组合模式
文章目录 1 、组合模式(Composite Pattern)概述2. 组合模式的结构3. C 代码示例4. C示例代码25 .应用场景 1 、组合模式(Composite Pattern)概述 定义:组合模式是一种结构型设计模式,它允许你将对象组合成…...
红米Note 9 Pro5G刷小米官方系统
前言 刷机有2种方式:线刷 和 卡刷。 线刷 线刷:需要用电脑刷机工具,例如:XiaoMiFlash.exe,通过电脑和数据线对设备进行刷机。 适用场景: 系统损坏无法开机。恢复官方出厂固件。刷机失败导致软砖、硬砖的…...
渗透测试-前端加密分析之RSA加密登录(密钥来源服务器)
本文是高级前端加解密与验签实战的第6篇文章,本系列文章实验靶场为Yakit里自带的Vulinbox靶场,本文讲述的是绕过RSA加密来爆破登录。 分析 这里的代码跟上文的类似,但是加密的公钥是通过请求服务端获取的 http://127.0.0.1:8787/crypto/js/…...
探索AI编程工具的民主化:从技术壁垒到开源共享的技术演进之路
探索AI编程工具的民主化:从技术壁垒到开源共享的技术演进之路 【免费下载链接】cursor-vip cursor IDE enjoy VIP 项目地址: https://gitcode.com/gh_mirrors/cu/cursor-vip "技术不应成为特权,而应是推动文明进步的共同财富。" —— 开…...
将Taotoken集成到自动化客服系统实现智能问答降本增效
🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 将Taotoken集成到自动化客服系统实现智能问答降本增效 对于需要构建或升级智能客服系统的企业而言,核心诉求往往集中在…...
AMD Ryzen SMU调试工具终极指南:3步掌握硬件级性能调优
AMD Ryzen SMU调试工具终极指南:3步掌握硬件级性能调优 【免费下载链接】SMUDebugTool A dedicated tool to help write/read various parameters of Ryzen-based systems, such as manual overclock, SMU, PCI, CPUID, MSR and Power Table. 项目地址: https://g…...
抖音批量下载神器:douyin-downloader开源工具完整使用指南
抖音批量下载神器:douyin-downloader开源工具完整使用指南 【免费下载链接】douyin-downloader A practical Douyin downloader for both single-item and profile batch downloads, with progress display, retries, SQLite deduplication, and browser fallback s…...
告别无效熬夜!10 款 AI 毕业论文工具实测,解锁高效通关路径
paperxie-免费查重复率aigc检测/开题报告/毕业论文/智能排版/文献综述/AI PPThttps://www.paperxie.cn/ai/dissertationhttps://www.paperxie.cn/ai/dissertation 打开 Word 文档盯着空白页面发呆,开题报告改了五版还是被导师打回,文献综述翻遍知网也理不…...
如何一键下载30+主流文档平台内容?kill-doc开源工具全解析
如何一键下载30主流文档平台内容?kill-doc开源工具全解析 【免费下载链接】kill-doc 看到经常有小伙伴们需要下载一些免费文档,但是相关网站浏览体验不好各种广告,各种登录验证,需要很多步骤才能下载文档,该脚本就是为…...
3个步骤:彻底释放华硕笔记本性能的终极指南
3个步骤:彻底释放华硕笔记本性能的终极指南 【免费下载链接】g-helper Lightweight Armoury Crate alternative for Asus laptops with nearly the same functionality. Works with ROG Zephyrus, Flow, TUF, Strix, Scar, ProArt, Vivobook, Zenbook, Expertbook, …...
CTF 实战必备 Hashcat 密码破解工具 零基础全套教程
HashCat密码破解工具介绍 hashcat号称世界上最快的密码破解,世界上第一个和唯一的基于GPU的规则引擎,免费多GPU(高达128个GPU),多哈希,多操作系统(Linux和Windows本地二进制文件)&a…...
别急着升级Android Studio!手把手教你降级AGP 8.3.0-alpha01到8.1.3,解决版本不兼容报错
别急着升级Android Studio!手把手教你降级AGP 8.3.0-alpha01到8.1.3,解决版本不兼容报错 接手一个Kotlin项目时,最令人头疼的莫过于刚打开就遭遇版本不兼容的红色报错。尤其当错误提示显示"项目使用了不兼容的Android Gradle插件版本(A…...
告别手动肝船!碧蓝航线自动化脚本Alas终极使用指南
告别手动肝船!碧蓝航线自动化脚本Alas终极使用指南 【免费下载链接】AzurLaneAutoScript Azur Lane bot (CN/EN/JP/TW) 碧蓝航线脚本 | 无缝委托科研,全自动大世界 项目地址: https://gitcode.com/gh_mirrors/az/AzurLaneAutoScript 还在为碧蓝航…...
