2023年5月31日 星期三

C/C++ 學習筆記 : 基本語法

今天繼續整理 C& C++ 的基本語法筆記. 本系列之前的文章參考 :


C++ 是 C 語言的延伸, 基本語法幾乎完全一樣, 可參考以前學 Arduino 時整理的筆記 :


本篇參考書籍如下 : 
  1. 快速學會 C++ 語言 (洪志維, 易習 2014)
  2. 第一次學 C++ 就上手第二版 (李啟龍, 碁峰 2017) 
  3. C++ 教學手冊第三版 (旗標 2012)


一. 程式基本架構 :

C 程式的基本架構從上至下依序分為下面四個區塊 : 
  1. 前置指令 : 主要是匯入函式庫標頭檔與定義常數等.
  2. 自訂函式之原型宣告 : 定義於主函式 main() 後面的其他函式都必須先做原型宣告. 
  3. main() 主函式 : 一個程式中只能有一個名稱為 main() 的主函式, 為程式執行入口. 
  4. 自訂函式 : 於 main() 中會呼叫的自訂函式.
C & C++ 程式的每個敘述結尾都必須以分號結束, 但下面兩個結尾不可用分號 :
  • 前置指令如 #include 或 #define 等.
  • 函式的結尾大括號.
其次, 如果函式定義於 main() 前面則不需要原型宣告. 

C 程式的基本範例如下 :

#include <stdio.h>
#include <stdlib.h>

int main() {
   printf("Hello, world!");    //在螢幕顯示字串
   system("PAUSE");           //暫停程式執行, 按 ENTER 繼續
   return 0;                            //傳回 0 表示程式正常結束
   }

C++ 程式的基本架構從上至下依序分為下面五個區塊 : 
  1. 前置指令 : 主要是匯入函式庫標頭檔與定義常數等.
  2. 自訂函式之原型宣告 : 定義於主函式 main() 後面的其他函式都必須先做原型宣告. 
  3. 命名空間宣告 : 可簡化函式的調用方式, 例如宣告標準函式庫 using namespace std. 
  4. main() 主函式 : 一個程式中只能有一個名稱為 main() 的主函式, 為程式執行入口. 
  5. 自訂函式 : 於 main() 中會呼叫的函式.
C++ 主要是多了一個可有可無的命名空間宣告, 基本範例如下 :

#include <iostream>
#include <cstdlib>

using namespace std;    //宣告命名空間 std (標準函式庫)
int main() {
  cout << "Hello, world!" << endl;    //在螢幕顯示字串
  return 0;                                           //傳回 0 表示程式正常結束
  }

由於 C++ 的標準輸出入函式定義在命名空間 std 下的 iostream 函式庫裡, 如果沒有用 using namspace 宣告命名空間, 則標準輸出必須把命名空間寫出來, 例如 std::cout 與 std::end1.


二. 標頭檔 :

C 與 C++ 遵循模組化設計精神, 提供了許多標準函式庫, C 語言函式庫的函式原型宣告都放在副檔名為 .h 的標頭檔 (header file), 只要在程式最前面用前置指令 #include 匯入這些標頭檔, 即可於程式中呼叫這些標準函式庫中的函式. 匯入函式庫標頭檔的語法如下 :

#include <標頭檔名稱.h>   

注意, 前置指令結尾不可加分號

C 語言最常用的標準函式庫標頭檔為 stdio.h 與 stdlib.h, 例如標準輸出入常用的 printf() 與 scanf() 函式的原型宣告放在 stdio.h 標頭檔內; 而呼叫作業系統的函式 system() 原型宣告與記憶體配置函式等則是放在 stdlib.h 標頭檔內,  故 C 程式的開頭通常都需要匯入這兩個標頭檔 :

#include <stdio.h>
#include <stdio.h>

常用的 C 語言標頭檔如下表 :

 常用的 C 語言標頭檔 說明
 stdio.h 標準輸出入 (螢幕與鍵盤) 函式原型宣告, 例如 printf(), scanf()
 stdlib.h 各種基本函式的標準函式庫, 例如 system()
 string.h 字串處理函式的原型宣告, 例如 strcpy()
 time.h 日期時間函式的原型宣告, 
 math.h 數學運算函式的原型宣告, 例如 sqrt()

C++ 最常用的標準函式庫標頭檔為 iostream 與 cstdlib (注意, 沒有 .h), 它們分別包含了 C 語言的 stdio.h 與 stdlib.h 標頭檔, 並且新增到 std 命名空間中 (因此不需要 .h). 除了這兩個函式庫在 C++ 中用法不同外, 其他函式庫例如 string, time 與 math 在 C++ 中用法與 C 程式完全相同.  


三. 註解 :

C 語言的註解必須放在 /* 與 */ 之間, 它原本是只能單行註解, 但 C++ 將其擴充為可單行也可以多行, 例如 :

/*  printf("這是單行註解");  */    
/* int i;
    for(i=0; i<5; i++) {
       printf("這是多行註解");
       }   */

C++ 另外新稱只能用於單行的 // 的註解方式, 例如 :

// printf("這是單行註解");

如果要用 // 註解一個區塊的程式碼, 則每一行都要用 // 註解掉 : 

// int i;
// for(i=0; i<5; i++) {
     //printf("這是多行註解");
     //}   

由於 C++ 編譯器都向下相容 C 語法, 因此這兩種註解方式都可以同時使用, 例如 : 

// printf("這是單行註解");
/* int i;
    for(i=0; i<5; i++) {
       printf("這是多行註解");
       }   */

亦即單行可使用 // 或 /* */, 而多行則使用 /* */.  但建議盡量都使用 C++ 的 // 來註解, 因為 /* */ 在註解中又有註解時會提前結束註解區域而導致編譯失敗, 例如 : 

/* int i;
    for(i=0; i<5; i++) {    /* 印五次的迴圈 */   
       printf("這是多行註解");
       }   */

上述程式碼在 for 迴圈那一行後面有 /* */ 註解, 由於中間的 */ 會被視為註解結束, 後面的程式碼脫離註解範圍而導致編譯失敗, 如果全部用 // 註解就無此問題, 例如 : 

// int i;
    //for(i=0; i<5; i++) {    // 印五次的迴圈
       //printf("這是多行註解");
       //}   


四. 變數, 常數, 與資料型態 :

變數與常數是程式儲存在記憶體中準備用來運算的資料, 變數的值在程式中可用指派運算子 = 更改, 而常數則不可更改. 


1. 資料型態 :

