2018年12月13日 星期四

解決 Presaria CQ40 Linux Mint wifi driver

參考文章: https://ubuntuforums.org/showthread.php?t=2181014
      sudo apt-get install firmware-b43-installer
      sudo modprobe -r b43 && sudo modprobe b43

2018年12月5日 星期三

c++ 中的別名 &, 與無名 &&, 和指標 * 等變數的複雜關係

c++ 語法中, 正常變數, 別名 & 變數和無名 &&變數的重要觀念:
1. 等號右邊(來源物件)必定是一個實體物件, 當它沒有具體名稱時稱為無名物件, 際上編譯器視需要會產生唯一識別碼當作名稱, 可以理解成用 && 來代表無名變數
2. 一旦實體化後賦予一個名稱, 就是具名物件, 也就是正常變數.
3. 如果宣告變數前置 & 符號, 代表的是別名, 它並不會產生新物件, 只是賦予一個名稱去共用等號右邊的資源, 可以理解成用 & 來代表別名變數.
4. 當用等於 = 符號指定物件時, 如果等號左邊(目標物件)的變數名稱未曾實體化過, 就會先呼叫建構式去產生物件, 再指定給該變數, 該變數就成為具名物件. 也就是正常變數
5. 如果傳進建構式的來源物件是無名物件(T  &&), 則呼叫移動型建構式, 如果是具名物件(T &)則呼叫複製型建構式
6. 如果用等於 = 符號指定物件時, 如果等號左邊的變數(目標物件)曾經實體化過, 意味著要根據來源物件去更新目標物件的內容
7.如果實體化的目標物件, 用等於 = 符號指定新物件, 則會呼叫等號 = 運算式, 根據來源物件, 如果是無名物件(T &&)則呼叫移動型運算式:  operator =(T &&), 如果是具名物件(T &)則呼叫複製型運算式: operator (T &), 去更新目標物件的內容
#include <stdio.h>
#include <iostream>
int main(){
    int a=3; // 3 是無名整數 &&,  建構新的物件 a = int(3), 之後無名變數 && 自動消滅
    int b=a;// 建構新的物件 b = int(a),  a, b 是正常變數, 但 b 與 a 不同實體
    int &c=b; // c 只是 b 的別名, b 與 c 共用一個實體
    int &&d=std::move(a); //  d 與 a 共用實體, 等同於 int &d = a; 但 d 不會消滅
    printf("\na=%d\n",a);  // 不同於無名的 3
    printf("b=%d\n",b);    //   不同於 a 的 3
    printf("c=%d\n",c);   //     等同於 b 3
    printf("d=%d\n",d);  //   等同於 a 的 3
    d=4;
    c=2;
    printf("\na=%d\n",a); // 4, a 內容跟著改變
    printf("b=%d\n",b); // 2, b 內容跟著改變
    printf("c=%d\n",c);  // 2, c 也變了
    printf("d=%d\n",d);   // 4, d 也變了

多變數指向相同位置時, 意味著該位置被多個變數參照(reference). 取出變數的參考位址要在變數前面加上  & 符號(注意與別命變數之差異: 別名變數放在目標物, 位址擷取則是在來源物), 取出指標的內容值則要在變數前慣上 * 符號.
    int    a;     // a 是一般變數, &a 就是該變數的參考位址
    int*  ptr; //  ptr 是指標變數, *ptr 就是指標的內容, 因此宣告成  int  *ptr; 也無妨
    ptr = &a;// 指標變數必須先初始化, 將 ptr 指向 a 的位置後, *ptr 與 a 的內容相同
如果覺得主角 * 太難理解, 用符號 & 可以宣告 a 的別名(alias) 變數  b(註: 必須用符號 = 同時去初始化)
    int   a;
    int&   b = a;// 也可以宣告成  int   &b=a; 也就是指定 b 是 a 的別名(名稱不同).
一旦理解了指標與別名, 那指標的別名就容易多了, 為了要取出指標物件的主角, 只要在指標前慣上星號 * 就是變數的內容:
#include "stdio.h"
int main() {
   int*   a;   //  a 是指標變數
   int*  &b = a; //  b 也是指標變數, 只不過初始化時為 a 的別名, 不管用 int*&   b = a; 或 int  *&b = a; 結果都相同
   int   c  = 3;      //  c 的內容等於 3
   b  = &c; //   既然 b 是 a 的別名且又是指標變數, 可以把 b 指向 c , 等同把 a 指向 c
   printf("*a = %d\n",  *a);// 因此取出 a 的內容等同取出  b 的內容, 也就是 c 的內容:  3
 }
宣告一個 * 的指標變數, 它就類似一維陣列, 而陣列就是一個固定連續空間, 但宣告陣列與宣告指標變數不同的是: 陣列會分配出記憶空間但指標不會, 而指標變數可以任意定位, 陣列卻不可以.因此兩者用法並不相同, 其實不可混淆.
    int   *ptr;          // 實體位置未指定, 初始化之前都不得使用
    int   dim[ ] = {1,2,3}; // 分配出 3 個整數的空間
    ptr = dim;        // 但 ptr 可以指向相同型態的陣列

至於連續兩個 ** 也就是連續的指標空間(類似儲存指標的陣列)
   int      a[ ]   = {1,2,3}; // a 內容陣列是整數, a 其實是指標, 只是不能重定位
   int      b[ ]   = {4,5,6}; // b 也是整數陣列, b 是指標
   int*  ab[ ]   = { a, b }; // ab 是儲存整數指標的陣列
   int** pab;       // 宣告整數型態的二維指標 pab
   pab = ab;       //  pab 可以指向 ab
上述 int** pab 也可宣告成  int*  *pab甚至 int   **pab, 運算結果都一樣, 當 pab 指向 ab 之後, 實際上 pab[0] 內容就是 a , pab[1] 為 b, 而 **pab 則為 pab[0][0], 因此全部內容展開就是:
   pab[0][0] = 1,  pab[0][1] =2 , pab[0][2] = 3
   pab[1][0] = 4,  pab[1][1] =5.  pab[1][2] = 6
函式的參數如果是以函數型式, 稱為無名函數(annonymous function vaule)傳進來, 就用符號 && 代表無名, 尤其在 move constructor 一定會用到. 最後要注意的是主角與別名 & 甚至無名 && 等變數代表了一個實體物件, 針對類型成員要是用句號 .  取用物件資源, 但 *,  *&, ** 等指標變數必須經過初始化後再用星號 * 轉回物件, 或用中括號取得陣列中的物件, 類型的指標變數要另外用箭號 ->  取用物件成員. 在類型中 *this 是物件主角,  但 this 是物件的指標.

2018年12月3日 星期一

有關 CMakeLists.txt 基本用法

編寫 Makefile 還要考慮到檔案相關性, 何不讓程式自動寫 Makefile 呢? cmake 就是做這種功能的, 簡單的像是編譯 tutor.cpp:
        g++   tutor.cpp   -lGLESv2   -lglfw    -lGLEW   -o   tutor
只要在根目錄用 cmake 的語法編輯檔案 CMakeLists.txt .  先替執行檔取名(例如 tutor), 把要編譯的程式檔加入 add_exetuable(...), 再把需要聯結的程式庫加進 target_link_libraries(...), 例如:
      add_executable(tutor   tutor.cpp)
      target_link_libraries(tutor GLESv2 glfw GLEW)
兩行就能搞定, 通常在專案的根目錄建個子目錄 build, 讓編譯的暫存檔放在裏面, 接著進入子目錄下命令 cmake .. 完成後自動生成 Makefile, 之後跟平常一樣, 只要進入子目錄 build 裏面,輸入 make 就可以編譯程式, 例如:
      [-d build ] || mkdir build
      cd build && cmake ..
      make
移除檔案時只要將專案的目錄 build 整個刪除就清乾淨了. 簡單寫個 Makefile 來支援 cmake 測試一下:
#Makefile to generate CMakeLists.txt
userpath= $(shell pwd)
build   = $(userpath)/build
target  = $(build)/tutor
run: $(target)
    $<

$(target): $(build) tutor.cpp
    cd $<; make
$(build): CMakeLists.txt
    @[ -d $@ ] || mkdir $@
    @[ -f $< ] &&    cd $@ && cmake .. || echo missing $<;echo
CMakeLists.txt: 
    echo "add_executable(tutor tutor.cpp)" > $@
    echo "target_link_libraries(tutor GLESv2 glfw GLEW)" >> $@
glfwlib:
    sudo apt-get install  libglfw3-dev  libgles2-mesa-dev  libglew-dev
tutor: tutor.cpp
    g++ $< -lGLESv2 -lglfw -lGLEW -o $@ && ./$@
clean:
    rm -rf build
     rm tutor
    clear

簡單的 c++ 畫圖程式碼:
// tutor.cpp
#include "stdio.h"
#include "GL/glew.h"
#include "GLFW/glfw3.h"
int    error(int errno, const char *msg){
    glfwTerminate();
    printf("Errir %d:%s",errno, msg);
    return -1;
 }
class  myShader  {  
    private:  
        struct glbuffer   { GLuint bind; int size; };//  dataclass contain 2 members  
        class  methodbind { // buffer inject myShader's method
            private:
                myShader *thispointer; // link with outer class
            public:
                glbuffer  buffer;
                methodbind(myShader  *pointer)       { thispointer = pointer; buffer.size=0; }
                void vec3(const char *name)                 { glBindBuffer(GL_ARRAY_BUFFER, buffer.bind); thispointer -> attribute(name, 3); }
         };
        int  programid, shaderid, bufferno;
        void attribute(const char *name, int size)   {
            int location = glGetAttribLocation(programid, name);
            glEnableVertexAttribArray(location);
            glVertexAttribPointer(location, size, GL_FLOAT, GL_FALSE, 0, 0);
         }      
        void detachshader(int sid)                   { glDetachShader(programid, sid); glDeleteShader(sid) ; }
        void assemble(int &&sid, const char **string){
            glShaderSource (sid, 1, string , NULL);
            glCompileShader(sid); 
            glAttachShader(programid, sid);
            if( shaderid == 0 ) shaderid = sid; // push shader id
            else {    // accumulate upto 2 shaders, ready to link
                glLinkProgram(programid);    // link with the program id
                detachshader(sid);    // detach current sid
                detachshader(shaderid);// pop previous sid and detach
                shaderid=0;            // restart to accumulate sid
            }       
         }  
        #define buffernMAX 100
        glbuffer  shaderbuffer[buffernMAX];      
        glbuffer& addbuffer(GLuint bid, int size)    {
            shaderbuffer[bufferno].bind = bid;
            shaderbuffer[bufferno].size = size;
            return shaderbuffer[bufferno++];              
         }
        auto arraydata(GLfloat *array , int size)    { 
            GLuint bid;
            glGenBuffers(1, &bid);
            glBindBuffer(GL_ARRAY_BUFFER, bid);
            glBufferData(GL_ARRAY_BUFFER, size, array, GL_STATIC_DRAW);
            return addbuffer(bid, size);
         }
    public:  // public area  
        methodbind  point = methodbind(this);// bind with this
        GLFWwindow* context;
        methodbind& pointbuffer(GLfloat *array, int size) {
            if( bufferno < buffernMAX && size > 0 ) point.buffer = arraydata(array, size);
            return point;
         }
        myShader(int width, int height,const char *vcode, const char *fcode)    {
            bufferno = 0;
            shaderid = 0;
            if( glfwInit() )  {
                glfwWindowHint(GLFW_SAMPLES, 4);
                glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
                glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1);
                context = glfwCreateWindow(width, height, "myShader", NULL, NULL);
                if( context ) {
                    glfwMakeContextCurrent(context);
                    if ( glewInit() == GLEW_OK ) {
                        glfwSetInputMode(context, GLFW_STICKY_KEYS, GL_TRUE);
                        glClearColor(0.0, 0.0, 0.0, 0.0);                     
                        programid = glCreateProgram();
                        assemble(glCreateShader(GL_VERTEX_SHADER)  , &vcode);
                        assemble(glCreateShader(GL_FRAGMENT_SHADER), &fcode);
                    }
                }
             }
         }
        void close() {
            for (int i = 0; i < bufferno; i ++)  glDeleteBuffers(1, &shaderbuffer[i].bind);
            bufferno = 0;
            glDeleteProgram(programid);
            glfwTerminate();
         }
        bool draw()  {
            glClear( GL_COLOR_BUFFER_BIT );
            glUseProgram(programid);
            glDrawArrays(GL_TRIANGLES, 0, point.buffer.size); // draw points
            glfwSwapBuffers(context);
            glfwPollEvents();
            if( glfwGetKey(context, GLFW_KEY_ESCAPE ) == GLFW_PRESS ) return false;
            return ! glfwWindowShouldClose(context);
         }
 };

