一篇文章入门C/C++自动构建利器之Makefile

2023-11-16

更多博文,请看音视频系统学习的浪漫马车之总目录

C/C++编译
浅析C/C++编译本质
一篇文章入门C/C++自动构建利器之Makefile
升级构建工具,从Makefile到CMake

Makefile简介

上一篇浅析C/C++编译本质已经比较详细地介绍了C/C++编译的流程和初步探讨了编译过程中底层的一些细节,可能各位已经发现,即使文章中的Demo很小,但是每次修改源码之后都要调用g++命令再重新链接一次,改了几次之后就有点吃力了,假如遇到大型项目,这样一个个调用g++(gcc)编译命令,那有点一顿操作猛如虎,结果什么的那味么。。

我们程序员总是很喜欢偷懒的,总是希望能交给机器的事情觉得不自己亲自处理,所以编译也是一样,我们要的是告诉机器一个编译流程,然后每次机器都帮我们一键编译好,于是,Makefile就应运而生了。

法国数学家laplace才会说,对数的发明延长了科学家生命,那么Makefile也可以说是延长了程序员的寿命。

什么是Makefile呢?首先要先了解下make。make 是一个命令工具,,是一个解释 makefile 中指令的命令工具,一般来说,大多数的 IDE 都有这个命令,比如:Visual C++ 的 nmake,QtCreator 的 qmake ,只是IDE帮我们封装好了,所以一般不需要我们亲自编写。

这里专门讲GNU的make,先看下官方文档的叙述:Overview of make

The make utility automatically determines which pieces of a large program need to be recompiled, and issues commands to recompile them. ……Our examples show C programs, since they are most common, but you can use make with any programming language whose compiler can be run with a shell command. Indeed, make is not limited to programs. You can use it to describe any task where some files must be updated automatically from others whenever the others change.

To prepare to use make, you must write a file called the makefile that describes the relationships among files in your program and provides commands for updating each file. In a program, typically, the executable file is updated from object files, which are in turn made by compiling source files.

Once a suitable makefile exists, each time you change some source files, this simple shell command:

make
suffices to perform all necessary recompilations. The make program uses the makefile data base and the last-modification times of the files to decide which of the files need to be updated. For each of those files, it issues the recipes recorded in the data base.

You can provide command line arguments to make to control which files should be recompiled, or how.

简单来说,make就是一个构建工具,通过make的shell命令,去跑Makefile脚本,而Makefile脚本就指定了项目中哪些文件需要编译,文件的依赖关系以及最终的编译目标产物。所以通过make命令加上Makefile脚本就可以实现一键编译整个项目的梦想,而熟悉Makefile,也为将来编译FFmpeg等C/C++开源库打好基础。

Makefile规则

显示规则

Makefile最基本的规则可用以下语句表示:

# 每条规则的语法格式:
target1,target2...: depend1, depend2, ...
	command
	......
	......

target:当前规则的生成目标,比如需要生成一个C语言的目标文件
depend:生成目标文件的依赖文件,比如要生成一个C语言的目标文件,需要一个汇编文件(.s后缀的文件)
command:表示具体从依赖文件生成目标文件的方法。比如要从汇编文件生成目标文件,则使用gcc -c命令。

Makefile中第一条规则的目标就是当前Makefile文件的终极目标,而第一条规则中的依赖如果不存在,则会往下寻找生成这个依赖的规则,依次递归执行直到生成终极(第一条规则)目标的依赖全部存在,再生成终极目标。

Makefile就是通过这样一条条这样的规则语句的组合,就实现了对一个大型项目的构建。

举个栗子?

还是用上一篇浅析C/C++编译本质里面的动物例子吧:

这是当前目录各个文件:
在这里插入图片描述

我们的目标是产生一个main的可执行文件,为了得到main,就需要Cat、Dog、main的目标文件,而为了得到这些目标文件,就要从它们的源文件进行预处理、编译、汇编流程,首先我们先根据上面的规则写出产生Cat.o的Makefile语句吧:

#终极目标是Cat.o,依赖是Cat.s
Cat.o:Cat.s
		#为了得到Cat.o的具体命令
        g++ -c Cat.s -o Cat.o
#目标是Cat.s,依赖是Cat.i
Cat.s:Cat.i
		#为了得到Cat.s的具体命令
        g++ -S Cat.i -o Cat.s
