2009年4月30日 星期四

C++ 用stringstream作型別轉換

今天寫程式的時候要把int轉成string 再把string轉成int

在C的時候有itoa跟atoi,或是直接sprintf來轉自串轉換

但是C++string沒有提供這些功能

但是有個擴展的類別stringstream

只要
#include <sstream>
就能使用了

他用法跟cout很像

舉個簡單的例子


stringstream ss;
int data=200;
int result;
ss<<data;//將data轉成字串插入ss
ss>>result;//將ss的內容輸出到result



他也可以這樣用


stringstream ss;
string name="Tom";
ss<<"Hello "<<name<<endl;

cout<<ss.str();\\output: Hello Tom

參考資料:stringstream ss;

C++ 的HashTable

最近因為作業的關係會用到hashtable

但是c++內建並沒有hashtable

有幾個己決方法


#include <ext/hash_map>
#include <ext/hashtable.h>
#include <map>
using namespace __gnu_cxx;


可以用map取代他,可以得到相同的效果

而hashtable雖然目前不是c++的標準類別

但是Linux下大概都已經有實作他

再ext的資料夾下應該都可以找的到

不過還是希望以後會有C++真正的標準hashtable

這邊要注意的是他們都是再__gnu_cxx這個namespace之下

上面3個 map,hashˍmap,hashtable

都可以拿來作類似的工作,但是他們底層實作的方法不一樣

詳細可以去看reference

C++可以把這類資料結構看成陣列來使用

接下來我都用hash_map舉例

...
hash_map<string,string> maps;
maps["左邊"]="老王";
maps["右邊"]="老李";
//他會很像
hash_map<int,string> maps2;
maps2[0]="老王";
maps2[1]="老李";
...


他用法跟一般陣列一樣,只是註標(key)可以是自訂型態

像是maps是用string當index的string陣列


string [] maps;

不過c++的這類型態只支援一些原生型態像是int,char之類的

如果想用自訂class當key或value就必須提供一些指定函式

以hash_map為例


template<class _Key, class _Tp, class _HashFn = hash<_Key>,
class _EqualKey = equal_to<_Key>, class _Alloc = allocator<_Tp>>

第一個參數是用來當index的Data Type

第二個參數是用來當容器內容的DataType

他總共有3個可擴充用參數 第三個跟第四個都是給key用的 第五個是給value用的


簡單來說

maps[key]=value;

想要再中括號[]放入自訂型態 就一定要寫HashFun跟EqualFun


這裡做個小訂正,EqualFun並不是非寫不可 2009/05/06
參考資料:http://www.stlchina.org/twiki/bin/view.pl/Main/STLDetailHashMap



然後想用=指定值 就要寫allocator

不過第五個參數通常沒人寫

因為可以用指標來折衷 指標是原生型態 c++自己可以處理

以下簡單一個例子


class Word: public Token {
public:
string lexeme;
Word(string s,int tag);
virtual ~Word();
string getData()const;
static const Word AND;
static const Word OR;
static const Word EQ;
static const Word NE;
static const Word LE;
static const Word GE;
static const Word MINUS;
static const Word TRUE;
static const Word FALSE;
static const Word TEMP;
};
struct hash_word{
size_t operator()(const string& w)const{
return __stl_hash_string(w.c_str());
}
};

struct eq_word{
bool operator()(const string& w1,const string& w2)const{
return true;
}

};
......
hash_map<string,const Word*,hash_word,eq_word> words;
words["Hello"]=new Word("s",254);


string c++有提供處理hash的方法__stl_hash_string

而通常都是習慣用struct去寫hash_map需要的函式

我們必須作()的運算子多載讓類別可以當function的角色

以hash_word為例,我們覆寫了operator()(const string& w)之後

就可以把hash_word當成function來使用

hash_word hashFun;
size_t hashkey=hashFun("Hello");



而再我宣告了如上的hash_map之後 就隨即產生了const* Word的陣列

就像是

const* Word[] words;

所以他可以接new 傳回來的值(new 會回傳一個指標)

為何有const呢 因為必須存static const Word這些常數Type code

如果直接用Word的話 他就像一般陣列一樣會產生實體必須實作default constructor

所以用指標是比較好的選擇

C++ 用檔案當getchar()的資料來源

getchar()通常是拿來取得鍵盤輸入

但是他其實是可以用檔案當他的資料來源的

今天寫了一支scan

void Lexer::readch() {
int i=getchar();
if(i!=-1)
{peek=(char)i;}
else
{throw exception();}
}


其中外面會有個無限回圈不斷call這個函式

他可以接受使用者輸入來分出Token

