C++ のクラスとオブジェクト (6) - 初期化リスト、明示的なキーワード、静的メンバー

目次

1. 初期化リスト 

1. 定義 

2. 注意事項

3. 初期化リストの初期化を使用してみる

4. 初期化シーケンス

2. 明示的なキーワード

1. 定義

2. 特徴

3. 静的メンバー

1. 定義 

2. 特徴 

3. 質問例


1. 初期化リスト 

次のコードは正常にコンパイルされます。

class A {
private:
	int _a1;//成员声明
	int _a2;
};
int main()
{
	A a;//对象整体定义
	return 0;
}

const型のメンバ変数_xを追加するとコンパイルが通りません。​ 

class A {
private:
	int _a1;
	int _a2;
	const int _x;
};
int main()
{
	A a;
	return 0;
}

これは、const 変数は定義された場所で初期化する必要があり、そうでないとコンパイルが通過しないためです。

class A {
private:
	int _a1;//声明
	int _a2;
	const int _x;
};

private スコープでは const 変数と 2 つの int 変数がメンバ変数の宣言ですが、const 変数を宣言する場合は定義する必要があるのですが、どこで定義するのでしょうか?

C++11以降では、宣言箇所で変数に初期値を代入できるようになりました。

const int _x = 0;

C++11 より前には、各メンバー変数を定義する場所を見つけるという解決策もありました。これにより、変数の初期化の問題が解決されます。この場所では、初期化と代入に初期化リストが使用されます。

1. 定義 

初期化リスト: コロンで始まり、データ メンバーのコンマ区切りのリストが続き、各「メンバー変数」の後に括弧内の初期値または式が続きます。
これにより、const メンバー変数の初期化の問題が解決されます。
class A {
public:
	A()
		:_x(1)
	{}
private:
	int _a1;
	int _a2;
	const int _x;
};
int main()
{
	A a;
	return 0;
}

オブジェクトがコンストラクターを呼び出す限り、初期化リストにはそのすべてのメンバー変数が定義されます。
初期化リストに記述されているかどうかに関係なく、コンパイラは初期化リスト定義内の各変数を初期化します。

class A {
public:
	A()
		:_x(1),_a1(6)
	{}
private:
	int _a1 = 1;
	int _a2 = 2;
	const int _x;
};
int main()
{
	A a;
	return 0;
}

初期化リストで初期化された変数はデフォルト値を使用せず、初期化リストを使用しない変数はデフォルト値を使用します。​ 

2. 注意事項

  1. 各メンバー変数は初期化リストに 1 回だけ出現できます (初期化は 1 回だけ初期化できます)。
  2. クラスには次のメンバーが含まれており、初期化のために初期化リストに配置する必要があります。
  • メンバー変数の参照
  • const メンバー変数
  • カスタム型メンバー (クラスにデフォルトのコンストラクターがない場合)
class B {
public:
	B():_b(0)
	{
		cout << "B()" << endl;
	}
private:
	int _b;
};

class A {
private:
	B _bb;
};
int main()
{
	A aa;
	return 0;
}

 ここで、aa のメンバー変数カスタム型 _bb は、デフォルトのコンストラクターの初期化リストを呼び出すことで初期化できます。

 デフォルトの構築はパラメータなしまたは完全にデフォルトにすることができます。

class B {
public:
	B(int n) :_b(0)//会报错
    B(int n=9) :_b(0)//全缺省
    B( ) :_b(0)//无参
private:
	int _b;
};

3. 初期化リストの初期化を使用してみる

初期化リストを使用するかどうかに関係なく、カスタム型のメンバー変数は最初に初期化リストを使用して初期化する必要があるためです。

 2 つのスタックで実装されたキューを見てみましょう。

typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 10)
	{
		cout << "Stack(size_t capacity = 10)" << endl;

		_array = (DataType*)malloc(capacity * sizeof(DataType));
		if (nullptr == _array)
		{
			perror("malloc申请空间失败");
			exit(-1);
		}

		_size = 0;
		_capacity = capacity;
	}

	void Push(const DataType& data)
    {
		_array[_size] = data;
		_size++;
	}

	Stack(const Stack& st)
	{
		cout << "Stack(const Stack& st)" << endl;
		_array = (DataType*)malloc(sizeof(DataType)*st._capacity);
		if (nullptr == _array)
		{
			perror("malloc申请空间失败");
			exit(-1);
		}

		memcpy(_array, st._array, sizeof(DataType)*st._size);
		_size = st._size;
		_capacity = st._capacity;
	}

	~Stack()
	{
		cout << "~Stack()" << endl;

		if (_array)
		{
			free(_array);
			_array = nullptr;
			_capacity = 0;
			_size = 0;
		}
	}

