【C++】C++入門に必要な知識を詳しく解説

まず最初に、C++ は C に基づいており、オブジェクト指向プログラミングのアイデアが組み込まれており、多くの便利なライブラリが追加されていることを知る必要があります。この章では、C++ が C 言語の文法の欠如を補完するものであること、および C++ が C 言語の不合理な設計をどのように最適化するかを理解することができます。

1. 名前空間

1. 名前空間

C/C++ には多数の変数、関数などがあり、これらの変数、関数、クラスの名前はすべてグローバル スコープ内に存在するため、多くの競合が発生する可能性があります。名前空間を使用する目的は、名前の競合を避けるために識別子の名前をローカライズすることです。名前空間キーワードの出現は、この種の問題を目的としています。

たとえば、変数 sqrt を定義したい場合は、次の図に示すように、グローバル変数で直接定義してからコンパイルすることができます。

ここに画像の説明を挿入します
しかし、sqrt は実際には math.h のヘッダー ファイルに含まれるライブラリ関数であることがわかっていますが、math.h のヘッダー ファイルを追加してもコンパイルできるでしょうか? 答えは「いいえ」です。名前が同じなので、 math.h のヘッダー ファイルがインクルードされている場合、コンパイルは成功せず、次の図のエラーが報告されます。
ここに画像の説明を挿入します

では、良い解決策はあるのでしょうか? 答えは「はい」です。C++ では、このような問題を解決するために、名前空間などのキーワードが追加されていますたとえば、定義する必要がある変数を namespace の名前空間に置き、それを使用してコンパイラに指定された名前空間を検索させることができます。コンパイラが指定されていない場合、コンパイラはまずグローバル ドメインで変数を検索します。 ; 名前空間の使用:

		#include <stdio.h>
		#include <math.h>
		
		// 命名空间的名字
		namespace Young
		{
			int sqrt = 10;
		}
		
		int main()
		{
			printf("%d\n", Young::sqrt);
			return 0;
		}

上記のコードを使用すると、コンパイラが指定された名前空間 Young で変数 sqrt を見つけて、この変数を使用できるようになり、ライブラリ関数内の sqrt 関数と名前の競合がなくなります。Young は名前空間です。それ自体で名前が付けられています。 名前は、必ずしもヤングである必要はなく、任意の名前を付けることができます。

画像ではprintf("%d\n", Young::sqrt);、sqrt の前にある :: 記号はスコープ修飾子と呼ばれます。これは、コンパイラーがスコープ修飾子の前の名前空間で定義されたものを使用できるようにすることを意味します。

2. ネームスペースの利用シーン

上記に加えて、名前空間を使用して名前空間内の変数を定義したり、関数や構造体などを定義したり、それらをネストすることもできます。たとえば、次のコード:

		namespace Young
		{
			//变量
			int sqrt = 10;
		
			// 函数
			int Add(int a, int b)
			{
				return a + b;
			}
		
			// 结构体
			struct ListNode
			{
				int data;
				struct ListNode* next;
			};
		
			// 嵌套使用 
			namespace Y
			{
				int a = 10;
			}
		
		}
		
		int main()
		{
			int ret = Young::Add(1, 2);
			printf("%d\n", ret);
		
			struct Young::ListNode node;
		
			printf("%d\n", Young::Y::a);
		
			return 0;
		}

上記のコードの main 関数部分では、構造体のドメイン修飾子は、struct の前ではなく、ListNode の前で使用する必要があります。ネストされた名前空間を使用する場合は、右から左に見て、指定された名前空間を検索します。

この方法は名前の競合の問題を効果的に回避できますが、使用するたびにその前にドメイン修飾子を追加するのは面倒ですか? 確かに、しかし、それを解決する別の方法があります。名前空間を展開します。例として、次のコードのように、上記の名前空間を取り上げます。

		// 将命名空间展开
		using namespace Young;
		using namespace Y;
		
		int main()
		{
			int ret = Add(1, 2);
			printf("%d\n", ret);
		
			struct ListNode node;
		
			printf("%d\n",a);
		
			return 0;
		}