但是也可以接受檔案 假設我把他編譯成scan

>./scan < Test1.txt


這樣就可以用Test1.txt的內容當getchar的input

他是利用到unix的Re-direct(重新導向)的方式

2009年4月29日 星期三

C++ Override小筆記

這兩天在寫程式用到Override的時候,出了一些問題


class Parent{
virtual void say(){
cout<<"I am Parent" ;
}
};

class Children:public Parent{
virtual void say(){
cout<<"I am Children" ;
}
};
.....

int main(){

Parent p=Children();
p.say();
}

根據之前java的習慣,以為這樣就可以override了

但是出來的結果竟然是

I am Parent

後來找了很久,原來C++的override是建立在指標物件之上

程式必須改成



int main(){

Parent* p=new Children();
p.say();
}


這樣出來的結果才會正確

順便介紹一個名詞

Pure virtual function


也就是virtual function不實作內容,他的子類別一定必須實作這個函式

其長相大概如下

virtual void f() = 0;

或是

virtual void f(){}\\在此只是不實作

為何可以這樣呢?

原來virtual宣個出來的函式會變成一個指標函式

一般函式宣告出來後不會在class使用空間,但是這個virtual函式卻會

因為他會變成一個指標的存在,所以可以給他指定值0(NULL)

pure的虛擬函式一定要由子類別來定義 否則無法用來宣告變數(指標可以)

參考文章:http://blog.yam.com/swwuyam/article/12114648

舉個簡單的範例


class Test{};

int main{
cout<<"sizeof(Test)" ;
}

\\Output:1 原生空類別會佔掉1byte空間




class Test{
int data;
void f(){}
};

int main{
cout<<"sizeof(Test)" ;
}

\\Output:4 有member會依據member而決定size 在此一般函式不佔class的空間




class Test{
int data;
virtual void f(){}
};

int main{
cout<<"sizeof(Test)" ;
}

\\Output:8 f變成虛擬函式,變成一個指標而多佔了4byte


2009年4月27日 星期一

NetBeans的程式碼提示功能

eclipse用習慣之後,就很難再去使用其他IDE了

主要我相當依賴他的快捷功能,只要alt+/就可以自動完成程式碼,alt+shift+F就可以排版程式等等

但這次我必須用NetBean當我的開發環境了,因為要開發J2ME eclipse支援的套件還是不多

但是我最依賴的程式碼自動完成輸入功能還是得用

在NetBeans的話Source->Complete Code



就可以使用程式碼提示了,指令是Ctrl+Space(空白鍵)

只是不知道為什麼我必須按Ctrl+Atl+Space她才會出來

2009年4月25日 星期六

重構一書閱讀小感

花了一個禮拜看完碁峯『重構-改善既有程式設計』

重構的定義:Refatoring:a change made to the internal structure of software to make it easier to understand and cheaper to modify without changing its observable behavior of the software.

主要就是在說再不改變程式外在行為的前提下,修改軟體內部結構讓程式碼更容易使人了解閱讀

因為想要增進一些寫程式的技巧,最近一直在看這類的書

由於這本是我看的有關重構的第一本書,也很難說他好還是不好

我本身算是碁峯出版的信者,相關書籍我原先都會先考慮碁峯出的書

雖然作者會影響書的內容,但我覺得出版社的風格也會有相當的影響

不過碁峯最近幾本書個人認為實在編寫不太好,像『Visual C# 2008 程式設計經典』這本

反而沒有他前面幾本寫的好

『重構-改善既有程式設計』是比較舊的書了,市面上有關重構的書其實不多,真要看的話還必須找中國那邊出的或原文本
這本『重構-改善既有程式設計』個人認為寫的算中規中矩,對於沒有UML跟物件導向基礎的人來說會讀的相對吃力

裡面總結了程式碼的22個壞味道82個重構方法,內容還蠻多的,講解方面稍嫌生澀,並非那麼容易閱讀

裡面有句話我印像蠻深的

Any fool can write code that a computer can understand. Good programmers write code that humans can understand.



大意就是說任何人都可以寫出電腦看得懂(可以Run)的程式,但唯有寫出『人』可以輕易看懂的程式,才是好的程式設計師。

這點實在讓我感同身受,至今我也看過也改過不少程式碼,想要把程式碼寫的讓人一看就懂實在不是件容易的事情。

為了那一點cpu cycle而將程式碼寫的非常難看,短期之內效能上可以非常好看

但是長期之下程式的維護成本將遠高過那一點點效能帶來的好處

一個程式主要的overhead集中在10%的不良演算法,除非有即時性(real-time)的考量