#目标是Cat.i,依赖是Cat.cpp
Cat.i:Cat.cpp
		#为了得到Cat.i的具体命令
        g++ -E Cat.cpp -o Cat.i

这里终极目标是Cat.o,为了得到Cat.o,就要得到Cat.s,为了得到Cat.s,就需要往下寻找得到Cat.s的规则语句,依次类推找到直到找到最后一条规则语句,找到依赖项Cat.cpp是已经存在的,然后倒过来执行生成每个步骤的依赖,直到生成最后的Cat.o。

在src目录中执行make命令:
在这里插入图片描述
可以看到make命令很体贴,都把整个过程打印出来了,可以清晰看到整个依赖链生成的全过程。Cat.i、Cat.s、Cat.o文件都已经生成:
在这里插入图片描述
现在已经初步实现了一键生成目标文件。

伪目标

如果想删除掉Makefile产生的文件(Cat.i、Cat.s、Cat.o),传统做法是:
在这里插入图片描述
这样子当然很不自动化,如果要删除的文件成百上千,那基本就是gg的节奏。既然Makefile帮我们生成了这些文件,那么它也有责任将这些文件删除。

是的,Makefile可以做到,它可以封装命令来达到一键执行一些命令,通过伪目标来实现,例如删除构建产生的文件:

Cat.o:Cat.s
        g++ -c Cat.s -o Cat.o
Cat.s:Cat.i
        g++ -S Cat.i -o Cat.s
Cat.i:Cat.cpp
        g++ -E Cat.cpp -o Cat.i

#.PHONY表示这是个没有目标的规则语句,即伪目标,clear表示伪目标命令名称
.PHONY:
clear:
		//具体的执行命令
		rm -rf Cat.i Cat.s Cat.o

.PHONY表示这是个没有目标的规则语句,clear表示伪目标的命令,换行的是具体的伪目标命令。之所以要加使用.PHONY,是因为要防止当前目录刚好有文件名叫clear,从而以为目标是clear而产生的冲突。

怎么使用呢,直接make后面跟着命令名:
在这里插入图片描述
可以看到rm -rf Cat.i Cat.s Cat.o已经被执行,文件也顺利被删除。

有了伪目标,我们就开始可以自由地搞一些动作了,只要对命令足够熟悉~

变量

只是产生Cat.o那还没完成项目构建呢,根据上述内容,依葫芦画瓢写出完整Makefile吧:

#终极目标是main可执行文件,所以依赖是3个目标文件
main:Cat.o Dog.o main.o
        g++ Cat.o Dog.o main.o -o main
Cat.o:Cat.s
        g++ -c Cat.s -o Cat.o
Cat.s:Cat.i
        g++ -S Cat.i -o Cat.s
Cat.i:Cat.cpp
        g++ -E Cat.cpp -o Cat.i
Dog.o:Dog.s
        g++ -c Dog.s -o Dog.o
Dog.s:Dog.i
        g++ -S Dog.i -o Dog.s
Dog.i:Dog.cpp
        g++ -E Dog.cpp -o Dog.i
main.o:main.s
        g++ -c main.s -o main.o
main.s:main.i
        g++ -S main.i -o main.s
main.i:main.cpp
        g++ -E main.cpp -o main.i

在这里插入图片描述

可以看到哗啦啦执行了一堆命令后,main可执行文件出现了。

可执行文件是出现了,但是是个人都能看到这也麻烦了吧,这么个小项目还要写一堆东西,算个什么自动构建系统?

接下来,就是开始大刀阔斧精简Makefile文件的时候了~

1.上篇文章已经说了,gcc命令可以一步到位从源文件生成目标文件,所以可以改为:

main:Cat.o Dog.o main.o
        g++ Cat.o Dog.o main.o -o main
Cat.o:Cat.cpp
        g++ -c Cat.cpp -o Cat.o
Dog.o:Dog.cpp
        g++ -c Dog.cpp -o Dog.o
main.o:main.cpp
        g++ -c main.cpp -o main.o

.PHONY:
clear:
        rm -rf Cat.o Dog.o main.o

执行下:

ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src$ make
g++ -c Cat.cpp -o Cat.o
g++ -c Dog.cpp -o Dog.o
g++ -c main.cpp -o main.o
g++ Cat.o Dog.o main.o -o main
ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src$ ls
Cat.cpp  Cat.h  Cat.o  Dog.cpp  Dog.h  Dog.o  main  main.cpp  main.o  Makefile
ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src$ ./main 
Cat::eat
externData:10
I am dog
Here is a Cat

