函数
1、关于返回值
函数的返回类型可以是内置类型(int、double)、复合类型(int&、string*)、还可以是void。不能返回数组或者函数,但可以返回指向数组的指针或指向函数的指针。
当函数返回非引用类型时,将函数的返回值复制给临时对象。这时,返回值可以是局部对象,也可以是求解表达式的结果。
当函数返回引用类型时,它返回的是对象本身而不是复制返回值。所以千万不要返回指向局部的变量的引用。
以下是正确返回引用类型
const string &shortString(const string &s1,const string &s2){
retrn s1.size()<s2.size()?s1:s2;
}
以下是错误的返回引用类型,因为ret是一个局部变量,函数运行完以后ret被回收。
const string &manip(const string &s){
string ret=s;
retrn ret;
}
事实上,返回引用的函数返回的是一个左值。因此,这样的函数可以用于任何需要使用左值的地方。如果不希望返回值被修改,应该应该将返回值声明为const。参见下例:
char &getValue(string &str,string::size_type ix){
return str[ix];
}
int main(){
string s("a value");
getValue(s,0)='A'; //function gets s[0],and then changed
cout<<s<<endl; //the result is "A value"
return 0;
}
注意:不要返回指向局部的指针,函数结束后局部对象会被释放,返回的指针变成了指向不再存在的对象的悬垂指针。
<!--[if !supportLists]-->2、<!--[endif]-->关于参数传递
C++是一种静态强类型语言,对每一次的函数调用,编译时都会检查实参的类型。
int gcd(int v1,int v2){ //求最大公约数
while(v2){
int temp=v2;
v2=v1%v2;
v1=temp;
}
return v1;
}
调用gcd(“hello”,”world”)是编译出错,因为实参不是int而是const char*。
调用gcd(3.14,6.29)时编译器发出警告,但是仍然会进行隐式参数转换,调用gcd(3,6)。
参数有两种类型:非引用型参数和引用型参数。
非引用型参数的传递是通过复制赋值完成参数的初始化的,即将实参复制给一个副本,副本参与函数的具体操作。前面gcd改变了v1和v2的值,但这只是改变了副本的值,它并没有改变实参的值,如果调用gcd(a,b),a,b的值并不会改变。
void reset(int *p){
*p=0;
p=0;
}
调用reset(a)后,a的值(指针)给副本_a,_a参与函数操作,所以a所指向的地址单元存储的值变成了0,a本身的地址没有改变。
可以用const来防止指针改变所指的内容。例:void use(const int *ip),不能用ip改变指针所指的内容。
use的实参可以是int*型和const int*型,而reset只能是int *型,不能是const int *型。
对于内置类型,由于函数使用的是副本,所以以下正确:
void fun1(int m); void fun2(const int m);
const int a=5; int b=6;
fun1(a);fun1(b);fun2(a);fun2(b);
fun1(a)是用const对象初始化了副本,fun2(b)表明b的副本在函数运行过程中其值不会改变。
引用形参数的传递会直接与实参绑定,形参只是实参的别名。下面的整数交换会成功:
void swap(int &m,int &n)
{int temp=m; n=m; m=n; }
引用型参数可以返回额外的结果。下例中得到了元素出现的次数。
vector<int>::const_iterator findValue(vector<int>::const_iterator begin,
vector<int>::const_iterator end,int value,vector<int>::size_type ×){
times=0;
vector<int>::const_iterator firstLocation=end;
for( ;begin!=end;begin++){
if(*begin==value){
if(firstLocation==end)
firstLocation=begin;
times++;
}
}
return firstLocation;
}
int main(){
vector<int> v;
v.push_back(1);v.push_back(2);v.push_back(1);v.push_back(3);v.push_back(5);
vector<int>::size_type t;
vector<int>::const_iterator iter=findValue(v.begin(),v.end(),1,t);
cout<<*iter<<" "<<t<<endl;
return 0;
}
大型对象在复制过程中效率比较低,用const型引用型参数可以省略复制过程,且保证实参不做改变。
bool isShort(const string &s1,const string &s2){
retrn s1.size()<s2.size();
}
如果函数有非const型引用型参数,那么不能传递const实参。因为函数可能修改了实参的内容。如果函数不会改变实参的值,那么应该将非const修改为const,扩大函数的使用范围。
void increaseValue(int &value){
value++;
}
const int v=1;
increaseValue(v); //error
increaseValue(0); //error
指针的引用与其他的数据的引用一样,只是它传递的是一个地址。下面的整数交换会成功。
void swap(int * &m,int * &n)
{int *temp=m; n=m; m=n; }
3、数组及vector的参数传递
为了避免复制vector,应该将vector参数声明为引用型。然而,更多人倾向于传递指向容器的迭代器。
void print(vector<int>::const_iterator begin,vector<int>::const_iterator end){
while(begin!=end)
cout<<*begin++;
cout<<endl;
}
数组不能通过复制赋值,所以无法编写数组类型的参数的函数;又由于使用数组名时,数组名会被转化为指向数组第一个元素的指针,所以函数用指向数组元素的指针来处理数组。有三种方式指定数组参数:
void print(int *); //用指向数组元素的指针来处理数组
void print(int[]); //等价于第一种形式
void print(int[10]); //等价于第一种形式,这种情况下的10没有意义,只会引起误解
数组形参也可以定义为引用型和非引用型。非引用型将第一个元素的地址复制给副本,函数操纵的是指针副本,这不会改变原来的数组的地址。不需要改变数组元素时应该将指针定义为指向const对象的指针,即:void f(const int*)。
对于引用型的数组形参,编译器不会将数组转化为指针,而是传递数组的本身。这时的数组大小为参数的一部分,编译器会严格的检查数组实参的大小与形参的大小是否匹配。
void print(int (&arr)[10]){
for(size_t i=0;i!=10;i++)
cout<<arr[i]<<" ";
cout<<endl;
}
int main(){
int i=0;
int j[2]={0,1};
int k[10]={0,1,2,3,4,5,6,7,8,9};
print(&i); //error,not an array of 10 ints
print(j); //error,not an array of 10 ins
print(k); //ok
return 0;
}
使用泛型可以解决引用型数组参数受数组的长度固定的限制。调用printArray(j)。
template<class T ,size_t N> void printArray(T (&arr)[N]){
for(size_t i=0;i!=N;i++)
cout<<arr[i]<<" ";
cout<<endl;
}
C++中没有多维数组,所谓的多维数组是指数组的数组,数组名也是指向第一个元素的指针。多维数组的元素本身就是数组,所以将其作为参数时,除第一维以外其它所有维的长度都是元素类型的一部分,必须指明。
void print(int (*arr)[10],int rowSize) //arr是指向含有10个int的数组的指针
void print(int arr[][10],int rowSize) //与上面等价
注意:int *matrix[10]是一个长为10的数组,数组的每个元素都是int*指针
int (*matrix)[10]是一个指针,指向一个数组,这个数组由10个int组成
传递数组时为了使操作不越界,常有三种方法。第一种是数组本身放一个结束的标记,C风格字符串就是用null作为标记符号;第二种是传递指向数组第一个和最后一个元素的指针;第三种是传入数组的大小。
void print(const int* begin,int *end){
while(begin!=end)
cout<<*begin++<<" ";
cout<<endl;
}
void print(const int* array,size_t size){
for(size_t i=0;i!=size;i++)
cout<<array[i]<<" ";
cout<<endl;
}
int main(){
int k[10]={0,1,2,3,4,5,6,7,8,9};
print(k,k+10);
print(k,10);
return 0;
}
4、声明、默认参数、局部静态对象及内联函数
变量可以在头文件中声明,源文件中定义。同样,函数也应该在头文件中声明,源文件中定义。放在头文件中可以保证指定函数所有的声明一致,如果函数的接口发生改变,只许修改唯一的声明。注意,定义函数的源文件应该包含声明函数的头文件。
默认实参是形参列表中形参提供的初始值来指定的。函数调用的实参按照位置解析,默认实参只能替代函数缺少的尾部实参。例如,声明的函数为:string initScreen(string::size_type height=24,string::size_typewidth=80,char background=''),则
string screen;
screen=initScreen(); //equivalent to initScreen(24,80,'')
screen=initScreen(66); //equivalent to initScreen(66,80,'')
screen=initScreen(66,36); //equivalent to initScreen(66,36,'')
screen=initScreen(66,36,'#');
screen=initScreen( , ,'?'); //error
screen=initScreen('?'); //error,calls initScreen('?',80,'')
也可以用其它的表达式来来确定默认实参。
string::size_type getHeight(int h);
string initScreen(string::size_type
height=getHeight(12),string::size_typewidth=80,char background='')
一般适合把默认参数放在头文件中。
static局部对象一旦被创建,直到程序结束前都不会被撤销,函数结束后它的值还在,以后调用函数时,都不会执行初始化语句。
size_t count(){
static size_t c=0;
return ++c;
}
int main(){
for(size_t i=0;i!=10;++i)
cout<<count()<<endl;
}
函数调用是一个消耗较大的工作:调用前先保存寄存器,并返回时恢复、实参复制、程序必须指向一个新的位置。使用内联函数能够避免这些开销。
inline const string &shortString(const string &s1,const string &s2){
retrn s1.size()<s2.size()?s1:s2;
}
那么调用cout<<shortString(str1,str2)<<endl,在编译时将展为
cout<<str1.size()<str2.size()?sstr11:str2<<endl
一般说来,内联机制适合优化小的、只有几行的而且经常调用的函数。它不支持递归。内联函数应该定义在头文件中,它对于编译器必须是可见的,这一点不同于其它函数。
<!--[if !supportLists]-->5、<!--[endif]-->类的成员函数
成员函数的声明必须在类定义的换括号里面(class SalesItem {...},省略号处)。它的定义可以放在括号里面也可以放在括号外面。下面的成员函数中sameISBN声明和定义在花括号内,averagePrice的声明在花括号里面,定义在花括号外面。在类外定义的函数需要用作用域操作符指明函数的作用范围。
class SalesItem{
public:
double averagePrice() const;
bool sameISBN(const SalesItem &other)const
{return isbn==other.isbn;} //等价于return this->isbn==other.isbn
SalesItem():sold(0),revenue(0.0){ }
private:
string isbn;
unsigned sold;
double revenue;
}
double SalesItem::averagePrice() const{
if(sold!=0)
return revenue/sold;
return 0;
}
当调用s.sameISBN(t)的时候,编译器会重写函数为SalesItem::sameISBN(&s,t),这里传入了隐含的指针this,它指向了调用对象的地址。函数后面的const就是用来修饰this所指的对象,这样的函数成为const成员函数,这时,函数不能修改对象的状态(成员变量)。类外定义的const成员函数不能把const省略掉。
SalesItem():sold(0),revenue(0.0){ }定义的是默认构造函数。
一般将类的声明放在头文件中,大多数情况下类外定义的函数成员函数放在源文件中,按照习惯,SalesItem的声明放在SalesItem.h中,成员函数的定义放在SalesItem.cc中,它应该包含SalesItem.h。
<!--[if !supportLists]-->6、<!--[endif]-->函数重载
作用域相同相同的两个函数,如果它们名字相同而形参表不同,则称它们重载。有些看起来不同的形参表的本质是相同的。例如
Record lookup(const Account &acc)与Record lookup(const Account &)相同;
typedef Phone Telno;则Record lookup(const Phone &)与Record lookup(const Telno&)相同;
Record lookup(const Name&)与Record lookup(const Name&=””)相同;
Record lookup(const Phone)与Record lookup(Phone)相同;形参列表被认为一样,当然,前者中副本的值一旦确定后就不能更改。这里值得注意的一点是,const的引用形或指针形参的参数表与非const不一样。
Record lookup(const Phone&)与Record lookup(Phone&)不同。
Record lookup(const Phone*)与Record lookup(Phone*)不同。
在局部声明的名子会屏蔽全局声明的名字。例如:
string init(){return "";}
int main(){
int init=1; //局部变量init屏蔽了全局函数init
string s=init(); //error
}
同样,局部声明的函数会屏蔽掉重载的函数。
void print(string s){cout<<s<<endl;}
void fa(){
void print(int i){cout<<i<<endl;}
print("hello");//error,print(string) is hidden
}
匹配时函数调用时从重载函数集合中选取函数关联的过程。这个过程分三步:1、确定重载函数集合;2、选取可行函数,可行函数要求参数个数与实参相同且类型匹配或者隐形转换后匹配;3、寻找最佳匹配,要求实参与形参越接近越好,参数类型匹配的好坏依次为:精确匹配、类型提升、标准转换、类类型转换。如char提升为int优于转换为short。double转换为long或者float均是标准转换,这时产生二义性。
对于引用和指针,编译器会通过形参和实参是否为const来确定哪个最匹配。
Record lookup(const Phone&);
Record lookup(Phone&);
const Phone a(“123456”);
Phone b;
lookup(a); //调用lookup(const Phone&)
lookup(b); //调用lookup(Phone&)
指针也一样,当然这里说的const指针是指const*,而不是fun(int *const a)。fun(int *const a)与fun(int *a)是重复定义。
<!--[if !supportLists]-->7、<!--[endif]-->指向函数的指针
语句bool (*pf)(const string&,const string&);将pf声明为一个指向函数的指针,所指向的函数的参数为两个const string&类型,返回类型为bool。
函数指针类型相当冗长,可以用typedef来简化函数指针的定义。
typedef bool (*pf)(const string&,const string&);
这样就可以使用pf类型了。这样的指针只能赋值完全对应类型的函数地址(函数名、函数名取地址)。
如果有函数bool compare(const string &,const string &),那么compare理解为如下形式的指针bool(*)(const string &,const string &)。注:compare与&compare等价。
pf m=0;//表示不指向任何函数
pf n=compare;
m=compare;
compare(“hi”,”hello”);
m(“hi”,”hello”);
(*m)(“hi”,”hello”);
还可以将函数指针作为函数的参数或者返回类型。
void useBigger(const string& ,const string&,bool(const string&,const string&));//最后个参数被认为是函数指针,也可以写成:
void useBigger(const string& ,const string&,bool (*)(const string&,const string&));
名称为ff、返回为int (*)(int*,int)、参数为int的函数为:int (*ff(int))(int*,int)。写起来极其麻烦,如果typedef int (*PF)(int*,int),那么只需写成PF ff(int)。
相关推荐
C语言电子谭浩强三函数学习教案.pptx
三函数的四则运算PPT课件.pptx
三函数的四则运算学习教案.pptx
江苏高考数学高考必会题型专题三函数与导数第15练函数的极值与最值.pdf
(衡水金卷)2016届高考数学二轮复习 三 函数作业专练1 文.doc
三、函数的嵌套调用和递归调用 函数可以嵌套调用,即一个函数调用另一个函数,嵌套调用可以形成一个函数调用链。函数的递归调用是指一个函数调用自身,递归调用可以形成一个无限的函数调用链。 四、局部变量和全局...
C语言中的函数是程序设计的重要组成部分,它们允许我们将代码组织成独立的模块,便于复用和管理。在第八章的PPT课件中,主要介绍了关于C程序设计中函数的各种概念和使用方法。 1. **概述**:函数分为库函数和用户...
第三部分题型三,对**函数性质的综合考查**。例3讨论了含参数的函数f(x) = x^2 + alnx的单调性问题。通过对导数的研究,分情况讨论了函数单调递增和递减的条件,涉及到了导数的几何意义和函数单调性与导数符号的关系...
本专题主要探讨了在考前最后三个月如何高效解决涉及抽象函数的问题,通过三个典型题型进行深入解析,帮助考生掌握解题策略。 题型一关注的是抽象函数的性质,特别是其周期性和单调性。在例1中,题目给出的函数f(x)...
本资料针对江苏省2015年高考数学,提供了三个重点题型的讲解和练习,分别是分段函数的值域问题、零点问题以及综合性问题。 1. **分段函数的值域问题**: - 例1展示了如何求解分段函数的值域。对于分段函数,我们...
【函数与导数】在高中数学中是核心概念之一,特别是在高考中占据重要地位。本资料专注于函数的图象问题,这是理解函数性质和解决实际问题的关键。以下是针对标题和描述中涉及的知识点的详细解释: 1. **函数图象的...
三次样条函数是一种在数值分析和计算数学中广泛使用的连续且光滑的插值方法,它在数据拟合和曲线平滑领域有着重要应用。MATLAB作为一种强大的数学计算环境,提供了方便的工具来实现三次样条函数的计算。下面将详细...
三次样条函数,顾名思义,是指由一系列三次多项式函数构成的光滑函数,这些函数在特定点(称为结点)处连接,使得整个函数在整个定义域内连续且三次可微。这种构造方式使得三次样条函数在处理数据插值时能够保持较高...
它通过构建一个三次多项式函数来平滑地连接数据点,确保在每个数据点处都连续且一阶导数和二阶导数也连续。这种方法在处理离散数据时非常有用,例如在曲线拟合、数据平滑和动画生成等方面。 在MATLAB中实现三次样条...
在数值分析和计算方法领域,三次样条插值函数是一种强大的工具,它在多个学科中均有广泛的应用。本文将详细介绍三次样条插值的概念、数学原理以及如何通过C++程序实现这一计算过程。 首先,需要理解三次样条函数的...
程序将由三个函数组成:主函数、perimeter函数和area函数。 标题解析 标题“输入圆的半径求周长和面积”表明了程序的主要目的,即根据输入的圆半径计算圆的周长和面积。 描述解析 描述部分告诉我们,程序由三个...
三次样条函数 MATLAB程序 三次样条函数S(1)= -6 3 2 8 14 0.20820 10 t - 3.1543 t + 0.15929 10 t - 0.26815 10 在区间[5050357.056000,5052919.512000]内 三次样条函数S(2)= -7 3 2 7 13 -0.24986 10 t + 0....
三次样条函数是一种在数值分析和计算数学中广泛使用的连续光滑插值方法。它通过构建一组函数,使得这些函数在给定的数据点上精确匹配,并且在这些点之间的区域保持平滑连续,同时满足二阶导数的连续性。这个过程涉及...
在MATLAB中绘制Ackley函数的三维图形是一项常见的可视化任务,尤其在优化算法和函数拟合的研究中。Ackley函数是一种常用的测试函数,它具有多个局部极小值和一个全局极小值,常用于评估优化算法的性能。下面将详细...
三、开尔文函数 开尔文函数,也称为 Kelvin 函数,是与热传导和弹性理论相关的一组特殊函数。主要包含两个基本函数:Kelvin函数bernu(x)和kerne(x)。这两个函数在实数域上都是偶函数,并且在0处有奇点。开尔文函数...