2018年2月11日 星期日

C / C++ 針對特定格式空格或跳行 切割字串 strtok() 範例

C / C++ 針對特定格式空格或跳行 切割字串 strtok() 範例

初學語言時做題目很容易遇到的題目就是處理字串,字串的處理常常對初學者來說很常搞不清楚,尤其容易遇到的問題字串的切割。
字串的常見問題可以參考這篇站內文,看完之後應該可以有比較深入的理解
https://charlottehong.blogspot.tw/2017/04/c-char.html
比如說有一個字串是以空白或跳行隔開的,怎麼把他們取出來。比如說
A B C
AB
把它變成可以這樣存取
str[0] => A
str[1] => B
str[2] => C
str[3] => AB
這裡我們引用一個內建已有的函式來做說明 strtok();
    char buff[]="1 2 5 44 66";

    // 設置切割字符
    const char* delim = " ";

    // 切割字串
    char* pch=strtok(buff,delim);
    while(pch) {
        printf("%s, ", pch);
        pch=strtok(NULL, delim);
    } printf("\n");
這是可以在官方上看到的說明,大致上的用法就是第一次使用的時候需要輸入字串的指針,到了第二次以後就輸入 NULL 即可,詳細的運作原理是在 function 裡面使用 static 變數,會把第一次進來的指針存著。
上面這個式子用了開頭設置變數與每次結尾都執行一行代碼,正好符合 for 迴圈的使用方式,可以簡化到 for 迴圈裡面。
實際上這麼做在C++也比較好可以把 pch 包進去內層區域變數不會汙染到外面的變數名稱,C++鼓勵把變數名稱盡可能延後到需要使用才宣告。C語言或舊的style則鼓勵把所有變數名稱都在開頭宣告好。
我是覺得現代編譯器會對這種情況做優化的(沒有詳細去考證,但是其他有很多會被優化到沒影響),所以如果優化開下去就沒效能問題了,當然是盡可能減少汙染搂;然後切開變數的副作用對後面的人也比較友善,看程式碼才不會猜說,這個變數有沒有在什麼地方被偷偷修改了找半天。
    for(char* pch=strtok(buff,delim); pch; pch=strtok(NULL, delim))
        printf("%s, ", pch);
    printf("\n");
有一點需要注意的是 strtok() 對輸入的指針有副作用(英),翻譯過來意思就是會改變其內容,如果輸入的字串是不允許被修改的,需要建立一個站存字串,然後把它複製進去,再把這個站存字串丟進去做 strtok()。

安全問題

這裡的安全有兩個意思,一個是會不會被駭客竊取資料(從記憶體裡面挖如非法存取偷挖),一個是可能引發未定義行為。語意上直覺得看起是A但是實際上卻是跑B,或是後面接手的人不知道潛規則,比如說你讓兩個指針同時指向同一個位址後面的人不知道就 free 這兩個就出問題了,安全的寫法是非必要避免同時指向,不要弄什麼潛規則了。
其實 string.h 很多都不安全,廣一點來說C的輸入指針的函式很多都有危險,這部分可以參考文首的連結貼文有大致說明了一些輕微的小問題。
這個函式的問題是比較明顯的比較好思考,你使用過後他的指針就一直存在那個 static 裡面,如果你沒有特別去處理這個問題,極端狀況駭客可能可以從這個洞獲取你的資料之類的。
這個函式有了安全的版本,把那個站存的指針丟出來給外面的使用者控制了,可以讓你自己用完之後自己歸零。
nullptr 是 C++ 的空指針,在C裡面用可以用NULL替代。(但是反過來C++裡面寫NULL表示空指針是不安全的盡量避免)
    char str[] = "1 2 3 4 5";
    char delims[] = " ";

    char *next = nullptr;
    char *pch  = strtok_s(str, delims, &next);
    for(;pch; pch = strtok_s(nullptr, delims, &next)){
        printf("%s, ", pch);
    } next = pch = nullptr;
    printf("\n");

