はじめに
今日は、新しい単元に進まず、復習と再確認をやっていこうと思う。てか、やらなきゃ私の頭がパンクする。
23日目あたりから。
uniform initialization
uniform initializationとは
「一様初期化 (uniform initialization)」は、コンストラクタの呼び出しを、リスト初期化と合わせて波カッコ { }で記述する構文である。
C++日本語リファレンス
std::initializer_list
初期化子リストという。
動的配列std::vector
を初期化するときはこのようにする。
std::vector<int> a{};
最初から箱を作っておくための初期化方法は
std::vector<int> a(10);//箱を10個作って初期化
また、数字をもとから入れて初期化する方法は
std::vector <int> a {5,2,7,8,3};
char型
const char*の意味
const char* の変数は、定数データへのポインタです。中身の変更不可、アドレスの変更可です。
char* constの意味
char* const の変数は、定数ポインタです。中身の変更可、アドレスの変更不可です。
const char* const
const char* const の変数は、定数データへの定数ポインタです。中身の変更不可、アドレスの変更不可です。
参照
とてもわかりやすいサイトでした。
int const * const * const ** const * p;
以前の記事(23日目)でコメントで書いてくださったコードなんですが、
int* const
とint const*
は同じ意味で、「アドレスは変えられないが、中身は変えられるということ」
なので、int***** const const const const p
になる?
たぶん、意味は、アドレスは変えられないが中身は変えらる」。
見える 見えない
スコープ解決演算子
クラス名::メンバ名
でメンバを呼べるのは、なんらかの基底クラスを継承したクラスで、明示的に基底クラスや派生クラスのメンバにアクセスする場合(オーバーライドされているかどうかは関係ない。自分自身のメンバもこの方法で呼べる)と、そのメンバが静的メンバであった場合のみである。
静的メンバ変数とは
これまで、クラス型のオブジェクトには全てのメンバが割り当てられました。
オブジェクトごとにメンバ変数が割り当てられ、メンバ変数はオブジェクト間で関係ありません。
しかし、あるクラスの全てのオブジェクトが、一つの変数を共有したいとすれば、困ります。
グローバル変数を使うこともできますが、それはオブジェクト指向プログラムに反した行為です。
そのような場合は、静的メンバ変数を用います。
静的メンバ変数は、全てのオブジェクトが共有するクラス内グローバル変数と呼べます。
これは、オブジェクトごとにコピーが作られるようなことはなく。
つねにクラスに一つしか存在しないメンバなのです。
つまり同じクラスで生成したインスタンスで、共通したメンバってことか。
PS4の例でいうと、「友達のPS4と自分のPS4のセーブデータをクラウドで共有する」といった感じだと思います。
静的メンバ変数の宣言には static キーワードを使う。
参照
静的メンバとthisの関係
メンバ関数からメンバ変数にアクセスするには、thisという、自分自身のクラスを指す特殊なポインタを利用します。これは暗黙のうちにメンバ関数を呼び出すときに0番目の引数として渡されています。
2つのコードの違い
#include <iostream>
#include <memory>
struct A {
virtual void f() { std::cout << "A" << std::endl; }
};
struct B : A {
virtual void f() override { std::cout << "B" << std::endl; }
void g() { A::f(); }
};
int main()
{
auto b = std::make_unique<B>();
b->g();// => A
}
#include <iostream>
namespace foo{
void f() { std::cout << "foo::f" << std::endl; }
}
int main()
{
foo::f();
}
::
を使えばどこでも、好きなメンバを呼び出せると考えていたがそれは違う。
mainの中でB::g()と呼べるわけではない。
クラスのメンバはインスタンスを通してではないと使えない(見えない)。
つまり、b
はB
のインスタンスではなく、B
を入れているだけなので、b::g()
とできない。
インスタンスがないときに、->
を使って、b->g()
でg()
を呼び出している。
と思っていたが、違うかもしれない。
もう少し考えてみる。
PS4で例えた見える見えない。
この例は非常にわかりやすかったので、こちらの記事でも紹介さしてもらいます。
まるまるコピペします。
たとえ話はあまり好きではありませんが、例えば PS4 を Game という名前空間でクラスにしたとして、そこにゲームを始めるというメンバ関数があったとします。
namespace Game {
class PS4 {
public:
void play() { ... }
}
}
この時、このPS4クラスにグローバル空間にあるmainからアクセスするためには、Game::PS4とスコープ解決演算子で名前空間を指定する必要があります。そうでないと、mainはどこからPS4クラスを見つけてくればいいかわからないのです。
これは、スコープ解決演算子がついていないあらゆる識別子(関数の場合はシグネチャ)は、ローカル変数、関数なら引数、その場所と同じかより外側にある名前空間1にあるクラス、関数、グローバル変数、クラス関数の定義の中であればそのクラスのメンバ、からしか探されない、という仕様によるものです(正確に規格になんて書いてあるかは存じません。)。したがって、mainの中でただPS4と書いても、mainのある空間であるグローバル空間(グローバル空間には外側に空間がないのでこれだけです)内しか「見え」ないため、見つからないのです。そのためにスコープ解決演算子でGame名前空間を指定しておくと、Game名前空間はグローバル空間に存在してmainから「見える」ので、じゃあそのGame名前空間からPS4を探そう、とやってくれるわけですね。
さて、ではGame::PS4::play()とできるか、という話ですが、これはできません。なぜでしょう。ちゃんというなら、メンバ関数は「どのインスタンスで実行されようとしているか」という情報が必要なので、インスタンスを介さないと呼べないのです。たとえ話で言うと、世界には PS4 は山ほど存在しているわけですから、単にGame::PS4::play()と呼んでも、どの PS4 でゲームを起動すればいいのかわからないわけです。
int main() {
// この PS4 はうちにあるもの!
Game::PS4 myPS4;
// 「うちの PS4」で遊ぶ!
myPs4.play();
}
と「どの PS4」かをインスタンス化してやることで、初めて「その PS4」でゲームできるわけですね(ちゃんと言うと、インスタンスを介してメンバ関数を呼ぶことで、そのメンバ関数にthisが渡される。)。
ものすごくざっくりとした説明でしたが、とにかく、クラスのメンバは、たとえスコープ解決演算子を用いても、
・そのメンバが静的である場合
・その場所がそのクラスのメンバ関数の定義の中である場合
でなければ呼べません。それは、抽象的に言うなら「見え」ないからですが、ちゃんと言うなら、メンバへのアクセスに必要不可欠なthisが存在しないからです。
終わりに
まだ復習は終わってないけど、眠いので寝ます。
明日は、復習と通常同里にすすめていけたらなと思っています。