# C語言解釋 - 資料型態 浮點數
## 程式碼
###### tags: `c`, `data type`, `float`
```clike=
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int A=3;
int B=2;
int C;
double D=1.236, E=9.99, F=0;
C=A+B;
printf("A+B=%d\t", C);
printf("A-B=%d, A=%d\t", A-B, A);
printf("A*B=%d\n", A*B);
printf("A/B=%f\n", (float)A/B);
F = (float)A/B;
printf("A/B=%f\n", F);
F=D+E;
printf("D+E=%f\n", F);
F=D-E;
printf("D-E=%f, D=%f\n", F, D);
F=D*E;
printf("D*E=%f\n", F);
char qq = 'C';
printf("%c \n", qq);
printf("dfaklkeasdfa \n");
printf("%s", "adpnklchladqfaKDkadkasKH \n");
return 0;
}
````
## 標準函式庫 standard library
```clike=
#include <stdio.h>
#include <stdlib.h>
```
開頭的`#include` 是前置處理器(preprocessor). 像C語言這種需要編譯的程式語言,從原始程式碼變成執行檔會有以下的流程。
```sequence
程式碼(.c)->物件檔(.obj): 編譯(compile)
物件檔(.obj)->執行檔(.exe): 連結(link)
```
從.c的原始碼經過編譯後會產生.obj檔, obj檔經過連結後,就會變成執行檔.
像`#include`, `#define` 這些都是前置處理器,它是在編譯前就會動作。像是`#include`, 在編譯前就會載入你所指定的函式庫.
載入的如果是標準函式庫,就是用角括號包住,如果自訂的函式庫,就是用雙引號,如
```clike
#include <stdio.h>. // 標準函式庫載入
#include "user_define.h" // 使用者自訂的函式庫,載入的方式不同
```
[cplusplus stdio.h](https://cplusplus.com/reference/cstdio/). 這是標準函式庫 stdio.h 的內容,像後面所用的`printf`就是來自此,除了`printf`外,還有很多很常用的函式,stdio.h它是標準輸出輸入的縮寫,這裡的標準輸入是鍵盤;標準輸出就是螢幕了。
## Main function
```clike=4
int main()
```
`main()` 是很重要的函式名稱,編譯器會以`main()`為主要函式,其餘函式都是附屬在其底下.
```clike=
int main(int argc, char* argv[])
```
如果完整寫的話,main function 應該是寫成上述。我們所有寫的程式都是為了跟其他程式溝通,所以要有能接收外部程式傳來的資料。這些資料我們會稱做參數(argument).
```clike=
./sum.exe 0 1 2 3 4 5
15
```
假設我們有一支以C語言寫成的sum.exe, 它的後面可以帶入多個參數且幫我們求出總合。那 sum.exe 就是我們編譯後的執行檔,後面帶的數字就叫參數(argument). 總共有六個參數,argc就會為6,而0到5這些數字會以字元陣列的方式儲存在argv.
argc(argument count)以及argv(argument vector), 這兩個也是特殊關鍵字,專門替 main function 處理外部參數。
標準的main function,就是上述的兩種寫法。
## 資料型態
[維基百科 資料型態(C語言)](https://zh.wikipedia.org/zh-tw/数据类型_(C语言))
```clike=5
int A=3;
int B=2;
int C;
double D=1.236, E=9.99, F=0;
C=A+B;
printf("A+B=%d\t", C);
printf("A-B=%d, A=%d\t", A-B, A);
printf("A*B=%d\n", A*B);
printf("A/B=%f\n", (float)A/B);
F = (float)A/B;
```
所謂資料型態,就是資料以什麼方式存在記憶體裡,這裡的方式是說資料長度要用多少個位元組(byte)來表示,或是以字元(char)的方式,或是浮點數(float)的方式。
### 整數
`int` 以整數的型式儲存,通常是指四個位元組大小,可以用`sizeof(int)`來確認當下機台的狀況。
我用lldb debug 工具,把實際記憶體的內容印出來,應該會比較清楚什麼是資料,什麼又是位址。
```clike=
(lldb) l 12
12 printf("A+B=%d\t", C);
13 printf("A-B=%d, A=%d\t", A-B, A);
14 printf("A*B=%d\n", A*B);
15
16 printf("A/B=%f\n", (float)A/B);
17
18 F = (float)A/B;
19
20 printf("A/B=%f\n", F);
21
(lldb) frame variable A B C
(int) A = 3
(int) B = 2
(int) C = 5
(lldb) frame variable &A &B &C
(int *) &A = 0x000000016fdfeab8
(int *) &B = 0x000000016fdfeab4
(int *) &C = 0x000000016fdfeab0
(lldb) x/8 0x000000016fdfeab0
0x16fdfeab0: 0x00000005 0x00000002 0x00000003 0x00000000
0x16fdfeac0: 0x6fdfec10 0x00000001 0x0001108c 0x00000001
(lldb)
```
* `l 12` 印出12行後程式碼.
* `frame varible A B C` 印出變數A, B及C. 我事先有設立中斷點在第11行,且執行到11行,所以A, B, C, D, F 應該都已經存入記憶體內。可以看上述的第13到15行,的確是程式裡所寫內容。
* `frame variable &A &B &C`這裡一樣是印出變數,不過我用了`&A`取出變數A實際記憶體位址,結果就印在17到19行。
* `x/8 0x000000016fdfeab0` 我選擇一個eab0位址,連續印八筆資料。可以稍微對照一下各個變數的位址及資料內容。因為儲存一個int需要四個位元組,所以才會看到開頭這麼多零。
### 浮點數
看完整數,我們接著看浮點數。浮點數儲存的方式並非像整數一樣那麼容易暸解,程式裡的浮點數都是以[維基百科 IEEE-754](https://zh.wikipedia.org/zh-tw/IEEE_754)
```clike=
(lldb) po sizeof(double)
8
(lldb) po sizeof(float)
4
(lldb) frame variable D E F
(double) D = 1.236
(double) E = 9.9900000000000002
(double) F = 12.34764
(lldb) frame variable &D &E &F
(double *) &D = 0x000000016fdfeaa8
(double *) &E = 0x000000016fdfeaa0
(double *) &F = 0x000000016fdfea98
(lldb) x/8 0x000000016fdfea98
0x16fdfea98: 0xdebd9019 0x4028b1fd 0x47ae147b 0x4023fae1
0x16fdfeaa8: 0xef9db22d 0x3ff3c6a7 0x00000005 0x00000002
```
這次我們看浮點數是以什麼方式存在記憶體裡。但首先我們先看double, float各別佔多少空間。從第二跟第五行的結果,在這我這台機器,分別是八個位元組及四個位元組,所以最基本的來講單精準度(float)跟雙精準度(double),就是能表達的位元組差一倍,也就是說雙精準度能夠表示更精準。
[IEEE-754 轉換](https://web.archive.org/web/20070417004305/http://babbage.cs.qc.edu/IEEE-754/Decimal.html) 可以試著玩看看從十進制的小數換成浮點數。
從上面的轉換工具,應該可以知道程式是怎麼表達浮點數的。
總共會轉換成三個欄位。
* sign
* exponent
* fraction
它並不是一般我們知道的數學上的小數表示,而是經過轉換的。為何需要轉換?目前電腦表示方式主要還是以二進制為主,以二進制的方式是不能完整表達數學上的小數的,所以才有這個轉換方式產生。
可以試著用上面的轉換工具,跟lldb印出來的值做個比較。
### 字元
```clike=
(lldb) po sizeof(char)
1
(lldb) frame variable qq
(char) qq = 'C'
(lldb) frame variable &qq
(char *) &qq = 0x000000016fdfea97 "C\U00000019\x90\xbd\xde\xfd\xb1(@{\U00000014\xaeG\xe1\xfa#@-\xb2\x9d\xef\xa7\xc6\xf3?\U00000005"
(lldb) x/8 0x000000016fdfea97
0x16fdfea97: 0xbd901943 0x28b1fdde 0xae147b40 0x23fae147
0x16fdfeaa7: 0x9db22d40 0xf3c6a7ef 0x0000053f 0x00000200
```
一樣用lldb看字元的表示方式,用`sizeof(char)`可以知道字元只需一個位元組大小儲存,儲存的位址放在0xea97,對照來看可以查到0x43這個資料,再去對照 [ASIC](https://zh.wikipedia.org/wiki/ASCII),大寫字母`C`, ASIC code就是0x43.