範例程式

/*****************************************************************************
Name : 
Date : 2018/02/11
By   : CharlotteHonG
Final: 2018/02/11
*****************************************************************************/
#include <stdio.h>
#include <string.h>

int main() {
    char buff[]="1 2 5 44 66";

    // 設置切割字符
    const char* delim = " ";
    // 切割字串
    for(char* pch=strtok(buff,delim); pch; pch=strtok(NULL, delim))
        printf("%s, ", pch);
    printf("\n");

    return 0;
}

2018年2月10日 星期六

C 錯誤時回報 當前所在位置的 檔案名稱、行數、函式名稱

C 錯誤時回報 當前所在位置的 檔案名稱、行數、函式名稱

C 沒有 C++ 的例外可以用多半就是用print回報信息,需要詳細知道所在位置可以用內建的Macro回報,方法如下
內建有這些
  • __FILE__ 檔案詳細名稱含路徑位置
  • __LINE__ 當前代碼的行數
  • __FILENAME__ 只回報檔名 (這個是我在 overflow 找到的方法)
#define __FILENAME__ strrchr("\\" __FILE__, '\\') + 1
#define POINT_IS_NULL(msg) \
    printf("%s \t\t # %s::%d --> \"%s()\" \n" , \
        (msg) , __FILENAME__, __LINE__, __FUNCTION__);

int main(int argc, char const *argv[]) {
    POINT_IS_NULL("point is NULL");
}
就可以回報所在的詳細位置了


參考

2018年2月9日 星期五

參考 referance 重要性以及是為什麼而存在的

參考 referance 重要性以及是為什麼而存在的

參考與指針的詳細差別可以參考這一篇站內文有詳細介紹
https://charlottehong.blogspot.tw/2018/01/cc-point-refrence.html
今天要來說的是 referance 的重要性,也就是上偏內所提到的各種練習,下面是簡化過的版本。
void fun(int* i){ ++i; }
void fun(int& i){ ++i;}
他們之間有什麼差異,在練習裡我們的要求是修改一個輸入的變數,已經詳細的說明他們的差異,但是這些差異最終也只是可以彌補的差異並且可以被忍受。
彌補的定義是可以透過增加代碼來處理,比如說指針需要檢查有效性就是多打一行if檢查就完事了。
可以忍受的定義是,反正那行if就在最上方,一次性就ok搞定不會一直煩到我。下面要來說的是無法忍受的部分—-別名
之所以無法忍受的原因是變數名稱在程式內會出現很多次,你之後再打程式也可能會在用到,所以無法用別名就會一直被搞到。

別名的問題

來看這一段代碼
/********************************************************************************
Name : 
Date : 2018/02/09
By   : CharlotteHonG
Final: 2018/02/09
********************************************************************************/

#include <iostream>
using namespace std;

// Case1
void euq(int* dst, int* src) {
    if(!dst or !src) { printf("Erroe\n");return; }
    dst = src;
}
void euq_fix1(int** dst, int* src) {
    if(!dst or !src) { printf("Erroe\n");return; }
    *dst = src;
}

// Case2
struct Int2{
    int* is_a_long_names_parameter;
};
void euq2(Int2* dst, int* src) {
    if(!src) { printf("Erroe\n");return; }
    int* alia = dst->is_a_long_names_parameter;
    alia = src;
    }

#define k_alia(i) (i->is_a_long_names_parameter)    
void euq2_fix(Int2* dst, int* src) {
    if(!src) { printf("Erroe\n");return; }
    k_alia(dst) = src;
}
//================================================================
int main(int argc, char const *argv[]){
    int i=10, j=-1;
    int *p1=&i, *p2;

    cout << "*p1=" << *p1 << endl;
    p2=p1;
    cout << "*p2=" << *p2 << endl;

    int *p3 = &j;
    euq(p3, p1);
    cout << "*p3=" << *p3 << endl;

    int *p4 = &j;
    euq_fix1(&p4, p1);
    cout << "*p4=" << *p4 << endl;

    Int2 k;
    k.is_a_long_names_parameter = &j;
    euq2(&k, p1);
    cout << "*k=" << *k.is_a_long_names_parameter << endl;

    euq2_fix(&k, p1);
    cout << "*k=" << *k.is_a_long_names_parameter << endl;
    return 0;
}
//================================================================

