目次
- 序文
- 1. 断片的な知識ポイント
- 2. C++ コア
-
- 2.1. メモリパーティション
- 2.2. 参考文献
- 2.3. 機能
- 2.4. クラスとオブジェクト
- 2.5. 継承
- 2.6. ポリモーフィズム
- 2.7. ドキュメント
序文
この学習ノートは個人的な学習をまとめたものです。比較的簡単で一般的な知識事項は含まれていません。主に学習過程での疑問点の補足と説明を目的としています。このノートは参考と学習のみを目的としており、チュートリアルには適していません。
1. 断片的な知識ポイント
定数:#define
およびconst
、プログラムの実行中に定数を変更することはできません。
1. マクロ定数は文字列ですか?
回答: その機能は、#define コンパイラ前処理指令で識別子と文字列を定義し、ソース プログラム内で識別子が見つかった場合に、指定された文字列に置き換えます。
2. 識別子では大文字と小文字が区別されるため、名前で意味を知ることが最善です。
デフォルトでは、小数は倍精度として扱われ、単精度に f が追加されます。デフォルトで小数を出力する場合、有効数字は 6 桁になります (単精度と倍精度の両方)。
科学表記法: 1e-3、1e-6
文字列:
C スタイル:
C++ スタイル:使用する際に注意が必要な構造には、追加する場合と追加しない場合の影響、追加する場合と追加しない場合の
if
影響の 3 種類があります。乱数の生成:
switch
break
default
int num = rand()% 100 + 1: // rand()%100 +1生成 0+1 99 +1 随机数
ランダムシード
//添加随机数种子 作用利用当前系统时间生成随机数,防止每次随机数都一样
srand((unsigned int)time (NULL)):
switch
整数型と文字型は実際にはデータ型として理解できるため、整数型または文字型のみにすることができますが、占有されるバイト数が異なります。
Jump ステートメントのbreak
ステートメント:
機能: 選択構造または
break
ループ構造のタイミングからジャンプするために使用されます。
- switch 条件ステートメントに表示され、その機能はケースを終了し、switch から飛び出すことです。
- ループ ステートメント内で使用され、現在のループ ステートメントから飛び出す機能があります。
- ネストされたループ内に表示され、内側のループ ステートメント
最近
から飛び出します。
ループと選択での Break の混合使用に注意してください。break はそれ自体に関連するステートメントに対してのみ機能します。
goto //跳转到某一句
C++ のトップダウン操作構造を破壊するため、使用はお勧めできません。
goto FLAG;//直接跳转到下面的FLAG:后面的语句
cout << "3"<< endl;
cout << "4" << endl;
FLAG:
cout << "4" << endl;
system("pause") ;
配列:
次元配列を定義するには 3 つの方法があります。
1. データ型配列名[配列長];
2. データ型配列名[配列長] = {値1, 値2...};
3. データ型配列名[] = {値1, 値2...};
配列の特徴: 配列内の各要素は、同じデータ型の連続したメモリ空間に配置されます
配列の長さは変数によって決定できません
1 次元配列名の目的
1. メモリ内の配列全体の長さをカウントできます。
2. メモリ内の配列の最初のアドレスを取得できます。
配列名は定数
バブルソートです。
1. 隣接する要素を比較します。最初のものが 2 番目のものより大きい場合は、両方を交換します。
2. 隣接する要素の各ペアに対して同じ作業を実行し、実行後に最初の最大値を見つけます。
3. 比較が必要なくなるまで、比較の数 -1 するたびに上記の手順を繰り返します。
2次元配列:列数は省略できません
1. データ型配列名 [行番号] [列番号];
2. データ型配列名 [行番号] [列番号]={ { { データ 1, データ 2, データ 3}, {データ 4, データ 5,データ 6}}
3. データ型配列名[行][列]={データ 1、データ 2、データ 3、データ 4、データ 5、データ 6} 4. データ型配列名[][列]= {
データ1、データ 2、データ 3、データ 4、データ 5、データ 6} コンパイラは行数を自動的にキャプチャできます
二次元配列名:
1. 占有メモリ空間のサイズを表示できます
2. 2 次元配列の先頭アドレスを表示できます
関数:
関数の定義には通常 5 つのステップがあります
1. 戻り値の型
2. 関数名
3. パラメータリスト
4. 関数本体の文
5. 戻り値の式
値による受け渡し:
正式なパラメータの変更は実際のパラメータには影響せず、
void
戻り値を追加できません
4 つの機能形式:
1. 参加なし、返却なし
2. 参加あり、返却なし
3. 参加なし、返却なし
4. 参加あり、返却なし
関数宣言:
関数が事前に存在することをコンパイラーに伝えます。コンパイラーはそれを事前に確認する必要があります。宣言は複数回行うことができますが、定義は 1 回だけ行うことができます。
関数ファイルの書き込み:
関数ファイルの書き込みには通常、次の 4 つの手順があります。
1.サフィックス名
.h
を付けたヘッダファイルを作成 2.
サフィックス名.cpp
を付けたソースファイルを
作成 3. ヘッダファイルに関数の宣言を記述する 通常、ソースファイルで使用するライブラリもここに記述します例iostream
:using namespace std;
4. ソースファイルに関数の定義を記述します。
上記の手順を実行するだけでは十分ではありません。作成したヘッダー ファイルの価格名を含めるために、ソース ファイルにカスタム ヘッダー ファイルを含める必要もあります。この手順の目的は、カスタム ヘッダーを含めることです。ファイル ソース ファイルに関連して、ソース ファイル内で必要な一部の宣言も、ソース ファイルではなくヘッダー ファイルに記述されます。使用する場合は、使用する場所の先頭に使用するファイルのヘッダーファイルを追加するだけです。
メインプログラムが今定義した関数を呼び出したい場合は、ヘッダファイルを直接インポートすることで直接使用できます。
ポインタ:
通常は 4 バイトを占め、各コンパイラで同じスペースを占有し、データ型とは関係ありません。
Null ポインタとワイルド ポインタ
Null ポインタ: ポインタ変数はメモリ番号 0 の空間を指します。
目的: ポインタ変数を初期化します
。 注: Null ポインタが指すメモリにはアクセスできません。0 ~ 255 のメモリ番号はシステムによって占有されているため、アクセスできません
//空指针
//1、空指针用于给指针变量进行初始化
int *p = NULL;
//2、空指针是不可以进行访问的,企图让p指向的内存填上数字100,是不可以的
*p = 100;
メモリ内の 0 ~ 255 の数値がシステムによって占有されているため、ヌル ポインタにアクセスできません。
ワイルド ポインター:
ポインター変数が不正なメモリ領域を指しています。
int main(
//野指针,随便让他们指向一个内存编号
//在程序中,尽量避免出现野指针
int *p = (int *)0x1100;
//访问野指针报错
cout << *p << end1;
system("pause");
return 0;
この部分については、次を参照してください: C++ ワイルド ポインターの概要
const
ポインターを装飾すると、
const
ポインターが渡されたときに値が変更されないようにすることができます。
const
ポインターを変更するには 3 つのケースがあります。
1. const 変更されたポインタ - 定数ポインタ
2. const 変更された定数 - ポインタ定数
3. const は変更されたポインタだけでなく、変更された定数も
const の背後に誰がいるかわかりますか?
定数ポインタ:
const int * p = &a;
ポインタの指す位置は変更できますが、ポインタが指す値を
ポインタ定数として変更することはできません。
int * const p = &a;
ポインターの指す位置は変更できませんが、指す値は変更できます。
const とは、ポインターを変更し、定数を変更することを意味します。
const int * const p = &a;
ポインタの指す位置も、ポインタが指す値も変更できません。
記憶方法:定数に直接
変換し、それをポインタに変換して、ポインタ定数と定数ポインタを明確に区別します。ポインタまたは指をたどる者は、変更したり操作したりすることはできません。const
"*"
const
"*"
ヒント:
const
右側のものがポインタか定数かを確認してください。ポインタの場合は定数ポインタ、定数の場合はポインタ定数です。
構造:
構文:
struct 结构体名 {
结构体成员列表 };
構造体を通じて変数を作成するには、次の 3 つの方法があります。
- struct 構造体名 変数スター名。
- struct 構造体名 変数名 = {メンバー 1 の値、メンバー 2 の値...}。
- ちなみに変数は構造体を定義するときに作成します。
注: C++ では構造体変数を作成するときに省略できますstruct
が、C では省略できません。
要約:
1: 構造体を定義するときのキーワードは struct であり、省略できません
2: 構造体を作成するときのキーワード struct は省略できます
3: 構造体変数は演算子 ".
" を使用してメンバーにアクセスします
構造体ポインタ:
演算子 -> を使用して、構造体ポインターを通じて構造体のプロパティにアクセスします。
上記のコードには説明を忘れた文が 2 つあります。
system("pause")//按任意键退出系统
system("cls")//清屏
三項演算は出力ステートメントに配置できます。
switch case:
case の後に複合ステートメントが続く場合は、中括弧で囲む必要があります。
配列削除のアイデア:
削除するラベルを見つけると、次のデータが全体として前に進みます。
2. C++ コア
2.1. メモリパーティション
メモリ パーティション モデル:
ここは非常に重要、非常に重要、非常に重要です。
C++ プログラムが実行されると、メモリの一般的な方向が4
領域に分割されます。
- コード領域:オペレーティング システムによって管理される関数本体のバイナリ コードを格納します。
- グローバル領域:グローバル変数、静的変数、定数を格納します。
- スタック領域:コンパイラによって自動的に割り当ておよび解放され、関数のパラメータ値、ローカル変数などが格納されます。
- ヒープ領域:プログラマによって割り当ておよび解放されます。プログラマが解放しない場合、プログラムの終了時にオペレーティング システムによって再利用されます。
4 つのメモリ領域の意味:
さまざまな領域に保存されたデータにはさまざまなライフサイクルが与えられ、より柔軟なプログラミングが可能になります。
プログラムの実行前:プログラムのコンパイル後、実行可能プログラムが
生成されます。プログラムの実行前は、コード領域とグローバル エリアコード領域の2 つの領域に分割されます。exe
CPU が実行する機械命令を格納します
コード領域は共有されます 共有の目的は、頻繁に実行されるプログラムの場合、メモリ上にコードのコピーが 1 つだけ必要になることです コード
領域は読み取り専用です作成理由読み取り専用です。プログラムが誤って命令を変更するのを防ぎます。
大域ゾーン:
グローバル変数と静的変数はここに格納されます。
グローバル領域には定数領域も含まれており、文字列定数やその他の定数もここに格納されます
。この領域のデータは、プログラム終了後にオペレーティング システムによって解放されます。。
定数:
文字列定数の
const
装飾。グローバルとローカルに分けられます。
概要: C++ は
プログラムが実行される前にグローバル領域とコード領域に分割されます。
コード領域は共有と読み取り専用の特徴があり、
グローバル変数、静的変数、定数は
グローバル領域に格納され、const
変更されたグローバル定数と文字列定数は定数領域に格納され、constで変更されたローカル変数は配置されません。グローバルエリア。マクロ定数はヒープ領域やグローバル領域に割り当てられるのではなく、前処理フェーズ中に定義された定数値に置き換えられるだけです。したがって、マクロ定数はコード内にのみ存在し、プログラム実行時にはメモリ空間が確保されませんので、以下の点に注意してください。
プログラム実行後:
スタック領域:
コンパイラによって自動的に割り当ておよび解放され、関数のパラメータ値やローカル変数などが保存されます。
注:ローカル変数のアドレスを返さないでください。次の例に示すように、スタック領域に作成されたデータはコンパイラによって自動的に解放されます。
int* func()
{
int a = 10;//局部变量 存放在栈区,栈区的数据在函数执行完后自动释放
return &a; //返回局部变量的地址
}
int main() {
//接受func函数的返回值
int *p = func();
cout << *p << endl; //第一次可以打印正确的数字,是因为编译器做了一次保留,只做一次保留;
cout << *p << endl; //第二次这个数据就不再保留了,此时相当于野指针
system(pause");
return 0;
}
ヒープ領域:
プログラマによって割り当ておよび解放されます。プログラマが解放しない場合、プログラムの終了時にオペレーティング システムによって回復されます。C++ では、主にヒープ領域のメモリを解放する
ために使用されます。new
int *func()
{
//利用new关键字 可以将数据开辟到堆区
//指针本质上也是局部变量,放在栈上,指针保存的数据是放在堆区
int *p = new int(10); //返回的也是地址
return p;
}
int main() {
//在堆区开辟数据
int *p = func();
cout << *p << endl;
//只要不手动释放,在程序退出前可以一直输出
cout << *p << endl;
cout << *p << endl;
cout << *p << endl;
system(pause");
return 0;
}
新しい演算子:
C++ では、new 演算子を使用してヒープ領域にデータを展開します。ヒープ領域に展開されたデータはプログラマによって手動で開発され、手動でリリースされます。リリースでは演算子が使用されます。構文: 、ポインタによって作成されたデータを new を使用して返します。 、ポインタのデータ型に
delete
対応するデータを返します。new 数据类型
void test010()
{
int * p = func();
cout << *p <<endl;
cout << *p <<endl;
cout << *p <<endl;
//堆区的数据 由程序员管理开辟,程序员管理释放
//如果想释放堆区的数据,利用关键字 delete
delete p; //释放p指向的内容
//报错,读取访问权限冲突,此时再访问就是野指针
cout << *p <<endl;
}
ヒープ内に配列を作成します。
void test02(){
//创建10整型数据的数组,在堆区
int * arr = new int[10]: //10代表数组有10个元素,返回数组的首地址
}
//释放堆区数组
//释放数组的时候 要加[]才可以
delete[] arr;
2.2. 参考文献
参照
エイリアスは変数に与えられ、同じ記憶空間内で一方が変更されると、他方も変更されます。
基本的な構文を引用するには、次のようにします。
データ型とエイリアス = 元の名前
知らせ:
- 参照する必要があります初期化、起動時にコンパイラにそれが誰のエイリアスであるかを伝える必要があります
- 参照を初期化した後は変更できません。変数のエイリアスのみにすることができます、 初めから最後まで
int a = 10;
//1、引用必须初始化//int &b; 错误,必须要初始化
int &b = a;
//2、引用在初始化后,不可以改变
int c = 20;
//赋值操作,而不是更改引用,相当于把c的值赋给了a和b,a和b完全等价,最终a,b,c三个值都是20.
b = c;
関数の引数として引用されます。
機能: 関数でパラメータを渡すときに、参照手法を使用して仮パラメータで実パラメータを変更できる
利点: 実パラメータを変更するためのポインタを簡素化できる
知らせ:
関数パラメータとして参照を使用する場合、仮パラメータと実パラメータは同じメモリ空間を共有します。つまり、実パラメータの値がそのまま仮パラメータに渡され、関数内での仮パラメータの変更がそのまま実パラメータに反映されます。
要約:
パラメータを参照で渡すことは、アドレスで渡すことと同じ効果があります。引用の構文がより明確かつシンプルになりました。
関数の戻り値としての参照:
関数の戻り値として参照が存在できます。
知らせ:
1. ローカル変数参照を返さない、 上記のようにローカル変数のアドレスを返さない理由はあります。
2. 左辺値としての関数呼び出し
//返回局部变量引用,会引起意想不到的错误,相当于野指针
int& test01() {
int a = 10; //局部变量
return a;
}
//返回静态变量引用
int& test02() {
static int a = 20; //静态变量,存放在全局区,程序运行完才释放
return a;
}
int main() {
//不能返回局部变量的引用。这里说明一下,函数返回的是引用,接受的时候可以用引用也可以不用引用,
//取决于是否需要对返回值进行修改。
int& ref = test01();
//第一次正确,编译器做了一次保留
cout << "ref = " << ref << endl;
//第二次错误,因为a的内存已释放
cout << "ref = " << ref << endl;
//如果函数做左值,那么必须返回引用
//上面声明的是静态的,这里可以无限次使用
int& ref2 = test02();
//下面两句都能正常输出
cout << "ref2 = " << ref2 << endl;
cout << "ref2 = " << ref2 << endl;
//如果函数的返回值是引用,这个函数的调用可以作为左值
test02() = 1000;
cout << "ref2 = " << ref2 << endl;
cout << "ref2 = " << ref2 << endl;
system("pause");
return 0;
}
上記のコードをもう一度説明します。
引用の性質:
参照の本質は、C++ 内でポインタ定数
C++
構文が便利であり、参照の本質はポインター定数であるため、参照テクノロジーを使用することをお勧めしますが、すべてのポインター操作はコンパイラーによって行われます。
//发现是引用,转换为 int* const ref = &a;
void func(int& ref){
ref = 100; // ref是引用,转换为*ref = 100
}
int main(){
int a = 10;
//自动转换为 int* const ref = &a; 指针常量是指针指向不可改,也说明为什么引用不可更改
int& ref = a;
ref = 20; //内部发现ref是引用,自动帮我们转换为: *ref = 20;
cout << "a:" << a << endl;
cout << "ref:" << ref << endl;
func(a);
return 0;
}
上記の例では、 ref は内部で参照であることが検出され、次の形式に自動的に変換されます: *ref = 20
常に参照:
誤用を防ぐために仮パラメータを変更し、C++
標準記述では可能なconst
限りその変更を使用しますconst
。
参照は正当なメモリ空間を参照する必要があります。
int a = 10:
int & ref = 10;//引用必须引一块合法的内存空间(栈区或者堆区的数据),这里会报错,因为10是放在常量区的
//引用使用的场景,通常用来修饰形参
void showValue(const int& v) {
//v += 10;
cout << v << endl;
}
int main() {
//int& ref = 10; 引用本身需要一个合法的内存空间,因此这行错误
/*加入const就可以了,编译器将代码修改为:int temp = 10; const int& ref = temp;
创建了一个临时变量就可以引用了,
其实这里引用的是个中间变量,我们找不到他的原名,原名是编译器起的,我们只能通过别名操作。*/
const int& ref = 10; //加入const之后变为只读,不可修改
//ref = 100; //加入const后不可以修改变量
cout << ref << endl;
//函数中利用常量引用防止误操作修改实参
int a = 10;
showValue(a);
system("pause");
return 0;
}
定数参照は通常、仮パラメータで記述されます。
2.3. 機能
関数のデフォルトのパラメータは次のとおりです。
データが渡される場合は独自のデータを使用し、そうでない場合はデフォルトを使用します。
特定の位置にデフォルト パラメータがすでに存在する場合は、この位置から始まるデフォルト パラメータが存在する必要があります。つまり、デフォルト パラメータは後ろに配置される必要があります。
関数の宣言にデフォルト パラメーターがある場合、関数の実装にはデフォルト パラメーターを含めることができません。または、宣言と実装のいずれか 1 つだけがデフォルト パラメーターを持つことができます。
関数のプレースホルダー パラメーター:
構文: 返回值类型 函数名 (数据类型){}
パラメーターを渡す場合、プレースホルダーにもパラメーターを入力する必要があります。プレースホルダー パラメーターには、後で使用されるデフォルト パラメーターを含めることもできます。デフォルト パラメーターがある場合、パラメーターを渡す必要はありません。
//函数占位参数 ,占位参数也可以有默认参数
void func(int a, int) {
cout << "this is func" << endl;
}
int main() {
func(10,10); //占位参数必须填补,否者报错
system("pause");
return 0;
}
関数のオーバーロード:
関数名は同じであってもかまいません。
オーバーロード条件: 3 つの条件が同時に満たされます。重要なのは、パラメーターが異なる必要があることです。
- 同じスコープ内、つまり呼び出したい関数が同じ場所から来ている
- 同じ関数名
- 関数パラメータ他の種類また番号が違いますまた順番が違う
注:
関数の戻り値をオーバーロードの条件として使用することはできません。
//函数重载需要函数都在同一个作用域下
void func()
{
cout << "func 的调用!" << endl;
}
void func(int a)
{
cout << "func (int a) 的调用!" << endl;
}
void func(double a)
{
cout << "func (double a)的调用!" << endl;
}
void func(int a ,double b)
{
cout << "func (int a ,double b) 的调用!" << endl;
}
void func(double a ,int b)
{
cout << "func (double a ,int b)的调用!" << endl;
}
//函数返回值不可以作为函数重载条件
//int func(double a, int b)
//{
// cout << "func (double a ,int b)的调用!" << endl;
//}
int main() {
func();
func(10);
func(3.14);
func(10,3.14);
func(3.14 , 10);
system("pause");
return 0;
}
関数のオーバーロードに関する注意:
- 参照はオーバーロード条件として使用できます
- 関数のオーバーロードで関数のデフォルト パラメーターが発生する
例:
//函数重载注意事项
//1、引用作为重载条件
void func(int &a)
{
cout << "func (int &a) 调用 " << endl;
}
void func(const int &a)
{
cout << "func (const int &a) 调用 " << endl;
}
//2、函数重载碰到函数默认参数
void func2(int a, int b = 10)
{
cout << "func2(int a, int b = 10) 调用" << endl;
}
void func2(int a)
{
cout << "func2(int a) 调用" << endl;
}
int main() {
int a = 10;
func(a); //调用无const
func(10);//调用有const,并且int& a = 10也不合法,但是const int& a = 10,相当于创建了一个临时的变量
//func2(10); //碰到默认参数产生歧义,不知道调用哪个了,需要避免这种情况。有函数重载的时候尽量不要使用默认参数。这里传两个参数可以。
system("pause");
return 0;
}
関数のオーバーロードの核心:関数を呼び出すときにあいまいさがあってはなりません。
2.4. クラスとオブジェクト
C++
オブジェクト指向の最も重要な内容:カプセル化,継承する,多態性。カプセル化から始めましょう。
パッケージ:
カプセル化は、C++
オブジェクト指向の 3 つの主要な特徴の 1 つです
。カプセル化の意味は次のとおりです。
- 人生における事柄を表す、全体としての属性と行動
- 権限を使用して属性と動作を制御する
パッケージの意味:
クラスを設計するときは、属性と動作を一緒に記述して物事を表現します
文法: class 类名{ 访问权限: 属性 / 行为 };
単純なクラスの例を見てください:円クラスを設計し、円の円周を見つけます。
//圆周率
const double PI = 3.14;
//1、封装的意义
//将属性和行为作为一个整体,用来表现生活中的事物
//封装一个圆类,求圆的周长
//class代表设计一个类,后面跟着的是类名
class Circle
{
public: //访问权限 公共的权限
//属性
int m_r;//半径
//行为
//获取到圆的周长
double calculateZC()
{
//2 * pi * r
//获取圆的周长
return 2 * PI * m_r;
}
};
int main() {
//通过圆类,创建圆的对象
// c1就是一个具体的圆
Circle c1;
c1.m_r = 10; //给圆对象的半径 进行赋值操作
//2 * pi * 10 = = 62.8
cout << "圆的周长为: " << c1.calculateZC() << endl;
system("pause");
return 0;
}
学生クラスを設計します。属性には名前と学生番号が含まれており、名前と学生番号に値を割り当て、学生の名前と学生番号を表示できます。
//学生类
class Student {
public:
void setName(string name) {
m_name = name;
}
void setID(int id) {
m_id = id;
}
void showStudent() {
cout << "name:" << m_name << " ID:" << m_id << endl;
}
public:
string m_name;
int m_id;
};
int main() {
Student stu;
stu.setName("德玛西亚");
stu.setID(250);
stu.showStudent();
system("pause");
return 0;
}
クラスとオブジェクトへのアクセス: 関数と変数を含む
- public パブリック許可メンバーはクラス内でアクセスでき、クラス外でもアクセスでき、通常は外部インターフェイスに使用されます。
- protected protected アクセス許可メンバー クラスは、子が継承にアクセスできない場合でも、親クラスの保護されたコンテンツにアクセスできます。
- private プライベート権限のメンバークラスはクラス外からアクセスできるものとアクセスできないもの 継承時、子クラスは親クラスのプライベートコンテンツにアクセスできない
例を見てみましょう:
//三种权限
//公共权限 public 类内可以访问 类外可以访问
//保护权限 protected 类内可以访问 类外不可以访问
//私有权限 private 类内可以访问 类外不可以访问
class Person
{
//姓名 公共权限
public:
string m_Name;
//汽车 保护权限
protected:
string m_Car;
//银行卡密码 私有权限
private:
int m_Password;
public:
void func()
{
m_Name = "张三";
m_Car = "拖拉机";
m_Password = 123456;
}
};
int main() {
Person p;
p.m_Name = "李四";
//p.m_Car = "奔驰"; //保护权限类外访问不到
//p.m_Password = 123; //私有权限类外访问不到
system("pause");
return 0;
}
保護されたアクセス許可とクラス外からアクセスできない
struct
プライベートアクセス許可の違いclass
:
クラスの定義から、構造体とクラスの定義は非常に似ていることがわかります。
C++ では
struct
、class
唯一の違いは次のとおりです。デフォルトのアクセス権が異なります
違い:
struct
デフォルトの権限はパブリックです。メンバーが権限修飾子を追加しない場合、デフォルトはパブリックです。public
class
デフォルトの権限はプライベートです。メンバーが権限修飾子を追加しない場合、デフォルトはprivate
class C1
{
int m_A; //默认是私有权限
};
struct C2
{
int m_A; //默认是公共权限
};
int main() {
C1 c1;
c1.m_A = 10; //错误,访问权限是私有
C2 c2;
c2.m_A = 10; //正确,访问权限是公共
system("pause");
return 0;
}
上記の例から、メンバー プロパティをプライベートとして設定することもわかります
。これは通常プロジェクト内で行われ、クラスの利点はプライバシーです。
利点 1:すべてのメンバー属性を非公開に設定し、読み取りおよび書き込み権限を自分で制御できる
利点 2:書き込み権限については、データの有効性をチェックできる
class Person {
public:
//姓名设置可读可写
void setName(string name) {
m_Name = name;
}
string getName()
{
return m_Name;
}
//设置年龄
void setAge(int age) {
if (age < 0 || age > 150) {
cout << "请输入正确的年龄!" << endl;
return;
}
m_Age = age;
}
//获取年龄
int getAge() {
return m_Age;
}
//爱人设置为只写
void setLover(string lover) {
m_Lover = lover;
}
private:
string m_Name; //可读可写 姓名
int m_Age; //只读 年龄
string m_Lover; //只写 爱人
};
int main() {
Person p;
//姓名设置
p.setName("张三");
cout << "姓名: " << p.getName() << endl;
//年龄设置
p.setAge(50);
cout << "年龄: " << p.getAge() << endl;
//爱人设置
p.setLover("xh");
//cout << "爱人: " << p.m_Lover << endl; //只写属性,不可以读取
system("pause");
return 0;
}
クラスは別のクラスをメンバーとして持つことができます。以下の例を参照してください。
#include<iostream>
using namespace std;
//点和圆的关系
// 点类
class Point
{
public:
//设置x坐标
void setX(int x)
{
m_X = x;
}
//获取x坐标
int getX()
{
return m_X;
}
//设置Y坐标
void setY(int y)
{
m_Y = y;
}
//获取Y坐标
int getY()
{
return m_Y;
}
private:
int m_X;
int m_Y;
};
//圆类
class Circle
{
private:
int m_R;
Point m_Center;
public:
//设置半径
void setR(int r)
{
m_R = r;
}
//获取半径
int getR()
{
return m_R;
}
//设置圆心
void setCenter(Point center)
{
m_Center = center;
}
//获取圆心
Point getCenter()
{
return m_Center;
}
};
//判断点和圆的关系
void isInCircle(Circle& c, Point& p)
{
//计算两点之间距离平方
int distance =
(c.getCenter().getX() - p.getX()) * (c.getCenter().getX() - p.getX()) +
(c.getCenter().getY() - p.getY()) * (c.getCenter().getY() - p.getY());
//计算半径平方
int rDistance = c.getR() * c.getR();
//判断关系
if (distance == rDistance)
{
cout << "点在圆上" << endl;
}
else if (distance > rDistance)
{
cout << "点在圆外" << endl;
}
else
{
cout << "点在圆内" << endl;
}
}
int main() {
//创建圆
Circle c;
c.setR(10);
Point center;
center.setX(10);
center.setY(10);
c.setCenter(center);
//创建点
Point p;
p.setX(10);
p.setY(10);
//判断关系
isInCircle(c, p);
system("pause");
return 0;
}
上記のコードはレベルがないと乱雑に見えますか。これはエンジニアリング開発にとって非常に不利です。ファイルの種類ごとに分類してみましょう。
上記を完了すると、コードはより簡潔になったように見えますか?
2.4.1. オブジェクトの初期化とクリーンアップ
2.4.2. コンストラクターとデストラクター
オブジェクトの初期化とクリーンアップも 2 つの非常に重要なセキュリティ問題です
オブジェクトまたは変数には初期状態がなく、その使用の結果は不明です。
同様に、オブジェクトまたは変数を使用した後、時間内にクリーンアップしないと、特定のセキュリティ上の問題が発生します。
C++ はコンストラクターとデストラクターを使用して上記の問題を解決し、これら 2 つの関数はコンパイラーによって自動的に呼び出され、オブジェクトの初期化とクリーンアップを完了します。
オブジェクトの初期化とクリーンアップはコンパイラによって強制されるものであるため、構築と破棄を提供しない場合は、コンパイラが提供します。
コンパイラーによって提供されるコンストラクターとデストラクターは空の実装です。
- コンストラクター: 主な機能は、オブジェクトの作成時にオブジェクトのメンバー プロパティに値を割り当てることです。コンストラクターはコンパイラーによって決定されます。自動通話、手動で電話をかける必要はありません。
- デストラクタ: 主な役割はオブジェクト内にあります破壊の前にシステムは自動的に呼び出しを行い、いくつかのクリーニング作業を実行します。
コンストラクターの構文:类名(){}
- コンストラクター、戻り値なし、書き込みなし
void
- 関数名はクラス名と同じです
- コンストラクターにはパラメーターを含めることができるため、オーバーロードが発生する可能性があります
- プログラムがオブジェクトを呼び出すと、自動通話構築中、手動で呼び出す必要はなく、一度だけ呼び出されます
デストラクターの構文: ~类名(){}
- デストラクター、戻り値なし、書き込みなし
void
- 関数名はクラス名と同じです。名前の前に記号を付けます。
~
- デストラクタはパラメータを持つことができないため、オーバーロードは発生しません
- プログラムはオブジェクトが破棄される前に自動的にデストラクターを呼び出します。手動で呼び出す必要はなく、一度だけ呼び出されます。
コア: 1 つは初期化を担当し、もう 1 つは破壊を担当します。
class Person
{
public:
//构造函数
Person()
{
//如果自己不写,系统默认为空,这里什么都不写
cout << "Person的构造函数调用" << endl;
}
//析构函数
~Person()
{
//如果自己不写,系统默认为空,这里什么都不写
cout << "Person的析构函数调用" << endl;
}
};
//构造和析构都是必须有的实现,如果我们自己不提供,编译器会提供一个空实现的构造和析构
void test01()
{
Person p; //在栈上的数据,test01执行完毕后释放对象。如果把创建对象放到main函数中,要等程序运行完才释放,即对象销毁后才释放
}
int main() {
test01();
system("pause");
return 0;
}
2.4.3. コンストラクターの分類と呼び出し
2 つの分類方法:
- パラメータに応じて、パラメータを使用した構築とパラメータを使用しない構築に分けられます(デフォルト) 建設
- 通常施工とコピー施工の種類別
3 つの呼び出し方法:
- ブラケット
- 表示方法
- 暗黙的な変換方法
//1、构造函数分类
// 按照参数分类分为 有参和无参构造 无参又称为默认构造函数
// 按照类型分类分为 普通构造和拷贝构造
class Person {
public:
//无参(默认)构造函数
Person() {
cout << "无参构造函数!" << endl;
}
//有参构造函数
Person(int a) {
age = a;
cout << "有参构造函数!" << endl;
}
//拷贝构造函数,拷贝构造顾名思义拷贝一份类对象,但是又不能改变本体,拷贝的同时按照引用的方式传值
Person(const Person& p) {
//将传入的人身上的所有属性,拷贝到我身上
age = p.age;
cout << "拷贝构造函数!" << endl;
}
//析构函数
~Person() {
cout << "析构函数!" << endl;
}
public:
int age;
};
//2、构造函数的调用
//调用无参构造函数
void test01() {
Person p; //调用无参构造函数
}
//调用有参的构造函数
void test02() {
//2.1 括号法,常用,推荐使用
Person p1; //默认构造函数
Person p11(10);//有参构造函数
Person p12(p1);//拷贝构造函数,传对象
//注意1:调用无参构造函数不能加括号,如果加了编译器认为这是一个函数声明,不认为是创建对象
//Person p2();
//2.2 显式法
Person p2; //默认构造函数
Person p21 = Person(10); //有参构造函数,可以理解成Person p21(10)
Person p22 = Person(p2); //拷贝构造函数,可以理解成Person p22(p2)
//Person(10)单独写就是匿名对象,当前行结束之后,马上析构,即这个对象调用完,下面的函数还没执行就已经销毁了
//不要用拷贝构造函数初始化匿名对象:Person(p2),会提示Person p2重定义,因为此时编译器会认为 Person p2;上面已经有一个了
//2.3 隐式转换法
Person p4 = 10; // Person p4 = Person(10); 有参构造函数
Person p5 = p4; // Person p5 = Person(p4); 拷贝构造函数
//注意2:不能利用 拷贝构造函数 初始化匿名对象 编译器认为是对象声明
//Person (p4);
}
int main() {
test01();
//test02();
system("pause");
return 0;
}
2.4.4. コピーコンストラクターを呼び出すタイミング
3 回:
- 作成済みのオブジェクトを使用して新しいオブジェクトを初期化する
- 値の受け渡しの方法は関数のパラメータに値を渡すことですが、参照ではないことに注意してください
- ローカル オブジェクトを値で返します。これは参照ではないことに注意してください
class Person {
public:
//默认(无参)构造
Person() {
cout << "无参构造函数!" << endl;
mAge = 0;
}
//有参构造
Person(int age) {
cout << "有参构造函数!" << endl;
mAge = age;
}
//拷贝构造,将传过来的对象的数据全部拷贝一份
Person(const Person& p) {
cout << "拷贝构造函数!" << endl;
mAge = p.mAge;
}
//析构函数在释放内存之前调用
~Person() {
cout << "析构函数!" << endl;
}
public:
int mAge;
};
//拷贝函数调用时机
//1. 使用一个已经创建完毕的对象来初始化一个新对象
void test01() {
Person man(100); //p对象已经创建完毕
Person newman(man); //调用拷贝构造函数
Person newman2 = man; //拷贝构造
//Person newman3;
//newman3 = man; //不是调用拷贝构造函数,赋值操作
}
//2. 值传递的方式给函数参数传值
//相当于Person p1 = p;
void doWork(Person p1) {
}
void test02() {
Person p; //无参构造函数
//这里会调用拷贝构造函数,实参传给形参的时候会调用拷贝构造,但是doWork里面的p不会改变 这里定义的p,因为拷贝的是临时的副本,不一个内存空间
doWork(p);
}
//3. 以值方式返回局部对象,局部对象有个特点,函数执行完立即释放
Person doWork2()
{
Person p1;
cout << (int *)&p1 << endl;
//以值的方式返回,这里返回的并不是p1本身,而是根据p1来创建一个新的对象再返回
return p1;
}
void test03()
{
//函数执行完会调用析构函数
Person p = doWork2();
cout << (int *)&p << endl;
}
int main() {
//test01();
//test02();
test03(); //执行完之后会有两个拷贝构造和两个析构函数打印出来。这里的析构函数要等程序运行完才会执行
system("pause");
return 0;
}
コンストラクター呼び出し規則:
デフォルトでは、クラスが記述されている限り、c++
コンパイラーは少なくとも 1 つのクラスを追加します。3つの機能
1. デフォルトのコンストラクター (パラメーターなし、関数本体は空)
2. デフォルトのデストラクター (パラメーターなし、関数本体は空)
3. デフォルトのコピー コンストラクター。属性の値をコピーします。属性の値が自分で書かれていない場合でも、コンパイラーはそれを自分で書き込みます。
例えば、以下のコードでは、コピーコンストラクタが書かれていない場合でも、Person p2(p1);
この文を実行するとクラス内にコピーコンストラクタが自動的に作成され、p1
に属性が代入されp2
、コンパイラが属性のコピーを作成します。
void test01()
{
Person p1(18);
//如果不写拷贝构造,编译器会自动添加拷贝构造,并且做浅拷贝操作
Person p2(p1);
cout << "p2的年龄为: " << p2.age << endl;
}
- パラメーター付きのコンストラクターがクラスに記述されているが、デフォルト (パラメーターなし) のコンストラクターが記述されていない場合、コンパイラーはパラメーターのないデフォルトのコンストラクターを提供しません。デフォルトのコンストラクターが呼び出された場合は、エラーが報告されます。コピー構築が書かれていても、コピーコンストラクタが提供されます。例は上に示しています。つまり、パラメーター化された構造体を作成した後、コンパイラーはデフォルトの構造体を提供しなくなります。
- クラスにコピー コンストラクターのみが記述され、デフォルト コンストラクターとパラメーター化されたコンストラクターが記述されていない場合、コンパイラーはそれを提供しなくなり、デフォルト コンストラクターとパラメーター化されたコンストラクターが再度呼び出される場合は、エラーが報告されます。つまり、コピー構造を作成した後は、もう一方の構造は提供されなくなります。
上記の質問の例を見てみましょう。
class Person {
public:
//无参(默认)构造函数
Person() {
cout << "无参构造函数!" << endl;
}
//有参构造函数
Person(int a) {
age = a;
cout << "有参构造函数!" << endl;
}
//拷贝构造函数
Person(const Person& p) {
age = p.age;
cout << "拷贝构造函数!" << endl;
}
//析构函数
~Person() {
cout << "析构函数!" << endl;
}
public:
int age;
};
void test01()
{
Person p1(18);
//如果不写拷贝构造,编译器会自动添加拷贝构造,并且做浅拷贝操作
Person p2(p1);
cout << "p2的年龄为: " << p2.age << endl;
}
void test02()
{
//如果用户提供有参构造,编译器不会提供默认构造,会提供拷贝构造
Person p1; //此时如果用户自己没有提供默认构造,会出错
Person p2(10); //用户提供的有参
Person p3(p2); //此时如果用户没有提供拷贝构造,编译器会提供
//如果用户提供拷贝构造,编译器不会提供其他构造函数
Person p4; //此时如果用户自己没有提供默认构造,会出错
Person p5(10); //此时如果用户自己没有提供有参,会出错
Person p6(p5); //用户自己提供拷贝构造
}
int main() {
test01();
system("pause");
return 0;
}
概要:
デフォルト (パラメータなし) コンストラクタ、パラメータ付きコンストラクタ、およびコピー コンストラクタの 3 つのコンストラクタがあり、後者のみが提供されます。前のコンストラクタが自分で記述されない限り、コンパイラはそれらを提供しません。
2.4.5. ディープコピーとシャローコピー
この知識は非常に重要であり、面接でもよく聞かれます。
- 浅いコピー: コンパイラでの単純な代入コピー操作
- ディープコピー: ヒープ領域のスペースを再申請し、コピー操作を実行します。
class Person {
public:
//无参(默认)构造函数
Person() {
cout << "无参构造函数!" << endl;
}
//有参构造函数
Person(int age ,int height) {
cout << "有参构造函数!" << endl;
m_age = age;
m_height = new int(height);
}
//拷贝构造函数
/*
Person(const Person& p) {
cout << "拷贝构造函数!" << endl;
//如果不利用深拷贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区问题
m_age = p.m_age;
m_height = new int(*p.m_height);
}
*/
//析构函数
~Person() {
//析构函数通常用来释放堆区中的数据
cout << "析构函数!" << endl;
if (m_height != NULL)
{
delete m_height;
}
}
public:
int m_age;
//创建一个指针为了上面把身高放在堆区。
int* m_height;
};
void test01()
{
Person p1(18, 180);
Person p2(p1);
cout << "p1的年龄: " << p1.m_age << " 身高: " << *p1.m_height << endl;
cout << "p2的年龄: " << p2.m_age << " 身高: " << *p2.m_height << endl;
}
int main() {
test01();
system("pause");
return 0;
}
上記のコードを実行すると、
次の結果が表示されます: このエラーが報告される理由を見てみましょう: 上記のコードに問題がある理由は、ヒープ領域に作成される
高さに問題があるためです。ヒープ領域に格納されている高さのアドレスが下位にコピーされ、オブジェクト作成時にデフォルトでヒープ領域のデータがコピーされます。つまり、ヒープ領域のデータが にコピーされます。プログラムの実行終了後、デストラクタを実行して sum を解放する必要がありますが、sumはスタックに置かれ、最初にスタック内のデータが解放され、次にスタック内のデータが解放されます。ヒープ領域のデータが繰り返し解放され、競合が発生します。問題の原因はコピー コンストラクターの浅いコピーによって引き起こされるのでしょうか? どうやって解決すればいいでしょうか?この問題はディープテストを使用することで解決できます。上記のコメントを取り消し、コピーコンストラクターを自分で実装し、浅いコピーによって引き起こされる問題を解決し、システムのデフォルトの構築コピーを使用しないことで解決できます。上図のコードに示すように、コピーしたデータを格納するヒープ領域を再度開きます。要約:p1
p1
p2
p1
p2
p2
p1
p2
p1
p2
p2
p1
/* */
属性がヒープ領域で開かれている場合は、浅いコピーによって引き起こされる問題を防ぐために、コピー コンストラクターを自分で提供する必要があります。
2.4.6. 初期化リスト
C++ は、プロパティを初期化するための初期化子リスト構文を提供します。エンジニアリングではこの書き方をお勧めします。コロンの位置に注意してください。
文法:构造函数():属性1(值1),属性2(值2)... {}
class Person {
public:
传统方式初始化,通常我们都是使用这种有参构造初始化,但是不推荐
//Person(int a, int b, int c) {
// m_A = a;
// m_B = b;
// m_C = c;
//}
//初始化列表方式初始化,工程中推荐用法,这样也可以在构造函数里面写一下其他的语法,这里一定注意冒号的位置
Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c) //相当于m_A = a, m_B = b, m_C = c
{
}
void PrintPerson() {
cout << "mA:" << m_A << endl;
cout << "mB:" << m_B << endl;
cout << "mC:" << m_C << endl;
}
private:
int m_A;
int m_B;
int m_C;
};
int main() {
Person p(1, 2, 3);
p.PrintPerson();
system("pause");
return 0;
}
2.4.7. クラスのメンバーとしてのクラスオブジェクト
C++
クラスのメンバーは別のクラスのオブジェクトになることができ、このメンバーを オブジェクトメンバーと呼びます。上の点と円の関係では、実際に一度示されています。
class Phone
{
public:
Phone(string name)
{
m_PhoneName = name;
cout << "Phone构造" << endl;
}
~Phone()
{
cout << "Phone析构" << endl;
}
string m_PhoneName;
};
class Person
{
public:
//初始化列表可以告诉编译器调用哪一个构造函数
/*Phone是一个类,这里给pName传过来的是个字符串,但是没有报错是因为什么呢?因为这里相当于隐式转换法
Phone m_Pname = pName。这里就展示了类对象也可以用初始化列表的方式赋初值。*/
Person(string name, string pName) :m_Name(name), m_Phone(pName)
{
cout << "Person构造" << endl;
}
~Person()
{
cout << "Person析构" << endl;
}
void playGame()
{
cout << m_Name << " 使用" << m_Phone.m_PhoneName << " 牌手机! " << endl;
}
string m_Name;
Phone m_Phone;
};
void test01()
{
//当类中成员是其他类对象时,我们称该成员为对象成员
//构造的顺序是 :先调用对象成员的构造,再调用本类构造
//析构顺序与构造相反
Person p("张三" , "苹果X");
p.playGame();
}
int main() {
test01();
system("pause");
return 0;
}
概要:
渡すときに暗黙的な型変換が発生します。
2.4.8. 静的メンバー
静的メンバーは次のように分割されます。
静的メンバー変数:
- すべてのオブジェクトが同じデータを共有する
- メモリはコンパイル段階で割り当てられ、プログラムの実行前に割り当てられます。
- クラス内宣言、クラス外初期化
- クラス名でアクセスできる
静的メンバー関数:
- すべてのオブジェクトは同じ機能を共有します
- 静的メンバー関数は静的メンバー変数にのみアクセスできます
- クラス名でアクセスできる
まず静的メンバー変数を見てください。
class Person
{
public:
//静态成员变量特点:
//1 在编译阶段分配内存
//2 所有对象共享同一份数据
//3 类内声明,类外初始化,必须要做的,否则没法访问
static int m_A; //静态成员变量
};
//类外初始化,注意初始化的时候作用域
int Person::m_A = 10;
void test01()
{
//静态成员变量两种访问方式
//1、通过对象
Person p1;
p1.m_A = 100;
cout << "p1.m_A = " << p1.m_A << endl;
Person p2;
p2.m_A = 200;
cout << "p1.m_A = " << p1.m_A << endl; //共享同一份数据,输出200
cout << "p2.m_A = " << p2.m_A << endl;//输出200
}
int main() {
test01();
system("pause");
return 0;
}
上記のクラス内宣言とクラス外初期化は、クラス内で定義してクラス外に代入する必要があることを意味しますが、宣言空間がどのクラスの下にあるかに注意してください。
完全なコードを見て、コメント セクションに注目してください。
class Person
{
public:
static int m_A; //静态成员变量
//静态成员变量特点:
//1 在编译阶段分配内存
//2 类内声明,类外初始化
//3 所有对象共享同一份数据
private:
static int m_B; //静态成员变量也是有访问权限的,外部无法访问
};
int Person::m_A = 10;
int Person::m_B = 10;
void test01()
{
//静态成员变量不属于某个对象,所有对象都共享同一份数据
//静态成员变量两种访问方式
//1、通过对象,其实这里创不创建对象没任何意义,因为静态成员本身不属于任何对象
Person p1;
p1.m_A = 100;
cout << "p1.m_A = " << p1.m_A << endl;
Person p2;
p2.m_A = 200;
cout << "p1.m_A = " << p1.m_A << endl; //共享同一份数据
cout << "p2.m_A = " << p2.m_A << endl;
//2、通过类名,因为静态成员本身不属于任何对象
cout << "m_A = " << Person::m_A << endl;
//cout << "m_B = " << Person::m_B << endl; //私有权限访问不到
}
int main() {
test01();
system("pause");
return 0;
}
静的メンバー関数をもう一度見てみましょう。
- プログラムは機能を共有する
- 静的メンバー関数は静的メンバー変数にのみアクセスでき
、クラス名を通じてアクセスできます。
静的メンバー関数が非静的メンバー プロパティにアクセスできるようにしないのはなぜですか? 静的メンバー関数のデータはメモリ内に 1 つのコピーしかないため、上記はm_B
非静的メンバー変数ではなく、オブジェクトを通じてアクセスする必要があります。上記の静的関数を呼び出すとき、関数本体の内部ではどのオブジェクトが変更されたかがわかりませんm_B
。たとえば、上記の静的メンバー関数を呼び出すために、 、 というオブジェクトが 2 つ作成されます。 関数内に 1 つありp1
、p2
静的関数の本体内にあります。変更方法がわかりません。この静的メンバー関数が呼び出されているにもかかわらず、この関数の本体には反映されません。同様のことが当てはまります。それは彼がどのオブジェクトにも属していないからかもしれません。p1
m_B=200
m_B
p1
200
p1
p1
p2
m_A
静的メンバー関数にもアクセス権があります。
class Person
{
public:
//静态成员函数特点:
//1 程序共享一个函数
//2 静态成员函数只能访问静态成员变量
static void func()
{
cout << "func调用" << endl;
m_A = 100;
//m_B = 100; //错误,不可以访问非静态成员变量
}
static int m_A; //静态成员变量
int m_B; //
private:
//静态成员函数也是有访问权限的,类外无法访问
static void func2()
{
cout << "func2调用" << endl;
}
};
int Person::m_A = 10;
void test01()
{
//静态成员变量两种访问方式
//1、通过对象,没特殊意义,因为函数不属于任何对象
Person p1;
p1.func();
//2、通过类名,因为函数不属于任何对象,可以直接通过类名访问
Person::func();
//Person::func2(); //类外访问不到私有权限静态成员函数
}
int main() {
test01();
system("pause");
return 0;
}
2.4.9. C++ オブジェクト モデルとこのポインター
2.4.9.1. メンバー変数とメンバー関数は別々に保存されます
- C++ では、クラス内のメンバー変数とメンバー関数は別々に格納されます。
- それだけ非静的メンバー変数静的メンバー変数のように、クラスに属するオブジェクトでは、静的メンバー関数は特定のオブジェクトに属さず、非静的メンバー関数はクラス オブジェクトに属しません。非静的メンバー関数のコピーは 1 つだけです。これらは静的メンバー変数とは別に保存されます。つまり、関数はクラス オブジェクトに属しません。
class Person {
public:
Person() {
mA = 0;
}
//非静态成员变量占对象空间
int mA;
//静态成员变量不占对象空间
static int mB;
//函数也不占对象空间,所有函数共享一个函数实例
void func() {
cout << "mA:" << this->mA << endl;
}
//静态成员函数也不占对象空间
static void sfunc() {
}
};
int main() {
//一个空对象的占员工内存空间是1个字节,即C++编译器会为每个空对象分配一个字节空间,是为了区分空对象的占内存的位置
//每个空对象也应该有一个独一无二的内存地址
cout << sizeof(Person) << endl;
system("pause");
return 0;
}
単一のオブジェクトにメンバーがない場合、コンパイラはそのオブジェクトを開きます。1バイト空。非静的メンバ変数が存在する場合、オブジェクトはメンバ変数のバイト数を占有し、つまりメンバ変数に応じてメモリが割り当てられ、この非静的メンバ変数はクラスのオブジェクトに属します。クラス内に静的メンバー変数がある場合、その静的メンバー変数はクラス オブジェクトに属さないため、クラス オブジェクトは静的メンバー変数用のメモリを開きません。クラス内に非静的メンバー関数がある場合、その関数はクラス オブジェクトに属さず、その関数にメモリを割り当てません。クラス内に静的メンバー関数がある場合、それはクラス オブジェクトに属さず、メモリも割り当てられません。上で言いたいのは、メンバー変数とメンバー関数は別々に保存されるということです。
結局のところ、ただ、非静的メンバー変数クラスオブジェクトに属します。クラスに属さないオブジェクトにはメモリは割り当てられません。
2.4.9.2. このポインタ
先ほど、クラス オブジェクトで定義された一部のメンバーはそのクラス オブジェクトに属さないと述べましたが、異なるオブジェクトを作成するときにどのオブジェクトが使用されているかをどのように区別するのでしょうか? メンバー変数とメンバー関数は別々に格納され、各非静的メンバー関数は関数インスタンスのみを生成することがわかっています。これは、同じ型の複数のオブジェクトが 1 つのコードを共有することを意味します
。C++
そこで問題は、このコード部分はどのオブジェクトが自分自身を呼び出すかをどのように区別するのかということです。
c++
特別なオブジェクトポインタ、this
ポインタ(Python
ルートの意味self
)を提供することで、上記の問題を解決します。this ポインタは、呼び出されたメンバー関数が属するオブジェクトを指し、それを呼び出す人は誰を指します。
this
ポインタは、各非静的メンバー関数に暗黙的に含まれるポインタです。非静的メンバー関数にはデフォルトで this ポインタがあり、自分で定義せずに直接使用できます。ポインタを定義する必要はありません。このポインタを
this
使用するために直接使用できます。
- 仮パラメータとメンバ変数が同じ名前の場合、
this
ポインタで区別できます。名前の競合を解決する- クラスの非静的メンバー関数でオブジェクト自体を返すには、次を使用します。戻る
*this
。オブジェクト自体を返す
見てみましょう名前の競合の例:
class Person
{
public:
/*
这里在编译的时候不会出现错误,但是最终的程序输出结果不是10,这里想做的是把传过来的值赋值给成员变量age,
但是呢成员变量和age同名,出现了名称冲突。规范写法应该是区分开来,成员属性前面加个m,即m_age,表示成员属性,
即下面的/**/注释部分写法。或者通过this指针去解决。
*/
Person(int age)
{
age = age;
}
int age;
/*
Person(int age)
{
m_age = age;
}
int m_age;*/
};
void test01()
{
Person p1(10);
cout << "p1.age = " << p1.age << endl;
}
int main() {
test01();
system("pause");
return 0;
}
2 番目の解決策:this
解決することです。
class Person
{
public:
Person(int age)
{
//1、当形参和成员变量同名时,可用this指针来区分
//this指针指向被调用成员函数所属的对象,此时的this就相当于p1.
this->age = age;
}
int age;
};
void test01()
{
Person p1(10);
cout << "p1.age = " << p1.age << endl;
}
int main() {
test01();
system("pause");
return 0;
}
下記参照オブジェクトを返す単独で使用する*this
:
class Person
{
public:
Person(int age)
{
//1、当形参和成员变量同名时,可用this指针来区分
this->age = age;
}
/*注意,这里返回的是引用,返回的还是对象本身,如果这里返回的不是引用直接Person而不是Person&,即直接返回值,那么下面的代码
p2.PersonAddPerson(p1).PersonAddPerson(p1).PersonAddPerson(p1);输出结果还是20。为什么呢?因为如果这里返回的是值的
话,在调用完p2.PersonAddPerson(p1)第一次之后p2的年龄加了10岁,这里没问题,但是呢这里返回的已经不是p2的本体了,而是按照本
体创建了一个新的数据调用了拷贝构造函数,我们知道在拷贝构造函数调用构造函数时,用值的方式返回会复制一份新的数据出来,相当于这
里的person已经和自身的不一样了。即每次返回的都是一个新的东西,跟原来已经不一样了。*/
Person& PersonAddPerson(Person p)
{
this->age += p.age;
//返回对象本身
//this指向对象本身,而*this指向的是p2这个对象本体
return *this;
}
int age;
};
void test01()
{
Person p1(10);
cout << "p1.age = " << p1.age << endl;
Person p2(10);
//上面如果改成返回值的话这里输出就是20,因为值返回是拷贝构造创建新的对象,值一直都是10,加10返回就是20。
p2.PersonAddPerson(p1).PersonAddPerson(p1).PersonAddPerson(p1);
cout << "p2.age = " << p2.age << endl;
}
int main() {
test01();
system("pause");
return 0;
}
関数の戻り値と戻り参照の違い: 値で返すと新しいデータがコピーされますが、参照はそれ自体を返します。連鎖プログラミングのアイデア。
2.4.9.3. Nullポインタアクセスメンバ関数
C++ではnullポインタでもメンバ関数を呼び出すことができますが、thisポインタが使用されているかどうかにも注意し、コードの堅牢性を確保するためにポインタを使用しているかどうかを判断する必要があります
。this
//空指针访问成员函数
class Person {
public:
void ShowClassName() {
cout << "我是Person类!" << endl;
}
void ShowPerson() {
//加上这句话,防止下面空指针代码出错
if (this == NULL) {
return;
}
//其实这里的mAge默认的是this->mAge,而传入的指针是NULL,不指向任何东西,这里会报错。
cout << mAge << endl;
}
public:
int mAge;
};
void test01()
{
//空指针,没有指向任何确切的对象
Person * p = NULL;
p->ShowClassName(); //空指针,可以调用成员函数,单独调用这行代码是不会出错的
//调用这行代码会出错
p->ShowPerson(); //但是如果成员函数中用到了this指针,就不可以了
}
int main() {
test01();
system("pause");
return 0;
}
上記のコードから、null ポインターはクラス メンバーにアクセスできることがわかりますが、使用する場合は注意してください。クラスでメンバー属性が使用されていない場合、null ポインターを使用してクラス メンバーにアクセスできます。
2.4.9.4. const 変更メンバー関数
定数関数:
- メンバー関数が追加された後、この関数を定数関数
const
と呼びます - メンバー属性は定数関数では変更できません
- メンバー プロパティを宣言するときにキーワードを追加した後も
mutable
、定数関数でキーワードを変更できます。
定数オブジェクト:
- オブジェクトは、オブジェクトを宣言する前に
const
定数オブジェクトと呼ばれます。 - 定数オブジェクトは定数関数のみを呼び出すことができます
まず定数関数を見てみましょう。この場所は少しわかりにくいので、次のコードとコメントを参照してください。
class Person {
public:
//this指针的本质是一个指针常量,指针的指向不可修改
//如果想让指针指向的值也不可以修改,需要声明常函数
//在成员函数后面加const,修饰的是this指向,让指针指向的值也不可以修改
void ShowPerson() const
{
//this = NULL; //this指针是不能修改指针的指向的,但是指向的值是可以修改的,this的本意相当于:Person* const this;
/*
但是this指针指向的对象的数据是可以修改的,这里的前提是void ShowPerson() const中的const没加,如果加了也不能修改值了。
总结:
如果不加const呢,在这里的this指针就相当于Person * const this,指向不可修改,但是指向的值可以修改,
如果加了const呢,就相当于const Person * const this,即指向不能改,指向的内容也不能改,
那么这个const加在哪合适呢,编译器想来想去还是加载函数后面吧,于是就有了void ShowPerson() const*/
this->mA = 100;
//const修饰成员函数,表示指针指向的内存空间的数据不能修改,除了mutable修饰的变量
//变量前面加了mutable,表示可以修改
//this->m_B = 100;
}
public:
int m_A;
mutable int m_B; //可修改 可变的,即使在常函数中也可以修改
};
//const修饰函数
void test01() {
Person p;
//当利用p去调用成员函数ShowPerson()的时候,类中的this就指向这个p。
p.ShowPerson();
cout << person.m_A << endl;
p.showPerson();
}
int main() {
test01();
system("pause");
return 0;
}
上記のコードは、this
ポインターの本質がポインタ定数の場合、ポインタの向きは変更できません。変更された定数オブジェクト
をもう一度見てみましょう。const
class Person {
public:
Person() {
m_A = 0;
m_B = 0;
}
//this指针的本质是一个指针常量,指针的指向不可修改
//如果想让指针指向的值也不可以修改,需要声明常函数
void ShowPerson() const {
//const Type* const pointer;
//this = NULL; //不能修改指针的指向 Person* const this;
//this->mA = 100; //但是this指针指向的对象的数据是可以修改的,加上了const就不能改了
//const修饰成员函数,表示指针指向的内存空间的数据不能修改,除了mutable修饰的变量
this->m_B = 100;
}
void MyFunc() {
//mA = 10000;
}
public:
int m_A;
mutable int m_B; //可修改 可变的
};
//const修饰对象 常对象
void test01() {
const Person person; //在对象前加上const变为常量对象,不允许修改指针指向的值,对象的属性不允许修改
cout << person.m_A << endl;
//person.mA = 100; //常对象不能修改成员变量的值,但是可以访问
person.m_B = 100; //但是常对象可以修改mutable修饰成员变量
//常对象访问成员函数
person.MyFunc(); //常对象能调用const的函数,不能调用普通成员函数,因为普通成员函数可以修改成员变量
}
int main() {
test01();
system("pause");
return 0;
}
定数関数ではメンバー属性を変更できません。その理由は、定数関数はポインタを変更するものであり、const
ポインタ自体がポインタ定数であるため、追加後はポインタが指す値すら変更できませんthis
。どうしても変更したい場合は、追加することができます。定数オブジェクトは定数関数のみを呼び出すことができます。this
cosnt
mutable
2.4.10.友元
プログラムでは、一部のプライベート属性にはクラス外の特別な関数やクラスからもアクセスする必要があるため、友人のテクノロジーを使用する必要があります。フレンドは、別のクラスのフレンドとしてこのクラスのプライベート メンバーにアクセスするために、いくつかの特別な関数またはいくつかの特別なクラスを宣言します。
友達のキーワードは友人
フレンドの実装方法は 3 つあります。
- グローバルは友達として機能する
- 友達としてクラス
- メンバーが友達として機能する
グローバルは友達として機能します。
class Building
{
//告诉编译器 goodGay全局函数 是 Building类的好朋友,就可以访问类中的私有内容了
friend void goodGay(Building * building);
public:
Building()
{
this->m_SittingRoom = "客厅";
this->m_BedRoom = "卧室";
}
public:
string m_SittingRoom; //客厅
private:
string m_BedRoom; //卧室
};
//全局函数
void goodGay(Building * building)
{
cout << "好基友正在访问: " << building->m_SittingRoom << endl;
//上面类里面如果没有friend void goodGay(Building * building)这个声明就会报错,无法访问私有属性。
cout << "好基友正在访问: " << building->m_BedRoom << endl;
}
void test01()
{
Building b;
goodGay(&b);
}
int main(){
test01();
system("pause");
return 0;
}
上でわかるように、グローバル関数を class の前に置く限りfriend
、クラス内のプライベート属性にアクセスできます
。クラスはフレンドです: フレンド
の目的は、クラスがプライベート メンバーにアクセスできるようにすることです。別のクラスで。
class Building; //为了防止下面写Building类的时候报错
class goodGay
{
public:
//函数具体内容写在外面,这样代码看着更简洁
goodGay();
void visit();
private:
Building *building;
};
class Building
{
//告诉编译器 goodGay类是Building类的好朋友,可以访问到Building类中私有内容
friend class goodGay;
public:
Building();
public:
string m_SittingRoom; //客厅
private:
string m_BedRoom;//卧室
};
//在类外声明构造函数,注意加上作用于,哪个类下面的。下面使用了this指针,如果使用初始化列表的方式就不能使用this指针了
Building::Building()
{
this->m_SittingRoom = "客厅";
this->m_BedRoom = "卧室";
}
//类外声明构造函数,注意哪个类下面的
goodGay::goodGay()
{
building = new Building;
}
//类外声明成员函数,注意哪个类下面的
void goodGay::visit()
{
cout << "好基友正在访问" << building->m_SittingRoom << endl;
cout << "好基友正在访问" << building->m_BedRoom << endl;
}
void test01()
{
goodGay gg;
gg.visit();
}
int main(){
test01();
system("pause");
return 0;
}
コンストラクターの初期化時に使用できるかどうかについては、上記の暗黙の注意事項があります。this
次の 2 つのブログ投稿を参照してください。
メンバーはフレンドとして機能します。
class Building;
class goodGay
{
public:
goodGay();
void visit(); //只让visit函数作为Building的好朋友,可以发访问Building中私有内容
void visit2();
private:
Building *building;
};
class Building
{
//告诉编译器 goodGay类中的visit成员函数 是Building好朋友,可以访问私有内容
friend void goodGay::visit();
public:
Building();
public:
string m_SittingRoom; //客厅
private:
string m_BedRoom;//卧室
};
Building::Building()
{
this->m_SittingRoom = "客厅";
this->m_BedRoom = "卧室";
}
goodGay::goodGay()
{
building = new Building;
}
void goodGay::visit()
{
cout << "好基友正在访问" << building->m_SittingRoom << endl;
cout << "好基友正在访问" << building->m_BedRoom << endl;
}
void goodGay::visit2()
{
cout << "好基友正在访问" << building->m_SittingRoom << endl;
//cout << "好基友正在访问" << building->m_BedRoom << endl;
}
void test01()
{
goodGay gg;
gg.visit();
}
int main(){
test01();
system("pause");
return 0;
}
2.4.11. 演算子のオーバーロード
さらに演算子のオーバーロード:
演算子のオーバーロードの概念: 既存の演算子を再定義し、さまざまなデータ型に適応する別の関数を与えます。たとえば、2 人を追加したい場合、2 人を追加するとはどういう意味ですか? 以下の凡例を見てください: 2 つのオブジェクトを追加するために、Person AddPerson
メンバー/グローバル関数を作成しましたが、全員が異なる名前を持っているため、コンパイラーoperator+
からの関数を使用して、コンパイラーが共通の名前を付けました。この名前は呼び出しメソッドを簡素化し、使用することができます。直接ご連絡ください+
。オーバーロードがグローバル関数を通じて実装される場合、2 つの変数を渡す必要があることに注意してください。
例を見てみましょう。
上記のエラーが報告された場合はどうすればよいでしょうか? オーバーロードすることで解決する必要があります。
class Person {
public:
Person(int a, int b)
{
this->m_A = a;
this->m_B = b;
}
//成员函数实现 + 号运算符重载
Person operator+(const Person& p)
{
//创建一个临时的Person变量
Person temp;
temp.m_A = this->m_A + p.m_A;
temp.m_B = this->m_B + p.m_B;
return temp;
}
public:
int m_A;
int m_B;
};
//全局函数实现 + 号运算符重载
//Person operator+(const Person& p1, const Person& p2) {
// Person temp(0, 0);
// temp.m_A = p1.m_A + p2.m_A;
// temp.m_B = p1.m_B + p2.m_B;
// return temp;
//}
//运算符重载 可以发生函数重载 ,实现一个对象加一个整数
Person operator+(const Person& p2, int val)
{
Person temp;
temp.m_A = p2.m_A + val;
temp.m_B = p2.m_B + val;
return temp;
}
void test() {
Person p1(10, 10);
Person p2(20, 20);
//成员函数方式
//成员函数的本质调用方法:Person p3 = p1.operator+(p2);
//简化方法
Person p3 = p2 + p1; //相当于 p2.operaor+(p1)
cout << "mA:" << p3.m_A << " mB:" << p3.m_B << endl;
//全局函数重载的本质调用:Person p3 = operator+(p1,p2)
Person p4 = p3 + 10; //相当于 Person p4 = operator+(p3,10)
cout << "mA:" << p4.m_A << " mB:" << p4.m_B << endl;
//运算符重载也可以发生函数重载
Person p4 = p3+10;
}
int main() {
test();
system("pause");
return 0;
}
要約 1: 組み込みデータ型の式の演算子を変更することはできません。たとえば、2 つの整数 1+1=2 を 1-1=0 に変えることはできません。
要約 2: 演算子のオーバーロードを乱用しないでください。たとえば、operator+ は明らかに加算演算ですが、コードの機能は減算です。
左シフト演算子:
<<
オーバーロードでカスタム内容を出力可能 例えばクラス名出力のメンバー変数を直接出力したい場合、通常は不可能ですが、オーバーロードすることで可能になります。
class Person {
friend ostream& operator<<(ostream& out, Person& p);
public:
Person(int a, int b)
{
this->m_A = a;
this->m_B = b;
}
/*如果这里参数写的是Person& p,最后调用的时候变成p.operator<<(p),简化版本变成了p<<p,
不是我们要的。那如果传cout呢(cout也是对象,可以传),最后调用的时候变成p.operator<<(cout),
简化版本为p<<cout,也不是我们想要的结果。成员函数无法实现移位运算符的重载*/
//void operator<<(Person& p){
//}
private:
int m_A;
int m_B;
};
//只能全局函数实现左移重载
/*
ostream(输出流)对象,全局只能有一个,必须用引用方式传过来。注意这里的返回类型ostream。重载的核心operator<<。
这里还要注意到友元,私有类型。这里返回ostream才可以无限使用<<输出,不然只能使用一次。
这里的使用的是引用,cout是别名,可以换成其他的名字。这里关于cout可以是别名仍有疑问,待更近一步研究(第二遍的理解:cout在可以理解成内部定义好的关键词,只有一个,全局成员函数只是通过引用的方式接收他而已,所以想起什么名字起什么名字)。
*/
//注意,这里cout还必须是引用,因为全局只有一个cout对象
ostream& operator<<(ostream& cout, Person& p) {
cout << "a:" << p.m_A << " b:" << p.m_B;
return cout;
}
void test() {
Person p1(10, 20);
cout << p1 << "hello world" << endl; //链式编程,因为自己实现了重载,因此可以实现cout << p1也可以实现cout << "hello world",相当于调用的函数不一样。
}
int main() {
test();
system("pause");
return 0;
}
概要: 左シフト演算子をフレンドでオーバーロードすると、カスタム データ型の出力を実現できます。オーバーロードはメンバー関数では実現できません。
インクリメント演算子のオーバーロード:
機能: インクリメント演算子をオーバーロードして独自の整数データを実現します。
class MyInteger {
friend ostream& operator<<(ostream& out, MyInteger myint);
public:
MyInteger() {
m_Num = 0;
}
//前置++,注意这里返回的是引用而不是值,如果返回值的话只能执行一次++,不可以连续多个++,如++(++a)仍然只做一次++。
MyInteger& operator++() {
//先++
m_Num++;
//再返回
return *this;
}
//后置++,这里不能返回引用,如果返回的是引用相当于返回的是局部对象的引用,局部对象这里运行完立即释放,后面在操作就是非法的,这里有个占位参数int,只能写int,可以区分前置和后置,不然重载错误
MyInteger operator++(int) {
//先记录
MyInteger temp = *this; //记录当前本身的值,然后让本身的值加1
//后递增
m_Num++;
return temp;
}
private:
int m_Num;
};
ostream& operator<<(ostream& out, MyInteger myint) {
out << myint.m_Num;
return out;
}
//前置++ 先++ 再返回
void test01() {
MyInteger myInt;
cout << ++myInt << endl;
cout << myInt << endl;
}
//后置++ 先返回 再++
void test02() {
MyInteger myInt;
cout << myInt++ << endl;
cout << myInt << endl;
}
int main() {
test01();
//test02();
system("pause");
return 0;
}
概要: インクリメント前の戻り値参照、インクリメント後の戻り値
代入演算子のオーバーロード:
C++ コンパイラは実際に、合計 4 つの関数を少なくとも 1 つのクラスに追加します。
- デフォルトのコンストラクター (パラメーターなし、関数本体は空)
- デフォルトのデストラクター (パラメーターなし、関数本体は空)
- プロパティの値をコピーするデフォルトのコピー コンストラクター
- 代入演算子
operator=
、プロパティの値をコピーします
class Person
{
public:
Person(int age)
{
//将年龄数据开辟到堆区
m_Age = new int(age);
}
//重载赋值运算符
Person& operator=(Person &p)
{
//先判断是否释放干净
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
//编译器提供的代码是浅拷贝,析构函数释放的时候会报错。在上面的拷贝构造函数中的浅拷贝已经解释过了。
//m_Age = p.m_Age;
//提供深拷贝 解决浅拷贝的问题
m_Age = new int(*p.m_Age);
//返回自身,自由返回自身才能多次赋值
return *this;
}
~Person()
{
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
}
//年龄的指针
int *m_Age;
};
void test01()
{
Person p1(18);
Person p2(20);
Person p3(30);
//p2 = p1; //赋值操作,将p1的所有数据赋值给p2,此时p1和p2中的堆中的数据指向同一个位置,如果析构函数中使用的是浅拷贝将会报错,内存释放两次。
p3 = p2 = p1;
cout << "p1的年龄为:" << *p1.m_Age << endl;
cout << "p2的年龄为:" << *p2.m_Age << endl;
cout << "p3的年龄为:" << *p3.m_Age << endl;
}
int main() {
test01();
//int a = 10;
//int b = 20;
//int c = 30;
//链式操作,我们的重载也应该要满足这个条件
//c = b = a;
//cout << "a = " << a << endl;
//cout << "b = " << b << endl;
//cout << "c = " << c << endl;
system("pause");
return 0;
}
クラス内にヒープ領域を指す属性がある場合、代入操作を実行するときに深いコピーと浅いコピーの問題も発生します。
関係演算子のオーバーロード:
機能: 関係演算子のオーバーロード。2 つのカスタム タイプ オブジェクトが比較演算を実行できるようにします。
class Person
{
public:
Person(string name, int age)
{
this->m_Name = name;
this->m_Age = age;
};
bool operator==(Person & p)
{
if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
{
return true;
}
else
{
return false;
}
}
bool operator!=(Person & p)
{
if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
{
return false;
}
else
{
return true;
}
}
string m_Name;
int m_Age;
};
void test01()
{
//int a = 0;
//int b = 0;
Person a("孙悟空", 18);
Person b("孙悟空", 18);
if (a == b)
{
cout << "a和b相等" << endl;
}
else
{
cout << "a和b不相等" << endl;
}
if (a != b)
{
cout << "a和b不相等" << endl;
}
else
{
cout << "a和b相等" << endl;
}
}
int main() {
test01();
system("pause");
return 0;
}
関数呼び出し演算子のオーバーロード:
- 関数呼び出し演算子 () もオーバーロードできます。
- オーバーロード後に使用されるメソッドは関数の呼び出しに非常に似ているため、ファンクターと呼ばれます。
- Functor には決まった書き方がなく、非常に柔軟です
class MyPrint
{
public:
void operator()(string text)
{
cout << text << endl;
}
};
void test01()
{
//重载的()操作符 也称为仿函数
MyPrint myFunc;
myFunc("hello world");
}
class MyAdd
{
public:
int operator()(int v1, int v2)
{
return v1 + v2;
}
};
void test02()
{
MyAdd add;
int ret = add(10, 10);
cout << "ret = " << ret << endl;
/*匿名对象调用MyAdd()代替add,执行完立即释放。匿名函数对象MyAdd(),首先他是个匿名对象,
然后他又重载了(),所以我们叫它仿函数,所以又叫匿名函数对象。*/
cout << "MyAdd()(100,100) = " << MyAdd()(100, 100) << endl;
}
int main() {
test01();
test02();
system("pause");
return 0;
}
2.5. 継承
一部のクラス間には特別な関係があります。たとえば、多くの Web サイトには共通の見出し、共通の下部、さらには共通の左リストがあることがわかります。中央のコンテンツだけが異なります。次に、通常の記述を使用し、継承が使用されますJava、C++、Python のチュートリアル Web ページの内容を実装し、継承の意味と利点を確認します。
一般的な実装:
//Java页面
class Java
{
public:
void header()
{
cout << "首页、公开课、登录、注册...(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
}
void left()
{
cout << "Java,Python,C++...(公共分类列表)" << endl;
}
void content()
{
cout << "JAVA学科视频" << endl;
}
};
//Python页面
class Python
{
public:
void header()
{
cout << "首页、公开课、登录、注册...(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
}
void left()
{
cout << "Java,Python,C++...(公共分类列表)" << endl;
}
void content()
{
cout << "Python学科视频" << endl;
}
};
//C++页面
class CPP
{
public:
void header()
{
cout << "首页、公开课、登录、注册...(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
}
void left()
{
cout << "Java,Python,C++...(公共分类列表)" << endl;
}
void content()
{
cout << "C++学科视频" << endl;
}
};
void test01()
{
//Java页面
cout << "Java下载视频页面如下: " << endl;
Java ja;
ja.header();
ja.footer();
ja.left();
ja.content();
cout << "--------------------" << endl;
//Python页面
cout << "Python下载视频页面如下: " << endl;
Python py;
py.header();
py.footer();
py.left();
py.content();
cout << "--------------------" << endl;
//C++页面
cout << "C++下载视频页面如下: " << endl;
CPP cp;
cp.header();
cp.footer();
cp.left();
cp.content();
}
int main() {
test01();
system("pause");
return 0;
}
ご覧のとおり、上記のコードの多くは繰り返されていますが、
継承の使用法を見てみましょう。
//公共页面
class BasePage
{
public:
void header()
{
cout << "首页、公开课、登录、注册...(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
}
void left()
{
cout << "Java,Python,C++...(公共分类列表)" << endl;
}
};
//Java页面
class Java : public BasePage
{
public:
void content()
{
cout << "JAVA学科视频" << endl;
}
};
//Python页面
class Python : public BasePage
{
public:
void content()
{
cout << "Python学科视频" << endl;
}
};
//C++页面
class CPP : public BasePage
{
public:
void content()
{
cout << "C++学科视频" << endl;
}
};
void test01()
{
//Java页面
cout << "Java下载视频页面如下: " << endl;
Java ja;
ja.header();
ja.footer();
ja.left();
ja.content();
cout << "--------------------" << endl;
//Python页面
cout << "Python下载视频页面如下: " << endl;
Python py;
py.header();
py.footer();
py.left();
py.content();
cout << "--------------------" << endl;
//C++页面
cout << "C++下载视频页面如下: " << endl;
CPP cp;
cp.header();
cp.footer();
cp.left();
cp.content();
}
int main() {
test01();
system("pause");
return 0;
}
ご覧のとおり、メソッドを継承すると、コードの重複を大幅に減らすことができます。
要約:
継承の利点:重複したコードを削減できる
継承方法:
class A : public B;
クラス A は子クラスまたは派生クラスと呼ばれます クラス
B は親クラスまたは基本クラスと呼ばれます
派生クラスのメンバー (2 つの部分を含む) :
1 つのクラスは基本クラスから継承され、もう 1 つのクラスは独自に追加されたメンバーです。
基本クラスから継承されたメンバーは共通性を示し、新しく追加されたメンバーはそれぞれの個性を反映します。
継承方法:
継承された構文:class 子类 : 继承方式 父类
継承には 3 つのタイプがあります。
- パブリック継承
- 保護された継承
- 私的継承
上の図の一番左はパブリック継承ですが、図では間違って書かれています。
次の 3 つの例を見てみましょう。
class Base1
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
//公共继承
class Son1 :public Base1
{
public:
void func()
{
m_A; //可访问父类中的 public权限成员
m_B; //可访问父类中的 protected权限,到子类中仍然是保护权限,只有类内能访问,类外不能访问
//m_C; //不可访问,父类中私有权限成员不可访问
}
};
//创建一个测试函数
void myClass()
{
Son1 s1;
s1.m_A; //其他类只能访问到公共权限
//s1.m_B; 报错,m_B到了子类Son1中变成了保护权限,类外不能访问保护权限
}
//保护继承
class Base2
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son2:protected Base2
{
public:
void func()
{
m_A; //父类中的公共成员,到子类中变成了保护权限
m_B; //父类中的保护成员,到子类中变成了保护权限
//父类中的私有成员,不可访问
//m_C; //不可访问
}
};
void myClass2()
{
Son2 s;
//报错,m_A到了子类Son2中变成了保护权限,类外不能访问保护权限
//s.m_A; //不可访问
}
//私有继承
class Base3
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son3:private Base3
{
public:
void func()
{
m_A; //父类中的公共成员到子类中变成了私有成员
m_B; //父类中的保护成员到子类中变成了私有成员
//m_C; //不可访问
}
};
class GrandSon3 :public Son3
{
public:
void func()
{
//Son3是私有继承,所以即使孙子以公共的方式继承父类Son3的属性在GrandSon3中都无法访问到,会直接报错
//m_A;
//m_B;
//m_C;
}
};
2.5.1. 継承におけるオブジェクトモデル
親クラスのすべての非静的メンバー プロパティは継承されますが、親クラスのプライベート メンバー プロパティはコンパイラによって隠蔽されるため、アクセスできませんが、実際には継承されます。
class Base
{
public:
int m_A;
protected:
int m_B;
private:
int m_C; //私有成员只是被隐藏了,但是还是会继承下去
};
//公共继承
class Son :public Base
{
public:
int m_D;
};
void test01()
{
//输出结果是12,父类中的所有非静态成员都别继承下去。父类中的私有属性是被编译器给隐藏了,因此访问不到,但是确实是被继承下去了。
cout << "sizeof Son = " << sizeof(Son) << endl;
}
int main() {
test01();
system("pause");
return 0;
}
開発者コマンド プロンプト ツールを使用して、どのプロパティが継承されているかを確認することもできます。具体的な手順は次のとおりです。
2.5.2. 相続における構築と破壊の順序
class Base
{
public:
Base()
{
cout << "Base构造函数!" << endl;
}
~Base()
{
cout << "Base析构函数!" << endl;
}
};
class Son : public Base
{
public:
Son()
{
cout << "Son构造函数!" << endl;
}
~Son()
{
cout << "Son析构函数!" << endl;
}
};
void test01()
{
//继承中 先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反
Son s;
}
int main() {
test01();
system("pause");
return 0;
}
サブクラスに継承がある場合、サブクラスのオブジェクトを作成する際、継承操作により、サブクラスは必ず親クラスのプロパティとメンバーを取得し、親クラスのオブジェクトも作成するため、親クラスを作成します。 object 親クラスのオブジェクトを呼び出すとき。
概要: 継承では、親クラスのコンストラクターが最初に呼び出され、次にサブクラスのコンストラクターが呼び出され、破壊と構築の順序は逆になります。
2.5.3. 同名処理メソッドの継承
class Base {
public:
Base()
{
m_A = 100;
}
void func()
{
cout << "Base - func()调用" << endl;
}
void func(int a)
{
cout << "Base - func(int a)调用" << endl;
}
public:
int m_A;
};
class Son : public Base {
public:
Son()
{
m_A = 200;
}
//当子类与父类拥有同名的成员函数,子类会隐藏父类中所有版本的同名成员函数
//如果想访问父类中被隐藏的同名成员函数,需要加父类的作用域
void func()
{
cout << "Son - func()调用" << endl;
}
public:
int m_A;
};
void test01()
{
Son s;
//直接访问就是自己类下面的
cout << "Son下的m_A = " << s.m_A << endl;
//如果想要访问父类中的同名成员,需要加上作用域
cout << "Base下的m_A = " << s.Base::m_A << endl;
s.func();
s.Base::func();
/* 如果子类中出现和父类中的同名成员函数,子类中的同名成员函数会隐藏掉父类中所有的同名成员函数即所有的重载函数都被隐藏了。
只要同名不管是否重载都被隐藏
如果想访问到父类中的隐藏的同名成员函数,需要加作用域。
*/
s.Base::func(10);
}
int main() {
test01();
system("pause");
return EXIT_SUCCESS;
}
- サブクラス オブジェクトは、サブクラス内の同じ名前のメンバーに直接アクセスできます。
- サブクラス オブジェクトとスコープは、親クラスの同じ名前のメンバーにアクセスできます。
- サブクラスに親クラスと同じ名前のメンバー関数がある場合、サブクラスは親クラスの同じ名前のメンバー関数を非表示にし(オーバーロードを含む)、親クラスの同じ名前の関数にアクセスするためのスコープを追加します。クラス
2.5.4. 同名静的メンバの継承処理方法
まず、前述した静的メンバー変数の特性を確認します。すべてのオブジェクトは同じデータを共有し、メモリはコンパイル中に割り当てられます。また、クラス内で宣言されクラス外で初期化される静的メンバー関数の特性も確認します。静的メンバー変数のみにアクセスできます。非静的メンバー変数には
アクセスできず、すべてのオブジェクトが 1 つのデータを共有します。
質問: 継承内で同じ名前を持つ静的メンバーにサブクラス オブジェクトでアクセスするにはどうすればよいですか?
同じ名前の静的メンバーと非静的メンバーは同じ方法で処理されます。
- 同じ名前のサブクラス メンバーに直接アクセスできる
- 親クラスと同じ名前のメンバーへのアクセスにはスコープが必要です
class Base {
public:
static void func()
{
cout << "Base - static void func()" << endl;
}
static void func(int a)
{
cout << "Base - static void func(int a)" << endl;
}
static int m_A;
};
int Base::m_A = 100;
class Son : public Base {
public:
static void func()
{
cout << "Son - static void func()" << endl;
}
static int m_A;
};
int Son::m_A = 200;
//同名成员属性
void test01()
{
//通过对象访问
cout << "通过对象访问: " << endl;
Son s;
cout << "Son 下 m_A = " << s.m_A << endl;
cout << "Base 下 m_A = " << s.Base::m_A << endl;
//通过类名访问
cout << "通过类名访问: " << endl;
cout << "Son 下 m_A = " << Son::m_A << endl;
//通过子类对象访问父类的静态成员数据,第一个::表示通过类名的方式访问,第二个::表示表示访问父类作用域下的
cout << "Base 下 m_A = " << Son::Base::m_A << endl;
}
//同名成员函数
void test02()
{
//通过对象访问
cout << "通过对象访问: " << endl;
Son s;
s.func();
s.Base::func();
cout << "通过类名访问: " << endl;
Son::func();
//通过类名访问父类中的func()。
Son::Base::func();
//出现同名,子类会隐藏掉父类中所有同名成员函数,需要加作作用域访问,如果下面的Base不加还是报错。
Son::Base::func(100);
}
int main() {
//test01();
test02();
system("pause");
return 0;
}
概要: 同じ名前の静的メンバーの処理方法は、2 つのアクセス方法 (オブジェクト経由とクラス名経由) があり、非静的メンバーにはオブジェクト経由でのみアクセスできる点を除いて、非静的メンバーの処理方法と同じです。
2.5.5. 多重継承
C++ では、クラスが複数のクラスを継承でき
ます文法: class 子类 :继承方式 父类1 , 继承方式 父类2...
多重継承により、親クラスに同じ名前のメンバーが現れる可能性があり、それを区別するためにスコープを追加する必要があります。実際の
C++ の開発では、複数のクラスを使用することは推奨されません。継承
class Base1 {
public:
Base1()
{
m_A = 100;
}
public:
int m_A;
};
class Base2 {
public:
Base2()
{
m_A = 200; //开始是m_B 不会出问题,但是改为mA就会出现不明确
}
public:
int m_A;
};
//语法:class 子类:继承方式 父类1 ,继承方式 父类2
class Son : public Base2, public Base1
{
public:
Son()
{
m_C = 300;
m_D = 400;
}
public:
int m_C;
int m_D;
};
//多继承容易产生成员同名的情况
//通过使用类名作用域可以区分调用哪一个基类的成员
void test01()
{
Son s;
//输出16,两个父类里面都继承了
cout << "sizeof Son = " << sizeof(s) << endl;
//当父类中出现同名的时候,使用的时候需要加作用域,所以一般不建议使用多继承
cout << s.Base1::m_A << endl;
cout << s.Base2::m_A << endl;
}
int main() {
test01();
system("pause");
return 0;
}
2.5.6. ダイヤモンドの継承
ダイヤモンド継承の概念:
2 つの派生クラスが同じ基本クラスを継承し、別のクラスが 2 つの派生クラスを同時に継承するこの種の継承は、ダイヤモンド継承またはダイヤモンド継承と呼ばれます。
例を見てみましょう:
- 羊は動物のデータを引き継ぎ、ラクダも動物のデータを引き継ぎますが、草泥馬がデータを利用する場合、羊とラクダは同じデータを引き継ぐため、この時点で曖昧さが生じます。
- 草泥馬は動物データの 2 つのコピーを継承しましたが、実際には、このデータのコピーは 1 つだけ必要であることを明確にする必要があります。
class Animal
{
public:
int m_Age;
};
//继承前加virtual关键字后,变为虚继承
//此时公共的父类Animal称为虚基类
class Sheep : virtual public Animal
{
};
class Tuo : virtual public Animal
{
};
class SheepTuo : public Sheep, public Tuo
{
};
void test01()
{
SheepTuo st;
st.Sheep::m_Age = 100;//这里有个注意的地方,::的优先级高于.的优先级,这里注意一下,不然有时候这样写代码看着很迷惑
st.Tuo::m_Age = 200;
//当菱形继承时,两个父类具有相同的数据,需要加作用域区分
cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;
cout << "st.Tuo::m_Age = " << st.Tuo::m_Age << endl;
//这份数据我们知道 只要有一份就可以,菱形继承导致数据有两份,资源浪费。那么这里输出的应该是100还是200呢?怎么解决这个问题?利用虚继承可以解决菱形继承的问题,继承之前 加上关键字 virtual 变为虚继承。这个时候的输出为200,包括上面的输出也变为了200。当我们发生虚继承之后只保留最后一次的,也就是只能有一份数据。
cout << "st.m_Age = " << st.m_Age << endl;
}
int main() {
test01();
system("pause");
return 0;
}
開発者コマンド プロンプト ツールを使用して確認してください。
上の図は仮想基底クラス ポインターであり、 (仮想基底クラス テーブル)vbptr
を指します。vbtable
要約:
- ダイヤモンド継承によって引き起こされる主な問題は、サブクラスが同じデータの 2 つのコピーを継承し、リソースの無駄と無意味が生じることです。
- 仮想継承を使用すると、ダイヤモンド継承の問題を解決できます (仮想基底クラス ポインター、後で仮想関数ポインターが存在します)。
- 使用はお勧めしません
2.6. ポリモーフィズム
ポリモーフィズムは、C++ の 3 つのオブジェクト指向機能の 1 つです。
ポリモーフィズムは 2 つのカテゴリに分類されます。
- 静的多態性:関数のオーバーロードと演算子のオーバーロード静的ポリモーフィズムに属し、関数名を再利用します
- 動的ポリモーフィズム: 派生クラスと仮想関数は実行時ポリモーフィズムを実装します。通常、ポリモーフィズムを動的ポリモーフィズムと呼びます。
静的ポリモーフィズムと動的ポリモーフィズムの違い:
- 静的多態性関数アドレス早期バインディング- コンパイル段階で関数アドレスが決定されます。
- 動的多態性関数アドレス遅延バインディング- ランタイムが関数アドレスを決定します
C++ では、親と子の間の型変換は必須の型変換なしで許可されており、親クラスのポインターまたは参照はサブクラスのオブジェクトを直接指すことができます。
上記のコード出力は動物の会話です。これは、関数のアドレスがアドレス バインディングに属し、受信オブジェクトがそのAnimal
アドレスにバインドされており、関数のアドレスがコンパイル段階で決定されているためです。Let the cat talk を実行したい場合、この関数のアドレスを事前にバインドすることはできません。実行フェーズ中にバインドする必要があり、アドレスは後でバインドされます。どうやってするの?親クラスの関数の前に追加することでvirtual
、これを追加するとvirtual
以下のサブクラスは親クラスと同名の関数を実装した上でアドレスレイトバインディングを実現できます。
ポリモーフィズムは次の条件を満たします。
1. 継承関係がある
2. サブクラスは親クラスの仮想関数をオーバーライドし、仮想キーワードは記述しても記述しなくてもよい
ポリモーフィックな使用:
親クラスのポインタまたは参照がサブクラス オブジェクトを指している
以下の例を参照してください。
class Animal
{
public:
//Speak函数就是虚函数
//函数前面加上virtual关键字,变成虚函数,那么编译器在编译的时候就不能确定函数调用了。
virtual void speak()
{
cout << "动物在说话" << endl;
}
};
class Cat :public Animal
{
public:
void speak()
{
cout << "小猫在说话" << endl;
}
};
class Dog :public Animal
{
public:
void speak()
{
cout << "小狗在说话" << endl;
}
};
//我们希望传入什么对象,那么就调用什么对象的函数
//如果函数地址在编译阶段就能确定,那么静态联编
//如果函数地址在运行阶段才能确定,就是动态联编
void DoSpeak(Animal & animal)
{
animal.speak();
}
//
//动态多态满足条件:
//1、有继承关系
//2、子类重写父类中的虚函数,virtual关键字可写可不写
//多态使用:
//父类指针或引用指向子类对象
void test01()
{
Cat cat;
DoSpeak(cat);
Dog dog;
DoSpeak(dog);
}
int main() {
test01();
system("pause");
return 0;
}
動的ポリモーフィズムは次の条件を満たします。
1. 継承関係がある
2. サブクラス親クラスの仮想関数をオーバーライドする、virtual
キーワードは書いても書かなくても構いません
ポリモーフィックな使用:
親クラスのポインタまたは参照がサブクラス オブジェクトを指している
2.6.1. ポリモーフィズムの低レベルの原理
上図の核となるのは仮想関数テーブルで、サブクラス内の仮想関数テーブルはサブクラスの関数アドレスに置き換えられます。親クラスに仮想関数テーブル ポインタがあり、親クラスの仮想関数を指します。サブクラスが親クラスの関数を書き換えると、親クラス オブジェクトの参照またはポインタがサブクラスを指し、ポリモーフィズムが発生します。このとき、ポインタはサブクラスを指します。この区別と上記の仮想基底クラス ポインターの違いに注意してください。
ポリモーフィズムの利点:
- コード構成が明確である
- 読みやすい
- 初期および後期の拡張とメンテナンスに役立つ
計算機を実装するケースを見てみましょう。
//普通实现
class Calculator {
public:
int getResult(string oper)
{
if (oper == "+") {
return m_Num1 + m_Num2;
}
else if (oper == "-") {
return m_Num1 - m_Num2;
}
else if (oper == "*") {
return m_Num1 * m_Num2;
}
//如果要提供新的运算,需要修改源码
}
public:
int m_Num1;
int m_Num2;
};
void test01()
{
//普通实现测试
Calculator c;
c.m_Num1 = 10;
c.m_Num2 = 10;
cout << c.m_Num1 << " + " << c.m_Num2 << " = " << c.getResult("+") << endl;
cout << c.m_Num1 << " - " << c.m_Num2 << " = " << c.getResult("-") << endl;
cout << c.m_Num1 << " * " << c.m_Num2 << " = " << c.getResult("*") << endl;
}
//多态实现
//抽象计算器类
//多态优点:代码组织结构清晰,可读性强,利于前期和后期的扩展以及维护
class AbstractCalculator
{
public :
virtual int getResult()
{
return 0;
}
int m_Num1;
int m_Num2;
};
//加法计算器
class AddCalculator :public AbstractCalculator
{
public:
int getResult()
{
return m_Num1 + m_Num2;
}
};
//减法计算器
class SubCalculator :public AbstractCalculator
{
public:
int getResult()
{
return m_Num1 - m_Num2;
}
};
//乘法计算器
class MulCalculator :public AbstractCalculator
{
public:
int getResult()
{
return m_Num1 * m_Num2;
}
};
void test02()
{
//创建加法计算器
AbstractCalculator *abc = new AddCalculator;
abc->m_Num1 = 10;
abc->m_Num2 = 10;
cout << abc->m_Num1 << " + " << abc->m_Num2 << " = " << abc->getResult() << endl;
delete abc; //用完了记得销毁
//创建减法计算器
abc = new SubCalculator;
abc->m_Num1 = 10;
abc->m_Num2 = 10;
cout << abc->m_Num1 << " - " << abc->m_Num2 << " = " << abc->getResult() << endl;
delete abc;
//创建乘法计算器
abc = new MulCalculator;
abc->m_Num1 = 10;
abc->m_Num2 = 10;
cout << abc->m_Num1 << " * " << abc->m_Num2 << " = " << abc->getResult() << endl;
delete abc;
}
int main() {
//test01();
test02();
system("pause");
return 0;
}
概要: ポリモーフィズムには多くの利点があるため、C++ 開発ではポリモーフィズム設計プログラム アーキテクチャの使用が推奨されています。
2.6.2. 純粋仮想関数と抽象クラス
ポリモーフィズムでは、通常、親クラスでの仮想関数の実装は無意味で、主にサブクラスによって書き換えられた内容を呼び出すため、仮想関数を純粋な仮想関数 に変更できます。親クラスが意味をなす場合には、純粋な仮想関数を記述する必要はありません。
純粋仮想関数の構文:virtual 返回值类型 函数名 (参数列表)= 0 ;
クラスに純粋仮想関数がある場合、このクラスも呼び出されます。抽象クラス
抽象クラスは主にインターフェイスとポリモーフィズムを実装するために使用され、ポインターまたは参照の形式でのみ使用できます。つまり、抽象クラスへのポインターまたは参照を作成できます。
抽象クラスの機能:
- オブジェクトをインスタンス化できませんでした
- サブクラスは抽象クラスの純粋仮想関数をオーバーライドする必要があります。そうでない場合は、サブクラスも抽象クラスに属します。
class Base
{
public:
//纯虚函数
//类中只要有一个纯虚函数就称为抽象类
//抽象类无法实例化对象
//子类必须重写父类中的纯虚函数,否则也属于抽象类,无法实例化对象
virtual void func() = 0; //纯虚函数,前面必须加virtual才可以等于0
};
class Son :public Base
{
public:
//子类必须重写父类中的纯虚函数,否则也属于抽象类,无法实例化对象
virtual void func()
{
cout << "func调用" << endl;
};
};
void test01()
{
Base * base = NULL;
// Base b;// 错误,抽象类无法实例化对象
//base = new Base; // 错误,抽象类无法实例化对象。无论栈还是堆方式都不可以
base = new Son;
base->func();
delete base;//记得销毁
}
int main() {
test01();
system("pause");
return 0;
}
ポリモーフィック テクノロジ:親クラスのポインタは、サブクラスのオブジェクトを参照またはポイントするために使用されます。
別の例を見て、さまざまな飲み物を作ってみましょう。
//抽象制作饮品
class AbstractDrinking {
public:
//烧水
virtual void Boil() = 0;
//冲泡
virtual void Brew() = 0;
//倒入杯中
virtual void PourInCup() = 0;
//加入辅料
virtual void PutSomething() = 0;
//规定流程
void MakeDrink() {
Boil();
Brew();
PourInCup();
PutSomething();
}
};
//制作咖啡
class Coffee : public AbstractDrinking {
public:
//烧水
virtual void Boil() {
cout << "煮农夫山泉!" << endl;
}
//冲泡
virtual void Brew() {
cout << "冲泡咖啡!" << endl;
}
//倒入杯中
virtual void PourInCup() {
cout << "将咖啡倒入杯中!" << endl;
}
//加入辅料
virtual void PutSomething() {
cout << "加入牛奶!" << endl;
}
};
//制作茶水
class Tea : public AbstractDrinking {
public:
//烧水
virtual void Boil() {
cout << "煮自来水!" << endl;
}
//冲泡
virtual void Brew() {
cout << "冲泡茶叶!" << endl;
}
//倒入杯中
virtual void PourInCup() {
cout << "将茶水倒入杯中!" << endl;
}
//加入辅料
virtual void PutSomething() {
cout << "加入枸杞!" << endl;
}
};
//业务函数
void DoWork(AbstractDrinking* drink) {
drink->MakeDrink();
delete drink;
}
void test01() {
DoWork(new Coffee);
cout << "--------------" << endl;
DoWork(new Tea);
}
int main() {
test01();
system("pause");
return 0;
}
ポリモーフィズムの核心は、親クラスのポインターまたは同様のオブジェクトへの参照を通じて、サブクラスから書き換えられた関数を探すことです。
2.6.3. 仮想デストラクターと純粋仮想デストラクター
仮想デストラクターと純粋仮想デストラクターを紹介しましょう。なぜこれを知る必要があるのでしょうか? ポリモーフィズムが使用される場合、親クラスのポインターまたは参照がサブクラス オブジェクトを指すために使用されることはわかっていますが、親クラスのポインターはサブクラス内のデストラクター コードを解放できないため、サブクラス内のデータが開かれます。ヒープ領域に親クラスのポインタがあると、delete
サブクラスのデータを解放できなくなり、メモリリークが発生します。
ポリモーフィズムを使用する場合、ヒープ領域に割り当てられているサブクラスの属性がある場合、親クラスのポインタは解放時にサブクラスのデストラクタ コードを呼び出すことができません。解決策: 親クラスのデストラクタ関数を仮想関数に変更してください
。デストラクターまたは純粋な仮想デストラクター
仮想デストラクターと純粋仮想デストラクターの共通機能:
- 親クラスのポインタがサブクラスのオブジェクトを解放できない問題を解決できます。
- 特定の関数を実装する必要がある
仮想デストラクターと純粋な仮想デストラクターの違い:
- コード内に存在する場合
纯虚析构
、そのクラスは抽象クラスであり、オブジェクトはインスタンス化できません。これは上記の純粋仮想関数と競合せず、デストラクターも関数です。
仮想デストラクターの構文:
virtual ~类名(){}
純粋な仮想デストラクターの構文:
virtual ~类名() = 0;
类名::~类名(){}
class Animal {
public:
Animal()
{
cout << "Animal 构造函数调用!" << endl;
}
virtual void Speak() = 0;
//析构函数加上virtual关键字,变成虚析构函数
//virtual ~Animal()
//{
// cout << "Animal虚析构函数调用!" << endl;
//}
/*纯虚析构,利用纯虚析构和虚析构都可以解决,只能用一个。纯虚析构里面也要写上代码实现,不然直接写这句话报错,
类外写上代码实现。区分跟纯虚函数的区别,纯虚函数可以直接写等于0*/
//有了纯虚析构 之后 ,这个类也属于抽象类,无法实例化对象
virtual ~Animal() = 0;
};
//如果这里不使用虚析构,将无法释放子类中的堆区数据
Animal::~Animal()
{
cout << "Animal 纯虚析构函数调用!" << endl;
}
//和包含普通纯虚函数的类一样,包含了纯虚析构函数的类也是一个抽象类。不能够被实例化。
class Cat : public Animal {
public:
Cat(string name)
{
cout << "Cat构造函数调用!" << endl;
m_Name = new string(name);
}
virtual void Speak()
{
cout << *m_Name << "小猫在说话!" << endl;
}
~Cat()
{
cout << "Cat析构函数调用!" << endl;
if (this->m_Name != NULL) {
delete m_Name;
m_Name = NULL;
}
}
public:
string *m_Name;
};
void test01()
{
Animal *animal = new Cat("Tom");
animal->Speak();
//父类指针在析构时候 不会调用子类中析构函数,导致子类如果有堆区属性,出现内存泄漏
//怎么解决?把基类改成一个虚析构函数
//虚析构函数就是用来解决通过父类指针释放子类对象,是因为在运行时,会根据对象的实际类型调用相应的析构函数。这样就可以确保子类对象被正确地销毁,避免了内存泄漏和其他异常问题。
delete animal;
}
int main() {
test01();
system("pause");
return 0;
}
要約:
- 仮想デストラクターまたは純粋仮想デストラクターは、親クラス ポインターを介してサブクラス オブジェクトを解放する問題を解決するために使用されます。どちらも使用できます。違いは、純粋仮想デストラクターを作成した後、このクラスは抽象クラスに属することです。
- サブクラスにヒープ領域データが存在しない場合、仮想デストラクタまたは純粋仮想デストラクタとして記述することはできません。
- 純粋な仮想デストラクタを持つクラスも抽象クラスです
- 仮想デストラクターと純粋仮想デストラクターの両方に次のものがある必要があります。特定の文字数値を実現しました。
- 親クラスの純粋な仮想デストラクターも抽象クラスであり、オブジェクトをインスタンス化することはできません。
2.7. ドキュメント
2.7.1. ファイル操作
プログラムの実行中に生成されるデータはすべて一時的なデータであり、プログラムの実行が終了すると解放されます。
C++ のファイル操作もオブジェクト指向操作に基づいています。
データはファイルを通じて永続化できます
C++ でのファイル操作にはヘッダー ファイルを含める必要があります< fストリーム >
次の 2 つのファイル タイプがあります。
- テキスト ファイル- ファイルはASCII形式のテキストとしてコンピュータに保存されます。
- バイナリ ファイル- ファイルはバイナリ形式のテキストでコンピュータに保存され、通常はユーザーが直接読み取ることはできません。
オペレーティング ファイルの 3 つのカテゴリ:
- ofstream: 書き込み操作
- ifstream: 読み取り操作
- fstream: 読み取りおよび書き込み操作
2.7.2. テキストファイル
ファイルを書き込む手順は次のとおりです。
- ヘッダー ファイルをインクルードします
#include <fstream>- ストリーム オブジェクト
ofstream ofs を作成します。- ファイルを開きます
ofs.open("ファイルパス", open メソッド);- writedata
ofs << "書き込まれたデータ";
ofs.close();ファイルを閉じます。
ファイルを開く方法:
オープンメソッド | 説明 |
---|---|
ios::で | 読み取り用にファイルを開く |
ios::out | 書き込み用にファイルを開く |
ios::食べた | 初期位置: ファイルの終わり |
ios::アプリ | ファイルを書き込むための追加 |
ios::トランク | ファイルが存在する場合は、まずそれを削除してから作成します |
ios::バイナリ | バイナリモード |
注:ファイルを開くメソッドは | 演算子と組み合わせて使用できます。
例: バイナリモードでファイルを書き込みます。ios::binary | ios:: out
#include <fstream>
void test01()
{
ofstream ofs;
ofs.open("test.txt", ios::out);
ofs << "姓名:张三" << endl;
ofs << "性别:男" << endl;
ofs << "年龄:18" << endl;
ofs.close();
}
int main() {
test01();
system("pause");
return 0;
}
上記のプログラムを実行すると、プログラムの場所に txt ファイルが生成されます
。
- ファイル操作にはヘッダー ファイル fstream が含まれている必要があります
- 読み取りファイルは ofstream または fstream クラスを使用できます
- ファイルを開くときは、操作ファイルのパスと開く方法を指定する必要があります
- データをファイルに書き込むには << を使用します
- 操作が完了したら、ファイルを閉じます
2.7.3. ファイルの読み込み
ファイルを読み取る手順はファイルを書き込む手順と似ていますが、さらに多くの読み取り方法があります。
ファイルを読み取る手順は次のとおりです。
- ヘッダー ファイルをインクルードします
#include <fstream>- ストリーム オブジェクト
ifstream ifs を作成します。- ファイルを開き、ファイルが正常にオープンしたかどうかを判定します。
ifs.open("ファイルパス", openメソッド);- データの読み取り
4 つの読み取り方法- ファイルを閉じる
ifs.close();
#include <fstream>
#include <string>
void test01()
{
ifstream ifs;
ifs.open("test.txt", ios::in);
if (!ifs.is_open())
{
cout << "文件打开失败" << endl;
return;
}
//第一种方式
//char buf[1024] = { 0 };
//while (ifs >> buf)
//{
// cout << buf << endl;
//}
//第二种
//char buf[1024] = { 0 };
//while (ifs.getline(buf,sizeof(buf)))
//{
// cout << buf << endl;
//}
//第三种
//string buf;
//while (getline(ifs, buf))
//{
// cout << buf << endl;
//}
char c;
while ((c = ifs.get()) != EOF)
{
cout << c;
}
ifs.close();
}
int main() {
test01();
system("pause");
return 0;
}
2.7.4. バイナリファイルの書き込み
バイナリモードでファイルを読み書きする
として指定して開きますios::バイナリ
2.7.5. ファイルの書き込み
バイナリ モードでのファイルの書き込みでは、主にストリーム オブジェクトを使用してメンバー関数 write を呼び出します。
関数プロトタイプ:ostream& write(const char * buffer,int len);
パラメータの説明: 文字ポインタ バッファはメモリ内の記憶領域を指します。len は読み書きされるバイト数です
#include <fstream>
#include <string>
class Person
{
public:
char m_Name[64];
int m_Age;
};
//二进制文件 写文件
void test01()
{
//1、包含头文件
//2、创建输出流对象
ofstream ofs("person.txt", ios::out | ios::binary);
//3、打开文件
//ofs.open("person.txt", ios::out | ios::binary);
Person p = {
"张三" , 18};
//4、写文件
ofs.write((const char *)&p, sizeof(p));
//5、关闭文件
ofs.close();
}
int main() {
test01();
system("pause");
return 0;
}
- ファイル入力ストリーム オブジェクトは、読み取り関数を通じてバイナリ モードでデータを読み取ることができます。
2.7.6. ファイルの読み込み
バイナリ モードでのファイルの読み取りでは、主にストリーム オブジェクトを使用してメンバー関数 read を呼び出します。
関数プロトタイプ:istream& read(char *buffer,int len);
パラメータの説明: 文字ポインタ バッファはメモリ内の記憶領域を指します。len は読み書きされるバイト数です
#include <fstream>
#include <string>
class Person
{
public:
char m_Name[64];
int m_Age;
};
void test01()
{
ifstream ifs("person.txt", ios::in | ios::binary);
if (!ifs.is_open())
{
cout << "文件打开失败" << endl;
}
Person p;
ifs.read((char *)&p, sizeof(p));
cout << "姓名: " << p.m_Name << " 年龄: " << p.m_Age << endl;
}
int main() {
test01();
system("pause");
return 0;
}