默认参数
默认参数指当函数调用中省略了实参时自动使用的一个值。设置默认参数需要通过函数原型。
//语法:void printFocal(double fo=2.0, ostream& os = cout);
//fo的默认值为2.0,os的默认对象是cout。
-
默认参数只能写在 "函数声明" 里,定义里不能重复写。
-
默认参数必须 "从右往左连续指定",不能跳过中间参数。
void func(int a, int b, int c = 30); // 可以
// void func(int a = 10, int b, int c); → 编译报错
- 调用时,默认参数的位置不能 "跳过",要按顺序传参
void func(int a, int b = 10, int c = 20);
...
func(5); // 正确:a=5,b=10(默认),c=20(默认)
// func(5, , 25); // 错误:不能跳过b直接给c传参(C++不支持)
...
- 默认参数不能和 "重载函数" 冲突
// 错误:两个函数会冲突
void func(int a); // 无默认参数
void func(int a, int b = 10); // 第二个参数有默认值
// 调用时:func(5) → 编译器不知道选哪个(是第一个?还是第二个用默认值?)
- 默认参数可以是 "全局变量、常量、表达式"
函数重载
函数多态是C++在C语言的基础上新增的功能。函数多态(函数重载)能够让程序员使用多个同名函数。
// 重载1:接收“焦距+输出流”(适配文件/屏幕输出)
void printFocal(double fo, ostream& os) {
os << "物镜焦距:" << fo << " mm" << endl;
}
// 重载2:只接收“焦距”(默认输出到屏幕,简化调用)
void printFocal(double fo) {
printFocal(fo, cout); // 直接调用重载1,避免重复代码
}
编译器判定两个函数是否为重载,只看参数的数量 、类型 、顺序。不看返回值、参数名。
结合学习的const和引用,这两种情况能形成合法重载;
- const可以改变引用和指针的参数类型,但对于值传递不会改变参数类型
// 1. 普通引用 vs const引用 → 重载(正确)
void print(const string& s); // 接收const字符串(保护数据,可接临时对象)
void print(string& s); // 接收非const字符串(可修改实参)
// 2. 指针 vs const指针 → 重载(正确)
void func(int* p); // 接收非const指针(可修改指针指向的值)
void func(const int* p); // 接收const指针(不可修改指针指向的值)
// 3. 值参数 vs const值参数 → 不算重载(错误,编译器视为同一类型)
// void func(int a);
// void func(const int a); // 编译报错(const不改变值参数的类型)
函数模板
函数模板是通用的函数描述。使用泛型来定义函数。
// 函数模板定义:template关键字+模板参数(T是通用类型名,随便起,比如T/U/V都可以)
template <typename T>//声明一个通用类型T(typename可以换成class,效果一样)
void my_print(const T& data) { // 用T作为参数类型,const引用传参(高效+安全)
cout << "输出数据:" << data << endl;
}
my_print(100); // 编译器生成void my_print(const int&)
my_print(3.14); // 生成void my_print(const double&)
my_print("Hello 模板"); // 生成void my_print(const char* const&)(const指针的引用)
my_print(string("C++")); //生成void my_print(const string&)
-
模板参数的声明:template <typename T> (
typename和class可以互换,早期只有class) -
template <typename T> 必须直接作用于紧随其后的那个函数(或类),不能隔其他函数 / 代码。
-
模板参数可以有多个(比如支持两个不同类型的参数)
template <typename T1, typename T2> -
模板的 "类型限制":不是所有类型都能用。实参类型支持才能操作。
template <typename T> T add(const T& a, const T& b) { return a + b; // 要求T类型支持“+”运算符 }// add("abc", "def"); // 错误:C风格字符串(char*)不支持'+'运算符 -
模板可以和引用 /const/ 继承结合
重载的模板
// 模板1:通用模板(适配同类型的基本类型)
template <typename T>
T add(const T& a, const T& b) {
cout << "通用模板(同类型):";
return a + b;
}
// 模板2:全特化重载模板(适配 string 类型,属于模板重载的一种形式)
template <> // 空模板参数列表,表示“全特化”(针对具体类型定制)
string add(const string& a, const string& b) {
cout << "重载模板(string专用):";
return a.append(b); // string用append()拼接,比+更安全
}
// 模板3:重载模板(适配两个不同类型,如int+double、double+int)
template <typename T1, typename T2> // 两个不同的模板参数
double add(const T1& a, const T2& b) {
cout << "重载模板(不同类型转double):";
return static_cast<double>(a) + static_cast<double>(b); // 统一转double
}
模板的局限性
模板的通用逻辑是写死的,如果遇到 "不支持这些操作" 的类型,模板就会失效。
解决方案一:重载运算符。
struct Point { //定义坐标结构体
int x;
int y;
};
// 重载+运算符:让Point支持a + b
Point operator+(const Point& a, const Point& b) {
Point res;
res.x = a.x + b.x; // 自定义坐标相加逻辑
res.y = a.y + b.y;
return res;
}
// 原通用模板(无需修改!)
template <typename T>
T add(const T& a, const T& b) {
return a + b; // 现在Point支持+,模板能正常调用
}
解决方案二:具体模板化
// 1. 通用模板(适用于int、double等支持+的类型)
template <typename T>
T add(const T& a, const T& b) {
cout << "通用模板:";
return a + b;
}
// 2. 全特化模板(针对string类型的专属实现)
template <> // 空模板参数列表,表示“全特化”
string add(const string& a, const string& b) {
cout << "string专属模板:";
return a.append(b); // 特殊逻辑:用append()拼接,比+更安全
}
| 解决方案 | 优点 | 缺点 |
|---|---|---|
| 重载运算符 | 模板无需修改,通用性强 | 只能给 "自己能修改的类型" 重载 |
| 模板具体化 | 不修改原类型,灵活度高 | 每个特殊类型都要写一个模板,繁琐 |
显示实例
函数模板本身并不会生成函数定义,它只是一个用于生成函数定义的方案。编译器使用模板为特定类型生成定义,得到模板实例。 ,函数调用导致编译器生成一个实例,这种实例化方式成为隐式实例化。
直接命令编译器创建特定实例,这种这种实例化方式成为显示实例化。
template <typename T>
T add(const T& a, const T& b) { // 模板里已定义形参列表
return a + b;
}
推荐写法
// 语法:template 返回类型 模板名<具体类型>;
template int add<int>(const int&, const int&);
// 解释:命令编译器生成“T=int”版本的add函数,参数列表和模板一致,不用重复写
不推荐写法
// 语法:template 返回类型 模板名<具体类型>(形参列表) { 实现 };
template int add<int>(const int& a, const int& b) {
return a + b; // 这里要重复模板的实现,容易和原模板不一致,不推荐
}