private:
	DataType *_array;
	size_t    _size;
	size_t    _capacity;
};

class MyQueue
{
public:
	MyQueue(int pushN, int popN)
		:_pushST(pushN)
		, _popST(popN)
	{}

private:
	Stack _pushST;
	Stack _popST;
	int _size = 0;
};

int main()
{	
	MyQueue q(2, 3);
	return 0;
}

デバッグすると、 23MyQueue のコンストラクターにパラメータとして渡され、これらを初期化することがわかります。初期化リストを介して 2 つのメンバー変数を使用します。

 このパラメーターなしのコンストラクターを使用して MyQueue オブジェクトを初期化するとどうなるでしょうか?

class MyQueue
{
public:
	MyQueue()
	{}

private:
	Stack _pushST;
	Stack _popST;
	int _size = 0;
};

初期化リストを書かない場合、MyQueue クラスは Stack のデフォルト コンストラクターを呼び出して、2 つの Stack クラスのオブジェクトを初期化することもできることがわかります。MyQueue のコンストラクターも同じ方法で初期化されます。本質的に同じです。

4. 初期化シーケンス

クラス内でメンバー変数が宣言される順序は、初期化リスト内の順序に関係なく、初期化リスト内で初期化される順序になります。

class A{
public:
	A(int a)
		:_a1(a)
		, _a2(_a1)
	{}

	void Print() {
		cout << _a1 << " " << _a2 << endl;
	}
private:
	int _a2;
	int _a1;
};

int main() {
	A aa(1);
	aa.Print();
}

_a2 は _a1 より前に宣言されているため、初期化リストでは _a1 よりも前に _a2 が初期化されます。_a2 が初期化されるとき、_a1 はまだ初期化されていないため、_a2 はランダムな値になります。

2. 明示的なキーワード

class A {
public:
	A(int a):_a1(a)
	{}
private:
	int _a2;
	int _a1;
};
int main()
{
	A aa1(1);	//构造函数
	A aa2 = 1;	//隐式类型转换
	int i = 1;	
	double d = i;//隐式类型转换
	return 0;
}

デフォルトでは、ここでの暗黙的な型変換は、追加で作成された一時変数の助けを借りて実装されます。一時変数は構築を通じて作成され、コピー構築を通じて変数に値を割り当てるプロセスは、直接構築に最適化されます。次の記事最適化プロセスについて詳しく説明します。

どちらの場合も、型変換プロセスを完了するために一時変数が作成されます。これらの一時変数は変換完了後に破棄され、プログラムの他の部分からは見えなくなります。このような一時変数の作成と破棄は、手動介入なしでコンパイラーによって自動的に処理されます。

  • A aa2 = 1; ここで、int から A への暗黙的な型変換が発生します。コンパイラーは自動的にクラス A のコンストラクターを呼び出して一時 A オブジェクトを作成し、整数値 1 をパラメーターとしてコンストラクターに渡します。この一時的な A オブジェクトは aa2 にコピーされ、暗黙的な型変換が完了します。

  • double d = i; ここで、int から double への暗黙的な型変換が発生します。コンパイラは一時的な double 変数を作成し、整数変数 i の値をこの一時変数にコピーします。次に、この一時的な double 変数の値が変数 d に代入され、暗黙的な型変換が完了します。

コピー構築も構築で初期化リストも使えるのですが、以下のメンバ変数はコピー構築を呼び出すのでしょうか?

class A
{
public:
	A(int a)
		:_a1(a)
	{
		cout << "A(int a)" << endl;
	}

	A(const A& aa)
		:_a1(aa._a1)
	{
		cout << "A(const A& aa)" << endl;
	}

private:
	int _a2;
	int _a1;
};

int main()
{
	A aa1(1);	//构造函数
	A aa2 = 1;	//隐式类型转换
	return 0;
}

出力は、参照型のコピー コンストラクターが呼び出されないことを示しています。

 

これは、C++ のコンパイラがカスタム型を最適化し、構築 + コピー + 最適化を構築するプロセスを最適化するためです。

次のコードで、最初のコードではエラーが報告されるのに、2 番目のコードでは問題がないのはなぜですか?​ 

A& ref = 10;
const A& ref = 10;
  • これは、C++ で参照 ( A& ref など) を宣言し、それを右辺値 (一時オブジェクトやリテラルなど) に初期化しようとすると、コンパイラが通常、サーバーはエラーを報告します。これは、非定数参照を右辺値にバインドできないため、一時オブジェクトへの非定数参照が防止されます。これにより、一時オブジェクトが予期せず変更され、未定義の動作が発生する可能性があります。ただし、const 参照 ( const A& ref など) を宣言すると、const 参照は右辺値にバインドできるため、コンパイラはこのバインディングを許可します。
  • 上記のコードでは、このコード行の const A& ref = 10; 10 は整数リテラルおよび右辺値です。この右辺値を参照 ref にバインドしようとします。 ref は const A& として宣言されているため、定数参照であるため、コンパイラはこのバインディングを許可し、 A クラスを呼び出します。 A(int a) は一時的な A オブジェクトを作成し、 ref をこの一時的なオブジェクトにバインドします。

