2025/11/07

SOCI

SOCI (Simple Oracle Call Interface) 一開始是由 Maciej Sobczak 在 CERN 工作時開發, 作為 Oracle 資料庫函式庫的 abstraction layer 並且在 CERN 的工作環境中使用,之後則又加入了數個資料庫的支援。

SOCI 目前支援 Oracle, MySQL, PostgreSQL, SQLite3 等資料庫以及 ODBC 作為通用的 backend。 下面是在 openSUSE 安裝 SOCI core 以及 SOCI SQLite3 開發檔案的指令:
sudo zypper in soci-devel soci-sqlite3-devel

(注意:SOCI 本身的體積並不大,但是安裝時需要 boost-devel,而 boost-devel 是個包含很多模組的函式庫, 如果之前就有安裝 boost-devel 那麼這就不是一個問題)

下面是一個使用 SOCI 取得 SQLite3 版本的測試程式:

#include <soci/soci.h>
#include <soci/sqlite3/soci-sqlite3.h>

#include <iostream>
#include <string>

int main() {
    try {
        soci::session sql("sqlite3", "db=:memory:");

        std::string version;
        sql << "select sqlite_version()", soci::into(version);
        std::cout << version << std::endl;
    } catch (soci::soci_error const &e) {
        std::cerr << "Failed: " << e.what() << std::endl;
    } catch (std::runtime_error const &e) {
        std::cerr << "Unexpected standard exception occurred: " << e.what()
                  << std::endl;
    } catch (...) {
        std::cerr << "Unexpected unknown exception occurred." << std::endl;
    }

    return 0;
}

使用下列的指令編譯:

g++ version.cpp -lsoci_core -lsoci_sqlite3 -o version

下面是在 openSUSE 安裝 SOCI core 以及 SOCI ODBC 開發檔案的指令:

sudo zypper in soci-devel soci-odbc-devel

下面是一個使用 SOCI ODBC 取得 PostgreSQL 版本的測試程式:

#include <soci/odbc/soci-odbc.h>
#include <soci/soci.h>

#include <iostream>
#include <string>

int main() {
    try {
        soci::session sql("odbc",
                          "DSN=PostgreSQL; UID=postgres; PWD=postgres;");

        std::string version;
        soci::rowset<std::string> rs = (sql.prepare << "select version()");

        for (soci::rowset<std::string>::const_iterator it = rs.begin();
             it != rs.end(); ++it) {
            std::cout << *it << std::endl;
        }
    } catch (soci::soci_error const &e) {
        std::cerr << "Failed: " << e.what() << std::endl;
    } catch (std::runtime_error const &e) {
        std::cerr << "Unexpected standard exception occurred: " << e.what()
                  << std::endl;
    } catch (...) {
        std::cerr << "Unexpected unknown exception occurred." << std::endl;
    }

    return 0;
}

使用下列的指令編譯:

g++ version.cpp -lsoci_core -lsoci_odbc -o version

參考連結

2025/10/30

Beyond 國語歌曲歌單

歌單的歌曲不固定,這只是我現在的歌單。

  • 大地

    收錄在 1990 年發行的專輯《大地》中。粵語版為《大地》。

  • 漆黑的空間

    收錄在 1990 年發行的專輯《大地》中。粵語版為《灰色軌跡》。

  • 九十年代的憂傷

    收錄在 1990 年發行的專輯《大地》中。粵語版為《相依的心》。

  • 送給不懂環保的人(包括我)

    收錄在 1990 年發行的專輯《大地》中。粵語版為《送給不知怎去保護環境的人(包括我)》。

  • 和自己的心比賽

    收錄在 1990 年發行的專輯《大地》中。粵語版為《戰勝心魔》。

  • 破壞遊戲的孩子

    收錄在 1990 年發行的專輯《大地》中。粵語版為《衝開一切》。

  • 今天有我

    收錄在 1990 年發行的專輯《大地》中。粵語版為《秘密警察》。

  • 光輝歲月

    收錄在 1991 年發行的專輯《光輝歲月》中。粵語版為《光輝歲月》。

  • 午夜怨曲

    收錄在 1991 年發行的專輯《光輝歲月》中。粵語版為《午夜怨曲》。

  • 撒旦的咀咒

    收錄在 1991 年發行的專輯《光輝歲月》中。粵語版為《撒旦的詛咒》。

  • 射手座咒語

    收錄在 1991 年發行的專輯《光輝歲月》中。粵語版為《亞拉伯跳舞女郎》。

  • 歲月無聲

    收錄在 1991 年發行的專輯《光輝歲月》中。粵語版為《歲月無聲》。

  • 曾經擁有

    收錄在 1991 年發行的專輯《光輝歲月》中。粵語版為《曾是擁有》。

  • 心中的太陽

    收錄在 1991 年發行的專輯《光輝歲月》中。粵語版為《又是黃昏》。

  • 長城

    收錄在 1992 年發行的專輯《信念》中。粵語版為《長城》。

  • 關心永遠在

    收錄在 1992 年發行的專輯《信念》中。粵語版為《遙望》。

  • 今天就做

    收錄在 1992 年發行的專輯《信念》中。粵語版為《不可一世》。我非常喜歡的其中一首國語歌。

  • 農民

    收錄在 1992 年發行的專輯《信念》中。粵語版為《農民》。

  • 最想念妳

    收錄在 1992 年發行的專輯《信念》中。粵語版為《早班火車》。

  • 可否衝破

    收錄在 1992 年發行的專輯《信念》中。粵語版為《可否衝破》。

  • 問自己

    收錄在 1992 年發行的專輯《信念》中。粵語版為《無語問蒼天》。

  • 年輕

    收錄在 1992 年發行的專輯《信念》中。粵語版為《快樂王國》。

  • 愛不容易說

    收錄在 1993 年發行的專輯《海闊天空》中。《海闊天空》是 Beyond 發行的第 4 張國語專輯,1993 年 6 月 24 日黃家駒發生意外昏迷,6 月 30 日逝世, 原定計畫無法實現,滾石唱片公司以這張專輯保留黃家駒原聲以及詞曲創作來完成黃家駒未完成的計畫和心願。黃家駒逝世後, 其餘 3 名成員決定進入錄音室,由黃家強和黃貫中演唱完成計畫中的國語專輯。歌曲結合粵語專輯《樂與怒》和日語專輯《This Is Love 1》。 雖然維基百科上說這是第 4 張國語專輯,但我個人感覺更像是個精選輯。

  • 身不由己

    收錄在 1993 年發行的專輯《海闊天空》中。

  • 一輩子陪我走

    收錄在 1994 年發行的專輯《Paradise》中。《Paradise》是 Beyond 樂隊在主音黃家駒逝世後,三人復出發行的首張國語專輯。 《一輩子陪我走》則是以一個輕快的伴奏,高歌四子手足之情。

  • 無名英雄

    收錄在 1994 年發行的專輯《Paradise》中。

  • 和平世界

    收錄在 1994 年發行的專輯《Paradise》中。

  • 因為有你有我

    收錄在 1994 年發行的專輯《Paradise》中。

  • 對嗎

    收錄在 1994 年發行的專輯《Paradise》中。

  • 唯一

    收錄在 1995 年發行的專輯《愛與生活》中。《活得精彩》《Love》《唯一》《夢》四首歌都是國語歌曲創作, 而只有《活得精彩》後來有錄製粵語版本。

  • Love

    收錄在 1995 年發行的專輯《愛與生活》中。

  • 活得精彩

    收錄在 1995 年發行的專輯《愛與生活》中。


  • 收錄在 1995 年發行的專輯《愛與生活》中。

  • 荒謬世界

    收錄在 1995 年發行的專輯《愛與生活》中。粵語版為《教壞細路》。

  • 忘記你

    收錄在 1998 年發行的專輯《這裡那裡》中。

  • 緩慢

    收錄在 1998 年發行的專輯《這裡那裡》中。

  • 候診室

    收錄在 1998 年發行的專輯《這裡那裡》中。粵語版為《不再猶豫》。

  • 情人

    收錄在 1998 年發行的專輯《這裡那裡》中。

  • 熱情過後

    收錄在 1998 年發行的專輯《這裡那裡》中。

  • 十字路口

    收錄在 1998 年發行的專輯《這裡那裡》中。

  • Amani

    收錄在 1998 年發行的專輯《這裡那裡》中。

  • 命運是我家

    收錄在 1998 年發行的專輯《這裡那裡》中。《這裡那裡》當中有七首是把黃家駒的作品,重新填上國語詞及編曲, 《命運是我家》這首的編曲是保留黃家駒原曲,為的是要保留黃家駒彈奏吉他的部份,是對黃家駒的敬意與尊重。

2025/10/19

Fast Light Toolkit

FLTK 由 Bill Spitzak 個人開始開發並且目前仍然是專案的領導者, 是一個跨平台的 C++ GUI toolkit,用在 UNIX/Linux、微軟 Windows 和 Mac OS X 上, 使用 OpenGL 相關的功能提供 3D 繪圖的支援,並且有一個簡單的界面設計工具 FLUID。 FLTK 被設計足夠小和模組化以被靜態連結,但作為共享庫也工作良好。FLTK 的授權使用 LGPL v2,不過允許使用靜態連結。

一般而言,跨平台 GUI toolkit 有以下二個主要的策略:

  • 使用一組共同的 API 包裝各平台提供的底層函式庫或者是工具箱,如果該平台沒有提供相關的視窗部件 (widget) 再自行撰寫。 使用此策略著名的 GUI toolkit 為 wxWidgets。
  • 使用各個平台的繪圖功能繪製出最基本的視窗,再由最基本的視窗建構各種視窗部件。 使用此策略著名的 GUI toolkit 為 GTK 與 Qt。

FLTK 的跨平台策略為使用各個平台的繪圖功能繪製出最基本的視窗,再由最基本的視窗建構各種視窗部件。 這個策略的優點是移植到不同的平台較為容易,缺點是缺少 native look and feel,需要使用佈景主題 (theme) 之類的方法改進視覺效果。

FLTK 是一個輕量化的函式庫,適合撰寫一些小工具或者是提供前端界面,不過雖然提供了大多數常見的視窗部件, 但是如果是很複雜的部件則未必有提供(有可能需要自行撰寫),如果要撰寫極為複雜的圖形使用者介面應用程式,那麼建議使用其它的 GUI toolkit。

從 FLTK 1.4 開始,編譯 FLTK 建議使用的 build system 為 CMake。


所有 FLTK 應用程式都基於事件處理模型。使用者滑鼠移動、按鈕點擊和鍵盤活動等操作都會產生事件, 並發送給應用程式。然後,應用程式可能會忽略事件或回應用戶。

下面就是 FLTK 的 Hello 例子:

#include <FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Box.H>

int main(int argc, char **argv) {
    Fl_Window *window = new Fl_Window(340, 180);
    Fl_Box *box = new Fl_Box(20, 40, 300, 100, "Hello, World!");
    box->box(FL_UP_BOX);
    box->labelfont(FL_BOLD + FL_ITALIC);
    box->labelsize(36);
    box->labeltype(FL_SHADOW_LABEL);
    window->end();
    window->show(argc, argv);
    return Fl::run();
}

在 FLTK 1.3.x,需要宣告 <FL/Fl.H> 才行,FLTK 1.4 則放鬆了這個限制,不過宣告了也不會有問題。

FLTK 提供了 fltk-config 可以用來查詢編譯與連結時需要的參數,下面是在 UNIX/Linux 環境下編譯的指令:

g++ hello.cxx -o hello `fltk-config --cxxflags --ldflags`

如果要使用 CMake 作為 build system,那麼建立一個 CMakeLists.txt,內容如下:

cmake_minimum_required(VERSION 3.15)

project(hello)

set(FLTK_DIR "/usr/local"
CACHE FILEPATH "FLTK installation or build directory")

find_package(FLTK 1.4 CONFIG REQUIRED)

add_executable       (hello WIN32 MACOSX_BUNDLE hello.cxx)
target_link_libraries(hello PRIVATE fltk::fltk)

Callbacks

Callbacks 是當視窗部件的值改變時所呼叫的函數。 下面是使用 Fl_Button 與 callback function 的程式,還示範了應該如何設置 shortcut。

#include <FL/Fl.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Window.H>
#include <cstdlib>

void close_callback(Fl_Widget *, void *) {
    if (Fl::event() == FL_SHORTCUT && Fl::event_key() == FL_Escape)
        return;

    exit(0);
}

int main(int argc, char **argv) {
    Fl_Window *window = new Fl_Window(340, 180);

    Fl_Button *button = new Fl_Button(20, 40, 300, 100, "Hello, World!");
    button->type(FL_NORMAL_BUTTON);
    button->color(FL_WHITE);
    button->labelcolor(FL_BLACK);    
    button->shortcut(FL_ALT + 'q');
    button->callback(close_callback);

    window->end();
    window->callback(close_callback);
    window->show(argc, argv);
    return Fl::run();
}

