2025/12/13

Object Pascal Regular Expression

Object Pascal 的自由軟體實作 Free Pascal 內建 Regular Expression 的支援,實作的部份在 RegExpr unit。

下面是使用 Free Pascal 解 1-9 位數不重複印出來的練習問題:

{$ifdef FPC} {$mode delphi} {$endif}

{$ifdef MSWINDOWS} {$apptype CONSOLE} {$endif}

program MyNumber;

uses sysutils, math, RegExpr;

var 
  num : integer;
  max : longint;
  index: longint;
  numstr: string;
  RegexObj: TRegExpr;

begin

  Write('Please give a number: ');
  ReadLn(num);

  if (num < 1) or (num > 9) then
    begin
      WriteLn('Out of range.');
      Exit;
    end;

  max := round(intpower(10.0,  num)) - 1;

  RegexObj := TRegExpr.Create;
  RegexObj.Expression := '1.*1|2.*2|3.*3|4.*4|5.*5|6.*6|7.*7|8.*8|9.*9|0.*0';

  try
    for index := 1 to max do
      begin
        numstr := IntToStr(index);
        if RegexObj.Exec(numstr) then
          continue
        else
          WriteLn(numstr);
      end;
  finally
    RegexObj.Free;
  end;
end.

參考連結

2025/11/29

Boost.JSON

Boost.JSON 是一個 C++ JSON parser 函式庫, 提供了雖然不是最快但是也足夠快的執行效率、以及雖然不是最方便但是足以滿足使用者需要的便利使用方式, 就綜合條件來說,我認為是十分優秀的 C++ JSON parser 函式庫。 他有二個使用方式,第一種需要連結函式庫:

#include <boost/json.hpp>

第二種是 header-only:

#include <boost/json/src.hpp>

下面是從一個字串分析 JSON 的測試:

#include <boost/json.hpp>
#include <iostream>
#include <string>

namespace json = boost::json;

int main() {
    const std::string json_str = R"(
        {
            "user": "johndoe",
            "id": 12345,
            "active": true,
            "numbers": [1, 2, 3, 4, 5]
        }
    )";

    // Parse the JSON string
    json::value data = json::parse(json_str);

    // Access the values
    std::string username = json::value_to<std::string>(data.at("user"));
    int user_id = json::value_to<int>(data.at("id"));
    bool is_active = json::value_to<bool>(data.at("active"));

    std::cout << "Username: " << username << std::endl;
    std::cout << "ID: " << user_id << std::endl;
    std::cout << "Active: " << (is_active ? "Yes" : "No") << std::endl;

    // For array
    json::array &arr = data.at("numbers").as_array();
    std::vector<int> numbers;
    for (auto const &value : arr) {
        numbers.push_back(json::value_to<int>(value));
    }

    std::cout << "Parsed Numbers: ";
    for (int num : numbers) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

使用 CMake 編譯,CMakeLists.txt 的內容如下:

cmake_minimum_required(VERSION 3.18)

project(parse)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED True)

find_package(Boost 1.89.0 REQUIRED CONFIG COMPONENTS json)

add_executable(parse parse.cpp)
target_link_libraries(parse PRIVATE Boost::json)

如果採用 header-only 的方式,CMakeLists.txt 的內容如下:

cmake_minimum_required(VERSION 3.18)

project(parse)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED True)

find_package(Boost 1.89.0 REQUIRED CONFIG COMPONENTS)

add_executable(parse parse.cpp)
target_link_libraries(parse)

下面是建立 JSON 內容的測試:

#include <boost/json.hpp>
#include <iostream>
#include <string>

namespace json = boost::json;

int main() {
    // Create a JSON object
    json::object obj;
    obj["user"] = "johndoe";
    obj["id"] = 12345;
    obj["active"] = true;

    // Create a JSON array
    json::array numbers;
    numbers.push_back(1);
    numbers.push_back(2);
    numbers.push_back(3);
    numbers.push_back(4);
    numbers.push_back(5);

    obj["numbers"] = numbers;

    // Serialize the object to a string
    std::string serialized_json = json::serialize(obj);

    std::cout << "Generated JSON: " << serialized_json << std::endl;

    return 0;
}

參考連結

Asio C++ Library

Asio C++ Library 是一個免費、開放原始碼、跨平台的 C++ 網路程式庫。 它為開發者提供一致的非同步 I/O 模型(包含 Timer、File、Pipe、Serial Port 以及網路協定 TCP, UDP 與 ICMP), Boost.Asio 在 20 天的審查後,於 2005 年 12 月 30 日被 Boost 函式庫接納。 目前 Asio C++ Library 提供二種函式庫,一種可以獨立使用的 Asio C++ library,一種是與 Boost 函式庫整合的 Boost.Asio, 二種函式庫的核心相同,差別在於 Boost.Asio 跟隨 Boost 函式庫的發佈時程(這表示當 bugs 修正的時候, 有時候會慢一點才會隨著 Boost 的新版更正)。因為已經有安裝 Boost 函式庫,所以我使用的是 Boost.Asio。

Asio 在設計上使用 Proactor pattern。 Proactor 是一種用於事件處理的軟體設計模式,其中耗時較長的活動在非同步部分運行(在 Asio 就是 I/O 處理的部份)。 非同步部分終止後,會呼叫完成處理程序。 所有使用 asio 的程式都需要至少一個 I/O execution context,例如 io_context 或 thread_pool 物件。 I/O execution context 提供對 I/O 功能的存取。如果是非同步的操作,那麼需要實作 completion handler 來提供工作完成之後的通知目標。

下面是一個測試的程式,來自 Asio 教學網頁的 Using a timer synchronously。 boost::asio::io_context 就是執行 I/O 的部份。

#include <boost/asio.hpp>
#include <iostream>

int main() {
    boost::asio::io_context io;

    boost::asio::steady_timer t(io, boost::asio::chrono::seconds(3));
    t.wait();

    std::cout << "Hello, world!" << std::endl;

    return 0;
}

使用 CMake 編譯,CMakeLists.txt 的內容如下:

cmake_minimum_required(VERSION 3.18)

project(timer)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED True)

find_package(Boost 1.89.0 REQUIRED CONFIG COMPONENTS)

add_executable(timer timer.cpp)

Using a timer asynchronously

使用 asio 的非同步功能意味著需要一個 completion token,該 token 決定了非同步操作完成後如何將結果傳遞給完成處理程序。 在這裡使用 print 函數,該函數將在非同步等待結束後被呼叫。

務必記住,在呼叫 boost::asio::io_context::run() 之前,要先給 io_context 一些工作。 如果沒指定一些工作(在本例中是 steady_timer::async_wait()),boost::asio::io_context::run() 會立即返回。

#include <boost/asio.hpp>
#include <iostream>

void print(const boost::system::error_code & /*e*/) {
    std::cout << "Hello, world!" << std::endl;
}

int main() {
    boost::asio::io_context io;

    boost::asio::steady_timer t(io, boost::asio::chrono::seconds(3));
    t.async_wait(&print);

    io.run();

    return 0;
}

Binding arguments to a completion handler

要使用 asio 實作重複定時器,需要在完成處理程序中更改定時器的過期時間,然後啟動新的非同步等待。 這意味著 completion handler 需要能夠存取定時器物件。

#include <boost/asio.hpp>
#include <functional>
#include <iostream>

void print(const boost::system::error_code & /*e*/,
           boost::asio::steady_timer *t, int *count) {
    if (*count < 5) {
        std::cout << *count << std::endl;
        ++(*count);

        t->expires_at(t->expiry() + boost::asio::chrono::seconds(1));
        t->async_wait(
            std::bind(print, boost::asio::placeholders::error, t, count));
    }
}