C & C++ 為強型態 (strong-typed) 的語言, 每一個變數都必須指定資料型別以便編譯器為其配置於記憶體空間. C 語言內建的基本資料型別有 4 種 (C 沒有字串型別, 字串使用以空字元 '\0' 結尾的字元陣列儲存) :
  • 字元 : char (1 byte), 必須用單引號括起來. 
  • 整數 : int (4 bytes)
  • 浮點數 : float (4 bytes) 與 double (8 bytes)
C++ 又新增了兩種基本型態 : 
  • 布林 : bool (1 byte), 只有 true(=1) 與 false(=0) 兩個值.
  • 無傳回值 : void, 用於宣告函式無傳回值. 
整數 int 前面可以用下列 4 個修飾詞 :
  • long : 與 int 相同為 4 bytes 整數.
  • short : 2 bytes 整數. 
  • signed : 有正數與負數. 
  • unsigned : 僅正數. 
其中 signed 與 unsigned 也用來修飾 char 類型. 如果整數變數均為正數, 則可宣告為 unsigned short 或 unsigned int.

字元資料必須用單引號括起來, 例如 'a' 與 'A' (單引號裡面只能放一個字元), 以一個 byte 的 ASCII 編碼 (0~127 的整數) 儲存, 例如 'A' 是以 65 這個整數儲存. 另外還有稱為脫逸字元的特殊字元, 常用的脫逸字元如下 (均占用 1 byte 的記憶體) : 
  • \0 : 空字元=NULL (用來標示字串的結束)
  • \n : 換行字元
  • \r : 回車字元
  • \t : 定位點字元
  • \\ : 倒斜線 \
  • \? : 問號 ?
  • \' : 單引號 '
  • \" : 雙引號


2. 變數與識別字 :

變數可以先宣告再賦值, 語法如下 : 

資料型態 識別字;    
識別字=值;    

int a;           // 先宣告
a=100;        // 再賦值

或者宣告同時賦值 :

資料型態 識別字=值;

例如 :

int a=100;     // 宣告同時賦值

可以同時宣告多個相同型別的變數, 各變數識別字以逗號隔開, 語法如下 : 

資料型態 識別字1, 識別字2, 識別字3, .... ; 

例如 :

int a, b, c;
int a=1, b=2, c=3; 

指定資料型態的目的是要讓編譯器為此變數配置所需的記憶體空間. 

識別字命名的規則如下 :
  • 字母有分大小寫, user 與 User 是不同的識別字. 
  • 只能使用英文字母, 數字, 與底線之組合, 但不可以用數字開頭.
  • 不可使用關鍵字或與內建函式同名
  • 長度不可超過 127 個字元.
注意, 這些命名規則不僅適用於變數名稱, 也適用於常數名稱, 函式名稱; 也適用於 C++ 中的物件與類別名稱.  

關鍵字是語言本身的保留字, 不可用作識別字.  C 的關鍵字如下表 : 


 auto break case char const
 continue default do double else
 enum extern float for goto
 if int long register return
 short signed sizeof static struct
 switch typedef union unsigned void
 volatile while   


C++ 又補充了如下關鍵字 : 


 bool catch explicit namespace delete
 dynamic_cast mutable protected friend class
 inline private template new operator
 static_cast try public reinterpret_case true
 virtual this throw typeid false
 type_name const_cast wchar_t asm using


3. 常數 :

常數的宣告有兩種方法 :
  • 使用前置指令 #define :
    語法 : #define 識別字 常數值  
    例如 : #define PI 3.14159
  • 使用 const 關鍵字 :
    語法 : const 資料型態 識別字=常數值;      
    例如 : const double PI=3.14159
注意 : 
  • 常數的識別字命名規則與變數一樣, 但習慣上全部使用大寫字母 (非強制). 
  • 使用前置指令時不需要指定資料型態 , 結尾也不可家分號; 而使用 const 時則要指定資料型態, 結尾必須用分號. 
  • 常數值若是字元要用單引號括起來; 若是字串則要用雙引號括起來.
C & C++ 中的字串常數以字元陣列 + '\0' 儲存, 故字串會比一般字元串列長度多出 1 個 byte, 字串常數以雙引號括起來, 例如 "A" 是字串, 長度為 2 個 byte; 而 'A' 則是字元, 長度為 1 個 byte. 

C++ 新增了 bool 布林型態, 其值只有 true 與 false, 等同於整數的 1 與 0.  

小霸王的 ESP32-CAM 套件

我上周六早上花了四個半小時聽小霸王尤博的 ESP32-CAM 套件使用說明線上教學, 覺得比原廠方便好用, 此套件在蝦皮上架, 一組 $799 元 : 





此套件的 ESP32-CAM 是小霸王特製款 (例如刺眼的 LED 燈改為較小的), 與原廠安信可功能相同, 但搭配 EasyCam 擴充板在上傳程式燒錄韌體操作上較方便 (不用按 Reset 鍵). 教學影片已上 Youtube (據說一周後會下架, 但我覺得應該放著比較好, 讓購買者回看用法, 潛在客戶也能透過影片了解其優點) : 


教學簡報參考 :


參考 :


等菁菁幫我搞定免運券就下標唄. 

2023年5月30日 星期二

GPT-4 多模態上課筆記

昨天晚上參加臉書 MQTT 社群益師傅的 GPT-4 多模態分享會, 內容非常精彩, 除了 GPT-4 以外,益師傅還介紹了另外4 個新創的 AIGC 網站 : 


另外還介紹了 bing 的外掛, 除了可以使用 GPT-4 外還能生圖, 不過都還沒時間去玩. 

2023-07-24 補充 :

前陣子還上過益師傅的 Recraft 線上分享, 真的太棒太精采了, 若時間允許可以去上他在資策會的線下課. 但我同樣還沒時間去玩 recraft.ai, 事情實在太多太忙了. 


生成式 AI 工具推陳出新, 令人目不暇給, 時間有限, 只能挑自己用得到的去深入. 

天瓏書店博碩曬書節 (五折好康)

今天在滑臉書時看到天瓏書店推出博碩曬書節, 有一票五折書優惠至 7 月底 :


下面幾本是我感興趣的書 :


2023 年第 22 周記事

本周已來到五月底, 不知不覺日子就到年中了, 離端午節 (6/22) 連假也不過是三周外的光景, 人到中年日子過得就像溜滑梯一樣, 時間比甚麼都寶貴, 在衡量價值時要以此為準. 週五打電話回家時聽爸說徳源伯母前些日子往生了, 以前母親住院來探病後已十年未見, 我週日早上去市場前先繞到德源伯母家點香悼念, 因下周一我無法參加告別式. 