上記のコードは、Young と Y という 2 つの名前空間のコンテンツを展開するため、スコープ修飾子を使用する必要はありません。さらに、一部の名前空間のコンテンツも展開できます。たとえば、次のように Add 関数を展開するだけです。

		// 展开部分
		using Young::Add;
		
		int main()
		{
			int ret = Add(1, 2);
			printf("%d\n", ret);
		
			struct Young::ListNode node;
		
			printf("%d\n", Young::Y::a);
		
			return 0;
		}

上記は拡張された部分の名前空間です。通常、プロジェクトで作業するときは、拡張すると安全でなくなるため、名前空間は拡張しません。しかし、通常、コードの練習を書くときは、名前空間を拡張できます。その方が練習に役立ちます。 。

2. C++ の入出力を理解する

まず、C++ では C 言語とは異なる入出力が導入されていることを知っておいてください。C 言語では入出力として scanf と printf を使用しますが、C++ では cout 標準出力オブジェクト (コンソール) と cin 標準入力オブジェクト (キーボード); まずその使用法を見てみましょう:

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

上記のコードの cout と cin は、それぞれストリーム挿入演算子ストリーム抽出演算子と呼ばれていることがわかります。これら 2 つについては今後の研究で詳しく紹介します。cout と cin には <iostream> ヘッダーが含まれている必要があります。ファイルを作成し、次に従って std を使用します。名前空間の使用法。std は C++ 標準ライブラリの名前空間名です。C++ は、標準ライブラリの定義と実装をこの名前空間に置きます。したがって、std 名前空間を拡張できます。

		#include <iostream>
		using namespace std;
		
		int main()
		{
			int input;
			double d;
			// 自动识别类型
			cin >> input >> d;
		
			cout << input << endl << d << endl;
			return 0;
		}

さらに、cin と cout は、上記のコードのように変数の型を自動的に識別することもでき、その出力は次のようになります。

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

3. デフォルトパラメータ

デフォルト パラメーターは、関数を宣言または定義するときに、関数のパラメーターのデフォルト値を指定します。この関数を呼び出すとき、実パラメータが指定されていない場合は仮パラメータのデフォルト値が使用され、それ以外の場合は指定された実パラメータが使用されます。まずデフォルトのパラメータの使用方法を見てみましょう。

ここに画像の説明を挿入します
上記の使用法では、Add 関数はデフォルトのパラメーターを使用します。Add 関数の定義では、a = 100、b = 200 と指定されています。これは、Add 関数が呼び出されたときにパラメーターが渡されない場合、そのパラメーターが使用されることを意味します。自分で定義した変数。パラメータを渡すときは、以下に示すように、指定された実際のパラメータを使用します。

ここに画像の説明を挿入します
もちろん、パラメータの一部だけを渡すことも可能ですが、パラメータが複数ある場合は、右から左の順にパラメータを渡す必要があり、次のように間隔をあけて渡すことはできません。

		#include <iostream>
		using namespace std;
		
		int Add(int a = 100, int b = 200, int c = 300)
		{
			return a + b + c;
		}
		
		int main()
		{
			int a = 10, b = 20, c = 30;
		
			int ret = Add(a);
			cout << ret << endl;
			return 0;
		}

上記のコードの出力結果は 510 であるため、たとえばint ret = Add(a,,c);このようなパラメーターの受け渡しは許可されません。

上記のコードのように、Add()渡さないものは完全なデフォルト パラメータと呼ばれ、一部だけを渡すものはAdd(a)デフォルト パラメータと呼ばれます。Add(a,b)

最後に、デフォルト パラメータは関数宣言定義の両方に使用できないことに注意してください。関数宣言と関数に同時に使用する場合は、宣言でデフォルト値を指定するだけで済みます。

4. 関数のオーバーロード

1. 関数のオーバーロードの概念