int main( void ) {
    const char *vcode = R"EOF(// Vertex shader program:
        attribute vec3 position;
        void main() {
            gl_Position = vec4(position, 1.0);
        }
    )EOF";
    const char *fcode= R"EOF(// Fragment shader program:
        void main() {
            gl_FragColor = vec4(1,0,0,1);
        }
    )EOF";  
    GLfloat vertices[] = {
        -1.0f, -1.0f, 0.0f,
         1.0f, -1.0f, 0.0f,
         0.0f,  1.0f, 0.0f,
    };  
    auto a =  myShader(800, 600, vcode, fcode);
    if( ! a.context )  return error(0, "initGL Error");  
    a.pointbuffer(vertices,sizeof(vertices)).vec3("position");// begin to transfer vertices
    while( a.draw() );
    a.close();
    return 0;
 }

有關 Makefile 一些語法

Makefile 一些基礎摘要:
1. 用井號 # 標記為註解, 例如:
    # 這一行是註解

2. 用等號 = 賦予變數內容,  例如:
    cc = gcc

3. 變數前頭加錢號 $ 隨後將變數用小括號 ( ) 前後括住, 代表引用變數內容, 例如:
    compile = $(cc)   -c

4. $ 括號變數, 甚至能執行 shell 命令, 例如:
    user_path = $(shell pwd)