通常只有當視窗部件的值發生變化時才會執行 callback,使用者可以使用 Fl_Widget::when() 方法進行相關的設定。


下面是一個簡單的 right-click context menu 例子。

#include <FL/Fl.H>
#include <FL/Fl_Menu_Button.H>
#include <FL/Fl_Multiline_Input.H>
#include <FL/Fl_Window.H>
#include <cstdlib>

void Menu_CB(Fl_Widget *w, void *d) {
    exit(0);
}

int main(int argc, char **argv) {
    Fl_Window *window = new Fl_Window(640, 480);
    window->tooltip("Use right-click for popup menu...");

    Fl_Menu_Button *menu = new Fl_Menu_Button(0, 0, 640, 480, "Popup Menu");
    menu->type(Fl_Menu_Button::POPUP3);  // pops menu on right click
    menu->add("Quit", "^q", Menu_CB, 0); // ctrl-q hotkey
    menu->menu_end();

    Fl_Multiline_Input *input =
        new Fl_Multiline_Input(100, 200, 350, 50, "Input");
    input->value("Right-click anywhere on gray window area\nfor popup menu");

    window->end();
    window->show(argc, argv);
    return Fl::run();
}

下面是一個簡單的 Fl_Menu_Bar 例子。

#include <FL/Fl.H>
#include <FL/Fl_Menu_Bar.H>
#include <FL/Fl_Window.H>
#include <cstdio>
#include <cstdlib>
#include <cstring>

static void MyMenuCallback(Fl_Widget *w, void *) {
    Fl_Menu_Bar *bar = (Fl_Menu_Bar *)w;
    const Fl_Menu_Item *item = bar->mvalue();

    char ipath[256];
    bar->item_pathname(ipath, sizeof(ipath));
    fprintf(stderr, "callback: You picked '%s'",
            item->label());                              // Print item picked
    fprintf(stderr, ", item_pathname() is '%s'", ipath); // ..and full pathname

    fprintf(stderr, "\n");
    if (strcmp(item->label(), "&Quit") == 0) {
        exit(0);
    }
}

int main() {
    Fl::scheme("gleam");
    Fl_Window *win = new Fl_Window(640, 480, "menubar");

    Fl_Menu_Bar *menu = new Fl_Menu_Bar(0, 0, 640, 25);
    menu->add("&File/&Quit", "^q", MyMenuCallback);
    menu->add("&Help/&About", 0, MyMenuCallback);

    win->end();
    win->show();
    return (Fl::run());
}

Layout

除了直接固定視窗部件的位置,FLTK 提供了一些佈局用的視窗部件可以用來作為自動佈局視窗部件的位置。

下面是 Fl_Flex 的例子。

#include <FL/Fl.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Double_Window.H>
#include <FL/Fl_Flex.H>
#include <FL/fl_ask.H>

void exit_cb(Fl_Widget *w, void *) {
    fl_message("The '%s' button closes the window\nand terminates the program.",
               w->label());
    w->window()->hide();
}

void button_cb(Fl_Widget *w, void *) {
    fl_message("The '%s' button does nothing.", w->label());
}

int main(int argc, char **argv) {
    Fl_Double_Window window(640, 60, "Simple Fl_Flex Demo");
    Fl_Flex flex(5, 5, window.w() - 10, window.h() - 10, Fl_Flex::HORIZONTAL);
    Fl_Button b1(0, 0, 0, 0, "File");
    Fl_Button b2(0, 0, 0, 0, "New");
    Fl_Button b3(0, 0, 0, 0, "Save");
    Fl_Box bx(0, 0, 0, 0); // empty space
    Fl_Button eb(0, 0, 0, 0, "Exit");

    // assign callbacks to buttons
    b1.callback(button_cb);
    b2.callback(button_cb);
    b3.callback(button_cb);
    eb.callback(exit_cb);

    // set gap between adjacent buttons and extra spacing (invisible box size)
    flex.gap(10);
    flex.fixed(bx, 30);

    // end() groups
    flex.end();
    window.end();

    // set resizable, minimal window size, show() window, and execute event loop
    window.resizable(flex);
    window.size_range(300, 30);
    window.show(argc, argv);
    return Fl::run();
}

Drawing

只有在某些特定的地方才可以執行繪圖到螢幕顯示器的 FLTK 程式碼。在其他地方呼叫這些函數將會導致未定義的行為!

  • 最常見的地方是在 Fl_Widget::draw() 方法內部。要在此處編寫程式碼, 必須將現有 Fl_Widget 類別之一子類化並實作自己的 draw() 版本。
  • 也可以建立自訂 boxtypes 與 labeltypes。這些涉及編寫可由現有 Fl_Widget::draw() 方法呼叫的小程序。 這些「類型」由儲存在 widget 的 box(), labeltype() 和可能的其他屬性中的 8 位元索引標識。
  • 可以呼叫 Fl_Window::make_current() 來對視窗部件進行增量更新。使用 Fl_Widget::window() 來尋找視窗。

下面的程式是畫一個 X 的示範程式。

#include <FL/Fl.H>
#include <FL/Fl_Double_Window.H>
#include <FL/fl_draw.H>

class DrawX : public Fl_Widget {
public:
    DrawX(int X, int Y, int W, int H, const char *L = 0)
        : Fl_Widget(X, Y, W, H, L) {}

    virtual void draw() FL_OVERRIDE {
        fl_color(FL_YELLOW);
        fl_rectf(x(), y(), w(), h());
        fl_color(FL_BLUE);
        int x1 = x(), y1 = y();
        int x2 = x() + w() - 1, y2 = y() + h() - 1;
        fl_line(x1, y1, x2, y2);
        fl_line(x1, y2, x2, y1);
    }
};

int main() {
    Fl_Double_Window win(400, 400, "Draw X");
    DrawX draw_x(10, 10, win.w() - 20, win.h() - 20);
    draw_x.color(FL_YELLOW);
    win.resizable(draw_x);
    win.show();
    return (Fl::run());
}

Event

每次使用者移動滑鼠指標、點擊按鈕或按下按鍵時,都會產生一個事件 (event) 並發送到使用者的應用程式。 事件也可以來自其他程序,例如視窗管理器 (window manager)。

在 FLTK 中事件由傳遞給 handle() 方法的整數參數標識,該方法覆蓋 Fl_Widget::handle() virtual method。 有關最近事件的其他資訊儲存在靜態位置,並透過呼叫獲取 Fl::event_∗() 方法。 此靜態資訊一直有效,直到從視窗系統讀取下一個事件為止,因此可以在 handle() 方法之外查看它,例如在 callbacks 中。

下面是一個繼承 Fl_Window,並且在 handle() 方法印出事件名稱的例子。

#include <FL/Fl.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Window.H>
#include <FL/names.h> // FLTK 1.1.8 and up
#include <cstdlib>

class PrintEevent_Window : public Fl_Window {
    int handle(int e) {
        fprintf(stderr, "EVENT: %s(%d)\n", fl_eventnames[e], e);
        return (Fl_Window::handle(e));
    }

public:
    PrintEevent_Window(int W, int H, const char *L = 0) : Fl_Window(W, H, L) {
    }
};

void close_callback(Fl_Widget *, void *) {
    if (Fl::event() == FL_SHORTCUT && Fl::event_key() == FL_Escape)
        return;

    exit(0);
}

int main(int argc, char **argv) {
    PrintEevent_Window *window = new PrintEevent_Window(340, 180);

    Fl_Button *button = new Fl_Button(20, 40, 300, 100, "Exit");
    button->type(FL_NORMAL_BUTTON);
    button->color(FL_WHITE);
    button->labelcolor(FL_BLACK);
    button->shortcut(FL_ALT + 'q');
    button->callback(close_callback);

    window->end();
    window->callback(close_callback);
    window->show(argc, argv);
    return Fl::run();
}

OpenGL

FLTK 提供了 Fl_Gl_Window,可以繼承此類別並且在 draw() 函式繪制自己需要的內容。

#include <FL/Fl.H>
#include <FL/Fl_Gl_Window.H>
#include <FL/gl.h>
#include <cstdlib>

class MyGlWindow : public Fl_Gl_Window {
    void draw() {
        if (!valid()) {
            glLoadIdentity();
            glViewport(0, 0, w(), h());
            glOrtho(-w(), w(), -h(), h(), -1, 1);
        }

        glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
        glColor3f(0.0, 0.0, 0.0);
        glBegin(GL_LINE_STRIP);
        glVertex2f(w(), h());
        glVertex2f(-w(), -h());
        glEnd();
        glBegin(GL_LINE_STRIP);
        glVertex2f(w(), -h());
        glVertex2f(-w(), h());
        glEnd();
    }

public:
    MyGlWindow(int X, int Y, int W, int H, const char *L = 0)
        : Fl_Gl_Window(X, Y, W, H, L) {}
};

int main(int argc, char **argv) {
    Fl_Window win(640, 480, "OpenGL sample");
    MyGlWindow mygl(10, 10, win.w() - 20, win.h() - 20);
    win.end();
    win.callback([](Fl_Widget *, void *) {
        if (Fl::event() == FL_SHORTCUT && Fl::event_key() == FL_Escape)
            return;

        exit(0);
    });

    win.resizable(mygl);
    win.show(argc, argv);
    return (Fl::run());
}

CMakeLists.txt 需要增加 FLTK OpenGL 的部份:

target_link_libraries(sample PRIVATE fltk::fltk fltk::gl)

相關連結

2025/08/07

理財投資資訊

總論

定存

定存的第一個特性就是通常本金不會有損失(注意如果需要換匯,換匯以後本金仍然可能會有損失), 再來就是隨時可以解約,只是利息打折,而不需要任何解約賠償。 因此如果有一段時間後才需要使用的現金部位,那麼就可以考慮使用定存。 定存有其獨特的功能和特徵,其他的理財工具並無法完全取代。 不過定存會有無法擊敗通膨的問題,因此除非採用極為保守的理財策略,建議不要全部都使用定存。

債券

債券是一種債務工具,發行債券的目的是在預先指定的期間內透過向外借貸來籌集資金。發債機構一般會承諾在指定日期償還本金及利息。 市場或會將債券稱為“票據”,雖然名稱不同,但所指的是同一類債務工具。

造成債券價格的波動因素,就稱為風險。市場利率會造成債券價格波動,這稱為利率風險。發行機構的信用發生危機,也會造成價格波動,稱為信用風險。 如果是持有不同貨幣計價的債券而帶來的價格變動,則是匯率風險。

債券可根據其付息方式來區分:
  • 固定利率債券
  • 浮動利率債券
  • 次級債券
  • 可換股債券
  • 抗通脹證券
  • 零息債券

債券價格與利息之間存在典型的反向關係。即利率升高時,債券價格通常會下跌,反之亦然。 利率可說是債券市場最重要的觀察指標,一般的認知是「利率漲、債券跌;利率跌、債券漲」。 值得注意的是,並非所有類型的債券價格都與市場利率(如美國聯邦資金利率或公債殖利率)走反方向。公司債與高收益債比較不受利率攀升的影響, 因為利率走揚通常是經濟較為活絡的時期,企業反而因為經營環境改善,更能順利償還利息與本金,為投資人創造額外報酬。

債券與其他投資資產最大的不同在於,債券能夠提供高度穩定的收益,而對於使用同貨幣的人來說,唯一影響債券的只有利率的改變。 提升利率會造成債券有更高的殖利率以及較低的價格,也因此會造成資產虧損。而利率的改變會反應多少幅度在債券的價格, 則跟債券的存續期間(Duration)長短有關。

存續期間是比較兩個固定收益投資最重要的關鍵,而公式則是當利率提昇一個百分點,那麼債券的價格將下跌的百分點會與存續期間一致, 例如七年期債券會下跌 7%。而這個公式原則上是長期債券和短期債券都適用,但有一個假設是當升息的時候, 長期債券與短期債券所構成的殖利率曲線會平行遷移,也就是不管長債或短債,對利率改變有相同的殖利率變化。 不過實際上則並非如此,因為短期債券的殖利率的確深受利率升降的影響,而長期債券的殖利率則是反應市場對通膨預期。

當我們在評估債券市場潛在的風險時,思考債券為什麼可以降低股市高波動性所帶來的風險是很重要的。簡單來說, 債券投資人對升息產生恐懼是合理的,但是要瞭解的是債券的熊市與股市(或其他風險資產)的熊市是完全不同的。事實上, 股市下跌超過20%通常被定義為熊市,但對債券投資人來說,債券市場的熊市指的則是負報酬的情況。

