江添亮のC++入門 を読んで見る
C++の経験が20年以上あるのですが、改めて入門という教材を見たら、何か新しい発見があるのではと思い、調べて見ました。
https://ezoeryou.github.io/cpp-intro/
この解説書は、とても内容量が多く、一通り読み終わるのに随分時間を費やしました
教材の実行環境
Linux環境でのプログラム実行例が示されています。私は、Windows 10 で次のツールを使い試しました。
- エディター Visual Studio Code
- gcc MinGW http://www.mingw.org/
- clang https://clang.llvm.org/
- make http://gnuwin32.sourceforge.net/packages/make.htm
再起関数
この節の例で、「1, 0のみを使った10進数から2進数へ変換する関数」があるが、プログラムの例は、「2進数から10進数へ変換する関数」に思えます。私が何か勘違いしている?
整数型のサイズ
例はLinux の例が出ています。Windows 10では、下記の通りです。昔調べた時から変わっていないですね。
auto print = [](std::size_t s)
{ std::cout << s << "\n"s; };
print(sizeof(char));
print(sizeof(short));
print(sizeof(int));
print(sizeof(long));
print(sizeof(long long));
1
2
4
4
8
整数型の表現できる値の範囲
INI_MIN や INT_MAX は使っていましたが、std にあることは知らなかった…。何が良い?
std::cout
<< std::numeric_limits<int>::min() << "\n"s
<< std::numeric_limits<int>::max() << "\n"s
<< std::numeric_limits<unsigned int>::min() << "\n"s
<< std::numeric_limits<unsigned int>::max() << "\n"s;
-2147483648
2147483647
0
4294967295
NaN (Not a Number)
使うことはないと思いますが、a == b
ならば a != b
が成り立たないケース。
double NaN = std::numeric_limits<double>::quiet_NaN() ;
// true
bool b = NaN != 0.0 ;
// false
bool a = NaN == 0.0 ;
bool c = NaN == NaN ;
bool d = NaN != NaN ;
bool e = NaN < 0.0 ;
有効桁数
std::cout
<< "float: "s << std::numeric_limits<float>::digits << "\n"s
<< "double: "s << std::numeric_limits<double>::digits << "\n"s
<< "long double: "s << std::numeric_limits<long double>::digits << "\n"s
<< "float: "s << std::numeric_limits<float>::digits10 << "\n"s
<< "double: "s << std::numeric_limits<double>::digits10 << "\n"s
<< "long double: "s << std::numeric_limits<long double>::digits10 << "\n"s
<< "float: "s << std::numeric_limits<float>::max_digits10 << "\n"s
<< "double: "s << std::numeric_limits<double>::max_digits10 << "\n"s
<< "long double: "s << std::numeric_limits<long double>::max_digits10 << "\n"s;
float: 24
double: 53
long double: 64
float: 6
double: 15
long double: 18
float: 9
double: 17
long double: 21
浮動小数点の1と比較可能な最小な値との差
std::cout
<< "float: "s << std::numeric_limits<float>::epsilon() << "\n"s
<< "double: "s << std::numeric_limits<double>::epsilon() << "\n"s
<< "long double: "s << std::numeric_limits<long double>::epsilon() << "\n"s ;
float: 1.19209e-007
double: 2.22045e-016
long double: 1.0842e-019
const
const int x = 0;
int const y = 0;
1番目しか書いたことない…。
int a = 0;
const int &b = a; // ok
int const &c = a; // ok
int & const d = a; // error
組み込み型の初期化
int a = 0;
int a(0);
int a{0};
1番目しか書いたことない…。3番目は、struct
で使うことはありますが。
std::vector
と std::array
の処理時間差
動的配列 vector
と固定長配列 array
の処理時間を比較してみました。
vector
は、array
と比較してメモリ拡張のために倍の時間が掛かっています。ただし、宣言時に必要な数を一度に確保してしまえば、array
とほぼ変わらなくなります。
vector
の at()
による参照は、[]
の倍の処理時間が掛かっています。at()
は配列外参照のチェックがあるからでしょうか?
using namespace std;
chrono::system_clock::time_point start, end;
const int num = 100'000;
std::vector<int> v1;
std::array<int, num> ar, ar2;
auto print_time = [](string s, chrono::system_clock::time_point start, chrono::system_clock::time_point end)
{
double time = static_cast<double>(chrono::duration_cast<chrono::microseconds>(end - start).count() / 1000.0);
cout << s << " = " << time << endl;
};
start = chrono::system_clock::now();
for (int ic = 0; ic < num; ic++) {
v1.push_back(ic);
}
end = chrono::system_clock::now();
print_time("vector push_back", start, end);
start = chrono::system_clock::now();
for (int ic = 0; ic < num; ic++) {
ar[ic] = ic;
}
end = chrono::system_clock::now();
print_time("array '='", start, end);
start = chrono::system_clock::now();
for (int ic = 0; ic < num; ic++) {
ar2[ic] = v1[ic];
}
end = chrono::system_clock::now();
print_time("vector []", start, end);
start = chrono::system_clock::now();
for (int ic = 0; ic < num; ic++) {
ar2[ic] = v1.at(ic);
}
end = chrono::system_clock::now();
print_time("vector at()", start, end);
start = chrono::system_clock::now();
for (int ic = 0; ic < num; ic++) {
ar2[ic] = ar[ic];
}
end = chrono::system_clock::now();
print_time("array '='", start, end);
vector push_back = 1.998
array '=' = 1.031
vector [] = 0.997
vector at() = 1.994
array '=' = 1.995
std::basic_string_view
basic_string_viewはストレージを所有しないクラス
basic_string_viewは文字列がnull終端文字列とbasic_stringのどちらで表現されていても問題なく受け取るためのクラス
namespace std {
template <
typename charT,
typename traits = char_traits<charT>
>
class basic_string_view ;
}
basic_string_viewにはbasic_stringと対になる各文字型に対する特殊化がある。
namespace std {
using string_view = basic_string_view<char> ;
using u8string_view = basic_string_view<char8_t> ;
using u16string_view = basic_string_view<char16_t> ;
using u32string_view = basic_string_view<char32_t> ;
using wstring_view = basic_string_view<wchar_t> ;
}
各basic_stringに対するユーザー定義リテラルサフィックスsvがある。
// string_view
auto str = "hello"sv ;
// u8string_view
auto u8str = u8"hello"sv ;
// u16string_view
auto u16str = u"hello"sv ;
// u32string_view
auto u32str = U"hello"sv ;
// wstring_view
auto wstr = L"hello"sv ;
二項分布(std::binomial_distribution)
指数分布(std::exponential_distribution)
正規分布(std::normal_distribution)
VA_OPT
VA_OPTは可変長引数マクロでVA_ARGSにトークン列が渡されたかどうかで置換結果を変えることができる。
VA_OPTは可変引数マクロの置換リストでのみ使える。VA_OPT(content)はVA_ARGSにトークンがない場合はトークンなしに置換され、トークンがある場合はトークン列contentに置換される。
#演算子
#はマクロ実引数を文字列リテラルにする。
#は関数風マクロの置換リストの中のみで使うことができる。#は関数風マクロの仮引数の識別子の直前に書くことができる。#が直前に書かれた識別子は、マクロ実引数のトークン列の文字列リテラルになる。
##演算子
##はマクロ実引数の結合を行う。
##は関数風マクロの置換リストの中にしか書けない。##は両端にマクロの仮引数の識別子を書かなければならない。##は両端の識別子の参照するマクロ実引数のトークン列を結合した置換を行う。