第6章 函数

6.1 函数基础

// 把ret和val的乘积赋给ret,然后将val减1
ret *= val--;

调用函数式要提供能够转换成形参类型的实参。

局部静态对象static,如果没有显式的初始值,它将自动执行值初始化,内置类型的局部静态变量初始化为0。

一般情况下,函数只能定义一次,但可以声明多次,如果用不到可以只有声明没有定义。

分离式编译

如果修改其中一个源文件,只需重新编译那个改动了的文件。大多数编译器提供了分离式编译每个文件的机制。这一过程会产生.obj(windows)或.o(unix)的目标对象文件,编译器把对象文件链接在一起形成可执行文件。

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 参数传递

  • 引用传递
  • 值传递
传值参数

指针形参

指针的行为和其他非引用类型一样。当执行指针拷贝操作时,拷贝的是指针的值。拷贝后,两个指针是不同的指针。可以通过指针修改它所指向的对象的值,而不能改变实参指针本身的值。

传引用参数

通过引用形参可以改变一个或多个实参的值,等价于直接传入对象而不是对象的地址。

使用引用避免拷贝

如果传入的对象占有较大的空间,例如类,结构,容器,较长的字符串等,在值传递拷贝操作时效率很低,或者有的不支持拷贝操作时,都应使用引用传参避免拷贝。如果为了避免低效的拷贝,但又不需要修改实参的值,可以把形参定义成常量引用

引用形参返回额外信息

引用形参可以帮助我们在函数中返回多个结果。

  • 定义新的数据结构
  • 传入引用实参
const形参和实参
int fcn(const int i);
int fcn(int i);

两个函数形式上虽不同但没有实质的区别,因为顶层const被忽略,两个函数传入的参数可以完全一样。

指针或引用形参、const
int i = 42;
const int *cp = &i;
const int &r = i;
const int &r2 = 42;
int *p = cp;    // 错误,p的类型和cp的类型不匹配
int &r3 = r;    // 错误,r3 的类型和 r 的类型不匹配
int &r4 = 42;   // 错误,不能用字面值初始化一个非常量引用

同样的规则应用到参数传递

数组形参
  • 不拷贝数组
  • 转换成指针
  • 确保不越界
void print(const int*);
void print(const int[]);
void print(const int[10]);

三个函数实质上相同,形参都是 const int *

int i = 0, j[2] = {0,1};
print(&i);  // &i,类型是int*
print(j);

管理指针形参

  • 使用标记指定数组长度

字符串存储在数组中,并且最后一个字符后面跟着一个空字符

  • 使用标准库规范

传递指向数组首元素的尾后元素(尾元素下一个位置)的指针

void print(const int *beg, const int *end)
{...}
  • 显示传递一个形参
传递多维数组
void print(int (*matrix)[10], int rowSize){}

void print(int matrix[][10], int rowSize){}
main函数参数
int main(int argc, char *argv[]){ ... }
int main(int argc, char **argv){ ... }

argc表示数组中字符串的数量,argv是一个元素是字符串指针的数组

argv[0]指向程序名或者空字符串,最后一个元素值为0

可变形参
  • initializer_list 使用标准库
  • 可变参数模板
  • 省略符形参

initializer_list
函数实参数量未知,但是类型相同,使用initializer_list形参。表示某种特定类型的数组,其定义在同名的头文件中。类似于vector,但是initializer_list的元素都是常量。

省略符形参
可用它传递可变数量的实参,一般用于与C函数交互的接口程序。

省略符参数只能出现在形参列表的最后一个位置,他的形式:

void foo(parm_list, ...);   // 逗号是可选的
void foo(...);

6.3 返回类型和return语句

不要返回局部对象的引用或指针

调用一个返回非常量引用的函数得到的是左值,其他返回类型得到右值。

列表初始化返回值

vector<string> process()
{
    // excepted actual 是 string 对象
    return {"function", expected, actual};
}

main函数返回值

没有return语句时,编译器自动隐式插入一句返回0return语句。

main函数返回0表示执行成功,返回非0具体含义依机器而定,为了使其含义与机器无关,cstdlib头文件定义了两个预处理变量来关联成功与失败。

int main()
{
    if(some_failure)
        return EXIT_FAILURE;
    else
        return EXIT_SUCCESS;
}
返回数组指针

数组不能拷贝,所以函数不能返回数组,但可以返回数组的指针或者引用

使用类型别名来定义函数

