函数和函数模板

一、函数的参数及其传递方式

C++的函数传递有两种传递方式:传值和伟引用。传值分为传“对象值”和“对象地址值”,“对象值”是指对象的数据成员值,“对象地址值”是指对象的首地址值。伟引用其实是传对象的地址。

注意:传地址值传递的是值,不是地址;传地址传的是地址,不是地址值。传递对象地址值是使用对象指针作为参数,传递对象地址是使用对象引用作为参数。

1、对象作为函数参数

将对象作为函数参数,是将实参对象的值传递给形参对象,这种传递是单向的。形参拥有实参的备份,当在函数中改变形参的值时,改变的是这个备份的值,不会影响原来实参的值,这种传值方式可以防止被调用的函数改变参数的原始值。

#include <iostream>
#include <string>
using namespace std;
void example1();
void swap1(string,string);//声明交换方法的函数原型
int main(){
	example1();
	return 0;
}
 
void example1(){
	string s1("过去"),s2("现在");//定义对象s1和s2
	cout << "交换之前:" << s1 << " " << s2 << endl;
	swap1(s1,s2);//使用传值方式传递s1和s2的数据成员值
	cout << "交换之后返回主函数:" << s1 << " " << s2 << endl;
	/**
	 * 交换之前:过去 现在
	 * 交换之后:现在 过去
	 * 交换之后返回主函数:过去 现在
	 */
}
 
void swap1(string s1,string s2){
	string temp = s1;
	s1 = s2;
	s2 = temp;
	cout << "交换之后:" << s1 << " " << s2 << endl;
};

2、对象指针作为函数参数

将指向对象的指针作为函数参数,形参是对象指针,实参是对象的地址值。虽然参数传递方式仍然是传值,但因为形参传递的就是实参本身,所以当在函数中改变形参的值时,改变的就是原来实参的值。传对象地址值要用到对象指针,常使用的传对象地址值的方式,如传递类或结构对象的地址值、传递数组名。

void example2();
void swap2(string *,string *);
int main(){
	example2();
	return 0;
}
 
void example2(){
	string s1("过去"),s2("现在");
	cout << "交换之前:" << s1 << " " << s2 << endl;
	swap2(&s1,&s2);//使用传地址值的方式传递s1和s2的地址值
	cout << "交换之后返回主函数:" << s1 << " " << s2 << endl;
	/**
	 * 交换之前:过去 现在
	 * 交换之后:现在 过去
	 * 交换之后返回主函数:现在 过去
	 */
}
 
void swap2(string *s1,string *s2){//string类的对象指针s1和s2作为参数
	string temp = *s1;
	*s1 = *s2;
	*s2 = temp;
	cout << "交换之后:" << *s1 << " " << *s2 << endl;
}

因为实参和形参的地址相同,所以改变形参就是改变实参。下面举例传递数组名:

void example3();
void swap3(int []);
int main(){
	example3();
	return 0;
}
 
void example3(){
	int a[] = {8,3};
	cout << "交换之前:" << a[0] << " " << a[1] << endl;
	swap3(a);//传递数组名a也就是传递指针
	cout << "交换之后返回主函数:" << a[0] << " " << a[1] << endl;
	/**
	 * 交换之前:8 3
	 * 交换之后:3 8
	 * 交换之后返回主函数:3 8
	 */
}
 
void swap3(int a[]){//使用数组名a也就是指针名作为函数参数
	int temp = a[0];
	a[0] = a[1];
	a[1] = temp;
	cout << "交换之后:" << a[0] << " " << a[1] << endl;
}

数组名就是数组指针名,数组指针名指向数组首地址,所以相当于传地址值,可以实现交换。

3、引用作为函数参数

使得引用作为函数,在函数调用时,实参对象名传给形参对象名,形参对象名就成为实参对象名的别名,实参对象和形参对象代表同一个对象,所以改变形参对象的值就是改变实参对象的值。

实际上,在传递地址时,是把实参对象的地址传给形参对象,使用形参对象的地址取实参对象的地址,从而使用形参对象和实参对象共享一个单元,这就是地址传递方式。

void example4();
void swap4(string &,string &);
int main(){
	example4();
	return 0;
}
 
