顯示具有 operator 標籤的文章。 顯示所有文章
顯示具有 operator 標籤的文章。 顯示所有文章

2017年3月22日 星期三

虛擬函式的實際用途 與 上下選型的概念

虛擬函式的實際用途 與 繼承時上下轉型的概念

tags: C++ Concept
  1. 敘述什麼時候必須、一定要使用虛擬函式,以及他真正的用途
  2. 敘述什麼是上下轉型及說明dynamic_cast的操作時機以及他與 static_cast 有什麼不同

虛擬函式

虛擬函式並不是,能夠讓函式被繼承這麼簡單,這是從答案反推的單向結果論

檢視

正確的形容方式應該說成,在以指針檢視時,能夠依據正確的 類型 自動套用正確的函式使用

選擇

virtual 可以把他當作可有可無的低優先權命令,當編譯器使用指標檢視遇到
目前儲存型態實際型態 時自動選擇實際的型態函式

示例

這麼說其實還是有點不好理解,實際由以下代碼來說明
代碼結構
class A {
public:
    A(){}
    void virtual fun(){
        cout << "this is A" << endl;
    }
};

class B: public A{
public:
    B(){}
    void fun(){
        cout << "this is B" << endl;
    }
    int i=1;
};

當我們使用指針檢視(操作)時
A *a, *b;

a = new A;
b = new B;
這裡的 A 我把它稱為 目前儲存型態 ,也就是我們告訴編譯器,會已 A 的角度(型態),來檢視(操作)這個指針
可注意到 b 它的實際型態應該是 B 但是被強迫只用 A 型態檢視,這裡稱為向上轉型,也就是 b 的實際型態應該為 B

(*a).fun();
目前 a 的檢視型態為 A 所以可以正確地呼叫 A 裡面的成員函式,這很容易理解並沒有什麼問題。

(*b).fun();
這裡就有問題了,b 的實際型態是B,可是卻被宣告用 A 的角度來檢視,一般來說會直接呼叫 A 的函式成員
但如果在 A 的資料函式加上 Virtual ,將可以自動選擇實際的 B 函式

上下轉型

在本範例中 A 稱為父類別 B 稱為子類別
  • 向上轉型:由子類別轉型為父類別(或再上一層)
  • 向下轉型:由父類別轉型為子類別(或再下一層)
他們之間的關係具有以下特性
  • B 繼承了 A 的所有資源,A 有東西 B 也一定有
  • B 新增的資料成員與函式 A 不會擁有
依據剛剛的 指針檢視行為 可以把想像成是我要檢視到哪裡。
(註:只是我自己想到的一個類比說明方法)
比如說的資料的地址長度可能是這樣
  單位 1    單位2
---------------------
  A 地址  | xxxxxxx
---------------------
  B    地      址
---------------------
          |    i
---------------------
可以看到 B 的角度檢視的範圍比較長一些,如果你用 A 的角度檢視,就無法見到變數 i 。
由此可見,你要將任何子類別向上轉型成父類別都不會出狀況的,只不過會無法存取(不會丟失),所有自己額外擁有的成員。

向下轉型可能造成非法存取

但是如果你要做向下轉型就會出問題的,如果他當初建立的時候就是用父型態建立,你將檢視到沒有被初始的垃圾範圍,而且這就像是越界存取一樣是非法存取。
為了避免這個問題可以使用 dynamic_cast<typename*>(T) 編譯器將自動檢視是否為合法行為。

安全的向下轉型

剛剛我們的 b 地址所初始化的為子類別型態初始化,只不過我們宣告成父類別的檢視行為。
我們可以將他安全的向下轉型,因為他當初初始的話就是一個 B 的型態,轉型後不會造成越界存取的非法行為。
// 向下轉型
cout << "(*b).i=" << dynamic_cast<B &>(*b).i << endl;
如果你很確定保證絕對不會出錯那麼也可以使用 static_cast<>
// 向下轉型
cout << "(*b).i=" << static_cast<B &>(*b).i << endl;
在向上轉型時,因為保證不會出意外所以使用兩者不會有什麼區別,只不過使用 dynamic_cast<> 可以具備上下轉型的閱讀語意,可能比較容易閱讀代碼。

非法的向下轉型

// 非法的向下轉型
cout << "(A->B). = " << static_cast<B &>(*a).i << endl;
這將會導致越界的非法存取,程式會崩潰沒有回應

範例代碼

/*****************************************************************
Name : 
Date : 2017/03/22
By   : CharlotteHonG
Final: 2017/03/22
*****************************************************************/
#include <iostream>
using namespace std;

class A {
public:
    A(){}
    void virtual fun(){
        cout << "this is A" << endl;
    }
};

class B: public A{
public:
    B(){}
    void fun(){
        cout << "this is B" << endl;
    }
    int i=1;
};