實在不該為了那一點點效能而讓整個程式發霉,整個軟體專案,Debug的時間常常是實際寫程式的好幾倍

還記得我之前寫過一個遊戲專案,技術的學習跟程式設計假設花了半個月,那我花在Debug的時間就是兩個月

這不成比例的時間配置就是沒有好好整理程式的下場,而且好的程式碼要最佳化也比較容易,他擁有更多可最佳化的的程式

真的要好好維護程式碼架構才真的是一個程式設計師該有的基本能力阿

2009年4月24日 星期五

C++ 變數的存放位置跟static三個用法

今天上課聽了一些關於變數的存放
以前寫程式時都不會注意這些東西,就姑且把他記下來了



首先看上面這張圖,一個Procress在Runtime期間,都會配置像這樣一塊記憶體規畫
他被分成四大塊:stack、heap、.data、.text,.data也可以再分成(.bss,.data)
然後只有stack跟heap是變動的,其他都是compile時候就固定了

stack


non-static的location variable(區域變數)都放會這邊,他是隨著function call一層一層堆疊,因此每進入一個scope就會疊一層,每離開一個scope就會少一層。
這邊特別標註non-static,因為static變數的話其生命週期(lifetime)會改變,雖然他仍是區域變數,但是他會放到.data的地方。

void f(){
int i;\\nont-static location variable in stack
static int l;\\static location variable in .data
}


heap


這塊區域的記憶體是由user來決定其增減的,他主要存放所有"new"出來的dynamic variable,也就是在runtime期間請求的記憶體,直得一提的是,java所有object都放在heap裡面,這是為了可以讓GC實現。
因為new出來的東西是user請求,他主要回傳一個指標(Pointer),所以相關回收動作也要由user去做,因為系統不會幫user回收new出來的記憶體。
系統只會去回收stack上的memory,如果不回收這些new出來的記憶體,而又用不到他就會造成memory leak,所以new出來的東西要由user自己去delete。
在此特別提一下,通常delete之後會再讓該指標指向NULL(第0塊位置),這是為了讓系統可以去回收這塊記憶體。就算記憶體被free掉,指標仍可以指向他。

new跟delete兩大步驟


new

  1. Memory allocation:先向heap去規劃一塊記憶體

  2. Initialization:初始化,呼叫物件建構子初始那塊記憶體


delete

  1. clean up:呼叫解構子清空那塊記憶體

  2. free:歸還塊記憶體



其中,那些步驟是可以單獨出現的,譬如說只呼叫解構子而沒有歸還記憶體,所以delete之後還要讓指標指向NULL

.data


這塊區域主要存放Global跟static variable,為了讓lifetime不受scope影響的物件可以被任何地方存取,這塊記憶體會固定且一開始就規劃好。

.text


主要是放程式碼的地方


這邊再舉個有趣的例子

void f(){
int* pa=new int;
}


在這裡要分成兩方面來看,pa是由我們宣告出來的"區域變數",注意!他仍是location variable
但是奇怪,上面不是說new出來的東西會放在heap。
沒錯我new出來的是放在heap,但是我new的是int而不是pa阿,pa仍是一個區域變數,只是他會指到heap去。

就是這樣,只有new出來的東西會放在heap,他通常是我們指標指向的實體,所以new出來後不去delete就會有一塊垃圾產生在那裏


static三個用處


static根據在不同的地方會有不同的解釋

在localation時使用:更改變數lifetime

用在區域變數時,他可以改變區域變數的生命週期,讓他不會出了scope即被回收,也就是他不會被放在stack而是放在.data裡面

void f(){
int i;\\nont-static location variable in stack
static int l;\\static location variable in .data
}


在Global時使用:更改變數scope

當他用在全域變數宣告時,他會從global scope變成file scope,也就是只有這個"檔案"可以使用他,其他link的目標檔則無法使用

舉個例子


//Test1.cpp
static int a;
int b;
void main(){
}


Test2.cpp
static int a;
static int c;
void main(){
}


在編譯程可執行檔的時候,會將所需程式或lib變成目標檔(.o)再透過連結器(linker)將他們link起來,才會變成我平常所需的可執行檔,在這邊所說file scope就是指一個.o檔
如上面例子,只有b是全域變數,Test1還是Test2都可以使用這個變數,但是a、c都被static變成了file scope,只有自己檔案看的到,所以不用有變數名稱重複的問題,因為scope不同,Test1看不到也不能用Test2的a,同樣的Test2的a也跟Test1的a不相干。

在Class時使用:讓member是Perclass


當在class的member使用static修飾子的時候代表,這個member是全部的instance都可以share的,大家共用一份,同時class不需要產生instance就可以使用

2009年4月22日 星期三

