c++ prime 第六章——函数

第六章函数
函数是一个命了名的代码块,函数可以有0个或多个参数,而且通常会产生一个结果。可以重载函数,也就是说同一个名字可以对应几个不同的函数。
6.1函数基础
一个典型的函数包括以下部分:返回类型,函数名字,由0个或多个形参组成的列表和函数体。其中,形参以逗号隔开。我们通过调用运算符来执行函数 。我们来举一个例子:
int fact(int val){ //int是函数返回类型,fact是函数名,val是形参
int i=1;
while(val>1)
*i=val–; //利用循环,是i为val的阶乘
return i;} //i是返回值
调用函数
int main ()
{
int j=fact(5);//调用函数时,主调用函数的执行被暂时中断,被调函数开始执行
cout << “5! is ”<<j<<endl;
return 0;
}
形参和实参
实参是形参的初始值。第一个实参的初始值,第二个实参是第二个形参的初始值,依次类推。所以实参的类型应该和形参一致。
形参列表中的形参通常用逗号隔开,其中每个形参都是含有一个声明符的声明。
即使两个形参的类型一样,也必须把两个类型都写出来
int f3(int v1,v2) //错误
int f4(int v1,int v2) //正确
函数返回类型
大多数类型都能用作函数的返回类型,函数的返回类型不能是数组类型或者函数类型,但可以使指向它们的指针。不带返回值的return语句只能用于返回类型为void的函数。在返回类型为void的函数中,return返回语句不是必需的,隐式的return发生在函数的最后一个语句完成时。
6.1.1局部对象
在c++语言中,名字有作用域,对象有生命周期。形参和函数体内定义的变量统称为局部变量。
自动对象:只存在与块执行期间的对象。当块的执行结束后,块中创建的自动对象的值就变成未定义的了。
但如果我们需要局部变量贯穿整个程序,于是就有了局部静态对象。使用static关键字可以延长变量的生命周期,使其到整个程序结束才会被销毁。
6.1.2函数声明
和其他名字一样,函数名字也必须在被使用前声明。
6.1.3分离式编译
分离式编译允许我们把程序分割到几个不同的文件中,每个文件独立编译。
****这一点请仔细阅读编译器的用户说明。
6.2参数传递
当形参是引用类型时,我们说他对应的实参被引用传递或者函数被传引用调用。当形参的值被拷贝给形参时,形参和实参就是俩个独立的对象,称为值传递或者时传值调用。
6.2.1传值参数
这里主要了解指针类型的形参。指针的行为和其他非引用类型一样。当执行指针拷贝操作后,拷贝的是指针的值。拷贝之后,俩个指针是不同的指针。eg:
void reset(int *p){
*p=0;//改变指针所指对象的值
p=0;//实参并未改变,只改变了p的局部拷贝
}
6.2.2传引用参数
我们知道对引用的操作其实是对被引用对象的操作。而形参引用与之类似。eg:
void reset(int &i){
i=0;//改变了实参的值
}
6.2.3 const形参和实参
在c++语言中,允许我们定义若干具有相同名字的函数,不过前提是不同函数的形参列表应该有明显区别。
我们可以使用非常量初始化一个底层const对象,但是反过来不行
所以我们应该尽量使用常量引用。比如常量引用和普通引用初始值规定不相同。
6.2.4数组形参
数组有俩个特殊性质:
1.不允许拷贝数组
2.使用数组时通常会被转换成指针。
因为不能拷贝数组,所以我们无法对数组进行值传递。因为数组会被转换成指针,所以我们为函数传递一个数组时,实际时传递的指针的首地址。eg:
void printf(const int *p );
void printf(const int [ ] );//可以看出意图作用于一个指针。
void printf(const int a[10]);//10是我们的期望值,但未必如意
管理指针形参三种常用技术
2、使用标准库规范
void print (const int *beg, const int *end)
{
while(beg!= end)
cout << *beg++<<endl;
}
print (begin(),end(j));

