C++ の概要 (名前空間、デフォルト パラメーター、関数のオーバーロード、参照、インライン関数)

序章

C 言語は構造化されたモジュール言語であり、小規模なプログラムの処理に適しています。高度な抽象化とモデリングを必要とする複雑な問題や大規模なプログラムには、C 言語は適していません。ソフトウェア危機を解決するために、1980年代にコンピュータ業界はOOP(オブジェクト指向プログラミング:オブジェクト指向)という考え方を提唱し、オブジェクト指向プログラミングをサポートするプログラミング言語が登場しました。
1982 年、Bjarne Stroustrup 博士は、C 言語に基づいてオブジェクト指向の概念を導入および拡張し、新しいプログラミング言語を発明しました。C言語との起源関係を表現するためにC++と名付けられています。したがって、 C++ は C 言語をベースにして作成されており、C 言語の手続き型プログラミングだけでなく、抽象データ型を特徴とするオブジェクトベースのプログラミングや、オブジェクト指向プログラミングも実行できます。

前回の C 言語の学習で、C 言語のプログラミングについてはすでに皆さんよく理解されていると思いますので、基本的な文法については繰り返しません。この記事では、C++ を始めるための新しい知識をいくつか紹介します。

C++ の入出力

最初に理解するのは、C++ の入力と出力です。

cout は標準出力オブジェクトであり、標準出力ストリーム (画面) にデータを書き込むために使用されます。endl は
マニピュレータです。endl を書き込んだ場合の効果は改行であり、バッファ内のデータはデバイスにフラッシュされます。出力演算子
を使用します。 < < は標準出力に情報を出力できます。

std::cout << 表达式1 << 表达式2 << sts::endl;

cin は標準入力オブジェクトであり、標準入力ストリーム (キーボード) からデータを読み取るために使用されます。指定された入力ストリームから指定されたオブジェクトにデータを読み取るには、入力演算子 >>
を使用します。

std::cin >> 变量1 >> 变量2;

入出力に cin と cout を使用すると、入力または出力の種類が自動的に識別され、アドレスを取得する操作は必要ありません。C言語と比べて入出力の形式を記述する必要があります。

cin、cout、および endl を使用する場合、ヘッダー ファイルをインクルードする必要があります<iostream>。また、それらの実装はすべて にあるためC++标准库命名空间std、使用時に名前空間またはドメイン スコープ修飾子を導入する必要があります。

名前空間

次に、名前空間に関する知識を紹介します。

C言語では、自分で定義した変数や関数がライブラリで定義した関数と競合したり、プロジェクトで自分が定義した関数と他人が定義した関数が競合したりすることがよくあり、退屈してしまいます。
名前空間を使用する目的は、名前の競合や名前汚染を避けるために識別子の名前をローカライズすることです。

コンセプト

名前空間を定義するときは、キーワード namespace を使用し、その後に名前空間の名前を続けて、次の {} でメンバー (変数や関数など) を定義します。

namespace qqq
{
    
    
	int rand = 0;
	int max(int a = 0, int b = 0)
	{
    
    
		if (a > b)
		{
    
    
			return a;
		}
		return b;
	}
}

上記の名前空間 qqq で定義された rand 変数と max 関数は、どちらもライブラリに実装された関数です。名前空間 qqq でこれら 2 つの文字を定義することは、グローバルに公開するのではなく、空間スコープ内の名前に rand と max を入れることと同じです。スコープにある場合、名前の競合は発生しません。

名前空間は入れ子にすることができ、同じ名前を持つ複数の名前空間を定義できます (コンパイル時に 1 つに結合されます)。

使用

名前空間でメンバー変数またはメンバー関数を使用する場合、次の 3 つの方法があります。

use-scope-qualifier::use-a-member

スコープ修飾子の使用:: メンバーを使用する場合、名前空間でメンバーを使用するたびに、明示的に次のように記述する必要があります命名空间名::成员名

//使用域作用限定符::使用某个成员
//省略头文件包含与命名空间qqq
int main()
{
    
    
	int a = qqq::rand;
	int b = 10;
	std::cout << qqq::max(a, b);
	return 0;
}

using namespace を使用して、名前空間ドメイン全体を導入します。

using namespace 命名空间;名前空間ドメイン全体を導入するために名前が使用される場合、それは名前空間ドメイン全体をグローバル ドメインに公開することと同等であり、名前空間内のメンバーはグローバルになります。以下を使用するときに直接使用できます。