int main() {
    boost::asio::io_context io;

    int count = 0;
    boost::asio::steady_timer t(io, boost::asio::chrono::seconds(1));
    t.async_wait(
        std::bind(print, boost::asio::placeholders::error, &t, &count));

    io.run();

    std::cout << "Final count is " << count << std::endl;

    return 0;
}

Using a member function as a completion handler

std::bind 函式對類別成員函式和函式同樣有效。由於所有非靜態類別成員函數都有一個隱式的 this 參數,我們需要將 this 綁定到函數上。 std::bind 將我們的 completion handler(現在是成員函數)轉換為函數對象。

#include <boost/asio.hpp>
#include <functional>
#include <iostream>

class printer {
public:
    printer(boost::asio::io_context &io)
        : timer_(io, boost::asio::chrono::seconds(1)), count_(0) {
        timer_.async_wait(std::bind(&printer::print, this));
    }

    ~printer() { std::cout << "Final count is " << count_ << std::endl; }

    void print() {
        if (count_ < 5) {
            std::cout << count_ << std::endl;
            ++count_;

            timer_.expires_at(timer_.expiry() +
                              boost::asio::chrono::seconds(1));
            timer_.async_wait(std::bind(&printer::print, this));
        }
    }

private:
    boost::asio::steady_timer timer_;
    int count_;
};

int main() {
    boost::asio::io_context io;
    printer p(io);
    io.run();

    return 0;
}

Synchronising completion handlers in multithreaded programs

strand class template 是 executor adapter,它保證透過它分發的處理程序,在下一個處理程序啟動之前, 目前正在執行的處理程序必須完成。無論呼叫 boost::asio::io_context::run() 的執行緒數是多少,此保證都有效。 當然,這些處理程序仍然可能與其他未透過 strand 分發的處理程序,或透過不同 strand 物件分發的處理程序並發執行。


#include <boost/asio.hpp>
#include <functional>
#include <iostream>
#include <thread>

class printer {
public:
    printer(boost::asio::io_context &io)
        : strand_(boost::asio::make_strand(io)),
          timer1_(io, boost::asio::chrono::seconds(1)),
          timer2_(io, boost::asio::chrono::seconds(1)), count_(0) {
        timer1_.async_wait(boost::asio::bind_executor(
            strand_, std::bind(&printer::print1, this)));

        timer2_.async_wait(boost::asio::bind_executor(
            strand_, std::bind(&printer::print2, this)));
    }

    ~printer() { std::cout << "Final count is " << count_ << std::endl; }

    void print1() {
        if (count_ < 10) {
            std::cout << "Timer 1: " << count_ << std::endl;
            ++count_;

            timer1_.expires_at(timer1_.expiry() +
                               boost::asio::chrono::seconds(1));

            timer1_.async_wait(boost::asio::bind_executor(
                strand_, std::bind(&printer::print1, this)));
        }
    }

    void print2() {
        if (count_ < 10) {
            std::cout << "Timer 2: " << count_ << std::endl;
            ++count_;

            timer2_.expires_at(timer2_.expiry() +
                               boost::asio::chrono::seconds(1));

            timer2_.async_wait(boost::asio::bind_executor(
                strand_, std::bind(&printer::print2, this)));
        }
    }

private:
    boost::asio::strand<boost::asio::io_context::executor_type> strand_;
    boost::asio::steady_timer timer1_;
    boost::asio::steady_timer timer2_;
    int count_;
};

int main() {
    boost::asio::io_context io;
    printer p(io);
    std::thread t([&] { io.run(); });
    io.run();
    t.join();

    return 0;
}

File

Linux io_uring 在 Kernel 5.1 加入,其主要目標是透過高效率的非同步 I/O 框架,解決傳統 I/O 模型中系統呼叫和上下文切換的效能瓶頸, 移除傳統同步I/O 與 epoll 就緒通知模型需要頻繁切換使用者空間與核心空間的負擔,進而大幅提升系統在處理大量並發 I/O 操作時的效能。 liburing 是 Jens Axboe 維護的輔助函式庫,其主要目的是簡化 io_uring 的使用。 Asio 對於 Linux liburing 提供了包裝(目前需要使用者使用 flag 啟用),下面是我測試的程式, 讀取 /etc/os-release 取得 Linux Distribution Name:

#include <boost/asio.hpp>
#include <boost/asio/stream_file.hpp>
#include <filesystem>
#include <iostream>
#include <vector>

namespace asio = boost::asio;
namespace fs = std::filesystem;

std::vector<std::string> split(const std::string &str,
                               const std::string &delim) {
    std::vector<std::string> tokens;
    size_t prev = 0, pos = 0;
    do {
        pos = str.find(delim, prev);
        if (pos == std::string::npos)
            pos = str.length();
        std::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;
}

void read_next_line(asio::stream_file &file, asio::streambuf &buffer) {
    asio::async_read_until(file, buffer, '\n',
                           [&](const boost::system::error_code &ec,
                               std::size_t bytes_transferred) {
                               if (!ec) {
                                   std::istream is(&buffer);
                                   std::string line;
                                   std::getline(is, line);

                                   auto splitArray = split(line, "=");
                                   if (splitArray[0].compare("NAME") == 0) {
                                       std::cout << splitArray[1] << std::endl;
                                   } else {
                                       read_next_line(file, buffer);
                                   }
                               } else if (ec == asio::error::eof) {
                                   std::cout << "End of file reached."
                                             << std::endl;
                               } else {
                                   std::cerr
                                       << "Error reading file: " << ec.message()
                                       << std::endl;
                               }
                           });
}

int main() {
    fs::path test_file_path = "/etc/os-release";

    asio::io_context io_context;

    boost::system::error_code ec_open;
    asio::stream_file file(io_context);
    file.open(test_file_path.string(), asio::stream_file::read_only, ec_open);

    if (ec_open) {
        std::cerr << "Failed to open file: " << ec_open.message() << std::endl;
        return 1;
    }

    asio::streambuf buffer;
    read_next_line(file, buffer);

    io_context.run();
    file.close();

    return 0;
}

使用 CMake 編譯,CMakeLists.txt 的內容如下:

cmake_minimum_required(VERSION 3.18)

project(name)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED True)

find_package(PkgConfig REQUIRED)
pkg_check_modules(uring REQUIRED IMPORTED_TARGET liburing)

find_package(Boost 1.89.0 REQUIRED CONFIG COMPONENTS)

add_executable(name name.cpp)
target_link_libraries(name PRIVATE PkgConfig::uring)
target_compile_definitions(name PRIVATE BOOST_ASIO_HAS_IO_URING BOOST_ASIO_DISABLE_EPOLL)

Tcp

A synchronous TCP daytime client

我們需要將作為參數傳遞給應用程式的伺服器名稱轉換為 TCP 端點。為此,我們使用 ip::tcp::resolver 物件。 resolver 接收主機名稱和服務名,並將它們轉換為端點列表。 程式接下來建立並連接 Socket。上面獲得的端點列表可能同時包含 IPv4 和 IPv6 端點,因此我們需要逐一嘗試,直到找到可用的端點。 這樣可以確保客戶端程式與特定的 IP 版本無關。boost::asio::connect() 函數會自動執行此操作。

#include <array>
#include <boost/asio.hpp>
#include <iostream>

namespace asio = boost::asio;

