介绍
- 首先介绍函数的定义和声明,包括如何传入参数以及函数如何返回结果。
- C++语言允许使用重载函数,即几个不同的函数可以使用向同一个名字。所以接下来介绍重载函数的方法,以及编译器选择如何从函数的若干重载的形式中选取一个与调用模板相互匹配的版本进行使用。
- 最后介绍一些关于函数指针的知识。
6.1 函数的基础
- 一个函数的定义包括以下几个部分:返回类型、函数的名字、0或者多个形参组成的列表或者函数体。
- 形参以逗号分隔,形参列表位于一对圆括号里面。
- 函数具体实现的代码写在一对花括号里面,整体称之为函数体。
编写函数
#include<iostream>
using namespace std;int fact(int val){int rel = 1;for(int i = val;i != 0;i--){rel *= i;}return rel;
}int main(){cout << "The result of rel is "<< fact(5) << endl;return 0;
}
调用函数
- 调用函数主要完成两项工作:1,实参初始化函数对应的形参;2,将控制权交给调用函数,这个时候,被调的函数开始执行。
形参和实参
- 实参数量和形参数量、类型一致
- 如果定义没有参数的函数,使用void关键字void f1();// 隐式定义空的形参列表 void f2(void); //显式定义空的形参列表
- 任意两个形参不可以同名,而且函数最外层作用域中的变量也不可以和形参同名。
函数的返回类型
- 一种特殊的返回类型是void
- 函数的返回类型不可以是数组或函数类型,但可以是指向函数或者数组的指针。
6.1.1 局部对象
- 形参和函数体内部定义的变量统一称为局部变量,对于函数而言是局部的、是隐藏的。
- 函数执行的时候创建,函数执行结束的时候会销毁的变量叫做自动对象。
- 对于局部变量对应的自动对象来说,如果变量本身含有初始化的数值,就采用初始化的数值,否则就采用默认的初始化的值。
局部静态对象
- 将局部变量定义成static的类型,这种使用static类型修饰的变量,只会在程序第一次进入的时候进行初始化,直到程序的终止才会销毁,在此期间,即使对象所在的函数结束也不会对它有任何的影响。
6.1.2 函数声明
- 函数声明不需要函数体,也就是无需形参的名字。
- 函数的三个要素(返回类型、函数名字、形参类型)描述了函数的接口,说明了调用这个函数的
- 一般将函数的声明放在头文件中,就可以确保同一函数的声明保持一致,一旦想要改变函数的接口,只需要改变一条声明语句即可。
- 定义函数的源文件应该包含所有函数声明的头文件,这样编译器就会负责验证函数的定义和声明是否匹配
6.1.3 分离式编译
- 将程序拆分成不同的部分分别存储,分离式编译器允许将程序分割到几个文件中,对于每个文件进行独立编译。
编译和链接多个源文件
- 头文件 存放函数的声明
- 源码文件 存放函数的具体实现的代码
- 主函数 调用函数的具体执行,需要引入头文件
例子
int fact(int val){int rel = 1;for(int i = val;i != 0;i--){rel *= i;}return rel;
}
int fact(int val);
#include<iostream>
#include "factHead.h"
using namespace std;int main(){cout << "The result is " << fact(5) << endl;return 0;
}
6.2 参数传递
- 每次调用函数的时候,都会重新创建它的形参,并用传入的实参进行初始化
- 和其他变量一样,形参的类型决定了形参和实参的交互方式。如果形参是引用类型,它将绑定到对应的实参上;否则,将实参的数值拷贝后赋值给形参。
- 当形参是引用类型时,它对应的实参被引用传递或者函数被传引用调用。和其他引用一样,引用形参也是它绑定的对象的别名,即引用形参是他对应的实参的别名。
- 当实参的数值被拷贝给形参的时候,形参和实参是两个相互独立的对象。这样的实参被值传递或者函数被传值调用。
6.2.1 传值参数
- 当初始化一个非引用类型的变量时,初始值被拷贝给变量,此时对于变量的改动不会影响初始值。
int n = 0;//int类型的初始变量
int i = n;//i是n的副本
i = 42;// 对于i的改变不会影响到n的数值
指针形参
- 指针的行为和其他非引用的类型一致。执行指针拷贝操作的时候,拷贝的是指针的数值。拷贝之后,两个指针是不同的指针。因为指针使我们可以间接地访问它所指的对象,所以通过指针可以修改它所指的对象的数值。
int main(){int n=0,i=42;int *p = &n,*q = &i;//p指向n,q指向i*p = 44;//n的数值改变,p不变p=q;//p现在指向i了,但是i和n的数值都不变cout << "The n result is " << n << endl; //44cout << "The i result is " << i << endl; //42cout << "The p result is " << *p << endl;//42cout << "The q result is " << *q << endl;//42return 0;
}
//该函数接受一个指针,然后将只针所指向的位置设为0
void reset(int *ip){*ip = 0;// 改变了指针ip所指向的数值ip = 0;//只改变了ip的局部拷贝,实参未被改变
}
int main(){int i = 42;reset(&i);cout << "i = " << i << endl;return 0;
}
6.2.2 传引用参数
int main(){int n = 0,i=42;int &r = n; //r绑定了n,r是n的另外一个名字r = 42; //改变了n的数值,n也是42cout << "n = " << n << endl;//42cout << "r = " << r << endl;//42return 0;
}
//该函数接受一个int对象的引用,然后将对象的数值设为0
void reset(int &i){//i是传给函数对象的另外一个名字i = 0;//改变了i所引对象的数值
}int main(){int j = 42;reset(j);cout << "j = " << j << endl;//42return 0;
}
- 和其他引用的类型一致,引用形参绑定初始化他的对象。当调用reset函数的时,i就会绑定我们传给函数的int对象,改变i的数值也就是改变i引用的数值。
使用引用避免拷贝
- 拷贝大的类类型对象或者容器对象比较低效,甚至有的类类型就根本不支持拷贝操作。当某种类型不支持拷贝操作时,函数只能通过引用形参来访问该类型的对象。
- 比如一个比较两个字符串大小的函数,考虑到字符串都比较长,就要避免直接拷贝他们,这个时候使用引用形参就是比较明智的选择,因为比较长度无法改变string对象的内容,所以把形参定义成对于常量的引用。
bool isShorter(const string &s1,const string &s2){return s1.size() < s2.size();
}
使用形参返回额外的信息
- 使用形参可以一次返回多个结果提供了有效的途径。
- 例子:函数返回在string中某个字符第一次出现的位置以及该字符总共出现的次数
- 如何使函数既可以返回位置也返回出现的次数呢?一种方法是定义一个新的数据类型,让他包含位置和次数两个成员;另外一种方法是给函数传入一个额外的引用实参,另其保存字符出现的次数
//返回s中c第一次出现的位置索引
//引用形参occurs负责统计c出现的总的次数
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;}++ occurs;}}return ret;
}bool isShorter(const string &s1,const string &s2){return s1.size() < s2.size();
}
int main(){string s1 = "HelloWorld";string s2 = "Hello";string::size_type ctr;auto index = find_char(s1,'o',ctr);cout << "index = " << index << " ctr = " << ctr << endl;bool s3 = isShorter(s2,s1);cout << "" << s3 << endl;return 0;
}
- 其中,给ret赋值为最大长度的目的是为了在后面判断的时候,查看是否改变,从而确定是不是第一次遇到这个数值,可以将大于数组长度的任意值作为判定的条件。
6.2.3 const形参和实参
- 当形参是const的时候,顶层的const作用于对象的本身。
const int ci = 42; // 不能改变ci,const是顶层的int i = ci; // 正确,当拷贝ci的时候,会忽略他的顶层的数值int * const p = &i;//const是顶层的,不可以给p赋值*p = 0;//正确,通过p改变对象的的内容是允许的,现在i的数值变为了0
- 和其他初始值一样,当使用实参初始化形参的时候会忽略掉顶层的const。即,形参的顶层const被忽略掉了。
- 当形参有顶层的const的时候,传给他的常量对象或者非常量对象都是可以的。
- void fun(const int i)//fun可以能够读取i,但是不可以向i中写值
- void fun(const int i)//错误,重复定义了fun(int) C++允许定义若干具有相同的名字的函数,前提是不同函数的形参列表应该有明确的区别。此处是因为const被忽略掉了,因IC两个函数没有任何的区别,不可以重复的定义。
指针或者引用形参与const
- 形参的初始化和变量的初始化的方式是一样的。我们可以使用非常量初始化一个底层的const对象,但是反过来不可以,同时一个普通的引用必须使用相同类型的对象初始化。
int i = 42;const int *cp = &i; //正确,cp不能改变i,const是顶层的const int &r = i; //正确,r不能改变i,const是顶层的const int *r2 = &i; //正确,r2不能改变i,const是顶层的int *p = cp;//错误,类型不符int &r3 = r;//错误,类型不符int &r4 = 42;//错误,不能用字面值初始化一个非常量的引用
尽量使用常量引用
- 把函数不会改变的形参定义成(普通的)引用是一种常见的错误,这么做会给调用者一种误导,即函数不会改变它的实参的数值。使用引用而非常量引用也会极大地限制函数所能接受的实参类型。
- 不能把const对象、字面值或者需要类型转换的对象传递给普通的引用形参。这种错误很难排解
- 这个是上面提到的代码,对其进行修改string::size_type find_char(const string &s,char c,string::size_type &occurs),改为string::size_type find_char(const string &s,char c,string::size_type &occurs),将string类型的形参定义成常量引用。假如将其定义成普通的string&,没有const进行修饰。那么在使用的时候只可以auto index = find_char(“Hello ”,'o',ctr);,编译会发生错误。
- 假如其他函数将他们的形参定义成常量的引用,那么第二个版本的函数无法在此类函数上正常使用。假设在一个判断string对象是否是句子的函数中使用find_char;
bool is_sentsence(const string &s){//如果s的末尾有一个句号且仅有一个,则是句子string::size_type ctr = 0;return find_char(s,'.',ctr) == s.size()-1 && ctr == 1;
}
- 如果find_char()第一个形参类型是string & ,那么会发生编译错误,是因为s是常量的引用,但是函数find_char被定义成可以只能接受普通引用。如果修改is_sentsence函数的形参类型只会转移错误,使得is_sentsence函数只可以接受非常量的string对象。
- 正确的思路是改变find_char的函数的形参。实在不行才修改is_sentsence函数,在其内部定义一个string类型的变量,另其为s的副本,然后把这个string对象传递给find_char()。
6.2.4 数组形参
- 数组的两个特殊的性质使得我们定义和使用作用在数组上的函数有影响,这两个性质是:1,不允许拷贝数组;2,使用数组的时候通常要将其转换为指针,所以当为函数传递一个数组的时候,实际上传递的是指向数组首元素的指针。
- 虽然不能以数值的方式传递数组,但是可以形参写成类似数组的形式。
//尽管形式不同,但是这三个print的函数是等价的//每个函数都会有一个const int*类型的形参void print(const int *);void print(const int[]); //函数的意图是作用于一个数组void print(const int[10]); //这里的维度表示我们期望数组会含有多少个元素,实际上不一定//尽管表现形式不同,但是上面这三个函数是等价的,每个函数的唯一形参都是const int *类型的//当编译器处理对print函数的调用的时候,只会检查传入的参数是否是const int *类型的int i = 0,j[2] = {0,1};print(&i); //正确,&i的类型是int *print(j); //正确,j转化成int *并且指向j[0]return 0;
- 如果传递给print函数是一个数组,则实参自动转化成指向数组首元素的指针,数组的大小对于函数的调用没有关系,但是在使用数组的时候需要注意到数组越界的问题。
使用标记指定数组的长度
- 要求数组本身拥有一个结束的标记,典型问题是C风格的字符串中,在字符后面跟着一个空的字符。
void print(const char *cp){if(cp) //如果cp不是一个空的指针while(*cp) //只要指针指向的字符不是空的字符串cout << *cp++ ; //输出当前字符的数值,并将指针向前移动一个位置
}
int main(){char s[] = "Hello World!";print(s);return 0;
}
- 这种方法适用于那些有着明显的结束标记,但是该标记不会与普通的数据相互混淆的情形,但是不适用于所有的取值都是合法的情形。
使用标准库规范
- 管理数组实参的第二种方法是传递指向数组首元素和尾后元素的指针
void print(const char *beg,const char *end){//输出beg到end之间(不包含end)的所有元素while (beg != end){cout << *beg++;}
}int main(){char s[] = "Hello";print(begin(s),end(s));return 0;
}
- while循环使用解引用运算符和后置递减运算符输出当前元素并且在数组内将beg向前移动一个元素,当beg和end指针相等的时候结束循环
- 为了调用这个函数,需要传入两个指针,一个指向要输出的首元素,一个指向尾元素的下一个位置。具体的调用方法如上图所示。此处使用bigin和end函数提供所需的地址。
显式传递一个表示数组大小的形参
- 第三种管理数组实参的方法是专门定义一个表示数组大小的形参,在C程序和过去的C++程序中常常使用这种方法。
//const int ia[] 等效于const int* ia
//size表示数组的大小,将它显式地传递给函数用于控制对ia元素的访问
void print(const int ia[],size_t size){for(size_t i = 0;i != size;i++){cout << ia[i] << endl;}
}
int main(){int j[] = {0,1,2,3,4,5,6,7,8,9};print(j,end(j) - begin(j));return 0;
}
数组形参和const
- 前三个函数都将数组的形参定义成指向const的指针,对于引用类型也同样适用于指针。当函数不需要对于数组元素执行写操作的时候,数组的形参应该是指向const的指针。只有当函数确实要改变元素数值的时候,才会把形参定义成指向常量的指针。
数组引用形参
- C++允许将变量定义成数组的引用,基于同样的道理,形参也是数组的引用。这个时候,引用形参绑定到对应的实参上,也就是绑定到数组上。
//正确,形参是数组的引用,维度是类型的一部分
void print(int (&arr)[10]){for(auto elem : arr){cout << elem << endl;}
}
- 因为数组的大小是构成数组类型的一部分,所以只要不超过维度,在函数体内就可以放心的使用数组。
- 但是,如果采用上面的代码格式就会限制了print函数的可用性,即只能将函数作用于大小是10的数值。
注意事项
- &arr的两端的括号必不可少
- f(int &arr[10]) //错误,将arr声明成了引用类型
- f(int (&arr)[10]) //正确,arr是具有10个整数的整型数组的引用
传递多维数组
- C++实际上并没有多维数组,所谓的所谓数组其实是数组的数组。
- 和所有数组一样,当将多维数组传递给函数的时候,真正传递的是指向数组首个元素的指针。因为多维数组就是数组的数组,因此首元素就是一个数组,指针是一个指向数组的指针。数组的第二维度以及后面所有的维度的大小都是数组类型的一部分,不可以忽略。
//matrix指向数组的首个元素,该数组的元素是由10个整数构成的数组void print(int (*matrix)[10],int rowSize){}
- 上面的语句是将matrix声明成指向10个整数的数组的指针。
//再一次强调,*matrix两端的括号必不可少int *matrix[10]; //10个指针构成的数组int (*matrix)[10]; //指向含有10个整数的数组的指针
- 也可以使用数组的语法定义函数,此时编译器会一如既往地忽略掉第一个维度,所以最好不要把它包括在形参的列表内,所以最好不要把它包括在形参列表内。
- //等价定义 void print(int matrix[][10] , int rowSize){}
- matrix的声明看起来是一个二维数组,实际上形参是指向含有10个整数的数组的指针。
6.2.5 main 处理命令行选项
- main函数是演示C++程序如何向函数传递数组的好例子。到目前为止,使用的是main函数都是只有空的形参列表的形式。比如int main(){}
- 有时候确实需要给main函数传递实参,一种常见的情况下是用户通过设置一组选项来确定函数所要执行的操作。
- 例如,假定main函数位于执行prog之内,我们可以向程序传递下面的选项。
- prog -d -o ofile data0 这些命令的选项通过两个可以选择的形参传递给main函数
- int main (int argc, char *argv[]){ }
- 第二个形参argv是一个数组,他的元素是指向C风格的字符串的指针,第一个形参argc表示数组中字符串的数量。因为,第二个形参是数组,所以main函数也可以定义成
- int main(int argc,char **argv[]){ },其中argv指向char *
- 当形参传给main函数之后,argv的第一个元素指向程序的名字或者一个空的字符串,接下来的元素依次传递命令行提供的实参。最后一个指针之后的元素数值保证为0。
- 当使用arrgv中的实参时,一定要记得可选的实参从arrgv【1】开始,arrgv[0]保存程序的名字,并非用户的输入
6.2.6 含有可变形参的函数
- 一般使用在无法提前预知向函数传递几个实参的情形。
- 为了处理不同数量实参的函数,C++11提供了两个主要的方法:1,如果所有的类型都相同,可以传递一个名为initializer_list的标准库类型;2,如果实参的类型不同,可以编写一个特殊的函数,也就是所谓的可变参数模板。
- C++还有一种特殊的形参类型,即省略符号,可以用它传递可变数量的实参。介绍的省略符形参一般只适用于与C函数交互的接口程序。
initializer_list形参
- 适用于函数的实参数量未知但是全部的实参类型是一致的情形。
- initializer_list是一种标准库类型,用于表示某种特定类型的数值和数组,其定义在同名的头文件中。
initializer_list<T>lst; //默认初始化,T类型元素的空的列表initializer_list<T>lst{a,b,c...}; //list元素的数量和初始值一样多,lst的元素是对应初始值的副本;列表中的元素是constlst2(lst); //拷贝或者赋值一个initializer_list对象不会拷贝列表中的元素,拷贝之后原始列表和副本共享元素lst.size();//类表中元素的数量lst.begin();//返回指向lst中首元素的指针lst.end();//返回指向lst中尾元素下一个未知的指针
- initializer_list和vector一样,也是一种模板类型,定义initializer_list的时候也必须说明列表中所包含元素的类型
- initializer_list<string> ls; //initializer_list的元素类型是string
- initializer_list<int> li; //initializer_list的元素类型是int
- initializer_list中的元素永远是常量,这一点不同于vector,因此无法改变initializer_list对象中元素的数值。
- 使用如下代码就可以编写输出错误信息的函数,使其作用于可以改变的数量的实参。
void error_msg(initializer_list<string> il){for(auto beg = il.begin();beg != il.end();++beg){cout << *beg << " "<< endl;}
}
- 如果想向initializer_list形参中传递一个数值的序列,则必须将序列放在一对花括号内。
省略符形参
- 省略符形参是为了C++程序访问特定的C代码而设置的,这些代码使用了名为varargs的C的标准库的功能。
- 通常省略形参不应该用于其他的目的。
- 省略形参应该用于C和C++通用的类型,但是值得注意的是,大多数的类类型的对象在传递给省略符形参的时候都无法正确的拷贝。
- 省略形参只能出现在形参列表的最后一个位置,形式无外乎两种
- void foo(parm_list,...);指定了foo函数的部分形参的类型,对应于这些形参的实参将会执行正常的类型检查,形参后面的逗号是可以选择的。
- void foo(...); 省略符形参所对应的实参不需要类型的检查
6.3 返回类型和return语句
- return语句终止当前正在执行的函数并将控制权返回到该函数被调用的地方。
- 两种形式:return ; 和 return expression;
6.3.1 无返回值函数
- 没有返回值的return语句只能用于返回类型是void的函数中。返回void的函数不需要非得有return语句,因为在该类函数的最后一句会隐式执行return。
- 通常情况下,void函数如果想在他的中间位置退出,可以使用return语句,这个用法类似break。
- 例如:写一个swap函数,使其在参与交换的数值相等的时候,什么也不做,直接退出
void swap(int &v1,int &v2){if(v1 == v2){return ;}int tmp = v2;v2 = v1;v1 = tmp;
}
int main(){int s1 = 1;int s2 = 1;swap(s1,s2);return 0;
}
- 一个返回类型是void的函数也可以使用return的第二种形式,不过此时return语句的expression必须是另外一个void的函数。强行令void函数返回其他类型的表达式将产生编译错误。
6.3.2 有返回值的函数
- 只要函数的返回类型不是void,那么函数内部的每一个return语句必须返回一个数值。
- return的返回类型必须和函数的返回类型相互一致,或者隐式转换成函数的返回类型
- 在含有return语句的循环后面的应该也有一条return语句,如果没有的话该程序是错误的,很多编译器都无法发现这个错误
值是如何返回的
- 数值的返回和初始化一个变量或者形参的方式是完全一样的,返回的数值用于初始化调用点的一个临时变量,该临时变量就是函数调用的结果。
- 必须注意函数返回局部变量时的初始化规则。
- 例子:给定计数值、单词和结束符之后,判断计数值是否大于1,是的话,返回单词的复数;否的话,返回单词的原型
//如果str的数值大于1,返回word的复数的形式
string make_plural(size_t ctr, const string &word, char ending){return (ctr > 1) ? word + ending : word;
}
int main(){string s1 = "Hello Hello string world";cout << make_plural(2,s1,'s') ;return 0;
}
- 这个函数返回的类型是string,意味着返回值将会被拷贝到调用点。因此,该函数将会返回一个word的副本或者一个未命名的临时的string对象,该对象是word和string的和。
- 同其他引用的类型一样,如果函数返回引用,则该引用仅仅是它所引用对象的一个别名。
- 例子:函数返回两个string中形参较短的那个并且返回其引用,其中形参和返回类型都是const string的引用,不管是调用函数还是返回的结果都不会真正的拷贝string对象。
//跳出两个string对象中较短的那个,并且返回其引用
const string &shorterString(const string &s1,const string &s2){return s1.size() < s2.size() ? s1 : s2;
}
int main(){string s1 = "Hello Hello string world";string s2 = "Hello Hello";cout << shorterString(s1,s2) ;return 0;
}
不要返回局部对象的引用或者指针
- 函数执行完毕之后,会释放掉占用的存储空间,因此函数的终止意味着将局部变量的引用指向不再有效的内存区域。
- 错误的原因在于试图访问未定义的变量
- 返回局部对象的指针也是错误的,函数完成,局部对象释放,指针将会指向一个不再可用的内存空间
//严重错误:这个函数试图返回局部对象的引用
cosnt string &manip(){string ret;//通过某种凡是改变一下retif(!ret.empty()){return ret;//错误,返回的是一个对于局部变量的引用}else{return "Empty!";//错误:"Empty是一个局部临时的变量"}
}
返回类类型函数和调用运算符
- 调用运算符存在优先级和结合律,其优先级和点运算符号和箭头运算符号等同,并且也符合左结合律。
- 如果函数返回的指针、引用或者类对象,就可以通过函数调用的结果来访问结果对象的成员。
- 例如,通过如下的形式得到较短的string对象的长度(这个例子相当于调用对象的子函数)
//跳出两个string对象中较短的那个,并且返回其引用
const string &shorterString(const string &s1,const string &s2){return s1.size() < s2.size() ? s1 : s2;
}int main(){string s1 = "Hello Hello string world";string s2 = "Hello Hello";
// cout << shorterString(s1,s2) ;auto sz = shorterString(s1,s2).size();cout << sz << endl;return 0;
}
引用返回左值
- 函数的返回类型决定了函数的调用是否是左值。
- 返回引用的函数得到左值,其他类型的函数返回右值。可以像使用其他左值的使用方式一样来返回引用的函数的调用,特别的是,可以为返回类型是非常量的引用的函数的结果进行赋值。
char &get_val(string &str,string::size_type ix){return str[ix];
}
int main(){string a("a Value"); //输出a valuecout << a << endl;get_val(a,0) = 'A';//将s[0]的数值改为Acout << a << endl; //将输出A valuereturn 0;
}
- 返回读的值是引用,因此调用是一个左值,和其他的左值一样他也能出现在赋值运算符号的左侧
- 如果返回的类型是对于常量的引用,不能给调用的结果赋值
列表初始化返回值
- 函数可以返回花括号内包围的熟知的列表。类似于其他返回的结果,此处的列表也用来对于对于表示函数返回的临时量进行初始化。如果列表为空,临时量执行数值初始化,否则返回的数值由函数的返回的类型决定。
主函数main的返回数值
- 函数的返回类型不是void,必须返回一个数值,但是mian函数例外,允许main函数没有return语句直接结束。如果控制到达了main函数的结尾而且没有return语句,编译器会隐式插入一个返回为0的return语句。
- mian函数的返回数值可以看做是状态的指示器。返回0表示执行成功,其他数值表示返回失败,其中非0的数值由具体机器而定。为了使得返回的类型和机器无关,可以引入两个预处理的变量,分别用来表示成功和失败。
if(1){return EXIT_SUCCESS;}else{return EXIT_FAILURE;}
- 其中EXIT_SUCCESS和EXIT_FAILURE定义在头文件cstdlib头文件中
- 因为上面两个变量属于预处理变量,因此既不需要在前面加上std::也不能在using声明中出现。
递归
- 如果一个函数调用了它自身,无论是直接还是间接调用都称该函数是递归函数。
- 例子:使用递归函数实现求阶乘的功能
int factorial(int val){if(val > 1 ){return factorial(val - 1) * val;return 1;}
}
int main(){cout << factorial(5) << endl;return 0;
}
- 注意事项,在使用递归函数的时候,一定会包含一支路径是不包含递归调用的,否则函数将会永远的执行下去。
6.3.3 返回数组的指针
- 因为数组不能被拷贝,因此函数不会返回数组。但是,函数可以返回数组的指针或者引用,其中最为有效的方法是使用类型别名的方式。
typedef int arrT[10];//arrT是一个类型的别名,它表示的类型是含有10个整数的数组using arrT = int[10];//arrT的等价声明arrT* func(int i);//func返回一个指向含有10个整数的数组的指针
声明一个返回数组指针的函数
- 要想在声明func时不使用类型别名,必须牢记被定义的名字后面的数组的维度
int arr[10]; //arr是一个含有10个整数的数组int *p1[10]; //p1是一个含有10个指针的数组int (*p2)[10] = &arr; //p2是一个指针,指向含有10个整数的数组
- Type (*function(parameter_list))[dimension]
- 其中Type表示元素的类型,dimension表示数组的大小,(*function(parameter_list))两边的括号必须存在,就像上面对于p2的定义一样,如果没有这对括号,函数的返回类型是指针的数组。
例子
- int (*func(int i))[10];
- func(int i)表示调用func函数的时候需要一个int类型的实参
- (*func(int i))[10] 表示解引用func的调用将得到一个大小是10的数组
- int (*func(int i))[10]表示数组中的元素类型是int类型
使用尾置返回类型
- 可以使用尾置返回类型来替代上面提到的func声明的方法。
- 任何函数都可以使用尾置返回,即使是返回类型比较复杂的函数也仍然可以使用,比如返回类型是数组的指针或者数组的引用。
- 尾置返回类型跟在形参列表的后面,并且以一个->符号开头。为了表示函数真正的返回类型跟在形参列表之后,在本应该出现返回类型的地方放置一个auto。
//func接受一个int类型的实参,返回一个指针,这个指针指向含有10个整数的数组
auto func(int i) -> int(*)[10];
- 把函数的返回值类型放在了形参列表的后面,所以func函数返回的是一个指针,并且这个指针指向了含有10个整数的数组
使用decltype(这一部分有问题)
- decltype关键字一般用于知道函数返回的指针指向哪个数组的时候进行对于返回类型的声明。
- 例子:函数返回一个指针,该指针可以根据参数i的不同指向两个已知数组中的一个
int odd[] = {1,3,5,7,9};int even[] = {0,2,4,6,8};//返回一个指针,这个指针指向含有5个整数的数组decltype (odd) *arrPtr (int i){return (i % 2) ? &odd :&even;}
- 程序无法执行
- decltype并不负责把数组类型转化成对应的指针,所以decltype的结果是一个数组,要想表示attPtr返回指针还必须在函数生命的时候加上一个*号。