0
点赞
收藏
分享

微信扫一扫

C++(六):函数

诗与泡面 2021-09-21 阅读 36

函数

函数是一个命名了的代码块,我们通过调用函数执行相应的代码,通过函数可以有0个或多个参数,而且会产生一个结果。可以重载函数,即同一个名字可以有几个不同的函数。

[TOC]

6.1 函数基础

一个典型的函数定义包括以下部分:返回类型、函数名字、形参列表、函数体。

以求取数的阶乘的函数为例:

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

调用fact函数时,如下所示:

int main()
{
    int j=fact(5);
    cout<<"5! is "<<j<<endl;
    return 0;
}

调用函数时,用实参初始化函数的形参,然后将控制权转移给被调用函数

当所调用的函数遇到return语句时,函数返回return语句中的值,将控制器从被调函数转移回主调函数。

实参:实参是形参的初始值,存在对应的关系。实参的类型必须与对应的形参类型匹配,且函数有几个形参就得提供相同数量的实参。

形参:每个函数的形参列表可以为空,但不能省略。

1.1 局部对象

在C++中,名字有作用域,对象有生命周期

  • 名字的作用域是程序文本的一部分,名字在其中可见
  • 对象的生命周期是程序执行过程中该对象存在的一段时间。

函数体是一个语句块,构成了一个新的作用域,在其中定义的形参和内部变量都是局部变量,仅在函数的作用域内可见,

自动对象:对于普通的局部变量对应的对象来说,当函数的控制路径经过变量定义语句时创建该对象,到达定义所在的块末尾时销毁它。只存在块执行期间的对象称为自动对象。形参是一种自动对象。

局部静态对象:如果要让局部变量的生命周期贯穿函数调用及之后的时间。可以将局部变量定义成static类型从而获得这样的对象。局部静态对象在程序执行路径第一次经过对象定义语句时初始化,直到程序终止才被销毁。

1.2 函数声明

函数在使用前必须先声明,类似于变量,但是函数只能定义一次,却可以声明多次。函数声明也叫做函数原型

函数的声明不包含函数体,所以无须形参的名字,即在声明中可以省略形参的名字。

函数的声明放在源文件中是合法的,但是可能容易出错。所以建议把函数声明放在头文件中,能够确保同一函数的所有声明都保持一致。

1.3 分离式编译

随着程序越来越复杂,希望把程序的各个部分分别存在不同文件中。C++语言支持分离式编译,可以把程序分割到几个文件中取,每个文件独立编译。

如果只是修改了其中一个源文件,那么只需要重新编译那个改动了的文件。

6.2 参数传递

每次调用函数时都会重新创建它的形参,并用传入的实参对形参进行初始化。

当形参时引用类型时,我们说它对应的实参被应用传递或者函数被传引用调用

2.1 传值参数

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

指针形参:指针的行为和其他非引用类型一样。当执行指针拷贝操作时,拷贝的是指针的值。因为指针使我们可以间接地访问它所指的对象,所以通过指针可以修改它所指对象的值。

void reset(int *ip)
{
    *ip=0;
    ip=0;
}

2.2 传引用参数

对引用的操作实际上是作用在引用所引的对象上。通过使用引用形参,允许函数改变一个或多个实参的值。例如

void reset(int &i)
{
    i=0;
}

使用引用可以避免拷贝,拷贝比较复杂类型时比较低效,而且有的类型不支持拷贝,所以函数只能通过引用形参访问该类的对象。

2.3 const形参和实参

当形参是const时,必须要注意顶层const(常量指针int* const p)。在初始化该形参时传递常量对象或非常量对象都是可以的。

void fcn(const int i) //fcn能够读取i,但不能修改i。

2.4 数组形参

数组不允许拷贝以及使用数组时会将其转换成指针。所以无法直接以值传递的方式传递给形参数组,但是可以用指向数组首元素的指针进行传递。

void print(const int*);
void print(const int[]);
void print(const int[10]);

但是使用形参数组时要保证数组不越界,因此可以向函数提供关于数组长度的额外信息。常用三种常用技术:

使用标记指定数组长度:该方法要求数组本身包含一个结束标记,例如字符数组最后面的空字符。

使用标准库规范:该方法传递指向数组首元素和尾后元素的指针。

显示传递一个表示数组大小的形参:该方法专门定义了一个表示数组大小的形参。

2.5 main:处理命令行选项

main函数不只有空形参列表。有时需要给main传递实参,一种常见的情况是用户通过设置一组选项来确定函数所要执行的操作。

2.6 含有可变形参的函数

如果无法提前预知应该向函数传递几个实参,c++11提供了两种主要的方法;

  • 如果所有实参类型相同,可以传递一个名为initialiear_list的标准库;

int sum_xu(initializer_list<int> data)
{
    int res=0;
    for (auto i = data.begin() ; i<data.end();i++)
    {
        res += *i;
    }
    return res;
}
a = sum_xu({ 1,2,3,4,5 });//传递参数
  • 如果实参类型不同,可以编写一种特殊的函数,也就是可变参数模板。

6.3 返回类型和return语句

return语句可以终止当前正在执行的函数并将控制权返回到调用函数的地方。

3.1 无返回值函数

没有返回值的return语句只能用在返回类型是void的函数中,可以无须显示的return语句。

3.2 有返回值函数

