2017年6月15日 星期四

什麼時候必須要用到指標

什麼時候必須要用到指標

很多時候會遇到必須使用指針的情況,舉例來說以下兩個降低依存關係的技巧,才不用動一個一個地方整份專案重編。
  • Handle Class
  • Protocol Class
實際上應該還有很多場合不得不用,或使用會更好。
舉個簡單的例子,考慮一個互相依存的類別,雙方彼此都要有雙方
struct A{
    B b;
};
struct B{
    A a;
};
這時候你怎麼辦呢在類別A內找不到B的定義沒但法實作
舉兩個簡單手段避開依存關係
  • vector (或一個設計良好自訂的容器)
  • 指針
struct B; // 前置宣告
struct A{
    B* b;
};
struct B{
    A a;
};
struct B; // 前置宣告
struct A{
    vector<B> b;
};
struct B{
    A a;
};
其中的前置宣告,也可以使用詳細的型別說明符(Elaborated type specifier)取代
struct A{
    struct B* b;
};

2017年6月8日 星期四

為什麼 cin.get() getline() 沒辦法讀取第二個

為什麼 cin.get() getline() 沒辦法讀取第二個

主要是偵測到結束符號導致被略過,最簡單的方式直接使用
string str;
cin >> str;
就可以連續讀取了
如果真的要用標題的方式則要讓串流做完這一次、調整指針、清除才可以
提供以下幾個方法,是用在不同的情況,需要使用再去詳細爬文
cin.ignore();
cin.sync();
cin.seekg(1,ios::end);
fflush(stdin);

2017年6月5日 星期一

指針(point) 與 參考(ref) 有什麼不同之處

指針(point) 與 參考(ref) 有什麼不同之處

tags: C++ Concept2
本文所指的指針的指針指的是指標,個人覺得這樣比較好做區別
初學時時常讓人搞不清楚的兩個不同的東西,這邊提供幾個大原則做判斷
指針:可以儲存記憶體位置的型別
參考:就好像別名一樣可以完全等同於所指之物
比較特別的還有一種情況叫做(*\&)指針的參考,這又是什麼呢?

兩個符號的用法

分別是
  • *取址 reference
  • &取值 dereference
他們放在非宣告時的變數的前方,從英文可以看出他們具有相反的關係。
首先最讓人搞不清楚的是為什麼他們在宣告時有不同的關係,那根本就是不一樣意思了。
int* i;
int& a;
你把它當作別的東西來看直接把整串看做是一個型別。
我的意思是你不要這樣看int* i; 而直接是 int* i;
前者表示宣告一個指針容器,後者表示宣告一個別名,跟剛剛的*&完全是兩回事。
由此我也更推薦你使用 int* i; 而不是 int *i;這樣更容易表達。
除非你需要連續宣告,這就真的不得已了int *i, *j; 連續宣告時要個別指定。
回到剛剛的取址與取值簡單來說就是你現在有一個變數,它存在記憶體的某個地方,這個某個地方會用一串數值表示位置比如說 0x01
現在你手上拿到 p=0x01 你希望獲取他的數值就是使用取值 *p 獲取內容
你現在手上拿到 p=0; 你希望獲取他的地址就是使用取址 &p 獲取地址

地址的傳遞可以共同編輯

函式(包含main)內都有各自的空間,彼此是不同互相存取的除非你使用全域函式。
他就像一個公用櫃子一樣,只要你告訴別人我的東西放在幾號櫃子別人就可以來存取。
比如以下的代碼
int i=0;
int* p=&i;
現在p也可以修改i
*p = 1;
把它放到函式內就像這樣
void fun(int* p){
    *p = 1;
}
使用時就像這樣
fun(&i);

參考基本用法

