C++ primer ----函数(模糊知识点)

含有可变形参的函数

有的时候,我们传入的参数的个数是不确定的,这个时候就需要使用到省略符形参initializer_list。
initializer_list是标准库类型,在该类型中的每一个元素都是常量,省略符形参只能出现在形参列表中的最后一个位置。
例如

void print(initializer_list<int> li) // 正确,函数中只有一个形参
{
	for(auto &i : li) // 由于省略符形参也是模板类型,支持范围for循环
	{
		cout<<i<<endl;
	}
}
void print(initializer_list<int> li,int i) // 错误,省略符形参只能在形参列表的末尾
{
	// 操作
}

initializer_list提供的操作

initializer lst; // 默认初始化,空列表
initializerlst{a,b,c,…} // 列表的大小和初始值一样多,并且列表中的值都是const类型
lst2(lst)
lst2 = lst // 这两个只拷贝对象,但是不会拷贝列表中的数值,原始列表和拷贝后的对象共享
lst.size()
lst.begin()
lst.end()

不要返回局部指针或者引用

因为局部变量在函数执行结束后就被释放了,返回后的指针或引用也被释放,再次调用则报错

返回数组指针

因为数组是不能被返回的,但是我们可以通过指针的形式来返回一个数组的指针。

using arrt = int[10]; // 等价于 typedef int arrt[10];
arrt* func(inti); // 表示返回一个含有10个整数的数组的指针

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

int arr[10];
int *p1[10]; // 表示p1含有10个整型指针的数组
int (*p)[10] = arr; // p2是一个指针,指向含有10个整数的数组

在阅读代码的时候我们从内往外读取:
如 int (*fun(int i ))[10];

fun(int i )// 表示一个函数
(*fun(int i )); // 表示函数返回值可以解引用
(*fun(int i ))[10] // 表示该函数解引用后得到一个大小为10的数组
int (*fun(int i ))[10] // 表示解引用后该数组是整数

使用decltype

如果我们一开始就知道函数返回的指针指向哪一个数组,那么可以直接使用decltype来声明返回类型

int arr[]= {1,2,3,4,5};
int arr2[] = {5,6,7,8,9};
decltype(arr) * printf(int i) // 表示的是返回一个指向5个整数的数组,不一定说是返回arr,只是返回arr的类型
{
	return (i % 2) ? arr : arr2;
}

尾置返回类型

如果我们要求的函数返回类型太长,我们可以使用 -> 来将返回类型后置,空出来的部分使用auto来弥补。

auto func(int i ) -> int (*)[10];

函数的重载

函数重载的注意事项:

  • 函数重载的时候只能够形参列表不同,依据返回类型不同不能发生重载
  • 函数重载的时候会寻找一个最佳的匹配,如果找不到不匹配则编译器会报错,并且在函数重载的时候要避免二义性

函数重载与作用域

在不同的作用域中不能够重载函数,因为内层作用域的函数声明会屏蔽掉外层的重载函数。

string read();
void print(int i ,int u);
void print(char a,char b);
void text()
{
	void print(int i ); // 产生新的作用域,屏蔽了外层的重载函数
	print(1,2); // 错误,因为欸外层的作用域被屏蔽了,所以只能调用print(int i )
	bool read = false; // 新作用域,屏蔽外层read
	string s = read(); // 错误,read是一个bool变量,不是函数 
}

函数匹配

函数发生重载的之后,编译器会找到最佳的精确的重载函数执行。
每一个重载函数都可以称为候选函数,而与之精确匹配的函数称之为可选函数。

函数重载的选择:

  • 不发生隐式转换的优先
  • 数组转化成指针
  • 含有顶层const或删除顶层const

雷区:

 void man(long i);
 void man(float);
 man(3.14);  // 错误,产生二义性

这两个在使用的时候,由于隐式转换等级相同,double既可以转换成float也可以转换成long 所以产生二义性

const传参
void look(const int i); // 标记为1
void look(int i); // 标记为2
const int a =1;
int b = 2;
look(a); // 执行1中的函数
look(b); // 执行2中的函数

在同时含有形参的区别是否为const的时候,如果传入的实参是const类型则执行含有const的形参的函数,否则优先执行没有const形参的函数。
但是只有const形参的时候,也可以传入非const类型的实参,只是将int转化成 const int

雷区:
函数重载的时候不能引发二义性,否则编译器报错
二义性的产生:传入的各各形参在不同的重载函数中都能够精确的匹配,这样子的话就产生了二义性

int f(int i,int u);
int f(double i,double u); // 声明两个重载函数
f(4,3.5); // 调用函数,但是引发错误

