第六章-函数

  • 函数的返回类型不能是数组类型或函数类型,但可以是指向数组或函数的指针

const形参和实参

  • 允许定义若干有相同名字的函数,前提是不同函数的形参列表要有明显的区别
//实参初始化形参时会忽略掉顶层const
//当形参有顶层const时传给它常量对象或非常量对象都行
void fcn(const int i) {} //不能向i写值
void fcn(int i) {} //错误!重复定义了fcn(int)
//因为顶层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; //不能用字面值初始化一个非常量引用
// const int &r4 = 42正确
_______________________________________
void reset (int *ip) { *ip = 0; }
void reset (int &i) { i = 0; }
int i = 0;
const int ci = i;
string::size_type ctr = 0;
reset(&i); //调用第一个reset
reset(&ci); //错误,不能用指向const int对象的指针初始化int*,同虚线上的int *p = cp错误
reset(i); //调用第二个reset
reset(ci); //错误,不能把普通引用绑定到const对象上,如虚线上int &r3 = r错误
reset(42); //错误,不能把普通引用绑定到字面值上,如虚线上int &r4 = 42错误
  • 不能把const对象、字面值或需要类型转换的对象传递给普通的引用形参
如果有函数
string::size_type find_char(string &s, char c, string::size_type &occurs);
函数调用
find_char("HelloWorld", 'o', ctr);
将发生错误,相当于不能int &n = 42,string &s = "HelloWorld"也是错的
但是改成
string::size_type find_char(const string &s, char c, string::size_type &occurs);
就没问题

数组形参

  • 因为1.不允许拷贝数组 2.使用数组时通常将其转换为指针。所以为函数传递数组时实际上是传递数组首元素的指针
void print(const int*);
void print(const int []);
void print(const int [10]); //这里期望数组有10个元素,实际不一定
  • 可以将变量定义成数组的引用
void print(int (&arr)[10]) //arr两端括号必不可少
{
	for (auto elem : arr)
		cout << elem << endl;
}
  • 传递多维数组
void print(int (*matrix)[10], int row);
//matrix指向数组的首元素,该数组的元素也是数组,由10个数构成
//等价定义
void print(int matrix[][10], int row);

main

  • int main(int argc, char *argv[]),当实参传给main后,argv的第一个元素指向程序的名字或一个空字符串,接下来的元素依次传递命令行提供的实参。argc表示数组中的字符串数量

含有可变形参的函数

  • 函数如果实参数量未知且实参类型全部相同,可以传递一个名为initializer_list的标准库类型
  • 和vector一样initializer_lis也是模板类型。但是initializer_list对象中的元素永远是常量值,所以不能改变initializer_list对象中的元素值
//编写输出错误信息的函数
void error_msg(initializer_list<string> il)
{
	for (auto beg = il.begin(); beg != il.end(); ++beg)
		cout << *beg << " ";
	cout << endl;
}
//expected和actual都是string
if (expected != actual)
	error_msg({"functionX", expected, actual});
else
	error_msg({"functionX", "okay"});
  • 含有initializer_list形参的函数也可以同时拥有其他形参
//ErrCode是表示不同类型错误的类
void error_msg(ErrCode e, initializer_list<string> il)
{
	cout << e.msg() << ": ";
	for (const auto &elem : il)
		cout << elem << " ";
	cout << endl;
}
if (expected != actual)
	error_msg(ErrOcde(42), {"functionX", expected, actual});
else
	error_msg(ErrCode(0), {"functionX", "okay"});

返回语句

  • 不要返回局部对象的引用或指针
const string &manip()
{
	string ret;
	if (!ret.empty())
		return ret; //错误!返回局部变量的引用
	else
		return "Empty"; //错误,"Empty"是局部临时量
}
//因为函数结束时临时对象占用的空间也随之释放,所以两条return都指向了不再可用的内存空间

引用返回左值

  • 调用一个返回引用的函数得到左值,其余返回类型得到右值
char &get_val(string &str, string::size_type ix) { return str[ix]; }
int main()
{
	string s("a value");
	cout << s << endl;
	get_val(s, 0) = 'A'; //将s[0]改为A
	cout << s << endl;
	return 0;
}
//返回值是引用,因此调用是个左值,所以能在赋值运算符左边

返回数组指针

  • 函数不能返回数组,但是能返回数组的指针或引用
typedef int arrT[10]; //arrT是类型别名,表示类型是含有10个整数的数组
using arrT = int[10]; //同上
arrT* func(int i); //返回一个指向含有10个整数的指针
  • 不使用类型别名则返回数组指针的函数形式是类型 (*函数(参数列表))[维度]