Google Analytics

今天想替網誌增加統計訪客流量的功能
尤記得之前替公司導入過"yahoo 站長工具"
Google其實也有類似的工具Google Analytics

使用上個人覺得yahoo站長工具比較好用,因為有中文可以看,而且也比較即時
相較於Google Analytics一段時間計算一次短期實在看不出什麼效用

2009年4月21日 星期二

「undefined reference to」錯誤

今天C++ 編譯的時候出現了這樣的錯誤

A a;
a.get();

呼叫a.get()竟然說找不到對應的reference

原來是MakeFile沒有對好


main:main.o A.o
g++ -ggdb -o main main.o A.o
main.o:main.cpp
g++ -ggdb -c main.cpp
A.o:A.cpp
g++ -ggdb -c A.cpp

改成

main:main.o A.o
g++ -ggdb -o main main.o A.o
main.o:main.cpp A.h
g++ -ggdb -c main.cpp
A.o:A.cpp
g++ -ggdb -c A.cpp

這樣就可以RUN了
雖然main有include A.h 但是編譯的時候compiler不認得
我們就手動幫他引入

2009年4月17日 星期五

XP 用cmd的batch改IP

因為我是用筆電,宿舍實驗是兩邊跑,常常要改IP時在麻煩
找了一下用批次檔改IP的方法

首先新增一個空白檔案
檔名取Auto.bat
然後打開他裡面輸入

netsh interface ip set address name="區域連線" source=static addr=xxx.xxx.xxx.xxx mask=xxx.xxx.xxx.0 gateway=xxx.xxx.xxx.xxx gwmetric=1
pause

其中那個name是你當初替連線取的名稱,繁體XP通常是用"區域連線"
但是如果有其他網路介面請以他們為準
再來比較重要的三個參數
addr=你要用的IP
mask=子網路遮罩
gateway=預設閘道

note:
上面只有兩行指令
netsh開頭,用來設定IP
pause 用來停註看看有沒有設定成功

2009年4月15日 星期三

C++ 類別初始化順序

C++的類別初始化順序是 Parent Class>member>this
而解構順序則正好反過來Parent Class<member<this

下面舉個簡單的例子


class Parent{
Parent(){
cout<<"建構parent"<<endl;
}
~Parent(){
cout<<"解構parent"<<endl;
}
};
class Member{
Member(){
cout<<"建構Member"<<endl;
}
~Member(){
cout<<"解構Member"<<endl;
}
};
class My:public Parent{
Member m;
My(){
cout<<"建構My"<<endl;
}
~My(){
cout<<"解構My"<<endl;
}
};

void main()
{
My m;
}


結果將是

建構parent
建構Member
建構My
解構My
解構Member
解構parent

C++ 參數傳遞小筆記

今晚你選哪道?



void f(int);\\call by value
void f(int*);\\call by address
void f(int&);\\call by reference

main(){
f(123); \\1
f(NULL); \\2
f(0); \\3
}

上面程式碼
call by value 三個都可以通過
call by address 2,3通過
call by reference 接不通過
為什麼呢?
原來是call by address和call by reference當參數傳遞時只能接受"變數"
也就是一個lvalue(memory location),他們必須參考到某一個記憶體位置
而指標比較特殊,他可以接受NULL,也就是0的直當初始化
而call by reference因為一定要初始化的特性,只要是純量他都不接受

void f(int& p){}

main(){
int i=0;
f(i);
}

上面動作就像是

int& p=i;

如果傳純量的話

void f(int& p){}

main(){
f(123);
}

則變成

int& p=100;

根據Reference Type的特性 他當然會編譯不過

切記!切記!

C++ Reference Type

Reference Type
C++有個很獨特型態 Reference type
宣告方式跟指標很像

int i=10;
int &r=i;

他必須以變數來初始化,而且一定要初始化,否則會編譯不過


int &r;
int &r=100;

以上兩種方式都是不行的
這種宣告像是在替變數取別名

int i=10;
int &r=i;

兩者是同一個東西,只是名稱不同
不同於指標
如果是

int i=10;
int *r=&i;

此時r是指向i的記憶體位置,但是他本身是用另一塊記憶體位置

int i=10;
int &r=i;

而這時候,r跟i是共用同一塊記憶體位置



順便講講指標一種特殊的宣告方式

char a[3][4]={"abc","bcd","cde"};
char (*str)[4]=a;