//使用using namespace 引入整个命名空间域
//省略头文件包含与命名空间qqq
using namespace qqq;
using namespace std;
int main()
{
    
    
	int a = rand;
	int b = 10;
	cout << max(a, b);
	return 0;
}

メンバーを紹介するために使用します

上記の方法は便利ですが、名前空間のメンバーをグローバルに公開すると、名前空間の本来の意味が失われます。using 命名空间名 :: 成员名;ただし、頻繁に使用される一部の関数については、明示的な記述が面倒なので、特定のメンバーを導入するメソッドを使用できます。これは、このメンバーをグローバルに公開するのと同じで、後でこのメンバーを使用するときに直接使用できますt のメンバーは引き続き明示的に宣言する必要があります。

//使用using引入某个成员
//省略头文件包含与命名空间qqq
using qqq::max;
using std::cout;
int main()
{
    
    
	int a = qqq::rand;
	int b = 10;
	cout << max(a, b);
	return 0;
}

上記の cin、cout、および endl はすべて、標準ライブラリの名前空間 std に実装されています。もちろん、 を使用するときに標準ライブラリを直接展開することも、毎回それを使用することもできますが、最も安全で便利な方法は次のとおりです。一般的に使用されるメンバーusing namespace std;を紹介します: (例: 上記)。それに加えて、すべてのライブラリ関数は std にあります。std::using std::cout;

デフォルトパラメータ

オブジェクトを初期化する必要がある場合、C 言語の #define で定数を定義するよりも、デフォルトのパラメータでデフォルト値を設定する方がはるかに便利です。

コンセプト

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

#include<iostream>
void add(int a = 1, int b = 2)
{
    
    
	std::cout << a + b << std::endl;

}
int main()
{
    
    
	add();
	add(10);
	return 0;
}

ここに画像の説明を挿入

定義と宣言のデフォルト値の違いを避けるため、関数の定義と宣言で同時にデフォルト値を設定することはできないことに注意してください。

分類

パラメータ リストが完全にデフォルト値に設定されているかどうかに応じて、完全デフォルトと半デフォルトに分けられます。

すべてのデフォルト パラメータとは、パラメータ リスト内のすべてのパラメータがデフォルト値に設定されていることを意味します。
すべてのデフォルトパラメータを使用して関数を呼び出す場合、パラメータが渡されると、実パラメータはパラメータリストの仮パラメータに左から右に値が割り当てられ、割り当てをスキップすることはできません。

#include<iostream>
void Func(int a = 1, int b = 2, int c = 3)
{
    
    
	std::cout << a << " " << b << " " << c << std::endl;
}
int main()
{
    
    
	//Func(10, , 30);错误调用
	Func();//正确调用
	Func(10, 20);
	return 0;
}

ここに画像の説明を挿入

準デフォルト パラメータとは、パラメータ リストの一部がデフォルト値を設定していないパラメータです。
準デフォルト パラメータは、右から左へのデフォルト値のみを与えることができます。呼び出し時の実際のパラメータの数は、デフォルト値のないパラメータの数よりも少なくすることはできず、左から右に順番に実際のパラメータに割り当てられます。

#include<iostream>
//void Func(int a = 1, int b, int c = 3){}错误定义
void Func(int a, int b = 2, int c = 3)
{
    
    
	std::cout << a << " " << b << " " << c;
}
int main()
{
    
    
	//Func();错误调用
	Func(10);//正确调用
	return 0;
}

ここに画像の説明を挿入

関数のオーバーロード

関数を実装する際、似たような機能を持つ関数がいくつか存在することがよくありますが、パラメータの種類は異なります。関数名は異なるが似たような関数を複数定義すると、呼び出すのが不便になります。関数のオーバーロードがあります:

関数のオーバーロードは関数の特殊なケースです。C++では、同様の関数を同じスコープ内で宣言できます。を持つ同じ名前の複数の

定義と呼び出し

関数のオーバーロードを定義する場合、同じ関数名、異なるパラメーターの数、異なるパラメーターの型、およびパラメーターの型の順序が異なると、オーバーロードが構成される可能性があります。

int Func(int a, int b = 20)
{
    
    
	cout << 1 << endl;
	return 1;
}
int Func(double a, int b)//与第一个函数参数类型不同
{
    
    
	cout << 2 << endl;
	return 2;
}
int Func(int a, double b)//与第二个函数参数顺序不同
{
    
    
	cout << 3 << endl;
	return 3;
}
int Func(int a)//与第一个函数参数个数不同
{
    
    
	cout << 4 << endl;
	return 4;
}