1. 定義

コンストラクターは、オブジェクトを構築して初期化するだけでなく、単一のパラメーター、またはデフォルト値のない最初のパラメーターを除くすべてのパラメーターに対してデフォルト値を持つこともできます。
コンストラクタには型変換の機能もあります。
単一パラメーターのコンストラクターの場合: 明示的な変更は使用されず、型変換効果があります。
明示的にコンストラクターを変更し、型変換を禁止します---明示的に削除した後、コードをコンパイルできます。

 先ほどのコードですが、コンストラクターの前に明示的なプログラムを追加するとどうなるでしょうか。

class A{
public:
	explicit A(int a)
		:_a1(a)
	{
		cout << "A(int a)" << endl;
	}
	A(const A& aa)
		:_a1(aa._a1)
	{
		cout << "A(const A& aa)" << endl;
	}

private:
	int _a2;
	int _a1;
};
int main()
{
	A aa1(1);	//构造函数
	A aa2 = 1;	//隐式类型转换
	const A& ref = 10;

	return 0;
}

 これら 2 つのコードはエラーを報告し、プログラムは型変換を禁止します。

 

2. 特徴

class A
{
public:
	//explicit A(int a)
	A(int a)
		:_a1(a)
	{
		cout << "A(int a)" << endl;
	}

	//explicit A(int a1, int a2)
	A(int a1, int a2)
		:_a1(a1)
		, _a2(a2)
	{}

private:
	int _a2;
	int _a1;
};
int main()
{
    // 单参数构造函数 C++98
	A aa1(1);	//构造函数
    A aa2 = 1;	//隐式类型转换

	// 多参数构造函数 C++11
	A aa2(1, 1);
    //A aa3= 2,2;//C98不支持
	A aa3 = { 2, 2 };//C++11支持

	return 0;
}
  1. A aa1(1); これは、単一パラメーターのコンストラクターを直接呼び出してオブジェクトを作成する例です。

  2. A aa2 = 1; これは暗黙的な型変換の例です。ここで、整数 1 は暗黙的にクラス A のオブジェクトに変換されます。これは、クラス A が int 型パラメーターを受け入れるコンストラクターを定義しているため、コンパイラーは自動的にそのコンストラクターを呼び出して一時的な A オブジェクトを作成し、それを aa2 に割り当てます。

  3. A aa2(1, 1); これは、2 つのパラメーターのコンストラクターを直接呼び出してオブジェクトを作成する例です。

  4. A aa3 = { 2, 2 }; これは、C++11 で導入されたリストの初期化の例です。このメソッドを使用すると、コンストラクターを明示的に呼び出さずにオブジェクトを初期化できます。

explicit キーワードは、コンパイラが不要な暗黙的な型変換を実行しないようにするために使用されます。コンストラクターの前のコメントを削除して、 explicit キーワードがコンストラクターの前にあるようにすると、 A aa2 = 1; のような暗黙的な型変換が禁止され、コンパイルが行われます。サーバーはエラーを報告します。

たとえば、単一引数のコンストラクタを explicit A(int a) に変更すると、 A aa2 = 1; の行は コンパイラが int から A への暗黙的な型変換を禁止しているため、コンパイル エラーが発生します。 A aa2(1); のように、コンストラクターを明示的に呼び出す必要があります。

一般に、explicit キーワードは型変換を制御し、不要な暗黙的な型変換によって引き起こされるエラーを防ぐのに役立ちます。

3. 静的メンバー

クラスを実装し、プログラム内で作成されたクラス オブジェクトの数をカウントします。

int count = 0;
class A
{
public:
	A(int a = 0)
	{
		++count;
	}
	A(const A& aa)
	{
		++count;
	}
};
void func(A a)
{}
int main()
{
	A aa1;
	A aa2(aa1);
	func(aa1);
	A aa3 = 1;

	cout << count << endl;

	return 0;
}

これにより、C++ xutility ファイル内に定義したグローバル変数の数と競合する関数の数が存在するため、名前の競合の問題が発生しました。

std を展開することはできず、必要なストリームの入出力のみを呼び出すことはできません。

#include <iostream>
//using namespace std;
using std::cout;
using std::endl;

成功した出力: 

上記の問題を解決するために、C++ は std を拡張し、静的に変更されるクラスのメンバーとして count を使用できます。

1. 定義 