而似乎有不少投資者認為,利率上升時,債券ETF的表現會遜於個別債券。當中的思維是只要持有個別債券至到期日,收益就不受利率升跌的影響。 而一般債券 ETF 不會到期,因此投資者無法避免利率上升帶來的損失。 一些投資者偏好個別債券多於互惠基金,是因為他們相信債券讓他們免受利率上升影響:即使利率上升導致債券價格下降, 他們依然可以選擇持有債券至到期日,收回預期的本金和利息。債券基金或ETF沒有預先定明的收益或本金,因此被視為對利率較敏感的產品。 許多投資者忽視的是債券收益固定,但債券基金或 ETF 的收益通常會在利率上升時增加。而這些增加的收入,往往能隨著時間補償利率上升初期的價格虧損。 也就是說,對於長期投資的投資人而言,債券 ETF 更能讓投資者分散債券的信貸風險,減少債券違約可能帶來的損失。 債券市場的交易成本高,規模大的債券 ETF 有助於降低交易時的費用,進而減少開支。

股、債還有一個很大的差異,那就是債券在價格下跌的時候也意味著更高的殖利率,但股票則不一定有這樣的關係存在,這是因為股票的價格不只受殖利率影響, 也受公司盈餘所影響,也就是股價有兩個影響因素。

投機等級債券為非投資等級,通常被歸類為垃圾債券 (Junk Bond),為了吸引投資者,通常有較高的殖利率。 垃圾債券的特性介乎股票與國債之間,無法為傳統的股債混合投資組合帶來分散投資的效益。而由於流動性欠佳, 在一些情況下垃圾債券更可能會有大幅下跌的情況。 垃圾債券除了跟股票、債券一樣都會受經濟增長、通脹等因素影響外,還多了流動性的風險。

經驗豐富的主動式基金經理有能力從低流動性的資產當中找出價格被低估的資產,從而獲得較高而且穩定的超額回報。 因此,如果投資者希望持有高收益債券資產,按照晨星的建議是選擇主動式管理的高收益債劵型基金,重點是要選對投資基金。 但是一般而言,使用者難以挑選出獲得較高而且穩定的超額回報的主動式基金,所以結論是對於一般較為保守的投資人而言不要投資高收益債劵型基金, 大部份投資者還是較適合投資於高評級的股票與債券。

股票

股票是一種有價證券,股份有限公司將其所有權藉由這種有價證券進行分配。 因為股份有限公司需要籌措長期資金,因此將股票發給投資者作為公司資本的部分所有權憑證, 成為股東以此獲得股利(股票股利)或/且股息(現金股利),並分享公司成長或交易市場波動帶來的利潤; 但也要共同承擔公司運作錯誤所帶來的風險。

GDP(Gross Domestic Product)就是國內生產毛額,指的是某一個經濟體(國家)在一特定時間內的經濟活動所創造出來的價值總和。

巴菲特提出了衡量目前市場情況的 GDP 理論。 根據 GDP 理論,大盤與 GDP 呈現相關的走向,由所有上市公司的市值,佔 GDP 的比重就可以知道目前市場的狀況。

巴菲特指標 = 股市總市值/GDP國民生產毛額
巴菲特指出,儘管這個比值並非萬能,卻可能是在特定時點,「衡量市場價位高低的最佳指標」。此一比值,若大幅降低, 代表企業產生的經濟價值,嚴重被低估,買進股票,會有不錯表現。反之,若如同 1999 年、2000 年的狀況, 急劇竄高,代表已經「名不副實」,是危險狀況(但此時的市場氣氛,往往也最樂觀)。

但是注意的是,巴菲特指標超過前高也不代表會立刻崩盤,股市的崩盤或者是修正有多種原因, 因此不能說超過前高就可能會發生崩盤。

房地產與 REITs

如果投資人對投資組合只有股票和債券兩種資產感到不足,可以考慮商品(Commodity)以及代表不動產的 REITs 作為資產配置的重要資產類別, 尤其這兩種較新的資產與股、債相關性均不高,因此也有助於整體投資組合的風險分散。

一般的投資人如果是要投資房地產,比較好的方法是使用 REITs。REITs 是一種不動產證券化商品,是受託機構以公開募集或私募方式, 交付受益證券,募集一筆資金,再投資不動產、不動產相關權利、不動產相關有價證券等。而證券化的不動產主要為辦公大樓、購物中心、倉庫等商業不動產為主。 運作方式是將不動產的所有權證券化,募集完成後將於在公開市場上購買,沒有最低額度限制, 投資人可以透過投資 REIT 來參與不動產市場。因為房地產的報酬與其它資產的相關性低,在投資組合裡加入 REITs,理論上可以降低整體的風險。

對 REITs 而言,基本的觀察項目為:

  1. 租金來源:承租率
  2. 標的穩定/成長性
  3. 標的風險分散性

不過台灣的 REITs 因為台灣本身的法規限制(只能採用信託制),所以產品缺乏多元性與成長性, 只有某些有避稅需求的人因為分離課稅的制度而可能投資,投資價值較低。

人一生的理財通常會有自住需求的問題。那麼何時是買房的時機?那就是,你有自住需求,已經準備好頭期款, 並且可以在不影響目前生活的情況下付得出貸款,那就是可以買房子的時候。對大多數人來說,買房子,特別是第一次買的房子, 那是要自用,而非投資的標的。 所以以自用的觀點出發,好好的篩選一間讓自己住的舒適,住得便利的房子,那就是很好的購屋計畫。 不是買了之後增值才是買了好屋。也不要再想買了之後房價的漲跌,因為它是買來用的。

期貨、選擇權和權證

期貨原本的用途是用來避險,但是發展到今日,已經成為比較投機的金融工具,這些金融產品都是零和遊戲,不建議散戶和一般投資人進行操作。 不論買賣期貨背後是出於甚麼原因,緊記期貨是槓桿式投資工具。槓桿效應既能使回報以倍數增大,但同樣亦能使損失以倍數遞增。 當然,如果是以 1 倍槓桿的方式用來避險,那麼期貨對投資人而言是一項有效的工具。

外匯

外幣交易的目的主要有二個,第一個是捕捉外幣的升值潛力及分散資產組合的風險,第二個是傳統對沖外匯眨值的工具。 對於台灣的投資者來說,主要有用的外國貨幣為美元(買賣的匯差理論上在各家銀行都是最小),再來是目前還未完全可以自由兌換的人民幣, 以及傳統上視為高息貨幣、波動度較大的原物料貨幣澳幣。

而外幣投資並不能以利息作為主要的考量點,而是要以是否有升值潛力作為最重要的考量,再來是持有該貨幣可以對沖本國貨幣貶值的風險。 因此,像是南非幣等本國財政出現赤字且波動過大的貨幣並不適合作為投資之用(也不適合作為投機的標的)。

美元指數(US Dollar Index,USDX)是通過平均美元與六種國際主要外匯的匯率得出的。美元指數顯示的是美元的綜合值,為衡量美元強弱的指標。


槓桿式外匯是一種利用「槓桿效應」而進行的投資。基本上,你以按金形式(俗稱「孖展」)買入一種外幣,並預期該外幣會相對於另一種貨幣轉強或轉弱。 最終盈虧將視乎你就指定的外幣合約開倉及平倉時的價格差別而定。

槓桿式外匯是以合約的方式進行交易。外幣的合約價值由你和你的中介人議定。 目前,最普遍買賣的外幣合約為歐元、英鎊、日圓、瑞士法郎及澳幣。 利潤及虧損是根據指定貨幣相對美元(或另一種貨幣)於開倉後的走勢而定。 在未平倉前,任何帳戶內的利潤或虧損均屬浮動性質,祇有當平倉時,利潤或虧損才會兌現。

槓桿式外匯投資並不適合散戶和一般投資人進行操作。

使用指數基金或者是 ETF

不論使用何者指標,都有泡沫破滅以後,人們才知道這是泡沫的問題。 因此如果是一般投資人要投資股票或者是建立股票與債券的長期投資組合, 建議選擇指數基金或者是追蹤指數的 ETF 並且長期投資。

如果你對一些個股真的非常熟悉,確定可以發揮自己在這個個股上的優勢,可以使用一定的資金比例投資在個股上, 不用硬性強迫自己一定要全部的資金都投資指數基金或者是追蹤指數的 ETF。或者是你有一些投資策略想要使用(例如長期持有並且分散在各種股票的投資組合), 只要你自己知道自己做什麼,就可以使用自己的投資策略。

注意:因為台灣公債長期利率偏低(甚至比定存還低,那我放定存就好)而且沒有相關的台灣公債 ETF, 所以因為台灣環境的關係,我會往美國公債或者是高等級的公司債 ETF 去尋找投資標的。 對於中國而言,我會建議中國政府應該引導基金公司遂步降低其費用(當然,作為交換,就是操作的時候選擇降低費用的 ETF,結果就是擴大其規模)。 再來就是,中國需要費用夠低的股債组合(股票以滬深三百指數 ETF 為主,債券以中國短期公債 ETF 為主, 當然債券最好也有中國中期公債或者是中國長期公債的選項),並且讓中國公民了解而且願意使用這方面的工具。

注意:按照中華民國(台灣)目前的稅制,海外營利所得依成分股來源地區區分為「大陸地區」與「大陸地區以外的海外地區」。 對於設籍中華民國並居住於台灣地區的個人,根據《台灣地區與大陸地區人民關係條例》的特別規定,大陸地區的所得需納入綜合所得稅申報與繳納, 營利所得也不例外,同時不是稽徵機關提供查詢的所得資料範圍,因此需要自行申報。 因此,如果是投資中國股票的 ETF,需要注意其分配收益的分類(還有要注意雖然是投資中國的 ETF,不代表所有收益都是從大陸地區配出, 要看 ETF 所投資個股的公司登記地)。至於海外地區收入,達 100 萬需要納入基本所得額並且申報,如果達 750 萬,課徵海外所得稅 20%。 就我個人而言,投資「大陸地區」的收益不論是股票或者是債券,在稅收方面十分麻煩,目前如果我確定我投資的標的有此收益項目我會直接放棄。 同時我會建議中華民國政府納入提供查詢的所得資料範圍並且不需要自行申報, 或者將「大陸地區」視為外國,跟海外地區一樣的管理方式,而不是像現在這樣掩耳盜鈴的方式。

注意:如果是設籍中華民國並居住於台灣地區的個人投資台灣上市上櫃股票, 以及投資產品中收益來源為「大陸地區」,若單次股利給付達 2 萬元(含)以上,需扣繳 2.11% 的二代健保補充保費, 且以單次給付上限 1,000 萬元為限。

台灣缺少全市場類型的 ETF,下面是追蹤上市中大型股的 ETF:

  • 元大台灣50 (0050):投資台灣五十指數,其成分股涵蓋台灣證券市場中市值前五十大之上市公司。 競爭產品為富邦台50 (006208)
  • 元大中型100 (0051):投資臺灣中型 100 指數,其成分股涵蓋「臺灣50 指數」成分股外、市值最大的前一百家上市公司
  • 永豐臺灣加權 (006204):追蹤指數為台灣加權指數,不過實際上是投資市值前二百大的上市公司 (透過歷史資料分析個股間與指數間之相關性,計算出最適化權重組合),缺點是規模過小導致其費用率過高

下面是追蹤上櫃權值股的 ETF:

  • 元大富櫃50 (006201):投資富櫃五十指數,其成分股需要最近四季每股稅後純益合計數為正數者, 涵蓋台灣櫃買市場中符合資格的市值前五十大之上櫃公司。基金規模並不算大,不過買賣價差在可以接受的範圍。

如果需要投資上市與上櫃中大型股票的 ETF,可以考慮追蹤 MSCI 臺灣指數的 ETF。台灣市場上目前有二隻 ETF 追蹤此指數, 但是規模過小而且有買賣價差過大的問題,因此一般而言不建議使用這二隻 ETF 進行投資。

  • 富邦摩台 (0057)
  • 元大MSCI台灣 (006203)

參考資料

2025/07/05

Beyond 中國音樂計畫

我沒有仔細查過目前中國的政策,有可能和實際狀況有出入,所以只是寫著玩的。不過也不可能有人會真的照做吧?所以就只是寫下想法。

這個計畫包含下列三個部份:

  1. 中國允許定調以後的六四學運資料出現在自身網路上
  2. 如果禁歌中有 Beyond 的部份,那麼就解禁(Beyond 全部的歌曲都可以聽到,並且成為接下來中國言論管制的底線)
  3. 強調 Beyond 的精神,也就是目前中國需要的部份:超越自我

