2021年4月7日 星期三

Windows 視窗置頂

C# 列出 windows 目前開啟的視窗(Process),將選擇的視窗(Process)設為置頂,固定在最上層。

程式碼:

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WinTopMost
{
    public partial class Form1 : Form
    {
        [DllImport("user32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int x, int y, int cx, int cy, int uFlags);
        private const int HWND_TOPMOST = -1;
        private const int HWND_NOTOPMOST = -2;
        private const int SWP_NOMOVE = 0x0002;
        private const int SWP_NOSIZE = 0x0001;

        [DllImport("user32.dll", SetLastError = true)]
        static extern int GetWindowLong(IntPtr hWnd, int nIndex);
        const int GWL_EXSTYLE = -20;
        const int WS_EX_TOPMOST = 0x0008;

        private ArrayList ProcessDataArrList;

        public Form1()
        {
            InitializeComponent();
            LoadProcesses();
        }


        //取得Process
        private void LoadProcesses()
        {
            //this.listBox1.Items.Clear();
            Process[] allProcesses = Process.GetProcesses();

            this.ProcessDataArrList = new ArrayList();
            foreach (Process process in allProcesses)
            {
                if (this.chkWinTitleNoEmpty.Checked && "" == process.MainWindowTitle)
                {
                    //略過MainWindowTitle為空
                    continue;
                }
                bool isTop = IsWindowTopMost(process.MainWindowHandle);
                if (this.chkOnlyTopWin.Checked && !isTop)
                {
                    //略過非置頂
                    continue;
                }
                string isTopStr = isTop ? "Top" : "X  ";
                string name = isTopStr + " # " + process.MainWindowTitle + " # " + process.Id + " # " + process.ProcessName;
                this.ProcessDataArrList.Add(new ProcessData(process, name));
            }

            this.listBox1.DataSource = ProcessDataArrList;
            this.listBox1.ValueMember = "MyKey";
            this.listBox1.DisplayMember = "MyName";
        }

        //視窗是否置頂
        private bool IsWindowTopMost(IntPtr hWnd)
        {
            int exStyle = GetWindowLong(hWnd, GWL_EXSTYLE);
            return (exStyle & WS_EX_TOPMOST) == WS_EX_TOPMOST;
        }

        //置頂
        private void btnTopMost_Click(object sender, EventArgs e)
        {
            if (this.listBox1.SelectedIndex != -1)
            {
                ProcessData ProcessData = this.listBox1.SelectedItem as ProcessData;
                SetWindowPos(ProcessData.MyKey.MainWindowHandle,
                    HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);

                LoadProcesses();
            }
        }

        //取消置頂
        private void btnNoTopMost_Click(object sender, EventArgs e)
        {
            if (this.listBox1.SelectedIndex != -1)
            {
                ProcessData ProcessData = this.listBox1.SelectedItem as ProcessData;
                SetWindowPos(ProcessData.MyKey.MainWindowHandle,
                    HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);

                LoadProcesses();
            }
        }

        //更新列表
        private void btnRefreshList_Click(object sender, EventArgs e)
        {
            LoadProcesses();
        }

        //MainWindowTitle不為空
        private void chkWinTitleNoEmpty_CheckedChanged(object sender, EventArgs e)
        {
            LoadProcesses();
        }

        //只列出置頂視窗
        private void chkOnlyTopWin_CheckedChanged(object sender, EventArgs e)
        {
            LoadProcesses();
        }

        //連結網址
        private void linkLabelxyz_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            string target = "https://xyz.cinc.biz";
            try
            {
                //System.Diagnostics.Process.Start(target);//系統找不到指定的檔案
                //https://stackoverflow.com/questions/21835891/process-starturl-fails
                System.Diagnostics.Process.Start(new ProcessStartInfo(target) { UseShellExecute = true });
            }
            catch (System.ComponentModel.Win32Exception noBrowser)
            {
                if (noBrowser.ErrorCode == -2147467259)
                    MessageBox.Show(noBrowser.Message);
            }
            catch (System.Exception other)
            {
                MessageBox.Show(other.Message);
            }
        }


    }

    public class ProcessData
    {
        private Process pKey;
        private string pName;

        public ProcessData(Process process, string name)
        {

            this.pKey = process;
            this.pName = name;
        }

        public Process MyKey
        {
            get
            {
                return pKey;
            }
        }

        public string MyName
        {

            get
            {
                return pName;
            }
        }
    }
}



執行畫面:




檔案下載:
https://drive.google.com/uc?export=download&id=11-f-e5sxz_yYdu-csaKu3CuUUzuItS--

SHA256:
ddebfe2fae4009667b2401d746f8113d57bb799f751c8056df2eec208c5e03ab




參考:



2021年3月31日 星期三

Google Sheets 抓上櫃股票股價

在 Google Sheets 試算表中,上市股票股價可以使用 GOOGLEFINANCE() 取得股價。 
上櫃OTC股票則需另外用指令碼處理。
  1. 新增一個工作表,假設名稱為「上櫃股票」,並填入股票代碼。
    如下圖:


  2. 「工具」 -> 「指令碼編輯器」


  3. 填入以下程式碼,選擇執行「mainOTC()」
    function getDataOTC(sheet_name, col_code, col_stock_name, col_price, col_updtime, col_err, row_start, row_end) {
      var spreadsheet = SpreadsheetApp.getActive();
      var rows = spreadsheet.getRange(sheet_name + "!" + col_code + row_start + ":" + col_code + row_end);
      var numRows = rows.getNumRows();
      var data = rows.getValues();
      var options = {
        //若來源網址SSL不安全(或過期)出現Exception: SSL Error,且確定來源資料安全時,可關閉validateHttpsCertificates
        //'validateHttpsCertificates': false
      };
      for (var i = 0; i < numRows; i++) {
        var code = data[i][0];
        //上市
        //https://mis.twse.com.tw/stock/api/getStockInfo.jsp?ex_ch=tse_2353.tw&json=1&delay=0
        //上櫃
        //https://mis.twse.com.tw/stock/api/getStockInfo.jsp?ex_ch=otc_6488.tw&json=1&delay=0
        var url = "https://mis.twse.com.tw/stock/api/getStockInfo.jsp?ex_ch=otc_" + code + ".tw&json=1&delay=0"
        //舊的
        //var url = "https://mis.tse.com.tw/stock/api/getStock.jsp?ch=" + code + ".tw&json=1&_=";
        try {
          var response = UrlFetchApp.fetch(url);
          var json = response.getContentText("UTF-8");
          var data_arr = JSON.parse(json);
          spreadsheet.getRange((sheet_name + "!" + col_stock_name) + (row_start + i)).setValue(data_arr.msgArray[0].n);//股票名稱
          // data_arr.msgArray[0].y 股價(昨天成交價)
          // data_arr.msgArray[0].z 股價(最近成交價)
          var stock_price = data_arr.msgArray[0].z.trim();
          if (isNaN(parseFloat(stock_price))) {
            throw ("股價數值異常:" + stock_price);//盤中可能抓不到股價,抓到"-"
          }
          spreadsheet.getRange((sheet_name + "!" + col_price) + (row_start + i)).setValue(stock_price);
          spreadsheet.getRange((sheet_name + "!" + col_updtime) + (row_start + i)).setValue("股價更新:" + Utilities.formatDate(new Date(), "GMT+8", "yyyy-MM-dd HH:mm:ss"));
          spreadsheet.getRange((sheet_name + "!" + col_err) + (row_start + i)).setValue("");
        } catch (e) {
          spreadsheet.getRange((sheet_name + "!" + col_err) + (row_start + i)).setValue("Err:" + Utilities.formatDate(new Date(), "GMT+8", "yyyy-MM-dd HH:mm:ss") + " " + e);
        }
        Utilities.sleep(1000);//等1秒再繼續下一個
      }
    }
    
    function mainOTC() {
      getDataOTC(
        "上櫃股票", //工作表名稱
        "A",//股票代碼在工作表的欄位
        "B",//股票名稱在工作表的欄位
        "C",//股價在工作表的欄位
        "D",//更新時間在工作表的欄位
        "E",//錯誤訊息在工作表的欄位
        2,  //資料從第幾列開始
        21  //資料在第幾列結束
      );
    }



  4. 因為要在工作表自動填入資料,所以須授權程式可存取 Google Docs
    若之後要取消授權,可於 https://myaccount.google.com/permissions?pli=1 取消。














  5. 抓取結果


  6. 可設定「觸發條件」,讓程式每隔一段時間自動執行,避免每次都要手動執行。











參考:

2021年2月25日 星期四

jQuery trggie 傳參數

$('#test').on('change', function (event, a1, a2) {
    console.log(event); //The event object is always passed as the first parameter to an event handler

    console.log(a1); //aaa
    console.log(a2); //bbb
});

//第二個列陣參數,可用來傳其它參數
$('#test').trigger('change', ['aaa', 'bbb']);


參考:
https://api.jquery.com/trigger/
https://stackoverflow.com/questions/16401538/passing-parameters-on-jquery-trigger




2021年1月23日 星期六

Go 語言筆記

  1. 型別轉換,只支持顯示轉換
    [須為互相兼容的型別資料]
    int(兼容的型別資料)、uint8(兼容的型別資料)、float64(兼容的型別資料).....

    [數字、字串間的轉換,可以用 strconv 套件]
    //數字轉字串 Itoa
    var i int = 100
    var str1 string = strconv.Itoa(i)
    fmt.Printf("%T %v \n", str1, str1) //string 100
    
    //字串轉數字Atoi
    var str2 string = "123"
    ii, err := strconv.Atoi(str2)
    if err != nil {
        // 發生錯誤
        fmt.Printf("%s\n", err.Error())
    }
    fmt.Printf("%T %v \n", ii, ii) //int 123

  2. 空白標識符、匿名佔位符(blank identifier、anonymous placeholder),底線「_」
    佔位置,不用對不須要的資料多設一個變數
    例1:
    func test() (string, string) {
        return "X", "Y"
    }
    func main() {
        var a, _ = test()
        fmt.Println(a) //X
    }
    

    例2:
    arr := [3]int{10, 20, 30}
    sum := 0
    for _, v := range arr {
        sum += v
    }
    fmt.Println(sum)//60

  3.  不定數量參數,「...」
    例1:陣列元素不定數量
    arr := [...]string{"A", "B", "C"}
    fmt.Println(len(arr))//3

    例2:function參數不定數量
    func test(args ...string) {
        fmt.Println(args) //[A B C]
    }
    func main() {
        test("A", "B", "C")
    }

    例3:slice打散
    sliceA := []string{"A", "B", "C"}
    sliceB := []string{"D", "E"}
    sliceC := append(sliceA, sliceB...)
    fmt.Println(sliceC)//[A B C D E]
  4. [...]string{"A", "B", "C"} 和 []string{"A", "B", "C"} 的差異
    [...]string{"A", "B", "C"} 產生的是陣列 array
    []string{"A", "B", "C"} 產生的是切片 slice  (背後產生一個隱性陣列)
    參考:Confusion with “…” operator in golang
    https://stackoverflow.com/questions/28692410/confusion-with-operator-in-golang
  5. 沒有 while,用 for 實現
    例1:
    i := 0
    for i < 10 {
        i++
    }

    例2:無窮迴圈
    for {
        //break
    }
  6. 沒有 class,用結構(struct)實現。
    沒有繼承,用嵌入組合、介面解決,避免繼承產生的維護難度(多用合成/聚合,少用繼承)。
    //猶如名稱為 Abc 的 class
    type Abc struct {
        X int
    }
    
    //猶如 Abc class 中有一個 SetX 方法
    func (a *Abc) SetX(v int) {
        a.X = v
    }
    
    func main() {
        var tt = &Abc{10}
        fmt.Println(tt) //&{10}
    
        tt.SetX(20)
        fmt.Println(tt) //&{20}
    }
  7. struct指標語法糖。
    根據struct接受的參數型態,自動進行取址(reference &)或取值。(dereference *)進行操作
    例1:指標自動轉換取值操作
    type aa struct {
        m int
    }
    func main() {
        var ptr = &aa{10}
        fmt.Println(ptr) //&{10}
        ptr.m = 20       //自動等同 (*ptr).m=10
        fmt.Println(ptr) //&{20}
    }

    例2:指標自動轉換取址操作
    type aa struct {
        m int
    }
    func (x *aa) add(v int) { //aa須為指標
        x.m = x.m + v
    }
    func main() {
        var p = aa{10}
        fmt.Println(p) //{10}
        p.add(20)      //自動等同 (&p).add(20)
        fmt.Println(p) //{30}
    }

    例3:指標自動轉換取值操作
    type aa struct {
        m int
    }
    func (x aa) add(v int) { //aa須為值
        x.m = x.m + v
        fmt.Println(x) //{30}
    }
    func main() {
        var p = &aa{10}
        fmt.Println(p) //&{10}
        p.add(20)      //自動等同 (*p).add(20)
        fmt.Println(p) //&{10} 傳值過去,所以原本的變數仍為10
    }
  8. errors
    errors.New()
    fmt.Printf("%#v\n", errors.New("test err"))
    //&errors.errorString{s:"test err"}

    fmt.Errorf()
    fmt.Printf("%#v\n", fmt.Errorf("test err %d", 456))
    //&errors.errorString{s:"test err 456"}
    
    //使用 %w 包裹(嵌套)錯誤
    fmt.Printf("%#v\n", fmt.Errorf("test err2 %w", errors.New("test err")))
    //&fmt.wrapError{msg:"test err2 test err", err:(*errors.errorString)(0xc000032e70)}

    errors.Unwrap() 解開包裹的錯誤,取得裡層的錯誤
    fmt.Printf("%#v\n", errors.Unwrap(fmt.Errorf("test err2 %w", errors.New("test err"))))
    //&errors.errorString{s:"test err"}

    errors.Is(errA, errB) bool
    判斷 errA 是否為 errB 的錯誤
    如果 errA 是包裹的錯誤,會一層一層解開,跟 errB 比對,任一層相符即為true
    var errB = errors.New("test err")
    var errWrap1 = fmt.Errorf("w1 %w", errB)     //包1層
    var errWrap2 = fmt.Errorf("w2 %w", errWrap1) //包2層
    
    fmt.Printf("%#v\n", errB)     //&main.myErr{msg:"test err"}
    fmt.Printf("%#v\n", errWrap1) //&fmt.wrapError{msg:"w1 test err", err:(*errors.errorString)(0xc000032210)}
    fmt.Printf("%#v\n", errWrap2) //&fmt.wrapError{msg:"w2 w1 test err", err:(*fmt.wrapError)(0xc000004500)}
    
    fmt.Printf("%t\n", errors.Is(errB, errWrap1))     //false
    fmt.Printf("%t\n", errors.Is(errWrap1, errWrap2)) //false
    fmt.Printf("%t\n", errors.Is(errWrap1, errB))     //true
    fmt.Printf("%t\n", errors.Is(errWrap2, errB))     //true, errWrap2是包裹的錯誤,會一層一層解開比較
    fmt.Printf("%t\n", errors.Is(errWrap2, errWrap1)) //true

    errors.As(errA, targetB interface{}) bool
    判斷 errA 是否為 targetB 類型的錯誤,如果 errA 是包裹的錯誤,會一層一層解開,跟 targetB 比對
    將第一個比對符合的錯誤,指派到 targetB,並回傳 true
    type myErr struct {
        msg string
    }
    
    func (e *myErr) Error() string {
        return e.msg
    }
    func main() {
        var targetErr *myErr
        errWrap1 := fmt.Errorf("w1 %w", &myErr{msg: "test err"}) //包1層
        var errWrap2 = fmt.Errorf("w2 %w", errWrap1)             //包2層
    
        fmt.Printf("%#v\n", targetErr) //(*main.myErr)(nil)
        fmt.Printf("%#v\n", errWrap1)  //&mt.wrapError{msg:"w1 test err", err:(*main.myErr)(0xc0000321f0)}
        fmt.Printf("%#v\n", errWrap2)  //&fmt.wrapError{msg:"w2 w1 test err", err:(*fmt.wrapError)(0xc0000044a0)}
    
        fmt.Printf("%t\n", errors.As(errWrap1, &targetErr)) //true
        fmt.Printf("%#v\n", targetErr)                      //&main.myErr{msg:"test err"}
    
        fmt.Printf("%t\n", errors.As(errWrap2, &targetErr)) //true
        fmt.Printf("%#v\n", targetErr)                      //&main.myErr{msg:"test err"}
    }
  9. defer 延遲執行,採LIFO後進先出的順序(Last In, First out)
    通常可使用在解鎖互斥鎖,或關閉文件defer f.Close()避免最後忘記關閉原先開啟的文件。
    defer fmt.Println(1)
    defer fmt.Println(2)
    defer fmt.Println(3)
    //結果
    3
    2
    1
  10. panic() 讓程式失敗中斷,類似其他語言的 throw exception
    可使用在巢狀深層呼叫,發生錯誤時,便於向外傳播
    panic("發生錯誤")
    fmt.Println("hello,world") //不會執行到這裡,程式在前一行已崩潰停止
    //執行結果
    panic: 發生錯誤
    goroutine 1 [running]:
    main.main()
    	d:/Go/test/test.go:29 +0x45
    Process exiting with code: 0
  11. recover(),recover 搭配 defer 可捕捉 panic 產生的錯誤,避免程式中斷崩潰停止
    recover() 回傳值為傳給 panic() 的值,但(所以)在以下情況會是 nil
    A.panic's argument was nil;
    B.the goroutine is not panicking;
    C.recover was not called directly by a deferred function.

    例1:捕捉錯誤,避免系統中斷崩潰停止,但後續程式仍不會執行
    defer func() {
        log.Println("AA")
        err := recover()
        fmt.Println(err)
        log.Println("BB")
    }()
    panic("發生錯誤")
    fmt.Println("hello,world") //雖然程式不會崩潰停止,但依然不會執行到這裡
    //執行結果
    2021/01/18 23:23:29 AA
    2021/01/18 23:23:29 BB
    發生錯誤
    Process exiting with code: 0

    例2:捕捉錯誤,避免系統中斷崩潰停止,後續程式仍可執行
    參考 https://golang.org/ref/spec#Handling_panics
    Handling panics
    //定義一個 protect() 函式,可傳入另一個可能發生panic的函式執行,
    func protect(g func()) {
        defer func() {
            log.Println("done") // Println executes normally even if there is a panic
            if x := recover(); x != nil {
                log.Printf("run time panic: %v", x)
            }
        }()
        log.Println("start")
        g()
    }
    
    func main() {
        protect(func() {
            panic("發生錯誤")
        })
        log.Println("hello,world")
        
        //執行結果
        2021/01/18 23:41:03 start
        2021/01/18 23:41:03 done
        2021/01/18 23:41:03 run time panic: 發生錯誤
        2021/01/18 23:41:03 hello,world
        Process exiting with code: 0
    }
  12. 疑問:log.Printf()、fmt.Println() 穿插寫,每次執行輸出的順序可能不一樣?
    log.Printf("A")
    fmt.Println("B")
    log.Printf("C")
    
    //執行結果1
    2021/01/15 23:38:59 A
    B
    2021/01/15 23:38:59 C
    Process exiting with code: 0
    
    //執行結果2
    2021/01/15 23:38:36 A
    2021/01/15 23:38:36 C
    B
    Process exiting with code: 0
  13. goroutine 併發
    語法:
    go funciotn或method
  14. channel 通道
    多個goroutine併發,之間溝通的管道

    無緩衝通道,Ex:make(chan int)
    有緩衝通道,Ex:make(chan int, 緩衝大小)
  15. 通道 deadlock 死鎖
    • 從沒資料的通道中,接收資料
      ch := make(chan int, 1)
      //ch <- 5
      A := <-ch //
      log.Print(A)
      
    • 空的 select
      select {}
    • 無緩衝通道(或有緩衝通道滿了之後),再發資料給此通道,發送、接收須用goroutine併發分開處理,否則會發生deadlock死鎖

      Ex1:未超出緩衝,不用另一個goroutine接收
      ch := make(chan int, 1) //1個緩衝區
      ch <- 5 //剛好用完緩衝區
      A := <-ch 
      log.Print(A) //A
      Ex2:緩衝滿了之後,發送和接收,須用goroutine併發處理
      ch := make(chan int, 1) //1個緩衝區
      
      ch <- 5 //剛好用完緩衝區
      go func() {
          ch <- 6 //緩衝用完,須用goroutine併發處理
          ch <- 7
      }()
      
      A := <-ch
      log.Print(A) //5
      A = <-ch
      log.Print(A) //6
      A = <-ch
      log.Print(A) //7
    • select 若沒 default, case敘述句跟沒資料的通道取資料、或發送資料給無緩衝區的通道,會出現deadlock死鎖
      ch := make(chan int, 1)
      //ch <- 5
      select {
      case <-ch:
      default:
          log.Print("避免deaklock")
      }
  16. select 通道中的有同時多個 case 條件都符合時,是隨機執行的
    ch := make(chan int, 1)
    //ch <- 5
    select {
    case ch <- 1:
    case ch <- 2:
    case ch <- 3:
    default:
        log.Print("避免deaklock")
    }
    A := <-ch
    log.Print(A) //1、2、3 隨機出現
  17. sync.Mutex 加鎖,避免併發競爭問題
    競爭檢測分析
    go run -race ./main.go
    加入互斥鎖
    var mux sync.Mutex
    mux.Lock()
    defer mux.Unlock()
    唯讀鎖定
    var rwMux sync.RWMutex
    rwMux.Lock()
    defer rwMux.Unlock()
    
    rwMux.RLock() //唯讀鎖定
    defer rwMux.RUnlock()
  18. sync.WaitGroup,等待所有任務完成,可避免併發尚未執行完畢,主程式已結束
    var wg sync.WaitGroup
    wg.Add(1)
    go func() {
        defer wg.Done()
        ....
    }()
    wg.Wait()
  19. reflect 反射
  20. Unit Test 單元測試
    go test 指令會執行所有檔名後綴為「_test.go」檔案內的函式
    go test
    go test -v
    go test -v run 指定要執行的函式
    「_test.go」檔案內的函式須為Test開頭
    例如:
    func TestAdd(t *testing.T) { /* ... */ } //後面接英文,須大寫
    func Test_add(t *testing.T) { /* ... */ }
    範例:
    add.go 專案程式檔案
    package main
    
    import (
        "fmt"
    )
    
    func add(a, b int) int {
        return a + b
    }
    
    func main() {
        fmt.Println(add(1, 2))
    }
    add_test.go 單元測試檔案
    package main
    
    import "testing"
    
    func TestAddA(t *testing.T) {
        if add(1, 2) != 3 {
            t.Error("1+2 err")
        }
    }
    
    func TestAddB(t *testing.T) {
        if add(20, 30) != 50 {
            t.Error("20+30 err")
        }
    }
    執行單元測試
    >go test    
    PASS
    ok      hello/UnitTest  0.216s
    
    >go test -v
    === RUN   TestAddA
    --- PASS: TestAddA (0.00s)
    === RUN   TestAddB
    --- PASS: TestAddB (0.00s)
    PASS
    ok      hello/UnitTest  0.203s
    
    
    >go test -v -run TestAddB
    === RUN   TestAddB
    --- PASS: TestAddB (0.00s)
    PASS
    ok      hello/UnitTest  0.214s
  21. new():根據傳入的資料類型,分配一個記憶體空間,回傳此指標。被分配的空間會清空。
    make():初始化 slice、map、channel
    package main
    
    import "fmt"
    
    func main() {
        //new() 返回的是指针
        p1 := new(int)
        fmt.Printf("p1=%#v\n", p1)   //p1=(*int)(0xc0000ac058)
        fmt.Printf("*p1=%#v\n", *p1) //*p1=0
        //等同new(int)
        var p2 *int
        i := 0
        p2 = &i
        fmt.Printf("p2=%#v\n", p2)   //p2=(*int)(0xc0000ac080)
        fmt.Printf("*p2=%#v\n", *p2) //*p2=0
    
        type Student struct {
            Name string
            Age  int
        }
        //new() 返回的是指针
        x1 := new(Student)
        fmt.Printf("x1=%#v\n", x1) //x1=&main.Student{Name:"", Age:0}
    
        //等同new(Student)
        x2 := &Student{}           //等同new(Student)
        fmt.Printf("x2=%#v\n", x2) //x2=&main.Student{Name:"", Age:0}
    
        //make使用於slice、map、channel,返回的不是指標
        y := make([]Student, 2)
        fmt.Printf("y=%#v\n", y) //y=[]main.Student{main.Student{Name:"", Age:0}, main.Student{Name:"", Age:0}}
    }





參考:


2020年12月28日 星期一

Redis Windows 版

Redis 官網,只提供 Linux 的版本。
但 Microsoft Open Tech 有提供 windows 版本
https://github.com/microsoftarchive/redis/releases
有.msi的安裝檔和免安裝的縮壓檔。

如果用安裝檔,安裝後,會將 Redis 設定成系統服務開機自動啟動,
安裝過程也可選擇是否加入系統 PATH 環境變數、記憶體限制、是否開放防火牆...

如果使用免安裝的縮壓檔,之後想設定成系統服務。
可參考資料夾下的說明文件範例:
安裝成系統服務
redis-server --service-install redis.windows-service.conf --loglevel verbose

移除安裝的系統服務
redis-server --service-uninstall

啟動
redis-server --service-start

停止
redis-server --service-stop

安裝並啟動三個獨立的Redis服務範例:
redis-server --service-install --service-name redisService1 --port 10001
redis-server --service-start --service-name redisService1
redis-server --service-install --service-name redisService2 --port 10002
redis-server --service-start --service-name redisService2
redis-server --service-install --service-name redisService3 --port 10003
redis-server --service-start --service-name redisService3



其他:
https://opensource.microsoft.com
https://github.com/microsoftarchive