2014年7月18日 星期五

auto_ptr 自動管理配置資源

對於使用new動態配置的資源,在不使用時必須記得delete,以釋放記憶體空間,然而動態記憶體配置很容易發生忘了delete,或是對同一個記憶體 位址delete兩次(例如一個物件被指定給兩個指標),或是對一個已經被delete的位址再作讀寫動作。

C++標準函式庫中提供auto_ptr,可以協助您動態管理new而建立的物件,要使用auto_ptr,您要含入memory表頭檔,例如:

#include <memory>

auto_ptr可以指向一個以new建立的物件,當auto_ptr的生命週期結束後,所指向的物件之資源也會被釋放,在建立auto_ptr時必須指 定目標物件之型態,例如:

auto_ptr<int> iPtr (new int(100));
auto_ptr<string> sPtr (new string("caterpillar"));

操作auto_ptr就像操作沒有使用auto_ptr的指標一樣,例如:
cout << *iPtr << endl; // 顯示100
if(sPtr->empty())
    cout << "字串為空" << endl;


您也可以建立一個未指向任何物件的auto_ptr,例如:

auto_ptr<int> iPtr;

未指向任何物件的auto_ptr不可以取值,否則會發生不可預期之結果,既然不可取值,如何判斷它是否有指向物件呢?您可以使用get()函式,它會傳 回所指向物件的位址,如果傳回0,表示不指向任何物件,如果不指向任何物件,您可以使用reset()來讓它指向一個物件,例如:

if(iPtr.get() == 0) {
    iPtr.reset(new int(100));
}

reset()可以接受一個指標或是0表示不指向任何物件,reset()會先delete目前指向的物件,然後重新指向新的物件,您也可以使用 release()釋放auto_ptr管理所指向物件的職責。


auto_ptr可以使用另一個auto_ptr來建立,這會造成所有權的轉移,例如:
auto_ptr<SafeArray> ptr1(new SafeArray(19));
auto_ptr<SafeArray> ptr2(ptr1);

當使用ptr1來建立ptr2時,ptr1不再對所指向物件的資源釋放負責,職責交給了ptr2,在使用指定運算時,也有類似的行為,例如:

auto_ptr<SafeArray> ptr1(new SafeArray(19));
auto_ptr<SafeArray> ptr2(new SafeArray(20));
ptr2 = ptr1;

ptr2所指向的物件會先被delete,然後ptr1的屬性會複製至ptr2,也就是ptr1所指向的物件,現在由ptr2指向它了,ptr1不再負責 所指向物件的資源釋放。

auto_ptr的資源維護動作是以inline的方式來完成,也就是在編譯時會被擴展開來,所以使用auto_ptr並不會犧牲效率。

最後要注意的是,auto_ptr不能用來管理動態配置而來的陣列,如果用它來管理動態配置而來的陣列,結果是不可預期的。



2014年7月17日 星期四

類別簡介

1. 對於簡單的成員函式,您可以將之實作於類別定義中,在類別定義中即實作的函式會自動成為inline函式,例如:
  • Ball.h
#include <string>
using namespace std;

class Ball { 
public: 
    Ball(); 
    Ball(double, const char*); 
    Ball(double, string&); 
 
    // 實作於類別定義中的函式會自動inline
    double radius() {
        return _radius;
    }
 
    string& name() {
        return _name; 
    }
 
    void radius(double radius) {
        _radius = radius;
    } 
 
    void name(const char *name) {
        _name = name;
    }
 
    void name(string& name) {
        _name = name;
    }
 
    double volumn() {
        return (4 / 3 * 3.14159 * _radius * _radius * _radius); 
    }
 
private:
    double _radius; // 半徑 
    string _name; // 名稱 
};

2. 在定義類別時,如果您只是需要使用到某個類別來宣告指標或是參考,但不涉及類別的生成或操作等訊息,則您可以作該類別的前置宣告(Forward declaration),而不用含入該類別的定義,例如:
  • Test.h
