(tab).

.

.

例如,考虑以下的 makefile :

=== makefile 开始 ===

myprog : foo.o bar.o

gcc foo.o bar.o -o myprog

foo.o : foo.c foo.h bar.h

gcc -c foo.c -o foo.o

bar.o : bar.c bar.h

gcc -c bar.c -o bar.o

=== makefile 结束 ===

这是一个非常基本的 makefile —— make 从最上面开始,把上 面第一个目的,‘myprog’,做为它的主要目标(一个它需要保 证其总是最新的最终目标)。给出的规则说明只要文件‘myprog’ 比文件‘foo.o’或‘bar.o’中的任何一个旧,下一行的命令将 会被执行。

但是,在检查文件 foo.o 和 bar.o 的时间戳之前,它会往下查 找那些把 foo.o 或 bar.o 做为目标文件的规则。它找到的关于 foo.o 的规则,该文件的依靠文件是 foo.c, foo.h 和 bar.h 。 它从下面再找不到生成这些依靠文件的规则,它就开始检查磁碟 上这些依靠文件的时间戳。如果这些文件中任何一个的时间戳比 foo.o 的新,命令 'gcc -o foo.o foo.c' 将会执行,从而更新 文件 foo.o 。

接下来对文件 bar.o 做类似的检查,依靠文件在这里是文件 bar.c 和 bar.h 。

现在, make 回到‘myprog’的规则。如果刚才两个规则中的任 何一个被执行,myprog 就需要重建(因为其中一个 .o 档就会比 ‘myprog’新),因此连接命令将被执行。

希望到此,你可以看出使用 make 工具来建立程序的好处——前 一章中所有繁琐的检查步骤都由 make 替你做了:检查时间戳。 你的源码文件里一个简单改变都会造成那个文件被重新编译(因 为 .o 文件依靠 .c 文件),进而可执行文件被重新连接(因为 .o 文件被改变了)。其实真正的得益是在当你改变一个 header 档的时候——你不再需要记住那个源码文件依靠它,因为所有的 资料都在 makefile 里。 make 会很轻松的替你重新编译所有那 些因依靠这个 header 文件而改变了的源码文件,如有需要,再 进行重新连接。

当然,你要确定你在 makefile 中所写的规则是正确无误的,只 列出那些在源码文件中被 #include 的 header 档……

2.2 编写 make 规则 (Rules)

最明显的(也是最简单的)编写规则的方法是一个一个的查 看源码文件,把它们的目标文件做为目的,而C源码文件和被它 #include 的 header 档做为依靠文件。但是你也要把其它被这些 header 档 #include 的 header 档也列为依靠文件,还有那些被 包括的文件所包括的文件……然后你会发现要对越来越多的文件 进行管理,然后你的头发开始脱落,你的脾气开始变坏,你的脸 色变成菜色,你走在路上开始跟电线杆子碰撞,终于你捣毁你的 电脑显示器,停止编程。到低有没有些容易点儿的方法呢?

当然有!向编译器要!在编译每一个源码文件的时候,它实在应 该知道应该包括什么样的 header 档。使用 gcc 的时候,用 -M 开关,它会为每一个你给它的C文件输出一个规则,把目标文件 做为目的,而这个C文件和所有应该被 #include 的 header 文 件将做为依靠文件。注意这个规则会加入所有 header 文件,包 括被角括号(`')和双引号(`"')所包围的文件。其实我们可以 相当肯定系统 header 档(比如 stdio.h, stdlib.h 等等)不会 被我们更改,如果你用 -MM 来代替 -M 传递给 gcc,那些用角括 号包围的 header 档将不会被包括。(这会节省一些编译时间)

由 gcc 输出的规则不会含有命令部分;你可以自己写入你的命令 或者什么也不写,而让 make 使用它的隐含的规则(参考下面的 2.4 节)。

2.3 Makefile 变量

上面提到 makefiles 里主要包含一些规则。它们包含的其它的东 西是变量定义。

makefile 里的变量就像一个环境变量(environment variable)。 事实上,环境变量在 make 过程中被解释成 make 的变量。这些 变量是大小写敏感的,一般使用大写字母。它们可以从几乎任何 地方被引用,也可以被用来做很多事情,比如:

i) 贮存一个文件名列表。在上面的例子里,生成可执行文件的 规则包含一些目标文件名做为依靠。在这个规则的命令行 里同样的那些文件被输送给 gcc 做为命令参数。如果在这 里使用一个变数来贮存所有的目标文件名,加入新的目标 文件会变的简单而且较不易出错。