5. 用冒號 : 描述檔案相關性的規則(rule), 每條規則從開頭起, 至換行字元結束. 冒號前面是目標, 後面是來源, 代表著目標與來源相關, 不同的目標就分別寫個別的規則, 當個別規則的來源很多時, 就用空白字元分開一一列表

6. 針對目標要執行的命令就寫在該目標的下一行, 為了要與規則有所區別, 所有命令列的開頭處都要先插入一個 Tab 字元(不能用空白字元), 不同的命令就隨後寫在不同行

7. 在命令列用的常用內建$變數:
    $@      就是目標
    $<   來源中第一個
    $^       來源全數列表
    $(@D) 目標只剩目錄名
    $(@F) 目標只剩檔名(移除目錄名), 等同 $(notdir $@)
    $(*D)   目標只剩主目錄名(移除副檔名)
    $(*F)   目標只剩主檔名(移除目錄及副檔名)
    $(<D)來源中第一個, 但只剩目錄名
    $(<F)來源中第一個, 但剩檔名(移除目錄名)
    $(^D)  來源全數列表, 但只剩目錄名

 8. 隱性規則代表內含很多條規則, 顯性規則就很明確只有一條規則, 內建通用的隱姓規則
 .c: .o  代表所有副檔名 .c 與個別的 .o 相關