int main(int argc, char *argv[]) {
    try {
        if (argc != 2) {
            std::cerr << "Usage: client <host>" << std::endl;
            return 1;
        }

        asio::io_context io_context;

        asio::ip::tcp::resolver resolver(io_context);
        asio::ip::tcp::resolver::results_type endpoints =
           resolver.resolve(argv[1], "daytime");

        asio::ip::tcp::socket socket(io_context);
        asio::connect(socket, endpoints);

        for (;;) {
            std::array<char, 128> buf;
            boost::system::error_code error;

            size_t len = socket.read_some(asio::buffer(buf), error);

            if (error == asio::error::eof)
                break; // Connection closed cleanly by peer.
            else if (error)
                throw boost::system::system_error(error); // Some other error.

            std::cout.write(buf.data(), len);
        }
    } catch (std::exception &e) {
        std::cerr << e.what() << std::endl;
    }

    return 0;
}

A synchronous TCP daytime server

需要建立一個 ip::tcp::acceptor 物件來監聽新連線。它被初始化為監聽 TCP 連接埠 13,支援 IP 版本 6。

#include <boost/asio.hpp>
#include <ctime>
#include <iostream>
#include <string>

namespace asio = boost::asio;

std::string make_daytime_string() {
    std::time_t now = std::time(0);
    return std::ctime(&now);
}

int main() {
    try {
        asio::io_context io_context;

        asio::ip::tcp::acceptor acceptor(
            io_context, asio::ip::tcp::endpoint(asio::ip::tcp::v6(), 13));

        for (;;) {
            asio::ip::tcp::socket socket(io_context);
            acceptor.accept(socket);

            std::string message = make_daytime_string();

            boost::system::error_code ignored_error;
            asio::write(socket, boost::asio::buffer(message), ignored_error);
        }
    } catch (std::exception &e) {
        std::cerr << e.what() << std::endl;
    }

    return 0;
}

An asynchronous TCP daytime server

#include <boost/asio.hpp>
#include <ctime>
#include <functional>
#include <iostream>
#include <memory>
#include <string>

namespace asio = boost::asio;

std::string make_daytime_string() {
    std::time_t now = std::time(0);
    return std::ctime(&now);
}

class tcp_connection : public std::enable_shared_from_this<tcp_connection> {
public:
    typedef std::shared_ptr<tcp_connection> pointer;

    static pointer create(asio::io_context &io_context) {
        return pointer(new tcp_connection(io_context));
    }

    asio::ip::tcp::socket &socket() { return socket_; }

    void start() {
        message_ = make_daytime_string();

        asio::async_write(socket_, asio::buffer(message_),
                          std::bind(&tcp_connection::handle_write,
                                    shared_from_this(),
                                    asio::placeholders::error,
                                    asio::placeholders::bytes_transferred));
    }

private:
    tcp_connection(asio::io_context &io_context) : socket_(io_context) {}

    void handle_write(const boost::system::error_code & /*error*/,
                      size_t /*bytes_transferred*/) {}

    asio::ip::tcp::socket socket_;
    std::string message_;
};

class tcp_server {
public:
    tcp_server(asio::io_context &io_context)
        : io_context_(io_context),
          acceptor_(io_context,
                    asio::ip::tcp::endpoint(asio::ip::tcp::v6(), 13)) {
        start_accept();
    }

private:
    void start_accept() {
        tcp_connection::pointer new_connection =
            tcp_connection::create(io_context_);

        acceptor_.async_accept(new_connection->socket(),
                               std::bind(&tcp_server::handle_accept, this,
                                         new_connection,
                                         asio::placeholders::error));
    }

    void handle_accept(tcp_connection::pointer new_connection,
                       const boost::system::error_code &error) {
        if (!error) {
            new_connection->start();
        }

        start_accept();
    }

    asio::io_context &io_context_;
    asio::ip::tcp::acceptor acceptor_;
};

int main() {
    try {
        asio::io_context io_context;
        tcp_server server(io_context);
        io_context.run();
    } catch (std::exception &e) {
        std::cerr << e.what() << std::endl;
    }

    return 0;
}

下面是我的練習程式,將 client 改寫為 asynchronous:

#include <boost/asio.hpp>
#include <iostream>
#include <vector>

namespace asio = boost::asio;

const int BUFFER_SIZE = 128;

void handle_read(const boost::system::error_code &error,
                 std::size_t bytes_transferred, asio::ip::tcp::socket &socket,
                 std::vector<char> &buffer) {
    if (!error) {
        for (std::size_t i = 0; i < bytes_transferred; ++i) {
            std::cout << buffer[i];
        }
    } else {
        std::cerr << "Error during read: " << error.message() << std::endl;
    }
}

int main(int argc, char *argv[]) {
    try {
        if (argc != 2) {
            std::cerr << "Usage: client <host>" << std::endl;
            return 1;
        }

        asio::io_context io_context;

        asio::ip::tcp::resolver resolver(io_context);
        asio::ip::tcp::resolver::results_type endpoints =
            resolver.resolve(argv[1], "daytime");

        asio::ip::tcp::socket socket(io_context);
        asio::connect(socket, endpoints);

        std::vector<char> buffer(BUFFER_SIZE);
        socket.async_read_some(asio::buffer(buffer),
                               std::bind(handle_read, std::placeholders::_1,
                                         std::placeholders::_2,
                                         std::ref(socket), std::ref(buffer)));

        io_context.run();
        socket.close();
    } catch (std::exception &e) {
        std::cerr << e.what() << std::endl;
    }

    return 0;
}

UDP

A synchronous UDP daytime client

我們使用 ip::udp::resolver 物件,根據主機名稱和服務名稱尋找要使用的正確遠端端點。 透過 ip::udp::v6() 參數,查詢被限制為僅傳回 IPv6 端點。 如果 ip::udp::resolver::resolve()函數沒有失敗,則保證至少會傳回清單中的一個端點。這意味著直接解引用回傳值是安全的。

#include <boost/asio.hpp>
#include <array>
#include <iostream>

namespace asio = boost::asio;

int main(int argc, char *argv[]) {
    try {
        if (argc != 2) {
            std::cerr << "Usage: client <host>" << std::endl;
            return 1;
        }

        asio::io_context io_context;

        asio::ip::udp::resolver resolver(io_context);
        asio::ip::udp::endpoint receiver_endpoint =
            *resolver.resolve(asio::ip::udp::v6(), argv[1], "daytime").begin();

        asio::ip::udp::socket socket(io_context);
        socket.open(asio::ip::udp::v6());

        std::array<char, 1> send_buf = {{0}};
        socket.send_to(asio::buffer(send_buf), receiver_endpoint);

        std::array<char, 128> recv_buf;
        asio::ip::udp::endpoint sender_endpoint;
        size_t len =
            socket.receive_from(asio::buffer(recv_buf), sender_endpoint);

        std::cout.write(recv_buf.data(), len);
    } catch (std::exception &e) {
        std::cerr << e.what() << std::endl;
    }

    return 0;
}

A synchronous UDP daytime server

#include <boost/asio.hpp>
#include <array>
#include <ctime>
#include <iostream>
#include <string>

namespace asio = boost::asio;

std::string make_daytime_string() {
    std::time_t now = std::time(0);
    return std::ctime(&now);
}

int main() {
    try {
        asio::io_context io_context;

        asio::ip::udp::socket socket(
            io_context, asio::ip::udp::endpoint(asio::ip::udp::v6(), 13));

        for (;;) {
            std::array<char, 1> recv_buf;
            asio::ip::udp::endpoint remote_endpoint;
            socket.receive_from(asio::buffer(recv_buf), remote_endpoint);

            std::string message = make_daytime_string();

            boost::system::error_code ignored_error;
            socket.send_to(asio::buffer(message), remote_endpoint, 0,
                           ignored_error);
        }
    } catch (std::exception &e) {
        std::cerr << e.what() << std::endl;
    }

    return 0;
}

An asynchronous UDP daytime server