過去幾天都在備課順便整理 C 語言筆記, 我進行中的專案與學習進度只好暫停, 忙過這回要優先弄 Django, 因為又有借閱中書籍被預約, 前年 Django 學到一半暫停, 這次要一口氣學完. 上週去逛明儀看到何敏煌寫的 "快速學會Python架站技術:活用Django 4建構動態網站的16堂課" (博客來七月底前打七折), 此書隨著 Django 改版已賣了 4 個版次, 最末章有提到安裝 SSL 很值得參考. 

鄉下家的四隻飯桶貓前兩周可能被對面高雄人養的狗追逐, 嚇得爬樹跳上二樓, 白天躲在有龍眼樹遮蔽的車庫鐵皮屋上, 晚上就散居於二樓祖堂前露台或窗台. 這四隻小貓不太親近人, 也不像之前的喵家四兄弟晚上會在曬穀場陪騎單車的爸, 週六的夜晚有時出來曬穀場漫步, 還真的蠻懷念喵家四兄弟啊! 不知它們離家後過得怎樣? 菁菁說去年應該帶它們去結紮才對, 公貓長大發情後都會離家. 

2023年5月29日 星期一

好站 : Listen Lab 的 Praat 與語音分析教學影片

最近在解決網友詢問 Praat 的 spectrogram 是否可設定用不同顏色顯示能量範圍時找到 Youtube 上一個很棒的頻道 : 


此頻道版主是美國明尼蘇達大學語言與聽力科學系教授 Mathew Winn, 參考 :


作者的個人網站 : 


作者錄製了使用 Praat 做語音分析的系列影片 (缺 18/19), 非常值得參考 : 


另外還有 6 個聲學語音學系列教學影片 :


哇, 真是太棒了!

C 語言 CH11 結構範例/練習/作業


範例 : 
  1. https://replit.com/@tony1966/c-test#ch11-struct/Example01.c
  2. https://replit.com/@tony1966/c-test#ch11-struct/Example02.c
  3. https://replit.com/@tony1966/c-test#ch11-struct/Example03.c
  4. https://replit.com/@tony1966/c-test#ch11-struct/Example04.c
  5. https://replit.com/@tony1966/c-test#ch11-struct/test01.c
  6. https://replit.com/@tony1966/c-test#ch11-struct/test02.c
  7. https://replit.com/@tony1966/c-test#ch11-struct/test03.c
  8. https://replit.com/@tony1966/c-test#ch11-struct/test04.c
  9. https://replit.com/@tony1966/c-test#ch11-struct/test05.c
  10. https://replit.com/@tony1966/c-test#ch11-struct/test06.c
  11. https://replit.com/@tony1966/c-test#ch11-struct/test07.c
  12. https://replit.com/@tony1966/c-test#ch11-struct/test08.c
  13. https://replit.com/@tony1966/c-test#ch11-struct/test09.c
  14. https://replit.com/@tony1966/c-test#ch11-struct/test10.c
  15. https://replit.com/@tony1966/c-test#ch11-struct/test11.c
  16. https://replit.com/@tony1966/c-test#ch11-struct/test12.c
  17. https://replit.com/@tony1966/c-test#ch11-struct/test13.c
練習題 : 
  1. C程式宣告名為record的結構,內含2int型態的成員變數ab,和1float型態的成員變數c,然後建立結構變數test,指定float型態的成員值為123.6,和2int型態的成員值都為150,最後計算和顯示成員變數的總和。
    https://replit.com/@tony1966/c-test#ch11-struct/practice01.c
  2. 請繼續實作題1,宣告結構指標ptr指向test,然後使用兩種指標方式來分別指定int成員變數值為8867,和計算和顯示成員變數的平均。
    https://replit.com/@tony1966/c-test#ch11-struct/practice02.c
  3. 請建立C程式宣告employee結構來儲存員工資料,包含姓名、年齡和薪水,然後建立結構陣列儲存5位員工的基本資料。
    https://replit.com/@tony1966/c-test#ch11-struct/practice03.c
  4. 請建立C程式宣告item結構,擁有成員變數name字串(大小為20), 2個整數變數armslegs儲存有幾隻手和腳,然後使用結構陣列儲存下表的項目,最後一一顯示項目的成員變數值,如下所示:(Human 2 2) (Cat,0,4) (Dog,0,4) (Table,0,4)
    https://replit.com/@tony1966/c-test#ch11-struct/practice04.c
作業 :
  1. 下列程式碼會在哪一行出現編譯錯誤?
    struct user {
      char *name;
      };
    struct user users[5];
    users.name[0]="Tony";
  2. 下列的結構宣告哪裡有錯?
    struct member {
      char name[30];
      struct member no;
      }

2023年5月25日 星期四

如何為 Praat 的聲譜圖 (spectrogram) 上色

昨天有網友留言詢問用 Praat 的聲譜圖時可否設定不同能量範圍用不同之顏色繪製? 我查了 Praat 介面並無所獲, 查詢線上使用手冊也沒找到, 看來 Praat 是固定用灰階來表示不同能量, 無法依照不同能量選擇不同顏色. 不過搜尋解決辦法時找到下面這個影片, 作者使用 R 程式將 Praat 匯出的聲譜資料進行後製處理, 利用 R 強大的繪圖功能來繪製彩色的聲譜圖 :





影片中的 R 程式原始碼放在 GitHub :


我用 ChatGPT 將此 R 程式轉換成相應的 Python 程式如下 :

# colored-spectroram.py
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors

# Read Praat spectrogram functions
def convert_spectrogram_to_df():
    # Implementation of convert_spectrogram_to_df function

def pre_emphasize():
    # Implementation of pre_emphasize function

def constrain_dynamic_range(df, column, dynamic_range):
    # Implementation of constrain_dynamic_range function

# Set file paths
spectrogram_file_path = "C:\\Users\\Matt\\Documents\\R\\Read_praat_spectrograms"
my_spect_file = "lake.Spectrogram"

# Load data into DataFrame
df_spectrogram = convert_spectrogram_to_df(my_spect_file)
df_spectrogram = pre_emphasize(df_spectrogram)
df_spectrogram = constrain_dynamic_range(df_spectrogram, "Level_preemp", 120)

# Plot spectrogram
fig, ax = plt.subplots()
pcm = ax.pcolormesh(df_spectrogram["Time"], df_spectrogram["Frequency"], df_spectrogram["Level_preemp_dr"],
                    shading='auto', cmap='gray')