ii) 贮存可执行文件名。如果你的项目被用在一个非 gcc 的系 统里,或者如果你想使用一个不同的编译器,你必须将所 有使用编译器的地方改成用新的编译器名。但是如果使用一 个变量来代替编译器名,那么你只需要改变一个地方,其 它所有地方的命令名就都改变了。

iii) 贮存编译器旗标。假设你想给你所有的编译命令传递一组 相同的选项(例如 -Wall -O -g);如果你把这组选项存 入一个变量,那么你可以把这个变量放在所有呼叫编译器 的地方。而当你要改变选项的时候,你只需在一个地方改 变这个变量的内容。

要设定一个变量,你只要在一行的开始写下这个变量的名字,后 面跟一个 = 号,后面跟你要设定的这个变量的值。以后你要引用 这个变量,写一个 $ 符号,后面是围在括号里的变量名。比如在 下面,我们把前面的 makefile 利用变量重写一遍:

=== makefile 开始 ===

OBJS = foo.o bar.o

CC = gcc

CFLAGS = -Wall -O -g

myprog : $(OBJS)

$(CC) $(OBJS) -o myprog

foo.o : foo.c foo.h bar.h

$(CC) $(CFLAGS) -c foo.c -o foo.o

bar.o : bar.c bar.h

$(CC) $(CFLAGS) -c bar.c -o bar.o

=== makefile 结束 ===

还有一些设定好的内部变量,它们根据每一个规则内容定义。三个 比较有用的变量是 $@, $

=== makefile 开始 ===

OBJS = foo.o bar.o

CC = gcc

CFLAGS = -Wall -O -g

myprog : $(OBJS)

$(CC) $^ -o $@

foo.o : foo.c foo.h bar.h

$(CC) $(CFLAGS) -c $

bar.o : bar.c bar.h

$(CC) $(CFLAGS) -c $

=== makefile 结束 ===

你可以用变量做许多其它的事情,特别是当你把它们和函数混合 使用的时候。如果需要更进一步的了解,请参考 GNU Make 手册。 ('man make', 'man makefile')

2.4 隐含规则 (Implicit Rules)

请注意,在上面的例子里,几个产生 .o 文件的命令都是一样的。 都是从 .c 文件和相关文件里产生 .o 文件,这是一个标准的步 骤。其实 make 已经知道怎么做——它有一些叫做隐含规则的内 置的规则,这些规则告诉它当你没有给出某些命令的时候,应该 怎么办。

如果你把生成 foo.o 和 bar.o 的命令从它们的规则中删除, make 将会查找它的隐含规则,然后会找到一个适当的命令。它的命令会 使用一些变量,因此你可以按照你的想法来设定它:它使用变量 CC 做为编译器(象我们在前面的例子),并且传递变量 CFLAGS (给 C 编译器,C++ 编译器用 CXXFLAGS ),CPPFLAGS ( C 预 处理器旗标), TARGET_ARCH (现在不用考虑这个),然后它加 入旗标 '-c' ,后面跟变量 $

$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $

当然你可以按照你自己的需要来定义这些变量。这就是为什么用 gcc 的 -M 或 -MM 开关输出的码可以直接用在一个 makefile 里。

2.5 假象目的 (Phony Targets)

假设你的一个项目最后需要产生两个可执行文件。你的主要目标 是产生两个可执行文件,但这两个文件是相互独立的——如果一 个文件需要重建,并不影响另一个。你可以使用“假象目的”来 达到这种效果。一个假象目的跟一个正常的目的几乎是一样的, 只是这个目的文件是不存在的。因此, make 总是会假设它需要 被生成,当把它的依赖文件更新后,就会执行它的规则里的命令 行。

如果在我们的 makefile 开始处输入:

all : exec1 exec2

其中 exec1 和 exec2 是我们做为目的的两个可执行文件。 make 把这个 'all' 做为它的主要目的,每次执行时都会尝试把 'all' 更新。但既然这行规则里没有哪个命令来作用在一个叫 'all' 的 实际文件(事实上 all 并不会在磁碟上实际产生),所以这个规 则并不真的改变 'all' 的状态。可既然这个文件并不存在,所以 make 会尝试更新 all 规则,因此就检查它的依靠 exec1, exec2 是否需要更新,如果需要,就把它们更新,从而达到我们的目的。

假象目的也可以用来描述一组非预设的动作。例如,你想把所有由 make 产生的文件删除,你可以在 makefile 里设立这样一个规则:

