首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C >> 關於C

用 C 語言打造圖形化電子時鐘:從零實作完整原始碼解析

     編輯:關於C

想在 DOS 時代的 Turbo C 環境下,親手繪製一個擁有模擬指針與數字顯示的電子時鐘嗎?本文將帶你完整走過一個經典 graphics.h 專案的生命週期:從繪製表盤、刻度、指針,到處理鍵盤輸入調整時間,最後實現秒針流暢跳動的動畫迴圈。文末附上完整可編譯的原始碼,適合對繪圖程式設計、即時系統邏輯有興趣的學習者參考。

效果預覽

程式執行後會彈出一個 640×480 的圖形視窗,左側為操作說明區,右側為紅藍配色的時鐘表盤,下方同步顯示數字時間。按鍵操作如下:

  • ↑ / ↓:遞增/遞減當前光標指向的時間單位(時/分/秒)
  • Tab:在時、分、秒之間移動光標
  • Esc:離開程式
電子時鐘執行畫面

核心架構與模組劃分

整支程式約 300 行,採用「主程式負責初始化與繪製靜態介面,ClockHandle() 負責動畫迴圈與事件處理」的雙層架構。關鍵全域變數集中管理表盤幾何參數(圓心、半徑)與時間狀態,各功能函式職責單一,便於維護。

1. 靜態介面繪製(main())

  • 圖層堆疊:先畫紅色三角頂標題列,再疊藍色矩形主區塊,最後用 floodfill() 填滿黑色表盤背景。
  • 刻度生成:for 迴圈配合三角函數 sin()/cos(),以 6° 間距繪製 60 條分刻度(白色細點),每 5 格加粗紅色為時刻度。
  • 固定文字:outtextxy() 寫入標題、作者、操作提示與裝飾字串。

2. 動畫核心 ClockHandle()

採用 XOR 繪圖模式(setwritemode(1)) 實現「畫一次、擦一次」的無閃爍指針動畫:

  1. 取得系統時間(gettime())初始化時、分、秒指針角度。
  2. 進入 for(;;) 無限迴圈,每圈只在秒數變化時重繪秒針;分、時指針僅在對應數值變動時更新。
  3. 內層 while 以 bioskey(1) 非阻塞檢查鍵盤,若有按鍵立即呼叫 KeyHandle() 處理時間調整與光標移動。
  4. 數字時鐘區同步更新:DigitalClock() 先用黑色長條遮蓋舊值,再印新值。

3. 鍵盤交互邏輯

Count 變數(1=時、2=分、3=秒)追蹤光標位置,Up_Key()/Down_Key() 直接修改 struct time Time[1] 並呼叫 settime() 寫回硬體 RTC,達成「改時間即改系統時間」的真實感。

關鍵技術細節

極座標轉直角座標

x = Center_x + Radius * sin(angle * PI / 180);
y = Center_y - Radius * cos(angle * PI / 180);

注意 Y 軸向下為正,故 cosine 取負。

XOR 繪圖模式

setwritemode(1) 將像素與背景做 XOR,同一座標畫兩次即還原背景,無需重繪整個表盤,極大降低 CPU 佔用。

數字時鐘寬度固定

DigitalClock() 以 bar() 清除固定寬度區域,再 sprintf() 輸出,避免位數變化(如 9→10)殘留舊字元。

完整原始碼

