函数和函数模板
目录
一、函数的参数及其传递方式
1.C++的函数参数有两种传递方式:
传值和传引用,其中传值可分为传对象值和传地址值,传地址值的时候传的实际是地址的整数值,也还是传值。
传递对象地址值是使用对象指针作为参数,传递地址时使用对象引用作为参数。
示例:
非数组指针或引用型变量做实参时,它和对应虚参之间的数据传递方式是( B )。
A.地址传递
B.单向值传递
C.双向值传递
D.由用户指定传递方式
非数组指针做实参时和对应虚参之间的数据传递方式是单向地址值的传递。
引用型变量做实参时,它和对应虚参之间的数据传递方式是单向数值传递。
引用型变量做形参时,它和对应实参之间的数据传递方式是地址传递。下面是引用型变量做实参时的程序实例:
#include <iostream>
#include <string>
using namespace std;
void swap(string s11, string s22){
string temp=s11; s11=s22; s22=temp;
cout<<"交换为strl =<<s11<<"str2="<<s22<<endl;
}
void main(){
string strl("现在"),str2("过去");
string &s1=strl, &s2=str2;
cout<<"引用:sl="<<s1<<"s2="<<s2<<endl;
swap(s1, s2);
cout<<"返回后:strl="<<strl<<"str2="<<str2<<endl;
}
运行结果:
2.函数参数的三种类型:
(a)对象作为函数参数:
将对象作为函数参数,是将实参对象的值传递给形参对象,是单向传递,形参拥有实参的备份,当函数中改变形参的值时,改变的是这个备份的值,不会影响原来实参的值。(2018版P51)
//为说明问题简化书上例子,详见P51页
void swap(string,string); //函数声明,传递的是string类对象作为形式参数
void main()
{
string str1("现在"),str2("过去");
swap(str1,str2); //函数调用,str1,str2是string类的实际参数
}
void swap(string s1,string s2) //函数定义,s1,s2是string类的形式参数
{
//函数体
}
(b)对象指针作为函数参数:
将指向对象的指针作为函数参数,形参是对象指针(指针可以指向对象的地址),虽然参数传递方式仍然是传值的方式,但因为形参传递的就是实参本身,所以当函数中改变形参的值时,改变的就是原来实参的值。传对象地址值要用到对象的指针。(2018版P52)
//为说明问题简化书上例子,详见P52页
void swap(string *,string *); //函数声明,传递的是string类的指针作为形式参数
void main()
{
string str1("现在"),str2("过去");
swap(&str1,&str2); //函数调用,使用传地址值的方式传递str1,str2的地址值
}
void swap(string *s1,string *s2) //函数定义,形式参数s1,s2是string类的指针
{
//函数体
}
(c)引用作为函数参数:
这时函数并没有对形参对象初始化,即没有指定形参对象是哪个对象的别名。在函数调用时,实参对象名传给形参对象名,形参对象名就成为了实参对象名的别名。实参对象和形参对象代表同一个对象,所以改变形参的值就是改变实参的值。
实际上就是把实参的地址传给了形参对象,从而使形参和实参共享一个储存单元,可以简单地认为是把实参的名字传给引用对象,使引用对象成为实参对象的别名。
在说明引用参数的时候,不需要提供初始值,其初始值在函数调用时由实参对象提供。(2018版P53)
//为说明问题简化书上例子,详见P53页
void swap(string &,string &); //函数声明,传递的是string类的引用对象作为形式参数
void main()
{
string str1("现在"),str2("过去");
swap(str1,str2); //函数调用,虽然系统向形参传递的是其地址,但实参必须用对象名
}
void swap(string &s1,string &s2) //函数定义,形式参数s1,s2是string类的引用对象
{
//函数体
}
二、默认参数以及使用const保护数据
默认参数是在函数原型中说明的,默认参数可以多于1个,但必须放在参数序列的后部。
用const传递参数,意思通知函数,只能使用参数而无权修改它。
//为说明问题简化书上例子,详见P55页
void Display(const int &i,string s1, string s2="",string s3 =""); //函数声明,i用const修饰
void main()
{
int i=0;
string str1("现在"),str2("过去"),str3("将来");
Display(str1,i); //错误
Display(i,str1); //可以,输出结果为:1,现在
Display(i,str1,str3) //可以,输出结果为:1,现在,,将来
cout<<i<<endl; //输出结果为0
}
void Display(const int &i, string s1, string s2, string s3)
{
i++;
cout<<i<<","<<s1<<","<<s2<<","<<s3<<endl;
}
三、函数返回值的4种情况
C++函数的返回值类型可以是除数组和函数以外的任何类型,数组只能返回地址。当函数的返回值是指针或引用对象时,需注意:函数返回所指的对象必须继续存在,因此不能将函数内部的局部对象作为函数的返回值。
3.1返回引用的函数
函数可以返回一个引用,这么做的目的主要是为了将该函数用赋值运算符的左边。
函数原型的表示方法为:数据类型 & 函数名(参数列表);
//这种情况下函数可以直接用在赋值运算符左边
#include <iostream>
using namespace std;
int a[] = {2,4,6,8,10,12}; //全局数组
int & index(int i); //返回引用的函数原型声明
void main()
{
index(3)=16; //将a[3]改为16
cout<<index(3)<<endl; //输出16
}
int & index(int i) //定义函数
{
return a[i]; //返回指定下标的数组内容
}
3.2返回指针的函数
一般定义如下:类型标识符 * 函数名(参数列表)
3.3返回对象的函数
//最常见的用法
string getString(string str)
{
string s1;
s1 = str;
return s1;
}
3.4函数返回值作为函数的参数
max(55,max(25,39));
四、内联函数
C++编译器会在遇到这个内联函数的地方调用函数体替换其调用表达式,除循环、switch不能做内联函数外,其他都可以说明为内敛函数。
语法:inline + 一般函数定义
五、函数重载和默认参数
函数重载是一种静态联编,在程序被编译时进行,因此也称为编译的多态性。
函数的重载体现在其参数列表的不同即参数的类型和个数的不同。
使用默认参数,就不能对少于参数个数的函数进行重载,因为编译器无法判断去使用用了几个参数的函数。
六、函数模板
6.1函数模板的声明和定义:
函数模板的写法如下:
template <class 类型参数1, class类型参数2, ...>
返回值类型 模板名(形参表)
{
//函数体
}
其中的 class 关键字也可以用 typename 关键字替换,例如:
template <typename 类型参数1, typename 类型参数2, ...>
函数模板看上去就像一个函数。函数max模板的写法如下:
template <class T>
T max(T m1,T m2)
{
return (m1>m2)?m1:m2;
}
T 是类型参数,代表类型。编译器由模板自动生成函数时,会用具体的类型名对模板中所有的类型参数进行替换,其他部分则原封不动地保留。同一个类型参数只能替换为同一种类型。编译器在编译到调用函数模板的语句时,会根据实参的类型判断该如何替换模板中的类型参数。
6.2函数模板的调用:
显式比较准则:函数模板名 <模板参数> (参数列表),如:max <int> (1,2) 就能知道模板参数是 int 。
默认调用方式:函数模板名 (参数列表),由这个调用的函数参数列表必须要能够唯一地标识出模板参数的一个集合。
历年真题
//改错题:
#include<iostream.h>
template<class T>
void Swap(T& a,T& b)
{
T t;
t=a,a=b,b=t;
}
void main( )
{
int a=3,b=4;
char str1[5]="abcd",str2[5]="hijk";
Swap(a,b);
Swap(str1,str2);
cout<<"a="<<a<<",b="<<b<<endl;
cout<<"str1="<<str1<<",str2="<<str2<<endl;
}
答案:str1,str2是char类型数组的名字也就是首地址,Swap(str1,str2);形参是引用,而实参是指向地址的指针,显然不对。