底層裡面都是做指針的存取,使用方法怎麼區別可以這樣想像
參考就像一個不用輸入符號的指針
什麼意思呢,假設有一個指針為下
int i=0;
int* p=&i;
現在p也可以修改i
如果寫成參考就像這樣子
int i=0;
int& ali=i;
現在ali也可以修改i,現在操作ali就像在操作i
叫王小明的綽號 阿明 就像在叫王小明一樣。
放到函式可以省很多事情
void fun(int& p){
    p = 1;
}
使用時就像這樣
int i=0;
fun(i);
現在你在函式內的操作以及呼叫函式時的操作,全部都不用加上符號上了,是不是更好用呢?
或許你會在學到參考後一股腦地全部換參考,但其實他們之間大概有細微的差異

指針

  • 需要檢查有效性
  • 可以指向Null表示無效,也可以隨時更換指向的目標
  • 有指針的指針

參考

  • 不需檢查有效永遠有效
  • 不能換參考,且只能在宣告時給定目標
  • 沒有參考的參考
彙整出來的兩條守則:
  1. 不想付出檢查成本就用參考
  2. 需要更動指向的目標物或初始化為無效,只能用指針

活用參考與指針

很多時候他們是可以互相通用的,這可能不好記憶,透過練習題更容易熟悉,詳見以下的範例
有一個函式
  • 可以在函式內改他的值
  • 並使回傳的可以更改內容
看起來有些繞口,直接看代碼會比較好理解
要可以做到以下的樣板功能
int i;
fun(/*i*/);       //更改 i 的值
fun(/*i*/) += 10; //並加10
大致可以有以下四種做法,各字看一次,做一次應該10分鐘就可以入門參考與指針了
#include <iostream>
using namespace std;

int* fun1(int* i){
    *i = 1;
    return i;
}
int* fun2(int& i){
    i = 2;
    return &i;
}
int& fun3(int* i){
    *i = 3;
    return *i;
}
int& fun4(int& i){
    i = 4;
    return i;
}

int main() {
    int i=0;
    *fun1(&i) += 10;
    cout << "i=" << i << endl;
    *fun2(i) += 10;
    cout << "i=" << i << endl;
    fun3(&i) += 10;
    cout << "i=" << i << endl;
    fun4(i) += 10;
    cout << "i=" << i << endl;
    return 0;
}

2017年6月4日 星期日

批次檔 bat 如何輸出當前目錄檔案名稱到文件.txt 內

CMD 讀取指定

檔案名稱與打印

使用 dir
dir /b /on >list.txt
再來是讀取檔案使用for迴圈一行一行讀出來
@echo off
for /f "tokens=*" %%i in (list.txt) do ( 
    echo %%i 
)
其中 “tokens=*” 是關鍵如果讀取的文字檔中有空白,不加會讀不到
詳細可以參考:Re: [問題] bat檔判斷ini內容

日文處理

如果有日文會亂碼,加一些指令使用UTF-8存檔
cmd /u /c dir /b /on >list_UTF8.txt
這樣輸出是正常的,不過日文字體用內建的記事本開啟可能會有問題,需要其他支援的軟件來開啟。(如 SublimeText);
最悲劇的是…使用UTF-8存檔會導致bat無法讀取,貌似沒救了找不到什麼解。



powershell

後來又花了幾小時改用 powershell 這個真的有點神
附檔名是 ps1 預設打開會是記事本,在CMD輸入指令改由直接啟動
ftype Microsoft.Powershellscript.1="%SystemRoot%\system32\windowspowershell\v1.0\powershell.exe" "%1"
新版的win10初始設定會限制是不能執行腳本的,需要打開權限在 powshell 輸入
Set-ExecutionPolicy RemoteSigned
回答是就可以正常執行了

先創一個讀檔的

Function Read_File($ex_name){
    $file=Get-ChildItem -Recurse -ErrorAction SilentlyContinue -Filter *$ex_name | 
    Where-Object { $_.Extension -eq $ex_name }
    return $file
}
然後直接使用
$file=Read_File '.jpg'
印出就直接打就好了
$file
印出來會一團東西加個
$file.Name           # 保留檔名
$file.BaseName   # 去除附檔名
他會特麼很神的直接變成陣列
$file.Name          # 印出全部
$file.Name [0]     # 印出第一個
$file.Name [-1]   # 印出最後一個
怎麼這麼神奇,果斷放棄bat QuQ
接下來for迴圈可以上場了,一個一個處理
# 複製至資料夾內
ForEach($item in $file){
    mkdir $item.BaseName -ErrorAction SilentlyContinue
    Copy-Item $item.Name $item.BaseName
    # Remove-Item $item.Name
}
$item就是當前檔案名稱了,他會挨個輪過一次,這裡我是要把它放入資夾內。
比如說有個檔案是 123.jpg 我要放到 123/123.jpg 內。


