第十二章 主要資料型別
Fundamental Data Types
12.1 數字概述
Numbers in General
以下是使用數字較不易出錯的提醒:- 避免使用「magic number」
- 必要時,可以hard code 0和1(本身有隱喻)
- 遞增、遞減、迴圈初始值
- 除法小心除0
- 讓型別轉換更明顯
- 避免混合型別比較
- 注意編譯器warning
12.2 整數
Integers
除法- 最簡單的方法:重新組識運算式
- 如果你的整數會在數年間穩定成長,就要考慮進來
- 算式最後的結果不是你唯一要擔心的數字
12.3 浮點數
Floating-Point Numbers
精確度到小數點後7位避免數量級差距很大的數字做加減運算
- 排序這些數字,從最小的開始運算,減少誤差
- 相同的浮點數不一定以相同的方式保存在記憶體
替代方案:用範圍的true, false來取代==
//Java Example of a Bad Comparison of Floating-Point Numbers
double nominal = 1.0;
double sum = 0.0;
for ( int i = 0; i < 10; i++ )
um += 0.1;if ( nominal == sum )
System.out.println( "Numbers are the same." );
else
System.out.println( "Numbers are different." ) ;
將這段程式,改成下面的寫法
//Java Example of a Routine to Compare Floating-Point Numbers
double const ACCEPTABLE_DELTA = 0.00001;
boolean Equals( double Term1, double Term2 )
{
if ( Math.abs( Term1 - Term2 ) < ACCEPTABLE_DELTA )
return true;
else
return false;
}
處理四捨五入的錯誤- 使用double
- 使用BCD變數
- 用int來替代float
- 缺點:慢
- 讓全部的運算都乘上相同的倍數,再除回來。
ex: 10.123×1000 = 10123
12.4 字元及字串
Characters and Strings
避免不斷重覆出現的magic characters和strings- 使用字串常數,統一管理
使用全域變數
統一管理,方便翻譯成不同語言
限制記憶體開發環境,獨立字串儲存空間
- 字串可以像陣列一樣索引化,有超出'\0'的問題要小心
開發初期就決定程式支援語系
選擇適合的編碼
- (只用英文)→ASCII→ISO8859→(用英文以外的語系)→Unicode
C語言的字串Strings in C
注意字串指標和字元陣列的差異
小心字串使用等號
- 正確的用法:使用strcmp(), strcpy(), strlen()
等號通常是指某種指標的錯誤
在c語言中看見這樣的陳述式
StringPtr = "Some Text String";
不是將內容複製到StringPtr,而是將指標指在第一個'S'。
- ps = ptrString
ach = arrayChar
- 很容易忘記constant長的字串,要宣告constant+1的字元陣列
解決方式:用字串常數取代字元陣列的用法。多宣告一個byte,避免使用最後一個byte。
用null初始化字元陣列,避免在最後忘記放null
- 靜態陣列宣告,直接放初始值 = { 0 };
動態陣列宣告,用calloc取代malloc
- malloc 不會初始化為0
calloc 會初始化為0
- 用字元陣列存放所有字串變數,編譯器也可以在出錯時給你警告
12.5 布林變數
Boolean Variables
要用錯,很難;善用,程式會變美
簡化複雜判斷如果if (陳述式) 的陳述式是一團泥,陳述式的判斷拆開成有意義的布林變數,可增加程式碼可讀性
(將布林表示式賦序一個變數,使判斷含意變明顯)
//Java Example of Boolean Test in Which the Purpose Is Unclear
if ( ( elementIndex < 0 ) || ( MAX_ELEMENTS < elementIndex ) || ( elementIndex == lastElementIndex ) )
{
...
}
改成
//Java Example of Boolean Test in Which the Purpose Is Clear
finished = ( ( elementIndex < 0 ) || ( MAX_ELEMENTS < elementIndex ) );
repeatedEntry = ( elementIndex == lastElementIndex );
if ( finished || repeatedEntry )
{
...
}
判斷的意思會更容易浮到字面上需要的話,創造自己的布林型別
C++、Java、Visual Basic都有bool的型別,但是像c這種常見的語言卻沒有。
//C Example of Defining the BOOLEAN Type
typedef int BOOLEAN; // define the boolean type
本書原文並無繁簡中的這個例子
//C Example of Defining the BOOLEAN Type Using an Enum
enum Boolean {
True = 1,
False = (!True)
};
12.6 列舉型別
Enumerated Types
是一種允許在物件的類別包含的每個資料成員用英文描述的資料
Public Enum Color
Color_Red
Color_Green
Color_Blue
End Enum
Public Enum Country
Country_China
Country_England
Country_France
Country_Germany
Country_India
Country_Japan
Country_Usa
End Enum
Public Enum Output
Output_Screen
Output_Printer
Output_File
End Enum
增加可讀性
if chosenColor = 1
寫得更有可讀性,可改成
if chosenColor = Color_Red
在此出現只有繁簡中版才有的例子,而原文版沒出現的例子但因為之前VB的例子已具代表性,所以就不在此介紹
增加可靠度
- 編譯器會檢查使用列舉型別的變數,只允許該變數使用該列舉的成員。
- 列舉對應的數字,並不需要管它,在新增移除列舉成員項目時,程式碼只要管「英文」部份的正確性,而所對應的數字會在編譯時重新調整
- 將true/false或1/0命名成英文,成為有意義明顯的變數。warning
列舉的第一項和最後一項,當作迴圈控制變數使用
enmu trafficLightColor{ ColorBegin = 0, Red = 0, Green, Yellow, ColorEnd };
for (int i = ColorBegin; i < ColorEnd; ++i)
//...
列舉、陣列、迴圈互相搭配使用(補充)- VB的索引,是以1開始,在此不適用,C、C++、Java都適用於這種方式。
陣列宣告時,中括號內要填入的數字往往代表的是總數。
在迴圈計數時,計數器的範圍,往往設定成「小於總數」。
在列舉宣告時,如果這個列舉的使用要配合迴圈回陣列,即可使用這種方式。
enmu trafficLightColor{ TC_Red = 0, TC_Green, TC_Yellow, TC_Total };
//用在陣列
int something[TC_Total ];
//用在迴圈
for (int i = 0; i < TC_Total; ++i)
//...
列舉的第一個項目保留為無效- (因為第一個項目可以訂為0)
注意全指定值列舉,在迴圈使用
enum Color
{
Color_InvalidFirst = 0,
Color_Red = 1,
Color_Green = 2,
Color_Blue = 4,
Color_InvalidLast = 8
};
for (int i = Color_InvalidFirst; i < Color_InvalidLast ;++i)
//...
此例的3, 5, 6, 7就是無效值。
如果你的語言不支援列舉
If Your Language Doesn’t Have Enumerated Types
像Java,可以使用類別,或者用全域變數,都可以達到相同的效果// Java Example of Simulating Enumerated Types
// set up Color enumerated type
class Color
{
private Color() {}
public static final Color Red = new Color();
public static final Color Green = new Color();
public static final Color Blue = new Color();
}
// set up Country enumerated type
class Country
{
private Country() {}
public static final Country China = new Country();
public static final Country England = new Country();
public static final Country France = new Country();
public static final Country Germany = new Country();
public static final Country India = new Country();
public static final Country Japan = new Country();
}
// set up Output enumerated type
class Output
{
private Output() {}
public static final Output Screen = new Output();
public static final Output Printer = new Output();
public static final Output File = new Output();
}
12.7 常數
Named Constants
參數化程式,讓軟體更軟。宣告時使用常數
- 任何一種可能變化之事物進行集中控制的技術,是減少維護工作量的好技術
For i = 1 To 12
profit( i ) = revenue( i ) – expense( i )
Next
上面這個例子,12是代表一年的十二個月嗎?如果是,改成下列的寫法是不是比較好?
For i = 1 To NUM_MONTHS_IN_YEAR
profit( i ) = revenue( i ) – expense( i )
Next
那索引用的i 是不是也可以給予更有意義的變數名稱?For month = 1 To NUM_MONTHS_IN_YEAR
profit( month ) = revenue( month ) – expense( month )
Next
嗯!現在看起來,程式似乎是可以了。那,如果把1也替換掉,要怎麼改寫呢?
用列舉的方式將1月到12月的寫法取代掉。
For month = Month_January To Month_December
profit( month ) = revenue( month ) – expense( month )
Next
這樣就變成全英文的程式碼了,可以表達的意思更加明確。
使用適當作用域的變數,來模擬常數- 像Java這種不支援列舉的語言,使用區域作用域→類別作用域→全域作用域的順序來運用常數。
- 不要一邊使用常數變數,一邊使用將數字寫死的程式寫法,要改也不是修改一處改全部,這是很危險的事。
12.8 陣列
Arrays
陣列是一組相同資料型別的項目,可使用索引直接存取。確定所有的索引,都在陣列的邊界內
考慮使用循序存取的容器類別取代陣列
- 陣列的「隨機存取」產生可靠性降低的問題。
循序結構: sets、stacks、and queues
- 第一個項目
- 第一個項目的Off-by-one錯誤
- 最後一個項目
- 最後一個項目的Off-by-one錯誤
- 陣列的中繼項目
在c語言中
使用ARRAY_LENGTH()巨集搭配陣列- 宣告這個
//C Example of Defining an ARRAY_LENGTH() Macro
#define ARRAY_LENGTH( x ) (sizeof(x)/sizeof(x[0]))
下面實作這個//C Example of Using the ARRAY_LENGTH() Macro for Array Operations
ConsistencyRatios[] =
{ 0.0, 0.0, 0.58, 0.90, 1.12,
1.24, 1.32, 1.41, 1.45, 1.49,
1.51, 1.48, 1.56, 1.57, 1.59 };
...
for ( RatioIdx = 0; RatioIdx < ARRAY_LENGTH( ConsistencyRatios ); RatioIdx++ );
...
這個方法適合用在一維陣列上,修改陣後,不需修改迴圈上的數字。陣列全長/陣列一個單元 = 陣列個數。
只可在宣告的function內使用,function互傳值時無效(補充)
- 陣列在宣告時,索引的操作,其實是指標在記憶體中的移動次數,當遇到function呼叫,需要傳遞陣列(其實不可以這樣做)時,都是傳遞指標,再操作指標位移,以達到陣列的操作。
12.9 創造自有型別(型別別名)
Creating Your Own Types
這是語言所能提供的最有力的功能之一C/C++使用者請善用typedef
其它支援的使用者也請務必使用等效功能
使用時機:
- 表達經緯度,在使用double還是float舉棋不定。
- 構築薪資系統,員工名字最多30個字元,但是有些員工還是會超過
- 結合自我構築型別及資訊隱藏的方法
- 修改更容易
- 修改的資訊集中在一個地方
- 讓編譯器可以檢查
- 彌補語言的弱點
為什麼自行構築型別的範例要用Pascal和Ada?
Why Are the Examples of Creating Your Own Types in Pascal and Ada?
-
Pascal和Ada和恐龍一樣,已經在走向滅亡。取代庫們的語言都更好用,但是在這方面卻反而退步了。
例子:希望可以確保currentTemperature的賦值,正確性提昇的用法
currentTemperature: INTEGER range 0..212
這一句包含了溫度是整數表示
int temperature;
再進一步,像這樣的類型宣告
type Temperature is range 0..212;
...
currentTemperature: Temperature;
充許currentTemperature賦值的動作受Temperature 的定義所規範。
以下是重新翻譯的一段:
「當然,程式設計師可以創造一個Temperature類別來執行相同的語義。(自己刻Ada的自動強制執行語法),但是,從用一行程式碼創造一個簡單的資料型別到用一個類別來創造,是跨了好大一步,許多情況之下,程式設計師會寫創造一個簡單的型別,但不會創造一個類別」
構築你的UDT原則
Guidelines for Creating Your Own Types
-
針對真實世界所面臨的問題本身來取名字
避免內建的資料型別
不要用相同的意思來重覆定義內建的資料型別
為可攜性定義替代型別
考慮用 class取代typedef
沒有留言:
張貼留言
(什麼是留言欄訊息?)