/*-----------------------------電子時鐘源代碼---------------------------------*/
/*------------------------------預處理----------------------------------------*/
#include <graphics.h>
#include <math.h>
#include <dos.h>
#include <stdio.h>
/*-----------------------------宏定義-----------------------------------------*/
#define PI     3.1415926
#define Up     0x4800
#define Down   0x5000
#define Tab    0x0F09
#define Esc    0x011B
/*-----------------------------定義全局變量-----------------------------------*/
double Hour, Minute, Second;
int Radius = 130, Center_x = 300, Center_y = 265;
double Hour_x, Hour_y, Minute_x, Minute_y, Second_x, Second_y;
struct time Time[1];
/*-----------------------------函數的聲明-------------------------------------*/
void ClockHandle();
int DigitalHour(double Hour);
int DigitalMinute(double Minute);
int DigitalSecond(double Second);
void DigitalClock(int x, int y, int clock);
int Up_Key(int Count);
int Down_Key(int Count);
void ClearCursor(int Count);
void DrawCursor(int Count);
int KeyHandle(int Key, int Count);
/*----------------------------主函數------------------------------------------*/
main()
{
    /*--------------繪底板----------------------*/
    int Driver, Mode;
    int i, j;
    Driver = DETECT;
    initgraph(&Driver, &Mode, "");
    setcolor(RED);
    line(300, 0, 5, 120);
    line(300, 0, 595, 120);
    line(5, 120, 595, 120);
    setfillstyle(SOLID_FILL, RED);
    floodfill(300, 100, RED);
    setcolor(BLUE);
    line(65, 120, 535, 120);
    line(65, 120, 65, 470);
    line(535, 120, 535, 470);
    line(65, 470, 535, 470);
    line(125, 120, 125, 470);
    line(475, 120, 475, 470);
    line(125, 410, 475, 410);
    setfillstyle(SOLID_FILL, BLUE);
    floodfill(80, 130, BLUE);
    floodfill(500, 130, BLUE);
    setfillstyle(SOLID_FILL, BLACK);
    floodfill(300, 450, BLUE);
    /*-------------繪時間間隔符-----------------*/
    for (i = 275; i <= 305; i += 30)
        for (j = 434; j <= 442; j += 8) {
            setcolor(WHITE);
            setlinestyle(0, 0, 3);
            circle(i, j, 1);
        }
    /*------------繪制光標----------------------*/
    setcolor(WHITE);
    line(270, 430, 270, 445);
    /*-------------繪制表軸---------------------*/
    setlinestyle(0, 0, 3);
    circle(Center_x, Center_y, 1);
    /*-------------繪制表框---------------------*/
    setlinestyle(0, 0, 1);
    circle(Center_x, Center_y, Radius + 3);
    /*--------------繪制分針刻度----------------*/
    for (i = 0, Minute = 0; i <= 59; i++, Minute++) {
        Minute_x = Center_x + Radius * sin(Minute / 30.0 * PI);
        Minute_y = Center_y - Radius * cos(Minute / 30.0 * PI);
        setlinestyle(0, 0, 1);
        circle((int)Minute_x, (int)Minute_y, 1);
    }
    /*--------------繪制時針刻度----------------*/
    setcolor(RED);
    for (i = 0, Hour = 0, Minute = 0; i <= 11; i++, Hour++) {
        Hour_x = Center_x + Radius * sin((Hour * 60 + Minute) / 360.0 * PI);
        Hour_y = Center_y - Radius * cos((Hour * 60 + Minute) / 360.0 * PI);
        setlinestyle(0, 0, 3);
        circle((int)Hour_x, (int)Hour_y, 1);
    }
    /*--------------添加文本--------------------*/
    setcolor(GREEN);
    outtextxy(283, 50, "CLOCK");
    outtextxy(320, 110, "Designed by Zhang Cheng Jin");
    outtextxy(80, 130, "Help");
    outtextxy(70, 150, "Up:");
    outtextxy(78, 168, "Time++");
    outtextxy(70, 216, "Down:");
    outtextxy(78, 234, "Time--");
    outtextxy(70, 286, "Tab:");
    outtextxy(78, 304, "Move");
    outtextxy(70, 356, "Esc:");
    outtextxy(78, 374, "Exit");
    outtextxy(500, 190, "No");
    outtextxy(490, 215, "time");
    outtextxy(490, 240, "like");
    outtextxy(495, 265, "the");
    outtextxy(480, 290, "present");
    outtextxy(500, 315, "!");
    /*----------調用函數-----------------------*/
    ClockHandle();
    /*-----------關閉圖形----------------------*/
    closegraph();
    return 0;
}
/*---------------------------時鐘動畫處理函數---------------------------------*/
void ClockHandle()
{
    int Key = 0, Count = 2;
    setcolor(WHITE);
    /*----------------獲取系統時間-------------*/
    gettime(Time);
    Hour = Time[0].ti_hour;
    Minute = Time[0].ti_min;
    /*----------------繪制分針-----------------*/
    Minute_x = Center_x + 100 * sin(Minute / 30.0 * PI);
    Minute_y = Center_y - 100 * cos(Minute / 30.0 * PI);
    line(Center_x, Center_y, (int)Minute_x, (int)Minute_y);
    /*----------------繪制時針-----------------*/
    Hour_x = Center_x + 80 * sin((Hour * 60 + Minute) / 360.0 * PI);
    Hour_y = Center_y - 80 * cos((Hour * 60 + Minute) / 360.0 * PI);
    line(Center_x, Center_y, (int)Hour_x, (int)Hour_y);
    /*----------------繪制數字時鐘-------------*/
    DigitalClock(255, 435, DigitalHour(Hour));
    DigitalClock(285, 435, DigitalMinute(Minute));

    setwritemode(1);  // XOR 模式
    for (Count = 2; Key != Esc; ) {
        /*----------------繪制秒針-----------------*/
        setcolor(RED);
        gettime(Time);
        Hour = Time[0].ti_hour;
        Minute = Time[0].ti_min;
        Second = Time[0].ti_sec;

        Second_x = Center_x + 120 * sin(Second / 30.0 * PI);
        Second_y = Center_y - 120 * cos(Second / 30.0 * PI);
        line(Center_x, Center_y, (int)Second_x, (int)Second_y);
        /*-----------------------------------------*/
        while (Hour == Time[0].ti_hour &&
               Minute == Time[0].ti_min &&
               Second == Time[0].ti_sec) {
            gettime(Time);
            if (bioskey(1) != 0) {
                Key = bioskey(0);
                Count = KeyHandle(Key, Count);
                if (Count == 5) Count = 1;
            }
        }
        /*----------繪制數字時鐘的秒---------------*/
        setcolor(WHITE);
        DigitalClock(315, 435, DigitalSecond(Second) + 1);
        /*----------清除前一個秒針的圖形-----------*/
        setcolor(RED);
        Second_x = Center_x + 120 * sin(Second / 30.0 * PI);
        Second_y = Center_y - 120 * cos(Second / 30.0 * PI);
        line(Center_x, Center_y, (int)Second_x, (int)Second_y);

        setcolor(WHITE);
        /*---------對分針變化的處理----------------*/
        if (Minute != Time[0].ti_min) {
            Minute_x = Center_x + 100 * sin(Minute / 30.0 * PI);
            Minute_y = Center_y - 100 * cos(Minute / 30.0 * PI);
            line(Center_x, Center_y, (int)Minute_x, (int)Minute_y);

            Minute = Time[0].ti_min;
            DigitalClock(285, 435, DigitalMinute(Minute));
            Minute_x = Center_x + 100 * sin(Minute / 30.0 * PI);
            Minute_y = Center_y - 100 * cos(Minute / 30.0 * PI);
            line(Center_x, Center_y, (int)Minute_x, (int)Minute_y);
        }
        /*----------對時針變化的處理---------------*/
        if (Hour != Time[0].ti_hour) {
            Hour_x = Center_x + 80 * sin((Hour * 60 + Minute) / 360.0 * PI);
            Hour_y = Center_y - 80 * cos((Hour * 60 + Minute) / 360.0 * PI);
            line(Center_x, Center_y, (int)Hour_x, (int)Hour_y);

            Hour = Time[0].ti_hour;
            DigitalClock(255, 435, DigitalHour(Hour));
            Hour_x = Center_x + 80 * sin((Hour * 60 + Minute) / 360.0 * PI);
            Hour_y = Center_y - 80 * cos((Hour * 60 + Minute) / 360.0 * PI);
            line(Center_x, Center_y, (int)Hour_x, (int)Hour_y);
        }
    }
}
/*------------------------數字時鐘模塊----------------------------------------*/
int DigitalHour(double Hour) { return (int)Hour; }
int DigitalMinute(double Minute) { return (int)Minute; }
int DigitalSecond(double Second) { return (int)Second; }