// 執行結果
*p1=10
*p2=10
*p3=-1
*p4=10
*k=-1
*k=10
先看第一個案例 euq() 示範了直接等號卻沒有置換的問題,這個問題是出在參數的部分是 copy pass 也就是複製一份資料近來,當前有2個地址指向 i 一個是 main的一個是函式內的,你修改的是函式內的不甘外面的事情。
修復方法可以多一個 *,不過這可能會增加後面的人閱讀問題(到底是二維陣列還是?),就也是還可以忍受不是大問題。
問題出在第二個例子 euq2() 當出現一個超長別名的時候,或者是多重結構一堆 -> 這時候不用別名就真的很困擾了,然後直接取別名就會跟一開始一樣的錯誤。
在C語言裡面用 macro 可以處理這個問題,如上面的程式。但是最好的解決方式還是用參考直接引入參考進來就什麼事情也沒有。

2018年2月8日 星期四

Windwos10 如何直接存取 ftp 伺服器,並新增捷徑直接打開

Windwos10 如何直接存取 ftp 伺服器,並新增捷徑直接打開

安裝軟體功能會比較多,不過如果只是簡單的要上傳以及下載,可以直接用內建的功能即可~
下面是過程的截圖
這裡第一次打開之後會要求輸入密碼~輸入就以存取了
預設也會在左邊出現捷徑,不過實際點了發現有點問題,如果沒有儲存密碼打開會是空白的,讀取不到。
直接在背景點右鍵登入身分就好了
可能需要登入兩次,實測發現他有兩種狀態,一種是直接在資料夾的位址上方輸入ftp位址,另一種是上面的圖示從左邊開啟,這兩種方式登入狀態是分開不同步的。

2018年2月6日 星期二

移動構造函數的必要性 並不像想像中的不重要

移動構造函數的必要性 並不像想像中的不重要

我知道移動構造函數非常重要可以節省很多成本,原本以為移動構造函數只是用到的時候才重要,今天發現一個問題解決了也意外發現,這個東西不能偷懶痾,他不像我想像中的那樣是主動使用才會遇到,某些情況下會在你不知道的時候自動呼叫。
今天再用vector的emplace_back()想了一些邏輯怎麼呼叫,原本的想法是需要移動構造函式他會自己用移動的方式移動進去省成本,沒有的話就是複製構造函式。
一開始沒住到我就傻傻的用地址的方式確認,這是我第一個遇到的問題,vector內容數值不能直接取址,沒遇到還真沒想到,直接取址會噴錯誤。(環境是 VS2017)
錯誤    C1001    編譯器發生內部錯誤。
弄了一下才想到 vector 正確的操作方式要用 iterator,取址要用 .data() 的方式。
回到正題來嘗試將一個數據 emplace_back() 進去
MyData a("a");
vector v;
v.emplace_back();
結果獲得複製進去的數據地址居然不一樣,跑去官方重看一次文件,發現我對emplace_back(); 的理解有誤,他並不是移動進去而是直接就地建造。
而原本上面的寫法會變成建構+移動。
而且上面的寫法也是不對的,那樣寫會跟直接push沒兩樣,要寫在同一行。
vector v;
v.emplace_back(MyData ("a"));
這個問題到這邊就解完了,只是後來我將它多推了幾次,又發現一個驚人的意外
#include <vector>
#include <string>
#include <iostream>

struct President
{
    std::string name;
    std::string country;
    int year;