関数のオーバーロード: これは関数の特殊なケースです。C++ では、同様の関数を持つ同じ名前の複数の関数を同じスコープ内で宣言できます。同じ名前のこれらの関数には、異なる仮パラメータ リスト (パラメータの数、型、または型の順序) があります。 ). これは、さまざまなデータ型の問題と同様の関数の実装に対処するためによく使用されます。まずは以下の使い方を見てみましょう。

		#include <iostream>
		using namespace std;
		
		void Add(int a ,double b)
		{
			// 打印数据方便观察
			cout << "void Add(int a ,double b)" << endl;
		}
		
		
		void Add(double a, int b)
		{
			// 打印数据方便观察
			cout << "void Add(double a, int b)" << endl;
		}
		
		
		int main()
		{
		
			Add(3, 3.14);
			Add(3.14, 3);
			
			return 0;
		}

操作の結果は次のとおりです。

ここに画像の説明を挿入します
上記のコードでは、コンパイラがこの関数を呼び出したことを示すために関数内のデータを出力します。同じ名前で 2 つの関数を定義しましたが、それらのパラメータの型が異なり、これら 2 つの関数を使用するときに、パラメータも次のように渡します。異なるため、対応する関数を呼び出します。

2. C++ サポート関数のオーバーロードの原理

C++ が関数のオーバーロードをサポートする理由は、C++ に独自の関数名変更規則があるためです。
.cpp ファイルまたは .c ファイルは、実行可能プログラムを生成する前に、前処理、コンパイル、アセンブリ、およびリンクのプロセスを経る必要があることがわかっています。特に、以前のブログを確認してください: 前処理とプログラミング環境

このうち、シンボル概要は、C 言語のコンパイル処理中に、すべての .c ファイルの関数名をまとめたものです。これは関数名であることに注意してください。そのため、C 言語では、同じ名前の関数名はコンパイル処理中に競合します。となり、コンパイルは成功しません。

ただし、C++ の関数名の変更規則では、C++ は関数名でまとめられるのではなく、独自の変更規則があり、具体的な変更規則はコンパイラごとに異なります。

		void func(int i, double d)
		{}
		
		void func(double d, int i)
		{}

これら 2 つの関数は、g++ コンパイラの関数変更後、図に示すように [_Z+関数の長さ+関数名+型の初期値] になります。

ここに画像の説明を挿入します
ここに画像の説明を挿入します
したがって、コンパイルおよび要約するときにそれらを区別できます。

5. 引用

1. 引用の概念

参照は変数の新しい定義ではなく、既存の変数のエイリアスです。コンパイラは参照変数用にメモリ空間をオープンせず、参照する変数と同じメモリ空間を共有します。まず簡単な例を見てみましょう。

		#include <iostream>
		using namespace std;
		
		int main()
		{
			int a = 10;
			int& b = a;
			return 0;
		}

上記のコードはint& b = a;参照型を定義しています。b は a のエイリアスです。a と b は実際には同じ空間を指します。a の変更は b に影響し、b の変更は a にも影響します。

2. 引用特性

  1. 参照は定義時に初期化する必要があります

  2. 変数には複数の参照を含めることができます

  3. 参照が 1 つのエンティティを参照すると、他のエンティティを参照できなくなります。

     	void Test()
     	{
     		int a = 10;
     		// int& ra;   // 该语句编译时会出错
     		int& ra = a;
     		int& rra = a;
     	}
    

int& ra; コンパイルエラーとなるのは、定義時に初期化が行われていないためで、上記のコードでは、rra は ra と a のエイリアスであり、これら 3 つの変数は同じ空間を使用しており、相互に変更すると相互に影響を及ぼします。

3. よく引用されるもの

引用を行う際には、引用の過程で権限を移動したり、権限を縮小したりすることはできますが、権限を拡大することはできないというルールを遵守する必要があります。例えば:

		int main()
		{
			const int a = 0;
			// 权限的放大,不允许
			//int& b = a;
		
			// 不算权限的放大,因为这里是赋值拷贝,b修改不影响a
			//int b = a; 
		
			// 权限的平移,允许
			const int& c = a;
		
			// 权限的缩小,允许
			int x = 0;
			const int& y = x;
		
			return 0;
		}

上記のコードでは、アクセス許可の拡大は、const int a = 0;const によって変更された変数 a は定数、変更不可、読み取り専用ですが、int& b = a;b の値は変更可能であり、b の値の変更は a と b に影響を与えることを意味します。は読み取り可能および書き込み可能ですが、 a は読み取り専用であるため、これはアクセス許可の増幅となります。ただし、int b = a; これは割り当てコピーであり、 b の変更は a に影響を与えないため、アクセス許可の増幅としてカウントされません。