3、显示传递一个表示数组大小的形参
void print(const int ia[],size_t size)
{
for (size_t i=0;i!=size;++i)
cout << ia[i]<<endl;
}
int j[]={0,1};
print(j,end(j)-begin(j));
当函数不需要对数组执行写操作时,数组形参应该是指向const 的指针,只有当函数确实要改变元素值的时候,才把形参定义成指向非常量的指针。

数组引用形参
void print(int (&ar)[10])//形参是数组的引用,维度是数组的一部分
{
for (auto elem:arr)
cout <<elem<<endl;
}//但是这样做的缺点是限制了Print的可用性,我们只能将函数作用于大小为10的数组

那么有没有一种方法,能够解决上述问题呢? 既能用到引用的有点,同时又能避免长度带来的限制?
template<size_t size>
void func(int (&p)[size]){ … }
当调用 func是,编译器会从数组的实参计算非类型形参的值,也就是编译器替我们
计算好了size的值,从而省去我们自己传递长度
***使用数组作为形参时,要注意数组不能越界。

传递多维数组
我们先前提到过,c++语言中并没有真正的多维数组,要按数组的数组理解。和所有数组一样,当多维数组传递给函数时,真正传递的其实是数组首元素的指针。而这个元素实际上是一个数组,所以这个指针其实是指向数组的指针。
void printf(int(*mateix)[],int rowsize){
//将matrix初始化声明成指向含有10个整数的数组指针}
**强调matrix前的括号不能丢。

6.3返回类型和return语句
return语句终止当前正在执行的函数并返回函数到调用该函数的地方。return语句有俩种形式:

  1. return;
  2. return expression;
    6.3.1无返回值函数
    没有return语句的函数只能是void类型的。返回void的函数不要求必须有return语句,因为该类型函数的最后一句的后面会隐式的执行return。
    ***如果你想让void函数在中间位置退出,可以使用return语句。eg:
    void swap(int &v1,int v2){
    if(v1==v2)
    return;
    int i=v1;
    v1=v2;
    v2=i;}//此处无需显式的return语句
    6.3.2有返回值的语句
    只要函数不是void类型,则必须在函数末加上return语句返回一个值。(main除外)
    eg:
    string make_plural(size_t ctr,const string &word,const string &ending){
    return (ctr>1)?word+ending:word;}
    ***注意不要返回局部对象的引用或者指针。因为函数完成后,他所占的空间也要被释放掉。函数的终止意味着局部变量的引用将不再指向有效的内存区域。
    函数的返回类型决定函数调用是否是左值,调用一个返回引用的函数得到左值,其他返回类型得到右值,我们能为返回类型是非常量引用的函数的结果赋值

列表初始化返回值
c++11标准规定,函数可以返回花括号包围的值的列表。类似于其他返回结果,此处的列表也用来对表示函数返回的临时量进行初始化
vector process()
{
if …
return {“functionx”,”ikay”};
}

main的返回值
main 可以不加return ,因为它会进行隐形的返回,返回0表示执行成功,返回其他值表示执行失败
int main()
{
if (some_failure)
return EXIT_FATLURE;
else
return EXIT_SUCCESS;
}

递归
函数调用它自身,该函数称为递归函数
但是main函数不能调用它自身
int factorial (int val){
if (val>1)
return factorial(val-1)*val;
return 1;}

返回数组指针的操作
int (fun(int i))[10];返回一个int型大小为10的数组的指针
auto func(int i) -> int(
)[10];
int odd[]={0,1,2,3,4,5,6,7,8,9}
decltype(odd) *fun(int i)//decltype不负责把数组类型转换成对应的指针,所以返回的仍是数组
6.4函数重载
如果同一作用域内的几个函数名字相同但形参列表不同,我们称之为重载函数.重载函数可以帮助程序员减少需要记忆的函数,从而减少程序员的负担。main函数不能重载
不允许两个函数除了返回类型不一样外其他所有的要素都相同
eg:定义重载函数:
Record loolup(const Account&);
Record loolup(const Phone&);
Record loolup(const Name&);
Account acct;
Phone phone;
Record loolup(acct);//调用第一个
Record loolup(phone);//调用第二个

下面形参看起来不一样 ,实际上一样
(const account &acct);
(const account &);

typedef phone telno;
(const phone&);
(const telno&);
一个拥有顶层const的形参无法和另一个没有顶层const的形参区分开来
当我们在内层作用域中声明名字,它将隐藏外层作用域中声明的同名实体。在不同的作用域中无法重载函数名。

默认实参
某些函数有这样一种形参,在函数的很多次调用中它们都被赋予一个相同的值,此时,我们把这个反复出现的值称为函数的默认实参。
string screen(sz ht=24,sz wid =80;char backgrnd = ‘ ’);
一旦某个形参被赋予了默认值,它后面的所有形参都必须有默认值
在给定作用域中一个形参只能被赋予一次默认实参,函数的后续声明只能为之前那些没有默认值的形参添加默认实参。
调用的时候 如果想使用默认实参,只要在调用函数的时候省略该实参就可以
win=screen(66,256);
win =screen(,’?’);

局部变量不能作为默认实参
sz wd=80;char def=’ ’; sz ht();
string screen (sz=ht(),sz=wd,char=def);
string window = screen();
void f2()
{
def=’*’;//改变默认实参的值
sz wd =100;//隐藏了外层定义的wd ,但是没有改变默认值。(局部变量)
window =screen();
}

内联函数
调用函数一般比求等价表达式的值要慢一些
将函数指定为内联函数,通常就是讲它在每个调用点上内联地展开
cout << short (s1,s2) << endl;
cout << (s1.size()<s2.size() ? s1:s2)<<endl;
在函数的返回值类型前面加上关键字inline就可以将它声明为内联函数
内联机制适用于优化规模小、流程直接、频繁调用的函数。

constexpr 函数
是指能用于常量表达式的函数。函数的返回类型及所有形参的类型都得是字面值类型,而且函数体中必须有且只有一条return语句。constexpr函数会被隐式地指定为内联函数
调试帮助
assert预处理宏
assert(expr);
对expr求值,如果表达式为假,assert输出信息并终止程序的执行,如果为真,什么也不做
宏定义在cassert文件中,宏的名字在程序中必须唯一
assert(word.size()>threshold);
NDEBUG预处理变量
如果定义了NDEBUG则assert什么也不做,没有定义,执行assert检查
#define NDEBUG

NDEBUG也有自己的条件调试代码
如果未定义 ,执行#ifndef 和#endif之间的代码,否则忽略
void print(const int ia[],size_t size)
{
#ifndef NDEBUG
cerr << _ func _<<”: array size is ”<< size << endl;
#endif
}
_ FILE _ 存放文件名的字符串字面值
_ LINE _ 存放当前行号的整形字面值
_ TIME _ 存放文件编译时间的字符串字面值
_ DATE _ 存放文件编译日期的字符串字面值
_ func _ 输出当前调试的函数的名字

函数指针
函数指针指向的是函数而非对象。和其他指针一样,函数指针指向某种特定类型
函数的类型由它的返回值和形参类型共同决定,与函数名无关
声明直接用指针替换函数即可
bool length(const string &, const string &);
bool (pf) (const string &, const string &);// pf指向一个函数,该函数时两个const string 的//引用 ,返回值时bool类型
当把函数名作为一个值使用时,该函数自动地转换成指针。
当我们使用重载函数时,指针类型必须与重载函数中的某一个精确匹配
void ff(int
)
void ff(unsigned int);
void (*pf1)(unsigned int)=ff; // pf1指向ff(unsigned)

形参可以使指向函数的指针
use(s1,s2,pf1);
可以使用类型别名和decltype进行代码简化

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

王蒟蒻

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值