ax.set_xlim(0, 0.56)
ax.set_ylim(0, 5000)
ax.set_xlabel("Time (s)")
ax.set_ylabel("Frequency (Hz)")
plt.colorbar(pcm, ax=ax)
plt.show()

# Plot segments in different colors
onset_l = 0.0324
onset_ei = 0.1254
onset_k = 0.306

df_spectrogram["segment"] = np.where(df_spectrogram["Time"] > onset_k, "k",
                                     np.where(df_spectrogram["Time"] > onset_ei, "eI",
                                              np.where(df_spectrogram["Time"] > onset_l, "l", np.nan)))

df_spectrogram["segment"] = pd.Categorical(df_spectrogram["segment"], categories=["l", "eI", "k"])

segment_colors = ["black", "#8C3B3B", "#1D2F50"]

fig, ax = plt.subplots()
pcm = ax.pcolormesh(df_spectrogram["Time"], df_spectrogram["Frequency"], df_spectrogram["Level_preemp_dr"],
                    shading='auto', cmap=mcolors.ListedColormap(segment_colors))
ax.set_xlim(0, 0.56)
ax.set_ylim(0, 5000)
ax.set_xlabel("Time (s)")
ax.set_ylabel("Frequency (Hz)")
plt.colorbar(pcm, ax=ax)
plt.show()

# Plot spectrogram with log-scaled Y axis
octave_breaks = [125, 250, 500, 1000, 2000, 4000]

fig, ax = plt.subplots()
pcm = ax.pcolormesh(df_spectrogram["Time"], df_spectrogram["Frequency"], df_spectrogram["Level_preemp_dr"],
                    shading='auto', cmap='gray')
ax.set_xlim(0, 0.56)
ax.set_ylim(50, 8000)
ax.set_xlabel("Time (s)")
ax.set_ylabel("Frequency (Hz)")
ax.set_yscale('log')
ax.set_yticks(octave_breaks)
ax.get_yaxis().set_major_formatter(plt.ScalarFormatter())
plt.colorbar(pcm, ax=ax)
plt.show()

# Plot narrowband style spectrogram
my_spect_file_nb = "lake_narrowband.Spectrogram"

df_spectrogram_nb = convert_spectrogram_to_df(my
_spect_file_nb)
df_spectrogram_nb = pre_emphasize(df_spectrogram_nb)
df_spectrogram_nb = constrain_dynamic_range(df_spectrogram_nb, "Level_preemp", None, 95)

fig, ax = plt.subplots()
pcm = ax.pcolormesh(df_spectrogram_nb["Time"], df_spectrogram_nb["Frequency"], df_spectrogram_nb["Level_preemp_dr"],
                    shading='auto', cmap='gray')
ax.set_xlim(0, 0.56)
ax.set_ylim(50, 8000)
ax.set_xlabel("Time (s)")
ax.set_ylabel("Frequency (Hz)")
ax.set_yscale('log')
ax.set_yticks(octave_breaks)
ax.get_yaxis().set_major_formatter(plt.ScalarFormatter())
plt.colorbar(pcm, ax=ax)
plt.show()

# Plot "Mongoose" spectrogram
df_mongoose = convert_spectrogram_to_df("mongoose_12.Spectrogram")
df_mongoose = pre_emphasize(df_mongoose)
df_mongoose = constrain_dynamic_range(df_mongoose, "Level_preemp", 60)

onset_m = 0.0517
onset_a = 0.13
onset_ng = 0.2535
onset_g = 0.3575
onset_u = 0.4016
onset_s = 0.579
endpoint = 0.883

segment_times = [onset_m, onset_a, onset_ng, onset_g, onset_u, onset_s, endpoint]

df_mongoose["segment"] = pd.cut(df_mongoose["Time"], bins=segment_times, labels=False, right=False)
df_mongoose["segment"] = df_mongoose["segment"] + 1

fig, ax = plt.subplots()
pcm = ax.pcolormesh(df_mongoose["Time"], df_mongoose["Frequency"], df_mongoose["Level_preemp_dr"],
                    shading='auto', cmap='gray')
ax.set_xlim(0, 0.89)
ax.set_ylim(0, 6000)
ax.set_xlabel("Time (s)")
ax.set_ylabel("Frequency (Hz)")
ax.set_yscale('log')
ax.set_yticks(octave_breaks)
ax.get_yaxis().set_major_formatter(plt.ScalarFormatter())
plt.colorbar(pcm, ax=ax)
plt.show()

不過裡面有幾個函式未實作, 有空再看看如何補全. 

高科大還書 1 本 (C + + 程式設計的樂趣)

由於有一本預約書到館, 只好拿這本 C++ 去還才能取書 : 

Source : 博客來


此書譯自 No Starch 於 2019 年出版的 "C++ Crash Course" 這本書, 全書近 1000 頁, 是個大部頭且內容深入的好書. 作者 Josh Lospinoso 博士是曾服役美軍 15 年的企業家, 他在序言中簡短地描述了 C++ 從一個 C 語言的分支, 歷經多次改版, 最終被 ISO 標準化的過程. 他認為 C++ 之所以會給人一種 "很複雜" 的印象, 主因是缺乏高品質的教材. 但我個人翻閱後, 覺得此書雖然內容豐富解說詳細, 但卻不適合做為 HANDS-ON 教材, 因為這是一本很深入的著作, 不是入門書. 

2023年5月24日 星期三

C/C++ 學習筆記 : 結構

結構 (structure) 是將數個相關的資料類型 (稱為欄位或成員) 集結起來組成一個衍生的資料類型, 讓程式設計者可以依據需要自訂資料類型. 結構裡面的欄位可以是同質的 (全部欄位之類型相同) 或異質的 (包含不同類型), 而且欄位也可以是另一個結構 (即巢狀結構). 

例如公司的產品, 可以用包含型號 (字串), 品名 (字串), 與價格 (整數) 這三個欄位組成的結構來儲存; 好友通訊錄可以用包含姓名 (字串), 電話 (字串), 與生日 (字串) 等類型組成之結構來表示. 結構可讓資料的儲存更有系統. 


1. 定義結構 : 

C/C++ 使用 struct 關鍵字來定義結構, 語法如下 :

struct 結構名稱 {   
    資料類型 欄位名稱1;   
    資料類型 欄位名稱2;   
    ....   
    };    

