[C++ 入門ガイド] クラスとオブジェクト (パート 1)


ここに画像の説明を挿入します


1. プロセス指向とオブジェクト指向についての予備理解

 C 言語はプロセス指向でありプロセス焦点を当て、問題を解決するための手順を分析し、関数呼び出しを通じて問題を徐々に解決します。
ここに画像の説明を挿入します


C++ はオブジェクト指向 に基づいておりオブジェクトに焦点を当てており 1 つのものをさまざまなオブジェクトに分割し、オブジェクト間の相互作用に依存します。
ここに画像の説明を挿入します


2. 授業の紹介

C言語の構造体では変数のみ定義できますが、C++では構造体をクラスにアップグレードすると、変数だけでなく関数も定義できるようになります。たとえば、データ構造の初期段階では、スタックが C 言語で実装されていたときは、構造体に変数のみを定義できましたが、 C ++ で実装されると、関数も構造体に定義できることがわかります。

Cpp コードのデモ:

struct Stack
{
    
    
	//函数
	void Init()
	{
    
    
		a = nullptr;
		capacity = top = 0;
	}

	void Push(int x)
	{
    
    
		a[top] = x;
		top++;
	}
	// ....其他函数
	
	//变量
	int* a;
	int top;
	int capacity;
};

しかし、CPP は構造体の代わりにクラスを使用することを好みます, クラスの使い方を見てみましょう(2つの違いについては後ほど紹介します)。


3. クラス定義

class ClassName
{
    
    
// 类体:由成员函数和成员变量组成
};  // 一定要注意后面的分号

クラスクラスを定義するためキーワードClassName はクラスの名前、{ } はクラスの本体です。ただし、クラス定義の最後のセミコロンは省略できないことに注意してください。
クラス本体の内容はクラスのメンバーと呼ばれ、クラス内の変数はクラスの属性またはメンバー変数と呼ばれ、クラス内の関数はクラスのメソッドまたはメンバー関数と呼ばれます

クラスを定義する 2 つの方法:

  1. すべての宣言と定義はクラス本体に配置されます。メンバー関数がクラスに定義されている場合、コンパイラはそれをインライン関数として扱う可能性があることに注意してください。
    ここに画像の説明を挿入します
  2. クラス宣言は .h ファイルに配置され、メンバー関数定義は .cpp ファイルに配置されます。 注: メンバー関数名の前に次の文字を付ける必要があります。クラス名::
    ここに画像の説明を挿入します

4. クラスアクセス修飾子とカプセル化

4.1 アクセス修飾子

C++ は通常、オブジェクトとメソッドを一緒にカプセル化し、選択的なアクセス許可を通じて外部ユーザーにインターフェイスを提供します。アクセス権限は次の 3 種類に分類されます。ここに画像の説明を挿入します
[アクセス修飾子の説明] :

  1. public によって変更されたメンバーはクラスの外部から直接アクセスできますが、protected および private によって変更されたメンバーはその逆です。(ここでの保護とプライベートは似ています)
  2. アクセス スコープは、このアクセス修飾子の出現で始まり、次のアクセス修飾子の出現で終了します。後でアクセス修飾子がない場合、スコープはクラスの終わりである } で終了します。
  3. デフォルトのアクセス権は、class は private で、struct は public です。(構造体がCと互換性がある必要があるため)

ヒント:アクセス修飾子はコンパイル時にのみ役立ち、データがメモリにマップされるときは、アクセス修飾子に違いはありません。

[典型的な面接の質問]: C++ の構造体とクラスの違いは何ですか?
回答: C++ は C 言語と互換性がある必要があるため、C++ の struct を構造体として使用できます。さらに、C++ の struct を使用してクラスを定義することもできます。class がクラスを定義するのと同じですが、struct で定義されたクラスのデフォルトのアクセス権限は public であり、class で定義されたクラスのデフォルトのアクセス権限は private である点が異なります。


4.2 梱包

カプセル化:データとデータを操作するメソッドを有機的に結合し、オブジェクトのプロパティと実装の詳細を非表示にし、オブジェクトと対話するインターフェイスのみを公開します。

カプセル化は本質的に、ユーザーがクラスを使いやすくする一種の管理です例: コンピューターのような複雑なデバイスの場合、ユーザーに提供されるのは電源オン/オフ ボタン、キーボード入力、モニター、USB ジャックなどだけであり、ユーザーはコンピューターを操作して日常のタスクを完了できます。しかし実際には、コンピュータの実際の働きは、CPU、グラフィックス カード、メモリ、その他のハードウェア コンポーネントです。
ここに画像の説明を挿入します
しかし、コンピュータ ユーザーは、マザーボード上の回路がどのようにレイアウトされているか、CPU がどのように設計されているかなど、内部コア コンポーネントについて心配する必要はありません。ユーザーが知っていればよいのは、コンピュータの電源を入れる方法と、キーボードとマウスを使用してコンピュータを操作する方法。そのため、コンピューター製造業者は工場から出荷されるとき、内部実装の詳細を隠すために外側にシェルを置き、ユーザーがコンピューターを操作できるように電源スイッチ、マウス、キーボード ジャックのみを外側に提供します


