C/C++ 標準之 main 寫法有二種,一種是不接受任何參數列;另一種是接受二個參數列。程式碼大致如下

int main(int argc, char **argv)
{
    return 0;
}

int main(void)
{
    return 0;
}

 

第二種於程式中較為常見,本文針對第一種 main 型態,說明主要有四點:

(1) int argc, char **argv 為何?
(2) 如何使用 argc, argv 測試程式?
(3) 在開發過程中,如何使用 VC 設定預設之 argv?
(4) 如何利用 VC 進行有效率之 argc, argv 測試?

 

0. 使用命令提示字元

在進入正題的時候,這裡說明如何使用命令提示字元 (應該說如何會用命令列)。先按下開始->執行-> cmd.exe 按下 Enter

好了後按下 "dir.exe /?",你會看到會有一堆參數跑出來,雖平常都說 "dir 這個指令是用來查目錄、檔案",但實際上dir 它是一個小的應用程式,副檔名是 .exe,只是在命令提示字元裡面我可以給它「外部參數」,比如說我鍵入

dir.exe C:\

它便會幫我找出 C:\ 底下所有檔案、資料夾,我再鍵入

dir.exe C:\ /b

它也會幫我找出 C:\ 底下所有資料夾與檔案,只是這次是簡單模式的輸出。這裡就可以開始說一點點 argc, argv 了。

第一次我鍵入 dir.exe C:\ ,總共有二個單字,所以 argc = 2;第一個字是 "dir.exe",所以 argv[0] = "dir.exe",第二個字是 "C:\",所以 argv[1] = "C:\\"

第二次我鍵入 dir.exe C:\ /b,一樣的道理,argc = 3,argv[0]="dir.exe",argv[1] = "C:\\",argv[2] = "/b"


 

1. argc 與 argv

這裡就簡單的說那個 int main (int argc, char *argv[]) 裡面的的 int argc, char *argv[]。

假設,生成出來之執行檔為 Testing.exe,第一次輸入 Testing.exe C:\a.txt,這是2個參數,所以 argc = 2而 argv[0] = Testing.exe, argv[1] = "C:\\a.txt"。

第二次輸入 Testing.exe C:\1234567890.txt,這是2個參數,所以 argc = 2, argv[0] = Testing.exe, argv[1] = "C:\\1234567890.txt"

最後一次輸入 Testing.exe 1 2 3 4 5 6,共 7 個參數,argc = 7:argv[0] = Testing.exe, argv[1] = 1, argv[2] = 2, argv[3]=3, ... argv[6]=6。

注意事項有三點:

1、argc是argument count(參數個數)的縮寫,代表包括指令本身的參數個數。系統會自動計算所輸入的參數個數。

2、argv則是argument value的縮寫。代表參數值。也就是使用者在命令列中輸入的字串每個字串以空白相隔 (但若用雙引號引住的路徑,即使有空白也只算一個字串參數)。同時,系統會自動將程式本身的名稱指定給argv[0],再將程式名稱後面所接續的參數依序指定給argv[1]、argv[2]….。

3. argv 事實上的確是以空白符號為分隔,但有個例外:若路徑名中間有空間,以雙引號引起來時則視為一組 argv。如: Testing.exe "C:\Program Files\Visual Studio 9.0" ,雖其中共有 4 個空白,但實際上 argv 只有二個,一個是 Testing.exe ;另一則為  "C:\Program Files\Visual Studio 9.0"。

 

2. 程式範例 - 顯示輸入之 argv

鑑於以上之說明,我們先寫個簡單之程式,這個程式將輸入之引數進行輸出,程式碼如下所示

#include <stdio.h>
int main(int argc, char **argv)
{
    int i=0;
    printf("argc = %d\n", argc);
    for(i=0; i!=argc; ++i) 
       printf("argv[%d] = %s\n", i, argv[i]);
    return 0;
}

 

3. 進行程式碼測試

好了之後,compiler 、execute,發現輸出竟然只有這個