第一點和 Beyond 有關的部份就是 Beyond 一些歌曲被認為與六四有關,所以第一步是解決六四運動的定位問題。 而我的答案也很簡單,避而不談是錯誤的,中國共產黨要做的是做出官方的定調,網路上能查到的資料就是中國政府 的定調版本。我的意思是,中國官方可以定調為一開始是中國學生為了追求民主而出現的學運,但是被境外敵對份子 滲透(其實就是美國)而加以利用,導致學運出現直接對抗政府並且無法停止的情況,以致於中國政府必須以強制的手 段中止學運。

第二點很簡單,Beyond 的全部歌曲都可以聽到。如果真的有疑慮,採用目前的做法,也就是沒問題的歌曲一般 人都可以聽到,如果真的有爭議的歌曲,在音樂平台上則採取只有登䤸後的 VIP 會員可以聽到的方法。

第三點就是目前中國需要的,也就是 Beyond 的建團精神。在1998年出版的樂隊自傳《擁抱Beyond歲月》中, 鼓手葉世榮解釋,由於樂隊喜愛自己創作,有別於當時其他樂隊多數翻唱外國樂隊的作品,故「Beyond」有超越一般 樂隊所涉足的音樂領域的意思,但葉世榮重申,Beyond 不是要超越他人,而是要超越自己。而藉由這個計畫,也鼓勵 中國音樂創作者在找出自己在音樂創作上的強項或者是舒適區以後,能夠嘗試更多不同的音樂流派以及曲調。

然後有可能會出現一些小混亂的情況,這時候不要急著又要禁掉 Beyond 的歌曲(我知道有些人就是蠢,所以還是先說 比較好),中國政府應該學著有方法和管道能夠正確的引導與論,如果這都無法做到,那麼很多事都只會事倍功半。

最後,我認為中國目前從上到下急迫需要的是思考周密成熟的官員,觀察以前中國的政策(包含文化大革命和生育管制的一胎化政策), 都會有為了解決一個問題,結果產生新的問題的狀況。在施政這麼多年以後,我想中國政府也應該有一批有能力的官員了, 所以重點就是需要思慮周密而且又能夠施行政策,在全盤思考目前的中國的問題以後改善目前的情況。

2025/03/25

C++ Thread

Pthreads

POSIX Threads 是 POSIX 的執行緒標準,定義了建立和操控執行緒的一套API。

Process 和 Threads 之間的不同點:

  • Process:
    A process is a program placed in memory or running with all the run-time environment (or all the resources) associated with it.
  • Thread:
    A thread is a portion or part of the process and the smallest sequential unit of instructions processed independently by the scheduler of the operating system.
    It is simply an execution or processing unit composed of a set of instructions contained in the process.

Process 之間的資源是隔離的,而同一個 Process 內的 Thread 之間的記憶體資源是共用的,所以使用 Thread 需要小心的處理全域變數, 需要使用互斥鎖(Mutex)等完成執行緒之間的同步管理。 除了共同的記憶體,執行緒區域儲存區(Thread Local Storage,TLS) 可以讓執行緒有私人的資料儲存區,讓執行緒可以儲存各自執行緒的資料。

Pthreads API 全都以 "pthread_" 開頭,並可以分為四類:

  • 執行緒管理,例如建立執行緒,等待(join)執行緒,查詢執行緒狀態等。
  • 互斥鎖(Mutex):建立、摧毀、鎖定、解鎖、設定屬性等操作
  • 條件變數(Condition Variable):建立、摧毀、等待、通知、設定與查詢屬性等操作
  • 使用了互斥鎖的執行緒間的同步管理

下面是使用的例子:

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

static void wait(void) {
    time_t start_time = time(NULL);

    while (time(NULL) == start_time) {
        /* do nothing except chew CPU slices for up to one second */
    }
}

static void *thread_func(void *vptr_args) {
    int i;

    for (i = 0; i < 20; i++) {
        fputs("  b\n", stderr);
        wait();
    }

    return NULL;
}