其他參數

回頭來說一下剛剛一團函式
Get-ChildItem
印出當前目錄檔案
Recurse
這個貌似是讀子目路
-ErrorAction
略過警告比如說移動檔案時問你要不要覆蓋,SilentlyContinue是他的附帶參數(安靜的繼續)
-Filter *$ex_name
第一階段過濾出擴展名(副檔名),不過能力有限,假設過濾圖檔.jpg.jpg這樣會一起進來
Where-Object { $_.Extension -eq $ex_name }
補足上一個問題 $_ 指的是當前的緩存物件,這裡有指名(給名字)是$file


輸出到文本

兩行就可以解決了,如果要寫成一行中間加個分號隔開即可,後面選定Unicode才可以支持中文、日文、韓文等等會出狀況的文字。
$file=Get-ChildItem
set-content $file -path list.txt -Encoding Unicode
如果是要在CMD環境下使用可以使用(不過前提是系統支援)
powershell $file=Get-ChildItem; set-content $file -path list.txt -Encoding Unicode
如果想換個位置的話,比如說我目前在使用者目錄,但工作目錄不想cd切過去桌面(這裡輸入的目錄也可以改成絕位置)
$file=Get-ChildItem -path Desktop
set-content $file -path Desktop/list.txt -Encoding Unicode


查找函式

要查找函式可以透過這樣的方式知道有哪些方法可以用
Get-ChildItem $FilenNme | format-list *
另外獲取目錄後來發現一個比較簡單的用法
$file=Get-ChildItem -Recurse -include "*.jpg"

如何在 Visual Studio 2017 使用 bits/stdc++.h

如何在 Visual Studio 2017 使用 bits/stdc++.h


gcc 有個便捷的功能,使用
#include <bits/stdc++.h>
可以一次載入全部的頭文件,你可以不用去思考缺了那些沒補上。
bits/stdc++.h 這份文件是 gcc 自己非標準檔案,VS並沒有也沒有替代方案,作法就是從gcc裡面複製出來,拷貝到VS裡面。
這裡以 Visual Studio 2017 為例,將 gcc 7.1.0 版本的檔案複製進去。
如果你的VS版本不是這個版本也可以使用,只是路徑會不一樣而已。
檔案我已經幫你準備好了,你可以從以下載點獲得:
下載:Visual Studio use stdc++

流程

複製 bits 整個資料夾到以下路徑:(快速點擊3下全選)
C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.10.25017\include
編譯器版本不一樣路徑會變,自己手動開進去找,大概就是2017還有14.10.25017這兩個會變吧。

2017年6月3日 星期六

隱式轉換 執行的條件 與 如何避免

隱式轉換

tags: C++ Concept2
More Effective C++ 條款5
在不同的型別之間允許執行單引數與轉型運算子的隱式轉換
編譯器會想盡辦法為你找出一個適當的的函式

什麼是引數與參數

參數指的是函式定義或宣告上的,比如說
void fun(int a){...}
這個A稱為 fun() 的第一個參數
引數指的是使用的時候比如說
int num=1;
fun(num);
這時候這裡的 num 的位置稱為第一個引數

型態不符

當引數型態與參數不符的時候
假設有一個函式的參數為 string
void funStr(string s){}
使用時輸入的引數是 char[]
char str[] = "ABC";
funStr(str);
這時候引數(char[])與參數(string)型態不符
編譯器會替你找出一個適合的建構子或轉型運算子轉換。

轉換函式

有兩個會成為轉換函式
  • 單引數建構子
  • 轉型運算子

