1. 程式人生 > >萬能makefile寫法詳解,一步一步寫一個實用的makefile,詳解 sed 's,\($*\)\.o[ :]*,\1.o [emai

萬能makefile寫法詳解,一步一步寫一個實用的makefile,詳解 sed 's,\($*\)\.o[ :]*,\1.o [emai

目的:編寫一個實用的makefile,能自動編譯當前目錄下所有.c/.cpp原始檔,支援二者混合編譯。並且當某個.c/.cpp、.h或依賴的原始檔被修改後,僅重編涉及到的原始檔,未涉及的不編譯。


要達到這個目的,用到的技術有:

1-使用wildcard函式來獲得當前目錄下所有.c/.cpp檔案的列表。
2-make的多目標規則。
3-make的模式規則。
4-用gcc -MM命令得到一個.c/.cpp檔案include了哪些檔案。
5-用sed命令對gcc -MM命令的結果作修改。
6-用include命令包含依賴描述檔案.d。

三 準備知識
(一)多目標

對makefile裡下面2行,可看出多目標特徵,執行make bigoutput或make littleoutput可看到結果:

bigoutput littleoutput: defs.h pub.h
 @echo [email protected] $(subst output,OUTPUT,[email protected]) $^ # [email protected]指這個規則裡所有目標的集合,$^指這個規則裡所有依賴的集合。該行是把目標(bigoutput或littleoutput)裡所有子串output替換成大寫的OUTPUT

(二)隱含規則
對makefile裡下面4行,可看出make的隱含規則,執行foo可看到結果:
第3、4行表示由.c得到.o,第1、2行表示由.o得到可執行檔案。
如果把第3、4行註釋的話,效果一樣。
即不寫.o來自.c的規則,它會自動執行gcc -c -o foo.o foo.c這條命令,由.c編譯出.o(其中-c表示只編譯不連結),然後自動執行gcc -o foo foo.o連結為可執行檔案。

foo:foo.o
 gcc -o foo foo.o; ./foo
foo.o:foo.c     #註釋該行看效果
 gcc -c foo.c -o foo.o #註釋該行看效果

(三)定義模式規則
下面定義了一個模式規則,即如何由.c檔案生成.d檔案的規則。

foobar: foo.d bar.d
 @echo complete generate foo.d and bar.d
%.d: %.c  #make會對當前目錄下每個.c檔案,依次做一次裡面的命令,從而由每個.c檔案生成對應.d檔案。
 @echo from $< to [email protected]
 g++ -MM $< > 
[email protected]

假定當前目錄下有2個.c檔案:foo.c和bar.c(檔案內容隨意)。
驗證方法有2種,都可:
1-執行make foo.d(或make bar.d),表示想要生成foo.d這個目標。
根據規則%.d: %.c,這時%匹配foo,這樣%.c等於foo.c,即foo.d這個目標依賴於foo.c。
此時會自動執行該規則裡的命令gcc -MM foo.c > foo.d,來生成foo.d這個目標。
2-執行make foobar,因為foobar依賴於foo.d和bar.d這2個檔案,即會一次性生成這2個檔案。


下面詳述如何自動生成依賴性,從而實現本例的makefile。

(一)
本例使用了makefile的模式規則,目的是對當前目錄下每個.c檔案,生成其對應的.d檔案,例如由main.c生成的.d檔案內容為:

 main.o : main.c command.h

這裡指示了main.o目標依賴於哪幾個原始檔,我們只要把這一行的內容,通過make的include指令包含到makefile檔案裡,即可在其任意一個依賴檔案被修改後,重新編譯目標main.o。
下面詳解如何生成這個.d檔案。

(二)
gcc/g++編譯器有一個-MM選項,可以對某個.c/.cpp檔案,分析其依賴的原始檔,例如假定main.c的內容為:

#include <stdio.h>//標準標頭檔案(以<>方式包含的),被-MM選項忽略,被-M選項收集
#include "stdlib.h"//標準標頭檔案(以""方式包含的),被-MM選項忽略,被-M選項收集
#include "command.h"
int main()
{
 printf("##### Hello Makefile #####\n");
 return 0;
}

則執行gcc -MM main.c後,螢幕輸出:

main.o: main.c command.h

執行gcc -M main.c後,螢幕輸出:

main.o: main.c /usr/include/stdio.h /usr/include/features.h \
/usr/include/bits/predefs.h /usr/include/sys/cdefs.h \
/usr/include/bits/wordsize.h /usr/include/gnu/stubs.h \
/usr/include/gnu/stubs-64.h \
/usr/lib/gcc/x86_64-linux-gnu/4.4.3/include/stddef.h \
/usr/include/bits/types.h /usr/include/bits/typesizes.h \
/usr/include/libio.h /usr/include/_G_config.h /usr/include/wchar.h \
/usr/lib/gcc/x86_64-linux-gnu/4.4.3/include/stdarg.h \
/usr/include/bits/stdio_lim.h /usr/include/bits/sys_errlist.h \
/usr/include/stdlib.h /usr/include/sys/types.h /usr/include/time.h \
/usr/include/endian.h /usr/include/bits/endian.h \
/usr/include/bits/byteswap.h /usr/include/sys/select.h \
/usr/include/bits/select.h /usr/include/bits/sigset.h \
/usr/include/bits/time.h /usr/include/sys/sysmacros.h \
/usr/include/bits/pthreadtypes.h /usr/include/alloca.h command.h

(三)
可見,只要把這些行挪到makefile裡,就能自動定義main.c的依賴是哪些檔案了,做法是把命令的輸出重定向到.d檔案裡:gcc -MM main.c > main.d,再把這個.d檔案include到makefile裡。
如何include當前目錄每個.c生成的.d檔案:

sources:=$(wildcard *.c) #使用$(wildcard *.cpp)來獲取工作目錄下的所有.c檔案的列表。
dependence=$(sources:.c=.d) #這裡,dependence是所有.d檔案的列表.即把串sources串裡的.c換成.d。
include $(dependence) #include後面可以跟若干個檔名,用空格分開,支援萬用字元,例如include  foo.make  *.mk。這裡是把所有.d檔案一次性全部include進來。注意該句要放在終極目標all的規則之後,否則.d檔案裡的規則會被誤當作終極規則了。

(四)
現在main.c command.h這幾個檔案,任何一個改了都會重編main.o。但是這裡還有一個問題,如果修改了command.h,在command.h中加入#include "pub.h",這時:
1-再make,由於command.h改了,這時會重編main.o,並且會使用新加的pub.h,看起來是正常的。
2-這時開啟main.d檢視,發現main.d中未加入pub.h,因為根據模式規則%.d: %.c中的定義,只有依賴的.c檔案變了,才會重新生成.d,而剛才改的是command.h,不會重新生成main.d、及在main.d中加入對pub.h的依賴關係,這會導致問題。
3-修改新加的pub.h的內容,再make,果然問題出現了,make報告up to date,沒有像期望那樣重編譯main.o。
現在問題在於,main.d裡的某個.h檔案改了,沒有重新生成main.d。進一步說,main.d裡給出的每個依賴檔案,任何一個改了,都要重新生成這個main.d。
所以main.d也要作為一個目標來生成,它的依賴應該是main.d裡的每個依賴檔案,也就是說make裡要有這樣的定義:

main.d: main.c command.h

這時我們發現,main.d與main.o的依賴是完全相同的,可以利用make的多目標規則,把main.d與main.o這兩個目標的定義合併為一句:

main.o main.d: main.c command.h

現在,main.o: main.c command.h這一句我們已經有了,如何進一步得到main.o main.d: main.c command.h呢?

(五)
解決方法是行內字串替換,對main.o,取出其中的子串main,加上.d字尾得到main.d,再插入到main.o後面。能實現這種替換功能的命令是sed。
實現的時候,先用gcc -MM命令生成臨時檔案main.d.temp,再用sed命令從該臨時檔案中讀出內容(用<重定向輸入)。做替換後,再用>輸出到最終檔案main.d。
命令可以這麼寫:

 g++ -MM main.c > main.d.temp
 sed 's,\(main\)\.o[ :]*,\1.o main.d : ,g' < main.d.temp > main.d

其中:
 sed 's,\(main\)\.o[ :]*,\1.o main.d : ,g',是sed命令。
 < main.d.temp,指示sed命令從臨時檔案main.d.temp讀取輸入,作為命令的來源字串。
 > main.d,把行內替換結果輸出到最終檔案main.d。

(六)
這條sed命令的結構是s/match/replace/g。有時為了清晰,可以把每個/寫成逗號,即這裡的格式s,match,replace,g。
該命令表示把源串內的match都替換成replace,s指示match可以是正則表示式。
g表示把每行內所有match都替換,如果去掉g,則只有每行的第1處match被替換(實際上不需要g,因為一個.d檔案中,只會在開頭有一個main.o:)。
這裡match是正則式\(main\)\.o[ :]*,它分成3段:
第1段是\(main\),在sed命令裡把main用\(和\)括起來,使接下來的replace中可以用\1引用main。
第2段是\.o,表示匹配main.o,(這裡\不知何意,去掉也是可以的)。
第3段是正則式[ :]*,表示若干個空格或冒號,(其實一個.d裡只會有一個冒號,如果這裡寫成[ ]*:,即匹配若干個空格後跟一個冒號,也是可以的)。