パーミッションの翻訳とは、全員が同じパーミッションを持つことを意味しており、例えばconst int& c = a;上記のコードのcとaをconstで修正し、全員が定数であるため、これが可能なパーミッションの翻訳です。

上記のコードでのアクセス許可の絞り込みはint x = 0; const int& y = x;、x は読み取りおよび書き込み可能ですが、y は const によって変更され、読み取り専用になりますが、読み取りおよび書き込み可能から読み取り専用への変更は許可されることを意味します。これはアクセス許可の絞り込みと呼ばれます。 。

それでは、次のステートメントが何に属するかを見てみましょう。

		void test()
		{
			int i = 0;
			double& d = i;
		}

まず第一に、int i = 0; double d = i;それらの間で整数昇格が発生するため、それが可能であることを明確に理解する必要があります。次に、この整数昇格プロセス中にコピー プロセスが発生し、d が i の一時コピーを取得することを明確にする必要があります。次の図に示すように、この一時コピーは永続的で変更できないため、これは権限の拡大であり、許可されていません。
ここに画像の説明を挿入します
したがって、正しいステートメントは次のようになります。

		void test()
		{
			int i = 0;
			const double& d = i;
		}

d の属性も変更不可能にすると、それらの間には翻訳関係が存在します。

4. 参考利用シーン

(1) パラメータの作成(パラメータの参照渡し)

パラメータを参照渡しする一般的な方法は交換関数です。一般的に使用される交換関数は次のように記述します。

		#include <iostream>
		using namespace std;
		
		void Swap(int* p1, int* p2)
		{
			int tmp = *p1;
			*p1 = *p2;
			*p2 = tmp;
		}
		
		int main()
		{
			int a = 10, b = 20;
		
			Swap(&a, &b);
			
			return 0;
		}

この交換関数では、a と b の値を変更するために a のアドレスと b のアドレスを渡す必要があります。C++ では、参照を使用して同じ交換を完了できます。コードは次のとおりです。

		void Swap(int& p1, int& p2)
		{
			int tmp = p1;
			p1 = p2;
			p2 = tmp;
		}
		
		int main()
		{
			int a = 10, b = 20;
		
			Swap(a, b);
		
			return 0;
		}

参照を使用すると、コード全体が非常に快適になります。ポインタのようにアドレスや逆参照を渡す必要はありません。同時に、参照やパラメータを渡すと、アドレスや値が渡されるたびにパラメータを渡す効率も向上します。一度に 1 つのコピーが必要であり、非常に非効率的ですが、仮パラメータは実パラメータのエイリアスであるため、参照をコピーする必要はありません。

また、参照やパラメータを渡すのに最も快適な場所は、以前に学習した単一リンク リスト内です。たとえば、以前のブログの単一リンク リストでは、先頭挿入か末尾挿入など、セカンダリ ポインタを渡す。リンク リストの全体的な構造を変更し、C++ が参照を導入した後は、次のコードのようにセカンダリ ポインタを渡す必要がなくなります。

		void SLTPushBack(SLTNode*& phead, SLTDateType x)
		{
		    // ...
		    if (phead == NULL)
		    {
		        phead = newnode;
		    }
		    else
		    {
		        //...
		    }
		}
		
		int main()
		{
		    SLTNode* plist = NULL;
		
		    SLTPushBack(plist, 1);
		    SLTPushBack(plist, 2);
		    SLTPushBack(plist, 3);
		
		    return 0;
		}

(2) 戻り値(参照戻り)

参照による戻りを使用する場合は注意が必要です。パラメータの参照による受け渡しとは異なり、参照による戻りは関数のスコープ オブジェクトがまだ存在する場合にのみ使用できます。関数のスコープ オブジェクトが関数のスコープ外にある場合は使用できません。次のコードのように:

		int& func()
		{
			int n = 0;
			n = 10;
		
			return n;
		}
		
		int main()
		{
			int ret = func();
			
			return 0;
		}