單引數建構子

上面的例子他發現 string (const char* s); 這個很適合,而且沒有第二個選擇,編譯器會自做主張的幫你修改。
funStr(string(str));
這裡可以參考 string 的 ref 其中第4個建構子就是被選中的函式。
http://www.cplusplus.com/reference/string/string/string/
// from c-string (4)
string (const char* s);
任何一個單引數的建構子都會成為成為隱式轉換的函式
(語意上來看基本型別也可以算在這裡。如:int i(0);)
這裡指的是引數不是參數,限制比較寬一些
以下的建構子都算是單引數,都會成為隱式轉換的函式
struct A{
    A(int i){}
};

struct B{
    B(int i, int j=0){}
};
一個具有預設指定的參數並不影響單引數,因為使用的時候可以不輸入。
A a(0);
B b(0);

轉型運算子

還有另一種情況是轉型運算子,假設我們有一個自訂型別內含一個轉型運算子。
struct A {
    A() {}
    operator string();
};
這個運算子用法可以有兩種方式呼叫
A num;

// C++ 轉型
static_cast<string>(num);
// C style 轉型
(string)num;
現在有一個函式如下
void funStr(string s) {}
如果你這樣使用
funStr(str);
編譯器會為你找出轉型運算子自動套用
funStr((string)str);

如果有兩個適合的隱式轉換函式

會發生曖昧(ambiguous)衝突,編譯器無法幫你做出決定。
有兩個類別與一個函式的定義如下
struct A{
    A(class B& i){}
};
struct B{
    B(){}
    operator A(){return B();}
};
void fun(A i){}
當你這樣使用的時候
B b;
fun(b);
error: conversion from 'B' to 'A' is ambiguous
這時候有兩個候選分別是 A 的建構子與 B 的轉型運算子,適用於這裡,編譯器沒辦法幫你做出取捨。

如何避免這個問題(拒絕編譯器隱式轉換)

明確的表達

為你的單引數建構子或轉型運算子加上 explicit 關鍵字
struct A {
    A() {}
    explicit operator string();
};
如此一來除非你明顯的告訴編譯器
A a;
(string)a;
需要轉型否則編譯器將不會自作主張的幫你轉換
用在建構子上也是直接加上即可。

替身類別

用一個類別轉介也可以達到相同的效果
struct Num {
    struct proxy{
        proxy(int i): len(i){}
        operator int&(){return len;}
        int len;
    };
    Num(proxy i): num(i) {}
    int num;
};
void fun(Num n){}
阻斷編譯器的隱式轉換,如此一來你只能明確的輸入正確的型別。
fun(Num(0));
因為它們是高度相關的,而且不應該被客戶使用,應該把它放入類內並使用私有保護。

使用函式轉換型別

明確的使用函式來轉換型別,而不使用隱式轉換運算子
如 string 內的函式 c_str() 就是一個應用的實例
依據自己想轉出的型別做適當的函式,必要時可以加上 const 避免在交出成員指針之後,成員資料非適當的被修改造成錯誤。

例外

某些條件下等號可以成為拒絕隱式轉換的手段,一個自訂型別宣告時如果是帶等號的會影響轉型目標。
型別定義如下
struct Str{
    Str(string s=""){}
};
現在當你使用建構子建構時
Str s("ABC");
當前型別為 char* 目標型別為建構子的 string,編譯器會嘗試將 char* 轉成 string 而 string 正好有一個引數為 char* 的單引數建構子,於是編譯器適當的幫你轉換。
Str s(string("ABC"));
當你使用等號作為建構子時
Str s = "ABC";
這時候目標型別是 Str (左右兩邊要相等),編譯器會嘗試將 char* 轉成 Str,因為找不到適合的函式,故無法轉換。(不過這在VC上不會出問題,只出現在gcc上)
等號這裡的語意是複製語意(copy initialization),之所以最終仍然直接呼叫建構子是因為,編譯器的優化—-複製省略(copy elision),使得最終的結果與直接呼較建構子(direct initialization)相似。

參考