因为第一个参数是int类型与第一个 f 的形参精确匹配,第二个参数的类型是double,与第二个 f 的形参精确匹配,导致编译器不知道应该调用哪一个,所以导致二义性

默认实参

再写入默认实参的时候,默认部分后面的参数也要进行初始化

void print(int i = 0,int u) // 错误,因为u在i的后面,i初始化后,u也必须初始化
{
	// 操作
}

void print(int u,int i = 0) // 正确
{
	// 操作
}

tips:尽量将不需要默认参数的形参放在前面

内联函数和constexpr函数

当我们在调用函数的时候,在系统中发生了很多的步骤,如先保存数据到寄存器,之后在恢复,这个过程增加了系统的开销,为了减小系统的开销,我们常常使用内联函数。

使用inline关键字声明内联函数 比如:

inline void print(int i ,int u)
{
	return (i<u) ? i : u;
}
void main()
{
	cout<<print(1,2)<<endl;
}

如果调用该函数的时候,直接在编译器中展开成

	cout<< (1<2) ? 1 : 2<<endl;

一般来说,内联函数用来优化规模较小,流程直接的函数。

constexper函数是返回一个表达式常量,要求在这个函数中返回值只能是一个字面值,以及只有一个返回值。

调试帮助

assert预处理宏

assert做为预处理宏,使用一个表达式做为他的条件。
asser(expr);

assert在使用的时候需要包含 cassert头文件,在执行的时候优先执行括号里面的表达式,如果为0则终止程序,如果非0,则不做任何处理。

#incldue <cassert>
int main(int argc,char **argv)
{
	cout << "程序执行" << endl;
	assert(0);
	cout << "并没结束" << endl;
}

执行结果
assert一般用于检验确实不可能发生的事

NDEBUG 预处理变量

assert依赖于NDEBUG的状态,如果NDEBUG被定义则不执行assert命令,一般默认情况下NDEBUG是未定义的。使用#define NDEBUG 来定义。
使用#ifndef和#endif 来检查NDEBUG是否被定义。

void print()
{
	#ifndef NDEBUG
		cerr<<__func__<<endl; // __func__ 返回的是当前执行的函数的名称-> print
	#endif
}

这段代码主要是检测NDEBUG是否被定义,如果没有被定义,则输出函数的名称。
预处理器定义了5个比较常用的程序调式的名字

  • _FILE_ 存放文件名的字符串的字面值
  • _LINE_ 存放当前程序行号的整型字面值
  • _TIME_ 存放文件编译时间的字符串的字面值
  • __DATE__存放文件编译日期的字符串字面值
  • __func__存放当前执行的函数名称

函数指针

函数指针指向的是函数,而不是具体的对象。

bool comparestring(string &s,string &s2); // 比较两个字符串的大小

创建一个函数指针

bool (*pf)(string&,string&); // 未初始化
/*pf旁边的两个括号不能少,如果少了括号,则表示声明了一个返回bool指针的函数*/

使用函数指针

bool b1= pf(“hello”,“goodbye”); // 调用函数
函数指针可以指向0,但是不能指向和自己不同的函数类型
int comparnum(int i ,int u);
pf = comparnum; // 错误,不同的数据类型
pf = 0; // 正确

函数指针形参

函数指针跟数组一样不能直接做为实参传递,但是能够以指针的形式进行传递

void print(striing s1,string s2,
			bool pf(string&,string&)); // 最后一个参数是传递函数指针

为了方便写入,可以使用decltype + typedef的形式另起一个名字

typedef decltype(comparestring) func; // 函数类型
typedef decltype(comparestring) *func // 创建函数指针
void useexper(string &,stribg&,*func); // 传入函数指针

返回函数指针的指针

把函数指针当成自定义的类型进行返回即可

using F= int(int *,int ); // 创建别名,普通函数类型
using PF = int(*)(int*,int); // 指针函数
PF f(int ); // 正确,可以返回指针函数类型
F f(int ); // 错误,不能够返回函数
F *f(int); // 正确,显式返回指针函数
/*等价于  
int (*f1(int))(int *,int);
这句代码需要从内到外解析,f1是一个函数,前面有一个*,所以f1返回一个指针,由于本身具有形参列表,所以指针指向函数,该函数的返回类型是int
*/

尾置返回类型

auto f1(int) -> int(*)(int *,int);

如果一开始我们就明确我们需要返回的指针函数类型,那么可以直接使用auto或者decltype

decltype(comparestring )*getfun(const string&);

需要注意的是:decltype作用与某一个函数的时候返回的是一个非指针的函数类型,我们需要显式的将*加上,表示是一个指针函数

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值