其中 a是個二維陣列,內容已經被預先指派
因為[]的優先權高於* 所以在此用()將char*括起來先做
而str在此代表一個指標變數(是變數而不是陣列喔),他指向一個size為4的的字元陣列
原本char*的指標是指向size為1 byte的記憶體,str+1 一次只能跳一個byte的大小
但是透過這個步驟,讓原先的char*指標指向size為4 byte的記憶體大小,讓str+1變成可以一次跳4個byte
這就就有點像宣告成
char[4]* str;
但是並沒有這種語法,所以只是很像而已
上面程式碼就等效於

int* str=(int*)a; \\int 也是4個byte


如果是
char *str[4]
則str代表一個size為4的陣列,其內容為char*

C++多維陣列

剛剛看到的C++多維陣列注意事項
趁現在還記得就把他打成文章

int a[3][4]={};\\正常用法 沒問題
int b[][4]={};\\這也可以通過編譯
int c[3][]={};\\這不行
int d[][]={}\\這也不行


多維陣列在宣告時,只有最左邊的的[]是可以不填的
因為compile會不知道a[index]之後哪個位置的size是多少
舉個例子
int a[][7][8];
像上面的例子,從a[0]到a[1],compiler必須知道他必須跨過多少memory(mapping)
依此例子a[0]到a[1]必須跨過7*8*4 byte個位置,所以後面的數字是不可省的

再來是填value

int arr[2][3]={1,2,3,4};
for(int i=0;i<2;i++)
{
for(int j=0;j<3;j++)
{
cout<<arr[i][j]<<" ";
}
cout<<endl;
}

出來的結果會是

1 2 3
4 0 0

多維陣列可用一維陣列填value的方式去設value
其中{...}內的元素可以小於或等於 多維陣列總大小
像上面例子arr的size是6 所以{}的元素不能多於6
不足的會自動補0 超過則會編譯不過

其中一個有趣的填法

int arr[2][3]={{1,2},3,4};
for(int i=0;i<2;i++)
{
for(int j=0;j<3;j++)
{
cout<<arr[i][j]<<" ";
}
cout<<endl;
}


1 2 0
3 4 0

或是

int arr[2][3]={{1},2,3,4};
for(int i=0;i<2;i++)
{
for(int j=0;j<3;j++)
{
cout<<arr[i][j]<<" ";
}
cout<<endl;
}


1 0 0
2 3 4

看到沒有,原來用預設填值的方式,{}裡面一個大括號會換一行row
如果裡面沒填滿的自動補0表示

int arr[2][3]={{1},{2,3,4}};
for(int i=0;i<2;i++)
{
for(int j=0;j<3;j++)
{
cout<<arr[i][j]<<" ";
}
cout<<endl;
}

這樣就等於有兩個row 結果是

1 0 0
2 3 4


但是如果是

int arr[2][3]={{1},{2},3,4};
for(int i=0;i<2;i++)
{
for(int j=0;j<3;j++)
{
cout<<arr[i][j]<<" ";
}
cout<<endl;
}

此時代表要填三個row的內容,分別是 {1},{2},{3,4}
但是此範例只有兩個row 因此會編譯不過

恐怖的多維陣列


其實上面的用法蠻冷門的,平常沒是盡量少碰
多維陣列到二維就非常恐怖了,平常我寫程式頂多也就是用到二維
用到二維以上就該想想自己程式是不是設計有問題
除非是像data mining的商業立方體(cube)
不然多維陣列其實效能相對於一維陣列是非常差
因為二維以上就必須做mapping才能取到元素(二維以上陣列的mapping 可以去研究程式語言原理)
通常能用一維陣列解決就不用多維
不然就是用一維陣列模擬二維陣列

以下一個範例

int* a[10];
int b[100];
a[0]=&b[0];
a[1]=&b[10];
......

像上面程式碼一樣,自己就做好二維陣列的mapping
此時a[0][0]就等於b[0]
a這個二維陣列是模擬出來的,效能會比真正的二維陣列還高

被廢棄的CLK_TCK

今天想測一下c++的時間函數
c++本身有內建clock()函式
他會回傳一個時間點,其中用的是clock_t 他是個long型態的type

#include
#include "time.h"

main(){
clock_t prev=clock();
doSomething();
cout<<(double)(clock()-prev)/CLK_TCK;//X
}

沒想到編譯不過
後來查了一下文件,原來是說CLK_TCK已經被捨棄不用了
因為享用CPU的派波算時間不理想
所以現在都改用CLOCKS_PER_SEC
因為clock函數現在的回傳就是毫秒了

#include
#include "time.h"

main(){
clock_t prev=clock();
doSomething();
cout<<(double)(clock()-prev)/CLOCKS_PER_SEC;//O
}

c++對於全域變數跟區域變數的區別

全域vs區域


當全域變數跟區域變數同名時,C++該如何區別呢?
是的,只要scope不同,他們的命名就不會影響


