[C++] C++ 入門パート 1 -- 名前空間、入出力、デフォルト関数、関数のオーバーロードの基本原理

目次

1. キーワード 

2. 名前空間

2.1 名前空間の定義

2.2 ネームスペースの使用方法

2.2.1 名前空間名とスコープ修飾子の追加

2.2.2 名前空間にメンバーを導入するために using を使用する

2.2.3 ネームスペースネームスペース名を使用してインポートする

3. C++ の入出力

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

4.1 デフォルトパラメータの概念

4.2 デフォルトパラメータの分類

4.2.1 すべてのデフォルトパラメータ

4.2.2 準デフォルトパラメータ

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

5.1 関数のオーバーロードの概念

5.2 C++ は関数のオーバーロードの原則をサポートしています -- 名前マングリング (名前マングリング)


1. キーワード 

C++ には合計 63 個のキーワードがあり、C 言語には 32 個のキーワードがあり、C++ のキーワードを次の図に示します。ここでは詳細には説明しません。後で使用し、注意を払い続け、使用中に説明します。

2. 名前空間

まずは C++ コードを書いてみましょう。見てみましょう。

#include <iostream>
using namespace std;

int main()
{
    cout << "Hello world!" << endl;
    
	return 0;
}

このコードの最初の文 #include <iostream> は、C 言語の #include <stdio.h> と同じで、入出力ライブラリ関数を呼び出すときにインクルードされるヘッダー ファイルです。C++ でも可能ですC++ は C 言語と完全な互換性があります。

入力ヘッダー ファイルと出力ヘッダー ファイルを理解した後、名前空間 std; を使用する 2 番目の文があることがわかりました。これは何のためのものでしょうか?

キーワードの名前空間は、C 言語のいくつかの問題を最適化した名前空間です。詳細を見てみましょう。

C では、名前の競合に問題があることがわかっています。

#include <stdio.h>
int main()
{
	int a = 10;
	int a = 20;
	
	printf("%d %d", a, a);
	return 0;
}

ここでの問題は再定義であり、名前の競合です。しかし、私たちが仕事中で、グループ内の複数のプログラマが同時に開発している場合、間違いなく名前の競合が発生します。

C++ では、名前空間ネームスペースの概念が導入されています。

2.1 名前空間の定義

名前空間を定義するには、名前空間キーワード、その後に名前空間の名前、そして名前空間のメンバーである{} のペアを使用する必要があります。

ここに名前空間のコードを書いてみましょう。

namespace lcx //命名空间名字叫lcx
{
	int a = 10;//成员变量

	int Add(int x, int y)//成员函数
	{
		return x + y;
	}
}

この名前空間の名前は lcx で、a は名前空間のメンバー変数、Add は名前空間のメンバー関数です。

名前空間はネストすることもできます。

namespace l1
{
	int a = 10;
	int b = 20;
	namespace l2
	{
		int a = 30;
		int b = 40;
	}

}

操作結果:

同じプロジェクト内に同じ名前の複数の名前空間が存在する可能性があり、コンパイラーは最終的に名前空間を合成します。

namespace l1
{
	int a = 10;
}
namespace l1
{
	int b = 40;
}

ここで、2 つの l1 は最終的に 1 つの名前空間にマージされるため、同じ名前の 2 つの名前空間に同じ名前の変数を定義することはできず、競合が発生します。

操作結果:

注:ネームスペースは新しいスコープを定義し、ネームスペース内のすべてのコンテンツはそのネームスペースに制限されます。

2.2 ネームスペースの使用方法

名前空間で定義された変数と関数を使用するにはどうすればよいですか?

2.2.1 名前空間名とスコープ修飾子の追加

namespace lcx
{
	int a = 10;

	int Add(int x, int y)
	{
		return x + y;
	}
}

ここで、を出力して Add 関数を呼び出します。ここでは、スコープ修飾子 ::、名前空間名 + このシンボル + 変数名/関数名 を使用する必要があります。そうすれば、名前空間の変数/関数を使用できます。

例: 上記のコードの変数 a を出力し、関数 Add を呼び出してみましょう。

namespace lcx
{
	int a = 10;

