一个典型的函数含有的几部分:返回类型,函数名字,由0个或多个形参组成的列表以及函数体
eg:一个简单的阶乘函数
函数返回类型 函数名 形参
I I I
int fun(int val)
{
int sum=1;
while(val>1)
{
sum=sum*val;
val--;
}
return sum;
}
调用函数
eg:
int main()
{
实参
I
int j=fun(6);
cout<<"6!="<<j<<endl;
return 0;
}
有些时候有必要令局部变量的生命周期贯穿函数调用及之后的时间,可以将局部变量定义成static类型从而获得这样的对象。(局部静态变量)
eg:知道一个函数被调用几次的程序
size_t count()
{
static size_t ctr=0;
return ++ctr;
}
int main()
{
for(size_t i=0;i<=10;i++)
cout<<count()<<endl;
return 0;
}
形参的类型决定了形参与实参的交互方式
如果形参是引用类型,它将绑定到对应的实参上,否则,将实参的值拷贝后赋给形参
引用形参它对应的实参的别名
实参的值被拷贝给形参时,形参和实参是两个相互独立的对象
指针形参
指针的行为与其他非引用形参类型一样,当执行操作时,拷贝的是指针的值
eg:
void reset(int *p)
{
*p=0; //改变了指针p所指向对象的值
p=0; // 只改变了p的局部拷贝,实参并未改变
}
int main()
{
int i=6;
reset(&i); //改变的是i的值而非i的地址
cout<<i<<endl; //输出i的值为0
}
引用操作实际是对对象本身操作
eg:
void reset(int &j)
{
j=j*j;
}
int main()
{
int j=6;
cout<<j<<endl; //6
reset(j);
cout<<j<<endl; //36
}
何时使用引用参数:
使用引用参数的主要的原因有两个:
- 程序员能够修改调用函数中的数据对象
- 通过传递引用而不是整个数据对象,可以提高程序的运行速度。
使用引用、指针、按住传递的一些指导原则:
1.对于使用传递的值而不对其作修改的函数:
- 如果数据对象很小,如内置数据类型或者小型数据类型,就使用传值调用。
- 如果数据对象是数组,则使用指针,因为这是唯一的选择,并将指针声明为指向const的指针
- 如果数据对象是较大的结构,则使用const指针或const引用,以提高效率,节省空间和时间
- 如果数据对象是类对象,则使用const引用。类设计的语义常常要求使用引用。传递类对象参数的标准方式是按引用传递。
2.对于修改调用函数中数据的函数:
- 如果数据对象时内置数据类型,就使用指针。
- 如果数据对象是数组,只能使用指针。
- 如果数据对象是结构,则使用引用或指针
- 如果数据对象是类对象。则使用引用。
如果函数无须改变引用形参的值,最好将其声明为常量引用
使用引用形参可以返回额外的信息
一个函数只能返回一个值,然而有时函数需要同时返回多个值,引用形参为我们一次返回多个结果提供了有效的途径
eg:
string find_char(const string &s,char c,string &occurs)
{
auto ret=s.size();
occurs=0;
for(decltype(ret)i=0;i!=s.size();i++)
{
if(s[i]==c)
{
if(ret==s.size())
ret=i;
++occurs;
}
}
return ret;//隐式返回出现次数occurs
}
当函数要改变元素的值的时候,把形参定义成非常量
数组形参
管理指针形参常用的三中方式
1.使用标记指定数组长度
eg:
void print(const char *c)
{
if(c) //不为空指针
{
while(*c) //不是空字符
{
cout<<*c++;//输出并向前移动一个位置
}
}
}
2.使用标准库规范
eg:
void print(const int *begin,const int *end)
{
while(begin!=end)
cout<<*begin++;
}
3.显示传递一个数组的大小的形参
eg:
void print(const int *a,size_t size)
{
for(size_t i=0;i<size;i++)
{
cout<<a[i]<<" ";
}
cout<<endl;
}
数组引用形参
eg:
void print(int (&arr)[18])
{
for(auto i : arr)
cout<<i<<" ";
cout<<endl;
}
注意:arr前的括号不能省
eg:
fun(int &arr[10]) //错误,将arr声明成了引用数组
fun(int (&arr)[10]) //正确,arr是具有10个整数的整型数组的引用
传递多维数组
与一维数组一样,多维数组传递给函数时,真正传递的是指向数组首元素的指针
eg:
void print(int (*p)[18],int size) //p是指向数组的首元素,该数组是由18个整数构成的数组
II
void print(int [][18],int size)
注意:*p前的括号不能省
eg:
int *p[18];//18个指针构成的数组
int (*p)[18];//指向含有18个整数的数组的指针
函数重载
函数多态(函数重载)能够使多个同名的函数存在,术语多态指的是有多种形式,因此函数多态允许函数可以有多种形式。术语函数重载指的是可以有多个同名的函数,因此对名称进行了重载。
函数重载的关键是函数的参数列表,也称为函数特征标。(并不是返回值,不同返回值不能够进行函数重载)
但是一些看起来彼此不同的特征表不能共存,eg:double cube(double x); 和 double cube(double & x);
因为:double x = 3; cuble(3); 与这两个原型都匹配,系统不知道调用哪个原型,因此它们不允许共存。
何时使用函数重载:仅当函数基本上执行相同任务,但是使用不同形式的数据时,才采用函数重载
函数模板
函数模板是通用的函数描述,也就是说,他们使用通用类型来定义函数。
eg:
template<class Any>
void Swap(Any &a,Any &b)
{
Any temp=a;
a=b;
b=temp;
}
template<typename Any>
void Swap(Any &a,Any &b)
{
Any temp=a;
a=b;
b=temp;
}
以上两者等价。第一行指出要建立一个模板,并将类型命名为Any。
调用的方式为:
int i=1;
int j=2;
Swap(i,j);
double x=1.0;
double y=2.0;
Swap(x,y);
编译器会自动生成函数的int版本和double版本,不需要程序员参与。
重载的模板:可以像重载常规函数定义一样重载模板定义。
eg:
template<class Any>
void Swap(Any a[],Any b[],int n)
{
Any temp;
for(int i=0;i<n;i++)
{
temp=a[i]
a[i]=b[i];
b[i]=temp;
}
}
template<class Any>
void Swap(Any &a,Any &b)
{
Any temp=a;
a=b;
b=temp;
}
int i=1;int k=2;Swap(i,k);
int d1[]={1,3};int d2={5,4};int n=3;Swap(d1,d2,n);
显示具体化:即,使用具体化来覆盖常规模板
由于C++允许将一个结构赋给另一个结构,如果Any是一个结构,假设为struct sysop,也可使用第一种方式,则结构中的所有成员都会被交换,但是如果只想交换结构中的name,则需要提供一个具体化函数定义(称为显示具体化)来覆盖常规模板。
1,第三代具体化
- 对于给定的函数名,可以有非模板函数、显示具体化模板函数、模板函数以及它们的重载版本
- 显示具体化的原型和定义,应该以template<>打头,并通过名称来指出类型。
- “具体化”将覆盖“常规模板”,而“非模板函数(正常函数)”将覆盖“具体化”和“常规模板”
非模板函数(正常函数):void swap(sysop &, sysop&)
模板函数:template<Any> void swap(Any &, Any &)
具体化的模板函数template<> void swap(sysop &, sysop &)、template<> void swap<sysop>(sysop &, sysop &)
2,早期的具体化方案不再说明
实例化和具体化:
隐式实例化:直接调用swap(x,j);导致编译器生成一个使用int类型的实例,叫做隐式实例化
显示实例化:template void swap<int>(int,int&)声明所需要的类型int,并加上关键字template,编译器会使用swap()
模板生成int类型的函数定义。不需要对其进行定义
显示具体化:template<> void swap(int &, int &)或等价声明:template<> void swap<int>(int &, int &)。意思是:不要使用swap()模板来生成函数定义,而应使用独立的、专门的函数定义显式地为int类型生成函数定义,这些原型需要程序员编写函数定义
声明:template<> void swap<int>(int &, int &);
定义:template<> void swap<int>(int &, int &){….实现具体过程}
注意:试图在同一个编程单元中使用同一种类型的显示实例化和显示具体化,将会出错
函数指针
函数指针指向的是函数而非对象.与其他指针一样,函数指针只向某种特定类型.函数的类型由它的返回类型和形参类型共同决定,与函数名无关
eg:
bool lengthCompare(const string &,const string &);
该函数的类型是bool(const string &,const string &).
其函数指针为bool (*p)(const string &,const string &) //p是指向一个函数,该函数的参数是两个const string的引用,返回值是bool类型
注:
*p两端的括号不可少,如果不写括号,则p是一个返回bool指针的函数
eg:
bool *p(const string &,const string &); //声明一个名为p的函数,该函数返回bool *