如果函数的返回类型不是void,则该函数内的每条return语句都必须返回一个值但是不能返回局部变量的引用或指针。

3.3 返回数组指针

由于数组不能被拷贝,所以函数不能返回数组,但是函数可以返回数组的指针或引用。

  • 声明一个返回数组指针的函数:要牢记被定义名字后数组的维度。
int (*func(int i))[10];
func(int i)表示调用func函数时需要输入int类型的实参;
(*func(int i))表示可以对函数调用的结果进行解引用;
(*func(int i))[10]表示解引用的调用将得到一个大小是10的数组;
int (*func(int i))[10];表示数组中的元素是int类型。
  • 使用尾置返回类型:在c++11中有简化的方法,就是使用尾置返回类型。尾置返回类型跟在形参列表后面并以一个->符号开头。
auto func(int i)->int(*)[10];

6.4 函数重载

如果在同一个作用域内的几个函数名字相同但形参列表不同,被称之为重载函数,例如:

void print(const char *cp);
void print(const int *beg,const int *end);
void print(const int ia[],size_t size);

当调用这些函数时,编译器会根据传递的实参类型推断想要的是哪个函数。

定义重载函数:对于重载的函数,它们应该在形参数量或形参类型上有所不同,不允许两个函数除了返回类型外其他所有要素都相同。

重载和const形参:顶层const不影响传入函数的对象,一个拥有顶层const的形参无法和另一个没有顶层const的形参区分开。而底层const可以通过区分其指向的是常量对象还是非常量对象实现函数重载。

const_cast:可以将一个常量转换回一个普通量。

调用重载的函数:函数匹配是一个过程,把函数调用与一组重载函数中某一个关联起来,也叫做重载确定。

4.1 重载与作用域

重载对作用域的一般性质并没有什么改变。如果在内层作用域中声明重载函数,它将隐藏外层作用域中声明的同名实体。在不同作用域中无法重载函数名。

6.5 特殊用途语言特性

有三种函数相关的语言特性,分别是默认实参、内联函数和constexpr函数,以及在程序调试过程中常用的一些功能。

5.1 默认实参

某些函数有这样一种形参,在函数的很多次调用时都赋予相同的值,此时可以将该值称为函数的默认实参。 在调用含有默认实参的函数时,可以包含该实参,也可省略该实参。例如

typedef string::size_type sz;
string screen(sz ht=24,sz wid=80,char backgrnd=' ');//每个形参都有个默认实参。且某个形参被赋予默认值,它后面所有形参都必须有默认值
string window;
window = screen();
window = screen(66);
window = screen(66,256);
window = screen(66,256,'#');//要覆盖默认实参时也得从先到后覆盖。

5.2 内联函数和constexpr函数

将函数指定为内联函数,可以避免函数调用上的开销,就是将它在每个调用点上"内联地"展开。内联函数定义如下:

inline const string &shorterString(const string &s1,const string &s2){
    return s1.size()<=s2.size()?s1:s2;
}

内联机制用于优化规模较小、流程直接、频繁调用的函数。

constexpr函数是指能用于常量表达式的函数:函数的返回类型及所有形参的类型都得是字面值类型,且函数体中必须有且只有一条return语句。

而内联函数和constexpr函数的定义通常放在头文件中。

5.3 调试帮助

程序可以包含一些用于调试的代码,但是这些代码只在开发程序时使用。当应用程序编写完成准备发布时,需要先屏蔽掉调试代码。这种方法用到两项预处理功能:assert和NDEBUG。

assert预处理宏:assert是一种预处理宏(定义在cassert头文件中),就是一个预处理变量,使用一个表达式作为它的条件:

assert(expr);
assert(word.size()>threshole);

首先对expr求值,如果表达式为佳,assert输出信息并终止程序的执行,如果表达式为真,则assert什么也不做。

NDEBUG预处理变量:assert行为依赖于一个名为NDEBUG的预处理变量的状态。如果定义了NDEBUG,则assert什么也不做,如果没有,此时assert将执行运行时检测。

#define NDEBUG

预处理器定义了5个对程序调控有用的名字:

--func--存放函数的名字
--FILE--存放文件名的字符串字面值
--LINE--存放当前行号的整型字面值
--TIME--存放文件编译时间的字符串字面值
--DATE--存放文件编译日期的字符串字面值

因此可以使用这些常量在错误消息中提供更多的信息

if(word.size()<threshold)
    cerr<<"Error: "<<_ _FILE_ _
        <<" : in function "<<_ _func_ _
        <<" at line "<<_ _LINE_ _<<endl
        <<"     Compiled on "<<_ _DATE_ _
        <<" at "<<_ _TIME_ _<<endl
        <<"     Word read was \""<<word
        <<"\":Length too short"<<endl;

6.6 函数指针

函数指针指向的是函数而非对象。函数指针指向某种特定类型,函数的类型由它的返回类型和形参类型共同决定,于函数名无关,如:

bool lengthCompare(const string &,cosnt string &);
bool (*pf)(const string &,const string &);

如果把函数名作为一个值来使用,该函数回自动地转换成指针。

pf = lengthCompare;
pf = &lengthCompare;//等价

同时也可用指针调用函数

bool b1=pf("hello","goodbye");
bool b2=(*pf)("hello","goodbye");
bool b3=lengthCompare("hello","goodbye");
举报

相关推荐

0 条评论