veryclean :

rm *.o

rm myprog

前提是没有其它的规则依靠这个 'veryclean' 目的,它将永远 不会被执行。但是,如果你明确的使用命令 'make veryclean' , make 会把这个目的做为它的主要目标,执行那些 rm 命令。

如果你的磁碟上存在一个叫 veryclean 文件,会发生什么事?这 时因为在这个规则里没有任何依靠文件,所以这个目的文件一定是 最新的了(所有的依靠文件都已经是最新的了),所以既使用户明 确命令 make 重新产生它,也不会有任何事情发生。解决方法是标 明所有的假象目的(用 .PHONY),这就告诉 make 不用检查它们 是否存在于磁碟上,也不用查找任何隐含规则,直接假设指定的目 的需要被更新。在 makefile 里加入下面这行包含上面规则的规则:

.PHONY : veryclean

就可以了。注意,这是一个特殊的 make 规则,make 知道 .PHONY 是一个特殊目的,当然你可以在它的依靠里加入你想用的任何假象 目的,而 make 知道它们都是假象目的。

2.6 函数 (Functions)

makefile 里的函数跟它的变量很相似——使用的时候,你用一个 $ 符号跟开括号,函数名,空格后跟一列由逗号分隔的参数,最后 用关括号结束。例如,在 GNU Make 里有一个叫 'wildcard' 的函 数,它有一个参数,功能是展开成一列所有符合由其参数描述的文 件名,文件间以空格间隔。你可以像下面所示使用这个命令:

SOURCES = $(wildcard *.c)

这行会产生一个所有以 '.c' 结尾的文件的列表,然后存入变量 SOURCES 里。当然你不需要一定要把结果存入一个变量。

另一个有用的函数是 patsubst ( patten substitude, 匹配替 换的缩写)函数。它需要3个参数——第一个是一个需要匹配的式样,第二个表示用什么来替换它,第三个是一个需要被处理的 由空格分隔的字列。例如,处理那个经过上面定义后的变量,

OBJS = $(patsubst %.c,%.o,$(SOURCES))

这行将处理所有在 SOURCES 字列中的字(一列文件名),如果它的 结尾是 '.c' ,就用 '.o' 把 '.c' 取代。注意这里的 % 符号将匹 配一个或多个字符,而它每次所匹配的字串叫做一个‘柄’(stem) 。 在第二个参数里, % 被解读成用第一参数所匹配的那个柄。

2.7 一个比较有效的 makefile

利用我们现在所学的,我们可以建立一个相当有效的 makefile 。 这个 makefile 可以完成大部分我们需要的依靠检查,不用做太大 的改变就可直接用在大多数的项目里。

首先我们需要一个基本的 makefile 来建我们的程序。我们可以让 它搜索当前目录,找到源码文件,并且假设它们都是属于我们的项 目的,放进一个叫 SOURCES 的变量。这里如果也包含所有的 *.cc 文件,也许会更保险,因为源码文件可能是 C++ 码的。

SOURCES = $(wildcard *.c *.cc)

利用 patsubst ,我们可以由源码文件名产生目标文件名,我们需 要编译出这些目标文件。如果我们的源码文件既有 .c 文件,也有 .cc 文件,我们需要使用相嵌的 patsubst 函数呼叫:

OBJS = $(patsubst %.c,%.o,$(patsubst %.cc,%.o,$(SOURCES)))

最里面一层 patsubst 的呼叫会对 .cc 文件进行后缀替代,产生的结 果被外层的 patsubst 呼叫处理,进行对 .c 文件后缀的替代。

现在我们可以设立一个规则来建可执行文件:

myprog : $(OBJS)

gcc -o myprog $(OBJS)

进一步的规则不一定需要, gcc 已经知道怎么去生成目标文件 (object files) 。下面我们可以设定产生依靠信息的规则:

depends : $(SOURCES)

gcc -M $(SOURCES) > depends

在这里如果一个叫 'depends' 的文件不存在,或任何一个源码文件 比一个已存在的 depends 文件新,那么一个 depends 文件会被生 成。depends 文件将会含有由 gcc 产生的关于源码文件的规则(注 意 -M 开关)。现在我们要让 make 把这些规则当做 makefile 档 的一部分。这里使用的技巧很像 C 语言中的 #include 系统——我 们要求 make 把这个文件 include 到 makefile 里,如下:

include depends

