c++ primer 学习(四) 函数

来源:互联网 发布:java反射机制的优缺点 编辑:程序博客网 时间:2024/05/21 22:55

之所以跳过了原书第五章的“语句”,是因为这个东西实在没啥意思,只要学过一点c,或者任何编程语言的应该都会,要说知识只有一点,就是后边的异常处理,不过这个在后边章节也要详细讲,只要记住一个try{条件{throw(error)}}catch(error){}就可以了。

但是第五章的题都是不错的,建议大家一个个做一下,其实感觉c++primer的一个优点就是题目不错,也不给你整些八皇后啥的,都是和语言、语法细节相关的题,很有意义,毕竟你给我出些带点算法的不如我直接去刷算法导论呢。

开始第6章吧。

形参和实参使用的注意:类型匹配,数量匹配。实参的类型要可以转化为形参的才可以,比如intf(int),好歹得给个double的数,可以转化为int。

函数最外层作用域的局部变量不能和函数形参名一样,因为形参会隐藏外部变量。

函数的返回类型不能是数组,或函数类型,但可以是指向数组和函数的指针。

函数块执行期间的对象成为自动对象,形参是自动对象,开始时申请空间,结束时销毁。就是个名词,没什么意义。

局部静态对象:让局部变量生命周期一直持续,知道程序结束。局部静态变量初始化为0。

函数声明:不需要形参名字,只要类型,因为不包含函数块,名字没卵用。

至于分离式编译,简单说就是文件分开写,函数声明写在头文件里,函数写在别的文件里,main再写一个,这样比较清晰,而且测试的时候比较方便(我的经验)。

参数传递

值传递:实参将值传递(拷贝)给形参,但不会改变实参,有时为了改变得调用指针形参。这样,拷贝的值就是实参的地址,改变时就会改变实参的值。

一个有趣的例子:void swap(int *a,int *b)

{

int t=*a;

*a=*b;

*b=t;

}

想说的不是这个例子,而是给他传递的时侯。如果前边声明为int p=0,q=1;那么调用时就要写为swap(&p,&q);因为我们需要p和q的地址,作为值传递出去。可是如果声明为:int i=0,j=1,*p=&i,*q=&j;时,只需写为swap(p,q);因为p和q的值就已经是地址了。

引用传递:此时形参就是实参的引用,别名,改变形参当然就是改变实参了。引用可以返回额外信息,这时外部也可以使用此引用。如书中例子:

string::size_type find_char(const string&s,char c,string::size_type &occurs)

{

...

if(...)

{...++occurs;}

return x;}此时occurs被隐式返回了。

const成为形参或实参时:注意顶层const。再次复习一下什么事顶层const?一个重要特征就是拷贝时会忽略掉顶层const,如:const int c=4;int i=c;所以当使用实参初始化形参时也会忽略掉形参的顶层const,即:形参有顶层const,可以传递常量/非常量。在函数重载中,可以同名,但是形参要不同,所以拥有顶层const形参和没有的函数都一样,不可以重载。至于底层const,可以用非常量初始化,但反之不可以。

而指针与引用的const,规则类似初始化对象。简单地说, 就是:const指针可以指向非常量(但不可用于更改,并且反之不行);const引用可以引用非常量(不可更改,反之不行);const引用可使用字面值(普通引用不行)。所以在函数中可以把实参看做右边值,所以有以下规定(假设函数的形参是变量指针或变量引用):

实参不能是const的地址;实参不能是const的引用。

数组形参

复习一下数组的性质:1.不允许拷贝。2.数组名会转换为指针。所以数组作为形参时,有以下调用形式:

void print(const int*)==void print (const int[])==void print (const int[10]),他们都是对数组名--指针的调用,所以即使规定大小后编译器无法检验大小。注意不超过数组的大小很重要,可以使用标准库规范限制(begin(),end()),也可以使用数组的引用直接限制大小,如:void print(int (&array)[10]),这样就必须传入大小为10的数组。注意括号(&array),表示是数组的引用,不是引用的数组。

多维数组当做形参,除第一维之外都要有大小,因为他们是数组类型的一部分,形式如下:void print(int (*array)[10])//二维

若处理可变形参,当实参类型一致时,可使用initializer_list类型。可使用操作如下:

initializer_list<t> lst//默认初始化

initializer_list<t> lst{a,b,c...}//固定初始化,元素类型是const

lst2=lst(或者lst2(lst))//lst和lst2共享元素,不会拷贝

lst.size() lst.begin()lst.end()//同容器成员函数

省略符形参

void foo(...)就是这个...用于特殊的c代码,他们使用了varargs库功能,而且省略号只能放在形参最后一个位置。