class Ball;

class Test { 
public:
    Test();
    Test(Ball*); 
 
    Ball* ball(); 
    void ball(Ball*);

private:
    Ball *_ball; // 名稱 
};


3. 如果您的類別定義有單一參數的建構函式(或除了第一個參數之外,其它參數都有預設值的建構函式),則預設會有自動轉換的作用,例如:

class Ball { 
public: 
    Ball(const char*); 
   ...
};

則您可以使用以下的方式來建構物件並初始化:

Ball ball = "Green ball";

預設的轉換行為是由編譯器施行的,但有時是有危險的,如果您不希望編譯器自作主張,則您可以使用explicit修飾,告訴編譯器不要自作主張:
class Ball { 
public: 
    explicit Ball(const char*); 
   ...
};


4. 在建構函式的初始化設定語法中,您還可以使用成員初始化列表(Member initialization list),例如:
  • SafeArray.cpp
#include "SafeArray.h"

SafeArray::SafeArray(int len) : length(len) {
    _array = new int[length];
}

...略

要被初始化的成員跟在參數列之後,要被設定給成員的引數被放在括號中,如果有多個成員要初始化,則以逗號分隔。

參考(Reference)

資料來源: http://openhome.cc/Gossip/CppGossip/Reference.html
參考(Reference)型態代表了變數或物件的一個別名(Alias),參考型態可以直接取得變數或物件的位址,並間接透過參考型態別名來操作物件, 作用類似於指標,但卻不必使用指標語法,也就是不必使用*運算子來提取值。

要定義參考型態,在定義型態時於型態關鍵字後加上&運算子,例如:

int var = 10;  // 定義變數
int *ptr = &var; // 定義指標,指向var的位址
int &ref = var;  // 定義參考,代表var變數

上面的程式中,最後一行即是在定義參考型態,注意參考型態一定要初始化,例如下面的定義是不能通過編譯的:

int &ref; // error, `ref' declared as reference but not initialized 

為何參考型態一定要初始化?因為參考初始化後就不能改變它所代表的物件,任何指定給參考的值,就相當於指定給原來的物件,例如:


#include <iostream>
using namespace std;

int main() {
    int var = 10;
    int &ref = var;
 
    cout << "var: " << var 
         << endl;
    cout << "ref: " << ref
         << endl;
 
    ref = 20;

    cout << "var: " << var 
         << endl;
    cout << "ref: " << ref
         << endl;

    return 0;
}

執行結果:
var: 10
ref: 10
var: 20
ref: 20

您也可以參考至一個字面常量,例如:
const int &ref = 10;

為什麼要在前面加上const才能參考至一個字面常量呢?您知道字面常量是不可定址的,為了能夠讓符合參考定址的語義,上面這段程式編譯器會傳如下的轉 換:

int tmp = 10;
const int &ref = tmp;

先想想沒有加上const的情況,如果您對ref重新指定值,則實際改變的是tmp的值,而不是字面常量10,這就在符合字面常量無法取址(也就無法改變 位址上的值)的語義,但使用者可能困惑明明改變了ref,為何字面常量沒有改變,所以加上const,明確指示不可以再重新指定值給ref,例如:

const int &ref = 10;
ref = 20; // error, assignment of read-only reference `ref' 

如果要定義指標型態的參考該如何呢?很簡單,指標型態是使用type*來宣告,而參考則是在名稱前加上&,所以指標型態的參考就如下所 示:

type *&refOfPtr = somePtr;

一個具體的例子如下:

int var = 10;
int *ptr = &var;
int *&ref = ptr;

舉一反三的話,如果有個const變數,您可以使用一個const指標,並可以如下宣告一個指標的參考:

const int var = 10;
const int *ptr = &var;
const int *&ref = ptr;