5. 授業の範囲

クラスは新しいスコープを定義し、クラスのすべてのメンバーはクラスのスコープ内にあります。クラス外のメンバーを定義する場合は、:: スコープ演算子を使用して、メンバーがどのクラス スコープに属するかを示す必要があります。(クラスは他のスコープとは異なります。コンパイルすると全体になります。変数を定義するときは、どこにでも指定できます。コンパイラーはクラスのスコープ内でグローバルに検索します。 )

[コードのデモ] :

class Date
{
    
    
public:
	void Print();

private:
	int _year;
	int _month;
	int _day;
};

//类体外定义成员函数
void Date::Print()
{
    
    
	cout << "_year" << "_month" << "_day" << endl;
}

6. クラスのインスタンス化

クラス型からオブジェクトを作成するプロセスは、クラスのインスタンス化と呼ばれます。

クラスはオブジェクトを記述します。それはモデルのようなものです。クラスのメンバーを制限します。クラスを定義しても、それを格納するための実際のメモリ領域は割り当てられませんただし、クラスは複数のオブジェクトをインスタンス化することができ、インスタンス化されたオブジェクトは実際の物理スペースを占有し、クラス メンバー変数を格納します

たとえを使ってみましょう。クラスからオブジェクトをインスタンス化することは、実際に家を建てるために建築設計図を使用することに似ています。クラスは設計図のようなものです。必要なものだけを設計しますが、物理的な建物はありません。同様に、クラスは単なる設計です。オブジェクトは実際にデータを保存し、物理スペースを占有することができます。
ここに画像の説明を挿入します


7. クラスオブジェクトモデル

7.1 クラスオブジェクトの格納規則

まずこのコードを見て、クラスとオブジェクトのサイズを調べてみましょう。

class A
{
    
    
public:
void PrintA()
{
    
    
   cout<<_a<<_i<<_d<<endl;
}

private:
char _a;
int _i;
double _d;
};

クラスにはメンバー変数とメンバー関数の両方を含めることができます。まず、メンバー変数と構造体の計算ルールは同じであると言っておきますが、メンバー関数についてはどうなるでしょうか?

各オブジェクトのメンバ変数は異なりますが、同じ関数が呼び出されるため、通常の関数として格納すると、クラスが複数のオブジェクトを作成したときに、各オブジェクトにコードのコピーが保存され、同じコードが呼び出されます。複数回保存すると、スペースの無駄が発生しますしたがって、C++ では、クラスのメンバー変数のみを保存し、メンバー関数はパブリック コード セクションに保存します
ここに画像の説明を挿入します

7.2 例

// 类中既有成员变量,又有成员函数
class A1 {
    
    
public:
    void f1(){
    
    }
private:
    int _a;
};

// 类中仅有成员函数
class A2 {
    
    
public:
   void f2() {
    
    }
};

// 类中什么都没有---空类
class A3
{
    
    };

検索: sizeof(A1) : ______ sizeof(A2) : ______ sizeof(A3) : ______ (結果: 4, 1, 1)

