2017年2月23日 星期四

C++ 重載 operator + , += 重複打兩次及效能問題

重載 operator+,+= 符號

tags: C++ Concept
如何合併++=的代碼,避免寫兩份一樣的代碼

類別的定義

class Arr{
public:
    Arr(size_t len=3, int value=1): num(len) {...}
    vector<int> num;
};

1. 常數項無法呼叫函式成員

首先如果把+寫進成員函式,讓他與常數做相加會遇到一些問題。
Arr & Arr::operator+(int value){...}
Arr p1;
p1=p1+1; // ok
p1=1+p1; // error
問題在於常數沒辦法呼叫成員函式 operator+
所以必須把她寫在外面來用全域函式
Arr operator+(int value, Arr const &rhs){...}
Arr operator+(Arr const &lhs, int value){...}
如此一來就可以任意將常數放置在前與後了。

注意

如果你把該函式放置在底下,會找不到該函式的定義,在類別宣告內加入
friend Arr operator+(Arr const &lhs, Arr const &rhs);

2. + 多建一個物件

觀察一下程式代碼 + 通常都是帶等於的
Arr p1, p2;
p1 = p1+p2;
p1+p2; // 不會出錯但貌似沒作用...
可以推論出
  1. 你不能改變 p1 與 p2
  2. 你必須返回他們相加的結果
這會導致一個問題
Arr operator+(Arr const &lhs, Arr const &rhs){...}
裡面的lhs rhs你都不能動他,但又要返回一個新值
你只能在裡面創一個暫存用來接收他們的相加值
Arr operator+(Arr const &lhs, Arr const &rhs){
    Arr temp;
}

又物件的建立與複製非常消耗效能
你會在函式內建構一個新的,然後在賦值
Arr temp;
temp.num[i]=lhs.num[i]+rhs.num[i];

到了外面主程式又複製一次
然後會解構兩次p1一次是函式內的暫存,一次是函式傳出的
p1 = p1+p2;
如果有三個會解構4次p1,因為每次只能2個2個來(每個2次)

補充效能問題

從以上可以觀察到,這樣子的代碼會比較拖效能
(多呼叫一次=的複製函式,還有p1會解構2次)
p1 = p1+p2;
這樣的子的代碼能夠比較節省效能
p1 += p2;
所以如果有多項要相加
p1 += p2;
p1 += p3;
代碼拆多一點,效能會好一點
實測p=p1+p2+p3+p4;會解構6次p1,拆開寫0次

3. 合併 +, += 代碼

為了避免物件建立後才給值的狀況你可以依靠+=來規避
同時也可以合併++=的代碼,使他成為一份主代碼(才不用改2次)
+=本身並不會多複製物件
Arr & Arr::operator+=(Arr const &rhs){
    for(unsigned i = 0; i < num.size(); ++i)
        this->num[i] += rhs.num[i];
    return *this;
}
operator+ 去呼叫 +=
(注意這裡的+是全域函式)
Arr operator+(Arr const &lhs, Arr const &rhs){
    cout << "lhs+rhs : ";
    return Arr(lhs) += rhs;
}
這樣做會函式內會優化成直接建構一個有初值的物件;
而不是先建構,建好了再賦值。

已知條件
  • operator+ 會產生暫存值(複製行為)
  • operator+= 不會產生暫存直(沒有複製行為)
如果你讓 operator+= 去呼叫 operator+ 是不是就變成
兩個都有複製行為了,不應該如此浪費效能才是。

參考代碼

/*****************************************************************
Name : 重載 operator+ 與 += 函式
Date : 2017/02/22
By   : CharlotteHonG
Final: 2017/03/05
*****************************************************************/
#include <iostream>
#include <vector>
using namespace std;

class Arr{
public:
    Arr(size_t len=3, int value=1): num(len) {
        // cout << "Constructor" << endl;
        for(auto&& i : num)
            i=value;
    }
    ~Arr(){
        // cout << "Destructor" << endl;
    }
    void pri(){
        for(auto&& i : num) {
            cout << i << ", ";
        }cout << endl;
    }
public:
    Arr & operator+=(Arr const &rhs){
        for(unsigned i = 0; i < num.size(); ++i)
            this->num[i] += rhs.num[i];
        return *this;
    }
    Arr & operator+=(int value){
        for(unsigned i = 0; i < num.size(); ++i)
            this->num[i] += value;
        return *this;
    }
private:
    vector<int> num;
};