static として宣言されたクラス メンバーはクラスの static メンバーと呼ばれ、static で変更されたメンバー変数は static メンバー変数、static で変更されたメンバー関数は static メンバー関数と呼ばれます。

2. 特徴

  • 静的メンバーは、すべてのクラス オブジェクトで共有され、特定のオブジェクトに属さず、静的領域に格納されます。
  • 静的メンバー変数はクラス外で定義する必要があり、定義時に static キーワードは追加されず、クラス内で宣言されるだけです。
  • クラスの静的メンバーには、classname::static member または object.static member を使用してアクセスできます。
  • 静的メンバー関数にはこのポインターが隠されていないため、非静的メンバーにはアクセスできません。
  • 静的メンバーもクラスのメンバーであり、public、protected、および private のアクセス修飾子によって制限されます。
class A
{
public:
	A(int a = 0)
	{
		++count;
	}

	A(const A& aa)
	{
		++count;
	}
	int Getcount()
	{
		return count;
	}
private:
	static int count; // 此处为声明
	int _a = 0;
};
	
int A::count = 0; // 定义初始化

void func(A a)
{}

出力したいとき:

int main()
{
	A aa1;
	A aa2(aa1);
	func(aa1);
	A aa3 = 1;

	cout << A::Getcount() << endl;

	return 0;
}
出力ステートメントはエラーを報告します。
出力が必要な場合は、静的メンバー関数を使用できます。
	//静态成员函数 没有this指针
	static int Getcount()
	{
		// _a++; // 不能直接访问非静态成员
		return count;
	}

成功した出力:

静的メンバー関数は非静的メンバー関数を呼び出すことができますか?

答えは「いいえ」です。静的メンバー関数には暗黙的な this ポインターがないため、特定のオブジェクトの非静的メンバー関数または非静的メンバー変数にアクセスする方法はありません。非静的メンバー関数は、特定のオブジェクト インスタンス ( this ポインターを介して) に依存して、非静的メンバー変数および関数にアクセスして操作します。静的メンバー関数内では、非静的メンバーに直接アクセスすることはできません。

非静的メンバー関数はクラスの静的メンバー関数を呼び出すことができますか?

はい、非静的メンバー関数はクラスの静的メンバー関数を呼び出すことができます。非静的メンバー関数は、実行中にクラス名またはオブジェクト名を介して静的メンバー関数に直接アクセスできます。静的メンバー関数は特定のクラス インスタンスに依存しないため、任意のインスタンスまたはクラスから直接呼び出すことができます。

次のステートメントによって作成されるクラス オブジェクトの数は何ですか?
A aa4[10];

出力結果:

 

3. 質問例

リンクは次のとおりです。

1+2+3+...+n_Niuke の質問 Ba_Niuke.com (nowcoder.com)

  • 次のコードは、 Sum クラスと Solution クラスを実装します。 Sum クラスは、次の計算に使用されます。 1 から n までの累積合計。 Solution クラスは Sum クラスを使用して、指定された整数 n の累積合計を計算します。 
  • クラスのコンストラクタと静的メンバ変数の特性を利用して累積和の計算と取得を実装する設計です。
class Sum{
public:
    Sum()
    {
        _sum+=_i;
        _i++;
    }
    static int Getsum()
    {
        return _sum;
    }
private:
    static int _sum;
    static int _i;
};
int Sum::_sum = 0;
int Sum::_i = 1;
class Solution {
public:
    int Sum_Solution(int n) {
        Sum a[n];
        return Sum::Getsum();
    }
};

まず、 Sum クラスの実装を段階的に説明します。

  • Sum クラスには 2 つの静的メンバー変数 _sum と _i があり、それぞれ累積合計と現在のカウンター値を保存するために使用されます。
  • コンストラクタ Sum() はパラメータなしのコンストラクタです。呼び出されるたびに、現在のカウンタ値 _i を累積合計 に加算します。 a>  を 1 増やします。 _sum を実行し、カウンタ _i
  • 静的メンバー関数 Getsum() は、累積合計 _sum の値を取得するために使用されます。

次に、 Solution クラスの実装を見てみましょう。

  • Solution クラス Sum_Solution(int n) のメンバー関数は、整数 n をパラメータとして受け取り、1 から n までの累積合計を返します。
  •  Sum_Solution 関数では、  型、つまり配列のサイズである a という名前の配列を作成しました。 Sumn
  •  Sum クラスのコンストラクタはオブジェクトの作成時に自動的に呼び出されるため、配列 a の作成プロセス中に呼び出されます。 in sequence  Sum クラスのコンストラクターは、1 から n までの累積和の計算を実装します。
  • 最後に、 Sum::Getsum() 関数を呼び出して累積合計の値を取得し、それを関数の戻り値として使用します。

おすすめ

転載: blog.csdn.net/m0_73800602/article/details/134612286