分析: A1 のサイズが 4byte であることは間違いありません。ただし、A2 のパブリック コード領域に関数が 1 つしかないことはカウントされず、A3 にはクラスがまったくありません。この特殊なケースでは、C++ はそのサイズを 1 バイト (プレースホルダー。データは保存されず、オブジェクトが存在したことを示すだけです

概要: クラスのサイズは、実際にはクラス内の「メンバー変数」の合計です。もちろん、メモリの配置には注意を払う必要があります。空クラスのサイズに注意してください。空クラスは特別です。コンパイラは、このクラスのオブジェクトを一意に識別するために空クラスに 1 バイトを与えます。(プレースホルダーを表します。データは保存されません。オブジェクトが存在したことを示すだけです。

7.3 構造体メモリのアライメント規則

  1. 最初のメンバーは構造体からのオフセット 0 にあります。
  2. 他のメンバ変数は、特定の数(アライメント番号)の整数倍のアドレスにアライメントされる必要があります。
    注: アライメント番号 = コンパイラのデフォルトのアライメント番号とメンバーのサイズの小さい方。
    VS のデフォルトのアライメント数は 8 です。
  3. 構造体の合計サイズは、最大アライメント数 (すべての変数タイプの最大値と最小のデフォルト アライメント パラメータ) の整数倍です。
  4. 構造体がネストされている場合、ネストされた構造体はそれ自体の最大アライメント数の整数倍にアライメントされ、構造全体のサイズはすべての最大アライメント数 (ネストされた構造体のアライメント数を含む) の整数倍になります。

[典型的な面接の質問] :

  1. 構造をどのように調整するか? なぜメモリの調整が必要なのでしょうか?
  2. 指定された位置合わせパラメータに従って構造を位置合わせするにはどうすればよいですか? 3、4、5、つまり任意のバイトに従って配置できますか?
  3. ビッグエンディアンとスモールエンディアンとは何ですか? 特定のマシンがビッグ エンディアンであるかリトル エンディアンであるかをテストするにはどうすればよいですか? ビッグ エンディアンまたはスモール エンディアンを考慮する必要があるシナリオに遭遇したことがありますか?

8. このポインタ

まず日付クラス Date を定義しましょう

class Date
{
    
    
public:
	void Init(int year, int month, int day)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}

	void Print()
	{
    
    
		cout << _year << "-" << _month << "-" << _day << endl;
	}

private:
	int _year; 
	int _month; 
	int _day; 
};

int main()
{
    
    
	Date d1, d2;
	d1.Init(2022, 1, 11);
	d2.Init(2022, 1, 12);
	d1.Print();
	d2.Print();
	return 0;
}


上記のクラスの場合、「Date クラスには Init と Print という 2 つのメンバー関数があります。関数本体では異なるオブジェクト間の区別がありません。したがって、d1 が Init 関数を呼び出すと、関数はどのように処理されるのでしょうか。」という問題がありますd2 オブジェクトを設定する代わりに、d1 オブジェクトを設定する必要があることを知っていますか?

C++ は、this ポインターを導入することでこの問題を解決します。つまり、C++ コンパイラーは、各「非静的メンバー関数」に隠しポインター パラメーターを追加し、ポインターが現在のオブジェクト (関数が呼び出されたときに関数を呼び出すオブジェクト) を指すようにします。関数本体内の「メンバー変数」に対するすべての操作は、このポインターを通じてアクセスされます。すべての操作がユーザーに対して透過的であるというだけです。つまり、ユーザーが操作を渡す必要はなく、コンパイラーが自動的に操作を完了します。

8.2 このポインタの特徴

  1. このポインターの型: クラスの型は const* です。つまり、メンバー関数では、このポインターに値を割り当てることはできません。
    2.このポインタは「メンバ関数」内でのみ使用できます。(実パラメータや仮パラメータの位置には表示・書き込みはできませんが、クラス内では表示・使用することができます。)
  2. this ポインタは本来「メンバ関数」の仮パラメータであり、オブジェクトがメンバ関数を呼び出す際に、オブジェクトのアドレスが実パラメータとして this パラメータに渡されます。したがって、this ポインタはオブジェクトに格納されません。
  3. this ポインタは、「メンバー関数」の最初の暗黙的なポインタ パラメータです。通常、これはコンパイラによって ecx レジスタを通じて自動的に渡され、ユーザーが渡す必要はありません。

ここに画像の説明を挿入します

8.3 典型的な面接の質問

面接での質問(1)

[面接の質問]:

  1. このポインタはどこにあるのでしょうか?
  2. このポインタは null であってもよいでしょうか?

回答:このポインタはスタック領域に存在します。これに単純に null を代入することはできず、強制的に直接 null を代入することもできますが、そのような操作は通常は実行されません。

面接での質問(2)

Tencent からのインタビューの質問を見てみましょう。

// 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
    
    
 void Print()
 {
    
    
 cout << "Print()" << endl;
 }
private:
 int _a;
};
int main()
{
    
    
 A* p = nullptr;
 p->Print();
 return 0;
}
//解析:本题主要在于P是否真的进行了空指针的解引用。
//     由于p指向的成员变量,但Print函数实际存在公共代码区(和全局变量类似)
//      所以实际上编译器是不会对p进行解引用。而是直接去符号表中查找Print函数地址。所以答案为C

// 2.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
    
     
public:
    void PrintA() 
   {
    
    
        cout<<_a<<endl;
   }
private:
 int _a;
};
int main()
{
    
    
    A* p = nullptr;
    p->PrintA();
    return 0;
}
//解析:这上面一样,编译器不会对p进行空指针的解引用,而是将p作为this指针传给PrintA函数。
//      而PrintA函数中,需要通过this(即p的形参)解引用指向_a,对空指针进行非法行为,运行崩溃

さて、このブログはこれで終わりますが、少しでもお役に立てれば幸いです。
ここに画像の説明を挿入します
ここに画像の説明を挿入します

おすすめ

転載: blog.csdn.net/Zhenyu_Coder/article/details/133362343