C++ primer第六章函数的学习

介绍

  • 首先介绍函数的定义和声明,包括如何传入参数以及函数如何返回结果。
  • 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 分离式编译

  • 将程序拆分成不同的部分分别存储,分离式编译器允许将程序分割到几个文件中,对于每个文件进行独立编译。

编译和链接多个源文件

  • 头文件 存放函数的声明
  • 源码文件 存放函数的具体实现的代码
  • 主函数 调用函数的具体执行,需要引入头文件

例子

  • 源文件 factCC.cpp
int fact(int val){int rel = 1;for(int i = val;i != 0;i--){rel *= i;}return rel;
}
  • 头文件 factHead.h
int fact(int val);
  • main函数
#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;
}
  • C++建议使用引用类型的形参替代指针

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返回指针还必须在函数生命的时候加上一个*号。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/447048.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言指针作为函数参数 以及智能指针作为函数参数

总所周知指针作为函数参数传递的时候 传递的是指针的拷贝&#xff08;指针也是变量&#xff09; 这里提供四种指针的传递方法 改到实际的指针。 #include <stdio.h> #include <memory> #include <iostream> using namespace std; void test1(char **string)…

Android Studio打包和引用aar

一、简介 Android 库在结构上与 Android 应用模块相同。它可以提供构建应用所需的一切内容&#xff0c;包括源代码、资源文件和 Android 清单。不过&#xff0c;Android 库将编译到您可以用作 Android 应用模块依赖项的 Android 归档 (AAR) 文件&#xff0c;而不是在设备上运行…

C++ primer第六章6.4函数的学习 之函数的重载

6.4 函数的重载 函数的名字相同但是形参的列表不同&#xff0c;将其称之为重载函数 void print(const char *cp); void print(const int *beg,const int * end); void print(const int ia[],size_t size); 形如上面所展现的这样&#xff0c;当调用这些函数的时候&#xff0c;…

C++有限状态机的实现

//待完善 有限状态机是一个很常用的技术&#xff0c;在流程控制和游戏AI中都比较实用&#xff0c;因为状态机编程简单又很符合直觉。与有限状态机类似的是设计模式中的状态模式。本文是参考《Programming Game AI by Example》 一、 记得最开始工作时候也接触过有限状态机&…

手势希尔排序