void example4(){
	string s1 = "过去",s2 = "现在";
	cout << "交换之前:" << s1 << " " << s2 << endl;
	swap4(s1,s2);//使用传地址的方式传递s1和s2的引用地址
	cout << "交换之后返回主函数:" << s1 << " " << s2 << endl;
	/**
	 * 交换之前:过去 现在
	 * 交换之后:现在 过去
	 * 交换之后返回主函数:现在 过去
	 */
}
 
void swap4(string &s1,string &s2){//string类的引用对象s1和s2作为函数参数
	string temp = s1;
	s1 = s2;
	s2 = temp;
	cout << "交换之后:" << s1 << " " << s2 << endl;
}

系统向形参传递的是实参的地址而不是实参的值,通过引用参数,一个函数可以修改另一个函数内的变量,因为引用对象不是一个独立的对象,不单独占用内存单元,而对象指针要另外开辟内存单元(其内容是地址),所以传引用比传指针要更好。

4、默认参数

默认参数就是不要求程序设定该参数,而由编译器在需要时给该参数赋默认值,当程序员需要传递特殊值时,必须显式的指明。默认参数是在函数原型中说明的 ,默认参数可以多于一个,但必须放在参数序列的后面。
void example6();
void display1(int first,int second=2,int third=3);
int main(){
	example6();
	return 0;
}
 
void example6(){
	int first = 10,second = 20,third = 30;
	display1(first);//第一个参数值是:10,第二个参数值是:2,第三个参数值是:3
	display1(first,second);//第一个参数值是:10,第二个参数值是:20,第三个参数值是:3
	display1(first,second,third);//第一个参数值是:10,第二个参数值是:20,第三个参数值是:30
}
 
void display1(int first,int second,int third){
	cout << "第一个参数值是:" << first << ",第二个参数值是:" << second << ",第三个参数值是:" << third <<endl;
}

5、使用const保护数据

用const修饰传递参数,是通知函数,它只能使用参数而无权修改它。

void display2(const string &,string &);

二、内联函数

① 使用关键字inline可以让C++编译器在遇到对函数isnumber调用的时候,都用这个函数体替换调用表达式;

② 由于编译器必须知道内联函数的函数体,才能进行内联替换,因此内联函数必须在程序中第一次调用此函数的语句出现之前定义;

③ 如果程序多次调用某个函数,将会大大降低使用效率,使用内联函数能加快程序执行速度;

④ 具有循环语句、switch语句的函数不能说明为内联函数。

void example11();
inline int isnumber(char c){
	return (c>='0'&&c<='9')?1:0;
}
int main(){
	example11();
	return 0;
}
 
void example11(){
	char c;
	cin >> c;
	if(isnumber(c)){
		cout << "你输入的是一个0-9之间的数字!" << endl;
	}else{
		cout << "你输入的不是一个数字!" << endl;
	}
 
//	0
//	你输入的是一个0-9之间的数字!
}

三、函数重载和默认参数

函数重载可使一个函数名具有多种功能,即具有“多种形态”,这种特性被称为多态性。C++的多态性被直观的称为“一个名字,多种函数”,即每个函数除了函数名相同外,其他的都不相同。这时源代码只指明函数调用,而不说明具体调用哪个函数,编译器的这种连接方式称为动态联编,在动态联编中,只有程序运行时才能确定调用哪个函数。

如果编译器在编译时,能根据源代码调用固定的函数标识符,并用物理地址替代它们,这就称为静态联编,静态联编是在程序被编译时进行的。

void example12();
float max(float,float);//具有两个实型参数的函数原型
int max(int,int,int);//具有两个整型和一个实型参数的函数原型
int main(){
	example12();
	return 0;
}
 
void example12(){
	cout << "请输入两个实型数:" << endl;
	float f,g;
	cin >>f,cin>>g;
	float ff = max(f,g);
	cout << f << "和" << g << "中最大的实型数是:" << ff << endl;
 
	cout << "请输入三个整数:" << endl;
	int i,j,k;
	cin >>i,cin>>j,cin>>k;
	int ii = max(i,j,k);
	cout << i << "、" << j << "、" << k << "中最大的整数是:" << ii << endl;
}
 
float max(float f,float g){
	return f>g?f:g;
}
 
