當前位置: 華文世界 > 數碼

輕松玩轉windows控制台(一):視窗標題

2024-02-10數碼

開篇預告

前段時間,我在其他平台釋出了一篇介紹「輕松玩轉windows控制台」的文章,被不少粉絲私信批評,甚至還有個別言辭激烈的。

是因為文章寫的不好嗎?應該不是,因為兩天時間就突破了90多個收藏,說明大家還是挺喜歡這種實用的、技巧性的技術文章,但是為什麽還是被批評呢?

因為文章通篇以程式碼為主,雖然功能很多,但並沒有詳細的解釋,導致大家在用的時候,有很多地方出現問題。

基於此,我決定圍繞「輕松玩轉windwos控制台」主題,寫一個系列性的文章,透過詳細介紹控制台方面的各種操作技巧,從而提高c語言初學者的學習興趣。

我大致的規劃主要分為這幾塊:

第一個系列是對控制台的各種「魔改」,包括標題修改、輸出文字的字型大小、字型樣式、文字顏色、螢幕背景顏色、控制台視窗的移動、居中顯示、視窗大小、以及文字在視窗內任意位置的顯示、中文字元的正確顯示等等。

這個系列看完之後,就開始釋出控制台視窗的圖形化操作的系列文章。

比如控制台如何控制滑鼠、鍵盤,如何將螢幕背景改成彩色圖片,如何在控制台內實作動畫效果等等。為後面第三個系列控制台圖形遊戲開發打下基礎。

第三個系列我準備圍繞一個遊戲的一步步實作來釋出文章,可以是一個超級瑪麗遊戲,或者一個撞球遊戲都可以,遊戲本身我早就已經調測執行。

話不多說,開始我們的第一個「玩轉之旅」。

控制台介紹

本段是控制台的基礎介紹,大家不感興趣,可以直接跳過,也不影響後面閱讀。

我們用c語言剛剛編寫程式時,一般都是會在螢幕上彈出一個黑乎乎的視窗,然後黑底白字的顯示一行行內容。這個黑乎乎的視窗界面,我們一般稱為「控制台」,英文是Console,而在控制台界面內執行的程式,我們一般稱為「控制台程式」。

我們把透過控制台運行程式的方式,稱為CUI,即Command User Interface,命令使用者介面。相對應的,還有一種圖形化使用者介面,GUI,即Graphics User Interface。有時候,我們也把CUI模式稱為CLI模式,即Command Line Interface ,命令列界面,都可以。

我們一般透過執行cmd.exe命令進入控制台視窗,從而執行控制台程式。

以上這些都是最基礎的概念,後面文章中用到什麽就介紹什麽,盡量降低復雜度。

控制台標題(Console Ttile)

我們先從控制台標題入手。

控制台程式啟動時,視窗上方會有一個title(標題)。這個title顯示的是程的絕對路徑和程式名稱。這個title字串我們稱為「原始的視窗標題名稱」,即original title。如圖:

這個字串顯示的真的很醜,我們一般都會改成我們喜歡的內容,如下所示:

圍繞控制台視窗的標題,可以實作三個功能。分別是「修改控制台標題」、「獲取控制台標題」、「獲取原始控制台標題」。控制台標題被我們修改以後,有時候我們可能需要知道程式所在的絕對路徑和程式名稱,透過「獲取原始控制台標題」這個功能就很方便。下面來看一下這3個功能對應的函數如何使用的, 並簡單的介紹下windows的win32API呼叫。

註釋:如果對Win32API不感興趣,可以直接復制程式碼使用即可。

功能:修改控制台標題

若修改控制台當前標題,需要用到的函數原型如下:

BOOL SetConsoleTitle(LPCTSTR lpConsoleTitle);

在學習如何使用這個函數時,我們先介紹一下一些windos程式設計的基本概念,以便於後面能熟練的使用各種控制台函數。

對初學者而言,使用windows的API函數,應該會有段很痛苦的適應過程。因為微軟把c/c++的基本數據類別和自訂數據類別,都采用了匈牙利命名法進行了封裝,我本人對這個命名法是非常厭惡的,但是還是要學習下。[狗頭]

函數名: SetConsoleTitle,設定當前控制台標題。參數類別: LPCTSTR, 這是微軟開發團隊自訂的數據類別,我們來分析下。

L表示long類別,用來表示長整型數據;P是pointer,表示指標類別;LP表示長整型指標(即32位元或64位元指標類別,取決於系統是32位元或64位元);C表示const,LPC表示這個指標類別指向的數據是常量數據;T是type,用來表示通用類別的含義(泛型),表示有unicode版本,或者ANSI版本,暫時不用理會;STR是字串string的含義。