9. 自訂通用隱性規則, 使用 % 代表通通適用
%.cc:   %.oo  代表所有副檔名 .cc 與個別的 .oo 相關

10. 當執行 make 時, 預設會用第 1 條顯性規則執行隨後命令

11. 一個簡單的範例:
#Makefile
main    = tutor.cpp
exefile = tutor
$(exefile): $(main)
    g++ $< -lGLESv2 -lglfw -lGLEW -o $@ && ./$@
libgl:
    sudo apt-get install libglfw3-dev  libglew-dev  libgles2-mesa-dev glew-utils
glinfo:
    glewinfo | grep "GL_VERSION" || make libgl
run:
    ./tutor
clean:
    rm -f $(exefile)   *.o

2018年12月2日 星期日

C++ 類型(class): 建構法(constructor), 解構法(destructor), 動態分配(new), 釋放(delete)

宣告一個新的 c++ 類型時, 如果在類型裏面使用動態記憶空間分配(可以使用 new 運算子或是 malloc( ) 函式傳回指標加以運用),就必須知道 6 種類型的方法及使用時機, 避免造成重複刪除的窘境. 類型裡面的成員分成可記憶的變數與不具記憶的函數(函式的傳回值), 這種在類型裡面的變數稱為狀態(state),而函式則特別稱為方法(method),當類型被引用要產生新物件時第一個被執行的就是類型建構法, 此時裏面變數的狀態都是混沌未明, 透過建構法才能將它初始化,當建構法完成,就會回傳一個該類型的新物件(裏面包含了已初始過的狀態與方法), 因此建構法最後無需再用 return 之類的程式碼. 當物件以指標型式傳進來時就要用箭頭符號引用成員, 如果是物件型式就用句號引用成員, new 回傳的的是物件指標使用 new 所分配的記憶空間必須用 delete 將它釋放. 在方法中使用的 this 就是該物件本身的指標. 指標的存取是一項危險的動作, 一旦指標成為空指標時(null pointer), 不小心產生 Null Pointer Exception(俗稱 NPE), 嚴重的話造成當機.