argc = 1
argv[0] = d:\Testing.exe  (視執行程式是位於哪裡)

為何?因直接用 compiler 去做 execute 動作,和在命令提示字元下只執行 D:\Testing.exe 沒二樣,並沒有再餵任何參數進去。於是測試方法大致如下

(0) 為方便說明,將 Testing.exe 放到 D:\ 底下
(1) 開始 -> 執行 -> 按下 cmd.exe  (或按 WinKey + R,輸入 cmd.exe)
(2) 將工作目錄切到 D:\ 底下 ( 輸入 D:\  後按 Enter )
(3) 輸入 Testing.exe a b c

以上便是測試步驟,執行結果如下所示

argc = 4
argv[0] = Testing.exe
argv[1] = a
argv[2] = b
argv[3] = c

 由上敘述可知,任何程式的 argc 必大於等於1,而且 argv[0] 就是程式名字本身。

 

4. 使用 Visual Studio 設定預設之命令參數列

在第三大點所提到的方式,每次寫完後都還要跑到命令提示字元底下去測,這麼下來很麻煩。為提昇開發效率,於是會想一些辦法去提供開發速度,以下提出三個方法改善

 

(1) 於 main 一開始便把 argc, argv 覆蓋掉 <注意,這方法具危險性...>

於程式碼一開始就把 argc, argv 覆蓋過去,開發完要發佈時,再將覆蓋之程式碼註解掉,大致如下

#include <stdio.h>
int main(int argc, char **argv)
{
    int i=0;
    
    // recover argc and argv
    argc = 3;
    argv[0] = "a", argv[1]="b", argv[2]="c";

    printf("argc = %d\n", argc);
    for(i=0; i!=argc; ++i) 
       printf("argv[%d] = %s\n", i, argv[i]);
    return 0;
}

 

argv 本身是一個指標的指標,指向其他的常數指標沒問題,危險的地方在於,由於我們不確定一開始程式傳進來的 argc 是多少、argv 有幾個,直接就假設存在 argv[1], argv[2] 做修改,這才是危險的地方。

 

(2) 寫批次檔進行測試

程式碼完全都不變動,是由批次檔進行處理。假設生成之執行檔位於 D:\Testing\Release\Testing.exe,則在任何地方建立起一副檔名為 bat 之檔案,內容如下

@echo off
D:\Testing\Release\Testing.exe   a   b   c
D:\Testing\Release\Testing.exe   1   2   3
pause

上述之 bat 可有修改空間 (如依個人喜好,視 @echo off 要不要拿掉)。在 compiler 完後,直接快點該 bat 二下執行,便會生成一 console 視窗出現執行結果,結果如下所示

argc = 4
argv[0] = D:\Testing\Release\Testing.exe
argv[1] = a
argv[2] = b
argv[3] = c
argc = 4
argv[0] = D:\Testing\Release\Testing.exe
argv[1] = 1
argv[2] = 2
argv[3] = 3
請按任意鍵繼續...

優點:
容易新增/移除命令參數列腳本

缺點:
每次 compiler 後都要再去點出該 bat 去執行

(3) 使用 Visual Studio IDE 協助

吾人手邊版本為 2008,操作步驟如下

step1: 專案 -> Testing 屬性 (或直接按 Alt + F7)

 vc-cmd1.png  

step2:進入屬性頁後,左側選偵錯。

vc-cmd2.png  

step3:右側於命令行輸入 testing edison x shih

vc-cmd3.png  

step4:大功告成,此後直接執行便會視輸入參數為 step 3 所輸入,執行結果如下所示

vc-cmd4.png  

優點:
1. 設定快速
2. 只需設定一次便不用再另開命令提示字元、也不用再點 .bat

缺點:
一次只能設一組測試參數 (若有方法可直接測試多個測試參數,請不吝告知)。

 

5. 死結問題

這是個有趣的邏輯問題。

標準函式庫裡面有個指令是 atexit (在 stdlib.h 內),使用這個函式時,會在程式退出後才執行指定之函式。給個例子看會更明確