void DigitalClock(int x, int y, int clock)
{
    char buffer[10];
    setfillstyle(0, 0);  // 黑色填充
    bar(x, y, x + 14, 449);
    if (clock == 60) clock = 0;
    sprintf(buffer, "%02d", clock);
    outtextxy(x, y, buffer);
}
/*----------------------按鍵處理函數------------------------------------------*/
int KeyHandle(int Key, int Count)
{
    switch (Key) {
        case Up:    Up_Key(Count);    break;
        case Down:  Down_Key(Count);  break;
        case Tab:
            ClearCursor(Count);
            Count++;
            if (Count == 4) Count = 1;
            DrawCursor(Count);
            break;
    }
    return Count;
}
/*--------------------向上方向鍵函數------------------------------------------*/
int Up_Key(int Count)
{
    gettime(Time);
    switch (Count) {
        case 1:
            Time[0].ti_hour = (Time[0].ti_hour + 23) % 24;
            settime(Time);
            break;
        case 2:
            Time[0].ti_min = (Time[0].ti_min + 59) % 60;
            settime(Time);
            break;
        case 3:
            Time[0].ti_sec = (Time[0].ti_sec + 59) % 60;
            settime(Time);
            break;
    }
    return 0;
}
/*--------------------向下方向鍵函數------------------------------------------*/
int Down_Key(int Count)
{
    gettime(Time);
    switch (Count) {
        case 1:
            Time[0].ti_hour = (Time[0].ti_hour + 1) % 24;
            settime(Time);
            break;
        case 2:
            Time[0].ti_min = (Time[0].ti_min + 1) % 60;
            settime(Time);
            break;
        case 3:
            Time[0].ti_sec = (Time[0].ti_sec + 1) % 60;
            settime(Time);
            break;
    }
    return 0;
}
/*---------------------光標處理函數-------------------------------------------*/
void ClearCursor(int Count)
{
    setcolor(BLACK);  // 用背景色抹除
    switch (Count) {
        case 1: line(270, 430, 270, 445); break;
        case 2: line(300, 430, 300, 445); break;
        case 3: line(330, 430, 330, 445); break;
    }
}