1. 解構法(destructor),  函式的特徵是 "~類型名稱 ( )", 物件消滅前, 當成員曾經 用new 或 malloc( )分配動態記憶空間時, 要在此要用程式碼 delete 運算子或函式 free( )將它釋放出來.
        ~className ( ) { // Here free all of dynamic resouce
               // delete dynamicResource;
      }

2. 基本建構法(constructor),無傳遞參數(void), 單純初始化內部狀態用,初始類型的陣列也會用此法來建構新物件
      className ( ) {   // Here init all states ...
            // dynamicResource =NULL;
      }  // No need to use return in the end. Do automatically by the compiler

3. 複本建構法(copy constructor), 此函式的特徵是"類型名稱 (物件)",  顧名思義就是要從一份物件來源去產生新類型的物件.例如:
      className (className&  source) { // Here init and copy all state from source
                 // dynamicResource = new className( );
      }   // No need to use return in the end. Do automatically by compiler

4. 物件指定運算(copy assignment), 發動的時機是一個有 = 類型的運算式, 將產生的物件指定給一個變數. 因為運算函式並非建構法, 因此運算函式最後要用 retrun *this 或是 return this; 視傳回的型態(type)而結束. 物件指定運算的特徵是 "類型名稱 operator = (物件)", 在運算函式裏面傳進來的參數必須先判斷非本尊(this) 時, 才需要作指定運算:
      className&  operator = (classNamme  &source) {
              if(   &source != this ) { // this is a pointer, &source is source pointer
                    // Here do copy assignment ...
                    //  dynamicResource = source.dynamicResource; // share dynamicResource
              }
              return *this; // object itself
      }

