2017年6月24日 星期六

在Visual Studio Code寫C/C++ (1) - 第一個執行檔HelloWorld

2017/06/24 10:28~12:25

Visual Studio Code (VSCode) 是個單純的編輯器,但有著非常多得外掛,安裝之後你可以用它來開發Java, PHP, JavaScript 和 Golane等等許多程式語言,也有很多很棒的編輯器工具可以使用。

自從我換從Windows跳槽到Mac之後,一直找不到像Notepad++好用的編輯器,最近因為覺得用XCode開發C/C++程式很不友善,而且XCode只有Mac能用,於事一直在找免費且跨平台的IDE,找到了VSCode,意外發現Notepad++好用的功能他都有,包含我最愛用的編輯器功能『利用關鍵字過濾出此檔案中包含關鍵字的所有行數文字』,既然有這個功能,就沒有理由不用它了啊~!

這篇網誌會用VSCode, C/C++ plugin 還有CMake完成一個helloWorld的執行檔 。


1. 建立專案







2. 設定專案為C/C++
新增兩個檔案:

  • CMakeLists.txt
  • hello-vscode.cpp







CMakeLists.txt內容如下:
cmake_minimum_required(VERSION 3.0)
project(hello-vscode)
set(SOURCE hello-vscode.cpp)
add_executable(${PROJECT_NAME} ${SOURCE})
hello-vscode.cpp內容如下:
#include <iostream>
int main(int argc, const char * argv[]) {
    std::cout << "hello Visual Studio Code! :)" << '\n'; 
    return 0;
}


3. 產生c_cpp_properties.json
  • 在編輯器按下F1,會出現Command列表
  • 輸入"C/Cpp: Edit Configurations"
  • 按下Enter


c_cpp_properties.json內容如下:
{
    "configurations": [
        {
            "name": "Mac",
            "includePath": [
                "${workspaceRoot}",
                "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1",
                "/usr/local/include",
                "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/8.1.0/include",
                "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include",
                "/usr/include"
            ],
            "defines": [],
            "browse": {
                "path": [
                    "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1",
                    "/usr/local/include",
                    "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/8.1.0/include",
                    "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include",
                    "/usr/include"
                ],
                "limitSymbolsToIncludedHeaders": true,
                "databaseFilename": ""
            }
        },
        {
            "name": "Linux",
            "includePath": [
                "${workspaceRoot}",
                "/usr/include",
                "/usr/local/include"
            ],
            "defines": [],
            "browse": {
                "path": [
                    "/usr/include",
                    "/usr/local/include"
                ],
                "limitSymbolsToIncludedHeaders": true,
                "databaseFilename": ""
            }
        },
        {
            "name": "Win32",
            "includePath": [
                "${workspaceRoot}",
                "C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/include"
            ],
            "defines": [
                "_DEBUG",
                "UNICODE"
            ],
            "browse": {
                "path": [
                    "C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/include/*"
                ],
                "limitSymbolsToIncludedHeaders": true,
                "databaseFilename": ""
            }
        }
    ]
}

4. 產生CMake和Make的Tasks設定檔tasks.json
  • 按下F1打開Command表
  • 輸入"Tasks: Configure Task Runner",
  • 選擇Task為"Others"



修改tasks.json內容如下:
{
    // See https://go.microsoft.com/fwlink/?LinkId=733558
    // for the documentation about the tasks.json format
    "version": "0.1.0",
    "command": "sh",
    "isShellCommand": true,
    "args": ["-c"],
    "showOutput": "always",
    "suppressTaskName": true,
    "options": {
        "cwd": "${workspaceRoot}/build"
    },
    "tasks": [
        {
            "taskName": "cmake",
            "args": ["cmake -G 'Unix Makefiles' -DCMAKE_BUILD_TYPE=Debug .."]     
        },
        {
            "taskName": "make",
            "args": ["make -j 8"],
            "isBuildCommand": true            
        }
    ]
}
其中cwd代表Current Working Directory,${workspaceRoot}/build則代表會把產生出來的檔案放在此目錄中。但目前沒有build的目錄,我們必須手動建立build資料夾。





5. 執行CMake

  • 案F1打開Command列表
  • 輸入"Run Task",選擇"Tasks: Run Task"
  • 選擇CMake



6. 產生執行檔
  • 案F1打開Command列表
  • 輸入"Run Build Task"
  • 進入build資料夾
  • 在Terminal中輸入"./hello-vscode"執行程式





