# 第十二節:變量的定義和賦值
## 【12.1 學習 C 語言的建議和方法】
先提一些學 C 語言的建議和方法,幫大家刪繁就簡,去掉一些初學者常見的思想包袱。現階段我們的學習是使用單片機,把單片機當做一個成品,把單片機當做一個忠誠的士兵,學習 C 語言就是學習如何使用單片機,如何命令單片機,如何讓單片機聽懂我們的話并且聽我們指揮。單片機內部太細節的構造原理暫時不用過多去關注,只要知道跟我們使用相關的幾個特征就可以,這樣初學者的學習包袱就沒有那么重,就可以把重點放在使用上的,而不是好奇于根本原理的死磕到底。學 C 語言跟學習英語的性質是一樣的,都是在學習一門外語,只是 C 語言比英語的語法要簡單很多,非常容易上手,詞匯量也沒有英語那么多,C 語言常用單詞才幾十個而已。學習任何一門語言的秘訣在于練習,學習 C 語言的秘訣是多在單片機上練習編程。本教程后面幾乎每個章節都有例程,這個例程很重要,初學者即使看懂了,我也強烈建議要把 “C 語言學習區域” 的那部分代碼親自上機敲鍵盤練習一遍,并且看看實驗現象是否如你所愿。
## 【12.2 變量定義和賦值的感性認識】
這些年我用過很多單片機,比如 51,PIC,LPC17 系列,STM8,STM32 等單片機。盡管各類單片機有一些差異,但是在選單片機時有 3 個參數我們一定會關注的,它們分別是:工作頻率,數據存儲器 RAM,程序存儲器 ROM。工作頻率跟晶振和倍頻有關,決定了每條指令所要損耗的時間,從而決定了運算速度。RAM 跟代碼里所定義變量的數量有關。ROM 跟程序代碼量的大小有關。程序是什么?程序就是由對象和行為兩者構成的。對象就是變量,就是變量的定義,就是 RAM,RAM 的大小決定了一個程序允許的對象數量。行為就是賦值,判斷,跳轉,運算等語法,就是 ROM,ROM 的大小決定了一個程序允許的行為程度。本節的標題是 “變量的定義和賦值”,其中 “定義” 就是對象,“賦值” 就是行為。
## 【12.3 變量的定義】
變量的定義。一個程序最大允許有多少個對象,是由 RAM 的字節數決定的 (字節是一種單位,后面章節會講到)。本教程的編譯環境是以 AT89C52 芯片為準,AT89C52 這個單片機有 256 個字節的 RAM,但是并不意味著程序就一定要全部占用這些 RAM。程序需要占用多少 RAM,完全是根據程序的實際情況來決定,需要多少就申請多少。這里的 “對象” 就是變量,這里的 “申請” 就是變量的定義。
定義變量的關鍵字。常用有 3 種容量的變量,每種變量的取值范圍不一樣。第一種是 `unsigned char` 變量,取值范圍從 0 到 255,占用 RAM 一個字節,比喻成一房一廳。第二種是`unsigned int` 變量,取值范圍從 0 到 65535,占用 RAM 兩個字節,比喻成兩房一廳。第三種是 `unsigned long` 變量,取值范圍從 0 到 4294967295,占用 RAM 四個字節,比喻成四房一廳。`unsigned char`, `unsigned int` 和 `unsigned long` 都是定義變量的關鍵字,所謂關鍵字也可以看成是某門外語的單詞,需要大家記憶的,當然不用死記硬背,只要多上機練習就自然熟記于心,出口成章。多說一句,上述的變量范圍是針對本教程所用的單片機,當針對不同的單片機時上述變量的范圍可能會有一些小差異,比如在 STM32 單片機中,`unsigned int` 的字節數就不是兩個字節,而是四個字節,這些都是由所選的編譯器決定的,大家暫時有個初步了解就可以。
定義變量的語法格式。定義變量的語法格式由 3 部分組成:關鍵字、變量名、分號。比如: ` unsigned char a; `
其中 unsigned char 就是關鍵字,a 就是變量名,分號 `;` 就是一條語句的結束符號。
變量名的命名規則。變量名的第一個字符不能是數字,必須是字母或者下劃線,字母或者下劃線后面可以帶數字,一個變量名之間的字符不能帶空格,兩個獨立變量名之間也不能用空格隔開(但是兩個獨立變量名之間可以用逗號隔開)。變量名不能跟編譯器已征用的關鍵字重名,不能跟函數名重名,這個現象跟古代要求臣民避諱皇帝的名字有點像。哪些名字是合法的,哪些名字是不合法的?現在舉一些例子說明:
```c
unsigned char 3a; // 不合法,第一個字符不能是數字。
unsigned char char; // 不合法,char 是編譯器已征用的關鍵字。
unsigned char a b; // 不合法,ab 是一個變量名,a 與 b 的中間不能有空格。
unsigned char a,b; // 合法,a 和 b 分別是一個獨立的變量名,a 與 b 的中間可以用逗號隔開。
unsigned char a; // 合法。
unsigned char abc; // 合法。
unsigned char _ab; // 合法。
unsigned char _3ab; // 合法。
unsigned char a123; // 合法。
unsigned char a12ced; // 合法。
```
定義變量與 RAM 的內在關系。當我們定義一個變量時,相當于向單片機申請了一個 RAM 空間。C 編譯器會自動為這個變量名分配一個 RAM 空間,每個字節的 RAM 空間都有一個固定唯一的地址。把每個字節的 RAM 空間比喻成房間,這個地址就是房號。地址是純數字編號,不利于我們記憶,C 語言編譯器為了降低我們的工作難度,不用我們記每個變量的地址,只需要記住這個變量的名稱就可以了。操作某個變量名,就相當于操作某個對應地址的 RAM 空間。變量名與對應地址 RAM 空間的映射關系是 C 編譯器暗中悄悄幫我們分配好的。比如:
```c
unsigned char a; //a 占用一個字節的 RAM 空間,這個空間的地址由 C 編譯自動分配。
unsigned char b; //b 占用一個字節的 RAM 空間,這個空間的地址由 C 編譯自動分配。
unsigned char c; //c 占用一個字節的 RAM 空間,這個空間的地址由 C 編譯自動分配。
```
上述 a,b,c 三個變量各自占用一個字節的 RAM 空間,同時被 C 編譯器分配了 3 個不同的 RAM 空間地址。
變量定義的初始化。變量定義之后,等于被 C 編譯器分配了一個 RAM 空間,那么這個空間里面存儲的數據是什么?如果沒有刻意給它初始化,RAM 空間里面存儲的數據是不太確定的,是默認的。有些場合,需要在給變量分配 RAM 空間時就給它一個固定的初始值,這就是變量定義的初始化。變量初始化的語法格式由 3 部分組成:關鍵字,變量名賦值,分號。比如:
```c
unsigned char a=9;
```
其中 unsigned char 就是關鍵字。
其中 a = 9 就是變量名賦值。a 從被 C 編譯器分配 RAM 空間那一刻起,就默認是預存了一個 9 的數據。
分號 `;` 就是一條語句的結束符號。
## 【12.4 變量的賦值】
賦值語句的含義。把右邊對象的內容復制一份給左邊對象。賦值語句有一個很重要的特性,就是覆蓋性,左邊對象原來的內容會被右邊對象復制過來的新內容所覆蓋。比如,左邊對象是變量 a,假設原來 a 里面存的數據是 3,右邊對象是數據 6,執行賦值語句后,會把右邊的 6 賦值給了對象 a,那么 a 原來的數據 3 就被覆蓋丟失了,變成了 6。
賦值語句的格式。賦值語句的語法格式由 4 部分組成:左邊對象,關鍵字,右邊對象,分號。比如:
`a = b;`
其中 a 就是左邊對象。
其中 `=` 就是關鍵字。寫法跟我們平時用的等于號是一樣,但是在 C 語言里不是等于的意思,而是代表賦值的意思,它是代表中文含義的 “給”,而不是用于判斷的 “等于”,跟等于號是兩碼事(C 語言的等于號是 “==”,這個后面章節會講到)。
其中 b 就是右邊對象。
其中分號 `;` 代表一條語句的結束符。
賦值語句與 ROM 的關系。賦值語句是行為的一種,所以編譯會把賦值這個行為翻譯成對應的指令,這些指令在下載程序時最終也是以數據的形式存儲在 ROM 里,指令也是以字節為單位 (字節是一種單位,后面章節會講到)。本教程的編譯環境是以 AT89C52 芯片為準,AT89C52 這個單片機有 8K 的 ROM 容量,也就是有 8192 個字節的 ROM(8 乘以 1024 等于 8192),但是并不意味著程序就一定要全部占用這些 ROM。程序需要占用多少 ROM,完全是根據程序的行為程度決定,也就是通常所說的你的程序容量有多大,有多少行代碼。多說一句,在單片機或者我們常說的計算機領域里,存儲容量是以字節為單位,而每 K 之間的進制不是我們日常所用的 1000,而是 1024,所以剛才所說的 8K 不是 8000,而是 8192,這個是初學者很容易迷惑的地方。剛才提到,賦值語句是行為,凡是程序的行為指令都存儲在單片機的 ROM 區。C 編譯器會把一條賦值語句翻譯成對應的一條或者幾條機器碼,機器碼指令也是以字節為單位的。下載程序的時候,這些機器碼就會被下載進單片機的 ROM 區。比如以下這行賦值語句:
```c
unsigned char a;
unsigned char b = 3;
a = b;
```
經過 C 編譯器編譯后會生成以字節為單位的機器碼。這些機器碼記錄著這些信息:變量 a 的 RAM 地址,變量 b 的 RAM 地址和初始化時的預存數據 3,以及把 b 變量的內容賦值給 a 變量的這個行為。所有這些信息,不管是 “數據” 還是 “行為”,本質都是以 “數據”(或稱數字,數碼都可以)的形式存儲記錄的,單位是字節。
## 【12.5 例程的分析和練習】
接下來練習一個程序實例。直接復制前面章節中第十一節的模板程序,練習代碼時只需要更改 “C 語言學習區域” 的代碼就可以了,其它部分的代碼不要動。編譯后,把程序下載進帶串口的 51 學習板,通過電腦端的串口助手軟件就可以觀察到不同的變量數值,詳細方法請看第十一節內容。本章節在 “C 語言學習區域” 練習的代碼如下:
```c
/*---C語言學習區域的開始。-----------------------------------------------*/
void main() //主函數
{
unsigned char a; //定義的變量a被分配了一個字節的RAM空間,保存的數據是不確定的默認值。
unsigned char b; //定義的變量b被分配了一個字節的RAM空間,保存的數據是不確定的默認值。
unsigned char c; //定義的變量c被分配了一個字節的RAM空間,保存的數據是不確定的默認值。
unsigned char d=9; //定義的變量d被分配了一個字節的RAM空間,保存的數據被初始化成9.
b=3; //把3賦值給變量b,b由原來不確定的默認數據變成了3。
c=b; //把變量b的內容復制一份賦值給左邊的變量c,c從不確定的默認值變成了3。
View(a); //把第1個數a發送到電腦端的串口助手軟件上觀察。
View(b); //把第2個數b發送到電腦端的串口助手軟件上觀察。
View(c); //把第3個數c發送到電腦端的串口助手軟件上觀察。
View(d); //把第4個數d發送到電腦端的串口助手軟件上觀察。
while(1)
{
}
}
/*---C語言學習區域的結束。-----------------------------------------------*/
```
在電腦串口助手軟件上觀察到的程序執行現象如下:
> 開始...
> 第1個數
> 十進制:255
> 十六進制:FF
> 二進制:11111111
> 第2個數
> 十進制:3
> 十六進制:3
> 二進制:11
> 第3個數
> 十進制:3
> 十六進制:3
> 二進制:11
> 第4個數
> 十進制:9
> 十六進制:9
> 二進制:1001
分析:
第 1 個數 a 居然是 255,這個 255 從哪來?因為 a 我們一直沒有給它初始值,也沒有給它賦值,所以它是不確定的默認值,這個 255 就是所謂的不確定的默認值,是編譯器在定義變量 a 時分配的,帶有不確定的隨機性,不同的編譯器可能分配的默認值都會存在差異。根據我的經驗,unsigned char 類型定義的默認值往往是 0 或者 255(255 是十六進制的 0xff,十六進制的內容后續章節會講到)。
- 首頁
- 第一節:我的價值觀
- 第二節:初學者的疑惑
- 第三節:單片機最重要的一個特性
- 第四節:平臺軟件和編譯器軟件的簡介
- 第五節:用Keil2軟件關閉,新建,打開一個工程的操作流程
- 第六節:把.c源代碼編譯成.hex機器碼的操作流程
- 第七節:本節預留
- 第八節:把.hex機器碼程序燒錄到單片機的操作流程
- 第九節:本節預留
- 第十節:程序從哪里開始,要到哪里去?
- 第十一節:一個在單片機上練習C語言的模板程序
- 第十二節:變量的定義和賦值
- 【TODO】第十三節:賦值語句的覆蓋性
- 【TODO】第十四節:二進制與字節單位,以及常用三種變量的取值范圍
- 【TODO】第十五節:二進制與十六進制
- 【TODO】第十六節:十進制與十六進制
- 【TODO】第十七節:加法運算的5種常用組合
- 【TODO】第十八節:連加、自加、自加簡寫、自加1
- 【TODO】第十九節:加法運算的溢出
- 【TODO】第二十節:隱藏中間變量為何物?
- 【TODO】第二十一節:減法運算的5種常用組合。
- 【TODO】第二十二節:連減、自減、自減簡寫、自減1
- 【TODO】第二十三節:減法溢出與假想借位
- 【TODO】第二十四節:借用unsigned long類型的中間變量可以減少溢出現象
- 【TODO】第二十五節:乘法運算中的5種常用組合
- 【TODO】第二十六節:連乘、自乘、自乘簡寫,溢出
- 【TODO】第二十七節:整除求商
- 【TODO】第二十八節:整除求余
- 【TODO】第二十九節:“先余后商”和“先商后余”提取數據某位,哪家強?
- 【TODO】第三十節:邏輯運算符的“與”運算
- 【TODO】第三十一節:邏輯運算符的“或”運算
- 【TODO】第三十二節:邏輯運算符的“異或”運算
- 【TODO】第三十三節:邏輯運算符的“按位取反”和“非”運算
- 【TODO】第三十四節:移位運算的左移
- 【TODO】第三十五節:移位運算的右移
- 【TODO】第三十六節:括號的強制功能---改變運算優先級
- 【TODO】第三十七節:單字節變量賦值給多字節變量的疑惑
- 【TODO】第三十八節:第二種解決“運算過程中意外溢出”的便捷方法
- 【TODO】第三十九節:if判斷語句以及常量變量的真假判斷
- 【TODO】第四十節:關系符的等于“==”和不等于“!=”
- 【TODO】第四十一節:關系符的大于“>”和大于等于“>=”
- 【TODO】第四十二節:關系符的小于“<”和小于等于“<=”
- 【TODO】第四十三節:關系符中的關系符:與“&&”,或“||”
- 【TODO】第四十四節:小括號改變判斷優先級
- 【TODO】第四十五節: 組合判斷if...else if...else
- 【TODO】第四十六節: 一維數組
- 【TODO】第四十七節: 二維數組
- 【TODO】第四十八節: while循環語句
- 【TODO】第四十九節: 循環語句do while和for
- 【TODO】第五十節: 循環體內的continue和break語句
- 【TODO】第五十一節: for和while的循環嵌套
- 【TODO】第五十二節: 支撐程序框架的switch語句
- 【TODO】第五十三節: 使用函數的三要素和執行順序
- 【TODO】第五十四節: 從全局變量和局部變量中感悟“棧”為何物
- 【TODO】第五十五節: 函數的作用和四種常見書寫類型
- 【TODO】第五十六節: return在函數中的作用以及四個容易被忽略的功能
- 【TODO】第五十七節: static的重要作用
- 【TODO】第五十八節: const(./book/或code)在定義數據時的作用
- 【TODO】第五十九節: 全局“一鍵替換”功能的#define
- 【TODO】第六十節: 指針在變量(./book/或常量)中的基礎知識
- 【TODO】第六十一節: 指針的中轉站作用,地址自加法,地址偏移法
- 【TODO】第六十二節: 指針,大小端,化整為零,化零為整
- 【TODO】第六十三節: 指針“化整為零”和“化零為整”的“靈活”應用
- 【TODO】第六十四節: 指針讓函數具備了多個相當于return的輸出口
- 【TODO】第六十五節: 指針作為數組在函數中的入口作用
- 【TODO】第六十六節: 指針作為數組在函數中的出口作用
- 【TODO】第六十七節: 指針作為數組在函數中既“入口”又“出口”的作用
- 【TODO】第六十八節: 為函數接口指針“定向”的const關鍵詞
- 【TODO】第六十九節: 宏函數sizeof(./book/)
- 【TODO】第七十節: “萬能數組”的結構體
- 【TODO】第七十一節: 結構體的內存和賦值
- 【TODO】第七十二節: 結構體的指針
- 【TODO】第七十三節: 結構體數據的傳輸存儲和還原
- 【TODO】第七十四節: 結構體指針在函數接口處的頻繁應用
- 【TODO】第七十五節: 指針的名義(例:一維指針操作二維數組)
- 【TODO】第七十六節: 二維數組的指針
- 【TODO】第七十七節: 指針唯一的“單向輸出”通道return
- 【TODO】第七十八節: typedef和#define和enum
- 【TODO】第七十九節: 各種變量常量的命名規范
- 【TODO】第八十節: 單片機IO口驅動LED
- 【TODO】第八十一節: 時間和速度的起源(指令周期和晶振頻率)
- 【TODO】第八十二節: Delay“阻塞”延時控制LED閃爍
- 【TODO】第八十三節: 累計主循環的“非阻塞”延時控制LED閃爍
- 【TODO】第八十四節: 中斷與中斷函數
- 【TODO】第八十五節: 定時中斷的寄存器配置
- 【TODO】第八十六節: 定時中斷的“非阻塞”延時控制LED閃爍
- 【TODO】第八十七節: 一個定時中斷產生N個軟件定時器
- 【TODO】第八十八節: 兩大核心框架理論(四區一線,switch外加定時中斷)
- 【TODO】第八十九節: 跑馬燈的三種境界
- 【TODO】第九十節: 多任務并行處理兩路跑馬燈
- 【TODO】第九十一節: 蜂鳴器的“非阻塞”驅動
- 【TODO】第九十二節: 獨立按鍵的四大要素(自鎖,消抖,非阻塞,清零式濾波)
- 【TODO】第九十三節: 獨立按鍵鼠標式的單擊與雙擊
- 【TODO】第九十四節: 兩個獨立按鍵構成的組合按鍵
- 【TODO】第九十五節: 兩個獨立按鍵的“電腦鍵盤式”組合按鍵
- 【TODO】第九十六節: 獨立按鍵“一鍵兩用”的短按與長按
- 【TODO】第九十七節: 獨立按鍵按住不松手的連續均勻觸發
- 【TODO】第九十八節: 獨立按鍵按住不松手的“先加速后勻速”的觸發
- 【TODO】第九十九節: “行列掃描式”矩陣按鍵的單個觸發(原始版)
- 【TODO】第一百節: “行列掃描式”矩陣按鍵的單個觸發(優化版)
- 【TODO】第一百零一節: 矩陣按鍵鼠標式的單擊與雙擊
- 【TODO】第一百零二節: 兩個“任意行輸入”矩陣按鍵的“有序”組合觸發
- 【TODO】第一百零三節: 兩個“任意行輸入”矩陣按鍵的“無序”組合觸發
- 【TODO】第一百零四節: 矩陣按鍵“一鍵兩用”的短按與長按
- 【TODO】第一百零五節: 矩陣按鍵按住不松手的連續均勻觸發
- 【TODO】第一百零六節: 矩陣按鍵按住不松手的“先加速后勻速”觸發
- 【TODO】第一百零七節: 開關感應器的識別與軟件濾波
- 【TODO】第一百零八節: 按鍵控制跑馬燈的啟動和暫停和停止
- 【TODO】第一百零九節: 按鍵控制跑馬燈的方向
- 【TODO】第一百一十節: 按鍵控制跑馬燈的速度
- 第一百一十一節: 工業自動化設備的開關信號的運動控制
- 【TODO】第一百一十二節: 數碼管顯示的基礎知識
- 【TODO】第一百一十三節: 動態掃描的數碼管顯示數字
- 【TODO】第一百一十四節: 動態掃描的數碼管顯示小數點
- 【TODO】第一百一十五節: 按鍵控制數碼管的秒表
- 【TODO】第一百一十六節: 按鍵控制數碼管的倒計時
- 【TODO】第一百一十七節: 按鍵切換數碼管窗口來設置參數
- 【TODO】第一百一十八節: 按鍵讓某位數碼管閃爍跳動來設置參數
- 【TODO】第一百一十九節: 一個完整的人機界面的程序框架的脈絡
- 【TODO】第一百二十節: 按鍵切換窗口切換局部來設置參數
- 【TODO】第一百二十一節: 可調參數的數碼管倒計時
- 【TODO】第一百二十二節: 利用定時中斷做的“時分秒”數顯時鐘
- 【TODO】第一百二十三節: 一種能省去一個lock自鎖變量的按鍵驅動程序
- 【TODO】第一百二十四節: 數顯儀表盤顯示“速度、方向、計數器”的跑馬燈
- 【TODO】第一百二十五節: “雙線”的肢體接觸通信
- 【TODO】第一百二十六節: “單線”的肢體接觸通信
- 【TODO】第一百二十七節: 單片機串口接收數據的機制
- 【TODO】第一百二十八節: 接收“固定協議”的串口程序框架
- 【TODO】第一百二十九節: 接收帶“動態密匙”與“累加和”校驗數據的串口程序框架
- 【TODO】第一百三十節: 接收帶“動態密匙”與“異或”校驗數據的串口程序框架
- 【TODO】第一百三十一節: 靈活切換各種不同大小“接收內存”的串口程序框架
- 【TODO】第一百三十二節:“轉發、透傳、多種協議并存”的雙緩存串口程序框架
- 【TODO】第一百三十三節:常用的三種串口發送函數
- 【TODO】第一百三十四節:“應用層半雙工”雙機串口通訊的程序框架