Arr operator+(Arr const &lhs, Arr const &rhs){
    cout << "lhs+rhs : ";
    return Arr(lhs) += rhs;
}
Arr operator+(int value, Arr const &rhs){
    cout << "rhs+    : ";
    return Arr(rhs) += value;
}
Arr operator+(Arr const &lhs, int value){
    cout << "lhs+    : ";
    return Arr(lhs) += value;
}
/*==============================================================*/
int main(int argc, char const *argv[]){
    Arr p;
    p=p+p;
    p.pri();
    p=1+p;
    p.pri();
    p=p+1;
    p.pri();
    return 0;
}
/*==============================================================*/

更新 20190511

關於這一份代碼
Arr operator+(Arr const &lhs, Arr const &rhs){
    cout << "lhs+rhs : ";
    return Arr(lhs) += rhs;
}
參考自 cpp ref 網站的 type 是
https://en.cppreference.com/w/cpp/language/operators
Arr operator+(Arr lhs, Arr const &rhs){
    cout << "lhs+rhs : ";
    return lhs += rhs;
}
兩個是等價的
上面的是把copy寫在Return的時候,那時候才創建一個新類別並複製。
下面的是傳入的時候就發生copy,那時候就創建一個新類別了。
我建議你用下面的版本,因為那是官方給的XDD
能想到的差別是,如果你的op+不是常規的方式,有自訂什麼特別功能,不像文中的功能只是呼叫op+=()就完事了,在定義內有複雜的運算,這時候上面的寫法,lhs這個變數就不能動他,因為傳進來的時候帶const,這會造成很大困擾的歐!
如果是常規狀態,如文內範例,就是等價的沒什麼差~
內文就不修改了,用補充的方式添加,修改還要校正QQ
沒校正怕哪邊失誤多一個字少一個字之類的出錯~~

參考:

  1. [心得] operator+ 與 operator+= 的設計
  2. 使用 friend 函式重載運算子

2017年2月21日 星期二

如何讓 Windows10 8 預設為英文輸入法 [或CMD預設為英文]

如何讓 Windows10 8 預設為英文輸入法 [或CMD預設為英文]


這其實是預設輸入法的問題而已Win10預設是中文輸入法其實很不方便
更改兩項設定即可讓系統預設都是英文輸入法了。

讓新注音輸入法 - 預設為英文

對著你右下角的輸入法(英或中的圖示)按右鍵
選擇 內容 將預設輸入模式改為英數模式
但這僅可以保證重新開機時一開始是英文輸入法而已,無法避免在別人的程序切為中文後切回來之後也變成中文了。
想像一個場景你現在開著終端機用英文輸入指令,突然想不起某個指令怎麼用,於是打開chrome切到中文輸入法打中文搜尋,這時候你切回來終端機的時候就變回英文了。
要解決這個問題就是要把輸入法分開,不要讓他用單一個狀態表示全部程序。

分開輸入法

Win10 改版之後設定變了這邊保留舊版與新板說明

Win10 1803

搜索地區及語言
然後進入進階鍵盤設定
打勾使用不同輸入法

Win10 1803 以前(不包含)

搜索地區及語言
開啟後進入
變更輸入法
進階設定
讓我為每個應用程式設定不同輸入法


完結

接下來開啟任何程式都可以保證是英文輸入法了

C++ 重載下標符號 const 與非 const 寫兩次整合成一次的辦法

重載operator[]符號

tags: C++ Concept
解決必須重複打兩次的問題

重載下標[]符號

如果一個類別的屬性是帶 const 屬性,那麼他將會呼叫 const 版本的
const Arr a(5);
const int & operator[](size_t idx) const{...}
即便不使用到帶const屬性的類別宣告
仍然有可能在重載運算符號時用上(待確認印象中記得會)
Arr a(5), b(5);
a=a+b;
你就必須寫兩份重載函式
const int & operator[](size_t idx) const{...}
int & operator[](size_t idx){...}
除非確定了,不然還是乖乖補上兩種代碼可能比較省事。
這時候就產生一個問題,代碼可能需要寫兩次,至直接導致修改時要改兩處,麻煩了不少。