注意, 每個欄位宣告後要用分號結束; 結尾大括號後面也要用分號結束. 定義一個結構就是在描述此新的資料型態是如何組成的, 它的成員可由既有的基本資料型態組成, 例如數值, 字串, 陣列, 指標, 甚至是另一個結構 (即巢狀結構, 結構裡面的成員是另一個結構). 其次, 結構的定義可以寫在 main() 裡面 (其他函式不可用), 也可以寫在 main() 外面 (其他函式可用).

例如定義一個記錄學生成績的結構 :

struct score {
    char name[10];
    int chinese;
    int english;
    int math;
    int nature;
    }

C++ 可以匯入 string 函式庫後使用 string 類型 : 

struct score {
    string name;
    int chinese;
    int english;
    int math;
    int nature;
    }


2. 結構變數的宣告與賦值 : 

定義一個結構後, 即可使用下列語法宣告結構變數 :

struct 結構名稱 結構變數名稱;      

以上面定義的 score 結構為例宣告兩個結構變數 : 

sctruc score score1, score2;       //宣告了兩個結構變數 score1, scroe2

但這兩個變數目前內容是空的, 賦值的方式是透過小數點符號取得欄位, 對於數值欄位 (整數與浮點數) 而言, 直接用 = 運算子將欄位值指派給該欄位即可, 但字串欄位則必須匯入 string 函式庫後使用 strcpy() 函式來賦值, 例如 :

strcpy(score1.name, "Tony");
score1.chinese=90;
score1.english=87;
score1.math=77;
score1.nature=92;

也可以用陣列的大括弧來賦值, 裡面的值一一對應到宣告結構時之順序, 例如 : 

struct score score2={"Amy", 98, 91, 56, 65}

使用此方式用不到 strcpy() 複製字串到欄位裏, 所以就不需要匯入 string 函式庫了. 結構成員的取值方式一律使用小數點符號與欄位名稱,  例如 score1.name, score2.nature 等. 如果要修改欄位內容, 除了字串需使用 strcpy() 外, 數值可直接用指派運算子 '=' 直接修改. 

下面範例中宣告了兩個結構變數 score1 與 score2, 分別用宣告後再賦值與宣告同時賦值兩種方式來儲存資料 : 

/* 建立結構和顯示結構內容 test01.c */
#include <stdio.h>
#include <string.h>

struct score {  //定義結構
  char name[10];
  int chinese;
  int english;
  int math;
  int nature;
  };

int main() {
  /* 宣告結構變數score1並使用.賦值 */
  struct score score1;   
  strcpy(score1.name, "Tony");
  score1.chinese=90;
  score1.english=87;
  score1.math=77;
  score1.nature=92;
  /* 顯示結構score1資料 */
  printf("結構score1的內容:\n");
  printf("姓名: %s\n", score1.name);
  printf("國文: %d\n", score1.chinese);
  printf("英文: %d\n", score1.english);
  printf("數學: %d\n", score1.math);
  printf("自然: %d\n", score1.nature);   
  /* 宣告結構變數score2並使用{}賦值 */
  struct score score2={"Amy", 98, 91, 56, 65); 
  /* 顯示結構score2資料 */
  printf("結構score2的內容:\n");
  printf("姓名: %s\n", score2.name);
  printf("國文: %d\n", score2.chinese);
  printf("英文: %d\n", score2.english);
  printf("數學: %d\n", score2.math);
  printf("自然: %d\n", score2.nature);   
  return 0;
  }

將此程式貼到線上編譯器 onlinGDB 執行結果如下 : 

結構score1的內容:
姓名: Tony
國文: 90
英文: 87
數學: 77
自然: 92
結構score2的內容:
姓名: Amy
國文: 98
英文: 91
數學: 56
自然: 65


3. 結構陣列 : 

陣列的元素為結構變數時, 此陣列變數稱為結構陣列, 其元素為在記憶體連續空間中的結構變數資料, 這與宣告一些相同定義之結構變數不同 (可能會放在不連續空間).  

結構陣列變數宣告方式如下 : 

struct 結構名稱 結構陣列名稱[長度];     

在此之前要先用 struct 關鍵字宣告一個結構, 例如 :

struct score {  //定義結構
  char name[10];
  int chinese;
  int english;
  int math;
  int nature;
  }; 

然後宣告含有兩個元素的結構陣列變數 scores[] : 

struct score scores[2];

注意, 給每一個元素賦值時不可以用 {}  指派給元素 : 

scores[0]={"Tony", 90, 87, 77, 92};
scores[1]={"Amy", 98, 91, 56, 65}; 

這種包含字串地內容要在宣告時同時賦值 :

struct score scores[2]={{"Tony", 90, 87, 77, 92}, {"Amy", 98, 91, 56, 65}}

完整範例程式如下 : 

/* 建立結構陣列和顯示其內容 : test02.c */
#include <stdio.h>
#include <string.h>

struct score {  //定義結構
  char name[10];
  int chinese;
  int english;
  int math;
  int nature;
  };

int main() {
  /* 宣告結構陣列變數scores並賦值 */
  struct score scores[2];   //宣告結構陣列變數
  strcpy(scores[0].name, "Tony");    //字串要用 strcpy() 賦值
  scores[0].chinese=90;
  scores[0].english=87;
  scores[0].math=77;
  scores[0].nature=92;
  strcpy(scores[0].name, "Amy");    //字串要用 strcpy() 賦值
  scores[1].chinese=98;
  scores[1].english=91;
  scores[1].math=56;
  scores[1].nature=65; 
  /* 顯示結構變數scores[0]資料 */
  printf("結構score[0]的內容:\n");
  printf("姓名: %s\n", scores[0].name);
  printf("國文: %d\n", scores[0].chinese);
  printf("英文: %d\n", scores[0].english);
  printf("數學: %d\n", scores[0].math);
  printf("自然: %d\n", scores[0].nature);   
  /* 顯示結構變數scores[1]資料 */
  printf("結構score[1]的內容:\n");
  printf("姓名: %s\n", scores[1].name);
  printf("國文: %d\n", scores[1].chinese);
  printf("英文: %d\n", scores[1].english);
  printf("數學: %d\n", scores[1].math);
  printf("自然: %d\n", scores[1].nature);  
  return 0;
  }

使用線上編譯器 onlinGDB 執行結果如下 :  

結構score[0]的內容:
姓名: Amy
國文: 90
英文: 87
數學: 77
自然: 92
結構score[1]的內容:
姓名: 
國文: 98
英文: 91
數學: 56
自然: 65

