C++ Primer 第6章 函数 学习笔记

来源:互联网 发布:js如何给span赋值 编辑:程序博客网 时间:2024/06/05 21:00

1.      编写+调用函数

a)      一个典型的函数定义包括以下部分:返回类型、函数名字、由0个或多个形参组成的列表以及函数体。

b)      我们通过调用运算符执行函数,它作用于一个表达式,该表达式是函数或者指向函数的指针。调用表达式的类型就是函数的返回类型

c)      函数的调用完成两项工作:一是用实参初始化函数对应的形参,二是将控制权转移给被调用函数

d)      return语句也完成两项工作:一是返回return语句中的值,二是将控制权从被调函数转移回主调函数

int fact(int val){int ret=1;while(val>1)ret*=val--;return ret;}



2.      形参和实参

a)      实参是形参的初始值。第一个实参初始化第一个形参,第二个实参初始化第二个形参,以此类推

b)      实参的类型必须与对应的形参类型匹配;且函数有几个形参,我们必须提供相同数量的实参

c)      void返回类型表示函数不返回任何值

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



3.      局部对象

a)      形参和函数体内部定义的变量统称为局部变量,仅在函数的作用域内可见

b)      对于普通局部变量对应的对象来说,当函数的控制路径经过变量定义语句时创建该对象,当到达定义所在的块的末尾时销毁它

c)      自动对象是指只存在于块执行期间的对象,形参是一种自动对象:函数开始时为形参申请存储空间,一旦函数终止,形参被销毁

d)      局部静态对象在程序的执行路径第一次经过对象定义语句时初始化,直到程序终止才被销毁,在此期间即使对象所在的函数结束执行也不会对它有影响。可以将局部变量定义成static类型获得这样的对象

#include <iostream>using namespace std;size_t count_calls(){static size_t ctr=0;return ++ctr;}int main(){for(size_t i=0;i!=10;++i)cout<<count_calls()<<endl;return 0;}输出结果是1~10



4.      函数声明

a)      类似于变量,函数名字也必须在使用之前声明。

b)      函数只能定义一次,但可以声明多次

c)      函数的返回类型、函数名、形参类型描述了函数的借口



5.      参数传递

a)      如果形参是引用类型,它将绑定到对应的实参上;否则,将实参的值拷贝后赋给形参

b)      当形参是引用类型时,引用形参是它绑定的对象的别名;引用形参是它对应的实参的别名(传引用参数)

c)      当实参的值被拷贝给形参时,形参和实参是两个相互独立的对象(传值参数)

#include <iostream>using namespace std;void reset(int *p){*p=0;p=0;}int main(){int i=42;reset(&i);cout<<i<<endl;return 0;}输出0


6.      传值参数

a)      当初始化一个非引用类型的变量时,初始值被拷贝给变量。传值函数对形参做的所有操作都不会影响实参

b)      指针的行为和其他非引用类型:当执行指针拷贝操作时,拷贝的是指针的值。拷贝之后,两个指针是不同的指针

#include <iostream>using namespace std;void swap(int *a,int *b){int c=*a;*a=*b;*b=c;}int main(){int i=42,n=56;swap(&i,&n);cout<<i<<" "<<n;return 0;}输出56 42



7.      传引用参数

a)      如果函数无须改变引用形参的值,最好将其声明为常量引用

b)      拷贝大的类类型对象或者容器对象比较低效,最好通过引用形参访问该类型的对象

c)      一个函数只能返回一个值,而引用形参为一次返回多个结果提供了有效的途径

下面的栗子是一个名为find_char的函数,它返回在string对象中某个制定自负第一次出现的位置及字符出现的总次数

#include <iostream>using namespace std;string::size_type find_char(const string &s,char c,string::size_type 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;//记录c第一次出现的值 ++occurs;//将出现的次数加1 }}return ret;//出现次数通过occurs隐式地返回 }//给函数传入一个额外的引用实参保存字符出现的次数 




8.      数组形参

a)      不允许拷贝数组(3.5.1节,102页),且使用数组时(通常)会将其转换成指针(3.5.3,105页)