7. 使用LLDB Debug
  • 點選debug按鈕
  • 點選齒輪
  • 選擇"C++ (GDB/LLDB)",產生launch.json
  • 將launch.json中的program改為"${workspaceRoot}/build/hello-vscode"
  • 將launch.json中的cwd改為"${workspaceRoot}"
  • 在hello-vscode.cpp中設定斷點
  • 點選Start Debugging開始debug






滑鼠點選行號左邊,即可產生程式斷點 



點選紅色框框處或按下快捷鍵F5開始除錯



最後launch.json內容會長這樣
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "(lldb) Launch",
            "type": "cppdbg",
            "request": "launch",
            "program": "${workspaceRoot}/build/hello-vscode",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${workspaceRoot}",
            "environment": [],
            "externalConsole": true,
            "MIMode": "lldb"
        }
    ]
}

。。。


終於完成在VSCode中的第一個C++程式啦!


2016年5月29日 星期日

從定義看重構

2016/5/29 14:06~14:45


圖片取自 <泰迪軟體-軟體重構入門實作班>投影片

重構就是『在不改變程式外在行為的前提之下改變城市內部結構以提升設計品質

Teddy從定義切入引伸出了六個議題:

1. 如何確認程式外在行為沒有被改變?


在正常的軟體開發過程中,通常會先規劃好設計規格(或類別),才開始動手寫程式,然後經過手動或自動化測試驗證功能是否符合規格。
程式會依時間或需求而變得越來越大坨,測試項目也會跟著越來越多,假設目前的程式已開發了100個功能,當你重構一小塊功能,通常要重新測試這100個功能,以確保自己修改的程式沒有問題。這時候如果沒有自動化測試的話通常開發人員也會開始不敢改程式,更別說整理程式碼了。

2. 如何定義哪些是外在行為?


一個使用者使用計算機類別的類別的public "add" 方法,對使用者來說,add的行為就是外在行為,計算機使用者來說是一個黑箱,我不知道它怎麼做,但我知道它會說。

3. 為什麼要以不改變程式外在行為當作前提?


這個問題很有趣,為什麼不能再新增功能的同時順便重構?
不知道你有沒有這樣的體驗,在新增功能的過程中,發現某一塊的程式碼寫了100行達成一個功能,但你發現其實寫10行就可以達到一樣的功能,所以你修改這一塊程式碼,改完之後才繼續做原本要新增的功能,好不容易把功能新增完,很興奮地按下"執行",再來你的情緒就跟著程式一起崩潰了,是剛剛重構錯誤,還是新增的功能錯誤?

4. 怎麼改變程式內部結構以提升品質?


藉由重構裡面所定義的壞味道與技巧,加上自己的反覆練行。想知道怎麼改可以讀Martin Flower的重構或者上泰迪軟體-軟體重構入門實作班

5. 那些內部結構可以改?


以Java來說,程式碼的結構從小到依序為
變數(Variable) → 條件式(Statement) → 方法(Method) → 類別(Class) → 包裹 (Package)
重構裡面所談的重構技巧也是有結構上的差別,例如調整類別的繼承關係、改變方法的介面、將1個100行的方法切成5個20行的方法等等。

6. 品質提升的目的為何?


讓軟體變軟


Teddy總是能把死的定義講得很活真的有很猛

2015年11月5日 星期四

Android StackTrace

2015/11/05 15:10~15:31

在C語言中,Log時可以使用 __func__  __LINE__ 取得目前程式所執行的函式和行數,在Android中的StackTraceElement物件也提供了類似的功能,它記錄了Call stack的許多資訊,包括執行檔案名稱、程式碼行號、方法(或函數)名稱、類別名稱。

程式範例:

package com.example.getstacktrace;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         trace("test");
    }

    private void trace(String msg) {
         StackTraceElement[] stack = Thread.currentThread().getStackTrace();        
         for(int i = 0; i < stack.length; i++) {
             StackTraceElement s = stack[i];
             Log.d("Brian", "i=" + i);
             Log.d("Brian", "ClassName="+s.getClassName());
             Log.d("Brian", "FileName="+s.getFileName());
             Log.d("Brian", "MethodName="+s.getMethodName());
             Log.d("Brian""LineName="+s.getLineNumber());      
             i ++;
         }
         Log.d("Brian ", msg);
    }

}