当然这只是简化gcc命令,接下来才是重点。

很容易发现,Makefile文件中有重复的内容,比如“Cat.o Dog.o main.o”,作为程序员,应该就有这方面的敏感度了,那么类似抽取变量操作就是必须的了。

没错,Makefile也有变量的概念,定义类似其他语言,常用符号为:

  1. =:替换
  2. :=:恒等于
  3. +=:追加

其实很简单,将上面例子改为:

#定义变量
TAR := main
DEPEND := Cat.o Dog.o main.o
G = g++
#通过${}进行变量的使用
${TAR}:${DEPEND}
        ${G} ${DEPEND} -o ${TAR}
Cat.o:Cat.cpp
        ${G} -c Cat.cpp -o Cat.o
Dog.o:Dog.cpp
        ${G} -c Dog.cpp -o Dog.o
main.o:main.cpp
        ${G} -c main.cpp -o main.o

.PHONY:
#删除中间产物
clear:
        rm -rf ${DEPEND}

执行结果:

ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src$ make
g++ -c Cat.cpp -o Cat.o
g++ -c Dog.cpp -o Dog.o
g++ -c main.cpp -o main.o
g++ Cat.o Dog.o main.o -o main
ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src$ ls
Cat.cpp  Cat.h  Cat.o  Dog.cpp  Dog.h  Dog.o  main  main.cpp  main.o  Makefile

也成功生成main可执行文件

再试试清理中间产物的伪目标:

ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src$ make clear
rm -rf Cat.o Dog.o main.o
ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src$ ls
Cat.cpp  Cat.h  Dog.cpp  Dog.h  main  main.cpp  Makefile

同样执行成功。

其实Makefile中已经内置了预定义的变量了:
在这里插入图片描述
这些预定义的变量也经常使用在一些主流开源项目中,所以多记记才能避免遇到主流开源项目的Makefile一脸懵逼。

所以其实上面的例子的对g++定义的变量也可以改为直接使用CXX:

TAR := main
DEPEND := Cat.o Dog.o main.o
#g++使用内置变量
#G = g++

${TAR}:${DEPEND}
        ${CXX} ${DEPEND} -o ${TAR}
Cat.o:Cat.cpp
        ${CXX} -c Cat.cpp -o Cat.o
Dog.o:Dog.cpp
        ${CXX} -c Dog.cpp -o Dog.o
main.o:main.cpp
        ${CXX} -c main.cpp -o main.o

.PHONY:
clear:
        rm -rf ${DEPEND}

执行下:

ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src$ make clear
rm -rf Cat.o Dog.o main.o
ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src$ make
g++ -c Cat.cpp -o Cat.o
g++ -c Dog.cpp -o Dog.o
g++ -c main.cpp -o main.o
g++ Cat.o Dog.o main.o -o main
ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src$ ls
Cat.cpp  Cat.h  Cat.o  Dog.cpp  Dog.h  Dog.o  main  main.cpp  main.o  Makefile

一样也是ok的。

隐藏规则

上面的Makefile看起来还不错,挺简介,但是Makefile可不是搞这个麻雀型项目的,燕雀安知鸿鹄志~~如果一个项目有成百上千个源文件,那岂不是要写上百行以上的gcc命令?即使有了变量,那定义起来一个个源文件列举出来也是要命,该如何是好呢?

为什么要列出具体文件名呢?有shell经验的估计已经想到了,当然就是通配符,Makefile 是可以使用 shell 命令的,所以 shell 支持的通配符在 Makefile 中也是同样适用的。

:匹配0个或者是任意个字符,比如 .c表示所有以.c结尾的文件。
%:也是匹配任意个字符,一般用于遍历上一层的依赖文件列表并匹配到目标对应的依赖文件

除了通配符,Makefile还有自动化变量来简化脚本,所谓的自动变量也是一种类似通配符的东西,常见的自动化变量:
在这里插入图片描述
通过通配符和自动变量这些隐藏规则,就可以大大简化脚本了。还是上面的栗子,一看就懂:

TAR := main
DEPEND := Cat.o Dog.o main.o

${TAR}:${DEPEND}
        ${CXX} ${DEPEND} -o ${TAR}
