Total = $11,100
- Intel Pentium G4500 , $2700
- 技嘉 B150M-D3H , $2790
- 美光 8G DDR4-2133 , $799
- Toshiba 1TB (DT01ACA100), $1680
- 華碩 24XSATA燒錄器 , $459
- 銀欣 PS11-Q , $1499
- 海韻 S12II-430W , $1490
Total = $11,100
想要找 Java 教材:
http://programming.im.ncnu.edu.tw/J_index.html
1. function 一定要明確 "指出" 回傳值 void int... JAVA 很在意這個
--
public class myExcep
{
public static void main(String args[])
{
java.util.Scanner sc = new java.util.Scanner(System.in);
int num = sc.nextInt(); /* 當輸入不是 int 時,會出現 exception */
System.out.println(x); /* java.util.InputMismatchException */
}
}
--
switch (grade)
{
case 'A':
System.out.println("ok");
break;
case 'B':
break;
}
--
陣列在存取時超出範圍,會發生例外狀況
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 10
如果存取一個並未配置空間的陣列參考,會出現
Exception in thread "main" java.lang.NullPointerException
--
Call by Value: 基本型別 (int, float, String...)
Call by Reference: 陣列、物件
foo (int tmp[][]) { do something ... }
bar {
int num[][] ={ {1,2}, {3,4} }
foo (num); /* 這邊是call by reference,所以把參照 num 值拷了一份給 tmp*/
}
--
類別方法(Class Methods)
就是用 static 描述的方法,不用生出物件也可以直接呼叫
class myExample
{ public static void showName()
{ System.out.print("name: Peter");
}
}
類別變數(Class Variables)
成員變數屬於類別本身,當類別第一次建立時,就配置變數的記憶體
class myExample
{
public static double PI=3.1415;
public static int count=0;
myExample() { count++; }
}
main ()
{
myEx obj[] = new myEx[2];
obj[0] = new myEx();
obj[1] = new myEx();
System.out.println( obj[0].count ); /* 會得到 2 */
}
--
函式在多載,主要是用 input 的參數型別、個數來當識別,例如像:
public static void show(int x) 兩者視為相同,無法分辨
public static double show(int x) 因為皆是傳入一個 int 參數。
--
數學函式的用法,直接用 Math.FUNCTION() 來呼叫就可以,不用 import 其他類別庫,
例如:
Math.max(12, 37) // 取大值
Math.round(12.32) // 四捨五入
重要的是 Math 類別提供了兩個 static 變數
Math.PI
Math.E
另外 Math 也提供了亂數可供使用
Math.random(); // 產生 0.0 ~ 0.9999999 的亂數
Math.random() * 100 // 產生 0.0 ~ 99.9999999(無窮小數)
--
陣列的宣告法
int [] data = {1,2,3,4,5};
int [][] data = { {1,2,3} , {4,5} , {29,3,2,4,5} };
int [] data = new int [] {1,2,3,4,5};
底下這個方式很特別,稍微學一下。其中 Float 是一種物件
Object data[] = new Object[] {new Integer(28), new Float(12.1)};
/* 把 [][] 看成雙重指標 所以千萬不可以寫成 new int[10] */
/* 否則會出現 incompatible types found : int[] required: int[][] */
int [][] num = new int[10][];
num[0] = new int[(int) (Math.random()* 10) ];
--
陣列你要想成,有多個指標幫你指向物件,
class node { public int data; }
在用的時候,千萬記住要各別 new 出物件來
node myNode[] = new node[5];
myNode[0] = new node(); /* 各別再去配置空間 */
myNode[0].data = 10;
System.out.println(myNode[0].data);
同理也用在 Double 物件上面
Double foo1[] = new Double[5];
foo1[0] = new Double(5);
System.out.println(foo1[0]);
但是如果是基本型別的資料,就可以直接用
double foo2[] = new double[5];
foo2[0] =5;
System.out.println(foo2[0]);
--
String 是種物件,用法
String tmp = new String("the_key_word");
System.out.println( tmp.indexOf("key") ); /* 找出 key 這個字第一次出現*/
String num="123";
Integer.parseInt(str);
--
建構元的名稱,要與類別名稱相同
class Circle
{
public Circle() /* 修飾子可為 public 表示外界能呼叫 */
{ this(19); } /* 用 this 這個關鍵字來呼叫建構元 */
private Circle(int x) /* 若為 private 則是只能透過內部呼叫 */
{ System.out.println(x); }
}
--
兒子的 Constructor 如果有呼叫到父親的 Constructor,則要放在第一行
而且是用 super 這個關鍵字。
class Student extends Person
{
Student()
{ /* 呼叫父親的建構子, should be at the 1st line */
super();
System.out.println("Student() ");
}
void foo() { System.out.println("Son foo"); }
void bar() { super.foo(); } /* 自己也有 foo() 所以
如果要呼叫老爸的 foo() 就用 super 這字 */
}
--
super 的用法有兩種:
1. 用來呼叫父類別建構子: super();
用來呼叫父類別特定的建構子: super(27, "abc");
2. 用來呼叫父類別的函式:如果父親、兒子都有 foo() ,
要特定指名呼叫 父的foo() 就用super.foo()
class CBase { protected int value=10; } /* protected 允許讓兒子繼承 */
class CExt mextends CBase
{
public int value=20; /* 此時 CExt 會有兩個 value:自身、由父類來的 */
public int get_Value() { return (value); }
public void set_Value(int x) { value = x; }
public int get_Base_Value() { return (super.value); }
public void set_Base_Value(int x) { super.value = x;}
}
public static void main(String args[])
{
CExt obj1 = new CExt(); obj1.set_Base_Value(28);
CExt obj2 = new CExt(); obj2.set_Base_Value(99);
System.out.println( obj1.get_Base_Value() ); /* 得到 28 */
System.out.println( obj2.get_Base_Value() ); /* 得到 99 */
}
--
this 的用法還有另一招
(注意:在 C++ 語言中,this 是一個指標,所使用時要用 -> 來操作)
class CBase
{
public int value=10;
public void show()
{ int value=99;
System.out.println (this.value); /* 物件成員 10 */
System.out.println (value); /* 區域變數 99 */
}
--
關於 final 的用法
1. 禁止 function 被子類別改寫(overriding)
class Base { public final void show() {...} }
/* 錯誤!! overridden method is final */
class Ext extends Base { public void show() {...} } /* 不允許自己重新定義 */
2. 用來達到 常數(constant) 的作用
class Base { public static final double pi=3.14; }
class Ext extends Base {
double pi=8; ○ /* 自己可以另外搞一套 */
show() { super.pi=10; ╳ } /* 但是不可修改到父類那組 */
show() { pi = 10; }
}
3. 禁止 class 被繼承
final class Base {}
class CExt extends CBase /* cannot inherit from final CBase */
--
問題:
1. 我在 myExcep.java 中用到 myClass.Java 的類別變數
為什麼 myClass.java 在每次重新執行時,不需要重新編譯?
2. Math.random() 到底產生的範圍是什麼? 去確認一下
類別 java.util.Random 又是怎樣
3. String.indexOf() 的用法到底怎樣
--
開始談 class 的用法
class FOO()
{
private int num=0; /* private 變數隻允許自己的類別來存取 */
public set_Num(int x) { num = x; }
}
public class myEx
{
public static void main(String args[])
{ FOO obj = new FOO();
FOO.num=10; ╳
FOO.set_Num(10); ○
}
}
--
在一個建構子中,要呼叫另一個建構子時,用的是 this
而且 this 關鍵字 只能放在建構元內的第一行。
class Cfoo
{ String name;
Cfoo()
{ this("default"); } /* this.Cfoo("default") 這是錯的用法 */
Cfoo(String x)
{ name = x ; }
}
--
預設的建構子
class Cfoo {} 的預設建構子會長成: public Cfoo()
--
在類別函式中,不可以呼叫 實例函式/實體變數,因為這些東西屬於個別物件所有
non-static method my() cannot be referenced from a static context
class Cfoo
{
static int num=0;
int age;
public void my() { }
static void my_s() /* (╳) 類別函式 不可以呼叫 實例函數、實例變數 */
{ my(); }
}
在類別函式中,也不能使用 this這個關鍵字,因為 this 是指呼叫該 method 的物件。
class Cfoo
{ int num=0; /* 物件變數 */
public static void get_Num() /* 類別函式 (!!不可以用到物件變數!!) */
{
System.out.println ( num ); /* 這也是不允許的 ,沒物件跟本不會有 num*/
System.out.println ( this.num ) /* this 代表呼叫這個函式的物件 */
} /* 不允許 */
}
--
class complex
{
double a,b;
public boolean compre(complex obj)
{ /* 要用每個欄位下去逐一比對 */
if ( this.a==obj.a && this.b==obj.b) return true;
else return false; /* 如果寫成 this==obj 這表示,比較兩指標所指位置 */
}
}
--
在 obj 物件內改寫 toString() 函式,注意看這邊的語法,奧義。
public static void main(String args[])
{
Object obj = new Object()
{
public String toString() /* 改寫 fucntion */
{ return ("noname"); }
};
System.out.println( obj.toString() );
}
--
繼承登場
父類別 子類別
====== ======
沒有宣告 public/private ----> 沒有宣告 public/private
public 成員 ----> 一樣是 public
private 成員 ----> 只能透過父類別的函式來存取
建構元 ----> 不能繼承
class Circle
{ public Circle() {System.out.println("Circle constructor()");}
public Circle(int x) {System.out.println("Circle constructor(int x)");}
}
class Coin extends Circle
{ /* 什麼都沒寫,預設會去呼叫父類別的 Circle() - 即 super() */
public Coin() { System.out.println("Coin construcotr()"); } 與下面同樣意思
public Coin() { super(); System.out.println("Coin construcotr()"); }
/* 如果想要 呼叫特定的 父類別建子,那就用 super() - 會呼叫 Circle(int x) */
public Coin(int x) { super(x); System.out.println("Coin construcotr()"); }
}
--
書本上有談到 overriding 與 overloading
overriding - 在子類別中,定義名稱、引數個數、傳回值型態均與父類別相同的method
overloading - 在相同類別中,定義名稱相同,引數數目不同,或者型態不同的method
--
抽象類別登場 (子類別用 extends 來繼承,再補齊原本 abstract 部份)
所謂抽象類別就是內含有 abstract function 的類別。其使用語法如下:
abstract class Shape /* 別忘了 class 要加上宣告 abstract */
{ public abstract void show(); } /* 然後當然也少不了 函式的部份 */
class Rectangle extends Shape /* 定義 Rectangle 之後準備實作 show() */
{ public void show() { System.out.println ("Rect()"); } }
class Circle extends Shape /* 定義 Circle 之後實作 show() */
{ public void show(){ System.out.println ("Circle()"); } }
1. 抽象類別(abstract class)不可用來產生物件
2. 在抽象類別中所有 "抽象函數" 必定要在子類別中實作,這樣才可用子類別建物件
(別忘了,只要有一個抽象函數存在,該 class 就變成 abstract class
abstract class 是不能用來產生物件的。)
3. 抽象類別中,可以有部份函數已經實作。未實作部份就是由子類別來補齊。
4. 一個抽象函數,可以宣告為 public/protected
--
談談 interface
1. 介面中的成員變數,要設定初值
2. 在介面中的 function 默認都是 abstract 了
在介面中的 成員變數 默認都是 final 了,不能更改其值 (一開始就要給初值)
3. 成員函數只能宣告為 public 或者不宣告。不能宣告為 protected/private。
4. 在實作 function 時,傳數參數的型別、數目,與回傳值 都要與介面定義內的相符合。
5. 所有在介面內定義的 function 都要在類別(class) 中實作出來,
否則該類別就變成 abstract class
interface Shape
{
int value=10;
public abstract void foo();
public abstract void bar();
}
abstract class Circle implements Shape /* 抽象 - 因為 bar() 沒有實作出來 */
{
public void foo() { System.out.println("hello"); }
}
class Triangle implements Shape /* 全部的函式都實作完,可以用來建立物件 */
{
public void foo() {...}
public void bar() {...}
}
--
在 java 中並不支援使用 extends 來多重繼承,而是用 "介面" implements 來實現
interface Shape { public abstract void show(); } /* 傳入參數要給名字 */
interface Color { public abstract void setColor(String x); }
class Circle implements Shape, Color
{ public void show() {...} /* 所有 函式 都要實作,否則會變成抽象類別*/
public void setColor(String x) {...}
}
--
討論 interface 與 abstract 的差別
1. 介面內的 變數成員,要設定為常數
介面內的 所有方法,都設定為抽象方法。
2. 抽象類別允許:變數不需要設定初值 + 一般函數/抽象函數 並存。
--
使用 rand() 產生亂數,會介於 0 ~ RAND_MAX(32767)
調整亂數範圍: 下限 + rand % (上限-下限+1)
設定亂數種子: srand( time(NULL));
--
關於 ++ 與 -- 的問題
1. 若 a=2, b=5 , x = ++(a*b) /* lvalue required as increment operand| */
這是一個錯誤用法,當使用 ++ 或 -- 時必需要有 左值(l value)
講白一點就是說,使用 ++ 或 -- 時要有變數能去儲存運算,而不是一個式子。
x = y 對於 x 而言它表示的是一個address,而 y 表示的是address裡的內容
l-value在compiletime就知道了,r-value在runtime時才會被知道,也就是說
所有symbol的address在compiletime時就會知道
有些東西只能當 r-value, 例如 literals
有些東西既可當 r-value 又可當 l-value, 例如變數
有些運算子需要 r-value, 例如 + 的兩側
有些運算子需要一個運算元同時可以扮演 r-value 與 l-value 的角色,例如 ++ --
--
關於迴圈
do-while 的語法,在 while 後面要有一個分號。
do
{
i++;
} while (i<=10);
--
關於陣列
int num[3][3] = {0}; /* 宣告時才能這樣用,設定為整個都是 0 */
int num[2][2] = {1,2,3,4}; /* 會自己分組 */
int num[2][2] = { {1,2} , {3,4} }; /* 同意 */
這樣宣告是錯的,對 c 語言來說,他不曉得每一組有多少人 (真怪 T_T)
int num[][] = { {1,2,3} , {4,5,6} ,{7,8,9} };
像下面這樣要講出每組有多少人才對
int num[][3] = { {1,2,3} , {4,5,6} ,{7,8,9} };
傳遞陣列給函式
foo (int a[] ) /* 一維陣列,可以這樣搞!! */
foo (int a[][3]) /* 一定要把每組有多少元素標出來 */
foo (int a[][] ) /* 這是錯的,因為不知道怎麼分組 */
在 C++ 中並不會檢查陣列的註標,也就是說在使用時,可能不小心超出範圍
--
陣列與指標的配合
1. 字串指標所形成的陣列
char* array[] = { "this", "is", "a", "book" }; /* 宣告一個字元指標的陣列 */
在這個例子中 array[1] 是指向 "this" 這個字串
2. 陣列名稱是指標沒錯,但是它是一個「常數指標」,所以是不能更改的。
int num={1,2,3,4,5}
int *ptr = num;
cout << *(num); /* 會得到 num[1] 沒錯 */
cout << *(num+1); /* 希望 num 向下移一個 大錯!! num 是指標常數,不能動的 */
cout << *(ptr+1); /* 這才是對的,ptr 是一個指標變數,可以移動的 */
3.
int num[] = {1,2,3,4,5};
int *ptr = num;
cout << *++ptr; /* 會把 ptr 向下移到 num[2] 並印出 num[2] 的值 */
cout << *ptr++; /* 印出 num[2] 的值,並把 ptr 向下移一格指到 num[3] */
cout << *ptr; /* 目前 ptr 指到 num[2] */
4. 注意一下這邊 ptr 與 ++ -- 用法順序
main()
{
int num[SIZE];
int *ptr = num;
for (int idx=0; idx<SIZE; idx++)
cin >> *(ptr++);
for (int idx=0; idx<SIZE; idx++)
cout << *(--ptr);
}
5.
void showArray (int data[]) /* 改成 int *data 也是可以 */
{
for (int idx=0;idx<SIZE;idx++)
cout << *(data + idx);
}
main() { int num[SIZE]={1,2,3,4,5}; showArray(num); }
6.
int *ptr = NULL;
cout << ptr; /* we got 0x0 */
cout << ptr+1; /* we got 0x4 */
7. 使用 new 與 delete 建立二維陣列
int **data;
data = new int*[m];
for(int i = 0; i < m ; i++)
data[i] = new int[n];
for(i=0; i<m; i++)
delete [] data[i];
delete [] data;
8. 不要被指標給嚇到了 char* a[] 沒你想的那麼難
void show(char* a[]) /* a[] 是一個陣列 裡面裝字元指標 */
{
for (int idx=0; idx<SIZE; idx++)
cout << a[idx] <<endl;
}
char* a[SIZE] = {"this", "is", "a", "book"};
show(a);
--
指標的奧義
1. 使用 ptr++ 表示指標移向下一格,不要想錯了
int num[10];
int *ptr = num; /* 表示 ptr 指向 num[1] */
ptr++; /* 表示 ptr 指向 num[2] */
2. 同型別的兩個指標不允許相加;但指標可以相減,
相減表示兩個指標間相差的元數個數(會自動幫你換算)。
int num [] = {1,2,3,4};
int *p = &num[0]; /* 0x23ff28 */
int *q = &num[1]; /* 0x23ff2c */
cout << p << endl;
cout << q << endl;
cout << q-p; /* 1 相差一個元素,不要懷疑 */
3. 函式指標 (function pointer)
void swap (int *p, int *q)
{ int tmp = *p; *p = *q; *q = tmp; }
int main()
{ int a=10, b=20;
void (*fp)(int*, int*);
fp = swap;
(*fp)(&a, &b); /* 寫成 fp(&a, &b) 也行 */
}
4. 函數指標的奧義
double add(double x, double y)
{ return (x+y); }
double multi(double x, double y)
{ return (x*y); }
double run(double x, double y, double (*fp)(double, double) )
{ cout << fp(x,y); } ^^^^^^^^^^^^^^^ 會送一個 function 進來
int main()
{
run (18.2, 25.1, add); /* 進行相加運算 */
run (281, 2, multi); /* 進行乘法運算 */
return 0;
}
5. 窮極無聊的指標
fun ( int array[] [3] ) /* 陣列是3個整數為一組,有多少組不曉得 */
fun ( int (*array)[3] ) /* 與上同義,每個指標指出去的,都是 int array[3] */
fun ( int * array[3] ) /* 有 3個整數指標,形成一個陣列 */
array → □□□
□□□
array[0] □→
array[1] □→
array[2] □→
--
static 的用法
static 用在變數上,表示變數所佔用的記憶體,直到程式結束才會歸還。
static 全域變數只允許在一個檔案內使用。而且變數生命週期是從定義處開始,
所以在沒有定義靜態全域變數的區域中,是無法使用 static全域變數。
即使用了 extern 也沒辦法使用。
int main()
{ extern int x; } /* x 是靜態全域變數,生命週期是從定義處才開始 */
static int x; /* 因此在 main() 內的用法,是錯的 */
static 區域變數
動態變數在 function 執行時,會在堆疊建立一個區塊供變數使用,
並沒有固定記憶體空間,這種方式建立變數稱為「宣告」
在編譯時如果已配有固定記憶體空間的變數,稱為「定義」如 static / extern
靜態區域變數。變數的生命週期不會隨著函數結束而結束。而要等到整個程式結束。
foo()
{ static int num; }
--
extern 的用法
1. extern 表示只是 宣告, 不是定義(define)
2. 用來表示變數會在其它的位置被定義,可能在同個檔案,或者其他文件中。
void foo()
{
extern int x; /* (這邊是宣告)變數x 在別處定義,不能寫成 extern int x=10 */
x=99; /* 一定要兩個式子拆開來寫 */
cout << x;
}
double x=10; /* 這邊定義全域變數 x */
--
函式的參數傳遞,有給預設引數的,要靠右邊
void foo (int a, float b, float c=2.3, char d='z')
呼叫時: foo(12, 84.1); /* 合法 此時 c預設2.3 d預設值'z' */
foo(12, 84.1, 10.1); /* 合法 狀況同上,但 c 值2.3 */
foo(12); /* 不合法,變數少一個 */
--
inline 函數
當一個 function 被呼叫時,程式的控制權會轉到函數上,並且使用堆疊(stack)
記錄返回位址。如此動作會耗費一些時間。
C++ 中的 inline function 被呼叫時,會直接「抄一份」貼到呼叫處。
使用 inline 函數可以減少程式主控權不停轉換。但是 inline 函數會佔用較大記憶體。
因為同一份程式碼,抄了很多次。
--
#define 巨集的使用
前置處理器會直接代換字串,不需要考慮型態問題,沒有函數的呼叫,
所以也省去了變數的定義、回傳值。所以如果十段相同的程式碼,
巨集 - 佔用的記憶體較大,但是程式控制權不用轉移,速度較快。
函數 - 程式較短,佔用的記憶體較小,但控制權要轉移,所以速度慢。
--
字串的處理
char str[10];
1. 讀取一行,可以包括空格
cin.getline(str, 10); /* 真正存的資料只會有9個,最後一個是 \0 佔用 */
2. 計算字串長度
strlen(str) /* 長度不包含最後 \0 符號,你輸入幾個就算幾個 */
2. 拷貝
strcpy(a,b) /* 字串 a 要準備 strlen(b)+1 的長度,因為最後還有一個 \0 */
--
關於 暫存器變數 register 用法
用來增加運算速度,將區域變數存在暫存器中,暫存器變數的活動範圍只在
所屬的左右大括號中。
foo()
{ register int num=10; }
--
關於 reference(參考) 的使用
1. 基本用法
int num;
int &ref = num; /* 在這邊 ref 就是一個參考,指向 num */
2. 用來達到 swap 的功能
void swap(int &a, int &b)
3. 一個函式也能傳回 reference
int& foo (int &a, int &b) { return ( a>b? a:b); } /* 這邊的 a,b 都是參考 */
main () { int num1, num2;
num1=20; num2=30;
foo(num1, num2)=99; /* foo() 執行完後,會回傳較大者的位址 */
} /* 之後把該變數值改成 99 */
--
volatile 加在變數的前面,表示該變數不會使用最佳化編譯。有時變數的值改變了
compiler 並不會馬上將他寫入記憶體中而先把結果放在CPU暫存器中 等到處理結束
之後 才寫入記憶體。若說這個變數是多執行緒的 flag 其他的執行緒要透過這個
變數來反應狀況。
而這個值卻又沒有寫入記憶體 這時便會發生意想不到的結果又或者是這變數為一個
硬體的暫存器會被硬體所改變然而compiler 並沒有正確的將值從硬體暫存器取出來
而是將自己暫存的值拿來使用。
這種情況 就是要用volatile 來宣告變數 告訴compiler不要自己暫存變數來提升速度
如此這個變數有任何的改變 便會馬上反應出來
*ptr = 0;
while(*ptr){ 會被聰明的編譯器給最佳化→ *ptr = 0
*ptr = 4 ; 但是每個值的改變,可能表 while(0) {}
*ptr = 0 ; 示 hardware 的一個狀態,
} 不允許省略的!!
--
結構的用法
結構的大小是「欄位總長度」+「數個位元組」,讓存放在記憶體的下一個變數
起始處是偶數位址。(其實測的結果,長度都是 8 的倍數 8 16 24 32...)
1. 直接在定義後面,宣告出變數
struct myData
{
char name[10];
int age;
} Jack, Peter, Tom; /* 宣告 Jack... 為 myData 格式 */
或者
struct myData Jack = { "jack", 18 };
cout << sizeof(Jack); /* 得到結果是 16 */
2. 相同的結構允許直接拷貝
struct myData Jack = { "jack", 18 };
struct myData Tom;
Tom = Jack;
3. 位元欄位 (unsigned bit) ... 有考過
struct File {
unsigned int face :4 /* 使用 4位元 */
unsigned int suit :2 /* 使用 2位元 */
unsigned int color:1
};
--
Union 的用法 (省空間的作法 會取最大欄位那個)
union myUni
{
char c; /* 字元佔 1 byte ■□□□ */
int score; /* 整數佔 4 byte 取最長的 4byte */
};
myUni obj = {65} ;
cout << sizeof(obj); /* 得到 4byte */
cout << obj.c; /* 會得到英文字母 A (因為ascii A 65) */
cout << obj.score; /* 會得到 65 */
--
enum 的用法
1. 基本值都是從 0 起跳,別忘了
enum myEn
{
A,B,C,D=7,E,F /* A=0, B=1, C=2, D=7!!, E=8 ... */
}; /* 注意了,這邊的值要給整數 */
int main()
{
myEn X=E; /* we get 8 here , X 是列舉形態,要用列舉的格式 */
cout << X;
myEn Y;
Y=1; /* 在 c 語言可以這樣用,但在 c++ 要改 Y=static_cast<myEn>(1) */
return 0;
}
2.
enum myEn
{ tennis, swimming, baseball }; /* tennis=0 swimming=1 baseball=2 */
enum myEn X=tennis;
switch (X)
{
case tennis: /* 這邊也可改成 case 0 */
cout << "tennis";
break;
case swimming:
cout << "swimming";
}
--
typedef 的用法
typedef struct
{
int hour;
int min;
} myTime; /* myTime = struct {...} */
myTime clock[3]; /* 宣告時鐘陣列,每個元素都是 myTime 結構 */
--
談類別之前,先提一下 this 指標
在 C++ 中這是指標,指向物件自身。 this->name
在 JAVA 中是參考,語法有些微差異勿混用。 this.name
--
討論一下類別
class CWin
{
private:
int height;
int width;
public:
CWin() {}
CWin(int height, int width) { this->height = height; this->width=width; }
int area();
};
/* scope resolution operator 範疇解析運算子 */
int CWin::area() { return (width*height); }
動態建立一個物件 CWin *ptr = new CWin();
使用指標呼叫 ptr->area();
刪除物件 delete ptr;
--
討論一下建構子 (constructor) / 解構子 (destructor)
class CWin
{
private:
char id;
int width, height;
}
0. 非常重要的事 A為父類別,B為子類別
建構子執行順序 A() -> B()
解構子執行順序 ~B() -> ~A()
1. 在建構子內直接初始化成員 (千萬記得,這招只能在建構子使用)
CWin( char x='Q', int y=10, int z=15 ):id(x), width(y), height(z)
直接指定 id=x, width=y, height=z
2. 建構子:不會有傳回值,但也不能宣告為 void!!
因為系統建立物件時會主動呼叫 constructor,建構子跑完才會回到主程式。
所以不用傳回值。
3. 解構子:與類別名稱同名,但多加一個 ~ 的函式稱為解構式。
與建構式相同,解構式也不能有傳回值。
4. 跟 JAVA 相同 C++ 預設會為每個類別建立一個空的「預設建構子」
預設建構子不會有任何引數,也不會做任何事。以類別 CWin 來說:
CWin() {} 這就是預設建構子
但如果我們有自訂建構子,那麼系統就不會自動建立預設建構子
5. 建構子的引數也可以提供預設值,當某個引數缺少時,會直接使用此值。
6. 注意建構子發生混淆的狀況
建構子有兩個:
CWin( char x='Q', int y=10, int z=15 ) {} /* 內建預設值,使用時可省略 */
CWin( ) {}
主程式中使用: /* 對應錯誤!! 不知選哪個建構子 !! */
CWin obj; /* 要解讀成第一個,還是第二個? */
7. 最先建立的物件,會是最後才進才解構
myClock(char str[]) { strcpy(id,str); cout << "create:" << id <<endl;}
myClock::~myClock() { cout << "bye" << id <<endl; }
main() { myClock obj("no.1"); myClock obj2("no.2"); }
會得到
create:no.1
create:no.2
byeno.2 /* 2號最後建立 但是最先解構 */
byeno.1
--
拷貝建構子 (之前很少用過,其實也沒什麼)
用法大概是這樣
class myClock { char *name, int hour, int min }
myClock obj1("no.1", 12, 37);
myClock obj2(obj1); /* 建立 obj2 物件,並把obj1 的資料拷過來 */
系統給的 預設 拷貝建構子會是這種型態: myClock(const myClock &)
[注意] 這邊會有一個 bug 當我們的成員變數有指標時,他會把指標的位址抄一份過來
也就是說在上例中,obj1 與 obj2 的 *name 指向「同一份 no.1」
這是不正確的。應該要存有兩個副本,否則一者 delete 掉之後,另一者就無法
參考到。
obj1.name ---> [no.1] <--- obj2.name
這個問題可以自行定義拷貝建構子,來解決。改寫下面這個建構子。
myClock (const myClock &src) /* 注意傳進來的一定是 const 參考。 */
{
/* 把字串拷貝過來 這樣就不是共享同一份 自己指定要寫入什麼值 */
name = new char[ strlen(src.name)+1 ]
strcpy (name, src.name);
hour = src.hour;
min = src.min;
}
--
多載運算子( operator overloading )
class myClock
{
int hour, min;
myClock(int x, int y) { hour=x, min=y; }
void operator+ (const myClock &rhs) /* 傳 reference 進來,而且是 const */
{ hour = (hour + rhs.hour) ; min = (min + rhs.min); }
}
使用時:
myClock obj1(12,31);
myClock obj2(1,1);
obj1+obj2; /* 也可以寫成 obj1.operator+(obj2) */
我也可以把加法改成這樣,算完之後會回傳一個結果:
myClock operator+ (const myClock &rhs)
{ return ( myClock( (hour+rhs.hour) , (min+rhs.min) ) ); }
使用時:
myClock obj1(12,31);
myClock obj2(1,1);
myClock obj3 = obj1+obj2 ; /* 記得準備一個變數來接 相加後傳回的結果 */
另一個要注意的就是: 等於(=)運算子
這邊會出現的問題就跟「拷貝運算子」那邊是一樣的,當物件的資料如果有指標時
去做 等於= 運算時只會把 address 抄一份,而不是複製出另一份實體。
造成兩個變數共用一份實體。
class myClock
{
private:
int hour, min;
char *name;
public:
void operator= (const myClock &rhs)
{
hour = rhs.hour, min=rhs.min;
name = new char[ strlen(rhs.name)+1 ] ;
strcpy (name, rhs.name); /* 直接拷一份過去 */
}
}
--
類別中的 靜態(static)變數與函式
一個 static 變數必需在類別內部宣告,但初值指定要在 class 外部 !!
class myClock
{
private:
int hour,min;
const static double pi=3.14; /* 靜態的 const 變數,可以在類別內初始化 */
static int dummy_count;
public:
static int count; /* 宣告靜態變數 */
static void get_count() { cout << count; } /* 宣告靜態方法 */
myClock(){ hour = min = 0; count++; } /* 每新生一個物件,計數加1 */
};
/* C++ 的 類別靜態成員 不可以在建構子內初始化 */
int myClock::count = 0;
/* 即使是 private 變數仍是這樣做 */
int myClock::dummy_count = 0;
int main()
{
myClock obj[10];
cout << myClock::count; /* 這邊的計數值=10 因為上面有10個物件產生 */
myClock::get_count(); /* 同樣道理,這是示範 static method 用法 */
return 0;
}
靜態函數有一些限制,因為沒有物件產生:
靜態函數 --不能存取--> 類別成員
靜態函數 --不能呼叫--> 類別函數
靜態函數 --不能使用--> this 這個關鍵字
但是反過來就不一樣了。
--
有關參考(reference)
CWin win1;
CWin &ref = win1;
cout << ref.width; /* 使用時 */
--
談論一下 友誼函數 (friend function)
1. 友誼函數可以存取 類別 內的公有、私有類別
2. 友誼函數本身不屬於類別,所以無 public/private 問題
3. 友誼函數在類別內宣告 Prototype 就好,實際的 Body 寫在函數外面。
class CWin
{
private:
int width, height;
public:
CWin() { width=height=10; }
friend void getValue(CWin); /* 定義友誼函數的 prototype */
};
void getValue(CWin X) /* 友誼函數的 body 注意這邊參數的傳遞 */
{ cout << X.width; }
main()
{ CWin obj; getValue(obj); }
--
C++ 的 try-catch 機制
C++ 有提供一套 try-catch 機制,在拋出例外時,什麼型別都可丟。
這點不像 Java 有內建了一套 Exception 類別。
在 try{} 中使用 throw 這個指令丟出例外,
在 catch(型別 變數名) {} 會去比對拋出的例外資料,是什麼型別。有符合才做處理。
int a=10;
try
{ if (a==10)
throw ("EXCEPTION"); /* 拋出了一個字串常數!! */
}
catch (const char *str) /* 所以進行型別比對,如果是字串常數,才來這邊 */
{ cout << str ; }
catch (int x) /* 如果拋出一個整數,就會在這邊處理 */
{ cout << x ; }
catch (...) /* 你沒看錯 "..." 表示什麼例外通吃
{ cout << "..."; }
cout << "try catch end"; /* 不論是否補捉到,try-catch 完之後就接這邊 */
--
樣板(template)的使用 - 背起來
template <class T, class U> /* 允許前後型別不同 */
T add ( T x, U y) /* 吃兩個不同型別變數 */
{ /* 注意,回傳型別是 T 記得考慮資料精確度 */
return (x+y);
}
int main()
{
cout << add <double,int> (12.7 , 10); /* 也可直接寫成 add(12.7, 10) */
return 0;
}
上面是 template 用在函式上面;如果用在類別上面,寫法會不同(一場災難):
template <class T>
class myClock
{
private:
T hour, min;
public:
myClock(T x, T y) {hour=x, min=y;}
void setValue(T x, T y) { hour=x, min=y; } /* 實作在裡面比較省事 */
void getValue(); /* 用來示範如何在類別外實作,很麻煩!! */
};
template <class T> /* 不要忘記寫這行,這樣下面函式的 T 才有作用 */
void myClock<T>::getValue() /* 這個 <T> 的位置也要放對,非常小心 */
{ cout << hour << endl << min; }
int main()
{
myClock <int>obj(9,23); /* (易忘)用的時候也要小心,型別要放在物件前 */
obj.getValue(); /* 這邊只有一個 <int> 而不是 <int, int> */
return 0;
}
底下這個例子跟上面一模一樣,但允許傳入 2 個型別。
template <class T, class U>
class myClock
{
private:
T hour;
U min;
public:
myClock(T x, U y) { hour=x , min=y; }
void setValue(T x, U y);
void getValue();
};
template <class T, class U>
void myClock<T, U>::setValue(T x, U y)
{ hour=x, min=y; }
int main()
{
myClock <int, float>obj(12,34); /* 也是一樣,使用時要先講好,載入的格式 */
return 0;
}
--
討論一下繼承
class Shape
{
private:
int width, height;
public:
Shape()
{
cout << "Shape Create";
width = height = 0;
}
void setWidth(int x) { width = x; }
int getWidth() { return width; }
};
class Circle:public Shape
{
private: /* 在 Java 中可以使用 super.func() 來呼叫父類的方法 */
int color; /* 但 C++ 則是該使用 父類別::func() 來進行呼叫,別弄混了 */
public:
void getWidth() { cout << Shape::getWidth(); }
void setWidth(int x) { Shape::setWidth(x); } /* 呼叫父類的 setWidth() */
};
int main()
{
Circle obj;
obj.setWidth(18);
obj.getWidth();
return 0;
}
--
父類別 | 子類別
public: | public:
show_member() +--> show_member()
|
private: | private: (子類別成員不能存取)
id, width, height +--> id, width, height
|
建構元 +--> 不能繼承
|
class father
{ public: show_member();
private: int id, width, height;
father()
}
class son:public father /* 使用 public 方式來繼承,原本屬性不變 */
{
- 兒子可以呼叫 show_member()
- 兒子擁有 id, width, height 變數,但是不能存取,要透過父類函式。
- 父親的建構元是不會繼承給兒子的
}
在 C++ 繼承中,建立子類別物件時,會先呼叫父類別中沒有引數的建構元。
再執行子類別的建構元。
兒子若想呼叫父親特定建構元呢?!
在兒子建構子後面加上 :父類建構子()
就像 son(int x, int y):father(x ,y) /* 把 x y 傳給父建構子 */
class father
{
father(int, int, int)
father()
father(char, int)
}
class son:public father
{
son() :father(10,20,30) /* 這樣就會呼叫特定父親的建構元 */
son(int n) /* 這一會對應到 father() */
son(char c):father (c, 28)
}
son obj; /* 建構子對應到 son() 且會去呼叫 father(10,20,30) */
son obj(28); /* 建構子對應到 son(int) 且會去呼叫 father() */
son obj('q'); /* 建構子會對到 son(char) 且會把傳入的字元,再傳給父建構子 */
類別繼承的存取模式
父 (A) 子 B:public A | 子 B:protected A | 子 B:private A
public public | protected | private
protected protected | protected | private
private 無法存取 | 無法存取 | 無法存取
--
多載(overload)
多載可以使用一個函式名稱來執行不同的實作,這是一種「編譯時期」就需決定的方式,
這是「早期繫結」(Early binding)、「靜態繫結」(Static binding),因為在編譯
時就可以決定函式的呼叫對象,它們的呼叫位址在編譯時就可以得知。
--
多型()
虛擬函式(Virtual function)可以實現「執行時期」的多型支援,是一個「晚期繫
結」(Late binding)、「動態繫結」(Dynamic binding),也就是指必須在執行時期
才會得知調用的物件。 (白話:虛擬函式是達到多型的一種方法)
透過指標或參考的操作,來達到多型的表現。
class A
{ virtual void show() {cout << "from A \n"; } } /* 宣告 show() 為虛擬函式 */
class B : public A { void show() { cout << "from B\n"; } }
A *ptr; /* 父類別指標 */
A obj1;
B obj2;
ptr = &obj1; ptr->show(); /* 得到 from A */
ptr = &obj2; ptr->show(); /* 得到 from B 因為 show() 設成 virtual */
或者像這樣用...
class A { virtual void show() {cout << "from A \n"; } }
class B : public A { void show() { cout << "from B\n"; } }
void call_show( A &obj ) { obj.show(); } /* 會去呼叫函數內建的show() */
A obj1;
B obj2;
call_show(obj1); /* 得到 from A */
call_show(obj2); /* 得到 from B */
--
純虛擬函數 (pure virtual function)
上面那個議題在討論 virtual function 但在這邊要講的,叫做 pure virtual function
這種函式只有一個空殼。將來會在子類別中實現。
在 Java 中這種函數叫做 抽象函數(abstract function) ;
而在 C++ 中則稱為 純虛擬函數(pure virtual function)
具有 純虛擬函數 的類別,則稱為 抽象類別(abstract class)...這點跟 JAVA 一樣
講白了:pure virtual function 就是只有標頭,至於身體要在子類別中實作。
語法: virtual void function() = 0; /* 宣告他 =0 , 表示父親這邊不會實作 */
一個類別中如果含有純虛擬函式,則該類別為抽象類別(Abstract class),
該類別只能被繼承,不能用來直接生成實例。
純虛擬函數,也是實現多型的一種技巧。雖然函數名稱相同,但執行時會因為型別不同
而去呼叫對應類別中的函式。
這有一個範例
class Shape
{
public:
int hight, width;
void setValue(int x, int y) { hight=x , width=y;}
virtual int area()=0 ; /* 宣告 area() 為純虛擬函數 */
};
/* 各子類別去實作 area() 函式 */
class Triangle : public Shape
{ public: /* public 別忘了加,很重要! 不然其他人怎麼用 */
int area() { return (hight * width * 0.5); }
};
class Square : public Shape
{ public: int area() { return (hight*width); } };
使用時:
Square obj;
obj.setValue(12, 11);
cout << obj.area(); /* 會呼叫到 Square 的 area() */
--
memory leak 的問題
A *ptr = new A;
A obj;
ptr = &obj; /* 把 ptr 改指向另一個物件,原本 new 出來的物件 失連 */
ptr->show();
delete ptr; /* 而且 obj 物件並不是動態配置產生,所以 delete 是沒效的 */
ptr->show(); /* 仍是照舊可以呼叫 show() */
(1)國文
(2)法學,英文
(3)程式語言 (念中文書,C++、JAVA、物件導向要熟)
《Concepts of Programming Language》Robert W. Sebesta
440《程式語言》(胡世雄,高點出版) (買新的)
(4)資料通訊
《Computer Network》ANDREW S. TANENBAUM
(中文版,電腦網路,邵喻美譯) (買新的)
663《資料通訊網路》 第四版 Forouzan(劉金順譯,麥格羅希爾) (買新的)
(5)資料結構
《Fundamentals of Data Structures IN C++》 聖經本
《資料結構歷屆試題詳解》洪逸
(6)資訊系統與分析
553《系統分析與設計—理論與實務應用》吳仁和、林信惠 (2007四版,買新的)
578《物件導向系統分析與設計:結合MDA與UML》吳仁和 (二手430,賺80)
(7)資料庫應用
425《資料庫應用》向宏 (高點出版,足以應付)
《Fundamentals of Database Systems》Ramez Elmasri
668資料庫系統原理 5/e (中文本,陳玄玲譯) (太厚 先不買)
(8)資訊管理
510《資訊管理─理論與實務》謝清佳、吳琮璠 (二手同價位)
578《資訊管理─e化企業的核心競爭能力》林東清 (最新第三版,二手480賺100)
《資訊管理概論 :企業e化建置實務》林震岩