void DrawCursor(int Count)
{
    setcolor(WHITE);
    switch (Count) {
        case 1: line(270, 430, 270, 445); break;
        case 2: line(300, 430, 300, 445); break;
        case 3: line(330, 430, 330, 445); break;
    }
}

編譯與執行環境建議

  • 編譯器:Turbo C 2.0 / Borland C++ 3.1 / DOSBox + TC
  • 專案設定:Large Memory Model、啟用 Graphics Library
  • BGI 驅動:確保 EGAVGA.BGI 與 TRIP.CHR 在執行目錄或 initgraph() 指定路徑

延伸練習方向

  1. 將 graphics.h 移植至 SDL2 / SFML / Windows GDI,體驗現代繪圖管線。
  2. 加入鬧鐘功能:以鍵盤輸入設定鬧鐘時間,到時播放 sound() 蜂鳴器。
  3. 實作 12/24 小制切換、夏令時間自動調整。
  4. 嘗試雙緩衝:建立記憶體 DC,離屏繪製後 BitBlt 一次貼上,完全消除閃爍。

這支程式雖屬教學範例,卻涵蓋了「圖形初始化、幾何繪製、即時迴圈、輸入處理、硬體時間存取」等系統程式設計核心要素,是從傳統 DOS 繪圖邁向現代遊戲迴圈架構的絕佳垫腳石。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved