第7章 函数

来源:互联网 发布:淘宝销量累计规则 编辑:程序博客网 时间:2024/05/22 09:44

7.1、函数的定义

       函数形参为函数提供了已命名的局部存储空间。

       实参则是一个表达式,它可以是变量或字面值常量,甚至是包含一个或几个操作符的表达式。实参必须具有与形参类型相同、或能隐式转换为形参类型的数据类型。

7.1.1、函数返回类型

       函数不能返回另一个函数或者内置数组类型,但可以返回指向函数的指针,或指向数组元素的指针的指针。

       函数必须指定返回类型(如test(doublec){},没写返回类型,在早期C++版本可以,会被假定为int型)。

       函数形参表可以为空,但不能省略。没有任何形参的函数可以用空形参表或含有单个关键字void的形参表示。即void porvess(void){}。

       将double传给int的形参是可以的。

7.2、参数传递

       如果形参具有非引用类型,则复制实参的值;如果形参为引用类型,则它只是实参的别名。

       指针形参,函数里对指针本身改变,外部不受影响,对指针指向的内容改变,外部指向的内容也改变了。指针的初始化规则在这边也适用,即可以将指向const对象的指针(形参)初始化为指向非const对象(即形参为const指针类型,可以用非const对象初始化),但不可以让指向非const对象(形参)的指针指向const对象(形参不是const指针类型,则不能用const对象初始化)。

       在调用函数时,如果该函数使用非引用的非const形参,则既可给该函数传递const实参也可以传递非const的实参(显然,因为形参不会对实参造成影响)。如果是非引用的const类型也一样~,这边有趣的是,函数形参是const,但是编译器却将该形参声明为普通的int型(该现象为了支持对C语言的兼容,C语言具有const形参或非const形参的函数并无区别)。

7.2.2、引用形参

       C++中,函数使用引用形参比传递指针来实现对实参的访问要更加安全和自然。

       使用引用形参,函数可以直接访问实参对象,而无需复制它(优点在于无需复制)。如果只是想避免复制形参(并没有想修改实参),则应将形参定义为const引用。

       对于const的引用形参不允许传递一个右值(如直接传递1、2或者是表达式v1+v2)或具有需要转换的类型的对象(将short实参传递给int实参)。当定义成const引用,不能修改参数,但没有了上述约束,既可以为右值也可以是需要转换的类型对象。

       传递指向指针的引用:如int *&v1,定义应该从右至左理解:v1是一个引用,与指向int型对象的指针相关联。

7.2.3、vector和其他容器类型的形参

       通常函数不应该有vector或其他标准库容器类型的形参。调用含有普通的非引用vector形参的函数将会复制vector的每一个元素。C++程序员倾向于通过传递指向容器中需要处理的元素的迭代器来传递容器。

7.2.4、数组形参

       数组形参的定义:(1)void f(int *a){}。(2)void f(int a[]){}。(3)void f(int a[10])。对于(3)编译器忽略为任何数组形参指定的长度,它只会检查实参是不是指针、指针的类型和数组元素的类型是否匹配,而不会检查数组的长度(因此,就算用b[2]或b[20]传给(3)也是可以的)。

       不需要修改数组元素时,函数应该将形参定义为指向const对象的指针:

void f(const int*){}

       通过引用传递数组:如果形参是数组的引用,编译器不会将数组实参转化为指针,而是传递数组的引用本身(这时编译器检查数组实参的大小与形参的大小是否匹配,一定得具有相同个数才行)。如:void f(int (&arr)[10]){},注意这边圆括号不能少(下标操作符具有更高的优先级)。

       多维数组的传递:如void f(int (*a)[10], int size){},注这边圆括号不能少,否则变为*a[10]为十个指针数组,而不是指向10个整型数组的指针了。也可以void f(int a[][10], int size){}。对于上述两个,列数是固定的,实参列数要与形参列数一致a[][10]不能传递给形参**a(等价于*arg[]···),因为a[][10]等价于(*a)[10],并不是指针的指针(用a[2][10],再将&a[0]传递还是不可以···将a[0]传递给一个指针,再将该指针取地址赋值给指针的指针是可以的。**a表示指向int *的指针)。

7.2.5、传递给函数的数组的处理

       三种常见的编程技巧确保函数的操作不超出数组实参的边界:(1)数组本身放置一个标记来检测数组的结束(C风格字符串就是采用这种方法)。(2)传递指向数组第一个和最后一个元素的下一个位置的指针(启发于标准库)。(3)将第二个形参定义为表示数组的大小(普遍是这种)。

7.2.6、main:处理命令行选项

       如:int main(int argc, char *arg[]){…}或int main(int argc, cjar **argv){…}

7.2.7、含有可变形参的函数

       省略符形参形式:void foo(parm_list,…);或者void foo(…);对于前者用法(前者中,可以省略):