5. 物件轉移運算(move assignment), 顧名思義就是一個將資產(resource)換手(transfer)的過程,把物件轉交給一個既有物件. 其特徵是 "類型& operator = (類型&& )",其發動的時機也是在一個有 = 類型的運算式, 通常是由編譯器視需求, 先產生一個暫時類型的物件(稱為匿名物件, 也許會透過 copy assignment 產生匿名物件)傳進來, 而受者本身的成員若之前使用 new 分配過動態記憶空間, 就要在此時先釋放,之後將它指向匿名物件的資源,避免記憶體洩漏(memory leak)的情況, 甚至當匿名物件的成員也帶有動態記憶空間時, 也必須最後將該成員設定為 NULL, 當交易完成,匿名物件也會跟著消滅, 這樣才不致在解構時發生重複釋放(double free)記憶體的危機, 當然轉移之前也要先判定來源,避免自我轉移的窘境:
      className& operator = (classNamme&& temp) { // temp is generated by compiler
              if(   &temp != this ) { // this is a pointer, &temp is source pointer
                   // Here do move assignment ...
                   // if(   dynamicResource != NULL ) delete dynamicResource;// free resource
                   // dynamicResource = temp.dynamicResource; // link to resource
                  // temp.dynamicResource = NULL; // temp cut off  the resource
              }
              return *this;  // object itself
      } // temp is destroy automatically by compiler

6. 交易建構法(move constructor), 函式的特徵是 "類型 (類型&& )", 同樣是將資產轉手用的, 只不過是由程式碼主動呼叫(move semantics, 可以透過 std::move(object) 來發動),他是建構法類型其中之一, 編譯器直接將物件轉化成匿名物件交由該建構法運用, 最後另外產生該類型的新物件, 要注意的是物件經轉移後, 其成員狀態, 如果有分配動態記憶空間時, 是否將成員指定為 NULL,視交易建構法程式碼而定,當然也須避免發生重複釋放的危機.
    classNamme(&& temp) { // temp is generated by compiler
         // Here do move assignment ...
        // dynamicResource = temp.dynamicResource; // link to dynamicResource                 
         // temp.dynamicResource = NULL; // Cut off the link of dynamicResource   
     } // temp is destroy automatically by compiler,
7. 範例程式碼:
// hex.c
#include "stdio.h"
#include "iostream"
#include "stdarg.h"
struct hex {
    const char *str;
    int  length;
    char id;
    int  numtable(char c) {  // λ(c)
            if ('0' <= c &&  c <= '9')         return c - '0';
            else if ('a' <=c &&  c <= 'f')  return c - 'a' + 10;
            else if ('A' <=c &&  c <= 'F')  return c - 'A' + 10;
            else return 0;
     };
    char hextable(int i) {  // λ(i)
            char ch;
            if (0 <= i &&  i <= 9)             ch = i + '0';
            else if (10 <= i &&  i <= 15)   ch = i - 10 + 'a';
            else ch = '0';
            return ch;
     };
    ~hex(void)                     { // destructor
        printf("Destroy ~ ");
        if (str) {
            printf("%s ! ",str);
            delete str;
        } else printf("Empty ! ");
        printf("finish\n"); 
     } 
    hex(void)                     { // 1 init constructor
        printf("Init constructor(): \n");
        str = NULL;
        length=0 ;
        id=0;
     }
    hex(hex &a)             {  // 2 copy  constructor     
        printf("Copy Constructor(%s): ", a.str);                
        str = string2hex(a.str); // need a new copy
        printf("finish\n"); 
     }  
    hex(const char *s)        { // 2 copy  constructor
        printf("Copy Constructor(%s)\n",s);
        str = string2hex(s);
     }      
 
    hex(hex &&a)             { // 3 move  constructor
        // printf("Move constructor(%s): ", a.str);
        str = a.str; // just steal
        length = a.length;
        id = a.id;
        a.str = NULL;
        printf("Move Constructor(%s) finish\n",str); 
     }
 