事實上很少會直接如上的方式來使用參考,而是用於函式傳遞時一種「傳參考」(Pass by reference)方式,目的在於可於函式中直接操作目標變數或物件,或者是避免複製一個大型物件,在之後要紹函式時會見到相關應用


指標與陣列

1.
被const宣告的變數一但被指定值,就不能再改變變數的值,您也無法對該變數如下取值:


const int var = 10;
var = 20; // error, assignment of read-only variable `var' 
int *ptr = &var; // error,  invalid conversion from `const int*' to `int*'


2.
用const宣告的變數,必須使用對應的const型態指標才可以:
const int var = 10;
const int *vptr = &var;

同樣的vptr所指向的記憶體中的值一但指定,就不能再改變記憶體中的值,您不能如下試圖改變所指向記憶體中的資料:

*vptr = 20; // error, assignment of read-only location 


3. 
另外還有指標常數,也就是您一旦指定給指標值,就不能指定新的記憶體位址值給它,例如:
int x = 10;
int y = 20;
int* const vptr = &x;
vptr = &x;  // error,  assignment of read-only variable `vptr' 


4.
在某些情況下,您會想要改變唯讀區域的值,這時您可以使用const_cast改變指標的型態,例如:
void foo(const int* p) {
    int* v = const_cast<int*> (p); 
    *v = 20; 
}

5. 
陣列的動態配置
int *arr = new int[1000];

用完要delete,記得要加[]
delete [] arr;


6.
#include <iostream> 
using namespace std; 

int main() {
    char *str = "hello"; 
    void *add = 0; 

    add = str; 
    cout << str << "\t" 
         << add << endl; 

    str = "world"; 
    add = str; 
    cout << str << "\t" 
         << add << endl; 
 
    return 0; 
}

執行結果:

hello    0x440000
world   0x440008



STL 型式(STL idiom) vector

資料來源: http://openhome.cc/Gossip/CppGossip/vector2.html

vector的STL型式,其實就是以物件導向的方式來操作vector(如果您還沒接觸過物件導向程式設計,這邊介紹的可能稍有難度),以物件的方式來 操作vector是比較被鼓勵的方式,以下將介紹幾個vector的基本操作。

您可以建構一個元素為空的vector物件:

vector<int> ivector;

如果打算將元素放入vector中,可以使用push_back(),例如:

for(int i = 0; i < 5; i++) {
    ivector.push_back(i);
}

如果打算將元素循序取出,則可以begin()與end()方法分別傳回起始位置的iterator與結束位置的iterator,例如:

for(vector<int>::iterator it = ivector.begin();
    it != ivector.end();
    it++) {
          
    cout << *it << " ";
}
cout << endl;

iterator是標準函式庫定義類別(Class),它是一個指標,指向iterator物件的真正位址,對它進行++的動作,表示移動至 iterator的下一個元素,對它使用*運算子(Dereferences operator),表示提取出iterator目前位址的值,如果iterator走訪至結束位置的iterator的位址,表示元素走訪完畢。

雖然您可以使用下標運算子[ ]來存取vector的元素,但實際上要知道vector與陣列本質上是不相同的,當您如最上頭那樣宣告一個空的vector物件時,其容量 (capacity)為0,長度(size)也為0,所以此時您不能使用ivector[0]來取得第一個元素值,因為實際上ivector中還沒有任何 的元素。

當使用push_back()將元素加入vector時,vector的長度會自動增長,由於每次增長度都要配置記憶體過於沒有效率,所以vector會 自動先增加足夠的容量,當元素的長度超過容量時,才會再重新配置新的容量,您可以使用capacity()取得,使用size()取得元素長度,下面這個 程式綜合以上的幾個介紹作了示範:
 

#include <iostream> 
#include <vector>
using namespace std; 

int main() { 
    vector<int> ivector;
 
    for(int i = 0; i < 10; i++) {
        ivector.push_back(i);
    }
 
    for(vector<int>::iterator it = ivector.begin();
        it != ivector.end();
        it++) {
 
        cout << *it << " ";
    }
    cout << endl;
 
    cout << "capacity: " << ivector.capacity() << endl
         << "size: " << ivector.size() << endl;
 
    return 0; 
}