int max(int i,int j,int k){
	int temp = i>j?i:j;
	return temp>k?temp:k;
}
<think>首先,用户的问题是:“我想理解C++中模板函数函数模板的概念及其区别 请问C++ 模板函数函数模板的区别使用”。我需要基于提供的引用系统指令来回答。 系统指令: - 所有行内数学表达式必须使用 $...$ 格式。 - 独立公式必须使用 $$...$$ 格式并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识,例如:[^1] - 回答结构清晰,帮助用户逐步解决问题,尽量保证回答真实可靠。 - 参考站内引用:[^1], [^2], [^3]。 提供的引用内容: - [^1]: 介绍C++模板的基本概念:模板是建立一个通用函数或类,内部类型用虚拟类型代表,是泛型编程的基础。 - [^2]: 解释函数模板:用于生成函数的模板,编译器根据使用情况创建具体函数,参数类型不同。 - [^3]: 展示普通函数函数模板的区别调用规则,包括二义性问题。 用户的问题是关于“模板函数函数模板”的区别使用。我需要澄清这些术语。 在C++中: - **函数模板 (Function Template)**: 这是一个模板,定义了一个通用的函数模式,其中类型是参数化的。例如:`template<typename T> T myAdd(T a, T b) { return a + b; }` - **模板函数 (Template Function)**: 这个术语有时被误用。实际上,函数模板实例化后生成的函数称为模板函数函数模板的特化。但在标准术语中,函数模板是模板,而模板函数是实例化后的具体函数。 从引用[^2]中:"函数模板是用于生成函数的模板"。引用[^3]中使用了"模板函数",可能指的是函数模板实例化后的函数。 所以,区别: - **函数模板 (Function Template)**: 是一个蓝图或模式,用于生成函数。 - **模板函数 (Template Function)**: 通常指函数模板被实例化后创建的具体函数。但有时人们互换使用这些术语,这可能导致混淆。 在回答中,我应该明确: - 函数模板是定义。 - 模板函数是实例。 现在,构建回答: 1. **结构清晰**:逐步解释概念。 - 先介绍模板概念。 - 然后解释函数模板。 - 最后讨论区别使用。 2. **使用中文**。 3. **引用标识**:在引用内容后添加[^1], [^2], [^3]。 4. **生成相关问题**:在最后添加相关问题部分。 5. **数学表达式**:用户问题中没有明显的数学内容,但如果有,确保使用$...$或$$...$$。这里可能不需要,除非涉及数学公式。 回答大纲: - **引言**:简要介绍C++模板。 - **函数模板的概念**:解释什么是函数模板,用例子。 - **模板函数的概念**:澄清术语,指实例化后的函数。 - **区别**:总结函数模板 vs. 模板函数。 - **使用**:如何定义使用,包括调用规则注意事项(如二义性)。 - **例子**:基于引用[^3]提供代码示例。 - **相关问题**:生成3-5个相关问题。 确保引用: - 当引用[^1], [^2], [^3]的内容时,自然地添加标识。 最终回答: - 以清晰的结构组织。 - 结束时添加"§§相关问题§§"部分。</think>### C++ 函数模板与模板函数的区别使用 在C++中,模板是泛型编程的核心工具,允许编写独立于具体类型的代码。用户的问题聚焦于“函数模板“模板函数”的概念区别以及实际使用。我将基于C++标准术语提供的引用内容,逐步解释这些概念,确保回答清晰可靠。以下内容参考了站内引用资料[^1][^2][^3]。 #### 1. **基本概念介绍** - **函数模板 (Function Template)**:这是一个通用函数蓝图,使用模板参数(如 `typename T`)表示未指定的类型。编译器在编译时根据实际调用参数类型生成具体函数函数模板是模板本身,不是一个具体函数。 例如,引用[^2]中提到:“函数模板是用于生成函数的模板。在编译阶段,编译器会根据函数模板的使用情况创建出函数名相同,参数类型不同的若干函数。” 定义示例: ```cpp template <typename T> T add(T a, T b) { return a + b; } ``` 这里,`add` 是一个函数模板,它定义了加法操作的通用模式,但 `T` 可以是 `int`、`double` 或任何支持 `+` 操作的类型。 - **模板函数 (Template Function)**:这个术语常被误用或混淆。在标准C++中,模板函数通常指函数模板被实例化后生成的具体函数。例如,当调用 `add<int>(10, 20)` 时,编译器会生成一个针对 `int` 类型的函数实例(即模板函数)。模板函数是编译后的实体,具有具体类型。 引用[^3]中的代码演示了这一点:当调用 `myAdd01(a, b)` 时,如果使用 `myAdd01<>(a, c)`,编译器会实例化模板函数。 **关键区别总结**: - **函数模板**:是模板定义(代码蓝图),使用 `template` 关键字声明,类型是泛化的(如 `T`)。 - **模板函数**:是函数模板实例化后的具体函数(编译后代码),类型已固定(如 `int` 或 `double`)。 简单来说,函数模板是“食谱”,而模板函数是“根据食谱做好的菜”。混淆这两个术语可能导致代码错误或二义性问题[^3]。 #### 2. **区别详述** - **定义方式**: - 函数模板:必须用 `template <typename T>` 开头,类型参数是虚拟的。 - 模板函数:无需额外定义,由编译器自动生成当函数模板被调用时。 - **使用场景**: - 函数模板用于编写通用代码,提高复用性(如支持多种类型的排序算法)。 - 模板函数用于实际执行,编译器根据调用参数推断类型(如 `add(3.14, 2.71)` 会生成 `double` 版本的函数)。 - **二义性问题**:如果同时存在普通函数函数模板,编译器优先调用普通函数,除非显式指定模板。引用[^3]的代码展示了这一点: ```cpp int myAdd01(int a, int b); // 普通函数 template <typename T> T myAdd01(T a, T b); // 函数模板 // 调用时: myAdd01(10, 20); // 调用普通函数 myAdd01<>(10, 20); // 调用模板函数(实例化后) ``` 如果类型不匹配(如 `myAdd01(10, 3.14)`),可能导致编译错误或二义性,因此建议优先使用函数模板以避免冲突[^3]。 #### 3. **使用方法与最佳实践** - **定义函数模板**: - 使用 `template <typename T>` 声明类型参数。 - 在函数体中,使用 `T` 作为参数或返回类型。 - 示例(基于引用[^1]): ```cpp template <typename T> T max(T a, T b) { return (a > b) ? a : b; } ``` 编译器会根据 `max(5, 10)` 或 `max(5.5, 10.5)` 生成 `int` 或 `double` 版本的模板函数。 - **调用规则**: - **隐式实例化**:编译器自动推断类型,如 `add(3, 4)` 生成 `int` 模板函数。 - **显式实例化**:指定类型,如 `add<double>(3, 4.5)`,强制生成 `double` 版本。 - **重载函数模板**:可以定义多个模板参数(如 `template <typename T, typename U>`),或重载函数模板(如引用[^3]中的三参数版本)。 - **注意事项**: - 避免与普通函数重名,以减少二义性风险(引用[^3]强调:“如果有模板函数就尽量不用普通函数”)。 - 模板代码必须定义在头文件中,因为编译时需要完整定义。 - 性能:模板在编译时实例化,无运行时开销,但可能增加编译时间。 #### 4. **实际示例** 基于引用[^3],这里是一个完整示例,展示函数模板模板函数的交互: ```cpp #include <iostream> using namespace std; // 普通函数 int myAdd(int a, int b) { cout << "调用普通函数" << endl; return a + b; } // 函数模板 template <typename T> T myAdd(T a, T b) { cout << "调用模板函数" << endl; return a + b; } int main() { int x = 10, y = 20; cout << myAdd(x, y) << endl; // 优先调用普通函数 cout << myAdd<>(x, y) << endl; // 显式调用模板函数(实例化为int版本) double a = 3.14, b = 2.71; cout << myAdd(a, b) << endl; // 调用模板函数(编译器生成double版本) return 0; } ``` 输出: ``` 调用普通函数 30 调用模板函数 30 调用模板函数 5.85 ``` 此例中,`myAdd` 函数模板被实例化为模板函数(如 `int` 或 `double` 版本),而普通函数在参数匹配时优先。 #### 5. **总结** - **函数模板**:是泛型编程的基础,提供代码复用性,使用 `template` 定义。 - **模板函数**:是函数模板实例化后的具体函数,由编译器生成。 - 核心区别:函数模板是“蓝图”,模板函数是“成品”。正确使用可提升代码灵活性,但需注意二义性问题[^3]。 C++模板是强大工具,支持泛型算法如快速排序(类似引用[^1]中的泛型示例)。通过编译器实例化机制,模板函数在保证类型安全的同时,减少代码冗余。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值