int x;

main(){
int x;
}



在此狀況 他是可以編譯不過的,所謂名稱不可重複,是指同scope不可以有同個命名參數
上面的情況 第一個x的scope為全域,而第二個為main的區域變數,互不影響

但是改成如下就不行了

main(){
int x;
double x;//X
}


這時候問題來了,這種狀態下要如何區別x呢?

萬能的scope運算子::


::相信大家都用過,是用來區別他是屬於哪個namespace
而他也能幫我們區別出全域變數

int x;

main(){
int x=10;
::x=20;
cout<<"區域變數x:"<<x<<endl;
cout<<"全域變數x:"<<::x<<endl;
}

輸出結果就會是

區域變數x:10
全域變數x:20

當scope運算子前面沒有東西時,就代表他想呼叫全域物件

2009年4月14日 星期二

C++的內隱型別轉換

這兩天複習C++,又碰到了很詭異的語法

後來發現C++的型別轉換真是萬能


class T{
T();
T(int);
T(T&);
}

我有一個class T而我執行

T t;
t=100;

竟然可以通過編譯,原來在此Compiler很聰明的,會讓
t=100;
變成
t=new T(100);
在此會呼叫Convert Construct(轉換建構子)
此時建構子扮演的型別轉換的角色,C++會自己去找有沒有實作相關的轉換建構子
如果沒有就會編譯不過

像是

class G;
class T{
T(G&)
}

void print(T&){}

main(){
print(new G());
}

他可以通過編譯,因為在這種情況下,參數傳遞用的是initization而非assign
他會自動去尋找有沒有相關的轉換建構子

2009年4月13日 星期一

設計模式-(序) 是設計更是藝術

序言


最近在研讀設計模式(Design Pattern),至從看了有關這類的書
再回頭看看我以前寫的程式,只能用不堪回首來形容
我覺得設計模式是每個物件導向程式設計師都該看的
在眾多設計模式的書籍當中,個人推薦這本:大話設計模式 出版(代裡)社:悅知


這本是之前去紀伊國屋看到的,由於紀伊國屋的東西都貴到昏倒
所以買這本的時候我很猶豫,加上我對悅知出品的印象都是:夠廣不夠深
就是悅知有關資訊的書籍都是探討範圍很廣,但是不夠深入(我是讀書狂,所以對出版社風格會有刻板映像)
但是這本推翻了我以往的想法,這本大話設計模式用對話跟日常生活例子為對象來延伸到設計模式,除了探討內容夠之外,容易閱讀是特點,這本大話設計模式我花了數個小時就閱讀完畢,而且也吸收豐富,相較於之前培生出版社那本,這本真是好物。

回到正題,就算不懂設計模式,程式寫多了也多多少少會用到類似的概念
Java或是.NET等都用到不少相關的設計模式,有拜讀過那些core的原始碼的話,一開始會像看天書一樣,有看沒有懂,但是了解其中的設計模式後,會發覺他是那麼的美麗。

設計模式:是設計更是藝術



一個設計好的程式,就像是美麗的女人般讓人百看不厭,以前從未注意過程式可以這樣散發如寶石般的光輝,想想之前我class隨便亂用,幾乎一個函式幾百行那種程式,真的只能用垃圾來形容。
程式寫多了,之後就只是語法上的問題,我甚至斷言照著書作人人都可以寫出一個系統。
尤記得我大學時候一位同學,他們的專題是作進銷存系統,雖然他們都不太會寫程式,但是靠著微軟VS2005的強大到也讓他們做出有模有樣的東西,但是一旦放上戰場,缺點立刻出來,根本就不堪用。
再舉一個比較近的例子,微軟最近在徵招實習生(未來生涯體驗),他們的主題網頁外觀華麗,但是根本經不起考驗,我因為也想報名,所以有去看他們的網頁,一開始我還讚嘆外觀設計的真漂亮,一旦報名按下去,缺點立刻浮現,也就是他們的網頁firefox不支援某些動作,逼得我一定要用IE去跑這是敗點一:瀏覽器的相容性沒做好。
再來就是註冊時明明沒叫我填身分證,登入時卻要填身分證(啥鬼),然後我身份證填好後不給我登入,造成我原本二月多要報名托到三月中,而且我可以登入還是因為我發現身分證少填就可以登入,敗點二:程式邏輯沒設計好,驗證也做不完全。

其他零零總總的缺點,所以說程式人人會寫,有些套裝軟體甚至拉一拉就寫好一套系統(VS之類的),但是是不是經得起考驗的系統才是我們資訊人員的優勢所在,不然套件拉一拉程式就寫好了,工程師還有飯吃嗎?