void f(const char*str...)

{

    va_list ap;

    char* s = 0;

    int d = 0;

    double f = 0.0;

 

    va_start(ap, str);

    s = va_arg(ap, char*);//获得第二个参数

    d = va_arg(ap, int);//获得第三个参数

    f = va_arg(ap, double);//获得第四个参数

    va_end(ap);

   

    printf("%s, %s, %d, %f", str, s,d, f);

}

调用f("function", "varargs", 1, 1.0);输出function, varargs, 1, 1.000000。注:va_start和va_arg在#include<stdarg.h>头文件中。

       对于与显示声明的形参相对应的实参进行类型检查,而对于与省略符对应的实参则暂停类型检查

7.3、return语句

       形式:return;或者return expression;

       对于void函数,可以返回另一个void函数,即void f(){…},void g(){return f();}

       任何返回类型不是void的函数都必须返回一个值,而且这个返回值的类型必须和函数的返回类型相同,或者能隐式转化(C++double返回给int也可以,只是有警告;javadouble类型returnint编译就不通过)为函数的返回类型

       主函数main的返回值:允许主函数main没有返回值就可结束,如果没返回编译器会隐式地插入返回0的语句。(0成功,非0表示程序运行失败)cstdlib头文件定义了两个预处理变量,分别表示程序运行成功和失败。

       返回非引用类型:当函数返回非引用类型时,其返回值既可以是局部对象,也可以是求解表达式的结果。

       返回引用:当函数返回引用类型时,没有复制返回值,而是返回对象本身。(千万不能返回局部变量的引用)即不能如:string &g(string a){

       string b = a;

       return b;

}//会出错

       引用返回左值:如一个函数返回形参数组的一个值,可以对这个值赋值。如:

char &g(string &a,string::size_type ix){//注意这边是引用,不是引用显然改不了值

       returna[ix];

}

int main(){

       string s("avalue");

       cout<<s<<endl;//输出avalue

       g(s,0) = 'A';

       cout<<s<<endl; //输出A value

       return 0;

}

       千万不要返回指向局部对象的指针(常识)。

7.3.3、递归

       直接或间接的调用自己的函数成为递归函数。递归函数必须定义一个终止条件;否则函数就会永远递归下去直到程序栈耗尽主函数main不能调用自身

7.4、函数声明

       函数声明由函数返回类型、函数名和形参列表(必须包括形参类型,但不必对形参命名)组成。函数应当在头文件中声明,并在源文件中定义。

       如果有一个形参具有默认实参,那么它后面所有的形参都必须有默认实参。默认实参只能用来替换函数调用缺少的尾部实参。使最少使用默认实参的形参排在最前,最可能使用默认实参的形参排在最后。既可以在函数声明也可以在函数定义中制定默认形参,但只能为一个形参指定默认形参一次(最好在函数声明中指定默认实参,并放在合适头文件中)

7.5、局部对象

       自动对象(automatic object):只有当定义它的函数被调用时才存在的对象。

       static局部对象(local object):确保不迟于在程序执行流程第一次经过该对象的定义语句时进行初始化。该对象一旦被创建,在程序结束前(更别说所在函数结束了)都不会被撤销。

7.6、内联函数

       为小操作定义函数的好处:(1)阅读函数(因为有函数名)比小操作好理解;(2)修改方便,无需一个个等价表达式都修改;(3)使用函数可以确保统一的行为;(4)函数可以重用,无需编写相同代码。

       在返回值前面加inline变成内联函数,可以避免函数调用的开销。它适用于几行,调用频繁的函数。(不同于一般函数)其应该在同文件中定义。大多数的编译器都不支持递归函数的内联(但是是否内联还是看编译器,我们写上只是给个建议,一切还得由编译器定夺···)。

7.7、类的成员函数

       每个成员函数都有一个额外的、隐含的形参将该成员函数与调用该函数的类对象捆绑在一起。如调用total.sum();,这个total对象也传给了函数,如果函数内用到成员变量,就意味着是total的成员变量(常识)。

       每个成员函数都有一个额外的、隐含的形参this,static成员函数除外。

       const成员函数(如bool Sale::add() const{…},::用于在类的定义外面定义成员函数)该const改变了隐含的this形参类型,变为指向total对象的const Sale*类型的指针。该函数被称为常量成员函数。由于this指向的是常量对象,因此该函数不能修改调用该函数的对象(即数据成员)了。

       const对象、指向const对象的指针或引用只能用于调用其const成员函数,如果尝试用它们来调用非const成员函数,则是错误的

       函数声明必须与其定义一致,声明为const,定义时后面也必须要有const。

       构造函数shiite特殊的成员函数,它没有返回值。每个构造函数必须有与其他构造函数不同数目或类型(可以是参数顺序不同)的形参。可以这么定义,如:Sale(): a(1),b(1.1){},a、b是成员变量。在冒号和花括号之间的代码称为构造函数的初始化列表。是一系列成员名及初始值。

       由编译器创建的默认构造函数通常称为合成的默认构造函数,它一般适用于仅包含类类型成员的类(显然,跟初始化规则差不多···)。