#include <boost/asio.hpp>
#include <array>
#include <ctime>
#include <functional>
#include <iostream>
#include <memory>
#include <string>

namespace asio = boost::asio;

std::string make_daytime_string() {
    std::time_t now = std::time(0);
    return std::ctime(&now);
}

class udp_server {
public:
    udp_server(asio::io_context &io_context)
        : socket_(io_context,
                  asio::ip::udp::endpoint(asio::ip::udp::v6(), 13)) {
        start_receive();
    }

private:
    void start_receive() {
        socket_.async_receive_from(
            asio::buffer(recv_buffer_), remote_endpoint_,
            std::bind(&udp_server::handle_receive, this,
                      asio::placeholders::error,
                      asio::placeholders::bytes_transferred));
    }

    void handle_receive(const boost::system::error_code &error,
                        std::size_t /*bytes_transferred*/) {
        if (!error) {
            std::shared_ptr<std::string> message(
                new std::string(make_daytime_string()));

            socket_.async_send_to(
                asio::buffer(*message), remote_endpoint_,
                std::bind(&udp_server::handle_send, this, message,
                          asio::placeholders::error,
                          asio::placeholders::bytes_transferred));

            start_receive();
        }
    }

    void handle_send(std::shared_ptr<std::string> /*message*/,
                     const boost::system::error_code & /*error*/,
                     std::size_t /*bytes_transferred*/) {}

    asio::ip::udp::socket socket_;
    asio::ip::udp::endpoint remote_endpoint_;
    std::array<char, 1> recv_buffer_;
};

int main() {
    try {
        asio::io_context io_context;
        udp_server server(io_context);
        io_context.run();
    } catch (std::exception &e) {
        std::cerr << e.what() << std::endl;
    }

    return 0;
}

下面是我的練習程式,將 client 改寫為 asynchronous:

#include <array>
#include <boost/asio.hpp>
#include <iostream>

namespace asio = boost::asio;

class udp_client {
public:
    udp_client(asio::io_context &io_context, const std::string &host)
        : socket_(io_context) {
        asio::ip::udp::resolver resolver(io_context);
        remote_endpoint_ =
            *resolver.resolve(asio::ip::udp::v6(), host, "daytime").begin();

        socket_.open(asio::ip::udp::v6());

        start_send();
    }

private:
    void start_send() {
        socket_.async_send_to(asio::buffer(send_buffer_), remote_endpoint_,
                              std::bind(&udp_client::handle_send, this,
                                        asio::placeholders::error,
                                        asio::placeholders::bytes_transferred));
    }

    void handle_send(const boost::system::error_code &error,
                     std::size_t /*bytes_transferred*/) {
        if (!error) {
            socket_.async_receive_from(
                asio::buffer(recv_buffer_), remote_endpoint_,
                std::bind(&udp_client::handle_receive, this,
                          asio::placeholders::error,
                          asio::placeholders::bytes_transferred));
        } else {
            std::cerr << "Error during send: " << error.message() << std::endl;
        }
    }

    void handle_receive(const boost::system::error_code &error,
                        std::size_t bytes_transferred) {

        if (!error) {
            std::cout.write(recv_buffer_.data(), bytes_transferred);
        } else {
            std::cerr << "Error during receive: " << error.message()
                      << std::endl;
        }
    }

    asio::ip::udp::socket socket_;
    asio::ip::udp::endpoint remote_endpoint_;
    std::array<char, 1> send_buffer_ = {{0}};
    std::array<char, 128> recv_buffer_;
};

int main(int argc, char *argv[]) {
    try {
        if (argc != 2) {
            std::cerr << "Usage: client <host>" << std::endl;
            return 1;
        }

        asio::io_context io_context;

        udp_client client(io_context, argv[1]);

        io_context.run();
    } catch (std::exception &e) {
        std::cerr << e.what() << std::endl;
    }

    return 0;
}

相關連結

2025/11/13

MariaDB

MariaDB 與 MySQL 高度相容,可以使用相同的用戶端協定、API 和資料檔案,一般而言是用來作為替代 MySQL 的資料庫使用, 不過即使一開始是從完全相同的原始碼開始分支,時間一長就會出現各種小地方的不同,所以仍然應該視為不同的資料庫比較好。 MariaDB 出現的原因是 MySQL 被 Michael Widenius 賣給 Sun 並且大賺一筆,而在之後 Sun 被 Oracle 收購, MySQL 落入 Oracle 手中之後產生的授權疑慮(因為 Oracle 有權利更改 MySQL 的授權成為閉源軟體), 因此 MySQL 的原始開發者(包含 Michael Widenius)創立 MariaDB,以確保其持續的開源性。 但是到目前為止,Oracle 仍然保持 MySQL 的授權為 GPL,並且 MariaDB 與 MySQL 都是使用相同的商業模式, 都是有社群版本與商業版本,只是 MariaDB 的開發更偏重自由軟體社群(不過我們無法確定是因為 MariaDB 的存在所以 Oracle 保持 MySQL 開源, 或者是因為策略考量,所以 Oracle 保持 MySQL 開源;另外有點黑色幽默的地方在於 Oracle 雖然持有 MySQL, 但是在雲端資料庫所得到的收益比其它公司還要低)。Michael Widenius 吸取了之前的教訓, 為了確保即使公司被收購以後 MariaDB 資料庫仍然會維持開源,MariaDB 的運作可以為二部份, 開放原始碼與社群的管理部份由 MariaDB 基金會運作, 商業與發展策略的部份由 MariaDB 公司運作,而開發的主導為 MariaDB 公司。 MariaDB 公司在 2022 年透過與特殊目的收購公司 (SPAC) Angel Pond Holdings 合併, 在紐約證券交易所上市,股票代碼為 MRDB,但是之後因為財務問題,又在 2024 年下市並且已出售給私募股權基金 K1。

在 MySQL 被 Oracle 取得並且 MariaDB 創立之後,多個 Linux 發行版改為提供 MariaDB。在 openSUSE 安裝的指令如下:

sudo zypper install mariadb

使用下列的方式開啟服務:
sudo systemctl start mariadb
關閉服務:
sudo systemctl stop mariadb
如果要開機的時候就啟動服務,使用:
sudo systemctl enable --now mariadb
如果不要開機的時候就啟動服務,使用:
sudo systemctl disable mariadb

查詢目前的狀態:

sudo systemctl status mariadb

openSUSE 可以使用下列的命令設定讓 MariaDB 更安全:

sudo mysql_secure_installation

在早期 MySQL 並沒有正確實作 UTF-8 編碼,作為其分支的 MariaDB 的 utf8 也繼承了此一特性,實際只支援三個位元組。 一般而言建議使用 utf8mb4,這是真正的 UTF-8 編碼,支援四個位元組。為確保兼容性,建議使用 utf8mb4 作為預設編碼。 修改 /etc/my.cnf 加入下列的內容:

[client]
default-character-set = utf8mb4

[mysqld]
character-set-server = utf8mb4
character-set-client-handshake = FALSE

使用 mariadb 指令連線測試(密碼就是在 mysql_secure_installation 設定時所設定的密碼):

mariadb -u root -p

使用下列的 SQL 語句查看編碼設定:

SHOW VARIABLES LIKE 'character_set%';

接下來建立新資料庫與新使用者 danilo。使用 mariadb 指令連線,然後使用下列指令建立一個名叫 danilo 的新資料庫。

CREATE DATABASE `danilo`;

新增一個資料庫使用者 danilo,並將密碼設定為 danilo:

CREATE USER 'danilo'@'localhost' IDENTIFIED BY 'danilo';

使用 GRANT 設定帳號的權限,讓使用者 danilo 可以在資料庫 dnailo 上有所有的權限:

GRANT ALL PRIVILEGES ON danilo.* TO 'danilo'@'localhost';

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 年發行的專輯《大地》中。粵語版為《衝開一切》。

  • 今天有我

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

  • 光輝歲月

    收錄在 1991 年發行的專輯《光輝歲月》中。周治平/何啟弘作詞,黃家駒作曲,粵語版為《光輝歲月》。 主打歌《光輝歲月》的粵語版是一首讚美南非的非洲人國民大會主席納爾遜·曼德拉的歌曲,以歌頌他在南非種族隔離時期為黑人所付出的努力, 當時曼德拉在監禁 28 年後剛被釋放,光輝歲月表達他的一生。在國語版裡面,這首《光輝歲月》是為激勵年輕人努力拼搏而作, 而當中的種族議題被淡化。

  • 午夜怨曲

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

  • 撒旦的咀咒

    收錄在 1991 年發行的專輯《光輝歲月》中。何惠晶作詞,黃貫中作曲,粵語版為《撒旦的詛咒》。

  • 射手座咒語

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

  • 歲月無聲

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

  • 曾經擁有

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

  • 心中的太陽

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

  • 兩顆心

    收錄在 1991 年發行的專輯《光輝歲月》中。何惠晶作詞,黃家強作曲,粵語版為《兩顆心》。

  • 長城

    收錄在 1992 年發行的專輯《信念》中。詹德茂作詞,黃家駒作曲,粵語版為《長城》。喜多郎創作的前奏十分優秀,我非常喜歡的其中一首國語歌。

  • 關心永遠在

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

  • 愛過的罪

    收錄在 1992 年發行的專輯《信念》中。陳昇作詞,黃貫中作曲,粵語版為《繼續沉醉》。

  • 今天就做

    收錄在 1992 年發行的專輯《信念》中。狗毛作詞,黃家駒作曲,粵語版為《不可一世》。我非常喜歡的其中一首國語歌。

  • 農民

    收錄在 1992 年發行的專輯《信念》中。粵語版為《農民》。《農民》的內容大抵是描述一個中國農民的生活,如何在艱困的生活中逆境自強。但廣東話和國語的版本卻有著極大不同。廣東話版本是由劉卓輝填詞,是對山區農民生活的影射;而國語版則由姚若龍填詞,內容更為廣泛,是描述北方人重視固有生活的個性。這是 Beyond 創作中十分嚴肅的作品。

  • 最想念妳

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

  • 可否衝破

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

  • 問自己

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

  • 年輕

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

  • 愛不容易說

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

  • 身不由己

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

  • Paradise

    收錄在 1994 年發行的專輯《Paradise》中。《Paradise》是 Beyond 樂隊在主音黃家駒逝世後,三人復出發行的首張國語專輯。 《Paradise》這首歌為黃貫中作詞作曲並且擔任主唱,為懷念黃家駒之作。

  • 一輩子陪我走

    收錄在 1994 年發行的專輯《Paradise》中。《一輩子陪我走》則是以一個輕快的伴奏,高歌四子手足之情。

  • 無名英雄

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

  • 和平世界

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

  • 因為有你有我

    收錄在 1994 年發行的專輯《Paradise》中。厲曼婷填詞,黃家強作曲,黃家強擔任主唱,為懷念黃家駒之作。

  • 對嗎

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

  • 一廂情願

    收錄在 1995 年發行的專輯《愛與生活》中。黃貫中作詞作曲,歌曲以直白的歌詞展現了單方面情感付出帶來的無奈與疲憊。

  • 糊塗的人

    收錄在 1995 年發行的專輯《愛與生活》中。黃家強作詞,黃貫中作曲。

  • 唯一

    收錄在 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)

MESSAGE ( STATUS "  FLTK_FOUND :          " ${FLTK_FOUND} )

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

注意:如果自行編譯 FLTK,其預設安裝位置為 /usr/local,但是使用者可以設定其安裝位置。 下面是安裝到自己家目錄下子目錄的例子。

cmake .. -DCMAKE_INSTALL_PREFIX=/home/danilo/Programs/fltk

如果改變預設安裝位置,結果 CMake 無法找到 FLTK,可以嘗試設定 CMAKE_PREFIX_PATH。

cmake .. -DCMAKE_PREFIX_PATH=/home/danilo/Programs/fltk

也可以在 CMakeLists.txt 設定:

cmake_minimum_required(VERSION 3.15)

project(hello)

set(CMAKE_PREFIX_PATH "/home/danilo/Programs/fltk")
set(FLTK_DIR "/home/danilo/Programs/fltk"
CACHE FILEPATH "FLTK installation or build directory")

find_package(FLTK 1.4 CONFIG REQUIRED)

MESSAGE ( STATUS "  FLTK_FOUND :          " ${FLTK_FOUND} )

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

下面更新的 Hello 例子,加入了設定整體的前景顏色與背景顏色,並且使用 Fl::scheme 設定視窗元件的顯示方案。

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

int main(int argc, char **argv) {
    uchar r, g, b;
    Fl::get_color(FL_BLACK, r, g, b);
    Fl::foreground(r, g, b);
    Fl::get_color(FL_WHITE, r, g, b);
    Fl::background(r, g, b);
    Fl::get_color(FL_WHITE, r, g, b);
    Fl::background2(r, g, b);

    Fl::scheme("gtk+");

    Fl_Window *window = new Fl_Window(640, 480);

    Fl_Box *box = new Fl_Box(20, 40, 450, 100, "Hello, World!");
    box->box(FL_UP_BOX);
    box->color(FL_BLACK);
    box->labelfont(FL_BOLD + FL_ITALIC);
    box->labelsize(36);
    box->labeltype(FL_SHADOW_LABEL);
    box->labelcolor(FL_RED);

    Fl_Box *box2 = new Fl_Box(20, 180, 450, 100, "Fast Light Toolkit");
    box2->box(FL_NO_BOX);
    box2->labelsize(36);
    box2->labeltype(FL_SHADOW_LABEL);

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

FLTK 提供了一些顯示文字的視窗元件,下面是使用 Fl_Text_Display 的例子。

// Fl_Text_Display example. -erco
#include <FL/Fl.H>
#include <FL/Fl_Double_Window.H>
#include <FL/Fl_Text_Display.H>

Fl_Double_Window *app_win = nullptr;
Fl_Text_Buffer *buff = nullptr;
Fl_Text_Display *disp = nullptr;

void my_build_app_window() {
    app_win = new Fl_Double_Window (640, 480, "Display");

    buff = new Fl_Text_Buffer();
    disp = new Fl_Text_Display(20, 20, 640 - 40, 480 - 40, "Display Text");
    disp->buffer(buff);
    app_win->resizable(*disp);

    buff->text("line 0\nline 1\nline 2\n"
               "line 3\nline 4\nline 5\n"
               "line 6\nline 7\nline 8\n"
               "line 9\nline 10\nline 11\n"
               "line 12\nline 13\nline 14\n"
               "line 15\nline 16\nline 17\n"
               "line 18\nline 19\nline 20\n"
               "line 21\nline 22\nline 23\n");
}

int main (int argc, char **argv) {
    my_build_app_window();
    app_win->show(argc, argv);
    return (Fl::run());
}

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();
}

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

FLTK 也可以使用 Lambda Expression 作為回呼函數,下面是改寫的版本:

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

