記事ディレクトリ
汎用プログラミング
import - 一般的なスワップ関数
2 つの数値を交換する関数を作成する場合。C 言語では、次のメソッドを使用します。
// 交换两个整型
void Swapi(int* p1, int* p2)
{
int temp = *p1;
*p1 = *p2;
*p2 = tmp;
}
// 交换两个双精度浮点型
void Swapd(double* p1, double* p2)
{
double temp = *p1;
*p1 = *p2;
*p2 = tmp;
}
C 言語は関数のオーバーロードをサポートしていないため、異なる型の変数を交換するために使用される関数の関数名を同じにすることはできません。また、パラメーターの受け渡しの形式は、値の受け渡しではなく、アドレスの受け渡しでなければなりません。
関数のオーバーロードと C++ のリファレンスを学習した後、次のメソッドを使用して 2 つの数値を交換します。
// 交换两个整型
void Swap(int& left, int& right)
{
int temp = left;
left = right;
right = temp;
}
// 交换两个双精度浮点型
void Swap(double& left, double& right)
{
double temp = left;
left = right;
right = temp;
}
C++ の関数のオーバーロードにより、異なる型の変数を交換するために使用される関数が同じ関数名を持つことができ、パラメーターは参照によって渡されて、コードの見栄えがよくなります。
ただし、このコードにはまだ欠点があります。
- 複数のオーバーロードされた関数は型が異なるだけで、コードの再利用率は比較的低く、新しい型を交換する必要がある限り、対応するオーバーロードされた関数を追加する必要があります。
- コードの保守性は比較的低く、1 つのエラーですべてのオーバーロードがエラーになる可能性があります
コンパイラにモデルを伝えて、コンパイラがそのモデルを使用して、さまざまなタイプに応じてコードを自動的に生成できるようにすることはできますか?
私たちが幼い頃に粘土の動物や他のモデルで遊んだように、異なる色の材料を追加することで、同じ形で異なる色の動物モデルを作ることができます.
このような型がC++にも存在できるとすれば、型に色の違う材料(型)を詰めることで、同じ形で色の違う月餅が得られる(特定の型のコードを生成する)ことができ、コードの冗長性が大幅に減ります。残る。偶然にも先人が植えた木なので、ここは日陰を楽しむだけ。
ジェネリック プログラミング: 型とは関係のないジェネリック コードを記述することは、コードを再利用する手段です。テンプレートは汎用プログラミングの基礎です
関数テンプレート
関数テンプレートの概念
関数テンプレートは、関数のファミリを表します。関数テンプレートは型とは関係ありません。使用時にパラメータ化され、実際のパラメータの型に従って関数の特定の型バージョンが生成されます。
関数テンプレートの形式
template<typename T1, typename T2,......,typename Tn>
返回值类型 函数名(参数列表)
{
//函数体
}
例えば:
template<typename T>
void Swap(T& left, T& right)
{
T temp = left;
left = right;
right = temp;
}
知らせ:
template
テンプレートを定義するキーワードで、その後に山括弧 < > が続きますtypename
テンプレート パラメーターのキーワードを定義するために使用され、使用することもできますclass
(覚えておいてください: クラスの代わりに構造体を使用することはできません)。- T1、T2、...、Tn は、テンプレートの名前として理解できる関数名 (関数のパラメーター名と同様) を表し、名前は自分で選択できます。
関数テンプレートを使用すると、上記の交換の問題を解決できます。
これらの異なるタイプが交換を完了したことがわかります。
関数テンプレートの原則
関数テンプレートは設計図であり、関数自体ではありません。特定の具象型の関数を生成するためのコンパイラの型です。つまり、実際には、テンプレートは、コンパイラーに行うべきであった反復作業を引き渡すことです。
コンパイラのコンパイル段階では、テンプレート関数を使用するために、コンパイラは、渡された実際のパラメーターの型に従って、呼び出し用の対応する関数の型を推測して生成する必要があります。例: double 型の関数テンプレートを使用する場合、コンパイラは、実パラメーターの型の推定によって T を double 型と判断し、double 型を具体的に処理するコードを生成します。文字についても同様です。タイプ
関数テンプレートのインスタンス化
関数テンプレートが異なる型のパラメーターで使用される場合、関数テンプレートのインスタンス化と呼ばれます。テンプレート パラメーターのインスタンス化は、暗黙的インスタンス化と明示的インスタンス化に分けられます。
上記は同じ型の変数の交換のみを反映しています.異なる型の変数を交換したい場合、上記の関数テンプレートを使用するとエラーが報告され、私たちのニーズを満たすことができません. だから何をすべきか?
1. 暗黙のインスタンス化: コンパイラーに、実際のパラメーターに従って、テンプレート パラメーターの実際の型を推測させます。
template<class T>
T Add(const T& left, const T& right)
{
return left + right;
}
int main()
{
int a1 = 10, a2 = 20;
double d1 = 10.1, d2 = 20.2;
// 自动推演实例化
cout << Add(a1, a2) << endl;
cout << Add(d1, d2) << endl;
return 0;
}
これはまだ同じ型の変数を追加しているので、異なる型を追加している場合はどうなるでしょうか? 異なる型の変数を追加するために上記のテンプレートを使用できないのはなぜですか?
Add(a1, d1);//什么不行呢?
注: テンプレートでは、コンパイラは通常、型変換操作を実行しません。これは、変換で問題が発生すると、コンパイラが責任を負う必要があるためです。
コンパイル中に、コンパイラがインスタンス化を確認すると、そのパラメータの型を推定する必要があるため、T は実パラメータ a1 によって int として推定され、T は実パラメータ d1 によって double 型として推定されますが、1 つしかありません。テンプレート パラメーター リスト内の T. コンパイラーは、T を int 型と double 型のどちらとして決定する必要があるかを判断できず、エラーを報告します。
現時点での対処法としては、パラメータを渡す際に b を int 型に変換する方法、後述の表示インスタンス化を利用する方法、複数のパラメータ テンプレートを使用する方法の 3つが考えられます。
異なるタイプの仮パラメーターが渡された場合の処理:
- パラメータを渡す際の強制的な変換 (対応する仮パラメータには const の変更が必要です)
template<class T>
T Add(const T& left, const T& right)//const接收常性实参
{
return left + right;
}
int main()
{
int a1 = 10, a2 = 20;
double d1 = 10.1, d2 = 20.2;
//对于只有一个参数模板,只能在传参时,自己去强转成一样的类型
cout << Add((double)a1, d2) << endl;//强转,临时变量传参,具有常性
cout << Add(a1, (int)d2) << endl;//强转,临时变量传参,具有常性
return 0;
}
強制型変換を使用して、推論中に仮パラメーターを同じ型に変換します。
- 明示的なインスタンス化 (パラメーターを渡すときの暗黙的な型変換、対応する仮パラメーターには const の変更が必要)
template<class T>
T Add(const T& left, const T& right)//需要使用const接收
{
return left + right;
}
int main()
{
int a1 = 10, a2 = 20;
double d1 = 10.1, d2 = 20.2;
// 显示实例化
//指定模板参数的实际类型为double
cout << Add<double>(a1, d2) << endl;//显式实例化,a1发生隐式类型转换
//指定模板参数的实际类型为int
cout << Add<int>(a1, d2) << endl;//显式实例化,d2发生隐式类型转换
return 0;
}
- 複数のテンプレートを使用する
template<class T1, class T2>//两个参数模板,可以传不同类型的参数 //可以写typename也可以写class
T1 Add(const T1& left, const T2& right)//返回值类型只能选择其中一种
{
return left + right;
}
int main()
{
int a1 = 10, a2 = 20;
double d1 = 10.1, d2 = 20.2;
cout << Add(a1, d2) << endl;
cout << Add(d1, a2) << endl;
return 0;
}
テンプレート パラメータのマッチング原則
- 非テンプレート関数は同じ名前の関数テンプレートと共存でき、関数テンプレートもこの非テンプレート関数としてインスタンス化できます
// 专门处理int的加法函数
int Add(int left, int right)
{
return left + right;
}
// 通用加法函数
template<class T>
T Add(T left, T right)
{
return left + right;
}
void Test()
{
Add(1, 2); // 与非模板函数匹配,调用非模板函数,编译器不需要实例化
Add<int>(1, 2); // 调用编译器实例化的Add版本
}
- 非テンプレート関数と同名の関数テンプレートの場合、他の条件が同じであれば、呼び出し時に非テンプレート関数が先に呼び出され、テンプレートからインスタンスが生成されません。テンプレートがよりよく一致する関数を生成できる場合は、テンプレートを選択します
//专门用于int类型加法的非模板函数
int Add(const int& x, const int& y)
{
return x + y;
}
//通用类型加法的函数模板
template<typename T1, typename T2>
T1 Add(const T1& x, const T2& y)
{
return x + y;
}
int main()
{
Add(10, 20); //与非模板函数完全匹配,不需要函数模板实例化
Add(2.2, 2); //函数模板可以生成更加匹配的版本,编译器会根据实参生成更加匹配的Add函数
return 0;
}
- テンプレート関数は自動型変換を許可しませんが、通常の関数は自動型変換を行うことができます
template<typename T>
T Add(const T& x, const T& y)
{
return x + y;
}
int main()
{
Add(2, 2.2); //模板函数不允许自动类型转换,不能通过编译
//因为只有一个参数模板,只能在传参时,自己去强转成一样的类型
return 0;
}
クラス テンプレート
クラステンプレートの定義形式
template<class T1, class T2, ..., class Tn>
class 类模板名
{
// 类内成员定义
};
例えば:
template<typename T>
class Scores
{
public:
void Print()
{
cout << "数学:" << _Math << endl;
cout << "语文:" << _Chinese << endl;
cout << "英语:" << _English << endl;
}
private:
T _Math;
T _Chinese;
T _English;
};
注:クラス テンプレートのメンバー関数がクラスの外部で定義されている場合は、テンプレート パラメーター リストを追加する必要があります。
template<class T>
class Scores
{
public:
void Print();
private:
T _Math;
T _Chinese;
T _English;
};
template<class T>
void Scores<T>::Print()
{
cout << "数学:" << _Math << endl;
cout << "语文:" << _Chinese << endl;
cout << "英语:" << _English << endl;
}
クラス テンプレートのインスタンス化
クラス テンプレートのインスタンス化は関数テンプレートのインスタンス化とは異なります.クラス テンプレートのインスタンス化はクラス テンプレート名の後に <> を付けてから, インスタンス化された型を <> に入れる必要があります (インスタンス化を表示します). クラス テンプレート名は実際のクラスではなく、インスタンス化の結果は実際のクラスです
この文の理解:
//Score不是真正的类,Score<int>和Score<double>才是真正的类
Score<int> s1;//需要显示实例化
Score<double> s2;
また、これらは同じクラス テンプレートからインスタンス化されていますが、型とサイズが異なるため、インスタンス化された 2 つのクラスは同じクラスではないことに注意する必要があります。
例: 前に定義したスタック クラス
template<typename T>
class Stack
{
public:
Stack(int capacity = 4)
{
cout << "Stack(int capacity = )" <<capacity<<endl;
_a = (T*)malloc(sizeof(T)*capacity);
if (_a == nullptr)
{
perror("malloc fail");
exit(-1);
}
_top = 0;
_capacity = capacity;
}
~Stack()
{
cout << "~Stack()" << endl;
free(_a);
_a = nullptr;
_top = _capacity = 0;
}
void Push(const T& x)
{
// ....
// 扩容
_a[_top++] = x;
}
private:
T* _a;
int _top;
int _capacity;
};
int main()
{
// 类模板一般没有推演时机,函数模板实参传递形参,推演模板参数
// 类模板只能显示实例化
// 他们是同一个类模板实例化出来的
// 但是模板参数不同,他们就是不同类型
Stack<double> st1; // double
st1.Push(1.1);
Stack<int> st2; // int
st2.Push(1);
return 0;
}