Modern C++ 筆記 - Language Usability 篇
Constants
nullptr
有些 C++ 把 NULL
跟 0
當成同一個東西,因此會產生一些奇怪的行為。以下例:
char *ch = NULL;
void foo(char*);
void foo(int);
當我們呼叫 foo(ch)
,foo(int)
會被呼叫,而不是我們預期內的 foo(char*)
。
因此 C++11 以上,用 nullptr
來分別 null pointer 跟 0。
constexpr
有些東西可以在 compile time 就被優化掉,如 const int day_in_secs = 24 * 60 * 60
之類的。
然而如果要做 arr[const]
,compiler 有時候會靠北這東西不是 constexpr
,因此 C++11 以上可以用 constexpr
的 keyword 來讓 compiler 知道。
int len = 10;
// char arr_3[len]; // illegal, not constexpr
const int len_2 = len + 1;
constexpr int len_2_constexpr = 1 + 2 + 3;
// char arr_4[len_2]; // illegal, but ok for most of the compilers
char arr_4[len_2_constexpr]; // char arr_5[len_foo()+5];
// legal
另外 constexpr 也可以作為 function return type,因此理論上可以在 compile time 直接算 recursion。
雖然沒用過,但感覺 leetcode 用得到。
constexpr int fibonacci(const int n)
{
if(n == 1) return 1;
if(n == 2) return 1;
return fibonacci(n-1) + fibonacci(n-2);
}
if constexpr
如果想在 compile-time 直接決定 branch judgement(也就是說在 compile 時就知道這個 if/else 要跑哪條 branch)也可以用 if constexpr
:
constexpr int MAX = 1000;
if constexpr (MAX > 100)
{
std::cout << "Very Big" << std::endl;
}
Variables and Initialization
if-switch
有時候我們會想先弄出個 temp variable 再對他用 if-else / switch-case 做判斷,如以下例:
std::vector<int> v = {3, 1, 4, 2, 5};
const int index = std::find(v.begin(), v.end(), 2) - v.begin();
if (index < 2)
{
std::cout << "2 is in the first half" << std::endl;
}
else if (index == 2)
{
std::cout << "2 is in the middle" << std::endl;
}
else
{
std::cout << "2 is in the second half" << std::endl;
}
這段 code 在尋找 2
在 v
的前中還後段。
以上程式碼執行起來沒問題,但 index 不會被宣告在 if
的 scope 內,因此如果你之後還需要用到 index
這個名字就有點不舒服。
C++17 提供以下寫法:
std::vector<int> v = {3, 1, 4, 2, 5};
if (const int index = std::find(v.begin(), v.end(), 2) - v.begin(); index < 2)
{
std::cout << "2 is in the first half" << std::endl;
}
else if (index == 2)
{
std::cout << "2 is in the middle" << std::endl;
}
else
{
std::cout << "2 is in the second half" << std::endl;
}
跟 for
回圈一樣,在分號前可以先 initialize 一個變數,這變數會跟著此 if-else / switch-case block 的結束而被 delete。
Initializer List
class Foo
{
public:
int value_a;
int value_b;
Foo(int a, int b) : value_a(a), value_b(b) {}
};
int main()
{
// before C++11
int arr[3] = {1, 2, 3};
Foo foo(1, 2);
std::vector<int> vec = {1, 2, 3, 4, 5};
return 0;
}
可以發現我們要 initialize Foo
的時候跟 array 或 vector 的 {}
形式不一樣,整個就不太爽了。
因此在 C++11 以後可以使用 std::initializer_list
來達到同樣的效果。如下例:
class Foo
{
public:
int value_a;
int value_b;
Foo(std::initializer_list<int> list) : value_a(*(list.begin())), value_b(*(list.begin() + 1)) {}
};
int main()
{
// after C++11
Foo foo = {1, 2};
}
在 function 中同樣也可以使用 std::initializer_list
,如下例:
void goo(std::initializer_list<int> list)
{
for (auto n: list)
{
std::cout << n << std::endl;
}
}
int main()
{
goo({1, 2, 3, 4});
}
Structured Binding
如果你想從一個 function 回傳兩個以上的變數,C++11 有 std::tuple
,但要把內容物從一個 tuple 拆出來挺麻煩的。
C++17 提供 Structured Binding 幫你解決:
std::tuple<int, double, std::string> f()
{
return std::make_tuple(1, 2.3, "456");
}
int main()
{
auto [x, y, z] = f();
std::cout << x << ", " << y << ", " << z << std::endl; return 0;
}
Type Inference
auto
跟其他現代語言一樣,C++ 在 C++17 以後也可以自己決定變數的 type:
auto i = 3; // int
C++20 後甚至可以在 function argument 中使用:
int add(auto x, auto y)
{
return x + y;
}
C++14 以後可以用 auto 當 return type:
auto add(auto x, auto y)
{
return x + y;
}
decltype
可以取得一個 expression 的 type,用法如下:
decltype(expression)
我們就可以拿他做型別判斷
auto x = 1;
if (std::is_same<decltype(x), int>::value)
std::cout << "int" << std::endl;
else if (std::is_same<decltype(x), float>::value)
std::cout << "float" << std::endl;
Control Flow
Range-based for loop
C++11 開始,可以跟其他語言一樣 iterate over container,而不需要自己在那邊 i++
。
std::vector<int> v = {1, 2, 3, 4};
for (auto e : v)
std::cout << e << std::endl; // read only
for (auto &e : v)
std::cout << e << std::endl; // read/write