typedef int arrT[10];   // arrT 含有10个int的数组
using arrT = int[10];   // 等价
arrT* func(int i);

声明一个返回数组指针的函数

int arr[10];
int *p1[10];
int (*p)[10] = &arr;

不用别名来声明函数

int (*func(int i))[10];

声明的含义:

  • func(int i)表示调用func函数时需要一个int类型的实参
  • (*func(int i))意味着我们可以对函数调用的结果执行解引用操作
  • (*func(int i))[10]表示解引用func的调用将得到一个大小是10的数组
  • int (*func(int i))[10]表示数组中的元素是int类型

使用尾置返回类型

尾置返回类型跟在形参列表后面并以一个->符号开头。在函数返回类型的位置用auto代替。

// 接受一个int类型的实参,返回一个指针,指向含有10个int的数组
auto func(int i) -> int(*)[10];

使用decltype

知道函数返回的指针将指向哪个数组时,可以使用decltype关键字声明返回类型。

如下例:根据参数i的不同指向两个已知数组中的某一个:

int odd[] = {1,3,5,7,9};
int even[] = {0,2,4,6,8};

decltype(odd) *arrPtr(int i)
{
    return (i % 2) ? &odd : &even;
}

arrPtr使用关键字decltype表示他的返回类型是个指针,并且指针所指向的对象与odd一致,所以attPtr返回一个指向含有5个int的数组的指针。但是decltype不把数组类型转换成指针,其结果是数组,要在arrPtr前加一个*符号。

6.4 函数重载

重载,函数名字相同形参列表不同,只有返回类型不同的不是重载。main函数不能重载。

重载和const形参

顶层const不影响传入函数的对象,所以一个拥有顶层const的形参无法和另一个没有顶层const的形参区分开。

Record lookup(Phone);
Record lookup(const Phone);     // 重复声明 Record lookup(Phone)

Record lookup(Phone *);
Record lookup(Phone* const);    // 重复声明 Record lookup(Phone *)

如果形参是某种类型的指针或引用,通过区分指向的是常量对象还是非常量对象可以实现函数重载,此时const是底层的。

Record lookup(Account&);
Record lookup(const Account&);  // 新函数

Record lookup(Account *);
Record lookup(const Account*);  // 新函数

const对象只能传给const形参,而非常量可以转换成const,所以const形参也可以接受非常量实参。当传递非常量对象的时候,编译器会优先选择非常量版本的函数。

const_cast和重载

const_cast在重载函数的情景中最有用。

const string & func1(const string &s1, const string &s2)
{
    // return &
}

string & func2(string &s1, string &s2)
{
    auto &r = func1(const_cast<const string&>(s1), const_cast<const string&>(s2));
    return const_cast<string&>(r);
}

利用const_cast转换调用const版本,保证安全。

重载函数的调用

当重载函数参数数量相同且参数类型可以相互转换时,确定所调用的重载函数更加复杂。

重载和作用域

如果在内层作用域中声明名字,它将隐藏外层作用域中声明的同名实体。在不同的作用域中无法重载函数名。

6.5 特殊用途特性

  • 默认实参
  • 内联函数
  • constexpr
默认实参

一旦某个形参设置了默认值,它后面所有形参都必须有默认值。

内联函数和constexpr函数

内联函数可避免函数调用的开销,

在函数返回类型前面加上关键字inline,声明成内联函数。

6.6 函数匹配

重载函数形参数量相等,类型可以转换时,确定具体函数的调用更加复杂。

  • 寻找可行函数
  • 寻找最佳匹配
  • 多参数匹配产生二义性拒绝请求

6.7 函数指针

函数指针指向的是函数而不是对象。用指针替换函数名。

bool stringcompare(const string &, const string &);

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

函数名使用时会自动作为指针。

// 把函数地址赋给指针
pf = stringcompare;
pf = &stringcompare;    // 等价

// 三个等价调用
bool b1 = pf("hello", "goodbye");
bool b2 = (*pf)("hello", "goodbye");
bool b3 = stringcompare("hello", "goodbye");

赋值时要注意参数类型匹配

bool cstrcomp(const char *, const char *);

pf = 0;
pf = cstrcomp;  // 错误,类型不匹配

使用重载函数指针要明确定义

void ff(int*);
void ff(unsigned int);

void (*pf1)(unsigned int) = ff;
void (*pf2)(int) = ff;  // 错误,参数列表不匹配
double (*pf3)(int *) = ff;  // 错误,返回类型不一致
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值