パラメータリストが同じである場合、戻り値の違いはオーバーロードを構成しないことに注意してください。

//错误代码,会报错:无法重载仅按返回类型重载的函数
int Func(int a, int b = 20)
{
    
    
	cout << 1;
	return 1;
}
void Func(int a, int b)
{
    
    
	cout << 1;
}

ここに画像の説明を挿入
オーバーロードされた関数を呼び出す場合、関数にはデフォルトのパラメーターがあるため、たとえば呼び出し時にパラメーターが 1 つだけ渡される場合、最初のパラメーターにデフォルト値のない関数を呼び出すか、最初のパラメーターにデフォルト値を 1 つだけ持つオーバーロードされた関数を呼び出すかはわかりません。仮パラメータ、またはパラメータを 1 つだけ渡すことができるその他の関数。したがって、あいまいさを避けるために特別な注意を払う必要があります。

Func(10);//错误调用。对于传一个整型,第1个函数与第4个函数均可

ここに画像の説明を挿入
通常の状況では、渡されたパラメータ リストに従って、対応する関数を呼び出すことができます。

Func(10, 30);
Func(1.1, 5);
Func(5, 1.1);

ここに画像の説明を挿入

原理

では、なぜ C++ は関数のオーバーロードをサポートしているのでしょうか?

C 言語の上級編で紹介したように、作成したコードは実行可能プログラムに変換される前に、プリコンパイル、コンパイル、アセンブル、リンクのプロセスを経る必要があります。
コンパイル時にシンボル テーブルが生成され、シンボル テーブルにはシンボル名とアドレスが含まれます。関数名変更規則により、関数ごとにシンボルが異なり、同じシンボルを持つ関数が 2 つある場合、コンパイルエラー (シンボルの再定義) が報告されます。

C 言語コンパイラが変更した後でのみ、関数名と同じシンボル名を持つ関数が再定義されますが、C++ コンパイラによって変更されたシンボル名には、関数名とパラメータ リストに関する情報が含まれますオーバーロードされた関数のシンボル名は異なるため、再定義されません。呼び出し時には関数名は同じですが、実際には異なる関数を呼び出しています。
ただし、戻り値の型は装飾ルールに含まれていないため、関数名とパラメータリストが同じで戻り値が異なる関数シンボル名も同じとなり、オーバーロードにはなりません。

引用

参照は alias です。参照する変数と基本的に同じスペースを使用するため、参照する変数の値は参照変数を通じて変更でき、構文内に新しいスペースが開かれることはありません。

意味

型の後に & を追加して参照変数を定義します。类型& 引用变量名 = 引用实体;

int main()
{
    
    
	int a = 10;
	int& ra = a;
	int& rra = ra;
	cout << a << " " << ra << " " << rra << endl;
	rra = 20;
	cout << a << " " << ra << " " << rra << endl;
	return 0;
}

ここに画像の説明を挿入

上記のコードでは、整数変数 a が定義されており、参照変数 ra は a のエイリアスであり、rra は ra のエイリアスです。つまり、rra は a のエイリアスでもあります。3 つの名前はスペースを共有しており、いずれかの名前の値が変更されると、他の 2 つの値も変更されます。

注意が必要です

参照変数を定義するときは、いくつかの問題に注意する必要があります。

  1. 参照は定義時に初期化する必要があります。つまり、それが誰の参照であるかを明確にする必要があります。
int& a;//错误代码,必须初始化引用
  1. エンティティは複数の参照を持つことができますが、参照変数は 1 つのエンティティのみを参照できます。
int a = 10;
int b = 5;
int& ra = a;
int& rra = ra;
//int& ra = b; 错误代码,ra重定义
  1. 権限は引用時にのみパンまたはズームアウトでき、ズームインはできません。

参照変数を定義すると、この参照を通じてエンティティの値を変更できます。しかし、定数属性を持つエンティティ変数の場合、その内容を変更できるエイリアスを与えることは明らかに非常に危険です。
したがって、定数属性を持つ変数の場合、定数参照のみを初期化できます (つまり、参照権限を拡大できません)。

const int a = 10;
//int& ra = a; 错误代码,权限放大
const int& ra = a; //正确代码

もちろん、非定数に対する定数参照、つまりアクセス許可の縮小または変換を定義する場合は、次のことが可能です。

int b = 5;
const int& rb = b;//正确代码,权限缩小
int& rrb = b;//正确代码,权限平移

