二级域名网站如何申请吗,网络编程培训班,溧阳网站建设,单页网站开发费用文章目录 总结1、重载示例代码特点1. 模板函数和非模板函数重载2. 重载示例与调用规则示例代码调用规则解释3. 特殊情况与注意事项二义性问题 函数特化与重载的交互 2. 函数隐藏#xff08;Function Hiding#xff09;概念示例代码特点 3. 函数覆盖#xff08;重写#xff… 文章目录 总结1、重载示例代码特点1. 模板函数和非模板函数重载2. 重载示例与调用规则示例代码调用规则解释3. 特殊情况与注意事项二义性问题 函数特化与重载的交互 2. 函数隐藏Function Hiding概念示例代码特点 3. 函数覆盖重写Function Overriding概念示例代码特点 总结
1、重载在同一个作用域函数名相同参数列表不同与返回值无关 2、隐藏在基类和派生类之间发生的关系函数名相同派生类的函数把基类的函数给隐藏了只关注函数数名不管返回值和参数 3、覆盖覆盖是隐藏的一种特殊情况派生类和基类的函数1 函数名相同2 返回值相同3 参数列表相同不包括this指针在内4 基类函数为虚函数即为覆盖。
1、重载
函数重载是指在同一个作用域通常是在同一个类中也可以是在全局作用域下内存在多个同名函数但它们的参数列表参数的个数、类型或者顺序不同。编译器会根据调用时传递的实际参数来决定调用哪个具体的重载函数函数重载实现了用同一个函数名来执行相似但参数有所不同的操作提高了代码的可读性和易用性。 总结来说就是在同一个作用域函数名相同参数列表不同与返回值无关
示例代码
#include iostream// 重载函数参数个数不同
int add(int num1, int num2) {return num1 num2;
}int add(int num1, int num2, int num3) {return num1 num2 num3;
}// 重载函数参数类型不同
double add(double num1, double num2) {return num1 num2;
}int main() {std::cout add(2, 3) std::endl;std::cout add(2, 3, 4) std::endl;std::cout add(2.5, 3.5) std::endl;return 0;
}在上述代码中add 函数有多个重载形式有的重载函数参数个数不同如两个 int 参数和三个 int 参数的版本有的重载函数参数类型不同如 int 参数和 double 参数的版本。在 main 函数中调用 add 函数时编译器根据传入的实际参数情况来选择匹配的重载函数进行调用。
特点
作用域相同重载的函数必须在同一个作用域内定义比如都在某个类的内部或者都在全局作用域中。函数名相同这是重载的关键特征之一多个函数共享同一个函数名方便代码的调用和理解从使用者角度看好像是同一个函数根据不同情况执行不同逻辑。参数列表有差异参数个数、类型或者顺序至少有一项不同而返回类型不同不能作为函数重载的依据因为仅返回类型不同时编译器无法仅根据调用情况准确判断该调用哪个函数。
除了以上常规的重载还有一些同学认为泛型编程也存在重载即模板函数和非模板函数的重载 在C中模板函数与非模板函数之间可以存在重载关系以下是关于它们重载的详细介绍
1. 模板函数和非模板函数重载
重载机制重载允许在同一作用域内存在多个同名函数对于函数重载而言这些函数的参数列表有所不同而返回类型不能作为区分重载的唯一依据编译器会根据调用时实际传入的参数情况来决定调用哪一个具体的函数。模板函数和非模板函数的重载就是利用了这一机制在合适的场景下编译器会依据调用参数去选择调用模板函数版本还是非模板函数版本。
2. 重载示例与调用规则
示例代码
#include iostream// 非模板函数
int add(int num1, int num2){return num1 num2;
}// 模板函数
templatetypename T
T add(T num1, T num2){return num1 num2;
}int main(){int result1 add(3, 5); // 调用非模板函数 add(int, int)double result2 add(3.5, 2.5); // 调用模板函数 adddouble(double, double)std::cout 整数相加结果: result1 std::endl;std::cout 浮点数相加结果: result2 std::endl;return 0;
}调用规则解释
精确匹配优先当进行函数调用时编译器首先会寻找参数类型与函数参数列表能精确匹配的非模板函数。在上述代码的 add(3, 5) 调用中非模板函数 add(int, int) 的参数类型刚好和传入的两个整数参数完全匹配所以编译器优先选择调用这个非模板函数而不会去考虑模板函数版本即使模板函数也能够通过实例化将 T 实例化为 int来处理这两个整数参数。模板函数实例化匹配如果没有找到精确匹配的非模板函数编译器会尝试对模板函数进行实例化看能否通过实例化后的模板函数来匹配调用参数。例如在 add(3.5, 2.5) 调用中不存在参数类型为两个 double 的非模板函数 add此时编译器会查看模板函数将模板参数 T 实例化为 double生成 add(double, double) 这样一个实例化后的函数版本它能很好地匹配传入的两个 double 类型参数所以就调用这个实例化后的模板函数版本。
3. 特殊情况与注意事项
二义性问题
当模板函数和非模板函数的参数匹配存在模糊情况时可能会导致编译错误出现二义性问题。例如
#include iostream// 非模板函数
void func(int num){std::cout 非模板函数func(int) std::endl;
}// 模板函数
templatetypename T
void func(T num)
{std::cout 模板函数func(T) std::endl;
}int main()
{func(5); // 编译错误存在二义性不知道该调用模板函数还是非模板函数return 0;
}在这个例子中调用 func(5) 时传入的整数 5 既可以匹配非模板函数 func(int)也可以通过将模板函数的 T 实例化为 int 来匹配模板函数 func(T)编译器无法确定到底该调用哪一个函数就会报二义性的编译错误。要解决这类问题可以通过显式指定模板参数如 funcint(5) 就会明确调用模板函数版本或者调整函数的参数类型等方式使得调用具有明确的匹配对象。这个问题并不是所有的编译器都存在在VS2022就不存在该问题。
函数特化与重载的交互
函数模板可以进行特化即针对特定的类型提供专门的模板函数实现。在存在函数特化的情况下特化版本、模板函数的通用版本以及非模板函数之间的重载关系也需要遵循上述的调用规则。例如
#include iostream// 非模板函数
void printData(int num){std::cout 非模板函数打印整数: num std::endl;
}// 模板函数
templatetypename T
void printData(T data)
{std::cout 模板函数通用版本打印数据: data std::endl;
}// 模板函数特化针对char类型
template
void printDatachar(char data)
{std::cout 模板函数特化版本打印字符: data std::endl;
}int main()
{int num 10;char ch A;printData(num); // 调用非模板函数printData(int)printData(ch); // 调用模板函数特化版本printDatachar(char)return 0;
}在这里对于 printData 函数有非模板函数、模板函数通用版本以及针对 char 类型的特化版本。调用 printData(num) 时根据精确匹配优先原则会调用非模板函数 printData(int)而调用 printData(ch) 时由于存在针对 char 类型的特化版本会优先调用这个特化版本而不是模板函数的通用版本同样体现了编译器在选择调用函数时遵循的优先匹配规则。
2. 函数隐藏Function Hiding
概念
函数隐藏同样出现在类的继承关系中是指在派生类中定义了与基类同名的函数不管参数列表是否相同此时派生类的函数会隐藏基类中同名的所有函数包括重载函数在派生类的作用域内如果不使用作用域限定符显式指定就无法访问到基类中被隐藏的同名函数。 隐藏在基类和派生类之间发生的关系函数名相同派生类的函数把基类的函数给隐藏了只关注函数数名不管返回值和参数
示例代码
#include iostreamclass Base {
public:void func() {std::cout Base类的func函数 std::endl;}void func(int num) {std::cout Base类的func(int)函数 std::endl;}
};class Derived : public Base {
public:void func(double num) {std::cout Derived类的func(double)函数 std::endl;}
};int main() {Derived derived_obj;derived_obj.func(3.0); // 调用Derived类的func(double)函数// 以下代码编译错误因为Derived类的func函数隐藏了Base类的func函数// 不能直接在Derived类作用域内调用Base类的func函数// derived_obj.func();// 使用作用域限定符可以访问Base类的func函数derived_obj.Base::func();return 0;
}在上述代码中Derived 类中定义了 func(double num) 函数它隐藏了 Base 类中的 func 函数和 func(int num) 函数所以在 Derived 类的作用域内直接调用 func 函数时编译器会认为是调用 Derived 类自身定义的函数如果要访问基类中被隐藏的同名函数需要通过 Base::func() 这样的作用域限定符来明确指定。
特点
存在继承关系也是基于类的继承场景出现的情况派生类定义了与基类同名的函数。同名即隐藏只要函数名相同就会发生隐藏与参数列表是否相同无关而且是隐藏基类中所有同名函数这一点和重载、覆盖都不同重载是通过参数差异来区分不同函数覆盖是严格按照函数签名一致且有虚函数特性来实现的。作用域相关访问问题隐藏导致在派生类作用域内默认情况下无法直接访问基类中被隐藏的同名函数需要使用作用域限定符来打破这种隐藏效果才能调用基类的同名函数。
3. 函数覆盖重写Function Overriding
概念
函数覆盖发生在类的继承关系中是指在派生类中重新定义了基类中的虚函数并且要求函数签名函数名、参数列表、返回类型返回类型如果是指针或引用类型时允许协变完全一致除了 const 修饰符派生类重写函数可以比基类函数多 const 修饰当通过基类指针或引用调用该函数时会根据对象的实际类型是基类对象还是派生类对象来决定调用基类的函数还是派生类重写后的函数这是实现多态性的重要机制。
覆盖覆盖是隐藏的一种特殊情况派生类和基类的函数1 函数名相同2 返回值相同3 参数列表相同不包括this指针在内4 基类函数为虚函数即为覆盖。 其原理是子类虚函数表的函数地址覆盖了父类虚函数表里函数的指针。
示例代码
#include iostreamclass Base {
public:virtual void func() {std::cout Base类的func函数 std::endl;}
};class Derived : public Base {
public:void func() override {std::cout Derived类的func函数 std::endl;}
};int main() {Base* ptr new Derived();ptr-func(); // 调用Derived类重写后的func函数Base base_obj;base_obj.func(); // 调用Base类的func函数Derived derived_obj;derived_obj.func(); // 调用Derived类的func函数return 0;
}在这个例子中Derived 类重写了 Base 类中的虚函数 func通过基类指针 ptr 指向派生类对象时调用 func 函数会执行 Derived 类中重写后的版本体现了多态性。而直接使用基类对象或者派生类对象调用 func 函数时则分别调用各自类中定义的函数。
特点
存在继承关系必须在派生类和基类之间发生基类中声明了虚函数派生类对其进行重写。函数签名要求严格一致函数名、参数列表、返回类型遵循协变规则等情况除外要相同比如基类函数是 void func(int num)派生类重写的函数也得是 void func(int num)目的是让编译器能准确识别这是重写关系以便在多态调用时正确执行相应版本的函数。虚函数特性基类中的函数必须是虚函数通过 virtual 关键字修饰这样编译器才会在运行时根据对象的实际类型来动态决定调用哪个类的函数实现多态行为。