GNU Make 看到这个,检查 'depends' 目的是否更新了,如果没有, 它用我们给它的命令重新产生 depends 档。然后它会把这组(新) 规则包含进来,继续处理最终目标 'myprog' 。当看到有关 myprog 的规则,它会检查所有的目标文件是否更新——利用 depends 文件 里的规则,当然这些规则现在已经是更新过的了。

这个系统其实效率很低,因为每当一个源码文件被改动,所有的源码 文件都要被预处理以产生一个新的 'depends' 文件。而且它也不是 100% 的安全,这是因为当一个 header 档被改动,依靠信息并不会 被更新。但就基本工作来说,它也算相当有用的了。

2.8 一个更好的 makefile

这是一个我为我大多数项目设计的 makefile 。它应该可以不需要修 改的用在大部分项目里。我主要把它用在 djgpp 上,那是一个 DOS 版的 gcc 编译器。因此你可以看到执行的命令名、 'alleg' 程序包、 和 RM -F 变量都反映了这一点。

=== makefile 开始 ===

######################################

#

# Generic makefile

#

# by George Foot

# email: george.foot@merton.ox.ac.uk

#

# Copyright (c) 1997 George Foot

# All rights reserved.

# 保留所有版权

#

# No warranty, no liability;

# you use this at your own risk.

# 没保险,不负责

# 你要用这个,你自己担风险

#

# You are free to modify and

# distribute this without giving

# credit to the original author.

# 你可以随便更改和散发这个文件

# 而不需要给原作者什么荣誉。

# (你好意思?)

#

######################################

### Customising

# 用户设定

#

# Adjust the following if necessary; EXECUTABLE is the target

# executable's filename, and LIBS is a list of libraries to link in

# (e.g. alleg, stdcx, iostr, etc). You can override these on make's

# command line of course, if you prefer to do it that way.

#

# 如果需要,调整下面的东西。 EXECUTABLE 是目标的可执行文件名, LIBS

# 是一个需要连接的程序包列表(例如 alleg, stdcx, iostr 等等)。当然你

# 可以在 make 的命令行覆盖它们,你愿意就没问题。

#

EXECUTABLE := mushroom.exe

LIBS := alleg

# Now alter any implicit rules' variables if you like, e.g.:

#

# 现在来改变任何你想改动的隐含规则中的变量,例如

CFLAGS := -g -Wall -O3 -m486

CXXFLAGS := $(CFLAGS)

# The next bit checks to see whether rm is in your djgpp bin

# directory; if not it uses del instead, but this can cause (harmless)

# `File not found' error messages. If you are not using DOS at all,

# set the variable to something which will unquestioningly remove

# files.

#

# 下面先检查你的 djgpp 命令目录下有没有 rm 命令,如果没有,我们使用

# del 命令来代替,但有可能给我们 'File not found' 这个错误信息,这没

# 什么大碍。如果你不是用 DOS ,把它设定成一个删文件而不废话的命令。

# (其实这一步在 UNIX 类的系统上是多余的,只是方便 DOS 用户。 UNIX

# 用户可以删除这5行命令。)

ifneq ($(wildcard $(DJDIR)/bin/rm.exe),)

RM-F := rm -f

else

RM-F := del

endif

# You shouldn't need to change anything below this point.

#

# 从这里开始,你应该不需要改动任何东西。(我是不太相信,太NB了!)

SOURCE := $(wildcard *.c) $(wildcard *.cc)

OBJS := $(patsubst %.c,%.o,$(patsubst %.cc,%.o,$(SOURCE)))

DEPS := $(patsubst %.o,%.d,$(OBJS))

MISSING_DEPS := $(filter-out $(wildcard $(DEPS)),$(DEPS))

MISSING_DEPS_SOURCES := $(wildcard $(patsubst %.d,%.c,$(MISSING_DEPS)) \

$(patsubst %.d,%.cc,$(MISSING_DEPS)))

CPPFLAGS += -MD

.PHONY : everything deps objs clean veryclean rebuild

everything : $(EXECUTABLE)

deps : $(DEPS)

objs : $(OBJS)

clean :

@$(RM-F) *.o

@$(RM-F) *.d

veryclean: clean

@$(RM-F) $(EXECUTABLE)

rebuild: veryclean everything

ifneq ($(MISSING_DEPS),)

$(MISSING_DEPS) :

@$(RM-F) $(patsubst %.d,%.o,$@)

endif

-include $(DEPS)

$(EXECUTABLE) : $(OBJS)