輸出結果:
11-05 15:25:49.748: D/Brian(6662): i=0
11-05 15:25:49.748: D/Brian(6662): ClassName=dalvik.system.VMStack
11-05 15:25:49.748: D/Brian(6662): FileName=VMStack.java
11-05 15:25:49.748: D/Brian(6662): MethodName=getThreadStackTrace
11-05 15:25:49.748: D/Brian(6662): LineNumber=-2
11-05 15:25:49.748: D/Brian(6662): i=1
11-05 15:25:49.748: D/Brian(6662): ClassName=java.lang.Thread
11-05 15:25:49.748: D/Brian(6662): FileName=Thread.java
11-05 15:25:49.748: D/Brian(6662): MethodName=getStackTrace
11-05 15:25:49.748: D/Brian(6662): LineNumber=599
11-05 15:25:49.748: D/Brian(6662): i=2
11-05 15:25:49.748: D/Brian(6662): ClassName=com.example.getstacktrace.MainActivity
11-05 15:25:49.748: D/Brian(6662): FileName=MainActivity.java
11-05 15:25:49.748: D/Brian(6662): MethodName=trace
11-05 15:25:49.748: D/Brian(6662): LineNumber=16
11-05 15:25:49.748: D/Brian(6662): i=3
11-05 15:25:49.748: D/Brian(6662): ClassName=com.example.getstacktrace.MainActivity
11-05 15:25:49.748: D/Brian(6662): FileName=MainActivity.java
11-05 15:25:49.748: D/Brian(6662): MethodName=onCreate
11-05 15:25:49.748: D/Brian(6662): LineNumber=12
11-05 15:25:49.748: D/Brian(6662): i=4
11-05 15:25:49.748: D/Brian(6662): ClassName=android.app.Activity
11-05 15:25:49.748: D/Brian(6662): FileName=Activity.java
11-05 15:25:49.748: D/Brian(6662): MethodName=performCreate
11-05 15:25:49.748: D/Brian(6662): LineNumber=5165
11-05 15:25:49.748: D/Brian(6662): i=5
11-05 15:25:49.748: D/Brian(6662): ClassName=android.app.Instrumentation
11-05 15:25:49.748: D/Brian(6662): FileName=Instrumentation.java
11-05 15:25:49.748: D/Brian(6662): MethodName=callActivityOnCreate
11-05 15:25:49.748: D/Brian(6662): LineNumber=1103
11-05 15:25:49.758: D/Brian(6662): i=6
11-05 15:25:49.758: D/Brian(6662): ClassName=android.app.ActivityThread
11-05 15:25:49.758: D/Brian(6662): FileName=ActivityThread.java
11-05 15:25:49.758: D/Brian(6662): MethodName=performLaunchActivity
11-05 15:25:49.758: D/Brian(6662): LineNumber=2416
11-05 15:25:49.758: D/Brian(6662): i=7
11-05 15:25:49.758: D/Brian(6662): ClassName=android.app.ActivityThread
11-05 15:25:49.758: D/Brian(6662): FileName=ActivityThread.java
11-05 15:25:49.758: D/Brian(6662): MethodName=handleLaunchActivity
11-05 15:25:49.758: D/Brian(6662): LineNumber=2521
11-05 15:25:49.758: D/Brian(6662): i=8
11-05 15:25:49.758: D/Brian(6662): ClassName=android.app.ActivityThread
11-05 15:25:49.758: D/Brian(6662): FileName=ActivityThread.java
11-05 15:25:49.758: D/Brian(6662): MethodName=access$600
11-05 15:25:49.758: D/Brian(6662): LineNumber=162
11-05 15:25:49.758: D/Brian(6662): i=9
11-05 15:25:49.758: D/Brian(6662): ClassName=android.app.ActivityThread$H
11-05 15:25:49.758: D/Brian(6662): FileName=ActivityThread.java
11-05 15:25:49.758: D/Brian(6662): MethodName=handleMessage
11-05 15:25:49.758: D/Brian(6662): LineNumber=1370
11-05 15:25:49.758: D/Brian(6662): i=10
11-05 15:25:49.758: D/Brian(6662): ClassName=android.os.Handler
11-05 15:25:49.758: D/Brian(6662): FileName=Handler.java
11-05 15:25:49.758: D/Brian(6662): MethodName=dispatchMessage
11-05 15:25:49.758: D/Brian(6662): LineNumber=99
11-05 15:25:49.758: D/Brian(6662): i=11
11-05 15:25:49.758: D/Brian(6662): ClassName=android.os.Looper
11-05 15:25:49.758: D/Brian(6662): FileName=Looper.java
11-05 15:25:49.758: D/Brian(6662): MethodName=loop
11-05 15:25:49.758: D/Brian(6662): LineNumber=158
11-05 15:25:49.758: D/Brian(6662): i=12
11-05 15:25:49.758: D/Brian(6662): ClassName=android.app.ActivityThread
11-05 15:25:49.758: D/Brian(6662): FileName=ActivityThread.java
11-05 15:25:49.758: D/Brian(6662): MethodName=main
11-05 15:25:49.758: D/Brian(6662): LineNumber=5777
11-05 15:25:49.758: D/Brian(6662): i=13
11-05 15:25:49.758: D/Brian(6662): ClassName=java.lang.reflect.Method
11-05 15:25:49.758: D/Brian(6662): FileName=Method.java
11-05 15:25:49.758: D/Brian(6662): MethodName=invokeNative
11-05 15:25:49.758: D/Brian(6662): LineNumber=-2
11-05 15:25:49.758: D/Brian(6662): i=14
11-05 15:25:49.758: D/Brian(6662): ClassName=java.lang.reflect.Method
11-05 15:25:49.758: D/Brian(6662): FileName=Method.java
11-05 15:25:49.758: D/Brian(6662): MethodName=invoke
11-05 15:25:49.758: D/Brian(6662): LineNumber=511
11-05 15:25:49.758: D/Brian(6662): i=15
11-05 15:25:49.758: D/Brian(6662): ClassName=com.android.internal.os.ZygoteInit$MethodAndArgsCaller
11-05 15:25:49.758: D/Brian(6662): FileName=ZygoteInit.java
11-05 15:25:49.758: D/Brian(6662): MethodName=run
11-05 15:25:49.758: D/Brian(6662): LineNumber=1083
11-05 15:25:49.758: D/Brian(6662): i=16
11-05 15:25:49.758: D/Brian(6662): ClassName=com.android.internal.os.ZygoteInit
11-05 15:25:49.758: D/Brian(6662): FileName=ZygoteInit.java
11-05 15:25:49.758: D/Brian(6662): MethodName=main
11-05 15:25:49.758: D/Brian(6662): LineNumber=850
11-05 15:25:49.758: D/Brian(6662): i=17
11-05 15:25:49.758: D/Brian(6662): ClassName=dalvik.system.NativeStart
11-05 15:25:49.758: D/Brian(6662): FileName=NativeStart.java
11-05 15:25:49.758: D/Brian(6662): MethodName=main
11-05 15:25:49.758: D/Brian(6662): LineNumber=-2
11-05 15:25:49.758: D/Brian(6662): test