#include <stdio.h>
#include <stdlib.h>
void test() {printf("test function\n");}
int main()
{
    printf("one\n");
    atexit(test);
    printf("two\n");
    return 0;
}

 

輸出結果

one
two
test function

也基於這個特性,所以有部份人會想:那寫一個 bat,做多次測試。該 bat 檔就長這樣 (假設為 Testing.bat)

@echo off
D:\Testing\Release\Testing.exe a b c
D:\Testing\Release\Testing.exe 1 2 3
pause

接下來再用 atexit ,使用 system 方式呼叫 .bat ,這樣就可以達成多次測試的目標了!於是寫了下面這段碼

#include <stdio.h>
#include <stdlib.h>
void test() {system("Testing.bat");}

int main(int argc, char **argv)
{
    int i=0;
    printf("argc = %d\n", argc);
    
    for(i=0; i!=argc; ++i) 
       printf("argv[%d] = %s\n", i, argv[i]);
    atexit(test);
    return 0;
}

 

悲劇就此發生了.. 使用 Ctrl + C 也沒辦法完全跳出來,最後直接按下 Console Window 右上方的關閉列,暫時關掉了,但,再次 Compiler 該專案,卻怎麼都沒辦法寫入 Testing.exe ,Why ?

這個問題很容易想到,留給各位思考。

 

解決方案為:

開始 -> 執行 -> 輸入 cmd.exe (Enter) ->
命令提示字元裡輸入 taskkill /f /im testing.exe /im cmd.exe

若不這麼做的話,電腦的確還能使用,不過效能上會降許多,另解的方式應就為重開機處理。

 

6. correct and better

 上述之描述,在 VC IDE 「命令引數」設定觀念上為誤。事實上,在屬性頁中,「組態屬性」-> 「偵錯」裡面,有二個參數是與命令行有關的,便為「命令」與「命令引數」。吾人上述乃簡化說明之過程,故沒提及到「命令」之設定。但事實上所有「命令引數」之第一個引數,根本就不用輸入執行檔名,因那在「命令」 (預設為  $(TargetPath) ) 便有,只要繼續輸入後面之引數即可。如

命令: ($TargetPath)
命令引數:a b c

這樣在 execute 展開便為

d:\Testing\Release\Testing.exe a b c

d:\Testing\Release\Testing.exe 即是在IDE 設定中的 ($TargetPath)

應用此特性,事實上可以做更有效率之設定,如下所示

命令:$(SolutionDir)\testing.bat
命令引數: $(TargetPath)

而在 testing.bat 可撰為

@echo off
%1 a b c
%1 1 2 3
pause

最後把 testing.bat 放在專案目錄底下便可進行大量之測試,同時每次只要再修改 .bat 內容便可,這段 IDE 設定與 bat 寫法吾人於此處便不解釋,避免過於離題。

 

7. 其餘不盡事項

(1) Dev-C / GCC 底下是否有類似 VC 功能,設定輸入命令引數?
(2) 若程式為「求得命令引數之所有總合」,該如何撰之? (提示:argv 為字串,記得用 atoi/atof 將字串轉數字)
(3) 請弄清楚 C:\Program Files\Visual Studio 9.0 與 "C:\Program Files\Visual Studio 9.0" 之影響。
(4) 關於批次檔之撰寫並不在本文之探討範圍內,所提供之批次檔已盡量簡潔,若對於批次檔之撰寫有所疑慮,請再自行進修。
(5) 試著在 vc 底下,發現更多類似 ($TargetPath) 、($SolutionDir) 之 compiler 巨集,使用上將更得心應手!
(6) 實際上 Visual Studio 10.0 與 Visual Studio 9.0 (恕筆者只用過6.0 / 9.0 / 10.0) 對於命令參數列之特性有所不同,最大差異便為 &pause 之解讀,讀者有興趣可試之。

 

                                                   - by EdisonX ( Edison . Shih )

edisonx 發表在 痞客邦 PIXNET 留言(0) 人氣()