總體來說match用來匹配'main.o :'這樣的串。
這裡的replace是\1.o main.d :,其中\1會被替換為前面第1個\(和\)括起的內容,即main,這樣replace值為main.o main.d :
這樣該sed命令就實現了把main.o :替換為main.o main.d :的目的。

這兩行實現了把臨時檔案main.d.temp的內容main.o : main.c command.h改為main.o main.d : main.c command.h,並存入main.d檔案的功能。

(七)
進一步修改,採用自動化變數。使得當前目錄下有多個.c檔案時,make會依次對每個.c檔案執行這段規則,生成對應的.d:

 gcc -MM  $< > [email protected];
 sed 's,\($*\)\.o[ :]*,\1.o [email protected] : ,g' < [email protected] > [email protected];

(八)
現在來看上面2行的執行流程:

第一次make,假定這時從來沒有make過,所有.d檔案不存在,這時鍵入make:
1-include所有.d檔案的命令無效果。
2-首次編譯所有.c檔案。每個.c檔案中若#include了其它標頭檔案,會由編譯器自動讀取。由於這次是完整編譯,不存在什麼依賴檔案改了不會重編的問題。
3-對每個.c檔案,會根據依賴規則%.d: %.c,生成其對應的.d檔案,例如main.c生成的main.d檔案為:

 main.o main.d: main.c command.h

第二次make,假定改了command.h、在command.h中加入#include "pub.h",這時再make:
1-include所有.d檔案,例如include了main.d後,得到依賴規則:

 main.o main.d: main.c command.h

注意所有include命令是首先執行的,make會先把所有include進來,再生成依賴規則關係。
2-此時,根據依賴規則,由於command.h的檔案戳改了,要重新生成main.o和main.d檔案。
3-先呼叫gcc -c main.c -o main.o生成main.o,
再呼叫gcc -MM main.c > main.d重新生成main.d。
此時main.d的依賴檔案裡增加了pub.h:

 main.o main.d: main.c command.h pub.h

4-對其它依賴檔案沒改的.c(由其.d檔案得到),不會重新編譯.o和生成其.d。
5-最後會執行gcc $(objects) -o main生成最終可執行檔案。

第三次make,假定改了pub.h,再make。由於第二遍中,已把pub.h加入了main.d的依賴,此時會重編main.c,重新生成main.o和main.d。
這樣便實現了當前目錄下任一原始檔改了,自動編譯涉及它的.c。

(九)
進一步修改,得到目前大家普遍使用的版本:

 set -e; rm -f [email protected]; \
 $(CC) -MM $(CPPFLAGS) $< > [email protected]$$$$; \
 sed 's,\($*\)\.o[ :]*,\1.o [email protected] : ,g' < [email protected]$$$$ > [email protected]; \
 rm -f [email protected]$$$$

第一行,set -e表示,如果某個命令的返回引數非0,那麼整個程式立刻退出。
rm -f用來刪除上一次make時生成的.d檔案,因為現在要重新生成這個.d,老的可以刪除了(不刪也可以)。
第二行:前面臨時檔案是用固定的.d.temp作為字尾,為了防止重名覆蓋掉有用的檔案,這裡把temp換成一個隨機數,該數可用$$得到,$$的值是當前程序號。
由於$是makefile特殊符號,一個$要用$$來轉義,所以2個$要寫成$$$$(你可以在makefile裡用echo $$$$來顯示程序號的值)。
第三行:sed命令的輸入也改成該臨時檔案.$$。
每個shell命令的程序號通常是不同的,為了每次呼叫$$時得到的程序號相同,必須把這4行放在一條命令中,這裡用分號把它們連線成一條命令(在書寫時為了易讀,用\拆成了多行),這樣每次.$$便是同一個檔案了。
你可以在makefile裡用下面命令來比較:

 echo $$$$
 echo $$$$; echo $$$$

第四行:當make完後,每個臨時檔案.d.$$,已經不需要了,刪除之。
但每個.d檔案要在下一次make時被include進來,要保留。

(十)
綜合前面的分析,得到我們的makefile檔案:

#使用$(wildcard *.c)來獲取工作目錄下的所有.c檔案的列表
sources:=$(wildcard *.c)
objects:=$(sources:.c=.o)
#這裡,dependence是所有.d檔案的列表.即把串sources串裡的.c換成.d
dependence:=$(sources:.c=.d)

#所用的編譯工具
CC=gcc

#當$(objects)列表裡所有檔案都生成後,便可呼叫這裡的 $(CC) $^ -o [email protected] 命令生成最終目標all了
#把all定義成第1個規則,使得可以把make all命令簡寫成make
all: $(objects)
 $(CC) $^ -o [email protected]

#這段是make的模式規則,指示如何由.c檔案生成.o,即對每個.c檔案,呼叫gcc -c XX.c -o XX.o命令生成對應的.o檔案。
#如果不寫這段也可以,因為make的隱含規則可以起到同樣的效果
%.o: %.c
 $(CC) -c $< -o [email protected]

include $(dependence) #注意該句要放在終極目標all的規則之後,否則.d檔案裡的規則會被誤當作終極規則了
%.d: %.c
 set -e; rm -f [email protected]; \
 $(CC) -MM $(CPPFLAGS) $< > [email protected]$$$$; \
 sed 's,\($*\)\.o[ :]*,\1.o [email protected] : ,g' < [email protected]$$$$ > [email protected]; \
 rm -f [email protected]$$$$

.PHONY: clean #之所以把clean定義成偽目標,是因為這個目標並不對應實際的檔案
clean:
 rm -f all $(objects) $(dependence) #清除所有臨時檔案:所有.o和.d。.$$已在每次使用後立即刪除。-f引數表示被刪檔案不存在時不報錯

(十一)
上面這個makefile已經能正常工作了(編譯C程式),但如果要用它編譯C++,變數CC值要改成g++,每個.c都要改成.cpp,有點繁瑣。
現在我們繼續完善它,使其同時支援C和C++,並支援二者的混合編譯。

#一個實用的makefile,能自動編譯當前目錄下所有.c/.cpp原始檔,支援二者混合編譯
#並且當某個.c/.cpp、.h或依賴的原始檔被修改後,僅重編涉及到的原始檔,未涉及的不編譯
#詳解文件:http://blog.csdn.net/huyansoft/article/details/8924624
#author:胡彥 2013-5-21

#----------------------------------------------------------
#編譯工具用g++,以同時支援C和C++程式,以及二者的混合編譯
CC=g++

#使用$(winldcard *.c)來獲取工作目錄下的所有.c檔案的列表
#sources:=main.cpp command.c

#變數sources得到當前目錄下待編譯的.c/.cpp檔案的列表,兩次呼叫winldcard、結果連在一起即可
sources:=$(wildcard *.c) $(wildcard *.cpp)

#變數objects得到待生成的.o檔案的列表,把sources中每個檔案的副檔名換成.o即可。這裡兩次呼叫patsubst函式,第1次把sources中所有.cpp換成.o,第2次把第1次結果裡所有.c換成.o
objects:=$(patsubst %.c,%.o,$(patsubst %.cpp,%.o,$(sources)))

#變數dependence得到待生成的.d檔案的列表,把objects中每個副檔名.o換成.d即可。也可寫成$(patsubst %.o,%.d,$(objects))
dependence:=$(objects:.o=.d)

#----------------------------------------------------------
#當$(objects)列表裡所有檔案都生成後,便可呼叫這裡的 $(CC) $^ -o [email protected] 命令生成最終目標all了
#把all定義成第1個規則,使得可以把make all命令簡寫成make
all: $(objects)
	$(CC) $(CPPFLAGS) $^ -o [email protected]
	@./[email protected]	#編譯後立即執行

#這段使用make的模式規則,指示如何由.c檔案生成.o,即對每個.c檔案,呼叫gcc -c XX.c -o XX.o命令生成對應的.o檔案
#如果不寫這段也可以,因為make的隱含規則可以起到同樣的效果
%.o: %.c
	$(CC) $(CPPFLAGS) -c $< -o [email protected]

#同上,指示如何由.cpp生成.o,可省略
%.o: %.cpp
	$(CC) $(CPPFLAGS) -c $< -o [email protected]

#----------------------------------------------------------
include $(dependence)	#注意該句要放在終極目標all的規則之後,否則.d檔案裡的規則會被誤當作終極規則了