    President(std::string && p_name, std::string && p_country, int p_year)
        : name(std::move(p_name)), country(std::move(p_country)), year(p_year)
    {
        std::cout << "I am being constructed.\n";
    }
    President(President&& other)
        : name(std::move(other.name)), country(std::move(other.country)), year(other.year)
    {
        std::cout << "I am being moved.\n";
    }
    President& operator=(const President& other) = default;
};

int main()
{
    std::vector<President> elections;
    std::cout << "emplace_back:\n";
    elections.emplace_back("Nelson Mandela", "South Africa", 1994);
    std::cout << "\n-------------\n";
    elections.emplace_back("Nelson Mandela", "South Africa", 1994);

    std::vector<President> reElections;
    std::cout << "\npush_back:\n";
    reElections.push_back(President("Franklin Delano Roosevelt", "the USA", 1936));

    std::cout << "\nContents:\n";
    for (President const& president : elections) {
        std::cout << president.name << " was elected president of "
            << president.country << " in " << president.year << ".\n";
    }
    for (President const& president : reElections) {
        std::cout << president.name << " was re-elected president of "
            << president.country << " in " << president.year << ".\n";
    }
}
驚訝於結果居然不是只建構兩次!怎麼有一個移動。(這裡有一個隱性的雷,應該是呼叫複製建構子,屬於深層的記憶體搬動,只是這個範例沒寫複製建構子變成呼叫移動建構子了)
emplace_back:
I am being constructed.

-------------
I am being constructed.
I am being moved.
想了一下才發現原來是vector空間不足!另闢新天地然後把他們移動過去,嘗試多 emplace_back 幾次就可以發現了。
這就是問題所在了!如果你要想用STL容器,他就可能會在你沒注意到的時候使用了移動函式,所以不能省啊!大原則就是有用STL就得乖乖遵守5條規則,官方可能也是因此才有了 rule of five 這個規則吧。
就算足夠熟悉STL,寫的程式也確實都避開了,也不能保證後面的人或是客戶會知道你沒寫不小心寫了本來應該是移動結果變成複製的代碼。

visual studio 插入片段代碼 快速打出重複文字

visual studio 插入片段代碼 快速打出重複文字

用過 sublimetext 一直很喜歡他的插入片段 snippet,後來需要開發比較大的專案 sublimetext 就顯得有點功能不足了,改用了VS,一開始很不習慣缺了很多片段插入,後來才發現其實VS也可以自訂

安裝套件


先到這裡來打開更新管理員

再來搜尋 snippet desingner 安裝他

使用

使用上很簡單,不過一開始可能會搞不清楚狀況

直接對著你需要插入的代碼框起來按右鍵,就可以新增了,儲存的時候不要改位置直接存檔。

這個是已經做好的片段,可以看一下這邊意思
$void$ $function_name$($int$ $var$){
    $end$
    $return$;
}

上方

A:snippet→ 你在片段管理員會看到的名稱可以詳細打全名,但是也不要太長
B:shortcut→ 你在打程式中呼叫的代號
這邊的定義就是在代碼中打B會看到一個片段是B,B的詳細說明是A

中間片段

中間手動打之後會自動跑到下面,大致上來說就兩個用法
$str$ 直接打你要的字符,str是預設出現的東西會框起來給你改
打出第二個以上會要讓你用 tab 選擇。
使用時會一直在亮起來的地方輪流
$end$ 結束的時候要在哪裡,結束的意思是指按enter,如果一直按 tab 就是前面的代碼一直輪流跑,直到在任一次循環按下enter直接跳到結尾。(預設的for迴圈也是這樣的可以玩玩看功能)
當你按下 enter 之後就跑來這裡
這裡還有一個用法是 $selected$ 不過我也用不出來是要幹嘛,參考內建的用法他好像通常都出現在 $end$ 之前,但是實測就算拿掉 $selected$ 功能也不會有影響。

其他