    // Assignment is an operator, need a return type, and must not assign by itself
    hex& operator =(hex &a) { // 4 copy  assignment
        printf("Copy assign = %s ", a.str);
        if( &a != this) {
            str = a.str;// point to the string
            length=0; while (a.str[length++]);// caculate the the length
            id = 0;
            tocstr();    // build a new string
        }
        printf("finish\n");
        return *this; // *this;
     }  
    hex& operator =(char *a){ // 4 copy  assignment
        printf("String assign = %s ", a);
        if( str != a) {
            str = a;    // point to new string
            length=0;    // caculate the the length
            id = 0;
            while (a[length++]);
            tocstr();    // conver to string
        }
        printf("finish\n"); 
        return *this; // *this;
     }
    hex& operator =(hex &&a){ // 5 move  assignment
        printf("Move assign = %s,  self str = %s\n", a.str, str);
        if( &a != this ) { // if pointer of a is not this
            if( str ) delete str;
            str = a.str;
            length = a.length;
            id = a.id;
            a.str = NULL; // to indicate not free a.str when destroy  a
        }
        printf("Move = finish\n"); 
        return *this;
     }

 
    hex& printf(const char *fmt, ...){// to chain printf
        va_list args;
        va_start(args, fmt);
        vprintf(fmt, args);
        va_end(args);
        return *this; // method chain if necessary
     }     
    hex& tohex(){
        if( id !=1 && length > 0) {
            int  k = length ;
            auto number = new char[(k << 1) + 1];
            int  length = 0;
            for (int i = 0; i < k; i++){
                unsigned char c = str[i];
                number[length++] = hextable((c&0xf0) >> 4);//convert to HEX char, MSB
                number[length++] = hextable(c&0xf);      //convert to HEX char, LSB
             }
            number[length++]=0; //EOS
        }
        return *this;
     }
    hex& tocstr(){
        if( id !=2 && length>0 ) {
            int  k = length;
            auto cstring = new char[(k >> 1) + 1];// include EOS, and each HEX number occupy 2 bytes
            length = 0;
            for (int i = 0; i < k; i += 2) cstring[length++] =  (numtable(str[i]) << 4) + numtable(str[i+1]);
            cstring[length++]=0;//EOS
            str = cstring; 
        }
        return *this;
     }
    const char *string2hex(const char *sx){
        id = 1;
        int k =0 ;
        while (sx[k]) k ++;        // break when EOS
       auto number = new char[(k << 1) + 1];// include EOS, and each HEX number occupy 2 bytes
        length = 0;
        for (int i = 0; i < k; i++){
            unsigned char c = sx[i];
            number[length++] = hextable((c&0xf0) >> 4);//convert to HEX char, MSB
            number[length++] = hextable(c&0xf);      //convert to HEX char, LSB
         }
        number[length++]=0; //EOS
        return number;
     }
    const char *hex2string(const char *number) {
        id = 2;
        int  k = 0 ;
        while (number[k]) k ++;     // break when EOS
        auto cstring = new char[(k >> 1) + 1];// include EOS, and each HEX number occupy 2 bytes
        length = 0;
        for (int i = 0; i < k; i += 2) cstring[length++] =  (numtable(number[i]) << 4) + numtable(number[i+1]);
        cstring[length++]=0;//EOS
        return cstring;
     }    
};
int main(){
    printf("\n1.\n");
    hex aaa;

    printf("\n2.\n");
    auto sss = hex("123"); 
    auto strhex = hex("中文也可以"); 

    printf("\n3.\n");
    sss = strhex.str;
 
    printf("\n4.\n");
    auto bbb = std::move(strhex);

    printf("\nfinal\n");
}
執行 g++ hex.c && ./a.out 輸出結果:
1.
Init constructor():

2.
Copy Constructor(123)
Copy Constructor(中文也可以)

3.
Copy Constructor(e4b8ade69687e4b99fe58fafe4bba5)
Move assign = 653462386164653639363837653462393966653538666166653462626135,  self str = 313233
Move = finish
Destroy ~ Empty ! finish

4.
Move Constructor(e4b8ade69687e4b99fe58fafe4bba5) finish

final
Destroy ~ e4b8ade69687e4b99fe58fafe4bba5 ! finish
Destroy ~ Empty ! finish
Destroy ~ 653462386164653639363837653462393966653538666166653462626135 ! finish
Destroy ~ Empty ! finish