【C++】C++の基本的な言語機能②

この記事は約7分で読めます。

先程に続いて第二章。

参考書

前回と同じ、翔泳社の「独習C++新版」
C++17対応とされている。
この記事のタイトルは、参考書の第2章のもの。目次&見出し&小見出しも参考書の項目名や節目を拝借させてもらう。

構造体・共用体・列挙体

特に変わったことはなさそう。

クラス概要

クラスについては先に勉強してしまったが、この概要では特に新しい話はでてこなかった。

参照

「変数に別名を付ける機能」だそうだが、どうなんだこれ?C++98には関数の引数の参照渡しはあったけど。もともとできることだったのか?

type-name& = reference-name = variable-name;

int value = 42;
int& reference = value;

const参照

参照したい変数がconstなら、参照もconstでなければならない。当然、書き換え不可。

型推論

ついに出てきた。

auto variable-name = initial-value;

初期値がないとエラーになるらしい。

範囲for文の型推論

これは例外で初期値がなくてもエラーにならないらしい。

int array[] = {0,1,2,3,4};
for( auto e : array)
{
std:cout << e << std:endl;
}

そうは言っても配列の中の値で型推論できるからエラーにしなくて済むってだけの話だな。

式の型を推論する

式から型を得る構文。

decltype(expression)

decltype(1) one(){ return 1;}
int main(){
auto i = one();
decltype(i)  j;  // この場合、jはint型に推論される
}

decltypeは変数宣言だけじゃなくて、テンプレートで大変有用だとある。確かにそうかも。いままでテンプレートは殆ど使ってこなかったけど。

配列の型推論

基本的にはエラーになると考えておけば良さそうなんだが、厳密に言うとややこしいらしい。詳しくは参考書。

型の別名定義

using宣言。

using new-type-name = old-type-name

using integer = int;

まだざっと見ただけだが、UEだとint32とか定義されているんだと思う。

元の型が同じでも別名として定義された型を使っていたら、元の型を使った関数を別名の型の関数でオーバーロードできないらしい。と文章で書いても分かりにくいと思うから詳細は参考書。

ネストした型名

クラス内で別の型を定義して、クラス外でその型を使う場合はスコープ解決演算子を使えば問題なく使えるらしい。

C言語との互換性

C言語ではtypedefを使っていた。今のC++でも使える。

コンソールからの入力

省略。

関数オーバーロード

基本的にはC++98の頃と変わっていないはず。

デフォルト引数

参考書に「厳密には関数オーバーロードではない」とあるが、ここにある。

return-type function-name(type-name parameter = default-argument);

int sum(int a, int b=0, int c=0)
{
return a+b+c;
}

sum(1,2,3); // 全ての引数が渡されて、6が返される
sum(4,6); // 不足分のcはデフォルト値が使われる
sum(5); // 不足分のbとcはデフォルト値が使われる

デフォルト引数は後ろから順番にしか指定できない。
上の関数の場合、aだけにデフォルト値を付けたり、bだけに付けたりできない。

たぶん関数オーバーロードとデフォルト引数ありの関数が混在するとややこしくなる。参考書にはオーバーロードするのが最善とある。

ラムダ式

これも知らなかった。
「述語(predicate)を必要とするアルゴリズムやコールバック関数を登録するような場面にとても強力な機能」なんだそうだ。述語ってのが何のことだか分からないのでググってみると、真偽値を返す関数のことらしい。

[](parameters・・・・) -> return-type
{
lambda-body ・・・・
}

その場で定義して関数のように使える。

int main()
{
auto show = [ ](int i) -> void
{
std::cout << “iの値は” << i << “です。” << std::endl;
}
show(42);
}

ラムダ式を格納する変数は常にautoでなければならないらしい。
※でもautoじゃない変数を使わずにラムダ式(コールバック)を定義しているソースを見掛けた。他の章に書いてあるんだろうか?

戻り値の型の省略

戻り値を省略するとラムダ式が戻り値の型推論をする。型推論できなければエラーになるんだと思う。return文が複数あって戻り値の型がバラバラだとエラーになる。戻り値がなければvoidに推論されるんだそう。

変数のコピーキャプチャ

「ラムダ式は、クロージャ(閉包:closure)と呼ばれることもあります」とあって、javascriptにもあったことを思い出した。使わなかったけど。仮想関数と同じで使いどころが分からない。いや、最近勉強し直して理屈は分かるようになったが、たぶん今後も必要ないってなる。俺がC言語脳だから。

ラムダ式を定義した時点で有効な変数を取り込んで使うことができるらしいが、この取り込むのがキャプチャってわけだ。

[variable, variable・・・・](parameters・・・・) -> return-type
{
lambda-body・・・・
}
[=, variable, variable・・・・](parameters・・・・) -> return-type
{
lambda-body・・・・
}

int a = 0;
auto lambda = [a]()
{
std::cout << a << std::endl;
}
lambda(); // コピーした時のaの中身が表示される
// ラムダ式はコピーを持っているので、元の変数が変更されてもコピーの中身は変わらない
a = 42;
lambda(); // コピーした変数は影響を受けない

実行結果
0
0

キャプチャしたい変数が多い場合にはデフォルトのコピーキャプチャが使える。

int a = 0;
floot b = 3.14;
auto lambda = [=]()
{
std::cout << a << std::endl;
}

この場合、ラムダ式でbは使ってないからコピーされないんだそうだ。パフォーマンスが落ちることはないって書いてある。

変更可能なコピーキャプチャ

「コピーしてキャプチャした変数は暗黙的にconstとなるので変更を加えることができません」とある。だったら尚更使わないってなる。
でもコピーキャプチャした変数を変更したい場合はmutable指定することで変更可能な変数としてキャプチャできるって。mutable指定は全てのキャプチャした変数に影響する、ともある。
mutableってクラスの話の時に俺が絶対に使うことが無いはずって言ったやつだと思う。

[variable, variable・・・・](parameters・・・・) mutable -> return-type
{
lambda-body・・・・
}

int a = 0;
auto lambda = [a]() mutable
{
a = 42;
std::cout << a << std::endl;
}
std::cout << a << std::endl;

出力結果
42
0
ラムダ式の中で変更しても、元の変数は変わらない。

// mutableを付けなくても、これで問題ないらしい。
int a = 0;
auto lambda = [a]()
{
int a_copy = a;
a_copy = 42;
}

参照を取得するキャプチャ

[&variable, &variable・・・・](parameters・・・・) -> return-type
{
lambda-body・・・・
}
[&, &variable, &variable・・・・](parameters・・・・) -> return-type
{
lambda-body・・・・
}

これはラムダ式の中で参照元が書き換えられれば当然、元の変数が書き換えられる。&だけ(デフォルト)にすれば全部参照になる。同時に全部参照と全部コピーはできない。全部参照と一部コピーとかならできるらしい。たぶん忘れちゃうだろうから使いたくなったら参考書だな。

これだけ勉強すればもう十分かも。あとは分からなかったらその都度調べるってことで。
自分でプログラミングする場合は知らない書き方はしないし、それで作れないってこともないはず。

コメント

タイトルとURLをコピーしました