#以上命令改为以下语句。
#相当于遍历DEPEND中每个.o文件名,然后从当前目录中找到同名的.cpp文件,每次执行一次gcc命令生成对应的.o文件
%.o:%.cpp
		#$^会用依赖对应的具体.cpp文件替换,$@会用目标对应的具体.o文件替换
        ${CXX} -c $^ -o $@

%.o:%.cpp相当于遍历DEPEND中每个.o文件名,然后从当前目录中找到同名的.cpp文件,每次执行一次gcc命令生成对应的.o文件,而命令中的$^会用依赖对应的具体.cpp文件替换,$@会用目标对应的具体.o文件替换

执行下:

ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src$ make clear
rm -rf Cat.o Dog.o main.o
ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src$ ls
Cat.cpp  Cat.h  Dog.cpp  Dog.h  main  main.cpp  Makefile
ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src$ make
g++ -c Cat.cpp -o Cat.o
g++ -c Dog.cpp -o Dog.o
g++ -c main.cpp -o main.o
g++ Cat.o Dog.o main.o -o main

是不是有种胖子突然减肥成功的感觉呢?

编译动态链接库

现在尝试用Makefile完成上一篇浅析C/C++编译本质所列举的编写动态链接库的栗子。

先把Cat.cpp和Dog.cpp编成动态链接库,这里将生成动态库的资源放到特定目录libSrc中:

ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src$ mkdir libSrc
ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src$ mv Cat.cpp Dog.cpp libSrc/
ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src$ ls
Cat.h  Dog.h  libSrc  main  main.cpp  Makefile
ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src$ ls libSrc/
Cat.cpp  Dog.cpp
ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src$ mv Cat.h Dog.h libSrc/
ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src$ ls
libSrc  main  main.cpp  Makefile
ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src$ ls libSrc/
Cat.cpp  Cat.h  Dog.cpp  Dog.h
ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src$
//移动头文件放到include目录下
ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src/libSrc$ mkdir include
ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src/libSrc$ ls
Cat.cpp  Cat.h  Dog.cpp  Dog.h  include
ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src/libSrc$ mv Cat.h Dog.h include/
ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src/libSrc$ ls
Cat.cpp  Dog.cpp  include
ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src/libSrc$ ls include/
Cat.h  Dog.h

创建新的Makefile文件,根据上面的语法,很容易写出Makefile脚本:

LIBTARGET:=libAnimal.so
LIBDEPEND:=Cat.cpp Dog.cpp
#指定是生成动态库
LDFFLAGS:=-shared
#C++编译选项指定生成pic代码以及头文件位置
CXXFLAGS:=-fpic -Iinclude

${LIBTARGET}:${LIBDEPEND}
        ${CXX} ${CXXFLAGS} ${LDFFLAGS} $^ -o $@
ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src/libSrc$ make
g++ -fpic -Iinclude -shared Cat.cpp Dog.cpp -o libAnimal.so
ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src/libSrc$ ls
Cat.cpp  Dog.cpp  include  libAnimal.so  Makefile

可以看出动态库libAnimal.so已经生成~~

嵌套执行make

现在已经生成动态链接库了,就差和主工程的目标文件一起生成可执行文件了。

这里先调整下目录结构:

ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src$ ls
app  libSrc  Makefile
ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src$ ls app/
include  main.cpp Makefile
ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src$ ls libSrc/
Cat.cpp  Dog.cpp  Makefile

在这里插入图片描述

项目目录下有app和libSrc 2个目录,app主要放主项目代码,libSrc放动态链接库代码,2个目录下都有各自的Makefile,项目根目录有根Makefile。

app下的Makfile:

TAR := main
CXXFLAGS := -Iinclude
DEPEND := main.o
#.o文件和动态链接库生成可执行文件
${TAR}:${DEPEND}
        ${CXX} $^ *.so -o $@
%.o:%.cpp
        ${CXX} ${CXXFLAGS} -c $^ -o $@

.PHONY:
clear:
        ${RM} main

libSrc目录Makefile:

#动态库生成输出到app目录下
LIBTARGET=../app/libAnimal.so
LIBDEPEND=Cat.cpp Dog.cpp
LDFFLAGS=-shared
CXXFLAGS=-fpic -I../app/include
#生成动态库
${LIBTARGET}:${LIBDEPEND}
        ${CXX} ${CXXFLAGS} ${LDFFLAGS} $^ -o $@

.PHONY:
clear:
        ${RM} ${LIBTARGET} *.o *.i *.s

clearTemp:
        ${RM} *.o *.i *.s

主要是看下根目录的Makefile,主要用于执行2个子目录的Makefile

LIB := libSrc
APP := app

.PHONY : all $(APP) $(LIB)
#这是遍历文件夹语句,每次遍历就执行下面的命令,即make命令
$(APP) $(LIB) :
#-C表示在当前目录下执行make
        $(MAKE) -C $@
#指定APP文件Makefile执行依赖LIB中的Makefile,即先执行LIB中的Makefile,再执行APP文件Makefile
$(APP):$(LIB)

主要就是遍历子目录执行对应的Makefile,这里要注意的就是子目录Makefile依赖关系,在最后一行已经指明。

执行根目录Makefile:

ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src$ make
make -C libSrc
make[1]: Entering directory '/home/ubuntu/study/projects/CatTest/src/libSrc'
g++ -fpic -I../app/include -shared Cat.cpp Dog.cpp -o ../app/libAnimal.so
make[1]: Leaving directory '/home/ubuntu/study/projects/CatTest/src/libSrc'
make -C app
make[1]: Entering directory '/home/ubuntu/study/projects/CatTest/src/app'
g++ main.o *.so -o main
make[1]: Leaving directory '/home/ubuntu/study/projects/CatTest/src/app'
ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src$ ls
app  libSrc  Makefile
//执行生成的main可执行文件
ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src$ ./app/main 
Cat::eat
externData:10
I am dog
Here is a Cat

可以看到Makefile依旧很贴心,整个在各个目录间游动的路径都打印出来了,一切非常顺利~~麻雀虽小五脏俱全,一个像模像样的迷你Makefile系统就这样搭建完成了,相信以后大家看到大的C/C++项目的Makefile就不会一脸懵逼了哈哈。

最后想说的就是,Makfile是增量更新的,不会每次都无脑全部编译一遍,如果目标文件已经存在,则每次make都会比较依赖和目标的生成时间,如果依赖比目标文件新才会执行生成指令。

本文主要就介绍了Makefile入门相关的内容,在本文的基础上,后面将开始讲述cmake以及ndk的知识。

下一篇文章:升级构建工具,从Makefile到CMake

如果觉得本文有帮助,别忘了点赞关注哦~

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

一篇文章入门C/C++自动构建利器之Makefile 的相关文章