#因為這4行命令要多次凋用,定義成命令包以簡化書寫
define gen_dep
set -e; rm -f [email protected]; \
$(CC) -MM $(CPPFLAGS) $< > [email protected]$$$$; \
sed 's,\($*\)\.o[ :]*,\1.o [email protected] : ,g' < [email protected]$$$$ > [email protected]; \
rm -f [email protected]$$$$
endef

#指示如何由.c生成其依賴規則檔案.d
#這段使用make的模式規則,指示對每個.c檔案,如何生成其依賴規則檔案.d,呼叫上面的命令包即可
%.d: %.c
	$(gen_dep)

#同上,指示對每個.cpp,如何生成其依賴規則檔案.d
%.d: %.cpp
	$(gen_dep)

#----------------------------------------------------------
#清除所有臨時檔案(所有.o和.d)。之所以把clean定義成偽目標,是因為這個目標並不對應實際的檔案
.PHONY: clean
clean:	#.$$已在每次使用後立即刪除。-f引數表示被刪檔案不存在時不報錯
	rm -f all $(objects) $(dependence)

echo:	#除錯時顯示一些變數的值
	@echo sources=$(sources)
	@echo objects=$(objects)
	@echo dependence=$(dependence)
	@echo CPPFLAGS=$(CPPFLAGS)

#提醒:當混合編譯.c/.cpp時,為了能夠在C++程式裡呼叫C函式,必須把每一個要呼叫的C函式,其宣告都包括在extern "C"{}塊裡面,這樣C++連結時才能成功連結它們。


makefile學習體會:

剛學過C語言的讀者,可能會覺得makefile有點難,因為makefile不像C語言那樣,一招一式都那麼清晰明瞭。
在makefile裡到處是“潛規則”,都是一些隱晦的東西,要弄明白只有搞清楚這些“潛規則”。
基本的規則無非是“一個依賴改了,去更新哪些目標”。
正因為隱晦動作較多,寫成一個makefile才不需要那麼多篇幅,畢竟專案程式碼才是主體。只要知道makefile的框架,往它的套路里填就行了。

較好的學習資料是《跟我一起寫Makefile.pdf》這篇文件(下載包裡已經附帶了),比較詳細,適合初學者。
我們學習的目的是,能夠編寫一個像本文這樣的makefile,以滿足簡單專案的基本需求,這要求理解前面makefile幾個關鍵點:
1-多目標
2-隱含規則
3-定義模式規則
4-自動生成依賴性
可惜的是,這篇文件雖然比較全面,卻沒有以一個完整的例子為引導,對幾處要點沒有突出指明,尤其是“定義模式規則”在最後不顯眼的位置(第十一部分第五點),導致看了“自動生成依賴性”一節後還比較模糊。
所以,看了《跟我一起寫Makefile.pdf》後,再結合本文針對性的講解,會有更實際的收穫。
另一個學習資料是《GNU make v3.80中文手冊v1.5.pdf》,這個手冊更詳細,但較枯燥,不適合完整學習,通常是遇到問題再去查閱。

[END]

相關推薦

萬能makefile寫法一個實用makefile sed 's,\($*\)\.o[ :]*,\1.o <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="a084e0">[emai

一 目的:編寫一個實用的makefile,能自動編譯當前目錄下所有.c/.cpp原始檔,支援二者混合編譯。並且當某個.c/.cpp、.h或依賴的原始檔被修改後,僅重編涉及到的原始檔,未涉及的不編譯。 二 要達到這個目的,用到的技術有: 1-使用wildcard函式來獲得當

<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="4c3f3c3e25222b2e2323380c09222d2e20290d3938230f23222a252b393e2d38252322">[emai

首先講解一下springboot中@EnableAutoConfiguration 註解的作用,見名知意,就是開啟自動配置.此時有些人肯定會想,這麼簡單的問題,你tm是xxx吧,哈哈,逗比了哈,開啟了

[ 轉]Shell中引數($0,$1,$#,$NF,<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="4b6f0b">[email protected]a>等)的含義

Shell中引數($0,$1,$#,$NF,[email protected]等)的含義         釋出時間:2018-01-19 來源:網路 上傳者:使用者  &nbs

makefile下$^,<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="cbef8b">[email protected]a>,$?,$<,$(@D),$(@F)定義使用

    每次看makefile的時候,總會遇到一些變數記不住,就需要去查詢資料,今天有時間,就順便把幾個常用的變數學習了下,順便總結了下記憶方法,感覺記住它們並不難,特把方法分享給大家。變數定義:$^所有的依賴目標的集合。以空格分隔。如果在依賴目標中有多個重複的,那個這個變數

<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="5400313a273b2632383b2379142032">[email protected]a>_export

Tensorflow經常看到定義的函式前面加了“@tf_export”。例如,tensorflow/python/platform/app.py中有: @tf_export('app.run') def run(main=None, argv=None): """Runs the progr

Makefile有三個非常有用的變數。分別是<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="a480e4">[email protected]a>$^$

原文地址:https://blog.csdn.net/u013774102/article/details/79043559 假設我們有下面這樣的一個程式,原始碼如下:  /* main.c */ #include "mytool1.h" #include "mytool2.h" i

kafka 消費者優化及配置 <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="4c3f3c3e25222b2e232338670c072d2a272d00253f382922293e">[email

自定義屬性和執行工廠 public KafkaListenerContainerFactory<?> batchFactory(){ ConcurrentKafkaListenerContainerFactory<Integer, Stri

【Spring】定時任務例項<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="92bfd2c1f1faf7f6e7fef7f6">[email protected]a>

最近在做專案,時間比較緊張,也有比較久沒寫部落格了。 現在專案的Redis快取需要用到定時任務,就學習了一下Spring 的@Scheduled註解。使用起來很簡單。 這個例子是建立在之前我的一篇部落格的例項上面的。 也就是架好了SSM框架。 SSM

unix中shell 非一般變數$0 $n $* <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="a581e5">[email protected]a> $! $?的

$0:獲取當前執行指令碼的檔名,包括路徑。 [[email protected] script]# cat 0.sh #!/bin/bash echo $0 [[email protected] script]# sh 0.sh 0.sh [[email protected

linux命令提示符[<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="c8baa7a7bc88a4a7aba9a4a0a7bbbc">[email protected]a> ~]#

       [[email protected] ~]#        root代表當前登入的使用者,在Linux中管理員賬戶是root        localhost當前計

makefile的特殊變數類似<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="0a2e4a">[email protected]a>, $+等 (copied)

來源:http://hi.baidu.com/jingweiyoung/item/dea74399c40eb24cf14215cf Makefile 特殊變數 常用特殊變數 例: %.o:%.c           ¥(CC) -c $(CFLAGS) $&l

<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="31624143585f561c7147505d4454">[email protected]a>用法

為了簡化讀取properties檔案中的配置值,spring支援@value註解的方式來獲取,這種方式大大簡化了專案配置,提高業務中的靈活性。 一、兩種使用方法 1、@Value("#{configProperties['key']}") 2、@Value("${key}"

Makefile單字尾雙字尾以及<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="113551">[email protected]a>的意.

均是根據《跟我一起寫Makefile》寫的,只不過是具體解釋 關於Makefile的單字尾,也就是.c: $(CC) -c $< $(CFLAGS) $(INCDIRS)相當於%:%.c $(CC) -c $< $(CFLAGS) $(INCDIRS)我

annotation(@<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="75271001101b011c1a1b35211407121001">[email protected]a>)

一、註解:深入理解JAVA註解   要深入學習註解,我們就必須能定義自己的註解,並使用註解,在定義自己的註解之前,我們就必須要了解Java為我們提供的元註解和相關定義註解的語法。 1、元註解(meta-annotation): 元註解的作用就是負責註解其他註解。Java

啟動hbase後出現ERROR [<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="7a4b4f49484b4d4d4a4c493a0b0e0a574f4d494b4a4842424b5748">[em

2018-11-07 09:28:54,771 INFO [master:16000.activeMasterManager] util.FSUtils: Waiting for dfs to exit safe mode... 2018-11-07 09:29:04,783 INFO [mas

無法解析的外部符號 <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="1e41497770537f77705e2f28">[email protected]a>該符號在函式 ___tmai

#include using namespace std; int main() { cout <<“This is a C++ program.”; return 0; } 1>------ 已啟動生成: 專案: hello1, 配置: Debug Win32 ---

SpringBoot 進階系列 定義全域性異常@<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="284b47465c5a4744444d5a694c5e414b4d03686d504b4d585c41

此方式優點是不用再control層進行try catch了 此方式的缺點恰恰也是隻能反饋control層的相關異常 首先我們定義一下,建立全域性異常控制類,並在類頭上添加註解:@controllerAdvice @ControllerAdvice public class GlobalE

error LNK2019: 無法解析的外部符號 <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="702f031f131b1504304142">[email protected]a>

Reason: 學習使用socket,在stdafx.h檔案加了#include ,編譯 #include "stdafx.h" #include   using namespace std;   int _tmain(int argc