このコードでは、変数 n は関数 func で定義されていますが、そのライフサイクルはこの関数内のみにあります。関数のスコープ外に出ると、その空間は破棄されます。よりよく理解するために図を描いてください:

ここに画像の説明を挿入します
上図に示すように、func が破棄された後、n も破棄され、その領域がオペレーティング システムに返されますが、main 関数では、ret は実際には破棄された n にアクセスすることと等価です。ワイルド ポインタの問題、つまり国境を越えたアクセスです。

ただし、コンパイラによって結果は異なりますが、vs2019 では、次のように n の値を取得できます。
ここに画像の説明を挿入します

gcc/g++ コンパイラでは、以下のようなエラーが報告されます:
ここに画像の説明を挿入します
理由は、スタック フレームが破壊された後に、コンパイラが破壊された領域を初期化するかどうかに依存するためです。 gcc/g++ のようなコンパイラは明らかにスペースのリサイクル中にスペースを初期化するため、範囲外が発生しますが、vs2019 には厳密なチェックがありません。

拡張子: コードを次のように変更してもコンパイルできますか?

		int& func()
		{
			int n = 0;
			n = 10;
		
			return n;
		}
		
		int main()
		{
			int& ret = func();
			cout << ret << endl;
			cout << ret << endl;
		
			return 0;
		
		}

ここでは、ret の受け取りが参照に変更されています。つまり、ret は返された n のエイリアスです。実行結果を見てみましょう。

ここに画像の説明を挿入します
2 回目の実行はランダムな値ですが、なぜですか? その理由は、ret は n のエイリアスであり、同じ空間を共有しているためです。cout ステートメントが実行されると、一連の関数スタック フレームも作成されるため、新しい空間が前の関数が配置されている空間を上書きします。 、つまり n の空間が覆われている、つまり ret の空間が覆われているため、n の値はランダムな値になります; 初めて 10 になるのは、元の空間が覆われていないためです。

そこで別の話題になりますが、n のスペースがカバーされていない場合、それは 10 のままですか? 次に、コードを次のコードに変更します。

		int& func()
		{
			int a[1000];
			int n = 0;
			n = 10;
		
			return n;
		}
		
		int main()
		{
			int& ret = func();
			
			cout << ret << endl;
			cout << ret << endl;
		
			return 0;
		}

func 関数内に、長さ 1000 の配列を追加しました。まず実行結果を見てみましょう。

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

このとき、再び10になりますが、これは関数のスタックフレームに下方向にスペースを作るためですので、func関数では、まずスペースを1000個作成し、次にn分のスペースを作成し、そのときのnの位置を指定します。時間が一番下になります func が破壊された場合、カバーされる新しい空間がある場合、この空間が元の func 空間より大きいかどうかによって決まります。この空間が大きくて n をカバーする場合、n はランダムな値になります。それ以外の場合は、n がランダムな値になります。 , n は元の値のままです。

では、参照による戻りのアプリケーション シナリオは何でしょうか? 一般的な参照による戻りを使用して、戻りオブジェクトを変更できます。たとえば、単一リンク リストでは、検索関数と変更関数を一緒に記述して参照による戻りに​​使用でき、検索したいデータを見つけることができます。変更する値。たとえば、次のコード:

		int& SLFindOrModify(struct SeqList& ps, int i)
		{
			assert(i < ps.size);
			// ...
			return (ps.a[i]);
		}
		
		int main()
		{
			// 定义对象
			struct SeqList s;
			
			// 查找 10 这个数据,并将它修改成 20
			SLFindOrModify(s, 10) = 20;
		
			return 0;
		}

(3) 参照とポインタの違い

ポインタと参照について学習しましたが、参照とポインタは実際には非常に似ていることがわかります。多くの用途で、ポインタは参照の代わりに使用でき、参照もポインタの代わりに使用できます。では、これらの違いは何でしょうか? それらを 1 つずつ分析してみましょう。