在一個選項 kind(種類) 中有一個 object 與 literal 可選,我有去查一下好像是插 object 可以從外部輸入,但是具體我也不曉得怎麼用。
在不從外部插入的情況下,使用上兩者沒有任何區別;沒特別需求的人可以當作一樣的好。

其他方便的片段

快速打出 cout
cout << "$string$=" << $var$ << endl;
$selected$ $end$
初始化main函式
#include <iostream>
using namespace std;

//====================================================================================
int main(int argc, char const *argv[]){
    $end$
    return 0;
}
//====================================================================================

2018年2月5日 星期一

Windwos 網路共享印表機 批次檔 自動連結印表機

Windwos 網路共享印表機 批次檔 自動連結印表機

連接的時候遇到幾個問題
  1. 沒有記住帳號開啟 pdf word 會卡很久沒有回應
  2. 共享資料夾管理員權限

沒有記住帳號

發生的原因是沒有記住登入帳號,導致重新開機之後就沒有權限可以存取印表機了,但是印表機還連接著,這時候如果開 PDF 或是 WORD 這種印列功能的軟體就會當掉,卡住等個幾分鐘才會正常。
只能記住帳號保持電腦一直有權限讀取印表機了沒有找到什麼解決方法。或者如果環境允許直接把那台電腦設成共享不用密碼,也可以避免這個問題。

共享資夾權限

遇到的時候覺得蠻奇怪,後來摸清楚了才知道這樣做比較合理,排除帳密權限問題假如現在有三台電腦,以及一個帶有盾牌的 .exe 也就是打開時會詢問你是否使用管理員權限的檔案。
  • 台電腦A開啟免密碼的可讀寫共用
  • 我用電腦B放置執行檔,在電腦B執行正常有管理員權限。
  • 我用電腦C讀取B所放置的執行檔,會沒有管理員權限導致執行失敗。
反過來也是一樣的情況,我一開始的規劃是電腦A開印表機共享+驅動資料夾共享,然後再驅動資料夾放置一個執行檔帶有管理員權限會自動連接印表機+裝驅動,後來測試一下用別的電腦都會失敗才發現這個規則;要放的話可能就是使用自己要copy到桌面再啟動。

批次檔的指令

最後放上鏈接的批次檔指令,分兩個部分
@Echo Off
Title AddPath - By:Charlotte.HonG
:: Date :2017/11/29
:: Final :2017/11/29
::===========================================================
::確認是否為管理員權限
call :IsAdmin
::===========================================================
::安裝裝驅動
pnputil -a \\192.168.1.1\Brother_DCP-7040_Driver\BRPRMA7A.inf
::連接印表機
rundll32 printui.dll,PrintUIEntry /in /n \\192.168.1.1\dcp7040
::===========================================================
Exit

:IsAdmin
@Echo Off
Reg.exe query "HKU\S-1-5-19\Environment"
If Not %ERRORLEVEL% EQU 0 (
  Cls
  Echo [權限不足] 需使用管理員權限開啟
  Pause & Exit
)
goto:eof
備註:BRPRMA7A.inf 這個檔案在驅動裡面有多份 inf 要看一下到底是哪個。
檔案內容長的像這樣,有明確表示說哪一個驅動用哪一段,這個應該可以協助判別正確是哪個檔案。

安裝驅動

這部分是安裝之後在連接的時候就不用在自動指定驅動位置,雖說是安裝不過用複製驅動到Win裡面,可能比較貼切。
這裡節省的動作就是會自動找驅動而不用手動選位置。

連接目標印表機

就是你到網路上的芳鄰按右鍵連線的意思
這裡節省的動作就是還要去開網路上的芳鄰打ip。
不曉得怎麼把指令串起來,這個命令執行之後就是出現安裝精靈,然後一直按下一步就好了。期望的目標結果是執行之後自動連好,不希望有什麼畫面還要按下一步
指令測試了好久還是失敗就放棄了~如果有人知道的希望可以留言告訴我