結果如下:
0 1 2 3 4 5 6 7 8 9
capacity: 16
size: 10

如果打算對vector進行排序、尋找、反轉等操作,可以使用標準函式庫中的泛型演算法,要使用泛型演算法必須先含入表頭檔:
#include <algorithm>

下面這個程式直接示範了排序、尋找、反轉等操作:


#include <algorithm>
#include <iostream> 
#include <vector>
using namespace std; 

int main() { 
    int iarr[] = {30, 12, 55, 31, 98, 11, 41, 80, 66, 21};
    vector<int> ivector(iarr, iarr + 10);
 
    // 排序 
    sort(ivector.begin(), ivector.end());
 
    for(vector<int>::iterator it = ivector.begin();
        it != ivector.end();
        it++) {
 
    cout << *it << " ";
    }
    cout << endl;

    cout << "輸入搜尋值:";
    int search = 0;
    cin >> search;
 
    vector<int>::iterator it = 
    find(ivector.begin(), ivector.end(), search);
 
    if(it != ivector.end()) {
        cout << "找到搜尋值!" << endl;
    }
    else {
        cout << "找不到搜尋值!" << endl;
    }
 
    // 反轉 
    reverse(ivector.begin(), ivector.end());
 
    for(vector<int>::iterator it = ivector.begin();
        it != ivector.end();
        it++) {
 
        cout << *it << " ";
    }
    cout << endl;
 
    return 0; 
}

執行結果:
11 12 21 30 31 41 55 66 80 98
輸入搜尋值:41
找到搜尋值!
98 80 66 55 41 31 30 21 12 11

陣列型式(Array idiom) vector

資料來源:http://openhome.cc/Gossip/CppGossip/vector1.html


您可以使用vector來替代陣列,並使用陣列型式(Array idiom)的方式來操作vector,要使用vector,必須含入vector表頭檔:
#include <vector>

要建立vector型態的物件(Object),您必須提供元素型態與長度資訊,例如下例中建立int元素的vector,並擁有10個元素:

vector<int> ivector(10);

您可以模彷陣列中存取元素的方式來存取vector的元素,使用下標(Subscript)運算子並指定索引來取得指定的元素,例如:


#include <iostream> 
#include <vector>
using namespace std; 

int main() { 
    vector<int> ivector(10);
 
    for(int i = 0; i < ivector.size(); i++) {
        ivector[i] = i;
    }
 
    for(int i = 0; i < ivector.size(); i++) {
        cout << ivector[i] << " ";
    }
    cout << endl;

    return 0; 
}

執行結果: 

0 1 2 3 4 5 6 7 8 9

與陣列型態不同的是,vector記得自己的長度資訊,您可以使用size()查詢vector的元素長度,或是使用empty()測試長度是否為0。

當您宣告vector物件時,所有的元素值會有與型態相應的初始值,例如算術相關型態會被初始為0,指標型態初始為0(表示不指向任何位址),如果您打算 將所有元素初始為指定的值,則可以如下:

vector<int> ivector(10, 5);

上例中將宣告有10個元素的vector,並將所有的元素值初始為5。

如果您願意,您可以使用一個陣列來作為建構vector的引數(Argument),如此建構出來的vector會擁有陣列所指定的元素初值,在這之前, 您要先知道,宣告一個陣列時,陣列名稱所儲存的是第一個元素在記憶體中的位址值,對該位址值加一,表示移至下一個元素,當使用陣列來建構vector時, 要指定的是元素的起始位址與最後一個元素的下一個位址,例如以整個陣列來建構vector的話,可以這麼撰寫:

#include <iostream> 
#include <vector>
using namespace std; 