記得當初上一堂課,助教要抓有沒有抄襲,他就把程式碼瀏覽過一遍之後,就抓出了誰抄誰,他說了一句話:"程式是個藝術,每個人風格皆不同,只要看了程式碼架構,大概就能猜出是誰寫的"
這句話在我碰觸過設計模式之後更是非常認同,程式如果沒設計好,寫出來的只是把螺絲起子,轉過螺絲後就毫無價值,但是設計好的程式就像把萬用刀,什麼事情都可以用,栓了螺絲還能用他做其他事情。

但是設計模式會造成程式效能的降低(function call增加),如何在這之間做取捨,就是程式設計師的功力了。
用我喜歡的RPG來舉兩個極端的例子,一支效能到極限但是沒有設計過的程式就像是萬靈藥,可以解除所有異常狀態(問題),但是用過一次就消失不能再用了。
而一個設計到毫無破綻,但是效能普普的程式,就像是僧侶,可以作到萬靈藥的作用但是一次只能做一件(解毒、治療、增加力量etc),而且還要考慮僧侶有沒有MP(現在的問題適不適合),但是僧侶沒有使用上的限制,只要有MP(設計模組正好能解決問題),就能一再使用。

說了那麼多,主要是在說程式是否能夠Reuse,一隻好的程式除了效能好之外,他還必須能夠重複使用來增加他的生命週期,如此一來才能說他是個有設計的程式。

copy不等於reuse


很多人都學過物件導向程式目標就是寫出內聚力強、耦合力弱的程式,但是會真正去實踐他的人卻不多,我認識許多人都有錯誤的概念,認為寫程式寫到後面就是copy&paste,這種是最要不得的概念,就因為程式沒設計好才會用複製貼上取代程式碼的覆用,當copy&paste用多了的時候,哪天要改就會花費非常大的成本,假如這隻程式用在一百個地方,就要改一百次(老闆會寧用fire這種人請另一個高明的來重構),這時候其實可以考慮用提煉函式的方法解決,但這是重構(Refactor)的部分了,在此不贅言。



接下來我打算花幾個篇幅連講解設計模式的基本23個pattern,希望我能完成

2009年4月5日 星期日

C/C++的Const(下)

const在類別的使用也是非常....有趣


class T{
private:
int data;
public:
void op()const{}

};

java跟C#碰多了,再回來看c++的一些用法真的會覺得很詭異

在此op稱作const member function(常態成員函數)

在跟compiler說函式op不會修改到成員變數(data)

所以如果在op裡面作出修改成員變數動作將會編譯不過

void op()const{data=0;}

同樣的,如果試圖呼叫函式或是回傳 讓compiler覺得你可能修改到成員變數,也會編譯不過

void op()const{dp(data);gp();}\\X
void dp(int data){}
void gp(){}

compiler會怕妳那個函數可能修改到成員變數
修改方式

void op()const{dp(data);}
void dp(const int& data)const{} \\告訴compiler你不會修改到data
void gp()const{}


或者是
int* op()const{return &data;}
也是沒辦法的,因為傳同一個記憶體變數會讓compiler覺得你會修改到他
必須修改成回傳常數的方式
const int* op()const{return &data;}

而當你變數宣告成常數時,如果使用不為const member function也會compiler失敗

class T{
....
void dp();
}
void get(T& t){}
main(){
const T tester;
tester.dp();\\ X
get(tester);\\X
}

必須修改成

class T{
....
void dp()const; \\我不會更改成員
}
void get(const T& t){}\\我不會更改成員
main(){
const T tester;
tester.dp();
get(tester);
}

簡單來說,就是const函數,只能存取同為const的行為
成員函數調用成員函數

呼叫 在呼叫 O/X
const const O
const non-const X
non-const const O
non-const non-const O
或者是將成員宣告成
mutable int data;
mutable是c++的關鍵字,基本上是不建議這樣做,只會讓debug更複雜
或是將迴船方法改成
return const_cast(data)
將const屬性去除


a. const對象只能訪問const成員函數,而非const對象可以訪問任意的成員函數,包括const成員函數.
b. const對象的成員是不可修改的,然而const對象通過指針維護的對象卻是可以修改的.
c. const成員函數不可以修改對象的數據,不管對象是否具有const性質.它在編譯時,以是否修改成員數據為依據,進行檢查.
e. 然而加上mutable修飾符的數據成員,對於任何情況下通過任何手段都可修改,自然此時的const成員函數是可以修改它的

引用自http://hi.baidu.com/maolang0/blog/item/e0546a8bd03a4c14c8fc7a12.html