constで修飾された変数だけでなく、渡された値の戻り値や型変換の変数も定数の性質を持ちます。これは、変換時に暗黙的にコピーを一時変数にコピーし、その一時変数に値を代入するためであり、この一時変数には定数属性があるためです。

  1. 参照型はそのエンティティ型と同じである必要があります。
int c = 2;
//double& rc = c; 错误代码,引用与实体的类型不同

使用

戻り値の型パラメータとしての参照

以前は、戻りパラメータを必要とする一部の関数実装では、パラメータを渡すのにポインタしか使用できず、パラメータを渡すときも呼び出すときも面倒でした。次の交換関数のように、参照を戻りパラメータとして使用すると便利です。

//命名空间展开与头文件包含已省略
void Swap(int& a, int& b)
{
    
    
	int temp = b;
	b = a;
	a = temp;
}
int main()
{
    
    
	int a = 10;
	int b = 20;
	Swap(a, b);
	cout << a << " " << b;
	return 0;
}

ここに画像の説明を挿入
パラメータを渡すときにアドレスを取得する必要がなく、交換するときに逆参照する必要がなく、見た目も非常に明確で、戻りパラメータに適しています。

戻り値としての参照

以前の戻り値は、値またはポインターによってのみ返すことができます。値によって返す場合、暗黙的なコピー プロセスが発生し、大きなオブジェクトの効率に大きく影響します。ポインタを渡して返すのは記述が不便です。参照による返却は効率的かつ直感的です。

ここに画像の説明を挿入
ここに画像の説明を挿入
このようなコードは一見問題なく、Addの機能も実現しているように見えますが、大きな問題があります。つまり、ローカル変数は参照によって返されます。この動作は非常に危険です。ライフサイクル終了後にローカル変数のスペースがオペレーティング システムに返され、返される参照が不定データのエイリアスであるためです (環境がデータをクリアすると、任意の値が返されます)。 )。

したがって、参照リターンを使用するときは特別な注意を払う必要があります。参照によって返された値は、範囲外になったときに破棄されてはなりません。静的変数などを返すことができます。

int& Add(int a, int b)
{
    
    
	static int c = a + b;
	return c;
}
int main()
{
    
    
	int a = 10;
	int b = 20;
	int c = Add(a, b);
	cout << c;
	return 0;
}

ここに画像の説明を挿入

リファレンスとポインタの違い

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

参照によって構文内に新しいスペースが開かれることはありませんが、基礎となるロジック内のポインターと一貫性があり、アドレスを格納するためのスペースも開く必要があることに注意してください。

インライン関数

C 言語部分では、単純で頻繁に呼び出される関数については、コンパイル前の段階でマクロ関数を使用してマクロを置き換え、関数がスタック フレームを開くための時間とスペースを節約して効率を向上させることができます。たとえば、マクロ関数 ADD は次のようになります。

#define ADD(a, b) ((a)+(b))

マクロ関数はパラメータを渡すのではなく直接置換するので、マクロ関数のロジックには問題はありませんが、書き方は明らかに少し面倒です。

インライン関数はそのような問題を解決できます:
変更されたinline関数はインライン関数と呼ばれ、C++ コンパイラーはコンパイル中にインライン関数が呼び出される場所でそれを展開します。

inline int Add(int a, int b)
{
    
    
	return a + b;
}

注意しなければならないことは次のとおりです。

  1. インラインは、空間を時間と交換する方法であり、コンパイラが関数をインライン関数として扱う場合、コンパイル段階で関数呼び出しを関数本体に置き換えます。欠点: ターゲット ファイルが大きくなる可能性があります; 長所: 呼び出しオーバーヘッドが減少し、プログラムの実行効率が向上します
  2. インラインはコンパイラに対する単なる提案です。コンパイラが異なれば、インライン実装メカニズムも異なる場合があります。一般的な提案は、規模が小さく、再帰的ではなく、頻繁に呼び出される関数にはインライン変更を使用することです。そうでない場合、コンパイラはインライン機能を無視します。 。
  3. インライン関数と変数の宣言は分離しないでください (別ファイル)。インライン関数は関数アドレスのない直接置換であるため、分離すると置換できず、関数ポインタに従って呼び出すことができず、エラーが報告されます。

要約する

ここまでC++導入に関する知識を紹介してきましたが、
今後もC++の知識を更新していきますので、今後ともご注目ください。

この記事が少しでもお役に立てましたら、ワンクリックでつながれば幸いです

C++ への道はまだ始まったばかりです。一緒に頑張りましょう!

おすすめ

転載: blog.csdn.net/weixin_73450183/article/details/130460140