int main(int argc, char **argv) {
    auto lambda = [](Fl_Widget *, void *) -> void {
        if (Fl::event() == FL_SHORTCUT && Fl::event_key() == FL_Escape)
            return;

        exit(0);
    };

    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(lambda);

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

下面是使用了 Fl_Check_Button 與 Fl_Output 的程式:

#include <FL/Fl.H>
#include <FL/Fl_Check_Button.H>
#include <FL/Fl_Output.H>
#include <FL/Fl_Window.H>

int main(int argc, char **argv) {
    Fl::scheme("gtk+");

    auto lambda = [](Fl_Widget *w, void *data) -> void {
        Fl_Check_Button *button = static_cast<Fl_Check_Button *>(w);
        Fl_Output *output = static_cast<Fl_Output *>(data);

        if (button->value()) {
            output->value("Checkbox is ON");
            output->redraw();
        } else {
            output->value("Checkbox is OFF");
            output->redraw();
        }
    };

    Fl_Window *window = new Fl_Window(350, 250, "FLTK Example");

    Fl_Check_Button *check_button =
        new Fl_Check_Button(50, 50, 150, 30, "Enable Feature");

    Fl_Output *output = new Fl_Output(50, 100, 200, 30);
    output->align(FL_ALIGN_BOTTOM);
    output->tooltip("Display checkbox status.");
    output->value("Checkbox is OFF");

    check_button->callback(lambda, output);

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

下面是使用了 Fl_Round_Button 的程式:

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

void button_cb(Fl_Widget *w, void *data) {
    Fl_Round_Button *button = static_cast<Fl_Round_Button *>(w);
    const char *label = button->label();

    Fl_Box *output_box = static_cast<Fl_Box *>(data);
    if (button->value()) {
        output_box->copy_label(label);
    }
    output_box->redraw();
}

int main(int argc, char **argv) {
    Fl::scheme("gtk+");

    Fl_Window *window = new Fl_Window(300, 200, "Example");

    Fl_Box *output_box = new Fl_Box(20, 150, 260, 30, "Selected: None");
    output_box->box(FL_FLAT_BOX);
    output_box->align(FL_ALIGN_LEFT | FL_ALIGN_INSIDE);
    output_box->labelcolor(FL_BLUE);

    Fl_Round_Button *button1 = new Fl_Round_Button(20, 20, 100, 30, "Option A");
    button1->type(FL_RADIO_BUTTON);
    button1->callback(button_cb, output_box);

    Fl_Round_Button *button2 = new Fl_Round_Button(20, 60, 100, 30, "Option B");
    button2->type(FL_RADIO_BUTTON);
    button2->callback(button_cb, output_box);

    Fl_Round_Button *button3 =
        new Fl_Round_Button(20, 100, 100, 30, "Option C");
    button3->type(FL_RADIO_BUTTON);
    button3->callback(button_cb, output_box);

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

下面是使用了 Fl_Slider 的程式:

#include <FL/Fl.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Slider.H>
#include <FL/Fl_Window.H>
#include <iomanip>
#include <sstream>
#include <string>

void slider_callback(Fl_Widget *w, void *data) {
    Fl_Slider *slider = static_cast<Fl_Slider *> (w);
    double value = slider->value();

    std::stringstream stream;
    stream << std::fixed << std::setprecision(2) << value;
    std::string myvalue = stream.str();

    Fl_Box *box = static_cast<Fl_Box *> (data);
    box->copy_label(myvalue.c_str()); // Update label
    box->redraw(); // Update UI
}

int main() {
    Fl::scheme("gtk+");
    Fl_Window *window = new Fl_Window(400, 250, "Example");

    Fl_Slider *slider = new Fl_Slider(50, 50, 300, 40);
    slider->type(FL_HORIZONTAL);
    slider->value(50.0);
    slider->range(0.0, 100.0);
    slider->step(0.5);

    std::stringstream stream;
    stream << std::fixed << std::setprecision(2) << slider->value();
    std::string myvalue = stream.str();

    Fl_Box *value_box = new Fl_Box(50, 150, 300, 30);
    value_box->box(FL_DOWN_BOX);
    value_box->label(myvalue.c_str());

    slider->callback(slider_callback, value_box);

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

下面是使用了 Fl_Choice 的程式:

#include <FL/Fl.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Choice.H>
#include <FL/Fl_Window.H>
#include <FL/fl_ask.H>

void choice_callback(Fl_Widget *w, void *data) {
    Fl_Choice *choice = static_cast<Fl_Choice *>(w);
    Fl_Box *box = static_cast<Fl_Box *>(data);

    int selected_index = choice->value();
    const char *selected_text = choice->text(selected_index);

    box->copy_label(selected_text);
}

void button_callback(Fl_Widget *w, void *data) {
    Fl_Choice *choice = static_cast<Fl_Choice *> (data);
    int selected_index = choice->value();
    const char *selected_text = choice->text(selected_index);

    // Display the selection in an alert box
    fl_message("You selected: %s (Index: %d)", selected_text, selected_index);
}

int main(int argc, char **argv) {
    Fl::scheme("plastic");

    Fl_Window *win = new Fl_Window(400, 300, "Fl_Choice Example");
    win->begin();

    Fl_Choice *choice = new Fl_Choice(100, 50, 150, 30, "Option:");
    choice->add("Ada");
    choice->add("C#");
    choice->add("C++");
    choice->add("Erlang");
    choice->add("Fortran");
    choice->add("Lua");
    choice->add("Java");
    choice->add("JavaScript");
    choice->add("Object Pascal");
    choice->add("Perl");
    choice->add("PHP");
    choice->add("Python");
    choice->add("Rexx");
    choice->add("Tcl");

    Fl_Box *box = new Fl_Box(100, 100, 150, 30);
    box->box(FL_UP_BOX);
    choice->callback(choice_callback, box);

    Fl_Button *button = new Fl_Button(100, 150, 150, 30, "Show Selection");
    button->callback(button_callback, choice);

    win->end();
    win->show(argc, argv);

    return Fl::run();
}

Fl_Browser 視窗元件顯示一個可捲動的文字行列表,並管理所有文字的儲存。下面是修改自 FLTK 範例程式的練習。

#include <FL/Fl.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Double_Window.H>
#include <FL/Fl_Hold_Browser.H>
#include <FL/Fl_Multi_Browser.H>

// Hold Browser's callback
void HoldBrowserCallback(Fl_Widget *w, void *data) {
    Fl_Hold_Browser *brow = static_cast<Fl_Hold_Browser *>(w);
    Fl_Box *box = static_cast<Fl_Box *>(data);

    int line = brow->value();
    box->copy_label(brow->text(line));
}

int main(int argc, char *argv[]) {
    Fl::scheme("gtk+");
    Fl_Double_Window *win = new Fl_Double_Window(360, 270, "Simple Browser");
    win->begin();

    Fl_Hold_Browser *brow = new Fl_Hold_Browser(10, 10, win->w() - 20, 100);
    brow->callback(HoldBrowserCallback); // callback for hold browser
    brow->add("One");
    brow->add("Two");
    brow->add("Three");
    brow->add("Four");
    brow->select(1);

    Fl_Box *box = new Fl_Box(10, 120, win->w() - 20, 100, "One");
    box->box(FL_UP_BOX);
    box->labelfont(FL_BOLD + FL_ITALIC);
    box->labelsize(36);
    box->labelcolor(FL_BLUE);

    brow->callback(HoldBrowserCallback, box); // callback for hold browser

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

下面是一個簡單的 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 = static_cast<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());
}

下面是修改自 Fl_Chart 範例,以及使用 FL_MENU_RADIO flag 的 Fl_Menu_Bar 的測試程式。

#include <FL/Fl.H>
#include <FL/Fl_Chart.H>
#include <FL/Fl_Double_Window.H>
#include <FL/Fl_Menu_Bar.H>
#include <cmath>
#include <cstdio>

Fl_Double_Window *g_win = nullptr;
Fl_Menu_Bar *g_menubar = nullptr;
Fl_Chart *g_chart = nullptr;

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

    if (item->flags & FL_MENU_RADIO) {
        g_chart->type((uchar)item->argument()); // apply change
        g_chart->redraw();
    }
}

int main(int argc, char **argv) {
    g_win = new Fl_Double_Window(720, 486);

    g_menubar = new Fl_Menu_Bar(0, 0, g_win->w(), 25);
    g_menubar->add("Options/FL_BAR_CHART", 0, MyMenuCallback,
                   (void *)FL_BAR_CHART, FL_MENU_RADIO);
    g_menubar->add("Options/FL_HORBAR_CHART", 0, MyMenuCallback,
                   (void *)FL_HORBAR_CHART, FL_MENU_RADIO);
    g_menubar->add("Options/FL_LINE_CHART", 0, MyMenuCallback,
                   (void *)FL_LINE_CHART, FL_MENU_RADIO);
    g_menubar->add("Options/FL_FILL_CHART", 0, MyMenuCallback,
                   (void *)FL_FILL_CHART, FL_MENU_RADIO);
    g_menubar->add("Options/FL_SPIKE_CHART", 0, MyMenuCallback,
                   (void *)FL_SPIKE_CHART, FL_MENU_RADIO);
    g_menubar->add("Options/FL_PIE_CHART", 0, MyMenuCallback,
                   (void *)FL_PIE_CHART, FL_MENU_RADIO);
    g_menubar->add("Options/FL_SPECIALPIE_CHART", 0, MyMenuCallback,
                   (void *)FL_SPECIALPIE_CHART, FL_MENU_RADIO);

    {
        Fl_Menu_Item *item =
            (Fl_Menu_Item *)g_menubar->find_item("Options/FL_BAR_CHART");
        item->value(1);
    }

    g_chart = new Fl_Chart(20, 40, g_win->w() - 40, g_win->h() - 80, "Chart");
    g_chart->bounds(-125.0, 125.0);
    const double start = 1.5;
    const double end = start + 15.1;
    for (double t = start; t < end; t += 0.5) {
        double val = sin(t) * 125.0;
        static char val_str[20];
        sprintf(val_str, "%.0lf", val);
        g_chart->add(val, val_str, (val < 0) ? FL_RED : FL_GREEN);
    }

    g_win->end();
    g_win->resizable(g_win);
    g_win->show();

    return (Fl::run());
}

Fl_Terminal 是 FLTK 1.4 系列新增的視窗元件,用來提供一個可滾動的顯示區域來模擬終端 (Terminal)。 下面是我修改 Fl_Terminal 範例程式的練習。

#include <FL/Fl_Box.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Double_Window.H>
#include <FL/Fl_Terminal.H>
#include <time.h>

#define TERMINAL_HEIGHT 550

// Globals
Fl_Double_Window *g_win = nullptr;
Fl_Box *g_msg = nullptr;
Fl_Button *g_button = nullptr;
Fl_Terminal *g_tty = nullptr;
Fl_Box *status_bar = nullptr;

// Append a date/time message to the terminal every 1 seconds
void tick_cb(void *data) {
    time_t lt = time(NULL);
    g_tty->printf("Timer tick: \033[32m%s\033[0m\n", ctime(&lt));
    Fl::repeat_timeout(1.0, tick_cb, data);
}

void button_cb(Fl_Widget *w, void *data) {
    Fl_Button *button = static_cast<Fl_Button *>(w);

    int status = Fl::has_timeout(tick_cb);
    if (status == 0) {
        Fl::add_timeout(0.5, tick_cb);
        status_bar->label("Running.");
    }
}

int main(int argc, char **argv) {
    Fl::scheme("gtk+");
    g_win = new Fl_Double_Window(840, 630, "Example");
    g_win->begin();

    g_msg = new Fl_Box(50, 10, g_win->w() - 150, 30, "Fl_Terminal example");
    g_msg->labelcolor(FL_BLUE);
    g_button = new Fl_Button(g_win->w() - 80, 10, 60, 30, "Run");
    g_button->callback(button_cb);

    g_tty = new Fl_Terminal(0, 50, g_win->w(), TERMINAL_HEIGHT);
    g_tty->ansi(true);

    // Creating a status bar in FLTK by using Fl_Box
    status_bar = new Fl_Box(0, g_win->h() - 30, g_win->w(), 30);
    status_bar->align(FL_ALIGN_LEFT | FL_ALIGN_INSIDE);
    status_bar->box(FL_DOWN_BOX);
    status_bar->label("Ready.");

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

下面是我的另外一個 Fl_Terminal 練習程式,使用 Fl_Input 取得使用者的輸入,而後使用 popen() 嘗試執行。

#include <FL/Fl_Box.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Double_Window.H>
#include <FL/Fl_Input.H>
#include <FL/Fl_Menu_Bar.H>
#include <FL/Fl_Terminal.H>
#include <FL/fl_ask.H>
#include <cstdio>
#include <cstdlib>
#ifdef _WIN32
#define popen _popen
#define pclose _pclose
#endif

#define TERMINAL_HEIGHT 530

// Globals
Fl_Double_Window *g_win = nullptr;
Fl_Menu_Bar *g_menu = nullptr;
Fl_Input *g_input = nullptr;
Fl_Button *g_button = nullptr;
Fl_Terminal *g_tty = nullptr;

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

    if (strcmp(item->label(), "&Quit") == 0) {
        exit(0);
    } else if (strcmp(item->label(), "&About") == 0) {
        fl_message_title("About");
        fl_message("Welcome.\n"
                   "It is a FLTK program to test Fl_Terminal widget.");
    }
}

void button_cb(Fl_Widget *w, void *data) {
    Fl_Button *button = static_cast<Fl_Button *>(w);

    char text[1024];
    sprintf(text, "%s 2>&1", g_input->value()); // stderr + stdout
    g_tty->printf("\nEXECUTING: %s\n", text);

    // The popen() function opens a process by creating a pipe, forking,
    // and invoking the shell.
    FILE *fp = popen(text, "r");
    if (fp == 0) {
        g_tty->printf("Failed to execute: '%s'\n", text);
    } else {
        char s[1024];
        while (fgets(s, sizeof(s) - 1, fp)) {
            g_tty->printf("%s", s);
        }
        pclose(fp);
    }
}

int main(int argc, char **argv) {
    Fl::scheme("gleam");
    g_win = new Fl_Double_Window(840, 630, "Example");
    g_win->begin();

    g_menu = new Fl_Menu_Bar(0, 0, g_win->w(), 30);
    g_menu->add("&File/&Quit", "^q", MyMenuCallback);
    g_menu->add("&Help/&About", 0, MyMenuCallback);

    g_input = new Fl_Input(50, 40, g_win->w() - 150, 30, "Input: ");
    g_input->maximum_size(1000);
    g_button = new Fl_Button(g_win->w() - 80, 40, 60, 30, "Run");
    g_button->callback(button_cb);

    g_tty = new Fl_Terminal(0, 90, g_win->w(), TERMINAL_HEIGHT);
    g_tty->ansi(true);

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

Layout and containers

Fl_Scroll, Fl_Tabs 與 Fl_Wizard 是所謂的容器 (containers),基本上是直接固定視窗元件的的佈局功能, 繼承 Fl_Group 的功能加上一些加強的能力。

下面是使用 Fl_Tabs 與 Fl_Widget::when() 方法的例子。 Fl_Widget::when() 設定用於決定何時呼叫 callback function 的標記。

#include <FL/Enumerations.H>
#include <FL/Fl.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Group.H>
#include <FL/Fl_Input.H>
#include <FL/Fl_Tabs.H>
#include <FL/Fl_Window.H>

void tab_closed_cb(Fl_Widget *w, void *data) {
    auto parent = w->parent();
    parent->remove(w);
}

int main(int argc, char **argv) {
    Fl::scheme("gleam");
    Fl_Window window(Fl::w() / 2, Fl::h() / 2, "test");
    Fl_Box windowBox(0, 32, window.w(), window.h() - 32);
    window.resizable(&windowBox);
    Fl_Tabs mainTabs(0, 32, window.w(), window.h() - 64);

    // First tab
    Fl_Group tab1(0, 64, window.w(), window.h() - 64, "Tab 1");
    Fl_Button *button1 = new Fl_Button(20, 100, 100, 25, "Button 1");
    Fl_Box *box1 = new Fl_Box(20, 145, 100, 50, "Text display");
    tab1.when(FL_WHEN_CLOSED);     // Let user can remove this tab
    tab1.callback(tab_closed_cb);
    tab1.end();

    // Second tab
    Fl_Group tab2(0, 64, window.w(), window.h() - 64, "Tab 2");
    Fl_Button *button2 = new Fl_Button(20, 100, 100, 25, "Button 2");
    Fl_Input *input = new Fl_Input(65, 145, 250, 25, "Input:");
    input->align(FL_ALIGN_LEFT);
    tab2.when(FL_WHEN_CLOSED);
    tab2.callback(tab_closed_cb);
    tab2.end();

    // Third tab
    Fl_Group tab3(0, 64, window.w(), window.h() - 64, "Tab 3");
    Fl_Button *button3 = new Fl_Button(20, 100, 100, 25, "Button 3");
    tab3.when(FL_WHEN_CLOSED);
    tab3.callback(tab_closed_cb);
    tab3.end();

    mainTabs.end();
    window.end();
    window.show(argc, argv);
    return Fl::run();
}

除了直接固定視窗元件的位置,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();
}

Fl_Tile 類別可以讓使用者透過拖曳子元件之間的邊框來調整其子元件的大小。下面是使用的例子:

#include <FL/Fl.H>
#include <FL/Fl_Double_Window.H>
#include <FL/Fl_Tile.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Pack.H>
#include <FL/fl_draw.H>
#include <cstdlib>

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

    exit(0);
}

// A custom box with a label and color
class ColorBox : public Fl_Box {
public:
    ColorBox(int x, int y, int w, int h, const char* label, Fl_Color c)
        : Fl_Box(x, y, w, h, label) {
        box(FL_FLAT_BOX);
        labelcolor(FL_BLACK);
        labelsize(24);
        labelfont(FL_BOLD);
        color(c);
    }
};

int main(int argc, char **argv) {
    Fl_Double_Window* window = new Fl_Double_Window(600, 400, "Fl_Tile Example");
    window->begin();

    Fl_Tile* tile = new Fl_Tile(0, 0, 600, 400);
    tile->begin();

    ColorBox* box1 = new ColorBox(0, 0, 200, 400, "Left", FL_YELLOW);
    ColorBox* box2 = new ColorBox(200, 0, 400, 200, "Top Right", FL_GREEN);
    ColorBox* box3 = new ColorBox(200, 200, 400, 200, "Bottom Right", FL_CYAN);

    tile->end();

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

下面是 Fl_Grid 的例子。

#include <FL/Fl.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Double_Window.H>
#include <FL/Fl_Grid.H>
#include <cstdlib>

int main(int argc, char **argv) {
    Fl_Double_Window *win =
        new Fl_Double_Window(320, 180, "3x3 Fl_Grid with Buttons");

    // create the Fl_Grid container with five buttons
    Fl_Grid *grid = new Fl_Grid(0, 0, win->w(), win->h());
    grid->layout(3, 3, 10, 10);
    grid->color(FL_WHITE);

    Fl_Button *b0 = new Fl_Button(0, 0, 0, 0, "New");
    Fl_Button *b1 = new Fl_Button(0, 0, 0, 0, "Options");
    Fl_Button *b3 = new Fl_Button(0, 0, 0, 0, "About");
    Fl_Button *b4 = new Fl_Button(0, 0, 0, 0, "Help");
    Fl_Button *b6 = new Fl_Button(0, 0, 0, 0, "Quit");
    b6->color(FL_BLACK);
    b6->labelcolor(FL_WHITE);
    b6->callback([](Fl_Widget *, void *) -> void { exit(0); });

    // assign buttons to grid positions
    grid->widget(b0, 0, 0);
    grid->widget(b1, 0, 1, 1, 2);
    grid->widget(b3, 1, 1);
    grid->widget(b4, 2, 0);
    grid->widget(b6, 2, 2);
    // grid->show_grid(1);     // enable to display grid helper lines
    grid->end();

    win->end();
    win->resizable(grid);
    win->size_range(300, 100);
    win->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();
}

再來改寫上面的程式,使用 FL_MOVE 事件取得目前滑鼠指標的位置,並且顯示在標題列。

#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>
#include <sstream>
#include <string>

class MouseEvent_Window : public Fl_Window {
    int handle(int e) {
        std::stringstream stream;
        std::string myvalue;

        switch (e) {
        case FL_MOVE:
            stream << "(" << Fl::event_x() << ", " << Fl::event_y() << ")";
            myvalue = stream.str();
            copy_label(myvalue.c_str());

            break;
        default:
            break;
        }

        return (Fl_Window::handle(e));
    }

public:
    MouseEvent_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) {
    MouseEvent_Window *window = new MouseEvent_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)

我在測試的時候不需要加入尋找 OpenGL 的部份,如果發現有相關的錯誤而需要加入,首先在 CMakeLists.txt 加入 find_package:

find_package(OpenGL REQUIRED)

MESSAGE ( STATUS "  OPENGL_FOUND :        " ${OPENGL_FOUND} )
MESSAGE ( STATUS "  OPENGL_INCLUDE_PATH:  " ${OPENGL_INCLUDE_PATH} )

更新 target_include_directories,然後在連結的部份加入:

target_link_libraries(hello PRIVATE fltk::fltk fltk::gl OpenGL::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

Process and Thread

行程 (Process) 和執行緒 (Thread) 之間的不同點:

  • 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) 可以讓執行緒有私人的資料儲存區,讓執行緒可以儲存各自執行緒的資料。

Windows 平台使用 CreateProcess() 建立一個新的行程。Unix-like 平台需要先使用 fork() 建立一個新的行程 (Process), 再使用 execvp() 或者 exec 系列的函數替換為新程式。 以下 C 程式示範在 Unix-like 平台上如何使用 fork() 建立一個新的行程 (Process), 並在子行程中使用 execvp() 替換為新程式(本例中為 ls -al)。父程序使用 wait() 暫停其執行,直到子程序完成。

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

int main() {
    pid_t pid;
    int status;

    pid = fork();

    if (pid == -1) {
        perror("fork failed");
        exit(EXIT_FAILURE);
    } else if (pid == 0) {
        // This is the child process
        printf("Child process: My PID is %u, my parent's PID is %u\n", getpid(),
               getppid());

        char *argv_list[] = {"ls", "-al", NULL};

        execvp("ls", argv_list);

        // If execvp() is successful, the code that follows it in the child
        // process is never executed because the original process is gone.
        perror("execvp failed");
        exit(EXIT_FAILURE);
    } else {
        // This is the parent process
        printf("Parent process: My PID is %u, my child's PID is %u\n", getpid(),
               pid);

        if (waitpid(pid, &status, 0) == -1) {
            perror("waitpid failed");
        } else {
            printf("Parent process: Child %u finished with status %d\n", pid,
                   status);
        }
        exit(EXIT_SUCCESS);
    }

    return 0;
}

Pthreads

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

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;
}

參考連結