既然是陣列, 可以用迴圈讓程式更簡潔 : 

/* 建立結構陣列和顯示其內容 : test03.c */
#include <stdio.h>
#include <string.h>

struct score {  //定義結構
  char name[10];
  int chinese;
  int english;
  int math;
  int nature;
  };

int main() {
  /* 宣告結構陣列變數scores並賦值 */
  struct score scores[2];   //宣告結構陣列變數
  strcpy(scores[0].name, "Tony");    //字串要用 strcpy() 賦值
  scores[0].chinese=90;
  scores[0].english=87;
  scores[0].math=77;
  scores[0].nature=92;
  strcpy(scores[0].name, "Amy");    //字串要用 strcpy() 賦值
  scores[1].chinese=98;
  scores[1].english=91;
  scores[1].math=56;
  scores[1].nature=65; 
  /* 顯示結構變數資料 */
  int i;
  for (i=0; i<2; i++) {
    printf("結構score[%d]的內容:\n", i);
    printf("姓名: %s\n", scores[i].name);
    printf("國文: %d\n", scores[i].chinese);
    printf("英文: %d\n", scores[i].english);
    printf("數學: %d\n", scores[i].math);
    printf("自然: %d\n", scores[i].nature);   
    }  
  return 0;
  }


4. 結構指標 : 

指標可以指向任何類型的資料, 指向結構開始位址的指標稱為結構指標, 只要在宣告結構變數時, 在變數名稱前面加個星號, 此指標所存之位址即指向一個結構. 

結構指標語法如下 :

struct *結構指標名稱;    

首先宣告一個結構 score :

struct score {  //定義結構
  char name[10];
  int chinese;
  int english;
  int math;
  int nature;
  }; 

接著宣告一個結構變數 score1 :

struct score score1={"Tony", 90, 87, 77, 92};

然後宣告一個結構指標 ptr, 並將結構 score1 的位址指派給 *ptr :

struct score *ptr=&score1;

這樣結構指標 ptr 就指向結構 score1 了. 

存取結構指標所指向的內容有兩種做法 :
  • 使用指標所指內容搭配點運算子 : (*ptr).欄位名稱
    由於存取欄位的小數點運算子比指標運算子優先權高, 因此要先用括號將指標內容括起來, 例如要用 (*ptr).name 而非 *ptr.name (這會編譯錯誤).
  • 使用指標名稱搭配 -> 運算子 : ptr->欄位名稱
    注意, 使用 -> 時要用指標名稱, 不是所指向的內容 (*ptr).
下面範例指標內容搭配點運算子來存取結構之欄位 :

/* 建立結構陣列和顯示其內容 : test04.c */
#include <stdio.h>

struct score {  //定義結構
  char name[10];
  int chinese;
  int english;
  int math;
  int nature;
  };

int main() {
  /* 宣告結構變數score1並賦值 */
  struct score score1={"Tony", 90, 87, 77, 92}; 
  struct score *ptr=&score1;
  /* 顯示結構變數score1資料 */
  printf("結構score1的內容:\n");
  printf("姓名: %s\n", score1.name);
  printf("國文: %d\n", score1.chinese);
  printf("英文: %d\n", score1.english);
  printf("數學: %d\n", score1.math);
  printf("自然: %d\n", score1.nature); 
  /* 使用指標所指內容(*ptr)搭配點運算子來顯示結構資料 */
  printf("結構指標*ptr的內容:\n");  
  printf("姓名: %s\n", (*ptr).name);
  printf("國文: %d\n", (*ptr).chinese);
  printf("英文: %d\n", (*ptr).english);
  printf("數學: %d\n", (*ptr).math);
  printf("自然: %d\n", (*ptr).nature);      
  return 0; 
  }

此例使用指標內容 (*ptr) 搭配點運算子來存取結構欄位. 

用線上編譯器 onlinGDB 執行結果如下 :

結構score1的內容:
姓名: Tony
國文: 90
英文: 87
數學: 77
自然: 92
結構指標*ptr的內容:
姓名: Tony
國文: 90
英文: 87
數學: 77
自然: 92

可見由於 ptr 指向 score1, 因此兩個內容完全一樣, 更改 (*ptr) 的任一欄位同時也會改到 score1 的欄位內容. 

下面範例指標名稱搭配 -> 運算子來存取結構之欄位 :

/* 建立結構陣列和顯示其內容 : test05.c */
#include <stdio.h>

struct score {  //定義結構
  char name[10];
  int chinese;
  int english;
  int math;
  int nature;
  };

int main() {
  /* 宣告結構變數score1並賦值 */
  struct score score1={"Tony", 90, 87, 77, 92}; 
  struct score *ptr=&score1;
  /* 顯示結構變數score1資料 */
  printf("結構score1的內容:\n");
  printf("姓名: %s\n", score1.name);
  printf("國文: %d\n", score1.chinese);
  printf("英文: %d\n", score1.english);
  printf("數學: %d\n", score1.math);
  printf("自然: %d\n", score1.nature); 
  /* 使用指標名稱搭配 -> 運算子顯示結構資料 */
  printf("結構指標*ptr的內容:\n");  
  printf("姓名: %s\n", ptr->name);
  printf("國文: %d\n", ptr->chinese);
  printf("英文: %d\n", ptr->english);
  printf("數學: %d\n", ptr->math);
  printf("自然: %d\n", ptr->nature);       
  return 0;
  }

執行結果與前面範例一樣. 


5. 巢狀結構 : 

結構的成員是另一個結構稱為巢狀結構, 語法如下 : 

struct 結構名稱1 {   
    資料類型 欄位名稱1;   
    資料類型 欄位名稱2;   
    ....   
    };   
struc 結構名稱2 {   
    資料類型 欄位名稱1;   
    struct 結構名稱1 結構變數名稱;   
    ....   
    }; 

例如 : 

struct name {  //定義結構 name
  char first_name[10];
  char last_name[10];
  };

struct score {  //定義結構 score
  struct name student_name;    // 此欄位為另一個結構 name
  int chinese;
  int english;
  int math;
  int nature;
  };

此處定義了兩個結構 name 與 score, 其中 score 的第一個成員是 name 結構, 形成巢狀結構. 接著宣告兩個 score 結構變數 score1 與 score2 :

struct score score1={{"Tony", "Huang"}, 90, 87, 77, 92};
struct score score2={{"Amy", "Chen"}, 98, 91, 56, 65); 

因為第一個成員是另一個結構 name, 因此要用 {} 賦值. 存取內層結構 name 的成員時必須使用兩層的點運算子, 例如 :