	int Add(int x, int y)
	{
		return x + y;
	}
}
int main()
{
	int ret = lcx::Add(2, 3);

	cout << lcx::a << " " << ret << endl;

	return 0;
}

操作結果:

2.2.2 名前空間にメンバーを導入するために using を使用する

namespace lcx
{
	int a = 10;
	int b = 20;

	int Add(int x, int y)
	{
		return x + y;
	}
}

普段、変数 a を何度も使い、印刷するたびに lcx::a を使用する場合、印刷するのが面倒なので、 using + 名前空間名 + :: + 変数名を使用すると、特定の A変数を後で使用する場合、lcx::a の形式で記述する必要はありません。

namespace lcx
{
	int a = 10;
	int b = 20;

	int Add(int x, int y)
	{
		return x + y;
	}
}
using lcx::a;
int main()
{
	cout << a << " " << lcx::b << endl;

	return 0;
}

操作結果:

2.2.3 ネームスペースネームスペース名を使用してインポートする

ある変数の値を出力するとき、coutとinput cinを使いますが、これらは実は他の人が書いたライブラリ関数なので、そのまま使うことができます。

普段の練習の過程では cin と cout をよく使うので、最初はこれら 2 つの関数を含む名前空間を直接展開し、後で直接使用します。

using namespace std;
int main()
{
	int a = 10;
	int b = 20;
	cout << a << " " << b << endl;

	return 0;
}

ここでローカル変数 a と b を出力するときに cout と endl を使用しますが、これらは C++ 標準テンプレート ライブラリの名前空間の関数なので、練習では標準テンプレート ライブラリの名前空間を直接展開します。

using namespace std;

std は、C++ 標準テンプレート ライブラリの名前空間です。

3. C++ の入出力

ここでは C++ の入力と出力について簡単に学びましょう。まず C++ の使用方法を学び、次に入力ストリームと出力ストリームについて具体的に学びます。

コードを見てみましょう:

#include <iostream>
using namespace std;
int main()
{
	int a = 0;
	float b = 0.0f;

	cin >> a >> b;//输入多个
	cout << a << " " << b << endl;//输出多个

	return 0;
}

ここで、cin と cout がそれぞれ入力と出力であることがわかります。では、2 つの記号 >> と << は何でしょうか?

C++ では、>> はストリーム抽出演算子、<< はストリーム挿入演算子で、入力と出力の両方に複数の入力と複数の出力を含めることができます。endl は C 言語の「\n」で、改行を意味します。

例証します:

1. cout 標準出力オブジェクト(コンソール)および cin 標準入力オブジェクト(キーボード)を使用する場合は、名前空間の使用方法に応じて < iostream > ヘッダファイルをインクルードし、std を使用する必要があります。
2. cout と cin はグローバル ストリーム オブジェクト、endl は改行出力を表す特別な C++ シンボルで、<iostream> を含むヘッダー ファイルにすべて含まれています。
4. 入出力には C++ を使用する方が便利で、printf/scanf 入出力のような形式を手動で制御する必要がありません。C++ の入力と出力では、変数の型を自動的に識別できます。上記のコードと同様に、入力時に %d と %f を使用する代わりに cin>>a>>b を直接使用します。
5. 実際、cout と cin は、それぞれ ostream 型と istream 型のオブジェクトです。>> と << には、演算子のオーバーロードやその他の知識も含まれます。この知識は後で学習するので、ここでは単にそれらの使用方法を学習するだけです。この後、IO フローの使用法と原理について詳しく学ぶ章が設けられます。

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

4.1 デフォルトパラメータの概念

C言語にはデフォルトパラメータはありませんが、C++のデフォルトパラメータは何ですか?

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

例を見てみましょう:

void func(int n = 10)
{
	cout << n << endl;
}
int main()
{
	func();  //不传参数的时候,使用func函数形参的默认值
	func(1); //传参数,使用实参

	return 0;
}