參考代碼

/*****************************************************************
Name : 重載下標符號 const 函式
Date : 2017/02/20
By   : CharlotteHonG
Final: 2017/02/20
*****************************************************************/
#include <iostream>
#include <vector>
#include <numeric>

using namespace std;

class Arr{
public:
    // 建構子
    Arr(int len): arr(len){
        iota(arr.begin(),arr.end(),1);
    }
    // 重載下標符號
    int & operator[](size_t idx){
        cout << "**Non_";
        return const_cast<int&>(static_cast<const Arr&>(*this)[idx]);
    }
    const int & operator[](size_t idx) const{
        cout << "**Const**" << endl;
        return arr[idx];
    }
private:
    vector<int> arr;
};

int main(int argc, char const *argv[]){
    // **Const**
    const Arr ca(5);
    cout << ca[0] << endl;
    // **Non-Const**
    Arr a(5);
    cout << a[0] << endl;
    return 0;
}

原理

讓 非const版本 的強制去呼叫 const版本的
static_cast<const class&>(*this)
然後再強制解除 const 屬性
const_cast<typename&>
就可以整合成一份主代碼了

是否可以反過來

實際上也是可以把主代碼寫在非const上,語法的運行是可以的,但這會有一種問題,由const呼叫非const貌似有些奇怪:
都已經限制不能修改了,你還去呼叫可以修改的?
也可以換一個角度想,在強制呼叫非const版本的時候,的這個瞬間所返回的參考所參考之處是可以被修改的,const版本的函式居然會有可能被修改,這不符合const的定義。
為避免這不必要的疑慮,應該由非const版本呼叫const版本,避開這些疑慮
/*****************************************************************
Name : 重載下標符號 const 函式
Date : 2017/02/20
By   : CharlotteHonG
Final: 2017/03/17
*****************************************************************/
#include <iostream>
#include <vector>
#include <numeric>

using namespace std;

class Arr{
public:
    // 建構子
    Arr(int len): arr(len){
        iota(arr.begin(),arr.end(),1);
    }
    // 重載下標符號
    int & operator[](size_t idx){
        cout << "Non";
        // return const_cast<int&>(static_cast<const Arr&>(*this)[idx]);
        return arr[idx];
    }
    const int & operator[](size_t idx) const{
        cout << "Const";
        // return arr[idx];
        return static_cast<const int&>(const_cast<Arr&>(*this)[idx]);
    }
private:
    vector<int> arr;
};

int main(int argc, char const *argv[]){
    // **Const**
    const Arr ca(5);
    cout << " = " << ca[0] << endl;
    // **Non-Const**
    Arr a(5);
    cout << " = " << a[0] << endl;
    return 0;
}

參考

ilikekotomi:參考Scott Meyers的Effective C++ 3rd的第三條款

2017年2月9日 星期四

台灣 無法開啟大陸 微博站 [不用VPN翻牆]

台灣 無法開啟大陸 微博站 [不用VPN翻牆]

設定上還挺奇怪的,我發現只有是登入狀態
才能夠看見微博的網站,否則會直接出現404
可是看不見網站怎麼登入呢,連輸入密碼的地方都看不見

解決辦法1

發現404就404繼續等或多開啟次有可能會好
2017.10.21 這一招好像失效了 方法2還有效

解決辦法2

先登入台灣站

台灣微博:http://tw.weibo.com/
這樣瀏覽器就會記住目前的登入狀態,而這個跟大陸站是共通的

然後再登入大陸微博站

因為這時候還是看不見網頁狀態,所以必須手動輸入這個網址來登入
登陸網址:http://weibo.com/login.php
利用台灣站的登錄紀錄,然後再輸入大陸站的登入網址就可以成功登入了。

完成

這時候就可以進入微博查看任何公開貼文了
微博主站:http://weibo.com

解決辦法3 - 手機、平板的網站不受限

直接用以下網址觀看也可以