score1.student_name.first_name  
score1.student_name.last_name 
score2.student_name.first_name  
score2.student_name.last_name 

完整範例如下 :

/* 建立巢狀結構和顯示其內容 : test06.c */
#include <stdio.h>

struct name {  //定義結構 name
  char first_name[10];
  char last_name[10];
  };

struct score {  //定義結構 score
  struct name student_name;  
  int chinese;
  int english;
  int math;
  int nature;
  };

int main() {
  /* 宣告結構變數score1並賦值 */
  struct score score1={{"Tony", "Huang"}, 90, 87, 77, 92};
  struct score score2={{"Amy", "Chen"}, 98, 91, 56, 65}; 
  /* 顯示結構變數score1資料 */
  printf("結構score1的內容:\n");
  printf("姓名: %s %s\n", score1.student_name.first_name, score1.student_name.last_name);
  printf("國文: %d\n", score1.chinese);
  printf("英文: %d\n", score1.english);
  printf("數學: %d\n", score1.math);
  printf("自然: %d\n", score1.nature); 
  /* 顯示結構變數score1資料 */
  printf("結構score2的內容:\n");
  printf("姓名: %s %s\n", score2.student_name.first_name, score1.student_name.last_name);
  printf("國文: %d\n", score2.chinese);
  printf("英文: %d\n", score2.english);
  printf("數學: %d\n", score2.math);
  printf("自然: %d\n", score2.nature);   
  return 0;
  }

使用線上編譯器 onlinGDB 執行結果如下 :  

結構score1的內容:
姓名: Tony Huang
國文: 90
英文: 87
數學: 77
自然: 92
結構score2的內容:
姓名: Amy Huang
國文: 98
英文: 91
數學: 56
自然: 65


6. 將結構傳入函式 : 

結構作為一種自訂的資料型態, 當然也可以當引數傳給函式, 但要注意, 直接將結構傳入函式中時使用的是傳值呼叫, 亦即函式接收到的參數只是一個副本, 在函式內對此副本的存取不會影響到外部的結構內容, 例如 :

/* 將結構傳入函式和顯示其內容 : test07.c */
#include <stdio.h>
#include <string.h>

struct score {  //定義結構
  char name[10];
  int chinese;
  int english;
  int math;
  int nature;
  };

void set_score(struct score s);     // 函式原型宣告必須在結構定義之後

int main() {
  /* 宣告結構變數score1並賦值 */
  struct score score1={"Tony", 90, 87, 77, 92};
  set_score(score1);   // 這是傳值呼叫
  /* 顯示函式外結構變數score1資料 */
  printf("函式外結構score1的內容:\n");
  printf("姓名: %s\n", score1.name);
  printf("國文: %d\n", score1.chinese);
  printf("英文: %d\n", score1.english);
  printf("數學: %d\n", score1.math);
  printf("自然: %d\n", score1.nature);   
  return 0;
  }

void set_score(struct score s) {
  /* 設定結構變數s資料 */
  strcpy(s.name, "Amy");
  s.chinese=98;
  s.english=91;
  s.math=56;
  s.nature=65;
  /* 顯示函式內結構變數s資料 */
  printf("函式內結構變數s的內容:\n");
  printf("姓名: %s\n", s.name);
  printf("國文: %d\n", s.chinese);
  printf("英文: %d\n", s.english);
  printf("數學: %d\n", s.math);
  printf("自然: %d\n", s.nature); 
  }

此例宣告了一個結構變數 score1, 然後將其作為引數傳入函式 set_score() 中, 由於直接將結構傳入函式為傳值呼叫, 傳入之參數為結構之副本, 故於函式內設定該結構副本之欄位對函式外部的結構毫無影響.

使用線上編譯器 onlinGDB 執行結果如下 : 

函式內結構變數s的內容:
姓名: Amy
國文: 98
英文: 91
數學: 56
自然: 65
函式外結構score1的內容:
姓名: Tony
國文: 90
英文: 87
數學: 77
自然: 92

可見函式內的結構雖然被改成 Amy 的成績, 但函式外的結構仍然是 Tony 的成績. 如果要在函式內更改函式外的結構變數內容, 必須使用結構指標, 例如 :

/* 將結構指標傳入函式和顯示其內容 : test08.c */
#include <stdio.h>
#include <string.h>

struct score {  //定義結構
  char name[10];
  int chinese;
  int english;
  int math;
  int nature;
  };

void set_score(struct score *ptr);

int main() {
  /* 宣告結構變數score1並賦值 */
  struct score score1={"Tony", 90, 87, 77, 92};
  struct score *ptr;   // 宣告結構指標變數
  ptr=&score1;         // 取得score1位址指定給指標ptr
  set_score(ptr);       // 這是傳址呼叫
  /* 顯示函式外結構變數score1資料 */
  printf("函式外結構score1的內容:\n");
  printf("姓名: %s\n", score1.name);
  printf("國文: %d\n", score1.chinese);
  printf("英文: %d\n", score1.english);
  printf("數學: %d\n", score1.math);
  printf("自然: %d\n", score1.nature);   
  return 0;
  }

void set_score(struct score *ptr) {
  /* 設定結構變數s資料 */
  strcpy((*ptr).name, "Amy");
  (*ptr).chinese=98;
  (*ptr).english=91;
  (*ptr).math=56;
  (*ptr).nature=65;
  /* 顯示函式內結構變數s資料 */
  printf("函式內結構變數s的內容:\n");
  printf("姓名: %s\n", (*ptr).name);
  printf("國文: %d\n", (*ptr).chinese);
  printf("英文: %d\n", (*ptr).english);
  printf("數學: %d\n", (*ptr).math);
  printf("自然: %d\n", (*ptr).nature); 
  }

用線上編譯器 onlinGDB 執行結果如下 : 

函式內結構變數s的內容:
姓名: Amy
國文: 98
英文: 91
數學: 56
自然: 65
函式外結構score1的內容:
姓名: Amy
國文: 98
英文: 91
數學: 56
自然: 65

可見由於傳入函式的引數是結構指標, 因此在函式內更改結構內容也就是更改函式外結構變數 score1 的內容, 所以列印的結果完全相同. 如上所述, 也可以使用結構專用的 -> 運算子來存取結構成員, 例如以 ptr->name 取代 (*ptr).name : 

/* 將結構指標傳入函式和顯示其內容 : test09.c */
#include <stdio.h>
#include <string.h>