gcc -o $(EXECUTABLE) $(OBJS) $(addprefix -l,$(LIBS))

=== makefile 结束 ===

有几个地方值得解释一下的。首先,我在定义大部分变量的时候使 用的是 := 而不是 = 符号。它的作用是立即把定义中参考到的函 数和变量都展开了。如果使用 = 的话,函数和变量参考会留在那 儿,就是说改变一个变量的值会导致其它变量的值也被改变。例 如:

A = foo

B = $(A)

# 现在 B 是 $(A) ,而 $(A) 是 'foo' 。

A = bar

# 现在 B 仍然是 $(A) ,但它的值已随着变成 'bar' 了。

B := $(A)

# 现在 B 的值是 'bar' 。

A = foo

# B 的值仍然是 'bar' 。

make 会忽略在 # 符号后面直到那一行结束的所有文字。

ifneg...else...endif 系统是 makefile 里让某一部分码有条件的 失效/有效的工具。 ifeq 使用两个参数,如果它们相同,它把直 到 else (或者 endif ,如果没有 else 的话)的一段码加进 makefile 里;如果不同,把 else 到 endif 间的一段码加入 makefile (如果有 else )。 ifneq 的用法刚好相反。

'filter-out' 函数使用两个用空格分开的列表,它把第二列表中所 有的存在于第一列表中的项目删除。我用它来处理 DEPS 列表,把所 有已经存在的项目都删除,而只保留缺少的那些。

我前面说过, CPPFLAGS 存有用于隐含规则中传给预处理器的一些 旗标。而 -MD 开关类似 -M 开关,但是从源码文件 .c 或 .cc 中 形成的文件名是使用后缀 .d 的(这就解释了我形成 DEPS 变量的 步骤)。DEPS 里提到的文件后来用 '-include' 加进了 makefile 里,它隐藏了所有因文件不存在而产生的错误信息。

如果任何依靠文件不存在, makefile 会把相应的 .o 文件从磁碟 上删除,从而使得 make 重建它。因为 CPPFLAGS 指定了 -MD , 它的 .d 文件也被重新产生。

最后, 'addprefix' 函数把第二个参数列表的每一项前缀上第一 个参数值。

这个 makefile 的那些目的是(这些目的可以传给 make 的命令行 来直接选用):

everything:(预设) 更新主要的可执行程序,并且为每一个 源码文件生成或更新一个 '.d' 文件和一个 '.o' 文件。

deps: 只是为每一个源码程序产生或更新一个 '.d' 文件。

objs: 为每一个源码程序生成或更新 '.d' 文件和目标文件。

clean: 删除所有中介/依靠文件( *.d 和 *.o )。

veryclean: 做 `clean' 和删除可执行文件。

rebuild: 先做 `veryclean' 然后 `everything' ;既完全重建。

除了预设的 everything 以外,这里头只有 clean , veryclean , 和 rebuild 对用户是有意义的。