int main(void) {
    int i;
    pthread_t thread;

    if (pthread_create(&thread, NULL, thread_func, NULL) != 0) {
        return EXIT_FAILURE;
    }

    for (i = 0; i < 20; i++) {
        puts("a");
        wait();
    }

    if (pthread_join(thread, NULL) != 0) {
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

建立執行緒的函式如下:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

執行緒屬性 pthread_attr_t 可以設定 __detachstate,表示新執行緒是否與行程中其他執行緒脫離同步。 如果設定為 PTHREAD_CREATE_DETACHED,則新執行緒不能用 pthread_join() 來同步,且在退出時自行釋放所占用的資源。 預設為 PTHREAD_CREATE_JOINABLE 狀態。也可以先建立執行緒並執行以後用 pthread_detach() 來設定。 一旦設定為 PTHREAD_CREATE_DETACHED 狀態,不論是建立時設定還是執行時設定,都不能再恢復到 PTHREAD_CREATE_JOINABLE 狀態。

C++ Thread

自 C++11 開始,C++ 標準函式庫提供了 Thread library。

下面就是一個使用的例子。

#include <iostream>
#include <thread>

void myfunc() { std::cout << "At myfunc..." << std::endl; }

int main() {
    std::thread t1(myfunc);

    std::this_thread::sleep_for(std::chrono::seconds(1));

    t1.join();
    return 0;
}

參考連結

2025/03/09

C++ 學習筆記

C++ 設計原則

Bjarne Stroustrup 博士在 20 世紀 80 年代在貝爾實驗室工作期間發明並實現了 C++。 一開始的想法可以追溯到 1979 年,起初這種語言被稱作「C with Classes」,作為 C 語言的增強版出現。 C++ 在 C 的基礎上增加功能,並且儘量與 C 的相容。 C++ 比起其它語言有巨大優勢的地方,就是可以直接使用 C 的函式。

C++ 編譯器自由軟體的主要實作為 GNU Compiler Collection (GCC) 與 Clang

C++ 是一個通用的語言,抽象資料型別為 C++ 的核心概念, 含有一些系統程式領域的特性,並且期望能夠達到下列的目標:

  • is a better C
  • supports data abstraction
  • supports object-oriented programming
  • supports generic programming
在《C++語言的設計和演化》(1994)中,Bjarne Stroustrup 描述了他在設計C++時,所使用的一些原則。
  • C++ 設計成靜態類型、和 C同樣高效率且可移植的多用途程式設計語言。
  • C++ 設計成直接的和廣泛的支援多種程式設計風格。
  • C++ 設計成給程式設計者更多的選擇,即使可能導致程式設計者選擇錯誤。
  • C++ 設計成盡可能與 C 相容,籍此提供一個從 C 到 C++ 的平滑過渡。
  • C++ 避免平臺限定或沒有普遍用途的特性。
  • C++ 不使用會帶來額外開銷的特性。
  • C++ 設計成無需複雜的程式設計環境。

但是,C++ 標準並沒有規範 Object Model 的實作方式,同時 C++ 也缺乏 ABI 的標準, 因此在 Windows Platform 上各家編譯器所產出的 DLL 很難互通,這是 C++ 在 binary level 上的缺點。


下面是一個 C 的 Hello World 程式:

#include <stdio.h>

int main() {
   /* my first program in C */
   printf("Hello, World! \n");

   return 0;
}

In a C program, the semicolon is a statement terminator. That is, each individual statement must be ended with a semicolon. It indicates the end of one logical entity.

C++標準表頭檔沒有副檔名,因為副檔名的命名規則隨各編譯器而有所不同。 下面是 C++ 版的 Hello World:

#include <iostream>

int main() {
    std::cout << "Hello! World!\n";

    return 0;
}

下面是一個範例,使用者在命令列輸入一個字串,然後程式計算字串 MD5 的值並且輸出:

//
// g++ test.cpp -std=c++17 -lcrypto
//
#include <cstring>
#include <string>
#include <iostream>
#include <openssl/md5.h>

std::string MD5(const std::string &src)
{
    MD5_CTX ctx;

    std::string md5_string;
    unsigned char md[16] = {0};
    char tmp[3] = {0};

    MD5_Init(&ctx);
    MD5_Update(&ctx, src.c_str(), src.size());
    MD5_Final(md, &ctx);

    for (int i = 0; i < 16; ++i)
    {
        memset(tmp, 0x00, sizeof(tmp));
        snprintf(tmp, sizeof(tmp), "%02X", md[i]);
        md5_string += tmp;
    }

    return md5_string;
}

int main(int argc, char *argv[])
{
    if (argc == 1)
    {
        std::cout << "Please give a string." << std::endl;
    }
    else
    {
        std::string mystring = argv[1];
        std::cout << "String: " << mystring << std::endl;
        std::cout << "Result: " << MD5(mystring) << std::endl;
    }
    return 0;
}

下面是一個範例,使用者在命令列輸入一個字串,然後程式計算字串 SHA256 的值並且輸出:

//
// g++ test.cpp -std=c++17 -lcrypto
//
#include <cstring>
#include <string>
#include <iostream>
#include <openssl/sha.h>

std::string SHA256(const std::string &src)
{
    SHA256_CTX ctx;

    std::string sha256_string;
    unsigned char md[32] = {0};
    char tmp[3] = {0};

    SHA256_Init(&ctx);
    SHA256_Update(&ctx, src.c_str(), src.size());
    SHA256_Final(md, &ctx);

    for (int i = 0; i < 32; ++i)
    {
        memset(tmp, 0x00, sizeof(tmp));
        snprintf(tmp, sizeof(tmp), "%02X", md[i]);
        sha256_string += tmp;
    }

    return sha256_string;
}

int main(int argc, char *argv[])
{
    if (argc == 1)
    {
        std::cout << "Please give a string." << std::endl;
    }
    else
    {
        std::string mystring = argv[1];
        std::cout << "String: " << mystring << std::endl;
        std::cout << "Result: " << SHA256(mystring) << std::endl;
    }
    return 0;
}

Date types

C++ 中基本的資料型態主要區分為「整數」(Integer)、「浮點數」(Float)、「字元」(Character),而這幾種還可以細分:

  • 整數:用 來表示整數值,可以區分為 short、int 與 long,可容納的大小各不相同,short的長度為半個 word,int 表示一個 word, 而 long 可能是一個或兩個 word(要看採用的 data model),在 32 位元機器上 int 與 long 的長度通常是相同的, 型態的長度越長,表示可表示的整數值範圍越大。 如果是 64 位元的機器 (64-bit computing), 大多數的 UNIX 系統在 64 位元採用 LP64 data model,這時候 long 就會是 8 bytes。 而 Windows 64 位元採用 LLP64 這個 data model,這時候 long 的大小仍然還是 4 bytes,也就是差異出現在 long 這個型別上。
  • 浮點數:用來表示小數值,可以區分為 float、double 與 long double,float 的長度為一個 word,double 的長度為二個 word, long double 長度為 3 或 4 個word。
  • 字元:用來儲存字元,長度為 1 個位元組,其字元編碼主要依 ASCII 表而來,由於字元在記憶體中所佔有的空間較小, 所以它也可以用來儲存較小範圍的整數。

以上的資料型態在記憶體中所佔有的大小依平台系統而有所差異,word 的大小取決於機器,在 32 位元機器上通常一個 word 是 4 個位元組, 如果想要知道這些資料型態在所使用的平台上所佔有的記憶體空間有多少,最好的作法是使用 sizeof() 運算子,取得確實的記憶體大小。

在 C11 標準中,建議包括 stdint.h 程式庫(亦包含在 C++ 的標準函式庫), 使用 int8_tint16_tint32_tint64_tuint8_tuint16_tuint32_tuint64_t 等作為整數型態的宣告,以避免平台相依性的問題。

有時候我們需要表示非十進位的數字(例如八進位或者十六進位)。 在 C 其八進位通常以「0」開頭(注意是數字 0),例如 0640;而十六進位通常以「0x」或「0X」開頭,例如 0xEF 或 0XEF; 而二進位通常以「0b」開頭,例如 0b1。

要注意的是,如果是需要精確求值的場合,那麼需要考慮使用 big number library 來實作,而不是使用浮點數。 有些程式語言(例如 Tcl、Common Lisp 等)直接支援大整數運算,無需顯式地使用 API。 使用浮點數有兩個最根本的問題:輸入與儲存的值不一定精確計算的結果會有誤差

另外,電腦的浮點數常用二進位或十六進位運算與儲存,所以在程式中的十進位數需要轉換為二進位或十六進位, 但是一個簡單的十進位數卻可能轉換成無限位的二進位或十六進位數,而儲存的位置是有限的, 算出來的結果就不夠精確,結果也可能受到四捨五入誤差的影響,如果再用來計算其它值,誤差就會愈滾愈大。


There are two kinds of expressions in C −

  • lvalue − Expressions that refer to a memory location are called "lvalue" expressions. An lvalue may appear as either the left-hand or right-hand side of an assignment.
  • rvalue − The term rvalue refers to a data value that is stored at some address in memory. An rvalue is an expression that cannot have a value assigned to it which means an rvalue may appear on the right-hand side but not on the left-hand side of an assignment.

Constants refer to fixed values that the program may not alter during its execution. These fixed values are also called literals.

Constants can be of any of the basic data types like an integer constant, a floating constant, a character constant, or a string literal.

There are two simple ways in C to define constants −

  • Using #define preprocessor.
  • Using const keyword.

Given below is the form to use #define preprocessor to define a constant −

#define identifier value

The following example explains it in detail −

#include <stdio.h>

#define LENGTH 10
#define WIDTH  5
#define NEWLINE '\n'

int main() {
   int area;

   area = LENGTH * WIDTH;
   printf("value of area : %d", area);
   printf("%c", NEWLINE);

   return 0;
}

You can use const prefix to declare constants with a specific type as follows −

const type variable = value;

The following example explains it in detail −

#include <stdio.h>

int main() {
   const int  LENGTH = 10;
   const int  WIDTH = 5;
   const char NEWLINE = '\n';
   int area;

   area = LENGTH * WIDTH;
   printf("value of area : %d", area);
   printf("%c", NEWLINE);

   return 0;
}

Macros in C and C++ are tokens that are processed by the preprocessor before compilation. Each instance of a macro token is replaced with its defined value or expression before the file is compiled. Macros are commonly used in C-style programming to define compile-time constant values. However, macros are error-prone and difficult to debug. In modern C++, you should prefer constexpr variables for compile-time constants:

#define SIZE 10 // C-style
constexpr int size = 10; // modern C++

constexpr 可以說是 C++11 對 const 修飾字的加強。 常數表達式 (constant expression) 代表的是可以在編譯時期經過固定確定運算得到確切值的表達式。


A storage class defines the scope (visibility) and life-time of variables and/or functions within a C Program. They precede the type that they modify. We have four different storage classes in a C program −

  • auto
  • register
  • static
  • extern

The auto storage class is the default storage class for all local variables.

{
   int mount;
   auto int month;
}

The register storage class is used to define local variables that should be stored in a register instead of RAM.

{
   register int  miles;
}

The static storage class instructs the compiler to keep a local variable in existence during the life-time of the program instead of creating and destroying it each time it comes into and goes out of scope. Therefore, making local variables static allows them to maintain their values between function calls.

The extern storage class is used to give a reference of a global variable that is visible to ALL the program files. When you use 'extern', the variable cannot be initialized however, it points the variable name at a storage location that has been previously defined.


Arrays a kind of data structure that can store a fixed-size sequential collection of elements of the same type. An array is used to store a collection of data, but it is often more useful to think of an array as a collection of variables of the same type.

You can initialize an array in C either one by one or using a single statement as follows −

double balance[5] = {1000.0, 2.0, 3.4, 7.0, 50.0};

If you omit the size of the array, an array just big enough to hold the initialization is created. Therefore, if you write −

double balance[] = {1000.0, 2.0, 3.4, 7.0, 50.0};

A pointer is a variable whose value is the address of another variable, i.e., direct address of the memory location. Like any variable or constant, you must declare a pointer before using it to store any variable address.

#include <stdio.h>

int main () {

   int  var = 20;   /* actual variable declaration */
   int  *ip;        /* pointer variable declaration */

   ip = &var;  /* store address of var in pointer variable*/

   printf("Address of var variable: %x\n", &var  );

   /* address stored in pointer variable */
   printf("Address stored in ip variable: %x\n", ip );

   /* access the value using the pointer */
   printf("Value of *ip variable: %d\n", *ip );

   return 0;
}

It is always a good practice to assign a NULL value to a pointer variable in case you do not have an exact address to be assigned. This is done at the time of variable declaration. A pointer that is assigned NULL is called a null pointer.

Arrays are not pointers! Arrays look like pointers, and pointers can refer to array objects. For example, people sometimes think that char s[] is identical to char *s. But they aren’t identical. The array declaration char s[12] requests that space for 12 characters be set aside, to be known by the name s. The pointer declaration char *p, on the other hand, requests a place that holds a pointer, to be known by the name p. This pointer can point to almost anywhere: to any char, to any contiguous array of chars, or frankly nowhere.


參考(Reference)是 C++ 新增加的語言特性,為物件的別名(Alias),也就是替代名稱,對參考名稱存取時該有什麼行為, 都參考了來源物件該有的行為,在 C++ 中,「物件」這個名詞,不單只是指類別的實例,而是指記憶體中的一塊資料。

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

int n = 10;
int *p = &n;
int &r = n;

上面的程式中,最後一行定義參考。參考一定要初始化,否則無法通過編譯。 與指標 (pointer) 不同,參考在初始化之後不能參照不同的物件,或設為 null,也因此參考一般而言比指標安全。


C 並沒有為 String 定義一個型別,字串在 C 語言中是一個以 null character '\0' 為結尾的一維陣列。 這讓 C 的字串需要小心的處理。

如果要在 C 進行字串串接,可以使用 asprintf,雖然是 GNU 自行擴充的 function,但是使用上很方便。

#define _GNU_SOURCE
#include <stdio.h>

下面則是使用的例子:

char *s;
asprintf(&s,"hello,%s","-Reader-");
printf("%s\n",s);
if (s) free(s);

C++ 在這一點則有所改變,加入了 std::string 作為字串物件類別。


Arrays allow to define type of variables that can hold several data items of the same kind. Similarly structure is another user defined data type available in C that allows to combine data items of different kinds.

To define a structure, you must use the struct statement. The struct statement defines a new data type, with more than one member. The format of the struct statement is as follows −

struct [structure tag] {

   member definition;
   member definition;
   ...
   member definition;
} [one or more structure variables];

The structure tag is optional and each member definition is a normal variable definition, such as int i; or float f; or any other valid variable definition. At the end of the structure's definition, before the final semicolon, you can specify one or more structure variables but it is optional. Here is the way you would declare the Book structure −

struct Books {
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
} book;

An enum is a special type that represents a group of constants (unchangeable values).

下面是一個 enum 的例子:

enum Level {
  LOW,
  MEDIUM,
  HIGH
}; 

下面就是宣告變數並且初始化的例子:

enum Level myVar = MEDIUM;

「Scoped and strongly typed enums」是 C++11 時所引進的一個新的功能,主要是要取代舊的列舉型別(enum)。 基本用法是在 enum 後面,再加上 class 或 struct;而要使用定義的值的時候,一定要加上範圍(scope、在這裡就是指 class 的名稱)。

enum class EColor
{
    RED,
    GREEN,
    BLUE
};

EColor eColor = EColor::RED;

另外,在 C++11 開始,不管是 enum 或 enum class,也都可以指定實際要使用的型別。

enum class EColor : char
{
  RED,
  GREEN,
  BLUE
};

A union is a special data type available in C that allows to store different data types in the same memory location. You can define a union with many members, but only one member can contain a value at any given time. Unions provide an efficient way of using the same memory location for multiple-purpose.

To define a union, you must use the union statement in the same way as you did while defining a structure. The union statement defines a new data type with more than one member for your program. The format of the union statement is as follows −

union [union tag] {
   member definition;
   member definition;
   ...
   member definition;
} [one or more union variables];

The union tag is optional and each member definition is a normal variable definition, such as int i; or float f; or any other valid variable definition. At the end of the union's definition, before the final semicolon, you can specify one or more union variables but it is optional. Here is the way you would define a union type named Data having three members i, f, and str −

union Data {
   int i;
   float f;
   char str[20];
} data;

Now, a variable of Data type can store an integer, a floating-point number, or a string of characters. It means a single variable, i.e., same memory location, can be used to store multiple types of data. You can use any built-in or user defined data types inside a union based on your requirement.

The memory occupied by a union will be large enough to hold the largest member of the union. For example, in the above example, Data type will occupy 20 bytes of memory space because this is the maximum space which can be occupied by a character string.


The declaration of a bit-field has the following form inside a structure −

struct {
   type [member_name] : width ;
};

The variables defined with a predefined width are called bit fields. A bit field can hold more than a single bit; for example, if you need a variable to store a value from 0 to 7, then you can define a bit field with a width of 3 bits as follows −

struct {
   unsigned int age : 3;
} Age;

The C programming language provides a keyword called typedef, which you can use to give a type a new name. Following is an example to define a term BYTE for one-byte numbers −

typedef unsigned char BYTE;

You can use typedef to give a name to your user defined data types as well. For example, you can use typedef with structure to define a new data type and then use that data type to define structure variables directly as follows −

#include <stdio.h>
#include <string.h>

typedef struct Books {
   char title[50];
   char author[50];
   char subject[100];
   int book_id;
} Book;

int main( ) {

   Book book;

   strcpy( book.title, "C Programming");
   strcpy( book.author, "Nuha Ali");
   strcpy( book.subject, "C Programming Tutorial");
   book.book_id = 6495407;

   printf( "Book title : %s\n", book.title);
   printf( "Book author : %s\n", book.author);
   printf( "Book subject : %s\n", book.subject);
   printf( "Book book_id : %d\n", book.book_id);

   return 0;
}

The C Preprocessor is not a part of the compiler, but is a separate step in the compilation process. In simple terms, a C Preprocessor is just a text substitution tool and it instructs the compiler to do required pre-processing before the actual compilation. We'll refer to the C Preprocessor as CPP.

All preprocessor commands begin with a hash symbol (#). It must be the first nonblank character, and for readability, a preprocessor directive should begin in the first column.

ANSI C defines a number of macros.

#include <stdio.h>

int main() {

   printf("File :%s\n", __FILE__ );
   printf("Date :%s\n", __DATE__ );
   printf("Time :%s\n", __TIME__ );
   printf("Line :%d\n", __LINE__ );
   printf("ANSI :%d\n", __STDC__ );

}

The token-pasting operator (##) within a macro definition combines two arguments. It permits two separate tokens in the macro definition to be joined into a single token. For example −

#include <stdio.h>

#define tokenpaster(n) printf ("token" #n " = %d", token##n)

int main(void) {
   int token34 = 40;
   tokenpaster(34);
   return 0;
}

A header file is a file with extension .h which contains C function declarations and macro definitions to be shared between several source files. There are two types of header files: the files that the programmer writes and the files that comes with your compiler.

You request to use a header file in your program by including it with the C preprocessing directive #include, like you have seen inclusion of stdio.h header file, which comes along with your compiler.

Including a header file is equal to copying the content of the header file but we do not do it because it will be error-prone and it is not a good idea to copy the content of a header file in the source files, especially if we have multiple source files in a program.

A simple practice in C or C++ programs is that we keep all the constants, macros, system wide global variables, and function prototypes in the header files and include that header file wherever it is required.

If a header file happens to be included twice, the compiler will process its contents twice and it will result in an error. The standard way to prevent this is to enclose the entire real contents of the file in a conditional, like this −

#ifndef HEADER_FILE
#define HEADER_FILE

the entire header file file

#endif

Control flow

C 語言用來判斷條件的 statement 有二個,ifswitch。 另外還有條件運算子 ? : 這個運算子。

Exp1 ? Exp2 : Exp3;

C 語言迴圈包含了 while, fordo ... while 等三種迴圈。


Write a program that displays the digits from 1 to n then back down to 1; for instance, if n = 5, the program should display 123454321. You are permitted to use only a single for loop. The range is 0 < n < 10.

首先是使用 switch 來解:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
    int n = 0;

    if (argc >= 2) {
        n = atoi(argv[1]);
    } else {
        fprintf(stderr, "Please give a number.\n");
        return 1;
    }

    if (n < 1 || n > 9) {
        fprintf(stderr, "Out of range.\n");
        return 1;
    }

    switch (n) {
    case 1:
        printf("1\n");
        break;
    case 2:
        printf("121\n");
        break;
    case 3:
        printf("12321\n");
        break;
    case 4:
        printf("1234321\n");
        break;
    case 5:
        printf("123454321\n");
        break;
    case 6:
        printf("12345654321\n");
        break;
    case 7:
        printf("1234567654321\n");
        break;
    case 8:
        printf("123456787654321\n");
        break;
    case 9:
        printf("12345678987654321\n");
        break;
    default:
        printf("Please input 0 < n < 10\n");
        break;
    }

    return 0;
}

接下來,改為使用 while 的版本:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
    int n = 0;

    if (argc >= 2) {
        n = atoi(argv[1]);
    } else {
        fprintf(stderr, "Please give a number.\n");
        return 1;
    }

    if (n < 1 || n > 9) {
        fprintf(stderr, "Out of range.\n");
        return 1;
    }

    int positive = 1;
    int count = 0;
    while (1) {
        if (positive == 1) {
            count++;
            printf("%d", count);
            if (count == n) {
                positive = 0;
                continue;
            }
        } else {
            count--;
            if (count > 0) {
                printf("%d", count);
            } else {
                break;
            }
        }
    }
    printf("\n");

    return 0;
}

C++ 的控制結構大致上與 C 相同,在 C++11 增加了以範圍為基礎的 for 陳述式。

#include <iostream>

int main() {

    for (int i : {1, 2, 3}) {
        std::cout << i << std::endl;
    }

    return 0;
}

Function

函式 (Function) 的組成主要包括四個部份:返回值、函式名稱、參數列與函式主體。前三者稱為函式宣告或函式原型(Function prototype), 在 C++ 中規定,如果函式是在 main 之後實作,必須在 main 之前進行宣告,否則會出現編譯錯誤。

如果函式不傳回任何值,則宣告為 void,若不傳入任何引數,參數列保持空白即可,雖然也可以使用 void 來加以註明, 要注意的是 void 註明參數列不使用為是 C 的風格,而在 C++ 中,參數列空白就表示這個函式不接受任何引數。

在含入標頭檔時,若標頭檔與含入標頭檔的文件在同一目錄,就使用雙引號 " " 來包括標頭檔名稱,如果是標準或專案專屬的標頭檔,例如 C++ 的標準表頭檔,那麼使用角括號 < > 來括住,編譯器在尋找時就會從設定的目錄尋找。

下面是 C 語言對函式的參數沒有固定數目時的方法。

#include <stdio.h>
#include <stdarg.h>

double average(int num,...) {

   va_list valist;
   double sum = 0.0;
   int i;

   /* initialize valist for num number of arguments */
   va_start(valist, num);

   /* access all the arguments assigned to valist */
   for (i = 0; i < num; i++) {
      sum += va_arg(valist, int);
   }

   /* clean memory reserved for valist */
   va_end(valist);

   return sum/num;
}

int main() {
   printf("Average of 2, 3, 4, 5 = %f\n", average(4, 2,3,4,5));
   printf("Average of 5, 10, 15 = %f\n", average(3, 5,10,15));
}

C 語言在 <stdlib.h> 定義了關於記憶體管理的函式,例如 malloc, realloc 與 free。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {

   char name[100];
   char *description = NULL;

   strcpy(name, "Zara Ali");

   /* allocate memory dynamically */
   description = (char *) malloc( 200 * sizeof(char) + 1);

   if( description == NULL ) {
      fprintf(stderr, "Error - unable to allocate required memory\n");
   } else {
      strcpy( description, "Zara ali a DPS student in class 10th");
   }

   printf("Name = %s\n", name );
   printf("Description: %s\n", description );

   /* Free memory */
   if(description) free(description);
}

下面是人類猜數字的小遊戲:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

int getA(char *ans, char *guess) {
  int len1 = 0;
  int len2 = 0;
  int count = 0;

  len1 = strlen(ans);
  len2 = strlen(guess);

  if (len1 != len2) {
    return 0;
  }

  for (int i = 0; i < len1; i++) {
    if (ans[i] == guess[i]) {
      count++;
    }
  }

  return count;
}

int getB(char *ans, char *guess) {
  int len1 = 0;
  int len2 = 0;
  int count = 0;

  len1 = strlen(ans);
  len2 = strlen(guess);

  if (len1 != len2) {
    return 0;
  }

  for (int i = 0; i < len1; i++) {
    for (int j = 0; j < len2; j++) {
      if (i != j) {
        if (ans[i] == guess[j]) {
          count++;
        }
      }
    }
  }

  return count;
}

int main() {
  int answer = 1;
  char ans[5];
  char guess[5];
  int avalue = 0;
  int bvalue = 0;

  srand(time(0));

  while (1) {
    answer = (int)(rand() % 9999);
    sprintf(ans, "%04d", answer);

    if (ans[0] != ans[1] && ans[0] != ans[2] && ans[0] != ans[3] &&
        ans[1] != ans[2] && ans[1] != ans[3] && ans[2] != ans[3]) {
      break;
    }
  }

  while (1) {
    printf("Please input your guess: ");
    scanf("%s", guess);
    avalue = getA(ans, guess);
    bvalue = getB(ans, guess);
    printf("Result: A = %d, B = %d\n", avalue, bvalue);

    if (avalue == 4 && bvalue == 0) {
      printf("Game is completed.\n");
      break;
    }

    printf("\n");
  }
}

下面是電腦猜數字的小遊戲:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int getA(char *ans, char *guess) {
  int len1 = 0;
  int len2 = 0;
  int count = 0;

  len1 = strlen(ans);
  len2 = strlen(guess);

  if (len1 != len2) {
    return 0;
  }

  for (int i = 0; i < len1; i++) {
    if (ans[i] == guess[i]) {
      count++;
    }
  }

  return count;
}

int getB(char *ans, char *guess) {
  int len1 = 0;
  int len2 = 0;
  int count = 0;

  len1 = strlen(ans);
  len2 = strlen(guess);

  if (len1 != len2) {
    return 0;
  }

  for (int i = 0; i < len1; i++) {
    for (int j = 0; j < len2; j++) {
      if (i != j) {
        if (ans[i] == guess[j]) {
          count++;
        }
      }
    }
  }

  return count;
}

int main() {
  int count = 0;
  int index = 0;
  char **total;
  char **total_new;
  char **newtotal;
  char ans[5];
  int avalue = 0;
  int bvalue = 0;

  total = (char **)malloc(sizeof(char *) * 5040);
  if (!total) {
    printf("Malloc failed.\n");
    return 0;
  }

  for (int i = 0; i <= 9; i++) {
    for (int j = 0; j <= 9; j++) {
      for (int k = 0; k <= 9; k++) {
        for (int m = 0; m <= 9; m++) {
          if (i != j && i != k && i != m && j != k && j != m && k != m) {
            char buffer[5] = {0};
            sprintf(buffer, "%d%d%d%d", i, j, k, m);
            total[count] = (char *)malloc(sizeof(char) * 5);
            strcpy(total[count], buffer);
            count++;
          }
        }
      }
    }
  }

  while (1) {
    if (count == 0) {
      printf("Something is wrong.\n");
      break;
    }

    strcpy(ans, total[0]);
    printf("My answer is %s.\n", ans);
    printf("The a value is: ");
    scanf("%d", &avalue);
    printf("The b value is: ");
    scanf("%d", &bvalue);

    if (avalue == 4 && bvalue == 0) {
      printf("Game is completed.\n");
      break;
    }

    index = 0;
    newtotal = (char **)malloc(sizeof(char *) * count);
    for (int i = 0; i < count; i++) {
      int aguess = 0;
      int bguess = 0;
      aguess = getA(total[i], ans);
      bguess = getB(total[i], ans);

      if (aguess == avalue && bguess == bvalue) {
        newtotal[index] = (char *)malloc(sizeof(char) * 5);
        strcpy(newtotal[index], total[i]);
        index++;
      }
    }

    total_new = (char **)realloc(total, sizeof(char *) * index);
    if (!total_new) {
        printf("Realloc failed.\n");
        break;
    }
    total = total_new;

    for (int i = 0; i < index; i++) {
      strcpy(total[i], newtotal[i]);
    }

    for (int i = 0; i < index; i++) {
      if (newtotal[i]) {
        free(newtotal[i]);
      }
    }
    if (newtotal) {
      free(newtotal);
    }

    count = index;
    printf("\n");
  }

  for (int i = 0; i < count; i++) {
    if (total[i])
      free(total[i]);
  }

  if (total) {
    free(total);
  }
}

realloc 的行為要特別注意,如果失敗會傳回 NULL,但是原本配置的記憶體並不會釋放!所以需要特別處理。

C++ Class

變數(Variable)提供一個有名稱的記憶體儲存空間,一個變數關係至一個資料型態,一個變數本身的值與一個變數的位址值。 變數資料型態決定了變數所分配到的記憶體大小;變數本身的值是指儲存於記憶體中的某個數值,而您可以透過變數名稱取得這個數值, 這個數值又稱為 rvalue或 read value;而變數的位址值則是指變數所分配到的記憶體之位置,變數本身又稱為lvalue或 location value。

由於 C++ 的演化來自 C,在 C++ 中的術語物件和 C 語言一樣是意味著記憶體區域,而不是類別實體。在 class 中,有兩大成員,一是資料(data member),一是行為(member function)。

C++ 使用 new 和 delete 來建立和刪除物件,
string *stringPtr1 = new string;
delete stringPtr1;
下面是另外一個形式:
string *stringPtr1 = new string[100];
delete [] stringPtr1;
C++ 的 new 運算子和 C 的 malloc 函式都是為了要配置記憶體,但是 new 不但配置物件所需的記憶體空間,同時會引發建構式的執行。

雖然 new 運算子看起來是單一運算,但是包含了二個動作:
  1. 透過適當的 new 運算子函式實體,配置所需的記憶體(new 運算子配置所需的記憶體實作上幾乎都是以標準的 C malloc() 來完成,正如 delete 運算子是以 C free() 來完成)
  2. 將所配置的物件設立初值

Brace initialization

It is not always necessary to define a constructor for a class, especially ones that are relatively simple. Users can initialize objects of a class or struct by using uniform initialization, as shown in the following example:

// no_constructor.cpp
// Compile with: cl /EHsc no_constructor.cpp
#include <time.h>

// No constructor
struct TempData
{
    int StationId;
    time_t timeSet;
    double current;
    double maxTemp;
    double minTemp;
};

// Has a constructor
struct TempData2
{
    TempData2(double minimum, double maximum, double cur, int id, time_t t) :
       stationId{id}, timeSet{t}, current{cur}, maxTemp{maximum}, minTemp{minimum} {}
    int stationId;
    time_t timeSet;
    double current;
    double maxTemp;
    double minTemp;
};

int main()
{
    time_t time_to_set;

    // Member initialization (in order of declaration):
    TempData td{ 45978, time(&time_to_set), 28.9, 37.0, 16.7 };

    // Default initialization = {0,0,0,0,0}
    TempData td_default{};

    // Uninitialized = if used, emits warning C4700 uninitialized local variable
    TempData td_noInit;

    // Member declaration (in order of ctor parameters)
    TempData2 td2{ 16.7, 37.0, 28.9, 45978, time(&time_to_set) };

    return 0;
}

Note that when a class or struct has no constructor, you provide the list elements in the order that the members are declared in the class. If the class has a constructor, provide the elements in the order of the parameters. If a type has a default constructor, either implicitly or explicitly declared, you can use default brace initialization (with empty braces).

For example, the following class may be initialized by using both default and non-default brace initialization:

#include <string>
using namespace std;

class class_a {
public:
    class_a() {}
    class_a(string str) : m_string{ str } {}
    class_a(string str, double dbl) : m_string{ str }, m_double{ dbl } {}
double m_double;
string m_string;
};

int main()
{
    class_a c1{};
    class_a c1_1;

    class_a c2{ "ww" };
    class_a c2_1("xx");

    // order of parameters is the same as the constructor
    class_a c3{ "yy", 4.4 };
    class_a c3_1("zz", 5.5);
}

If a class has non-default constructors, the order in which class members appear in the brace initializer is the order in which the corresponding parameters appear in the constructor, not the order in which the members are declared (as with class_a in the previous example).

靜態變數與函式

static 成員變數不屬於物件的一部份,而是類別的一部份,所以可以在沒有建立任何物件的情況下就處理 static 成員變數。而且即使 static 成員變數的權限為 private,設定 static 成員變數初值時,不受任何存取權限的束縛。

static 成員函式和 static 成員變數一樣,可以在沒有建立任何物件的情況下就被呼叫執行。 而之所以可以在未建立任何物件的情況下就被呼叫執行, 是因為編譯器不會為它暗自加上一個 this 指標, 也因為缺少 this 指標,static 成員函式無法處理類別之中的 non-static 成員變數。

C++ Object Model

Stroustrup 所設計的 Model:

  • Nonstatic data members 被配置在每一個 class object 之內
  • static data members 被配置在個別的 class object 之外
  • function members 被配置在個別的 class object 之外
  • 每一個 class 產生出指向 virtual functions 的指標,放在表格中(virtual table)。 每一個 class object 都被安插一個指標,指向相關的virtual table。
  • 為了支援 RTTI,會在 virtual table 的第一個 slot 插入型別資訊

多型的實現方式很複雜,大致上是編譯器或 VM 在資料結構內加入一個資料指標,此指標通常稱爲 vptr,是 Virtual Table Pointer 的意思。vptr 指向一個 Virtual Table,此 Virtual Table 是一個陣列 (array),由許多函數指標所組成,每個函數指標各自指向一個函數的地址。不管是 C++ 編譯器、或是 Java VM、或是 .NET CLR,內部都是以此方式來實現多型。

C++ Object Model 提供了有效率的執行時期支援,再加上與 C 之間的相容性,造成了 C++ 的流行。但是因為 Object Layout (物件大小、每一個非虛擬的 member)在編譯時間就已經固定下來,在 binary level 上阻礙了使用的彈性。

C++:封裝(Encapsulation)

資訊隱藏的意義在於將物件的實際內容除非有必要,否則最好將實際內容隱藏在介面裡。

C++ 的存取等級分為 public, protected, private 三種,把資料宣告為 private,只能透過特定的介面來操作,這就是物件導向的封裝(encapsulation)特性。

C++:繼承 (Inherit)

繼承:讓使用者藉由在已經有的類別上,加入新成員(資料或者是函式來定義新的類別,而不必重新設計。

C++ 的繼承分為三種情況:

  • Public
  • Protected
  • Private

成員函式有一個隱藏參數,名為 this 指標(代表著物件自己),因此可以知道所應喚起的函式。

C++ 多型支援:虛擬函式

對物件導向的程式語言來說,在繼承關係發生的同時,子類別可能會去覆寫父類別的函式。因此在所謂「多型」的機制下, 就無法在編譯時期決定要呼叫的究竟是那個函式,也就是到了執行時期才決定要呼叫的究竟是那一個函式(所引申的意義,就是多型的前提是繼承, 只有繼承才有多型行為的產生)。

物件導向的三個特色:封裝、繼承、多型,其中最重要的多型,C++ 是靠繼承和動態繫結(Dynamic binding)達成, 「虛擬函式」可以實現「執行時期」的多型支援,是一個「動態繫結」, 也就是指必須在執行時期才會得知所要調用的物件或其上的公開介面,以相同的指令卻喚起了不同的函式,也就是「一種介面,多種用途」。

如果 C++ 沒有虛擬函式,如果以一個「基礎類別之指標」指向一個「衍生類別之物件」, 那麼使用此指標就只能夠呼叫基礎類別(而不是衍生類別)所定義的函式。C++ 透過指標或參考來支援多型,以 Virtual function 來達到多型的實現, Virtual function 在基底類別中使用關鍵字 virtual 宣告,並在衍生類別中重新定義虛擬函式。 多型與動態繫結只有在使用指標或參考時才得以發揮它們的特性。

虛擬函式可以實現執行時期的「多型」,一個含有虛擬函式的類別被稱為「多型的類別」(Polymorphic class), 當一個基底類別型態的指標指向一個含有虛擬函式的衍生類別,您就可以使用這個指標來存取衍生類別中的虛擬函式。

class Window // Base class for C++ virtual function example
{
  public:
  virtual void Create() // virtual function for C++ virtual function example
  {
    cout <<"Base class Window"<<endl;
  }
};

class CommandButton : public Window
{
  public:
  void Create()
  {
    cout<<"Derived class Command Button - Overridden C++ virtual function"<<endl;
  }
};

void main()
{
  Window  *x, *y;

  x = new Window();
  x->Create();

  y = new CommandButton();
  y->Create();
}

如果沒有宣告為 virtual 的話,CommandButton 所呼叫的 Create() 仍然為 base class 所定義的 Create(),但是宣告為 virtual function 之後,將會視所使用的指標型別而決定要喚起的函式,因此將可以增加使用上的彈性。

純虛擬函式 (Pure Virtual Function)

C++ 提供「純虛擬函式」(Pure virtualfunction),它的存在只是為了在衍生類別中被重新定義,指明某個函式只是提供一個介面, 要求繼承的子類別必須重新定義該函式。

class Work {
  public:
    // pure virtual function
    virtual void doJob() = 0;
};

一個類別中如果含有純虛擬函式,則該類別為一「抽象類別」(Abstract class), 該類別只能被繼承,而不能用來直接生成實例,如果直接產生實例會發生編譯錯誤。

final specifier and override specifier

C++11 標準提供了對虛擬函式更好的表達方式。

final specifier specifies that a virtual function cannot be overridden in a derived class or that a class cannot be inherited from. 如果設定為 final,就表示這就是最後一個覆寫的 virtual function。

struct Base
{
    virtual void foo();
};

struct A : Base
{
    void foo() final; // A::foo is overridden and it is the final override
    void bar() final; // Error: non-virtual function cannot be overridden or be final
};

struct B final : A // struct B is final
{
    void foo() override; // Error: foo cannot be overridden as it's final in A
};

struct C : B // Error: B is final
{
};

override specifier specifies that a virtual function overrides another virtual function. 也就是用來指定是否可以覆寫 virtual function。

struct A
{
    virtual void foo();
    void bar();
};

struct B : A
{
    void foo() const override; // Error: B::foo does not override A::foo
                               // (signature mismatch)
    void foo() override; // OK: B::foo overrides A::foo
    void bar() override; // Error: A::bar is not virtual
};

對於 C++ 而言,因為其設計哲學是「任何特性在不使用的時候,絕對不增加程式的負擔」,所以在不使用虛擬特性的時候,就不會增加呼叫時的負擔。與 Java 這些內建使用動態繫結的語言不同,C++ 比較偏向使用靜態繫結,只有在使用 Virtual 關鍵字時才具有動態繫結的能力,因此與其說 C++ 是一個物件導向語言,不如說是支援物件導向的 Template based 語言。

Templates

Templates are parametrized by one or more template parameters, of three kinds: type template parameters, non-type template parameters, and template template parameters.

C++ 所提供的 template 機制,就是將目標物的資料型別參數化

下面是一個 template 的例子:

template <class T>
inline const T& max(const T& a, const T& b)
{
  return a < b ? b : a;
}

一旦程式以指定引數的方式,確定了型別之後,編譯器便自動針對這個(或這些)型別產生出一份實體。 針對目標物之不同,C++ 支援 function templates 和 class templates 兩大類型, 而後者的 members 又可以是 templates(所謂 member templates),帶來極大的彈性與組合空間。

「由編譯器產生出一份實體」的動作,我們稱之為具現化(instantiation)。由於  Template 具現化的行為是在編譯時期完成,所以愈複雜的 templates,就會需要愈多的編譯時間。然而 template 卻不會影響到執行時間,因此對於程式的執行效率仍然得以保障。

C++ 泛型編程的中心思考是 Template,運算子多載也為 C++ 泛型編程帶來了幫助, 讓我們得以用看起來行為像函式的 Function Object (函式物件, or Functor)寫出更有彈性的程式。 Standard Template Library 是 C++ 泛型編程開花結果之後所帶來的成品,讓我們得以享用這些高編程品質的函式庫。

Templates 是 C++ 支援泛型的重要關鍵。

template<typename To, typename From> To convert(From f);

void g(double d)
{
    int i = convert<int>(d); // calls convert<int,double>(double)
    char c = convert<char>(d); // calls convert<char,double>(double)
    int(*ptr)(float) = convert; // instantiates convert<int, float>(float)
}

關鍵字:typename

C++ 引進了 typename 關鍵字,用來指定 template 內的標識符號為一種型別。 C++ 的規則是,除非用 typename 修飾,template 內的標識符號都會被視為一個實值 (value) 而不是型別。

Parameter pack (since C++11)

A template parameter pack is a template parameter that accepts zero or more template arguments (non-types, types, or templates). A function parameter pack is a function parameter that accepts zero or more function arguments.

A template with at least one parameter pack is called a variadic template.

template<class ... Types> struct Tuple {};
Tuple<> t0;           // Types contains no arguments
Tuple<int> t1;        // Types contains one argument: int
Tuple<int, float> t2; // Types contains two arguments: int and float
Tuple<0> error;       // error: 0 is not a type

A variadic function template can be called with any number of function arguments (the template arguments are deduced through template argument deduction):

template<class ... Types> void f(Types ... args);
f();       // OK: args contains no arguments
f(1);      // OK: args contains one argument: int
f(2, 1.0); // OK: args contains two arguments: int and double

A pattern followed by an ellipsis, in which the name of at least one parameter pack appears at least once, is expanded into zero or more comma-separated instantiations of the pattern, where the name of the parameter pack is replaced by each of the elements from the pack, in order.

template<class ...Us> void f(Us... pargs) {}
template<class ...Ts> void g(Ts... args) {
    f(&args...); // “&args...” is a pack expansion
                 // “&args” is its pattern
}
g(1, 0.2, "a"); // Ts... args expand to int E1, double E2, const char* E3
                // &args... expands to &E1, &E2, &E3
                // Us... pargs expand to int* E1, double* E2, const char** E3

Type casting

Type casting is a way to convert a variable from one data type to another data type. For example, if you want to store a 'long' value into a simple integer then you can type cast 'long' to 'int'. You can convert the values from one type to another explicitly using the cast operator as follows −

(type_name) expression

Consider the following example where the cast operator causes the division of one integer variable by another to be performed as a floating-point operation −

#include <stdio.h>

main() {

   int sum = 17, count = 5;
   double mean;

   mean = (double) sum / count;
   printf("Value of mean : %f\n", mean );
}

Type conversions can be implicit which is performed by the compiler automatically, or it can be specified explicitly through the use of the cast operator. It is considered good programming practice to use the cast operator whenever type conversions are necessary.

因為更重視型別安全的關係,除了上面 C 語言風格的型別轉換, C++ 加入了 static_castdynamic_castconst_castreinterpret_cast 四種 cast。

static_cast 執行於編譯時期,功能與 C-Style cast 相似,但更安全,可以避免不合理的型別轉換。

float f = 3.5;
int n1 = static_cast<int>(f);

為了支援執行時期的型態轉換動作,C++ 提供了dynamic_cast 用來將一個基底類別的指標轉型至衍生類別指標, 稱之為「安全向下轉型」(Safe downcasting),它在執行時期進行型態轉換動作,首先會確定轉換目標與來源是否屬同一個類別階層, 接著才真正進行轉換的動作,檢驗動作在執行時期完成,如果是一個指標,則轉換成功時傳回位址,失敗的話會傳回 0, 如果是參考的話,轉換失敗會丟出 bad_cast例外。

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

class Base { 
public: 
    virtual void foo() = 0;
}; 

class Derived1 : public Base { 
public: 
    void foo() { 
        cout << "Derived1" << endl; 
    } 
 
    void showOne() {
        cout << "Yes! It's Derived1." << endl;
    }
}; 

class Derived2 : public Base { 
public: 
    void foo() { 
        cout << "Derived2" << endl; 
    } 
 
    void showTwo() {
        cout << "Yes! It's Derived2." << endl;
    }
}; 

void showWho(Base &base) {
    try {
        Derived1 derived1 = dynamic_cast<Derived1&>(base);
        derived1.showOne();
    }
    catch(bad_cast) {
        cout << "bad_cast 轉型失敗" << endl;
    }
}

int main() { 
    Derived1 derived1;
    Derived2 derived2; 

    showWho(derived1);
    showWho(derived2);
 
    return 0;
}

const_cast 的用途是移除 const 的屬性。除非真的有需要,否則不應該使用。

const int a = 10;
const int *ptr = &a;
int *cc = const_cast<int *>(ptr);
*cc = 99;

reinterpret_cast 用途是強制轉換型別,不論資料大小是否相同。

int number = 10;
// Store the address of number in numberPointer
int* numberPointer = &number;

// Reinterpreting the pointer as a char pointer
char* charPointer
    = reinterpret_cast<char*>(numberPointer);

Exception

如果要在 C++ 中實作例外狀況 (exception) 處理,使用 trythrowcatch 運算式。 開發者需要使用 try 包住可能會發生例外的程式碼區段(也就是使用 throw 丟出例外狀況的程式碼), 在 catch 處理這個 try 區域所發生的例外,並且 catch 裡面的 exception 變數應該要用 reference 的方式。 另外,如果在 catch 拿到例外狀況無法即時處理而需要丟給更上層的 try ... catch 處理,可以使用 throw 重新丟出一個例外狀況。

下面是一個例外狀況處理的例子。

#include <iostream>
#include <vector>
#include <exception>

int main()
{
    std::vector<int> v = {1,2,3};
    try {
        std::cout << v.at(0) << std::endl;
        std::cout << v.at(1) << std::endl;
        std::cout << v.at(2) << std::endl;
        std::cout << v.at(3) << std::endl;
    } catch (std::exception &e) {
        std::cout << "exception: " << e.what() << std::endl;
    }

    return 0;
}

C++ 並不支援 finally,因為 C++ 可以使用 RAII(Resource Acquisition Is Initialization), 也就是物件銷毀的時候也關閉或移除其使用的資源。

Namespace

C++ 可以使用 namespace 來定義名稱空間(或者開啟既存的名稱空間),例如,可以在 account.h 中定義 bank 名稱空間:

#include <string>

namespace bank {
    using namespace std; 

    class Account { 
    private:
        string id;  
        string name; 
        double balance;

    public: 
        Account(string id, string name, double balance);
        void deposit(double amount);
        void withdraw(double amount);
        string to_string() const;
    };
}

在使用上,可以在 account.cpp 可以開啟 bank 名稱空間,並在其中實作類別定義:

#include "account.h"

namespace bank {
    using namespace std;

    Account::Account(string id, string name, double balance) {
        this->id = id;
        this->name = name;
        this->balance = balance;
    }

    string Account::to_string() const {
        return string("Account(") + 
            this->id + ", " +
            this->name + ", " +
            std::to_string(this->balance) + ")";
    }
    
    // ...
}

或者是在實作時指定 bank 範疇:

bank::Account::Account(string id, string name, double balance) {
    this->id = id;
    this->name = name;
    this->balance = balance;
}

string bank::Account::to_string() const {
    return string("Account(") + 
           this->id + ", " +
           this->name + ", " +
           std::to_string(this->balance) + ")";
}

名稱空間會是類別名稱的一部份,因此在使用時,必須包含 bank 前置; 或者是使用 using 來指明使用哪個名稱空間,例如:

using namespace std;
using namespace bank;

using 也可用來導入某個名稱,例如僅導入 std::string、std:cout:

#include <iostream>
#include <string>
using std::string;

int main() {
    string str = "Example";
    using std::cout;
    cout << str;
}

C++ 其實並不建議使用 using,因為這樣如果程式碼如果大到一定規模,可能會出現命名衝突的問題。

C++ 11 Lambda Expression

Lambda expression 在 C++ 中可以視為是一種匿名函數的表示方式,它可以讓程式設計師將函數的內容直接以 inline 的方式寫在一般的程式碼之中, 使用時機跟 functor 與 function pointer 類似,一般的狀況都是使用 lambda expression 定義一個匿名的函數, 然後再將此函數當作另外一個函數的傳入參數來使用。

基本的用法如下:
[=] (int x) mutable throw() -> int
{
  // 函數內容
  int n = x + y;
  return n;
}
[=]:lambda-introducer,也稱為 capture clause。
所有的 lambda expression 都是以它來作為開頭,不可以省略,它除了用來作為 lambda expression 開頭的關鍵字之外,也有抓取(capture)變數的功能,指定該如何將目前 scope 範圍之變數抓取至 lambda expression 中使用,而抓取變數的方式則分為傳值(by value)與傳參考(by reference)兩種,跟一般函數參數的傳入方式類似,不過其語法有些不同,以下我們以範例解釋:
  • []:只有兩個中括號,完全不抓取外部的變數。
  • [=]:所有的變數都以傳值(by value)的方式抓取。
  • [&]:所有的變數都以傳參考(by reference)的方式抓取。
  • [x, &y]x 變數使用傳值、y 變數使用傳參考。
  • [=, &y]:除了 y 變數使用傳參考之外,其餘的變數皆使用傳值的方式。
  • [&, x]:除了 x 變數使用傳值之外,其餘的變數皆使用傳參考的方式。

這裡要注意一點,預設的抓取選項(capture-default,亦即 = 或是 &)要放在所有的項目之前,也就是放在第一個位置。

(int x):lambda declarator,也稱為參數清單(parameter list)。
定義此匿名函數的傳入參數列表,基本的用法跟一般函數的傳入參數列表一樣,不過多了一些限制條件:
  • 不可指定參數的預設值。
  • 不可使用可變長度的參數列表。
  • 參數列表不可以包含沒有命名的參數。

參數清單在 lambda expression 中並不是一個必要的項目,如果不需要傳入任何參數的話,可以連同小括號都一起省略。

mutable:mutable specification。
加入此關鍵字可以讓 lambda expression 直接修改以傳值方式抓取進來的外部變數,若不需要此功能,則可以將其省略。
throw():例外狀況規格(exception specification)。
指定該函數會丟出的例外,其使用的方法跟一般函數的例外指定方式相同。如果該函數沒有使用到例外的功能,則可以直接省略掉。
-> int:傳回值型別(return type)。
指定 lambda expression 傳回值的型別,這個範例是指定傳回值型別為整數(int),其他的型別則以此類推。如果 lambda expression 所定義的函數很單純,只有包含一個傳回陳述式(statement)或是根本沒有傳回值的話,這部分就可以直接省略,讓編譯器自行判斷傳回值的型別。
mutable:compound-statement,亦稱為 Lambda 主體(lambda body)。
這個就是匿名函數的內容,就跟一般的函數內容一樣。

下面是一個最簡單的 Hello World 範例。

#include <iostream>

using namespace std;
int main() {
  auto lambda = []() { cout << "Hello, Lambda" << endl; };
  lambda();
}

再來是 Trailing Zero-Bits 的解法:

/*
 * Trailing Zero-Bits
 * Given a positive integer, count the number of trailing zero-bits in its binary
 * representation. For instance, 18 = 10010, so it has 1 trailing zero-bit,
 * and 48 = 110000, so it has 4 trailing zero-bits.
 */
#include <iostream>

int main(void)
{
    int number = 0;

    auto lambda = [](int num) -> int {
        int count = 0;
        while((num & 1) == 0) {
            count++;
            num = num >> 1;
        }

        return count;
    };

    std::cout << "Please input a number: ";
    std::cin >> number;
    if (std::cin.fail()) {
        std::cout << "It is not a number." << std::endl;
        return 1;
    }

    if (number <= 0) {
        std::cout << "Number requires > 0." << std::endl;
    } else {
        std::cout << lambda(number) << std::endl;
    }
    return 0;
}

也可以在參數列加上 void,明確標示沒有傳入參數,並將傳回值的類型設為 void,明確標示這個函數沒有傳回值:

auto lambda = [](void) -> void { cout << "Hello, Lambda" << endl; };
下面的例子是直接呼叫 lambda expression 所定義的匿名函數,將兩個參數傳入其中進行運算,最後再將運算結果傳回來:
#include <iostream>

int main() {
  using namespace std;
  int n = [] (int x, int y) { return x + y; }(5, 4);
  cout << n << endl;
}

C++ 標準程式庫中有許多的函數在使用時會需要其他的函數作為傳入參數,最常見的就是一些對於陣列的處理函數, 這個例子是 std::count_if 最簡單的使用方式:

#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;

bool condition(int value) {
  return (value > 5);
}

int main() {
  vector<int> numbers { 1, 2, 3, 4, 5, 10, 15, 20, 25, 35, 45, 50 };

  auto count = count_if(numbers.begin(), numbers.end(), condition);
  cout << "Count: " << count << endl;
}

這裡我們定義一個 condition 函數,作為 std::count_if 在判斷元素時的依據,std::count_if 會將每個元素一一傳入 condition 函數中檢查, 最後傳回所有符合條件的元素個數。

由於 std::count_if 所使用到的判斷函數都需要另外定義,這樣會讓程式碼顯得很冗長, 我們可以使用 lambda expression 改寫一下,讓整個程式碼更簡潔:

#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;
int main() {
  vector<int> numbers { 1, 2, 3, 4, 5, 10, 15, 20, 25, 35, 45, 50 };

  auto count = count_if(numbers.begin(), numbers.end(),
    [](int x) { return (x > 5); });
  cout << "Count: " << count << endl;
}

我們將原本 condition 函數所在的位置,直接使用一個 lambda expression 替換,至於傳入的參數與傳回值的類型則維持不變(傳入 int,傳回 bool)。

C++ 11 Automatic Type Deduction and decltype

In C++03, you must specify the type of an object when you declare it. Yet in many cases, an object’s declaration includes an initializer. C++11 takes advantage of this, letting you declare objects without specifying their types:
auto x=0; //x has type int because 0 is int
auto c='a'; //char
auto d=0.5; //double
auto national_debt=14400000000000LL;//long long
Automatic type deduction is chiefly useful when the type of the object is verbose or when it’s automatically generated (in templates). Consider:
void func(const vector<int> &vi)
{
    vector<int>::const_iterator ci=vi.begin();
}
而在 C++11,現在可以這樣使用:
auto ci = vi.begin();
C++11 offers a similar mechanism for capturing the type of an object or an expression. The new operator decltype takes an expression and “returns” its type:
const vector<int> vi;
typedef decltype (vi.begin()) CIT;
CIT another_const_iterator;

C++11 也支援了 Range-based for loop,並且支援 auto 的使用,下面是一個九九乘法表的範例:

#include <iostream>
#include <vector>

int main()
{
    std::vector<int> x = {1, 2, 3, 4, 5, 6, 7, 8, 9};
    std::vector<int> y = {1, 2, 3, 4, 5, 6, 7, 8, 9};

    for (auto& nx: x) {
        for (auto& ny: y) {
            int z = nx * ny;
            std::cout << nx << " x " << ny << " = " << z << std::endl;
        }
    }
}

在 C++14,則可以結合 Lambda Expression 與 auto 的使用,下面是一個範例:

#include <iostream>
#include <vector>
#include <string>
#include <numeric>

int main()
{
  std::vector<int> ivec = { 1, 2, 3, 4};
  std::vector<std::string> svec = { "red",
                                    "green",
                                    "blue" };
  auto adder  = [](auto op1, auto op2){ return op1 + op2; };
  std::cout << "int result : "
            << std::accumulate(ivec.begin(),
                               ivec.end(),
                               0,
                               adder )
            << "\n";
  std::cout << "string result : "
            << std::accumulate(svec.begin(),
                               svec.end(),
                               std::string(""),
                               adder )
            << "\n";
  return 0;
}
下面就是執行的結果:
int result : 10
string result : redgreenblue

IO Stream

C++ comes with libraries that provide us with many ways for performing input and output. In C++ input and output are performed in the form of a sequence of bytes or more commonly known as streams.

  • Input Stream: If the direction of flow of bytes is from the device(for example, Keyboard) to the main memory then this process is called input.
  • Output Stream: If the direction of flow of bytes is opposite, i.e. from main memory to device( display screen ) then this process is called output.

The stream-based input/output library is organized around abstract input/output devices. These abstract devices allow the same code to handle input/output to files, memory streams, or custom adaptor devices that perform arbitrary operations (e.g. compression) on the fly.

Most of the classes are templated, so they can be adapted to any basic character type. Separate typedefs are provided for the most common basic character types (char and wchar_t).

C++ 提供了 stream 的方式來看待輸出與輸入。下面的範例是讓使用者輸入 A 與 B 以後,輸出相加的數字:

#include <iostream>

int main()
{
    int a = 0, b = 0;

    std::cout << "Please input the name a: ";
    std::cin >> a;

    std::cout << "Please input the name b: ";
    std::cin >> b;

    std::cout << "The sum is " << a+b << "." << std::endl;
}

下面是從 /etc/os-release 讀取內容,然後取得 Linux Distribution Name 的範例:

#include <iostream>
#include <fstream>
#include <string>
#include <vector>

#ifdef _WIN32
   #include <io.h>
   #define access    _access_s
#else
   #include <unistd.h>
#endif

#ifdef ENABLE_STD
   #include <filesystem>
#endif

using namespace std;

/*
 * Using access function to check file exist or not
 */
bool FileExists( const string &Filename )
{
    return access( Filename.c_str(), 0 ) == 0;
}

/*
 * Split incoming string and return a vector
 */
vector<string> split(const string& str, const string& delim)
{
    vector<string> tokens;
    size_t prev = 0, pos = 0;
    do
    {
        pos = str.find(delim, prev);
        if (pos == string::npos) pos = str.length();
        string token = str.substr(prev, pos-prev);
        if (!token.empty()) tokens.push_back(token);
        prev = pos + delim.length();
    } while (pos < str.length() && prev < str.length());

    return tokens;
}

/*
 * get Linux distributed name: use /etc/os-release file
 */
string getListributedName()
{
    string name = "";

#ifdef ENABLE_STD
    std::filesystem::path p("/etc/os-release");
    if(std::filesystem::exists(p)) {
#else
    if(FileExists("/etc/os-release")==true) {
#endif
        ifstream releasefile("/etc/os-release");
        string line;

        if (releasefile.is_open()) {
            while ( getline (releasefile, line) )
            {
                auto splitArray = split(line, "=");

                if(splitArray[0].compare("NAME")==0) {
                   name =  splitArray[1];
                   break;
                }
            }

            releasefile.close();
       }
    }

    return name;
}


int main(int argc, char *argv[])
{
    string name = getListributedName();
    std::cout << name << std::endl;
}

C++17 加入了 Filesystem library,提供了對於 files 與 directories 的操作。

編譯:
g++ getDistributionName.cpp -std=c++17 -o getDistributionName

如果要配合 C++ standard library:
g++ getDistributionName.cpp -std=c++17 -o getDistributionName -DENABLE_STD

參考資料