int (*func(int i))[10];
//func(int i)表示调用func函数时需要int实参
//(*func(int i))表示可以对函数调用结果执行解引用
//(*func(int i))[10]表示解引用后得到大小为10的数组
//int (*func(int i))[10]表示数组是int型
  • 也可以使用decltype
int arr[] = {1, 2, 3};
decltype(arr) * arrPtr(int i)
{
	...
	return &arr;
}
//注意decltype不负责把数组类型转换成对应指针,所以decltype的结果是数组
//要表示arrPtr返回的是指针还得再函数声明时加上*

函数重载

  • 拥有顶层const的形参无法和一个没有const的形参区分开来(之前说过,顶层const会被忽略)
int fun(int);
int fun(const int); //重复声明
int fun(int*);
int fun(int* const); //重复声明
  • 如果形参是某种类型的指针或引用,那就能通过指向的是常量还是非常量来实现重载(现在是底层const)
int fun(int&);
int fun(const int&); //新函数
int fun(int*);
int fun(const int*); //新函数
//const对象只能传递给const形参
//非常量能转换为const,所以上面4个函数都能用
//此时编译器优先选择非常量版本

重载和作用域

  • 在内层作用域中声明名字,将隐藏外层作用域中声明的同名实体
string read();
void print(const string &);
void print(double);
void fooBar(int ival)
{
	bool read = false; //隐藏了外层的read
	string s = read(); //错误,read现在是bool值
	
	void print(int); //隐藏了之前的print
	print("abc"); //错误,print(const string &)被隐藏了
	print(ival); //正确
}
  • C++中,名字查找发生在类型检查之前

默认实参

  • 一旦某个形参被赋予了默认值,它后面所有形参都必须有默认值
  • 局部变量不能作为默认实参

内联函数和constexpr函数

  • 一般吧规模较小、流程直接、调用频繁的函数定义为内联函数。在函数名前加上inline表示为内联
  • 因为内联函数的定义对编译器而言必须可见,所以要把内联函数的声明和定义都放在头文件中。
  • constexpr函数的返回类型及所有形参的类型都得是字面值类型,并且函数体中必须有且只有一个return
  • constexpr函数不一定返回常量表达式

函数匹配

void f();
void f(int);
void f(int, int);
void f(double, double = 3.14);
f(5.6);
  • 匹配第一步选定候选函数(与被调用函数同名且可见的函数)。4个f全部选上
  • 第二步根据实参选出可行函数(形参数量与调用提供的实参数量相同,且对应的类型相同或能转换成形参类型的函数)。选出f(int)f(double, double = 3.14),这两个都能使用一个实参调用
  • 第三步从可行函数中选择与本次调用最匹配的函数,即实参类型与形参类型越接近越好。这里选出f(double, double = 3.14)
  • 如果调用f(42, 2.56),就第一个实参来看会发现void f(int, int)更好,而第二个实参和void f(double, double = 3.14)更匹配。这时会发生二义性。

函数指针

  • 声明一个函数指针,用指针替换函数名即可
bool lengthCompare(const string &, const string &);
bool (*pf)(const string &, const string &); //未初始化
//赋值
pf = lengthCompare;
pf = &lengthCompare; //等价
//调用
bool b1 = pf("abc", "def");
bool b2 = (*pf)("abc", "def");
bool b3 = lengthCompare("abc", "def");
  • 不能定义函数类型的形参,但是形参可以是指向函数的指针
void f(const string &s1, const string &s2, bool pf(const string &, const string &));
void f(const string &s1, const string &s2, bool (*pf)(const string &, const string &));
//可以直接把函数作为实参用,它会自动转换成指针
f(s1, s2, lengthComapre);
  • 可以用类型别名简化使用函数指针
//func和func2是函数类型
typedef bool func(const string &, const string &);
typedef decltype(lengthCompare) func2; //等价上
//funcp和funcp2是函数指针
typedef bool (*funcp)(const string &, const string &);
typedef decltype(lengthCompare) *funcp2; //等价上
//decltype返回函数类型,加上*才是指针
void f(const string &, const string &, func); //自动将func转换成指针
void f(const string &, const string &, funcp2); //等价
  • 可以返回函数指针
using F = int(int*, int); //函数类型
using pf = int(*)(int*, int); //指针
pf f1(int); //f1返回指向函数的指针
f f1(int); //错误,不能返回函数
f *f1(int); //正确
int (*f1(int))(int*, int); //不用类型别名直接声明f1
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值