我还没有发现当给出一个源码文件的目录,这个 makefile 会失败的 情况,除非依靠文件被弄乱。如果这种弄乱的情况发生了,只要输入 `make clean' ,所有的目标文件和依靠文件会被删除,问题就应该 被解决了。当然,最好不要把它们弄乱。如果你发现在某种情况下这 个 makefile 文件不能完成它的工作,请告诉我,我会把它整好的。

3 总结

~~~~~~~~~~~~~~~

我希望这篇文章足够详细的解释了多文件项目是怎么运作的,也说明了 怎样安全而合理的使用它。到此,你应该可以轻松的利用 GNU Make 工 具来管理小型的项目,如果你完全理解了后面几个部分的话,这些对于 你来说应该没什么困难。

GNU Make 是一件强大的工具,虽然它主要是用来建立程序,它还有很多 别的用处。如果想要知道更多有关这个工具的知识,它的句法,函数, 和许多别的特点,你应该参看它的参考文件 (info pages, 别的 GNU 工具也一样,看它们的 info pages. )。

C Scene 官方网站: http://cscene.differnet.org

C Scene 官方电邮: cscene@mindless.com

This page is Copyright ? 1997 By C Scene. All Rights Reserved

gun c语言教程,(转)GUN make指南相关推荐

  1. 小白也能看懂:最全无线渗透测试与攻防+Fluxison钓鱼教程+路由器安全设置指南 2021.3.31

    小白也能看懂:最全无线渗透测试与攻防+Fluxison钓鱼教程+路由器安全设置指南 2021.3.31 一. 无线AP的加密方式 WEP加密 WPA-PSK/WPA2-PSK加密 WPS加密 二. 无 ...

  2. 区块链教程、区块链指南、区块链中文手册、区块链原理

    区块链的技术有望以一个更低的成本解决更广泛的信任问题所以被受人关注. 维基说了那么长一串还是没有说清楚区块链到底是什么,又有什么用.我在一篇36Kr的文章<未来十年,Blockchain会如何互 ...

  3. while保留五位小数c语言,C语言教程(共同学习)

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 C语言教程第三章: C语言程序设计初步 C语言程序设计 本课介绍C语言程序设计的基本方法和基本的程序语句. 从程序流程的角度来看,程序可以分为三种基本结构 ...

  4. Xamarin XAML语言教程Visual Studio中实现XAML预览

    2019独角兽企业重金招聘Python工程师标准>>> Xamarin XAML语言教程Visual Studio中实现XAML预览 每次通过编译运行的方式查看XAML文件效果,需要 ...

  5. Xamarin XAML语言教程模板视图TemplatedView(二)

    Xamarin XAML语言教程模板视图TemplatedView(二) (2)打开MainPage.xaml文件,编写代码,将构建的控件模板应用于中TemplatedView.代码如下: <? ...

  6. Xamarin XAML语言教程模板视图TemplatedView(一)

    Xamarin XAML语言教程模板视图TemplatedView(一) 模板视图TemplatedView 与模板页面相对的是TemplatedView,它被称为模板视图,它的功能和模板页面类似,也 ...

  7. Xamarin XAML语言教程模板页面TemplatedPage

    Xamarin XAML语言教程模板页面TemplatedPage 模板页面TemplatedPage 在上文中我们提到了TemplatedPage,它被称为模板页面,用来显示控件模版.Templat ...

  8. Xamarin XAML语言教程构建ControlTemplate控件模板 (四)

    Xamarin XAML语言教程构建ControlTemplate控件模板 (四) 2.在页面级别中构建控件模板 如果开发者要在页面级别中构建控件模板,首先必须将ResourceDictionary添 ...

  9. Xamarin XAML语言教程构建ControlTemplate控件模板 (三)

    Xamarin XAML语言教程构建ControlTemplate控件模板 (三) (3)打开MainPage.xaml.cs文件,编写代码,实现主题的切换功能.代码如下: using System; ...

最新文章

  1. 在Ant-design-pro框架中引入jQuery和jQuery-ui步骤
  2. jQuery - 设置内容和属性
  3. iOS Icon Size 快速得到三种大小的图标
  4. 《编写可读代码的艺术》读书笔记
  5. java 交集怎么写_Java里面如何求两个集合的交集
  6. 九章基础算法02:栈和队列
  7. Nginx 核心架构设计
  8. Visual Graph图形控件的高级应用
  9. 数据库原理与应用第三版何玉洁第二章课后题答案
  10. Windows 8 经典主题
  11. 开店选址分析(转自:https://www.sohu.com/a/228415364_167028)
  12. Mysql同步数据到Elasticsearch(实时Canal)
  13. 信奥一本通-动态规划-例9.2-数字金字塔-方法四-逆推法代码实现
  14. 标题: 连接到服务器 ------------------------------ 无法连接到 DESKTOP-TGC5ASS\HAHA。 ----------------------------
  15. 基于SPI的OLED温湿度显示
  16. 一线城市广州:全日制本科生参保即可落户
  17. 美国哪些公司实习生工资高?Facebook每月给5万5
  18. rstudio安装后如何打开_如何安装R与Rstudio
  19. 镜片阿贝数与程序员的三验
  20. 投标方案应该怎么写?

热门文章

  1. 《当你的才华还撑不起你的梦想时》-特立独行的猫
  2. TCP/IP 流量控制
  3. win7服务器未能登录怎么解决,win7开机提示group policy client服务未能登录拒绝访问怎么办...
  4. ftp放行端口 linux,Linux中快速架设FTP服务和在 iptables放行FTP
  5. linux安装deb程序,Linux软件安装的几种方法 (二)—— deb包安装
  6. JiaThis社区分享按钮的使用,提升网站访问流量
  7. iOS NSKeyedArchiver和NSKeyedUnarchiver
  8. cadence line 删除_PCB中如何删除特定的线
  9. 公摊面积用计算机怎么计算,公摊面积如何计算?
  10. php mysql 论坛 源码_LNMP架构的搭建(linux+nginx+mysql+php源码安装 )+Discuz论坛的搭建...