最後來提個有趣的例子 關於運算子多載跟const

class T { // "unlimited precision int"
  public:
   T& operator++(); // prefix
   const T operator++(int); // postfix
};

為什麼在此要用const呢

是為了user做蠢事像是

T a,b,c;
(a+b)=c;
\\or 防止
(a+b)+c;


以上 最近複習C++ const的小結

C/C++的Const(中)

const在函數裡可以做到保護的作用

void add(Card c);

像這樣一段函式,直接用call-by-value的方式傳遞

如果Card是個非常大的結構,那overhead將會非常的高,而且高到不行(里昂調)

因為他是用copy的方式整個copy一份到stack裡面使用

假如你的c大小是1MB 那麼光作這1MB的copy(包括init跟記憶體規畫)跟傳遞就耗費無數時間

所以通常都會選擇只傳他的指標

void add(Card& c);

這樣子只需要傳遞Pointer(4 byte),而不須整份copy(n byte 過去)

但是有時候我希望傳進去的東西不要被修改,我希望他唯讀

這時候const就小兵立大功了

void add(const Card& c);

改成這樣,就會告訴compiler 傳進來的參數是不可更改,如果做了任何指派的動作就會編譯不過

void add(const Card& c){c=new Card;}\\X

包括把他傳進另一個函式或者是回傳


void add(const Card& c){test(c);}
void test(Card& c){}


此時儘管test沒做什麼,仍然會編譯不過 因為compiler不確定test是否會修改到c

但是改成如下就可以


void test(const Card& c){}\\告訴compiler test不會修改c
\\or
void test(Card c){}\\不共用記憶體 但是失去意義


同樣的,你也不可能回傳c的記憶體位置
Card* add(const Card& c){return &c;}\\X
因為要避免你在函式外面修改他

Card* a=add(c);
a.x=XXX;

但是可以用回傳值使用const的方式解決

const Card* add(const Card& c){return &c;}
const Card* a=add(c);

其中const Card* 代表回傳的是一個常數,a必須也是個常數才能接收


Note:

const Card add();

const用於非指標回傳一點意義都沒有

C/C++的Const(上)

一般說到const 只會想到常數

也就是類似 const int a;

但是const在c/c++裡面 扮演了一個小兵立大功的角色

const用到函數、類別、指標時 他會變成一個非常強大的修飾子

但一方面常常有感 我學了很多語言 const的神卻只在c/c++看的到

java或C#以及ActionScript、PHP等弱型別語言他經常無用武之地

c/c++雖然強大也不免感慨,他跟目前主流商用程式語言上的差別

也罷 自少c/c++大概可以在戰幾十年

先來談談指標上 const的用法


const int* i; (1)
int const* i; (2)
int* const i; (3)
int* i; (4)
const int*const i; (5)

(4)個是原生型態 就是都不加的情況,這時候指標i所指的位址幾及該位址內容是可改的
(1)跟(2)兩個是一模一樣的東西 主要判斷方式是const再星號左邊,代表的是指標i指向的內容為常數不可改,但是指向位置可改
也就是

*i=3; //X

像這樣從新指派內容是不可以的,但是

i=new int; //O

這是可行的,可以從新指向一個新的地方

而(3)則是代表指標i所指的內容可改,但是指向位置不可改,判斷方法是const在星號右邊
此時
i=new int;\\X
從新指向另一個位置是不可行,但是
*i=100;\\O
從新更改該位址內容卻是可以

置於(4)則是指向內容跟指向位置都不可改,就像是宣告一般常數一樣

const i;


附帶一提,我們常用的陣列,其實就是宣告一個位置不可改的指標

int arr[10];

就等效於

int* const arr=new int[10];


今天到此為止

const在函數及類別的用法 下回再說吧

資管與資工

從資管跳來資工也有一段時間了

深深體會兩者本質上的差異

常常有人問資管跟資工有什麼不同

真要說的話
資管:去學將電腦的技術運用到企業上
資工:去學電腦本身的科技

一者學應用,一者學科學

以資管來說,資管人比較適合投入職場 所以大部分的資管人都相當的活潑

而資工呢...則適合去做研究,資工窮其一生都在研究怎麼提出更有效率的演算法

所以相較於資管,資工人通常都比較死板,感覺上死氣沉沉的

就以做PowerPoint來說好了,通常資工的的PowerPoint只要求簡單明瞭、條列分明說難聽點就是非常死版

而資管則是力求表達主旨,所以通常都會以生動的外表來做訴求,基本上就是把報告當企劃書在做,但是常常流於外表沒啥實際內容

兩者是各有優缺,不過也常常有特例(俺就是)