【C++】クラスとオブジェクト その1

目次

クラスとオブジェクトのパート 1:

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

                            2.授業の紹介

                            3. クラス定義

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

                            5. クラススコープ

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

                            7. クラスのオブジェクトのサイズの計算

                            8. クラスメンバ関数の this ポインタ


クラスとオブジェクトのパート 1:

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

C言語はプロセス指向であり、プロセスに焦点を当て、問題を解決するためのステップを分析し、関数呼び出しを通じて段階的に問題を解決します。

C++ はオブジェクト指向に基づいており、オブジェクトに焦点を当て、1 つのものを別のオブジェクトに分割し、オブジェクト間の相互作用に依存して完了します。

  

2.授業の紹介

C言語の構造体には変数しか定義できない.C++では構造体に変数だけでなく関数も定義できる.例えばC言語で実装されているスタックは構造体に変数しか定義できない.は C++ で実装されており、構造体でも関数を定義できることがわかりました。

typedef int DataType;
struct Stack
{
     void Init(size_t capacity)
     {
         _array = (DataType*)malloc(sizeof(DataType) * capacity);
         if (nullptr == _array)
         {
             perror("malloc申请空间失败");
             return;
         }
         _capacity = capacity;
         _size = 0;
     }
    void Push(const DataType& data)
    {
         // 扩容
         _array[_size] = data;
         ++_size;
    }
     DataType Top()
     {
         return _array[_size - 1];
     }
     void Destroy()
     {
         if (_array)
         {
             free(_array);
             _array = nullptr;
             _capacity = 0;
             _size = 0;
         }
     }
     DataType* _array;
     size_t _capacity;
     size_t _size;
};
int main()
{
     Stack s;
     s.Init(10);
     s.Push(1);
     s.Push(2);
     s.Push(3);
     cout << s.Top() << endl;
     s.Destroy();
     return 0;
}

上記の構造体の定義は、C++ では代わりにクラスを使用することをお勧めします。

3. クラス定義

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

class はクラスを定義するキーワード、className はクラスの名前、{} はクラスの本体ですが、クラス定義の末尾のセミコロンは省略できません。

クラス本体の内容はクラスのメンバーと呼ばれます。クラスの変数はクラスの属性またはメンバー変数と呼ばれ、クラスの関数はクラスのメソッドまたはメンバー関数と呼ばれます。

クラスを定義するには、次の 2 つの方法があります。

1. 宣言と定義はすべてクラス本体に置く 注: メンバ関数がクラスで定義されている場合、コンパイラはそれをインライン関数として扱う場合があります。したがって、一般に、小さくて頻繁に呼び出される関数はクラスに配置し、その他は .cpp ファイルに配置して、宣言と定義を分離します。

2. クラス宣言は .h ファイルに配置され、メンバー関数定義は .cpp ファイルに配置されます. メンバー関数名をクラス名に追加する必要があることに注意してください::

 一般に、2 番目のアプローチの方が望ましいと言えます。宣言と定義を分離する理由: 他の人がコードを読むのに便利であり、2 つ目は、静的ライブラリをインポートするときにソース コードの漏洩を防ぐためです。宣言が定義から分離されている場合、定義はクラス ドメイン (void Stack::Init(int N = 4)) を反映する必要があり、そのようなクラスのメンバーは、どのクラスであるかを指定するため、自由に使用できます。

メンバー関数の命名規則に関する提案:

class Date
{
public:
     void Init(int year)
     {
         _year = year;
     }
private:
     int _year;
};
// 或者这样
class Date
{
public:
     void Init(int year)
     {
         mYear = year;
     }
private:
     int mYear;
};
// 其他方式也可以的,主要看公司要求。一般都是加个前缀或者后缀标识区分就行

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

アクセス修飾子:

カプセル化を実現する C++ の方法: クラスを使用してオブジェクトのプロパティとメソッドを結合し、オブジェクトをより完全なものにし、アクセス権を通じて外部ユーザーにそのインターフェイスを選択的に提供します。

 アクセス修飾子の説明:

1. public によって変更されたメンバーは、クラスの外部から直接アクセスできます。

2. 保護された非公開の変更されたメンバーは、クラスの外部から直接アクセスできません。

3. アクセス権の範囲は、アクセス修飾子の出現位置から次のアクセス修飾子の出現位置までです。

4.後ろにアクセス修飾子がない場合、スコープは}、つまりクラスの最後に行きます。

5. クラスのデフォルトのアクセス許可はプライベートで、構造体はパブリックです (構造体は C と互換性があるため)。

注: 1. アクセス修飾子はコンパイル時にのみ有効で、データがメモリにマップされるときは、アクセス修飾子に違いはありません。

           2. アクセス修飾子は、クラス内ではなく、クラス外へのアクセスを制限します。

           3. C++ では、コンパイラがクラス全体を処理するため、変数や関数を上記で定義する必要はありません。

面接の質問:

質問: C++ の構造体とクラスの違いは何ですか?

回答: C++ は C 言語と互換性がある必要があるため、C++ の struct を構造体として使用できます。さらに、C++ の struct を使用してクラスを定義することもできます。クラス定義 class と同じですが、違いは struct で定義されたクラスのデフォルトのアクセス権が public であり、class で定義されたクラスのデフォルトのアクセス権が private であるということです。注: 構造体とクラスには、継承とテンプレート パラメーター リストの位置にも違いがあります。

カプセル化

面接の質問:

オブジェクト指向の 3 つの主要な特徴は、カプセル化、継承、ポリモーフィズムです。

クラスとオブジェクトのフェーズでは、主にクラスのカプセル化の特性を調べます。

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

カプセル化とは、基本的に、ユーザーがクラスを使いやすくするための管理です。例: コンピューターなどの複雑なデバイスの場合、ユーザーに提供される唯一のものは電源ボタンであり、キーボード入力、モニター、USB ジャックなどを介して、ユーザーはコンピューターと対話し、日常業務を完了することができます。 ., しかし、実際にコンピュータが実際に動作するのは、CPU、グラフィックス カード、メモリなどの一部のハードウェア コンポーネントです。

  

コンピュータ ユーザーは、マザーボード上の回路がどのように配置されているか、CPU が内部でどのように設計されているかなど、内部のコア コンポーネントについて気にする必要はありません。キーボードとマウスを介してコンピュータと対話します。したがって、コンピューターの製造元が工場を出荷するとき、内部の実装の詳細を隠すために外側にシェルを配置し、ユーザーがコンピューターとやり取りできるように、スイッチ、マウス、およびキーボードのジャックのみを外側に提供します。

C++ 言語でカプセル化を実現するには、データとデータを操作するメソッドをクラスによって有機的に結合し、オブジェクトの内部実装の詳細をアクセス権によって隠し、制御メソッドをクラスの外部で直接使用できます。

5. クラススコープ

クラスは新しいスコープを定義し、クラスのすべてのメンバーはクラスのスコープ内にあります。クラス外でメンバーを定義する場合、 :: スコープ演算子を使用して、メンバーが属するクラス ドメインを示す必要があります。

class Person
{
public:
     void PrintPersonInfo();
private:
     char _name[20];
     char _gender[3];
     int  _age;
};
// 这里需要指定PrintPersonInfo是属于Person这个类域
void Person::PrintPersonInfo()
{
     cout << _name << " "<< _gender << " " << _age << endl;
}

注: クラスは構造体と同じです. クラスはその中に型が定義されており, 宣言されているだけです. オブジェクトが定義されている場合 (st.a) にのみアクセスできます. したがって, :: を使用してアクセスすることはできません.クラスのメンバー (Stack::a) ですが、このポインター(Stack::Print)にはオブジェクト アドレスが渡されないため、クラス内のメンバー関数へのアクセスには使用できません。

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

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

1. クラスはオブジェクトの記述であり、モデルのようなもので、クラスのメンバーを制限しますが、クラスを格納するための実際のメモリ空間を割り当てずにクラスを定義します。

2. クラスは複数のオブジェクトをインスタンス化でき、インスタンス化されたオブジェクトは実際の物理空間を占有し、クラス メンバー変数を格納します。Person クラスにはスペースがなく、Person クラスからインスタンス化されたオブジェクトのみが特定のスペースを持ちます。

int main()
{
     Person._age = 100;   // 编译失败:error C2059: 语法错误:“.”
     return 0;
}
3. 類推すると、クラスからオブジェクトをインスタンス化することは、実際に建築設計図を使用して家を建てるようなものです. クラスは設計図のようなものです. 必要なものだけを設計しますが、物理的な建物はありません. 同じクラス単なる設計であるため、インスタンス化されたオブジェクトは実際のデータを格納し、物理スペースを占有できます。

 

7. クラスのオブジェクトのサイズの計算

クラス オブジェクトのサイズを計算する方法:

class A
{
public:
    void PrintA()
    {
       cout<<_a<<endl;
    }
private:
    char _a;
};

質問: クラスはメンバー変数とメンバー関数の両方を持つことができるので、クラス オブジェクトには何が含まれますか? クラスのサイズを計算する方法は?

クラス オブジェクトがどのように格納されているかを推測します。

1. オブジェクト ストレージには、クラスのさまざまなメンバーが含まれます。

欠点:各オブジェクトのメンバ変数が違うのに、同じ関数が呼び出される このように格納すると、1 つのクラス ストレージに複数のオブジェクトを作成すると、各オブジェクトがコードのコピーを保存し、同じコードが保存されます。複数回保存. スペースの無駄. どうすれば解決できますか?
2. コードのコピーは 1 つだけ保存され、コードが保存されているアドレスはオブジェクトに保存されます。

 3. メンバー変数のみが保存され、メンバー関数はパブリック コード セグメントに格納されます。

結論: 1. 実際には、上記の 3 つの保存方法の場合、コンピューターは 3 番目の保存方法に従って保存します。

           2. クラスのサイズは、実際にはクラス内のメンバ変数の合計です. もちろん、メモリ アラインメントには注意が必要です。

           3. 空のクラスのサイズは 1 バイトです。場所を占有し、有効なデータを格納せず、オブジェクトが存在することを示します。

// 类中既有成员变量,又有成员函数
class A1 {
public:
    void f1()
    {}
private:
    int _a;
};
// 类中仅有成员函数
class A2 {
public:
   void f2() 
   {}
};
// 类中什么都没有---空类
class A3
{};

面接の質問

1.構造を整列させる方法は?なぜメモリアライメント?

2. 指定された配置パラメーターに従って構造を配置する方法は? 3、4、5、つまり任意のバイト数でアラインできますか?

3. ビッグエンディアンとスモールエンディアンとは?マシンがビッグ エンディアンかリトルエンディアンかをテストする方法ビッグ エンディアンまたはスモール エンディアンを考慮する必要があるシナリオに遭遇したことがありますか?

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

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

8. クラスメンバ関数の this ポインタ

このポインターの導入:

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 つのメンバー関数があり、関数本体には異なるオブジェクト間の区別はありません。したがって、di が Init 関数を呼び出すとき、関数は d2 の代わりに di オブジェクトを設定する必要があることをどのように認識しますか?物体?

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

注:このポインターの定義と転送はコンパイラーの仕事です。干渉することはできませんが、クラスでこのポインターを使用することはできます。

このポインターの特徴:
1. this ポインターの型: クラスの型 * const、つまり、メンバー関数では、this ポインターに値を割り当てることはできません。
2.メンバー関数内でのみ使用可能
3. this ポインタは本質的にメンバ関数の仮パラメータであり、オブジェクトがメンバ関数を呼び出すと、オブジェクトのアドレスが実パラメータとして this 仮パラメータに渡されます。したがって、 this ポインターはオブジェクトに格納されません。
4. this ポインターは、メンバー関数の最初の暗黙的なポインター パラメーターであり、通常、ecx レジスターを介してコンパイラーによって自動的に送信され、ユーザーが渡す必要はありません。

 面接の質問:

1. このポインターはどこにありますか?

this ポインターは正式なパラメーターとして使用され、通常はスタック フレームに存在します。(VS は以下で最適化され、ecx レジスターを使用して渡されます)

2. このポインターを空にすることはできますか?

this ポインターを空にすることはできず、逆参照するとエラーが報告されます。

// 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:
     void Print()
     {
         cout << "Print()" << endl;
     }
private:
     int _a;
};
int main()
{
     A* p = nullptr;
     p->Print();
     return 0;
}
答案:C
//不发生过解引用,因为成员函数的地址不在对象中,在公共代码区域 Print(p) -> void Print(A* const this),传递一个指针不会报错
// 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{ 
public:
    void PrintA() 
   {
        cout << _a << endl;
   }
private:
     int _a;
};
int main()
{
    A* p = nullptr;
    p->PrintA();
    return 0;
}
答案:B
//cout<<this->_a 空指针解引用 程序运行崩溃 成员变量在对象里面,要通过this指针去访问