b)      因为数组会被转换成指针,所以当我们为函数传递一个数组时,实际上传递的是指向数组首元素的指针

void print(const int*);void print(const int[]);void print(const int[10]);//这三个函数是等价的,每个函数的唯一形参都是const int*类型 

c)      管理数组实参有三种常用的方法:

1)      使用标记指定数组长度:要求数组本身包含一个结束标记,使用这种方法的典型示例是C风格字符串。C风格字符串存储在字符数组中,并且在最后一个字符后面跟着一个空字符。函数在处理C风格字符串时遇到空字符停止

2)      使用标准库规范:传递指向数组首元素尾后元素指针

void print(const int *beg, const int *end){//输出beg到end之间的所有元素while(beg!=end)cout<<*beg++<<endl;//输出当前元素并将指针向前移动一个位置 }

3)      显式传递一个表示数组大小的形参:专门定义一个表示数组大小的形参

void print(const int ia[], size_t size){//const int ia[]等价于const int *ia //size表示数组的大小,将它显式地传给函数用于控制对ia元素的访问 for(size_t i=0;i!=size;++i)cout<<ia[i]<<endl;}

d)      数组引用形参:形参可以是数组的引用,引用形参绑定到对应的实参上,也就是绑定到数组上

void print(int (&arr)[10]){for(auto elem:arr)cout<<elem<<endl;}
&arr两端的括号必不可少
f(int&arr[10])//错误:将arr声明成了引用的数组f(int (&arr)[10])//正确:arr是具有10个整数的整形数组的引用

e)      数组的大小对函数的调用没有影响




9.      return语句(终止当前正在执行的函数并将控制权返回到调用该函数的地方)

return;

return expression;

a)      无返回值函数

1)      只能用在返回类型是void的函数中

2)      void函数如果想在它的中间位置提前退出,可以使用return语句。这种用法类似于用break语句退出循环

void swap(int &v1, int &v2){if(v1==v2)return;//如果两个值是相等的,不需要交换,直接退出int tmp=v2;v2=v1;v1=tmp;//不需要显式的return语句}

3)      一个返回类型是void的函数也能使用return语句的第二种形式,但语句的expression必须是另一个返回void的函数

b)      有返回值函数

1)      只要函数的返回类型不是void,则该函数内的每条return语句必须返回一个值

2)      return语句返回值的类型必须与函数的返回类型相同,或者能隐式地转换成函数的返回类型

c)      不要返回局部对象的引用或指针,因为函数终止意味着局部变量的引用将指向不再有效的内存区域




10.      函数重载

a)      如果同一作用域内的几个函数名字相同但形参列表不同,我们称之为重载函数

b)      这些函数接受的形参类型不一样,但执行的操作非常类似。调用这些函数时,编译器会根据传递的实参类型推断想要的是哪个函数

c)      对于重载的函数来说,它们应该在形参数量或形参类型上有所不同

d)      不允许两个函数除了返回类型外其他所有的要素都相同



11.      调用重载的函数

a)      在函数匹配过程当中,我们把函数调用与一组重载函数中的某一个关联起来。编译器首先将调用的实参与重载集合中每一个函数的形参进行比较,然后根据比较的结果决定到底调用哪个函数

b)      当调用重载函数时有三种可能的结果:

1)      编译器找到一个与实参最佳匹配的函数,并生成调用该函数的代码

2)      找不到任何一个函数与调用的实参匹配,编译器发出无匹配的错误信息

3)      有多于一个函数可以匹配,但是每一个都不是明显的最佳选择,发生错误,成为二义性调用




12.      assert预处理宏

a)      可以有选择地执行调试代码

b)      预处理宏其实是一个预处理变量

c)      assert宏使用一个表达式作为它的条件:assert(expr); 首先对expr求值。如果expr为假(0),assert输出信息并终止程序的执行。如果为真,assert什么也不做

d)      assert宏定义在cassert头文件中

e)      含有cassert头文件的程序不能再定义名为assert的变量、函数或者其他实体