7.8、重载函数

       任何程序都仅有一个main函数的实例,main函数不能重载(常识)。

int init(){

       return 1;

}

int fcn(){

       int init= 0;//这边定义的init会把外面的init隐藏(有意思~),因此下面的语句是错误的。如果先执行init();在定义int init则是可以的。

       int a =init();

}

       在某个函数声明函数A,A是外面的重载函数(它有B、C等),那么内部都无法识别外部其他的重载函数,只认内部这个函数A。如:

int init(){

       return 1;

}

int init(int i){

       return 2;

}

int fcn(){

       int init();

       int a =init();

       int b =init(1);//这个是错的,fcn函数内部只认init()。

}

       在调用init时,编译器先检查这个名字的声明,知道只有一个init函数的局部声明。一旦找到(找不到还是会去外面找),编译器就不再继续检查这个名字是否在外层作用域中存在。余下的工作只是检查该名字的使用是否有效。

7.8.3、重载确定的三个步骤

       (1)确定该调用所考虑的重载函数集合;(2)选择可行函数(函数的形参个数与该调用的实参个数相同;每一个实参的类型必须与对应形参的类型匹配或者可被隐式转换为对应的形参类型,这边调用带默认实参的函数可以忽略这些默认实参);(3)如果有的话,寻找最佳匹配。而对于f(int ,int )与f(double, double),调用函数f(1,1.0)则会因为二义性出错。匹配成功的条件:1)其每个实参的匹配都不劣于其他可行函数需要的匹配;2)至少有一个实参的匹配优于其他可行函数提供的匹配。

       解决上述二义性可通过显示的强制类型转换,强制函数匹配。

7.8.4、实参类型转换

       int型版本都是优于short型版本的较佳匹配。如传入字符,则与int匹配而不与short匹配。对于3.14,即可转为long也可转为float···

       仅当形参是引用或指针时,形参是否为const才有影响。将引用形参定义为const来重载函数是合法的(如f(constfloat &a)和f(float &a)。但不能基于指针本身是否为const来实现函数重载(如f(int *)和f(int *const),值传递方式,函数操纵的只是副本,无法修改实参,有无const没有区别)。

7.9、指向函数的指针

       函数指针是指向函数而非指向对象的指针。用typedef简化函数指针的定义,如typedef bool (*cmpFcn)(const string &, const string&);cmpFcn是一种指向函数的指针类型的名字。用typedef可以不必每次都把整个类型声明全部写出来(不写则是:int (*pd)(double i,double j) =&f;这样的方式然后直接用pd(1.0,1.0)这种形式调用即可)。(注意*cmpFcn两侧圆括号千万不能少

       可使用函数名对函数指针做初始化或赋值(在引用函数名但又没有调用该函数时,函数名将被自动解释为指向函数的指针)。因此对于bool f(const string &, const string &){}可以直接将f赋值给cmpFcn,它也等效于cmpFcn = &f。(函数指针只能通过同类型函数或函数指针或0值常量表达式(可以用“1-1”这种表达式赋值给函数指针)进行初始化)。

    指向不同函数类型的指针之间不存在转换(参数类型不同或返回类型不同都不可以)。

       cmpFcn可以cmpFcn(“hi”,”bye”);隐式解引用也可以(*cmpFcn)(“hi”,”bye”);显式解引用。

       函数指针形参:可以这么写(1)void g(bool f(const string &, const string &));(2)int g(bool (*f)(const string &, const string &))

       返回指向函数的指针:如int (*ff(int))(int *,int),ff声明为一个函数,带有int形参,该函数返回int (*)(int*,int),它是一个指向函数的指针,所指向的函数返回int型并带有两个分别是int*和int型的形参。

       用typedef可使定义简明:typedef int (*PF)(int*,int);PF ff(int);

       可以将形参定义为函数类型,但函数的返回类型则必须是指向函数的指针,而不能是函数。typedefint pf(double i,double j);//如果只是函数名pf,那么后面必须这么写。

pf *ff(int);//如果是(*pf)(…),那么这边用pf  ff(int)则是可以的,下一行也只要pf  p1=ff(1)                //就可以了。

pf *p1 = ff(1);

       指向重载函数的指针:指针的类型必须与重载函数的一个版本精确匹配(不允许类型转换)。如:

int f(double i,double j){

       return 1;

}

int (*ppf)(doublei,int j)=f;//这边则是错的
原创粉丝点击