下面我列個表,就看的比較清爽了。

  • L:long類別
  • P:pointer,指標類別
  • LP:長整型指標
  • C:const
  • LPC:指標指向常量
  • T:type,泛型(暫時忽略)
  • STR:string,字串
  • LPCTSTR:LP + C + (T) + STR
  • 由此可知,LPCTSTR,就是一個指向字串常量的指標(字串本身就是常量數據),可以近似理解成c語言中的const char*。

    返回值:BOOL

    BOOL,就是布爾類別,其實就是一個int類別:

    typedef int BOOL;

    如果真,就是TRUE,假就是FALSE。其實TRUE就是1,FALSE就是0。

    本函數的參數是輸入型參數(什麽是輸入型參數和輸出型參數,可以檢視我的這篇文章:c語言解剖課:函數的輸入參數和輸出參數)

    這個參數在使用之前,其所在的外部變量是一個字元指標類別,必須被初始化,並且已經存放了新的標題字串,字元個數必須小於 64K(等於64*1024個字節,誰能用這麽多?)當函數執行成功,當前標題修改成新的字串時,就返回為TRUE,執行失敗就返回FALSE。

    我們來寫一個演示程式碼:

    #include <stdio.h>#include <windows.h>int main() { LPCSTR newStr = "hello,title!" ; if(SetConsoleTitle(newStr) == TRUE){ printf("Title is changed!\n"); } system("pause"); return 0;}

    首先,檔的字尾需要是 .cpp 或其他C++原始檔的格式,而 不能是.c的字尾 了。

    其次,需要包含windows.h表頭檔,因為對控制台操作的函數,都是這個表頭檔提供的。我們需要先定義一個字串,存放新的標題。

    當然,你也可以用 char* 來定義,但是建議最好和windows編程風格保持一致,就用這個非常別扭的 LPCSTR類別,然後判斷是否設定成功。

    隨後的System函數,參數是一個字串,這個字串實際上就是一個DOS命令,這裏是一個暫停命令。如果沒有這條語句,執行這個程式時,螢幕會一閃而過。執行效果如圖:

    視窗中的「請按任意鍵繼續...」,就是system("pause");這行程式碼中的pause暫停命令起的作用。當然,為了簡潔,你也可以直接寫成如下形式:

    #include <stdio.h>#include <windows.h>int main() { SetConsoleTitle("hello,title!"); system("pause"); return 0;}

    但是,最好不要這麽寫,因為隨著功能越來越強大(復雜),會很不利於維護。那麽我們想顯示中文行不行?我們先測試一下,請看下面這段程式碼:

    #include <windows.h>int main() { SetConsoleTitle("你好世界"); system("pause"); return 0;}

    看一下執行的效果,如圖:

    控制台標題輸出的是亂碼,當然,有可能你的編譯器執行後,輸出的是中文。沒關系,繼續往下看就好了。為什麽會這樣?不用急。我們右擊標題列,點「內容」進去,註意看,下面這個截圖中的紅框,顯示了系統當前使用的編碼。

    內碼表,即Code Page,簡寫為CP,表明了當前控制台的編碼情況。如果是437,則是美國英語;如果是936,則是ANSI/OEM - 簡體中文預設的GBK編碼;如果是950 ,則是繁體中文;如果是65001,則是UTF-8編碼。當然,內碼表有很多種,我們只需要關心一個就可以了,就是UTF-8編碼。用列表顯示內碼表識別元和編碼標準,會更清楚:

  • 437 :美國英語
  • 936 : ANSI/OEM - GBK
  • 950 : 繁體中文
  • 650001 :UTF-8
  • 如果控制台顯示亂碼,我們可以將當前內碼表設定為65001,即UTF-8,就可以正確顯示中文。 註意,並不是因為當前內碼表是936,所以就顯示亂碼。

    顯示亂碼是編譯器的編碼規範和系統的編碼規範不一致,需要手動修改編碼規範。透過下面介紹的方法, 透過修改內碼表為UTF-8,可以快速解決問題,但不是唯一的解決辦法。

    函數SetConsoleCP是設定控制台 輸入字元 使用的編碼標準,SetConsoleOutputCP是設定控制台 輸出字元 的編碼標準,函數原型如下:

    BOOL SetConsoleCP(UINT wCodePageID);BOOL SetConsoleOutputCP(UINT wCodePageID);

    參數類別:UINT

    微軟將基本數據類別int封裝成INT,約定為32位元的有符號整數,取值範圍是 -2147483648 到 2147483647。即:

    typedef int INT;

    而無符號整型unsigned int 被封裝成UINT,約定為32位元無符號整數,取值範圍為 0 到 4294967295。即:

    typedef unsigned int UINT;

    參數的值就是內碼表的識別元,比如要顯示中文,UINT變量的值就是655001即可。

    返回值是BOOL類別,前已介紹過,如果執行成功,則返回TRUE,否則返回FALSE。

    舉例如下:

    #include <stdio.h>#include <windows.h>int main() { SetConsoleCP(65001);//控制台標題 SetConsoleOutputCP(65001);//輸出內容 LPCSTR newStr = "你好世界!" ; if(SetConsoleTitle(newStr) == TRUE){ printf("標題修改成功!\n"); } system("pause"); return 0;}

    我們看下執行結果:

    我們透過 SetConsoleOutputCP ,設定了 輸出字元的內碼表 (Code Page,CP)為UTF-8,所以prinftf函數的輸出內容,正常顯示中文了,同樣的 輸入字元 顯示中文,透過 SetConsoleCP 就可以了。

    進一步的,我們在程式裏,如果需要顯示中文,我們需要知道當前內碼表的數值是不是65001,如果不是,我們需要設定成6501。我們如知曉呢?

    可以透過GetConsoleCP獲取輸入狀態的內碼表,GetConsoleOutputCP獲取輸出狀態的內碼表,從而進行判斷。函數原型如下:

    UINT GetConsoleCP(void);UINT WINAPI GetConsoleOutputCP(void);

    這兩個函數沒有參數,返回值是UINT類別,也就是當前所用的內碼表識別元。第一個函數返回的是輸入模式的內碼表識別元,第二個函數返回的是輸出模式的內碼表識別元。透過對內碼表判斷,然後始終保持當前內碼表識別元為65001即可。

    範例程式碼如下:

    #include <stdio.h>#include <windows.h>int main(){ UINT codePage = GetConsoleOutputCP(); if(codePage != 65001){ SetConsoleCP(65001); SetConsoleOutputCP(65001); } LPCSTR newStr = "你好,世界!" ; if(SetConsoleTitle(newStr) == TRUE){ printf("標題修改成功!\n"); } return 0;}

    我們現在可以隨心所欲的修改視窗標題名了,並且也可以正確顯示中文內容了。接下來,我們如何獲取當前視窗的標題名呢?

    功能:獲取控制台標題

    透過GetConsoleTtile函數,就可以獲取道當前控制台的標題。函數原型如下:

    DWORD GetConsoleTitle(LPTSTR lpConsoleTitle, DWORD nSize );

    函數名: GetConsoleTitle;第一個參數類別:LPTSTR

    這個參數的類別和上面介紹的SetConsoleTtitle函數的LPCTSTR類別,很相似。本質的區別在於字母C,LPCTSTR是指向常量字串,不允許修改,屬於輸入性參數。而LPTSTR類別,指向的是可以被修改的一段區域,一般我們定義成一個陣列,或者一段動態記憶體分配區域。

    第二個參數類別:DWORD

    windows編程規範中,把8個位(bit)當作1個字節BYTE,把2個BYTE當作1個字,即WORD,2個WORD為雙字,即DOUBLE WORD,簡寫為DWORD。

    1個DWORD類別的變量,等於4個字節,表示32 位無符號整數。 範圍為 0 到十進制4294967295。其實就是c語言中的 unsigned long ,實際上也是這樣定義的:

    typedef unsigned long DWORD;

    返回值和第二個參數一樣,也是DWORD。

    第一個參數是輸出型參數,接受函數的返回數據。(什麽是輸入型參數和輸出型參數,可以檢視我的這篇文章:c語言解剖課:函數的輸入參數和輸出參數),用來存放獲取的當前視窗標題字串。

    第二個參數是要顯式的說明第一個參數所指向的記憶體區域的大小,以字元為單位。

    如果函數成功,則 返回值為控制台視窗標題的字元個數 。如果函數失敗,則返回值為零。

    下面是一段演示程式碼:

    #include <stdio.h>#include <windows.h>#include <stdlib.h>#include <malloc.h>int main(){ DWORD charactor_total = MAX_PATH; LPTSTR title = (LPTSTR)malloc(sizeof(CHAR)*MAX_PATH); DWORD n = GetConsoleTitle(title,charactor_total); printf("標題字元個數:%d\n",n); system("pause"); return 0;}

    程式執行結果如圖:

    大家可以數一下標題的字元數,是不是91個。[憨笑]

    功能:獲取原始標題

    當控制台視窗標題被修改後,我們有時候需要獲取視窗的原始標題,也就是程式的絕對路徑和程式全名(包括字尾),這非常有用,因為可以根據這個字串提取出程式檔所在的目錄。

    獲取原始標題的函數為GetConsoleOriginalTitle(),當視窗標題未被修改時,GetConsoleTtile()和它的功能是一樣的,當被修改後,就出現了本質的差別,所以小心不要用錯函數。

    函數原型如下:

    DWORD GetConsoleOriginalTitle( LPTSTR lpConsoleTitle, DWORD nSize);

    這個函數的參數和用法,就不再講解,和GetConsoleTitle函數類似。

    總 結

    今天因為是第一篇文章,特別是對windows程式設計比較陌生的讀者,對微軟的自訂數據結構估計也會不適應,所以本文對微軟自訂的數據結構講解的比較詳細,後面文章都會對出現的數據結構進行詳細講解,感興趣的讀者可以持續關註我,感謝。

    段譽,2024年2月9日(除夕),寫於合肥。