参照とポインタの違い:

  1. 参照は概念的に変数の別名を定義し、ポインタは変数のアドレスを格納します。
  2. 参照は定義時に初期化する必要があり、ポインタは必要ありません
  3. 初期化中に参照がエンティティを参照すると、他のエンティティを参照できなくなり、ポインタはいつでも
    同じタイプのエンティティを指すことができます。
  4. NULL 参照はありませんが、NULL ポインタはあります
  5. sizeof の意味は異なります。参照結果は参照型のサイズですが、ポインタは常にアドレス空間によって占有されているバイト数 (32 ビット プラットフォームでは
    4 バイト)です。
  6. 参照自己インクリメントは、参照されるエンティティが 1 ずつ増加することを意味し、ポインター自己インクリメントは、ポインターが型のサイズだけ後方にオフセットされることを意味します。
  7. マルチレベル ポインタはありますが、マルチレベル参照はありません
  8. エンティティにアクセスする方法が異なり、ポインタを明示的に逆参照する必要があり、参照コンパイラがそれを単独で処理します。
  9. 参照はポインタよりも比較的安全に使用できます。

6. インライン関数

1. #define はマクロを定義します

以前にマクロを定義する #define を学習しました。たとえば、前のブログ#define でマクロを定義すると、マクロは多くの利点をもたらします。たとえば、頻繁に呼び出される小さな関数の場合、スタック フレームを作成する必要はありません。次のコードのように効率が向上します。

		#define ADD(a,b) ((a)+(b))
		
		int main()
		{
			int ret = ADD(10, 20);
		
			cout << ret << endl;
			return 0;
		}

上記のマクロは 2 つの数値の加算を定義していますが、ここでのマクロ定義は「Executing | and & again」のようにこと((a)+(b))記述する(a+b)ADD(1 | 2 + 1 & 2)

上記マクロ定義は前処理段階で直接展開・置換されるため、スタックフレームが発生せず、効率が大幅に向上します。

ただし、マクロは便利な反面、不便も伴います。マクロ定義を使用すると間違いが発生しやすくなります。上記の2つの数値を加算するマクロと同様に、括弧が1つもないと機能しません。マクロには構文上の落とし穴がたくさんあります。

最後に、マクロの長所と短所をまとめます。

アドバンテージ:

  1. 種類に厳密な制限はありません。
  2. 効率を上げるための関数スタックフレームの確立はありません。

欠点:

  1. マクロをデバッグするのは不便です。(プリコンパイルフェーズが置き換えられるため)
  2. コードの可読性が低下します。
  3. 型安全性のチェックはありません。
  4. 間違いを犯しやすく、文法上の落とし穴もたくさんあります。

2. インライン関数の概念

そのため、C++ ではインライン関数 が導入されました。インラインで変更された関数はインライン関数と呼ばれます。コンパイル時に、C++ コンパイラはインライン関数が呼び出される場所を展開します。スタック フレームを確立するための関数呼び出しのオーバーヘッドはありません。インライン関数により効率が向上します。プログラム動作です。

たとえば、2 つの数値を加算する次のインライン関数は次のとおりです。

		inline int Add(int a, int b)
		{
			return a + b;
		}
		
		int main()
		{
			int ret = Add(10, 20);
		
			cout << ret << endl;
			return 0;
		}

上記のコードでは、2 つの数値を加算するインライン関数は関数スタック フレームを作成せず、パフォーマンスがよく反映されており、演算子の問題により多くの括弧を追加する必要がないため、インライン関数はマクロと関数の長所と短所を組み合わせて設計されています。

2. インライン関数の特徴

(1) inline は空間と時間を交換する方法であり、コンパイラが関数をインライン関数として扱うと、コンパイル段階で関数呼び出しを関数本体に置き換えます。欠点: オブジェクト ファイルが大きくなる可能性があります。 : 少ない 呼び出しのオーバーヘッドを削減し、プログラムの実行効率を向上させます。

(2) inline はコンパイラに対する単なる提案です。コンパイラが異なれば、inline の実装メカニズムも異なる可能性があります。一般的な提案は、関数を小さくすることです (つまり、関数がそれほど長くないことです。正確なステートメントはありません)。コンパイラの内部実装に依存します。)、再帰的ではなく、頻繁に呼び出される関数はインライン変更を使用します。それ以外の場合、コンパイラはインライン機能を無視します。