C 言語と C++ のスタックの比較:

1. C 言語の実装:

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int DataType;
typedef struct Stack
{
	DataType* array;
	int capacity;
	int size;
}Stack;
void StackInit(Stack* ps)
{
	assert(ps);
	ps->array = (DataType*)malloc(sizeof(DataType) * 3);
	if (NULL == ps->array)
	{
		assert(0);
		return;
	}
		ps->capacity = 3;
	ps->size = 0;
}
void StackDestroy(Stack* ps)
{
	assert(ps);
	if (ps->array)
	{
		free(ps->array);
		ps->array = NULL;
		ps->capacity = 0;
		ps->size = 0;
	}
}
void CheckCapacity(Stack* ps)
{
	if (ps->size == ps->capacity)
	{
		int newcapacity = ps->capacity * 2;
		DataType* temp = (DataType*)realloc(ps->array,
			newcapacity * sizeof(DataType));
		if (temp == NULL)
		{
			perror("realloc申请空间失败!!!");
			return;
		}
		ps->array = temp;
		ps->capacity = newcapacity;
	}
}
void StackPush(Stack* ps, DataType data)
{
	assert(ps);
	CheckCapacity(ps);
	ps->array[ps->size] = data;
	ps->size++;
}
int StackEmpty(Stack* ps)
{
	assert(ps);
	return 0 == ps->size;
}
void StackPop(Stack* ps)
{
	if (StackEmpty(ps))
		return;
	ps->size--;
}
DataType StackTop(Stack* ps)
{
	assert(!StackEmpty(ps));
	return ps->array[ps->size - 1];	
}
int StackSize(Stack* ps)
{
	assert(ps);
	return ps->size;
}

C 言語で実装した場合、スタック関連の操作関数には次のような共通点があることがわかります。

1. 各関数の最初のパラメーターは Stack* です。

2. パラメータが NULL の可能性があるため、最初のパラメータを関数で検出する必要があります。

3. 関数では、スタックは Stack* パラメーターを介して操作されます。

4. 呼び出し時に Stack 構造体変数のアドレスを渡す必要があります。

注:データを格納するための構造のみを構造に定義でき、データを操作する方法を構造に配置することはできません。つまり、データとデータを操作する方法は分離されており、実装は非常に複雑です。ポインタ操作が多いので、注意しないと間違える可能性があります。

2. C++ の実装

typedef int DataType;
class Stack
{
public:
	void Init()
	{
		_array = (DataType*)malloc(sizeof(DataType) * 3);
		if (NULL == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}
		_capacity = 3;
		_size = 0;
	}
	void Push(DataType data)
	{
		CheckCapacity();
		_array[_size] = data;
		_size++;
	}
	void Pop()
	{
		if (Empty())
			return;
		_size--;
	}
	DataType Top() { return _array[_size - 1]; }
	int Empty() { return 0 == _size; }
	int Size() { return _size; }
	void Destroy()
	{
		if (_array)
		{
			free(_array);
			_array = NULL;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	void CheckCapacity()
	{
		if (_size == _capacity)
		{
			int newcapacity = _capacity * 2;
			DataType* temp = (DataType*)realloc(_array, newcapacity *
				sizeof(DataType));
			if (temp == NULL)
			{
				perror("realloc申请空间失败!!!");
				return;
			}
			_array = temp;
			_capacity = newcapacity;
		}
	}
private:
	DataType* _array;
	int _capacity;
	int _size;
};
int main()
{
	Stack s;
	s.Init();
	s.Push(1);
	s.Push(2);
	s.Push(3);
	s.Push(4);

	printf("%d\n", s.Top());
	printf("%d\n", s.Size());
	s.Pop();
	s.Pop();
	printf("%d\n", s.Top());
	printf("%d\n", s.Size());
	s.Destroy();
	return 0;
}

C++ では、データとデータを操作するメソッドをクラスで完全に組み合わせることができ、アクセス権を使用して、クラスの外部でどのメソッドを呼び出すことができるかを制御することができます。物事の認識、および各メソッドが Stack* パラメーターを渡す必要がない場合、パラメーターはコンパイラーのコンパイル後に自動的に復元されます。つまり、C++ の Stack* パラメーターはコンパイラーによって維持されます。 、および C 言語はユーザーが保守する必要があります。

おすすめ

転載: blog.csdn.net/qq_66767938/article/details/129935855