void shell_sort(int *data, int length){int gap0;int i0,j0;for(gaplength/2;gap>1;gap/2){//组内插入排序for(igap;i<length;i){int temp data[i];for(ji-gap;j>0&&temp<data[j];jj-gap){data[jgap]data[j];}data[jgap]temp;}} }

Android之android.os.Build

一、类概述&#xff1a;从系统属性中提取设备硬件和版本信息。 二、内部类&#xff1a; 1、Build.VERSION 各种版本字符串 2、Build.VERSION_CODES 目前已知的版本代码的枚举类 三、常量&#xff1a;UNKNOWN 当一个版本属性不知道时所设定的值。其字符串值为 “unknown” 。 …

C++ unsigned char*转化为string的形式

unsigned char*转化为string int main(int argc,char **argv){//unsigned char * 转化为string//参考链接 https://www.itdaan.com/tw/4ff531a5e6651468a5b7c6d95927ba3dunsigned char *foo;unsigned char str[] "Hello world";string strHH;foo str;strHH.append…

KMP算法面试题

面试题&#xff1a;写一个在一个宇符串(n)中寻找一个子串&#xff08;m)第一个位置的函数。 10G的日志中&#xff0c;如何快速地查找关键字&#xff1f;

C++对于程序调试很有用的系统自带的名字

简单介绍 __func__当前调试的函数的名字__FILE__存放文件名的字符串的字面值__LINE__存放当前行号的整型字面值__TIME__存放文件编译时间的字符串的字面值__DATE__存放文件编译日期的字符串的字面值 例子 if(word.size() < threshold){cerr << "Error: " …

Android中List、Set、Map数据结构详解

Android中一般使用的数据结构有java中的基础数据结构List&#xff0c;Set&#xff0c;Map。还有一些Android中特有的几个&#xff0c;SparseArray(使用Map时Key是int类型的时候可以用这个代替)等。 继承关系&#xff1a; Collection<–List<–ArrayList Collection<…

Android设计模式之——单例模式

一、介绍 单例模式是应用最广的模式之一&#xff0c;也可能是很多初级工程师唯一会使用的设计模式。在应用这个模式时&#xff0c;单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个全局对象&#xff0c;这样有利于我们协调系统整体的行为。 二、定义 …

我的职业生涯规划(软件工程)

以后笔记先在语雀整理 方便一点https://www.yuque.com/juhao-pqdor/goeie3 整理一下自己的笔记 弥补一下以前没写博客的遗憾吧 二十载求学路将尽&#xff0c;行文至此&#xff0c;思绪万千。求学之路始于家乡&#xff0c;竿转热河&#xff0c;而今终于石门。一路行之如人饮水…

C++ primer第六章6.5函数的学习 之特殊用途的语言特性

6.5.1 默认实参 将反复出现的数值称为函数的默认实参&#xff0c;调用含有默认实参的时候可以包含该实参也可以不包含比如程序打开页面会有一个默认的宽高&#xff0c;如果用户不喜欢也允许用户自由指定与默认数值不同的数值&#xff0c;具体例子如下图所示 typedef string::s…

Android设计模式之——Builder模式

一、介绍 Builder模式是一步一步创建一个复杂对象的创建型模式&#xff0c;它允许用户在不知道内部构建细节的情况下&#xff0c;可以更精细的控制对象的构造流程。该模式是为了将构建复杂对象的过程和它的部件解耦&#xff0c;使得构建过程和部件的表示隔离开来。 因为一个复…

c++后端开发书籍推荐

推荐书籍: 略读80% 精读50% C&#xff1a; C Primer Plus C和指针&#xff08;入门书 不只是指针&#xff09; C陷阱与缺陷&#xff08;宏相关&#xff09; C专家编程 C&#xff1a; 有专门的视频 C primer C程序设计原理与实践&#xff08;c之父写的 入门经典&#xff09; Ef…

C++ primer第六章6.6函数匹配

函数的匹配 当重载函数的形参数量相等以及某些形参的类型可以由其他的类型转化得来的时候&#xff0c;对于函数的匹配就会变得很难 确定候选函数和可行函数 函数匹配的第一步就是选定本次调用对应的重载函数集&#xff0c;集合中的函数称为候选函数。候选函数具有两个特征&am…

Android设计模式之——原型模式

一、介绍 原型模式是一个创建型的模式。原型二字表明了该模型应该有一个样板实例&#xff0c;用户从这个样板对象中复制出一个内部属性一致的对象&#xff0c;这个过程也就是我们俗称的“克隆”。被复制的实例就是我们所称的“原型”&#xff0c;这个原型也是可定制的。原型模…

C++ primer第六章6.7函数指针

函数指针 函数指针指向的是函数而不是对象。和其他指针一样&#xff0c;函数指针指向某种特定的类型。函数的类型由他的返回类型和形参类型共同决定&#xff0c;而与函数的名字无关。 //比较两个string对象的长度 bool lengthCompare(const string &,const string &);…

Android设计模式之——工厂方法模式

一、介绍 工厂方法模式&#xff08;Factory Pattern&#xff09;&#xff0c;是创建型设计模式之一。工厂方法模式是一种结构简单的模式&#xff0c;其在我们平时开发中应用很广泛&#xff0c;也许你并不知道&#xff0c;但是你已经使用了无数次该模式了&#xff0c;如Android…

C++ primer第十八章 18.1小结 异常处理

18.1 异常处理 异常处理机制&#xff0c;允许程序独立开发的部分能够在运行的时候出现的问题进行通信并且做出相应的处理&#xff0c;异常的处理使得我们可以将问题的检测和处理分离开来。程序的一部分负责检测问题的出现&#xff0c;然后将解决这个问题的任务传递给程序的另一…