/*==============================================================*/
int main(int argc, char const *argv[]){
    // 用父類別來操作所有指標
    A *a, *b;
    a = new A;
    b = new B;

    // 自動選擇正確的類型
    (*a).fun();
    (*b).fun();

    // 無法存取 (找不到該成員)
    // cout << "(*b).i=" << (*b).i << endl; // Error

    // 安全向下轉型
    cout << "(*b).i = " << dynamic_cast<B &>(*b).i << endl;

    // 非法的向下轉型
    cout << "(A->B). = " << static_cast<B &>(*a).i << endl;

    return 0;
}
/*==============================================================*/

2017年3月21日 星期二

重載雙下標 operator[][] 的方法

重載雙重下標 operator[][] 的方法

tags: C++ Concept
一般而言 operator[] 只能重載一次,因為第一次的指標就已經指向元素了,不可能再讓元素去呼叫 operator[] 除非是二維陣列還能夠在做一次呼叫
需要透過一些技巧來實現雙下標這個功能,主要最主要的差別是
Arr a;
a[0];   // 一維存取
a[0][0] // 二維存取
不過這種語意可能很容易讓人誤會,比如說如果是三維陣列的話就沒辦法區分這種方式的重載了。
最關鍵的技巧在於需要重載的 operatoe[] 要返回第二個類別,利用這個類別來重載第二個 operatoe[] ,並重載第二個類別的 operatoe T& ,即便只有單一個下標返回的類別也能夠正確的傳回元素。
參考代碼:
/*****************************************************************
Name : 
Date : 2017/03/18
By   : CharlotteHonG(整理)
Final: 2017/03/18

原文:https://www.ptt.cc/bbs/C_and_CPP/M.1478167551.A.5ED.html
原始代碼:http://ideone.com/Tm4Bgk
*****************************************************************/
#include <iomanip>
#include <iostream>
#include <numeric>
#include <vector>
using namespace std;

template<class T>
class Middle {
public:
    using size_type=typename vector<T>::size_type;
public:
    // 建構子
    Middle(vector<T> &vec, const size_type column, 
        const size_type index) noexcept
        :vec_{addressof(vec)}, column_{column}, index_{index}{}
    // 複製建構子
    Middle(const Middle &) = default;
public:
    // 取址函式
    operator T&(){
        return (*vec_)[index_];
    }
    // 複製函式
    Middle& operator=(const Middle &) = delete;
    void operator=(T const & data){
        (*vec_)[index_] = move(data);
    }
    // 重載下標
    T& operator[](const size_t i){
        return (*vec_)[index_*column_+i];
    }
private:
    vector<T> *vec_;
    const size_type column_;
    const size_type index_;
};

template<class T>
class Const_Middle {
public:
    using size_type=typename vector<T>::size_type;
public:
    // 建構子
    Const_Middle(const vector<T> &vec, const size_type column, 
        const size_type index) noexcept
        :vec_{addressof(vec)}, column_{column}, index_{index}{}
    // 複製建構子
    Const_Middle(const Const_Middle &) = default;
public:
    // 取址存取
    operator const T&(){
        return (*vec_)[index_];
    }
    // 複製函式
    Const_Middle& operator=(const Const_Middle &) = delete;
    // 重載下標
    const T& operator[](const size_t i){
        return (*vec_)[index_*column_+i];
    }
private:
    const vector<T> *vec_;
    const size_type column_;
    const size_type index_;
};

template<class T>
class Test {
public:
    using size_type=typename vector<T>::size_type;
public:
    // 建構子
    Test(const size_type row,const size_type column)
        :vec_(row*column), column_{column}{}
    Test const& pri() const{
        for(unsigned j = 0; j < vec_.size()/column_; ++j) {
            for(unsigned i = 0; i < column_; ++i) {
                cout << vec_[j*column_ + i] << ", ";
            } cout << endl;
        } cout << endl;
        return (*this);
    }
public:
    // 重載下標
    Middle<T> operator[](const size_t i){
        return Middle<T>{vec_,column_,i};
    }
    Const_Middle<T> operator[](const size_t i) const{
        return Const_Middle<T>{vec_,column_,i};
    }
public:
    vector<T> vec_;
private:
    const size_type column_;
};

int main(int argc, char const *argv[]){
    // 初始化
    Test<int> test(3, 4);
    iota(begin(test.vec_), end(test.vec_), 0);
    const Test<int> & test2 = test;
    // 測試
    test.pri();
    test[0][0] = 7;
    test[1] = 7;
    test.pri();
    test2.pri();
}

operator=() 進階重載,具2種或多種語意重載

operator=() 進階重載,具2種或多種語意重載

tags: C++ Concept
除了一般重載等號之外,如果你想做不同的操作,比如說你希望在指定單一元素的時候可以用等號做存取
Raw a(4, 4);
假設這是一張4x4總共16格的一維陣列

a[0] = -1;
你可以透過下標的等號直接存取

a.at2d(0, 1) = -1;
也可以透過函式進行二維模式的存取

// 區塊等號賦值
a.blk(2) = a.blk(3);
甚至可以在不影響原本運算子的情況下進型區塊複製
區塊指的是以4個單位為一組計算,全部16個可以分成4組
最左上4個為0,最右下4個為3