struct score {  //定義結構
  char name[10];
  int chinese;
  int english;
  int math;
  int nature;
  };

void set_score(struct score *ptr);

int main() {
  /* 宣告結構變數score1並賦值 */
  struct score score1={"Tony", 90, 87, 77, 92};
  struct score *ptr;   // 宣告結構指標變數
  ptr=&score1;         // 取得score1位址指定給指標ptr
  set_score(ptr);      // 這是傳址呼叫
  /* 顯示函式外結構變數score1資料 */
  printf("函式外結構score1的內容:\n");
  printf("姓名: %s\n", score1.name);
  printf("國文: %d\n", score1.chinese);
  printf("英文: %d\n", score1.english);
  printf("數學: %d\n", score1.math);
  printf("自然: %d\n", score1.nature); 
  }

void set_score(struct score *ptr) {
  /* 設定結構變數s資料 */
  strcpy(ptr->name, "Amy");
  ptr->chinese=98;
  ptr->english=91;
  ptr->math=56;
  ptr->nature=65;
  /* 顯示函式內結構變數s資料 */
  printf("函式內結構變數s的內容:\n");
  printf("姓名: %s\n", ptr->name);
  printf("國文: %d\n", ptr->chinese);
  printf("英文: %d\n", ptr->english);
  printf("數學: %d\n", ptr->math);
  printf("自然: %d\n", ptr->nature); 
  }

結果與上面用 (*ptr). 作法結果相同.


7. 用 typedef 定義新資料型態 :    

型態定義關鍵字 typedef 用來為既有之資料型態取別名, 語法如下 :

typedef 原資料型態名稱 新資料型態名稱 

這經常被用在結構這種自訂資料型態上, 可使結構變數之宣告變得較簡短, 不必每次宣告一個結構變數時都要冠上 struct 關鍵字, 例如上面範例中所定義的結構 :

struct score {  //定義結構
  char name[10];
  int chinese;
  int english;
  int math;
  int nature;
  };

當宣告結構變數時必須使用 struct 關鍵字 :

struct score score1, score2;

如果用 typedef 為此結構取一個型態別名 :

typedef struct score myscore;   // 為結構 score 取一個型態別名 myscore

則宣告 score 結構變數時便可直接使用 myscore 型態 :

myscore score1, score2;  

測試範例如下 : 

/* 用typedef為結構取一個別名 : test10.c */
#include <stdio.h>

struct score {  //定義結構
  char name[10];
  int chinese;
  int english;
  int math;
  int nature;
  };

int main() {
  typedef struct score myscore;  //用typedef為結構score取別名myscore
  /* 宣告結構變數score1並賦值 */
  myscore score1={"Tony", 90, 87, 77, 92}; //用myscore宣告結構變數
  struct score score2={"Amy", 98, 91, 56, 65};  //用struct score宣告結構變數
  /* 顯示函式外結構變數score1資料 */
  printf("結構score1的內容:\n");
  printf("姓名: %s\n", score1.name);
  printf("國文: %d\n", score1.chinese);
  printf("英文: %d\n", score1.english);
  printf("數學: %d\n", score1.math);
  printf("自然: %d\n", score1.nature); 
  /* 顯示函式外結構變數score2資料 */
  printf("結構score2的內容:\n");
  printf("姓名: %s\n", score2.name);
  printf("國文: %d\n", score2.chinese);
  printf("英文: %d\n", score2.english);
  printf("數學: %d\n", score2.math);
  printf("自然: %d\n", score2.nature);  
  }

此例用 typedef 為結構 socre 取了一個型態別名 myscore, 然後分別用 myscore 與原始的 struct score 方式宣告了兩個結構變數 score1 與 score2, 顯然用新型態 myscore 較簡短, 以線上編譯器 onlinGDB 執行結果如下 : 

結構score1的內容:
姓名: Tony
國文: 90
英文: 87
數學: 77
自然: 92
結構score2的內容:
姓名: Amy
國文: 98
英文: 91
數學: 56
自然: 65

也可以在定義結構時就用 typedef 為結構取一個型態別名, 語法如下 : 

typedef struct 結構名稱 {   
    資料類型 欄位名稱1;   
    資料類型 欄位名稱2;   
    ....   
    } 新型態名稱;    

例如 : 

typedef struct score {  //定義結構並取一個新型態名稱 myscore
  char name[10];
  int chinese;
  int english;
  int math;
  int nature;
  } myscore;

這樣既可以用原始的 struct score 來宣告結構變數, 也可以用 myscore 來宣告 :

struct score score1;
myscore score2; 

可將上面範例修改為 :

/* 用typedef為結構取一個別名 : test11.c */
#include <stdio.h>

typedef struct score {  //定義結構並取一個新型態名稱
  char name[10];
  int chinese;
  int english;
  int math;
  int nature;
  } myscore;

int main() {
  /* 宣告結構變數score1並賦值 */
  myscore score1={"Tony", 90, 87, 77, 92}; //用myscore宣告結構變數
  struct score score2={"Amy", 98, 91, 56, 65};  //用struct score宣告結構變數
  /* 顯示函式外結構變數score1資料 */
  printf("結構score1的內容:\n");
  printf("姓名: %s\n", score1.name);
  printf("國文: %d\n", score1.chinese);
  printf("英文: %d\n", score1.english);
  printf("數學: %d\n", score1.math);
  printf("自然: %d\n", score1.nature); 
  /* 顯示函式外結構變數score2資料 */
  printf("結構score2的內容:\n");
  printf("姓名: %s\n", score2.name);
  printf("國文: %d\n", score2.chinese);
  printf("英文: %d\n", score2.english);
  printf("數學: %d\n", score2.math);
  printf("自然: %d\n", score2.nature);  
  }
 
執行節果與上一個範例相同.

事實上 typedef 可以用來為任何資料型態取別名, 例如為 int 型態取 integer 別名 : 

typedef int integer;
integer year=2023;

或為 char* 指標取 string 別名 :

typedef char* string; 
string="Hello World!" 

注意, 字串指標要用 char* 而非 *char. 

完整範例如下 : 

/* 用typedef為int與*char取一個別名 : test13.c */
#include <stdio.h>

int main() {
  typedef int integer;       // 為int型別取別名integer
  integer year=2023;         // 用integer宣告整數變數
  printf("%d\n", year);      
  typedef char* string;      // 為*char型別取別名string
  string str="Hello World!"; // 用string宣告字串變數
  printf("%s\n", str);
  return 0;
  }

執行結果如下 : 

2023
Hello World!