当前位置: 首页 > news >正文

makefile案例学习

makefile案例学习

很多时候, 我们在git clone完一个project之后,就会让我们使用make命令进行项目的构建。这个make命令的背后就是按照了Makefile文件定义的格式去完成项目构建。

因此Makefile的作用就是帮助程序员进行项目的构建,它按照项目的需求个性化的定义自己的构建过程。Makefile并不限定编程语言,但是在c/c++项目中使用相对较多。其他的一些构建工具,例如qmake,也是将*.pro文件转化为Makefile,再进行构建。

可以看出Makefile的应用面还是非常广泛的, 下面将一步一步的讲解Makefile最常使用的语法, 并通过案例进行实践, 一步一步深入Makefile, 本文的案例主要使用了C++语言。

makefile的基本规则

Makefile的格式通常有如下两种:

格式一:

targets : prerequisitescommand...

格式二:

targets : prerequisites ; commandcommand...

其中,targets为目标文件, prerequisites为依赖文件, command为如果使用依赖文件构建出目标文件的命令。

格式一中,command不与"target:prerequisites"在一行,必须以Tab键开头。

格式二中, command和prerequisites在一行,那么可以用分号做为分隔。

通常情况下, 一般使用格式一, 命令和依赖分开, 比较清晰。

构建的逻辑如下所示:

(1)如果发现目标文件不存在,但是依赖文件存在,就会执行命令集构建生成目标文件。

(2)如果发现目标文件不存在,但是依赖文件也不存在,那么就会寻找依赖文件的构建模块, 尝试构建依赖文件, 然后再构建目标文件。

(3)如果发现目标文件已经存在依赖文件也存在,make指令会自动去比较两者的修改时间:

  • 依赖文件的最后修改时间晚于目标文件,就会执行指令集合。

  • 依赖文件的最后修改时间早于目标文件,就不会执行指令集合。同时会提示目标文件已经是最新的。

(4)如果发现目标文件已经存在依赖文件不存在,那么makefile将会寻找依赖文件的构建模块,并尝试构建依赖模块, 由于依赖模块生成时间晚于目标文件, 因此目标文件将会重新构建。

下面我们用过一些demo,一步一步的深入Makefile。

下面是demo1, 通过demo1来熟悉makefile的基本语法。

demo1:第一个Makefile

demo1的目录结构如下所示:

.
├── main.cpp
└── Makefile

其中main.cpp如下:

#include <iostream>
using namespace std;
int main () 
{cout << "Hello World" << endl;
}

为其编写的Makefile如下:

main:main.og++ main.o -o main
main.o:main.cppg++ -c main.cpp main.o
.PHONY : clean
clean:rm -rf *.o main

首先看构建main对象,main对象依赖于main.o对象main:main.o, 因此需要完成main.o对象的构建。

接着看到main.o对象依赖于main.cpp文件, 该文件存在与当前目录中, 因此执行相应的命令g++ -c main.cpp

main.o:main.cppg++ -c main.cpp

如此之后, main.o对象构建成功,这样就可以构建main对象,于是执行了main对象的命令g++ main.o -o main

至此main对象的构建完毕。

makefile的最后一部分是一个clean对象, 用于清理生成的文件, 使用make clean即可构建clean对象。 关于关键字.PHONY将在下面的例子中讲解。

.PHONY : clean
clean:rm -rf *.o main

使用$@ $< $^符号简化编写

在Makefile中,可以使用一些预定好的符号来简化书写, 例如$@ $< $^,其含义如下所示:

$@ 表示目标文件

$^ 表示所有的依赖文件

$< 表示第一个依赖文件

例如:
main: main.cpp add.cpp

$@指的就是main,$<指的就是main.cpp, $^指的就是main.cpp add.cpp

下面我们就使用它们来改动demo1中的makefile。

demo2:使用$@ $< $^ 简化书写

demo2的文件结构如下所示:

.
├── main.cpp
└── Makefile

main.cpp如下:

#include <iostream>
using namespace std;
int main () 
{cout << "Hello World" << endl;
}

Makefile内容如下:

main:main.og++ $< -o $@
main.o:main.cppg++ -c $< -o $@
.PHONY : clean
clean:rm -rf *.o main

首先看main目标的command,g++ $< -o $@, $<代表第一个依赖项,即main.o, $@代表构建目标,即main, 因此该语句可以翻译成g++ main.o -o main

main.o可以以此类推。

vpath和VPATH

vpath和VPATH主要作用是通过指定文件的搜索路径自动寻找源文件,但是这种自动推导需要你将vpath/VPATH与$<,$^结合使用。

VPATH是一个变量, 其格式如下所示:

VPATH = PATH1:PATH2:PATH3

将需要搜索的目录按照冒号分割。

vpath是一个关键字, 有三种格式:

1、vpath <pattern> <directories>

为符合模式<pattern>的文件指定搜索目录<directories>

vpath %.c path1:path2

其表示搜索.c结尾的文件,先在path1目录搜索,接着在path2目录搜索。

2、vpath <pattern>

清除符合模式<pattern>的文件的搜索目录。

3、vpath

清除所有已被设置好了的文件搜索目录。

第一个格式用于添加搜索路径, 后两个格式用于清除搜索路径。

下面需要注意vpath/VPATH的一个使用误区, 即vpath没有和$<,$^结合使用, 会有什么结果。

看下面的一个目录结构,

.
├── Makefile
└── src└── main.cpp

所要编译的文件main.cpp在src目录下, 我们使用VPATH指定了搜索路径src, 我们在command直接指定了文件名, 没有使用$<,$^, 试问这样编写Makefile能正确编译吗?

VPATH=src
main.o:main.cppg++ -c main.cpp -o main.o

答案是否定的, 执行结果如下:

g++ -c main.cpp -o main.o
cc1plus: fatal error: main.cpp: No such file or directory
compilation terminated

因为此时已经手动指定了文件名称, Makefile没有能力去为这种场景做适配。

因此VPATH想要生效,需要与$<,$^配合, 当搜索相应的目录找到对应的文件时, Makefile就会将<,<,<,^替换为文件的相对路径。

下面通过demo3, 来实践一下vpath/VPATH。

demo3:使用vpath和VPATH指定依赖文件搜索路径

demo3的文件目录结构如下所示:

.
├── inc
│   └── add.hpp
├── Makefile
└── src├── add.cpp└── main.cpp

main.cpp

#include "add.hpp"int main()
{int a = 1;int b = 2;int c = my_add(a, b);return 0;
}

add.cpp

#include "add.hpp"
int my_add(int a, int b)
{return a + b;
}

add.hpp

int my_add(int a, int b);

Makefile

VPATH = src:incmain : main.o add.og++ -o $@ $^main.o : main.cpp add.hppg++ -c $< -I inc/add.o : add.cpp add.hppg++ -c $< -I inc/.PHONY: clean
clean:rm -rf *.o main

组合使用VPATH和$<之后, makefile自动推导出了语句,在main.cpp前加上了src前缀。

g++ -c src/main.cpp -I inc/
g++ -c src/add.cpp -I inc/
g++ -o main main.o add.o

使用内置函数wildcard,patsubst, foreach, notdir等函数帮助我们构建

makefile提供了一些内置函数帮助我们的构建过程更加自动化。

wilecard:

使用格式:

$(wildcard PATTERN...)

它被展开为已经存在的、使用空格分开的、匹配此模式的所有文件列表。

例如下面的语句可以获取工作目录下的所有.c文件列表。

objects = $(wildcard *.c) 

patsubst:

$(patsubst <pattern>,<replacement>,<text> ) 

查找<text>中的单词(单词以"空格"、“Tab"或"回车”"换行"分隔)是否符合模式<pattern>,如果匹配的话,则以<replacement>替换。

下面的例子可以快速的生成一个目录下.c文件生成的.o文件。

首先使用"wildcard"函数获取工作目录下的.c文件列表;之后将列表中所有文件名的后缀.c替换为.o。这样我们就可以得到在当前目录可生成的.o文件列表。

$(patsubst %.c,%.o,$(wildcard *.c))

notdir:

用于去掉文件的绝对路径,只保留文件名。

下面的例子用于去除sub目录的前缀。

file=$(notdir $(wildcard ./sub/*.c)),

foreach:

$(foreach ITEM, LIST, TEXT)

实际上是一种循环, 常用于遍历文件夹下的所有文件。

foreach函数的工作过程是:把LIST中使用空格分割的单词依次取出并赋值给变量ITEM,然后执行TEXT表达式。重复这个过程,直到遍历完LIST中的最后一个单词。函数的返回值是TEXT多次计算的结果。

例如

dirs = src src/math
srcs = $(foreach dir, $(dirs), $(wildcard $(dir)/*.cpp))

这段makefile就取出了src和src/math目录下所有的cpp文件

静态模式

静态模式可以更加容易地定义多目标的规则,可以让我们的规则变得更加的有弹性和灵活。我们还是先来看一下语法:

<targets ...> : <target-pattern> : <prereq-patterns ...><commands>...

targets定义了一系列的目标文件,可以有通配符。是目标的一个集合。

target-pattern是指明了targets的模式,也就是的目标集模式。

prereq-patterns是目标的依赖模式,它对target-pattern形成的模式再进行一次依赖目标的定义。

下面的例子, 将多个构建目标合并在了一个静态模式中:

objects = main.o add.oall: $(objects)$(objects): %.o: %.cpp$(CXX) -c $(CFLAGS) $< -o $@

其中:

$(objects): %.o: %.cpp$(CXX) -c $(CFLAGS) $< -o $@

相当于

main.o : main.c$(CXX) -c $(CFLAGS) main.cpp -o main.o
add.o : add.cpp$(CXX) -c $(CFLAGS) add.cpp -o add.o

makefile自动生成依赖

在讲解makefile自动生成依赖之前,先给出本节中例子的目录结构和文件内容:

首先给出本节中例子的目录结构:

.
├── add.hpp
├── main.cpp
├── Makefile

main.cpp内容如下:

#include <iostream>
#include "add.hpp"int main()
{int a = 1;int b = 2;int c = my_add(a, b);return 0;
}

add.hpp内容如下:

int my_add(int a, int b)
{return a + b;
}

在Makefile中,我们的依赖关系可能会需要包含一系列的头文件,比如,如果我们的main.cpp中有一句#include "add.hpp" ,那么我们的依赖关系应该是:

main.o : main.cpp add.hpp

但是使用一些模式匹配的方法是不能够自动将这些依赖的头文件也包含进去的, 例如下面的语句:

%.o: %.cppg++ -c $< -o $@

这就意味着, 如果add.hpp文件添加了内容,并不会使得main.o重新构建。这不是我们所期望的。

这里就需要我们借助gcc/g++的 -MM参数自动生成依赖
例如g++ -MM main.cpp的输出则是:

main.o: main.c add.hpp

GNU组织建议把编译器为每一个源文件的自动生成的依赖关系放到一个文件中,为每一个.cpp的文件都生成一个.d的Makefile文件,然后再使用include将.d的依赖关系添加进来
例如:

main: main.o main.dg++ $< -o $@
%.o: %.cppg++ -c $< -o $@
%.d: %.cppg++ -MM $<include main.d

main.d的内容如下:

main.d

main.o: main.cpp add.hpp

将include的内容展开, 等效的makefile文件如下所示:

main: main.o main.dg++ $< -o $@
main.o: main.cppg++ -c $< -o $@
main.d: main.cppg++ -MM $<main.o: main.cpp add.hpp

可以看出展开后,在该makefile中,出现了两个main.o的目标。

对于这种多规则同目标文件是有描述的:

在Makefile中,一个文件可以作为多个规则的目标出现。这种情况时,此目标文件的所有依赖文件将会被合并成此目标一个依赖文件列表,其中任何一个依赖文件比目标更新(比较目标文件和依赖文件的时间戳)时, make将会执行特定的命令来重建这个目标。

对于一个多规则的目标,重建此目标的命令只能出现在一个规则中(可以是多条命令)。如果多个规则同时给出重建此目标的命令,make将使用最后一个规则所以的命令,同时提示错误信息(一个特殊的例外是:使用"."开头的多规则目标文件,可以在多个规则中给出多个重建命令。这种方式只是为了和其他版本make进行兼容,一般在GNU make中应该避免使用这个功能)。

因此上述含有多目标的makefile可以转化为如下的makefile:

main: main.o main.dg++ $< -o $@
main.o: main.cpp add.hppg++ -c $< -o $@
main.d: main.cppg++ -MM $<

到此, 当我们修改add.hpp时, main.o会重新构建。 这似乎已经很完美了,但是真的如此吗?

我们试想此时在add.hpp中添加新的依赖sub.hpp, 因为main.cpp依赖于add.hpp,因此main.o会重新编译。 但是由于main.d文件只依赖于main.cpp, 因此main.d不会重新生成, 因此当我们这个时候修改sub.hpp时, main.cpp并不会更新。

因此这就要求main.d 也要添加对头文件的依赖。

修改上面的makefile:

main: main.o main.dg++ $< -o $@
%.o: %.cppg++ -c $< -o $@
%.d: %.cpp@set -e; rm -f $@; \g++ -MM $< > $@.$$$$; \sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \rm -f $@.$$$$include main.d

这里使用了sed去修改了gcc默认生成的依赖关系,即将main.o: main.cpp add.hpp转换成main.o main.d : main.cpp add.hpp

也就是在main.d的依赖中增加了add.hpp的依赖。

等效的makefile如下所示:

main: main.o main.dg++ $< -o $@
main.o: main.cpp add.hppg++ -c $< -o $@
main.d: main.cpp add.hppg++ -MM $<

这样, 即使在add.hpp中增加其他头文件依赖, 然后再修改其他的头文件,也会触发main.o的更新。

伪目标

在demo1中,我们提到过一个"clean"的目标,这是一个"伪目标"。

"伪目标"并不是一个文件,只是一个标签

假设我们不使用.PHONY:

clean:rm *.o temp

当本地也有一个文件叫做clean时, 那么rm *.o temp的操作将不会被执行。

因此为了避免和文件重名的这种情况,我们可以使用一个特殊的标记".PHONY"来显式地指明一个目标是"伪目标",向make说明,不管是否有这个文件,这个目标就是"伪目标"。

.PHONY : clean

只要有这个声明,不管是否有"clean"文件,要运行"clean"这个目标,只有"make clean"这样。于是整个过程可以这样写:

.PHONY : clean
clean :rm *.o temp

demo4: 一个综合案列使用内置函数+静态模式+自动生成依赖

在最后的这个例子中, 我们将综合运用上述的一些技巧去完成模块的构建。

该目录中有inc和src两个子目录, 其中inc目录中包含了add.hpp和sub.hpp两个头文件。

在src目录中, 包含一个main.cpp的入口函数所在的文件, 还包含一个math子目录,math子目录中包含了add.cpp和sub.cpp两个文件。

.
├── inc
│   ├── add.hpp
│   └── sub.hpp
├── Makefile
└── src├── main.cpp└── math├── add.cpp└── sub.cpp

以下是这些文件中的内容, 案例主要关注Makefile的编写, 因此源文件的代码都较为简单。

add.hpp

int my_add(int a, int b);

sub.hpp

int my_sub(int a, int b);

main.cpp

#include <iostream>
#include "add.hpp"
#include "sub.hpp"
using namespace std;int main()
{int a = 1;int b = 2;int c = my_add(a, b);cout << a << " + " << b << " = " << c << endl;int d = my_sub(a, b);cout << a << " - " << b << " = " << d << endl;return 0;
}

add.cpp

#include "add.hpp"int my_add(int a, int b)
{return a + b;
}

sub.cpp

#include "sub.hpp"int my_sub(int a, int b)
{return a - b;
}

以下是Makefile中的内容:

VPATH = src:src/math:incCXX=g++
MODULE=demo4
SOURCE_PATH += ./src \./src/math
TEMP_PATH=./tmp
TARGET=$(TEMP_PATH)/$(MODULE)CPP_SOURCES = $(foreach d,$(SOURCE_PATH),$(wildcard $(d)/*.cpp) )
CPP_OBJS += $(patsubst %.cpp, $(TEMP_PATH)/%.o, $(notdir $(CPP_SOURCES)))
CPP_DEPS += $(patsubst %.cpp, $(TEMP_PATH)/%.d, $(notdir $(CPP_SOURCES)))
OBJS = $(CPP_OBJS)CXXFLAGS+=-I./inc$(TARGET): $(OBJS) $(CPP_DEPS)@echo "generate final target"$(CXX) -o $@ $(OBJS)$(CPP_OBJS): $(TEMP_PATH)/%.o : %.cpp-@mkdir -p $(TEMP_PATH)@echo [$(MODULE) CXX] $<$(CXX) -c  $(CXXFLAGS) $< -o $@$(CPP_DEPS): $(TEMP_PATH)/%.d: %.cpp-@mkdir -p $(TEMP_PATH)-@echo -n "$(TEMP_PATH)/" > $@@set -e; rm -f $@; \$(CXX) -MM  $(CXXFLAGS) $< > $@.$$$$; \sed 's,\($*\)\.o[ :]*,tmp/\1.o $@ : ,g' < $@.$$$$ > $@; \rm -f $@.$$$$include $(CPP_DEPS).PHONY: clean
clean:rm -rf $(TEMP_PATH)/*.o  $(TEMP_PATH)/*.d  $(TEMP_PATH)/$(MODULE)

下面一一分析。

VPATH = src:src/math:inc

首先使用VPATH依赖文件存在的路径, 以便下面自动推导文件的相对路径。

CXX=g++
MODULE=demo4
SOURCE_PATH += ./src \./src/math
TEMP_PATH=./tmp
TARGET=$(TEMP_PATH)/$(MODULE)CXXFLAGS+=-I./inc

这里定义了一些变量,包括源文件的路径, 目标文件存放的位置, 编译参数等等。

CPP_SOURCES = $(foreach d,$(SOURCE_PATH),$(wildcard $(d)/*.cpp) )
CPP_OBJS += $(patsubst %.cpp, $(TEMP_PATH)/%.o, $(notdir $(CPP_SOURCES)))
CPP_DEPS += $(patsubst %.cpp, $(TEMP_PATH)/%.d, $(notdir $(CPP_SOURCES)))
OBJS = $(CPP_OBJS)

这里首先使用foreach去遍历SOURCE_PATH路径下的所有的.cpp文件

然后将.cpp文件做字符串替换, 替换为.o, 同时增加了存放的路径, 这里使用了patsubst做字符串替换, 并使用了notdir去获取文件名。

$(TARGET): $(OBJS) $(CPP_DEPS)@echo "generate final target"$(CXX) -o $@ $(OBJS)

这里展开便是:

tmp/demo4: tmp/main.o tmp/sub.o tmp/add.o tmp/main.d tmp/sub.d tmp/main.dg++ -o tmp/demo4 tmp/main.o tmp/sub.o tmp/add.o
$(CPP_OBJS): $(TEMP_PATH)/%.o : %.cpp-@mkdir -p $(TEMP_PATH)@echo [$(MODULE) CXX] $<$(CXX) -c  $(COMPILEFLAGS) $< -o $@

这里展开便是:

tmp/main.o: src/main.cppg++ -c src/main.cpp -o tmp/main.o -I./inc
tmp/add.o: src/math/add.cppg++ -c src/math/add.cpp -o tmp/add.o -I./inc
tmp/sub.o: src/math/sub.cppg++ -c src/math/sub.cpp -o tmp/sub.o -I./inc

该步骤就是生成了.o文件:

$(CPP_DEPS): $(TEMP_PATH)/%.d: %.cpp-@mkdir -p $(TEMP_PATH)-@echo -n "$(TEMP_PATH)/" > $@@set -e; rm -f $@; \$(CXX) -MM  $(CXXFLAGS) $< > $@.$$$$; \sed 's,\($*\)\.o[ :]*,tmp/\1.o $@ : ,g' < $@.$$$$ > $@; \rm -f $@.$$$$

该步骤使用了g++ -MM参数用于自动生成依赖文件, 以便于当头文件修改时,也可以自动编译/

.PHONY: clean
clean:rm -rf $(TEMP_PATH)/*.o  $(TEMP_PATH)/main

最后这个模块用于清除生成的文件。

至此, demo4结束。

相关文章:

makefile案例学习

makefile案例学习 很多时候&#xff0c; 我们在git clone完一个project之后&#xff0c;就会让我们使用make命令进行项目的构建。这个make命令的背后就是按照了Makefile文件定义的格式去完成项目构建。 因此Makefile的作用就是帮助程序员进行项目的构建&#xff0c;它按照项目…...

MySQL性能优化六 事物隔离级别与锁机制

概述 我们的数据库一般都会并发执行多个事务&#xff0c;多个事务可能会并发的对相同的一批数据进行增删改查操作&#xff0c;可能就会导致我们说的脏写、脏读、不可重复读、幻读这些问题。 这些问题的本质都是数据库的多事务并发问题&#xff0c;为了解决多事务并发问题&#…...

四数之和-力扣18-java排序+双指针

一、题目描述给你一个由 n 个整数组成的数组 nums &#xff0c;和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] &#xff08;若两个四元组元素一一对应&#xff0c;则认为两个四元组重复&#xff09;&#xff1a…...

操作系统开发:BIOS/MBR基础与调试

这里在实验之前需要下载 Bochs-win32-2.6.11 作者使用的是Linux版本的&#xff0c;在Linux写代码不太舒服&#xff0c;所以最好在Windows上做实验&#xff0c;下载好虚拟机以后还需要下载Nasm汇编器&#xff0c;以及GCC编译器&#xff0c;为了能够使用DD命令实现磁盘拷贝&#…...

华为OD机试真题JAVA实现【数组合并】真题+解题思路+代码(20222023)

🔥系列专栏 华为OD机试(JAVA)真题目录汇总华为OD机试(Python)真题目录汇总华为OD机试(C++)真题目录汇总华为OD机试(JavaScript)真题目录汇总文章目录 🔥系列专栏题目输入输出示例一输入输出示例二输入输出解题思路核心知识点...

说说Real DOM和Virtual DOM的区别?优缺点?

说说Real DOM和Virtual DOM的区别&#xff1f;优缺点&#xff1f;Real DOM(真实的DOM)真实dom的优缺点&#xff1f;Virtual DOM(虚拟的DOM)虚拟dom的优缺点&#xff1f;两者的区别Real DOM(真实的DOM) 在页面渲染出的每个节点都是一个真实的DOM结构 <div class"root&…...

使用脚本以可读的 JSON 格式显示 curl 命令输出

在我们经常调试微服务或者使用 Elasticsearch API 时&#xff0c;经常会使用curl 来进行调试。但是有时我们的输出不尽如意。显示的不是一 pretty 格式进行输出的。我们有时还必须借助于其他的一些网站工具&#xff0c;比如 Best JSON Formatter and JSON Validator: Online JS…...

计算机网络9:HTTP和HTTPS的区别

1.HTTP和HTTPS的区别 &#xff08;1&#xff09;安全性 HTTP是超文本传输协议&#xff0c;信息传输存在安全问题HTTPS是安全套接字超文本传输协议&#xff0c;在TCP和HTTP之间加入了SSL/TLS安全协议&#xff0c;进行加密传输 &#xff08;2&#xff09;连接步骤HTTP建立相对简…...

Spring+SpringMVC+SpringBoot+MyBatis面试题

什么是Spring框架&#xff1f;使用Spring框架的好处是什么&#xff1f;Spring是一款开源的轻量级Java开发框架&#xff0c;可以提高开发人员的开发效率以及系统的可维护性。Spring框架是很多模块的集合&#xff0c;使用这些模块可以很方便地协助我们进行开发&#xff0c;比如说…...

ContextCapture Master 倾斜摄影测量实景三维建模技术

ContextCapture实景建模大师是一套无需人工干预&#xff0c;通过影像自动生成高分辨率的三维模型的软件解决方案。它集合了全球最先进数字影像处理、计算机虚拟现实以及计算机几何图形算法&#xff0c;在易用性、数据兼容性、运算性能、友好的人机交互及自由的硬件配置兼容性等…...

MySQL事务

文章目录MySQL事务事务的四个特性 ACID事务提交的类型事务的使用MySQL事务 事务是什么&#xff1f; 事务就是一组逻辑操作单元&#xff0c;是数据从一种状态变成另外一种状态。整个单元有一个或多个SQL语句构成&#xff0c;在这个操作单元中&#xff0c;每一个SQL语句相互依赖…...

CData Drivers for Acumatica

CData Drivers for Acumatica Acumatica的CData驱动程序为用户提供了使用AcumaticaERP数据的便捷途径&#xff0c;该数据来自商业智能、分析、定制应用程序、报告以及ETL。通过JDBC、ADO.NET和ODBC等标准驱动程序&#xff0c;以及与PowerShell、Power BI、Excel、SSIS等流行应用…...

智慧税务+数据可视化:企业财务管理告别难题

一、引言在发展社会主义市场经济的过程中&#xff0c;税收承担着组织财政收入、调控经济、调节社会分配的职能。中国每年财政收入的90%以上来自税收&#xff0c;其地位和作用越来越重要&#xff0c;可称之为国家经济的“晴雨表”&#xff0c;有效进行税务管理、充分挖掘税务大数…...

Ansible中常用的模块

目录 一、Ansible Ad-Hoc命令集 1 Ad-hoc 使用场景 2 Ansible的并发特性 3 Ansible-doc用法 4 ansible命令运行方式及常用参数 5 ansible的基本颜色代表 6 ansible中的常用模块 command模块 shell模块 script模块 copy模块 fetch模块 unarchive模块 archive模块…...

问:你是如何进行react状态管理方案选择的?

前言&#xff1a;最近接触到一种新的&#xff08;对我个人而言&#xff09;状态管理方式&#xff0c;它没有采用现有的开源库&#xff0c;如redux、mobx等&#xff0c;也没有使用传统的useContext&#xff0c;而是用useState useEffect写了一个发布订阅者模式进行状态管理&…...

【华为OD机试真题 java、python、jsNode】任务总执行时长【2022 Q4 100分】

代码请进行一定修改后使用,本代码保证100%通过率,本题提供了 java、python、JsNode三种代码 题目描述 任务编排服务负责对任务进行组合调度。参与编排的任务有两种类型,其中一种执行时长为taskA,另一种执行时长为taskB。任务一旦开始执行不能被打断,且任务可连续执行。服…...

react基础

react组件传参 父传子 父组件 < ChildA value{this.state.num}></ChildA> 子组件 {props.value}接收父组件传入参数 ChildA.defaultProps{vaue:1} defaultProps默认参数 子传父 props回调函数形式 父 setNum>v>this.setState({num:v}) v形参 < ChildA…...

【Spark分布式内存计算框架——Spark SQL】2. SparkSQL 概述(上)

第二章 SparkSQL 概述 Spark SQL允许开发人员直接处理RDD&#xff0c;同时可以查询在Hive上存储的外部数据。Spark SQL的一个重要特点就是能够统一处理关系表和RDD&#xff0c;使得开发人员可以轻松的使用SQL命令进行外部查询&#xff0c;同时进行更加复杂的数据分析。 2.1 前…...

Kubeadm搭建K8S

目录 一、部署步骤 1、实验环境 2、环境准备 3、所有节点安装Docker 4、 所有节点配置K8S源 5、所有节点安装kubeadm&#xff0c;kubelet和kubectl 6、部署 kubernetes Master 节点 7、token制作 8、k8s-node节点加入master节点 9、 master节点安装部署pod网络插件&a…...

【技术分享】搭建java项目引入外部依赖教程

文章目录引言如何在linux中编译运行java程序IDEA中新建一个简单的java工程项目并运行IDEA中如何引入外部依赖并运行maven引入log4j jar包手工引入log4j jar包如何使用命令行的方式添加外部依赖如何新建一个spring源码项目并为其添加依赖给定一个spring工程源码&#xff0c;如何…...

算法 ——世界 二

个人简介&#xff1a;云计算网络运维专业人员&#xff0c;了解运维知识&#xff0c;掌握TCP/IP协议&#xff0c;每天分享网络运维知识与技能。个人爱好: 编程&#xff0c;打篮球&#xff0c;计算机知识个人名言&#xff1a;海不辞水&#xff0c;故能成其大&#xff1b;山不辞石…...

数据治理CDGP选择题 4

5、根据DMBOK2&#xff0c;在实施数据治理时&#xff0c;要注重数据标准的建设&#xff0c;以下关于数据标准的描述&#xff0c;哪个选项是不正确的? (知识点: CDGP仿真题)A.数据标准必须得到有效沟通、监控&#xff0c;并被定期审查和更新;最重要的是&#xff0c;必须有强制手…...

动态规划之01背包问题和完全背包问题

01背包的问题描述&#xff1a;&#xff08;内容参考代码随想录&#xff09;有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i]&#xff0c;得到的价值是value[i] 。每件物品只能用一次&#xff0c;求解将哪些物品装入背包里物品价值总和最大。问题示例&#…...

MATLAB算法实战应用案例精讲-【图像处理】数字图像灰度化(附Java、python、matlab和opencv代码实现)

目录 前言 几个相关概念 1、RGB 2、ARGB 3、灰度化 4.图像点运算 5.线性点运算...

Linux(强大的yum命令)

yum 读 [jʌm] &#xff0c;中文谐音&#xff1a; 样安ing。 yum&#xff08; Yellow dog Updater, Modified&#xff09;是一个在 Fedora 和 RedHat 以及 SUSE 中的 Shell 前端软件包管理器。 基于 RPM 包管理&#xff0c;能够从指定的服务器自动下载 RPM 包并且安装&#x…...

28.结语

文章目录28 Epilogue 结语28 Epilogue 结语 Don’t let it end like this. Tell them I said something. 不要让它就这样结束。 告诉他们我说了些什么 —Pancho Villa 您已到达旅程的尽头。 辛苦了 我们希望您能从本书中找到一些有价值的收获。 我们建议该列表应包括以下内…...

ICRS、GCRS、CIRS、TIRS和ITRS坐标系统简介

ICRS、GCRS、CIRS、TIRS和ITRS坐标系统简介1. 简介2. ICRS、GCRS、CIRS、TIRS和ITRS分别介绍2.1 ICRS详细说明2.2 GCRS详细说明2.3 CIRS详细说明2.4 TIRS详细说明2.5 ITRS 详细说明1. 简介 ICRS、GCRS、CIRS、TIRS和ITRS都是天文学中使用的坐标系统&#xff1a; ICRS (Intern…...

你是真的“C”——详解结构体知识点

你是真的“C”——详解结构体知识点&#x1f60e;前言&#x1f64c;什么是结构体&#xff1f;&#x1f64c;1. 结构体的声明&#x1f64c;1.1 结构的基础知识1.2 结构的声明1.3 结构成员的类型1.4 结构体变量的定义和初始化2. 结构体成员的访问&#x1f64c;3结构体传参&#x…...

2023新华为OD机试题 - 单词接龙(JavaScript) | 刷完必过

单词接龙 题目 单词接龙的规则是: 可用于接龙的单词,首字母必须要与前一个单词的尾字母相同; 当存在多个首字母相同的单词时,取长度最长的单词; 如果长度也相等,则取字典序最小的单词; 已经参与接龙的单词不能重复使用; 现给定一组全部由小写字母组成的单词数组, 并指…...

第一章 一般错误信息 - 错误代码 0 到 99

文章目录第一章 一般错误信息 - 错误代码 0 到 99一般错误信息错误代码 0 到 99第一章 一般错误信息 - 错误代码 0 到 99 一般错误信息 错误代码被报告为 ERROR #nnn。这些错误代码有时称为 %Status 错误代码。 可以使用 DisplayError() 和 Error() 方法确定指定错误代码的错…...

dreamweaver网站制作步骤/文山seo

扬帆起航&#xff0c;希望就在前方2010年11月20号&#xff0c;北京PMP1011班如期开班&#xff0c;大家对周末两天的课程反映良好&#xff0c;感触颇多&#xff0c;觉得学到了很多知识。11月20号&#xff1a;早上8点就有学员陆续报道&#xff0c;领取讲义和考勤卡&#xff0c;按…...

网站系统建设申请报告/百度网盘下载官网

开发混合app上架应用市场&#xff0c;需要进行应用签名&#xff0c;但是申请签名如果没搞过&#xff0c;会特别麻烦&#xff0c;所以我自自己总结了一下申请的步骤&#xff0c;在此记录一下1.首先需要下载安装java环境即jdk&#xff0c;2.配置环境变量假设JDK安装在C:\Program …...

网站前台用什么做/网站seo排名优化工具在线

注意下base64的验证码&#xff0c;有类似data:image/jpg;base64这种前缀&#xff0c;后面才是具体字符串&#xff0c;所以要么后端拼接上前缀&#xff0c;要么前端&#xff0c;才能解析出来。...

二级域名网站可以做关键词优化吗/今日广东头条新闻

Emacs v25.1 在win7 卡顿得厉害&#xff0c;滚动条拖动一下&#xff0c;就卡半天没反应&#xff0c;就像中了病毒一样。 解决&#xff1a; 打开菜单Options->Set Default Font&#xff0c;将字体改为宋体。 然后再回到Options菜单&#xff0c;点Save Options。这一步可千…...

做房产网站在百度推广推广费/关键词挖掘网站

一、Linux 中文件名的命名规范1.严格区分大小写2.可以使用除了“/”以外的任意字符&#xff0c;最长可以达到255个字符&#xff0c;但是不建议使用特殊字符和空格作为用户名。容易造成混淆&#xff0c;可能误将“a b”当成两个文件“a”和“b”3.后缀名&#xff0c;比如“.txt”…...

网站建设 域名 空间/网页优化公司

当我们在ASP.NET开发时&#xff0c;经常会遇到一个头疼的问题&#xff1a;字符数的控制 由于数据库的字段长度是固定的&#xff0c;因此在进行字符输入时&#xff0c;最关键的就是控制字符的个数不能超过字段的长度&#xff0c;要不然&#xff0c;一个个异常会让人疯掉的。 …...