[C++学习]函数

本文详细介绍了C++中的函数,包括函数基础、参数传递、返回类型、函数重载、特殊用途语言特性等内容。重点讲解了局部对象、函数声明、分离式编译、传值参数与传引用参数的区别,以及函数指针的使用。文中还探讨了默认实参、内联函数和constexpr函数在调试和优化中的作用,并讨论了函数匹配的规则和实参类型转换的等级。
摘要由CSDN通过智能技术生成

第六章 函数

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

6.1 函数基础

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

函数的形参列表

函数的形参列表可以为空,但不能省略。要想定义一个不带形参的函数,最常用的办法是写一个空的形参列表。不过为了与C语言兼容,也可以使用关键字void表示函数没有形参。

函数返回类型

大多数类型都是能用作函数的返回类型。一种特殊的返回类型是void,它表示函数 不返回任何值。函数的返回类型不能是数组类型或函数类型,但可以是指向数组或函数的指针。

6.1.1 局部对象

形参和函数体内部定义的变量统称为局部变量(local variable)。它们对函数而言是“局部”的,仅在函数的作用域内可见,同时局部变量还会隐藏(hide)在外层作用域中同名的其他所有声明中。

自动对象

对于普通局部变量对应的对象来说,当函数的控制路径经过变量定义的语句时创建该对象,当到达定义所在的块末尾时销毁它。我们把只存在于块执行期间的对象称为自动对象(automatic object)。当块的执行结束后,块中创建的自动对象的值就变成了未定义的了。

局部静态对象

某些时候,有必要令局部变量的生命周期贯穿函数调用及之后的时间。可以将局部变量定义成static类型从而获得这样的对象。局部静态对象在程序的执行路径第一次经过对象定义语句时初始化,并且直到程序终止才被销毁,在此期间即使对象所在的函数结束执行也不会对它有什么影响。

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 2 3 4 5 6 7 8 9 10

控制流第一次经过ctr的定义之前,ctr被创建并初始化为0。每次调用将ctr加1并返回新值。

size_t count_calls(size_t idx){
	if(idx != 0){
		cout << "count_calls " << ctr << endl; // 错误ctr未声明
 	}
    static size_t ctr = 0;	// 调用结束后,这个值仍然有效
    return ++ ctr;
}
int main(){
    for(size_t i = 0; i != 10; ++ i){
        cout << count_calls(i) << endl;
    }
    return 0;
}

6.1.2 函数声明

和其它名字一样,函数的名字也必须在使用之前声明。类似于变量,函数只能定义一次,但可以声明多次。如果一个函数永远也不会被我们用到,那么它可以只有声明没有定义。

函数的三要素(返回类型、函数名、形参类型)描述了函数的接口,说明了调用该函数所需的全部信息。函数声明也称作**函数原型(**function prototype)。

6.1.3 分离式编译

C++语言支持所谓的分离式编译(separate compilation)。分离式编译允许我们把程序分割到几个文件中去,每个文件独立编译。

编译和链接多个文件

假设fact函数的定义位于一个名为fact.cc的文件中,它的声明位于名为Chapter6.h头文件中。显然与其它所有用到fact函数的文件一样,fact.cc应该包含Chapter6.h头文件。另外,我们在名为factMain.cc的文件中创建main函数,main函数将调用fact函数。要生成可执行文件(executable file),必须告诉编译器我们用到的代码在哪里。

$ CC factMain.cc fact.cc # generates factMain.exe or a.out
$ CC factMain.cc fact.cc -o main # generates main or main.exe

如果我们修改了其中一个源文件,那么只需重新编译那个改动了的文件。大多数编译器提供了分离式编译每个文件的机制,这一过程通常会产生一个后缀名是.obj(Windows)或.o(UNIX)的文件。后缀名的含义是该文件包含对象代码(object code)

$ CC -c factMain.cc	#generates factMain.o
$ CC -c fact.cc #generates fact.o
$ CC factMain.o fact.o #generates factMain.exe or a.out
$ CC factMain.o fact.o -o main # generates main or main.exe

6.2 参数传递

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

当形参是引用类型时,我们说他对应的实参被引用传递(passed by reference)或者函数被**传引用调用(**called by reference)。和其他引用一样,引用形参也是它绑定的对象的别名:也就是说,引用形参是它对应的实参的别名。

当实参的值拷贝给形参时,形参和实参是两个互相独立的对象。我们说这样的实参被值传递(passed by value)或者函数被传值调用(called by value)。

6.2.1 传值参数

当初始化一个非引用类型的变量时,初始值被拷贝给对象。此时,对变量的改动不会影响初始值。

指针形参

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

熟悉C的程序员常常使用指针类型的形参访问函数外部的对象。在C++语言中,建议使用引用类型的形参代替指针

6.2.2 传引用参数

通过使用引用形参,允许函数改变一个或多个实参的值。

void reset(int &i){
    i = 0;		// 改变了i所引对象的值
}

使用引用避免拷贝

拷贝大的类类型对象或者容器对象比较低效,甚至有的类类型(包括IO类型在内)根本就不支持拷贝操作。当某种类型不支持拷贝操作时,函数只能通过引用形参访问该类型对象。

// 比较两个string对象的长度
bool isShorter(const string &s1, const string &s2){
    return s1.size() < s2.size();
}

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

使用引用形参返回额外信息

一个函数只能返回一个值,然而有时函数需要同时访问多个值,引用形参为我们一次返回多个值提供了有效的途径。

6.2.3 const和形参实参

顶层const作用于对象本身:

const int ci = 42; 	// 不能改变ci,const是顶层的
int i = ci;			// 正确:当拷贝ci时,忽略了它的顶层const
int * const p = &i;	// const是顶层的,不能给p赋值
*p = 0;				// 正确:通过p改变对象的内容是允许的,现在i变成了0

和其它初始化过程一样,当用实参初始化形参时会忽略掉顶层const。换句话说,形参的顶层const被忽略掉了。当形参有顶层const时,传给它常量对象或者非常量对象都是可以的:

void fcn(const int i);	// fcn能读取i,但是不能向i写值

调用fcn函数时,既可以传入const int,也可以传入int。忽略掉形参的顶层const可能产生意想不到的结果:

void fcn(const int i);	// fcn能读取i,但是不能向i写值
void fcn(int i);		// 错误:重复定义了fcn(int)

指针或引用形参与const

我们可以使用非常量初始哈一个底层const对象,但反过来不行,同时一个普通的引用必须用同类型的对象初始化。

int i = 42;
const int *cp = &i;	// 正确:但是cp不能改变i
const int &r = i;	// 正确:但是r不能改变
const int &r2 = 42;	// 正确
int *p  = cp;		// 错误:p的类型和cp类型不匹配
int &r3 = r;		// 错误:r3的类型和r的类型不匹配
int &r4 = 42;		// 错误:不能用字面值初始化一个非常量引用

尽量使用常量引用

把函数不会改变的形参定义成(普通的)引用是一种比较常见的错误,这么做带给函数的调用者一种误导,即函数可以修改它的实参的值。此外,使用引用而非常量引用也会极大地限制所能接受的实参类型。

// 不良设计:第一个形参的类型应该是const string&
string::size_type find_char(string &s, char c, string::size_type &occurs);

只能将find_char作用于string对象。类似下面这样的调用

find_char("Hello World", 'c', ctr);

将在编译时发生错误。

还有一种更难察觉的问题,假如其它函数(正确地)将它们形参定义成常量引用,那么第二个版本的find_char无法在此类函数中正常使用。

bool is_sentence(const string &s){
    // 如果在s的末尾有
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值