int main() { 
    int iarr[] = {1, 2, 3, 4, 5};
 
    vector<int> ivector(iarr, iarr + 5);
 
    for(int i = 0; i < ivector.size(); i++) {
        cout << ivector[i] << " ";
    }
    cout << endl;
 
    return 0; 
}

執行結果: 

1 2 3 4 5

如果打算只使用陣列中的某個範圍來建構vector,例如複製索引1到3的元素,則可以如下:

#include <iostream> 
#include <vector>
using namespace std; 

int main() { 
    int iarr[] = {1, 2, 3, 4, 5};
 
    vector<int> ivector(iarr + 1, iarr + 4);
 
    for(int i = 0; i < ivector.size(); i++) {
        cout << ivector[i] << " ";
    }
    cout << endl;

    return 0; 
}

執行結果: 

2 3 4

vector可以使用另一個vector作為引數來建構,例如以ivector1作為引數來建構ivector2:
vector<int> ivector1(5, 1);
vector<int> ivector2(ivector1);

vector可以使用=直接指定給另一個vector,這會將vector的值複製給被指定的vector,例如:


#include <iostream> 
#include <vector>
using namespace std; 

int main() { 
    vector<int> ivector1(5, 1);
    vector<int> ivector2; // 定義一個空的vector
 
    ivector2 = ivector1;
 
    for(int i = 0; i < ivector2.size(); i++) {
        cout << ivector2[i] << " ";
    }
    cout << endl;
 
    ivector2[0] = 2;

    for(int i = 0; i < ivector2.size(); i++) {
        cout << ivector2[i] << " ";
    }
    cout << endl;
 
    for(int i = 0; i < ivector1.size(); i++) {
        cout << ivector1[i] << " ";
    }
    cout << endl;
 
    return 0; 
}

由於指定時是將值一一複製加入目標vector,所以您改變目標vector的值,並不會影響原來的vector, 執行結果如下: 

1 1 1 1 1
2 1 1 1 1
1 1 1 1 1

陣列

1.
宣告陣列之後,陣列所配置到的記憶體空間中所儲存的數是未知的,所以在初始陣列元素值之前,當中的元素值是未知的,如果您在宣告變數時尚未決定陣列中的 值,可以這麼宣告陣列: 
    int irr[10] = {0}; 
    double darr[10] = {0.0}; 
    char carr[10] = {'\0'};

    bool barr[10] = {false};

2.
如果您在宣告陣列時只希望初始幾個元素,則可以這麼宣告: 
int iarr[5] = {0, 1}; 
double darr[5] = {0.0, 0.1}; 
char carr[5] = {'A', 'B'}; 
bool barr[5] = {false, true};

像上例中,都只初始索引0與索引1的兩個元素,其它未初始的元素,整數的話會自動初始為0,浮點數的話會自動初始為0.0,字元的話會自動初始為空字元 ('\0'),而boolean數的話會自動初始為false。 


3.
如何得知陣列的長度:
    int iarr[] = {1, 2, 3, 4, 5, 6};
  陣列長度為: sizeof(iarr) / sizeof(iarr[0])


4.
不可以將陣列直接指定給另一個陣列,或是直接比較兩個陣列是否相同
int arr1[5];
int arr2[5];
...
arr1 = arr2; // 錯誤!不能直接指定陣列給另一個陣列
...
if(arr1 == arr2) { // 錯誤!不能直接比較兩個陣列是否相同
    ...
}

只能循序一個一個元素進行複製或比對


Java整理

1. >>> : shift right並且強迫補0
   >>是根據signed bit去補


2014年7月15日 星期二

decodeFile/decodeStream的問題

decodeFile( )/decodeStream( )可能會有OutOfMemory exception的問題
所以改用decodeFileDescriptor( )會比較好
它呼叫與decodeFile( )/decodeStream( )不同的native function
其Bitmap不是用標準的Java方式去allocate的  應該是在virtual heap外另外allocate

參考資料:
http://www.cnblogs.com/akira90/archive/2013/03/15/2960666.html