// 不影響原本的等號
Raw b(4, 4);
a=b;
仍然是預設複製運算子,複製所有資源

用途

這在某些複雜的運算上,可以很大的程度的減少代碼量與程式設計難度
只不過效能上可能多少有些損耗。

參考代碼:
/*****************************************************************
Name : 
Date : 2017/03/21
By   : CharlotteHonG
Final: 2017/03/21
*****************************************************************/
#include <iostream>
#include <iomanip>
#include <vector>
#include <numeric>
#include <cmath>
#include <typeinfo>
using namespace std;

class Raw {
public:
    Raw(size_t y, size_t x): col(x), img(y*x){
        iota(img.begin(), img.end(), 1);
    }
public: // 運算子
// 重載下標符號
int & operator[](size_t idx){
    return const_cast<int&>(static_cast<const Raw&>(*this)[idx]);
}
const int & operator[](size_t idx) const{
    return img[idx];
}
int & at2d(size_t y, size_t x){
    return const_cast<int&>(
        static_cast<const Raw&>(*this).at2d(y, x));
}
const int & at2d(size_t y, size_t x) const{
    return (*this)[y*col+x];
}
public: // 基礎函式
void info(){
    for(unsigned j = 0; j < img.size()/col; ++j) {
        for(unsigned i = 0; i < col; ++i) {
            cout << setw(3) << img[j*col+i];
        }cout << endl;
    }cout << endl;
}
void get_block(size_t h, size_t w);
private:// 資料成員
    size_t col;
    vector<int> img;
private:
    class Block;
    vector<Block> blk_p;
public:
    Block blk(size_t pos);
};

// 轉介的類別
class Raw::Block{
public:
    #define BlkSize 4 // 區塊大小
    Block(): p(BlkSize){}
    Block(Raw & img, size_t pos): p(BlkSize){
        size_t Sidelen = sqrt(BlkSize);
        // 取得對應位置
        size_t pos_y=((pos/((img.img.size())/img.col/Sidelen))*Sidelen);
        size_t pos_x=((pos%(img.col/Sidelen))*Sidelen);
        // 複製指標
        for(unsigned j=0, c=0; j < Sidelen; ++j)
            for(unsigned i = 0; i < Sidelen; ++i, ++c)
                p[c] = &img.at2d(pos_y+j, pos_x+i);
    }
    // 深層拷貝
    Block & operator=(Block const & rhs){
        cout << "C &" << endl;
        if (this == &rhs)
            return (*this);
        for (unsigned i = 0; i < p.size(); ++i)
            *(p[i]) = *(rhs.p[i]);
        return (*this);
    }
    // 淺層拷貝
    Block & copy(Block const & rhs){
        if (this == &rhs){
            return (*this);
        } else {
            p = rhs.p;
        } return (*this);
    }
    Block & copy(Block && rhs){
        if (this == &rhs){
            return (*this);
        } else {
            p = std::move(rhs.p);
        } return (*this);
    }
    void info(){
        for(auto&& i : p) {
            cout << setw(3) << *i ;
        }cout << endl;
    }
public:
    vector<int*> p;
};
auto Raw::blk(size_t pos)-> Block{
    return Block((*this), pos);
}

void Raw::get_block(size_t h=2, size_t w=2){
    size_t len = (img.size()/col/h) * (col/w);
    // cout << "len=" << len << endl;
    this->blk_p.resize(len);
    // blk_p 預載 len 個區塊
    for (unsigned i = 0; i < len; ++i){
        this->blk_p[i].copy( Block((*this), i) );
        // this->blk_p[i].info();
    }
}

/*==============================================================*/
int main(int argc, char const *argv[]){
    Raw a(4, 4);
    a.info();
    // 一般等號賦值
    a[0] = -1;
    a.at2d(0, 1) = -1;
    a.info();
    // 區塊等號賦值
    a.blk(2) = a.blk(3);
    a.info();
    return 0;
}
/*==============================================================*/

盡可能的為你的程式設計空建構子

盡可能的為你的程式設計空建構子

tags: C++ Concept
在某些情況下沒有空的建構子可能會導致很難尋找的錯誤,比如說Vector的初始化。
/*****************************************************************
Name : 
Date : 2017/03/21
By   : CharlotteHonG
Final: 2017/03/21
*****************************************************************/
#include <iostream>
#include <vector>
using namespace std;

class ClassB{
public:
    ClassB(int i){}
};

class ClassA {
public:
    ClassA(){
        // i.resize(1); // Error
    }
public:
    vector<ClassB> i;
};
/*==============================================================*/
int main(int argc, char const *argv[]){
    ClassA a;
    return 0;
}
/*==============================================================*/
上述代碼因為沒有空建構子導致 vector 初始化,或調整長度時出問題
ClassB(int i=1){}
ClassB(){}
補上空的建構子或是具有預設數值的引入參數