このコードでは、func 関数を初めて呼び出すときにパラメーターを渡しませんでした。C 言語では、これはエラーを報告しますが、関数の仮パラメーターにデフォルト値がある場合、呼び出し元の関数はこれをサポートしているため、C++ はこれをサポートしています。パラメータを渡さない場合は、仮パラメータのデフォルト値が使用されます。ただし、関数を呼び出すときに実際のパラメータを渡すと、実際のパラメータが優先されます。悪く言えば、デフォルトのパラメータはスペアタイヤのようなものです。

4.2 デフォルトパラメータの分類

4.2.1 すべてのデフォルトパラメータ

完全なデフォルト パラメータとは、関数の各仮パラメータにデフォルト値があることを意味するため、関数を呼び出すときに実際のパラメータを渡す必要はありません。実際のパラメータを渡したい場合は、左から右に順番に渡す必要があります。ただし、パラメータを渡す場合、たとえば次のようにスキップして渡すことはできません。

void func(int a = 10, int b = 20, int c = 30)
{
	cout << a << " " << b << " " << c << endl;
}
int main()
{
	func(1);        //可以传一个
	func(1, 2);     //可以传两个
	func(1, 2, 3);  //可以全部传
	//我们传参是从左到右依次传

	return 0;
}

操作結果:

パラメータをスキップして渡します。


C++ のデフォルト関数の呼び出しにはパスをスキップする概念がまったくないため、実行できません。

4.2.2 準デフォルトパラメータ

セミデフォルトとは、複数の仮パラメータの一部にデフォルト値を与えることですが、デフォルト値を与える場合は右から左にデフォルト値を与える必要があり、デフォルト値をスキップすることはできません。

準デフォルトを確認するコードを書いてみましょう。

//半缺省
void func(int a, int b = 20, int c = 30)
{
	cout << a << " " << b << " " << c << endl;
}
int main()
{
	func(1);        //第一个必须传
	func(1, 2);     //第二个可以传,可以不传
	func(1, 2, 3);  //后两个都是可传,可不传
	//我们传参是从左到右依次传

	return 0;
}

操作結果:

上記のコードでは、作成した func 関数の最初の仮パラメータ a にデフォルト値が与えられていないことがわかります。そのため、最初のパラメータは呼び出し時に渡す必要があり、渡されていない場合はエラーが発生します。

デフォルトのパラメータを学習した後、それは何に役立つのでしょうか?

スタックでは、これを使用するときに、事前に 1000 個のスペースを空けておきたい場合に、デフォルトのパラメーターを使用して書き込むことができることがすでにわかっています。

struct Stack
{
	int* a;
	int size;
	int capacity;
};

void StackInit(Stack* ps, int n = 4)
{
	assert(ps);
	ps->a = (int*)malloc(sizeof(int) * n);
	if (NULL == ps->a)
	{
		perror("malloc fail:");
		return;
	}

	ps->size = 0;
	ps->capacity = n;
}

int main()
{
	Stack st;
	StackInit(&st, 1000);

	return 0;
}

ここではセミデフォルトパラメータを使用します. 初期化するだけでどのくらいのスペースが適切かわからない場合は、それを渡さないこともできます. 初期化中にオープンされるスペースは 4 つだけであり、無駄がありません. これは、デフォルトのパラメータを柔軟に使用することです。

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

5.1 関数のオーバーロードの概念

関数のオーバーロード: これは関数の特殊なケースです。C++ では、同じ名前の複数の関数が、同じスコープ内で同様の関数を宣言できます。同じ名前のこれらの関数には、異なる仮パラメータ リスト (パラメータ番号、型、または型の順序) があります。関数の実装は、さまざまなデータ型の問題に似ています。

a. パラメータの型が異なっていても、同じ関数名を使用して関数を呼び出すことができます。

int Add(int x, int y)
{
	return x + y;
}
double Add(double x, double y)
{
	return x + y;
}
int main()
{
	cout << Add(1, 2) << endl;
	cout << Add(1.1, 2.2) << endl;

	return 0;
}

操作結果:

上記のコードでは、名前はすべて Add ですが、パラメータの型が異なることがわかります。呼び出し時に渡す実際のパラメータは、同じ名前で型に応じて異なる型の関数を呼び出すため、異なる型が構成されます。重い荷物

b. 同じ関数名を使用して、仮パラメータの型の異なる順序に従って関数を呼び出すこともできます。