返回

一般在有return的循环语句后要有一个return,为防止循环结束后无法返回。

返回的值是对象的临时拷贝或副本。引用类型返回的是引用。不能返回局部对象的引用或指针,因为结束函数后引用/指针无法指向原来有效的内存空间。

返回的对象若是指针,引用或类类型,可以使用其数据类型具有的成员函数,如:string shortstring();auto sz=shortstring().size();

调用一个返回引用的函数,返回的是左值,否则返回的是右值,左值可以被赋值。如:char &get_val(string s,string::size_type x){return s[x;]}可以使用:get_val(s,0)='A';这样就将s[0]改为了A。当然你要保证可以更改!

返回值可以用花括号包含{},内定类型只能一个,自定类类型则可自定包含几个。

主函数的返回值,当返回0时代表正确,其他值不一定。cstdlib中有2个量,表示返回成功和失败:EXIT_FAILURE, EXIT_SUCCESS。

递归:不好讲,道理都懂,不过设计一个好的递归函数还是很要经验的。

返回数组指针:数组不能被拷贝,所以不能返回数组,但可以是他的指针或引用,声明如下:

1.int (*func(int i))[10];这样就返回了有10个int型元素的数组,且形参是int型。也可使用类型别名:typedef int arr[10];(或者:using arr=int [10];)//声明如下:arr* func(int i);

2.复习一下typedef:将某个类型给他别名,*,&,[]等运算符算原来类型。如:typedef double *p;这时p是类型:double* 的别名。

3.也可使用尾置返回:就是将返回类型放在最后,对上述例子举例如下:auto func(int i)->int(*)[10];

4.还可使用decltype:用于返回指向一个已有数组的指针。如下:int x={1,2,3};decltype(x) *arr(int i){...}注意:对x取decltype得到的是大小为3的数组,要返回指针必须加个“*”号。

重载

其实重载挺简单的,不知道为什么书会讲这么多。

1.形参是顶层const不会影响拷贝,会导致重载失败。如果形参是个引用或指针,则const是底层的,可重载。

2.使用const_cast,在重载中便于转换const为非const,书上的例子非常经典,原函数:const string &fun(const string &s1,const string &s2),现在实现:实参是非const,返回也是非const:string &fun(string &s1,string &s2){auto &r=fun(const_cast<const string&>(s1),const_cast<const string&>(s2));return const_cast<const string&>(r);}

3.调用重载函数:

函数匹配:先形成候选函数。条件:1.同名。2.声明在调用点可用。在函数内声明同名函数会隐藏外部的同名函数。再选出可行函数:1.形参实参数量相同。2.形参实参类型相同,或实参可用转换为形参的类型。然后精确匹配:尽量不转换类型。多个参数匹配的优先级为:每个实参的匹配不劣于其他函数;至少有一个优于其他。若不满足,则导致调用二义性,重载失败。

实参的类型转换:最佳匹配的优先级:1.实、形类型一致;实参从数组、函数类型转换为指针类型;实参的顶层const增、减。2.const转换。3.类型提升。4.算数类型转换或指针转换。5.类类型转换。

tip,所有算数转换的优先级一致。int到long,和int到double是一样的优先级。

函数指针

这一节我认为是比较难的,因为我觉得他举的例子太少了,都是干巴巴的概念和辨析,所以我推荐认真做这一小节的最后一道题,非常有意义。

函数指针指向的是函数。只需将一般函数的名字变为:name1→(*name2)即可。给函数指针赋值时形式如下:name2=name1;name2=&name1;取地址符可选,因为把函数名作为值时,名自动转换为指针。而调用时也不需要解引用,如:int i1=name2(/**/);int i2=*name2(/**/)原理同上。

指向不同函数类型的指针不存在转换。但可赋值nullptr。而重载时必须:形参匹配;返回值匹配。

形参不能是函数类型,但可以是指向函数的指针。同理我们可以使用别名简化形参列表。如:typedef bool func(const string&),等价于typedef decltype(fun) func。假设:bool fun(const string&)。

返回不能是函数,但可以是函数的指针。类似上边:using f=int(*)(int*,int*),此时一个返回函数指针的函数:f f1(int)即可,因为f本身是一个函数指针。若f如下:using f=int (int*,int*),则需要:f *f1(int),才是返回函数指针的函数。也可直接声明:int (*f1(int))(int*,int*)或是尾置:auto f1(int)->int(*)(int*,int*)。同样的也可使用auto和decltype,但需要知道返回的是某一个确定函数。

至于incline和constexpr函数,比较简单,就不说了。


0 0
原创粉丝点击