つまり、inline を使用する場合、関数のサイズが大きくコード量が多いとコード拡張が発生するため、コンパイラは必ずしもこの関数をインライン関数として認識しない可能性があるため、全体的なパフォーマンスを考慮して、インライン関数を使用してコードを簡素化しようとした場合。

(3)インラインで宣言と定義を分離するとリンクエラーが発生するため推奨しません。inline展開なので関数アドレスがなくリンクが見つかりません。

たとえば、Test.hAdd 関数の宣言を含むヘッダー ファイルを定義します。

		inline int Add(int a, int b);

Test.cppAdd 関数の実装を含む別のファイルを定義します。

		#include "Test.h"
		int Add(int a, int b)
		{
			return a + b;
		}

次に、main.cpp関数内で Add 関数を呼び出します。

		#include "Test.h"

		int main()
		{
			int ret = Add(10, 20);
		
			cout << ret << endl;
			return 0;
		}

以下に示すように、最終的なコンパイル エラーが発生しました。

ここに画像の説明を挿入します
その理由は何でしょうか? 理由は、#include "Test.h" 前処理の段階でヘッダーファイルが main.cpp ファイル内に展開され、展開後に Add 関数の宣言があり、Add 関数の前に inline が追加されるため、コンパイラはそれを認識するためです。インライン関数であり、それがインライン関数であると考えられます。直接展開されるため、コンパイル段階では有効なアドレスが与えられず、シンボル テーブルに入りません。また、メイン関数で Add 関数が呼び出されたときも同様です。の場合、シンボル テーブル内に対応する関数のアドレスが見つからないため、リンク エラーが発生しました。

7. 自動キーワード

C++11 では、auto は、 auto で宣言された変数がコンパイル時にコンパイラによって推定される必要があることを意味します。つまり、auto は変数に基づいて型を自動的に推測するキーワードです。

例えば:

8. 範囲ベースの for ループ (C++11)

配列を走査する必要がある場合、通常は次のメソッドを使用します。

		int main()
		{
			int arr[] = { 1,2,3,4,5,6,7,8 };
		
			for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
			{
				cout << arr[i] << " ";
			}
		
			return 0;
		}

範囲コレクションの場合、プログラマがループの範囲を指定するのは冗長であり、場合によってはエラーが発生しやすくなります。したがって、範囲ベースの for ループが C++11 で導入されました。for ループの後の括弧は、コロン「:」によって 2 つの部分に分割されます。最初の部分は反復に使用される範囲内の変数で、2 番目の部分は反復される範囲を表します。range for を使用すると、次のコードのように、上で学んだ auto キーワードと組み合わせることができます。

ここに画像の説明を挿入します
配列の値を変更する必要がある場合は、次のコードのように使用する必要がありますか?

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

明らかに答えはノーです。e は配列内のデータの一時的なコピーにすぎず、一時コピーの値を変更しても配列内の元の値には影響しないため、参照を追加する必要があります。

		int main()
		{
			int arr[] = { 1,2,3,4,5,6,7,8 };
		
			for (auto& e : arr)
			{
				e *= 2;
			}
		
			for (auto e : arr)
			{
				cout << e << " ";
			}
			return 0;
		}

参照を追加した後の e は、配列内のデータのエイリアスです。e を変更すると、配列の内容が変更されます。

9. ポインタ null 値 nullptr

NULL ポインターが初期に設計されたとき、NULL は実際には 0 でした。そのため、いくつかの場所で NULL を使用すると、次のようにあいまいな関数呼び出しが発生します。

ここに画像の説明を挿入します
void func(int*)上記のコードでは、 func は関数のオーバーロードを構成しており、NULLが関数を呼び出すことを期待していましたが、別の関数が呼び出されたため、あいまいな関数呼び出しが発生しました。

したがって、C++11 では nullptr が導入され、その型は型なしポインター (void*) となり、上記の状況を効果的に回避できます。たとえば、次の図では、nullptr はポインター型の関数を呼び出します。

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

ついに、C++ 入門のすべての内容が共有されました。役に立ったと思われるお友達は、いいね! と保存をお願いします~ご支援ありがとうございました!

おすすめ

転載: blog.csdn.net/YoungMLet/article/details/131802354