void func(int x, double y)
{
	cout << "void func(int x, double y)" << endl;
}
void func(double x, int y)
{
	cout << "void func(double x, int y)" << endl;
}
int main()
{
	func(1, 1.1);
	func(1.1, 1);

	return 0;
}

操作結果:

ここでの 2 つの func 関数の仮引数は整数型と浮動小数点型ですが、2 つの仮引数の順序が異なり、関数呼び出し時に渡された実引数がその型に応じて呼び出されます。関数が異なると、パラメーターの順序が異なると、オーバーロードが構成されます。
c. パラメータの数は異なりますが、関数名は同じで関数を呼び出します。

void func(int x, double y)
{
	cout << "void func(int x, double y)" << endl;
}
void func(int x, double y, int z)
{
	cout << "void func(int x, double y, int z)" << endl;
}
int main()
{
	func(1, 1.1);
	func(1.1, 1, 2);

	return 0;
}

操作結果:

ここでパラメータの数が異なり、最初の関数はパラメータが 2 つ、2 番目の関数はパラメータが 3 つあり、呼び出すときに渡されるパラメータの数が異なり、呼び出される関数も異なります。関数の繰り返しを構成します

これを書くと、異なる戻り値の型もオーバーロードを構成できるのではないかと考える人もいるでしょう。

まず答えましょう: いいえ。それを確認してみましょう:

short func(short x, short y)
{
	return x + y;
}
int func(short x, short y)
{
	return x + y;
}

ここで、異なる戻り値の型がオーバーロードを構成できないことがわかります。

概要:同じ関数名を前提として、パラメータの型が異なる、パラメータの順序が異なる、パラメータの数が異なる、これら 3 つのケースは関数のオーバーロードとなる可能性があります。

5.2 C++ は関数のオーバーロードの原則をサポートしています -- 名前マングリング (名前マングリング)

C++ は関数のオーバーロードをサポートしているのに、C 言語は関数のオーバーロードをサポートしていないのはなぜですか?
C/C++ では、プログラムを実行するには、前処理、コンパイル、アセンブル、リンクという 4 つの段階を経る必要があります。

これら 4 つの段階のプロセスは次のとおりです (Linux プロセスの観点から)。

1. 前処理: ヘッダファイルの展開、マクロ置換、コメント削除、条件付きコンパイル、test.i の生成

2. コンパイル: 構文をチェックし、アセンブリ (命令レベルのコード) を生成し、test.s を生成します。

3. アセンブリ: アセンブリ コードをバイナリ マシン コードに変換し、test.o を生成します。

4. リンク: マージリンク、実行可能プログラムの生成、a.out

C++ 関数のオーバーロードは主に、リンク時に関数名を使用してアドレスを検索することです。これは C 言語でも同様です。しかし、C++ では関数名の変更ルールが導入されており、これが C++ が関数のオーバーロードをサポートする根本的な理由です。Linux 環境から始めましょう。

ここでは、関数のオーバーロードを形成するために 2 つの関数を作成しました。

Linux では、g++ には独自の関数名変更ルールのセットがあります。見てみましょう。

上記の 2 つのスクリーンショットを組み合わせると、g++ での関数名の変更ルールは次のように結論付けることができます。

_Z 関数名の長さ 関数名の仮パラメータの最初の文字

したがって、関数名が同じであっても、型、順序、番号が異なると、関数名も異なります。

関数が呼び出されるとき、命令は呼び出し関数名であり、変更された関数名はジャンプ後に対応する関数のアドレスを見つけるため、対応する関数を見つけることができます。このため、関数名変更ルールが修正された後、さまざまな関数名に従ってシンボル内で対応する関数名が検索され、対応するアドレスが検索され、最後に対応する関数を呼び出す理由にジャンプします。

C言語が動かないのは、C++のような関数名変更ルールがなく、関数名がそのままシンボルテーブルに格納されているため、同じ関数名だと入った瞬間にめちゃくちゃになってしまいます。このように最初の文字で関数が区別されるため、C 言語では関数のオーバーロードを実装できません。

おすすめ

転載: blog.csdn.net/Ljy_cx_21_4_3/article/details/131889153