第七章: 函数
调用函数时如果该函数使用非引用的、非const形参,则既可以给函数传递const实参也可以传递非const实参。虽然函数的形参是const int类型的,但是编译器却将该形参定义为普通的int类型,在内部处理中const与非const是没有区别的。编译器在编译时进行检查确保我们不会修改const类型的变量。
当传递的实参为大型对象时为了降低传值赋值实参的开销,应该将参数定义为传引用或传指针。
应该讲不修改相应实参的形参定义为const引用,这不仅仅是为了防止在函数中不小心修改了实参,还有更深层的含义。
string::size_type find_char(string &s,char c)
{
string::size_type i=0;
while(i!=s.size()&&s[i]!=c)
{
i++;
}
return i;
}
如果将s定义为非const引用将会导致find_char("Hello World!!",'0');无法被调用,虽然字符串可以经过调用string的单参构造函数执行隐式转换,但由于能不能将const引用类型的实参传递给非const引用类型的形参,上述调用将会失败。
继续沿着这个问题讨论下去,
bool is_sentence(const string &s)
{
return (find_char(s,'.')==s.size()-1)
}
该函数的调用仍然会发生错误!!!
综上:应该将不需要修改的额形参定义为const引用,普通的非const引用在使用时不太灵活。这样的实参既不能用const对象初始化也不能用字面值或产生右值得表达式实参初始化。
注意以上都是对const和非const引用,对于非引用类型处理方式是不同的,见后面介绍。
传递对指针的引用,形式为int*&i,这已经在链表的使用以及在实验室写的与matlab结合的程序中熟练的使用。
通常函数中不应该有vector或是其他标准库容器类型的形参,调用普通的非引用vector形参的函数将会复制vector的每一个元素。应该将形参声明为引用类型。C++程序员更倾向于通过传递指向容器中需要处理的元素的迭代器来传递容器。看来我还不是合格的C++程序员。
将数组传递给函数时数组名会被转换为指针。使用sizeof(array)/sizeof(array[0])不再能计算出数组的元素个数了。要避免这种转换可以通过引用传递数组。如
int printArray(int (&array)[10]);
此时数组大小成为形参和实参类型的一部分,编译器会检查实参的大小是否与形参规定的数组元素个数相等。(&array)使用括号是必须的,下标操作符具有更高的优先级。不加编译器会报错的哦。
二维数组作为函数参数时形参的声明方式为:1:int (matrix*)[10]。2:int matrix[][20];无论哪种方式第二维的元素个数不能少哦。
二维数组也可以通过引用传递给函数。
有两种方式可以确保函数对数组的操作不超出数组的边界。1:将数组元素个数作为参数传递。2:向函数传递数组的第一个和最后一个元素下一个位置的指针。这是受标准库使用的迭代器的技术启发而得。
含有可变形参的函数如何实现和调用,关于此C++primer仅仅是一带而过,具体可以参考转载文章:变长参数函数(转载)。此处不再赘述。
返回值为void类型的函数可以使用return;提前强制结束函数执行。
对于有返回值的函数函数内有if用来判断当条件满足时返回一个值,函数末尾如果没有返回值,大部分编译器无法检测这个错误。如
bool foo()
{
int i=0;
while(1)
{
cout<<"!!!!!!! "<<endl;
if(i==20)break;
if(i==300)return false;
i++;
}
//函数最终会执行到此,但是由于i==300不会被满足,此函数没有返回值。
}
运行时出现什么错误都是不确定的。
main函数可以不返回值哦,此时 编译器会自动插入返回0的语句。
不要返回局部对象的引用,虽然明白其原理也明白不能返回局部对象的指针,但是经常会不小心返回局部对象的引用。太粗心。
默认实参仅能用来替代函数调用缺少的尾部实参。如果一个形参具有默认实参那么它后面所有的形参都必须有默认实参。既可以在函数声明中也可以在函数定义中指定默认实参。但是一个文件中只能为一个形参指定默认实参一次。如
//hh.h
int ff(int =0)
//hh.cpp
#include"hh.h"
int ff(int i=0)
{
}
此句是错误的。在h文件中已经指定默认实参,函数定义中就不可以再指定。通常应在函数声明中指定默认实参,并将声明放在合适的头文件中。如果仅仅在函数定义的形参列表提供默认实参,那么只有包含该函数定义的源文件中调用该函数默认实参才是有效地。
一般的函数也可以定义为内联函数。在返回之前加上inline即可。
内联函数应该在头文件中定义。
编译器隐式的将在类内定义的成员函数当做内联函数。
const成员函数中形参表后的const改变了隐含的this指针的类型。隐含的this指针将是一个指向const对象的指针。用这种方式使用const的函数被称为const成员函数。此类成员函数不能修改调用该函数的对象的成员变量。另外注意的是:const对象,指向const对象的指针或引用只能用于调用const成员函数,之所以这样规定是因为它们调用一般成员函数时可能会修改对象的内容。
不含参数的构造函数被称为默认构造函数。
对于没有显式为类提供任何构造函数的类,编译器自动为这个类生成 默认构造函数。它被称为合成构造函数。对于类类型的成员它会调用该成员所属类的默认构造函数实现初始化。对于内置类型的成员变量将不会被初始化。
如果函数形参完全相同仅仅返回值不同,则第二个声明是错误的。不能仅仅基于不同的返回类型实现重载。
bool lookup(Phone p);
bool lookup(const Phone p);
注意以上函数完全等价,因为函数为传值,由于操作的只是实参的副本,函数无法修改实参,因此无论形参是const和非const效果是一样的。前面介绍过const和非const的差异是建立在传引用的基础之上的。
在函数中局部生命的名字会屏蔽在全局作用域内声明的同名名字。这样的规则对于函数名也同样适用。因为函数名也是标识符的一种。如果局部的声明一个函数,那么该函数将屏蔽而不是重载在外层作用域声明的同名函数。如
void print(const string&);
void print(double);
void foo(int ival)
{
void print(int);//函数声明
print("Hello world!!");//错误
print(39);//正确。
print(3.33);//正确,存在double向int的隐式转换。
}
函数内声明的void print(int);将屏蔽foo函数外的其他print声明。因此在foo函数内仅仅void print(int)是可见的。在调用print时编译器首先检索这个名字的声明,找到一个只有int类型形参的print函数的局部声明,一旦找到,编译器不再继续检查这个名字是否在外层作用域中存在。然后再检查这个名字是否有效。屏蔽发生在不同作用域的声明之间,而重载仅仅发生在相同作用域的声明之间,这一点要弄清楚。如:此时便不存在屏蔽的问题。
void print(const string&);
void print(double);
void print(int);//函数声明
void foo(int ival)
{
print("Hello world!!");//正确
print(39);//正确。
print(3.33);//正确,
}
函数指针与其他指针一样也指向某个特定的类型。其类型是由返回类型以及形参表确定,而与函数名无关。
如:bool(*pf)(const string&,const string &);
此语句将pf声明为指向函数的指针。(*pf)括号是必须的。、
使用typedef可以简化函数指针的定义。如
typedef bool (*cmpFcn)(const string&,const string&);
该定义表示cmpFuc是一种指向函数的函数指针。该类型为指向返回值bool类型,带有两个const string 引用形参的函数指针。要使用时只需 cmpFunc pfFunc;即可。不必每次都把整个类型声明全部写出来。可以使用函数名或NULL对函数指针进行初始化。直接引用函数名与在函数名取地址符等价,如;
cmpFunc pf1=lengthCompare;
cmpFunc pf2=&lengthCompare;
它们是等价的。
函数指针只能通过同类型的函数指针或是NULL来进行初始化。不同类型的函数指针可以通过强制类型转换,转换为同一类型,然后再赋值。
函数指针可以作为函数的形参。它可以有两种形式:
1:void fun(const string &,const string&,
bool (const string&,int ));
2:void funI(const string &,const string &,
bool(*)(const string &,int));
可以返回指向函数的指针。
如:
int ( *ff ( int ) (int *,int);
它等价于
typedef int (*func) (int *,int);
func ff(int);
ff(int)声明一个函数,它带有一个int类型的形参,该函数返回值类型为:int(*)(int*,int);
具有函数类型的形参所对应的实参将被自动转换为指向相应函数类型的指针,但是当返回的是函数时,同样的转换操作则无法实现。
如:
typedef int func(int *,int);
void fff(func);//正确,函数类型被转换为函数类型的指针。
func fff1(int);//错误,返回的是函数类型,而不是指针类型。
func *fff2(int );//正确,返回的是函数指针。