挑自己需要的StackTraceElement包成Method就很好Debug了!

2015年10月26日 星期一

印出環境變數

2015/10/26 11:38~11:54


在Windows中,很多時候需要用命令提示字元觀看環境變數,在Powershell中,這時可以使用下面的命令

$env:Path

輸出結果:

C:\Windows\Microsoft.NET\Framework\v4.0.30319;C:\ProgramData\Oracle\Java\javapath;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Program Files\Microsoft SQL Server\110\Tools\Binn\;C:\ProgramFiles\TortoiseSVN\bin;C:\HashiCorp\Vagrant\bin;C:\Program Files\SourceGear\Common\DiffMerge\;C:\Program Files (x86)\Skype\Phone\


在Windows的環境變數中,每一個路徑使用分號隔開,直接印出來很難閱讀有哪先路徑。


這時可以把分隔符號分號取代為跳行,結果如下:

($env:Path).Replace(';', "`n")

輸出結果:

C:\Windows\Microsoft.NET\Framework\v4.0.30319
C:\ProgramData\Oracle\Java\javapath
C:\Windows\system32
C:\Windows
C:\Windows\System32\Wbem
C:\Windows\System32\WindowsPowerShell\v1.0\
C:\Program Files\Microsoft SQL Server\110\Tools\Binn\
C:\ProgramFiles\TortoiseSVN\binC:\HashiCorp\Vagrant\bin
C:\Program Files\SourceGear\Common\DiffMerge\
C:\Program Files (x86)\Skype\Phone\

Powershell用起來比命令提示字元順好多!