随机推荐

  • 安装双系统后,将windows设置为默认启动选项的方法

    原先的电脑只有windows系统 后来加装了ubuntu系统 但由于大部分时间仍然需要使用windows 但是默认启动项为ubuntu 难免会带来一些不便 将windows设为默认第一启动项的方法很简单 打开终端 查看grub的配置文件 s
  • VC++实用宏定义

    前言 在日常的编程工作中 常常定义一些实用的宏方便调用 该文章将收集一些常用的宏供大家参考 欢迎大家讨论和添加 指针释放 最常用的就是指针的安全释放 对应new的释放 ifndef ReleasePtr define ReleasePtr
  • File Processing by Python

    Go through all the file in destination path import os import sys def GetFileList dir fileList newDir dir if os path isfi
  • 【计算机网络】TCP协议

    实验目的 应用所学知识 1 熟悉 TCP 的协议格式 2 理解 TCP 对序列号和确认号的使用 3 理解 TCP 的流量控制算法和拥塞控制算法 实验步骤与结果 1 任务一 将Alice txt上传到服务器 使用wireshark捕获数据包
  • Windows平台的SDK、DDK与WDK

    尽管Windows平台的SDK DDK与WDK都包含了WinDBG工具包 但是用户获取WinDBG工具包的最主要方式还是从微软网站自由下载 因为这样获得的版本最新 最近尝试去了解WINDOWS下的驱动开发 现在总结一下最近看到的资料 1 首
  • 下采样与上采样

    一 下采样 概念 下采样 subsampled 又称为降采样 downsampled 可以通俗地理解为缩小图像 减少矩阵的采样点数 方法 1 最常用隔位取值 每行每列每隔k个点取一个点 2 合并区域 每 row k col k 窗口内所有像
  • python selenium 键盘操作 常用

    键盘事件 前面的 send keys 方法用来模拟键盘输入 keys 类提供了键盘上几乎所有按键的方法 组合键也是可以的 常用的键盘操作如下 send keys Keys BACK SPACE 删除键 BackSpace send keys
  • 三十、纯虚函数、抽象类、多态、简单工厂模式

    一 纯虚函数 虚函数是多态是实现多态的前提 如果我们需要在基类中定义共同的结构 那么接口就需要定义成虚函数 但是很多情况下基类的接口是无法实现的 比如形状类Shape 定义一个Draw方法 很明显这个方法没法实现 因为我们可以画出圆 正方形
  • 乾坤微服务子项目图片资源加载失败

    一 背景 子项目单独运行时正常 放在乾坤上 img 加载图片时失败 二 分析原因 假设乾坤项目域名为 http www aaa com 子项目域名为 http www bbb com 项目实际运行时 图片的 html 写法为 img src
  • python:正向最大匹配法分词(以藏文为例)

    前段时间研究了如何用分词工具进行分词 但是分词中涉及的一些算法 不太了解 所以 准备这段时间专攻分词算法原理 大家有补充 或者建议 欢迎留言 1 最大匹配法 Maximum Matching 最大匹配法是指以词典为依据 取词典中最长词长度作
  • 某游戏大厂测开笔试题分享

    测开笔试题 某厂笔试题 执行时限1000ms 一个典型的电话拨号盘如下 1 2 3 4 5 6 7 8 9 0 手指在两个按键之间的移动距离被定义为这两个键的x y坐标差的绝对值之和 比如 6到自身的距离是0 到3 5 9键的距离是1 到2
  • 证件照如何换底色,分享三种证件照换底色的方法!

    在我们的日常生活中 不同场景需要使用不同颜色的证件照 如果我们需要更换证件照的背景颜色 通常情况下人们会选择去照相馆重新拍摄一组照片 但这样费时费力 而且在遇到紧急情况时可能来不及 本文将介绍三种非常实用的方法 希望能对您有所帮助 方法一
  • Langchain使用介绍之-文档加载

    Lanchain提供了加载多种文档的能力 Lanchain初了能加载txt csv等格式文档外 还支持加载网页 音频 pdf等 本篇博客将介绍如何通过Langchain完成PDF文档 音频文档 网页文档的加载 加载PDF文档 通过使用Lan
  • ChatGPT 中文调教指南。各种场景使用指南。学习怎么让它听你的话

    ChatGPT是由OpenAI训练的一款大型语言模型 能够生成类人文本 您只需要给出提示或提出问题 它就可以生成你想要的东西 在此页面中 您将找到可与 ChatGPT 一起使用的各种提示 正经指南 写小说 写一本拥有出人意料结局的推理小说
  • 1.1关于数据挖掘

    一 数据挖掘是什么 从技术层面讲 数据挖掘指从大量数据中提取潜在有用的信息和知识的过程 从商业层面讲 数据挖掘是一种对大量业务数据进行抽取 转换 分析和建模处理 并从中提取辅助商业决策的关键数据的商业信息处理技术 二 数据挖掘与传统传统数据
  • 2021-5-13 爬虫之Xpath的下载与安装,简单教学!

    5 13学习日记之Xpath Xpath的安装 怎么安装Xpath 问题一 Xpath的安装 XPath 是一门在 XML 文档中查找信息的语言 XPath 可用来在 XML 文档中对元素和属性进行遍历 简单来说 在进行网页信息爬取时 Xp
  • 抖音新版本抓包(绕过sslpinning证书校验)

    目录 前言 方案 frida 替换so Xposed 前言 当我们想要分析较新版本的接口时 会发现一个有趣的现象 无论是用Charles还是Fiddler 都会出现抓不到包的情况 如下图 这是因为使用SSL Pinning证书锁定技术 是一
  • mysql性能优化

    1 表字段要选择合适的属性 邮政编码设置char 6 就可以了 文本字段如省份或者性别用enum enum被当做数值型数据来处理 比文本类型快 2 建立索引 3 优化查询语句 查询条件里最好用in替代on 条件列表值如果连续 用betwee
  • 注意力机制——CAM、SAM、CBAM、SE

    CAM SAM CBAM详见 CBAM 即插即用的注意力模块 附代码 目录 1 什么是注意力机制 2 通道注意力机制 SE 1 Squeeze 2 Excitation 3 SE Block 3 CAM 4 SAM 5 CBAM 6 代码
  • 一篇文章入门C/C++自动构建利器之Makefile

    更多博文 请看音视频系统学习的浪漫马车之总目录 C C 编译 浅析C C 编译本质 一篇文章入门C C 自动构建利器之Makefile 升级构建工具 从Makefile到CMake Makefile简介 上一篇浅析C C 编译本质已经比较详