(六)函数

基础

函数

函数编写

  • 执行函数时首先定义并初始化形参,当遇到renturn语句时参数结束执行过程。返回retur语句中的值并将控制权从被调函数转移回主调函数。
 returntype functionname(para1,para2,...){statement}
  • 函数形参列表不能省略,可以写为
void f1(){}
void f2(void){}  //与C语言兼容
int fact(int val){
	int ret = 1;
	while(val>1){
		ret*=val--;
	}
	return ret;
}

函数调用

  • 使用调用运算符,形式是一对圆括号,作用与一个表达式,该表达式时函数或指向函数的指针。圆括号之内是用逗号隔开的实参列表。
  • 调用主要实现实参初始化函数的形参,并将控制权转移给被调用函数。
int main(){
	int j = fact(5);
	cout<<j<<endl;
	return 0;	
}

局部对象

  • 名字有作用域,对象有生命周期。
  • 局部变量指形参和函数体内部定义的变量。分为自动对象局部静态对象
  • 自动对象指函数经过变量定义时创建,到达定义所在尾块时销毁。
  • 局部静态对象指变量生变周期贯穿函数调用以及之后的时间。第一次经过变量定义时初始化,知道程序终止才销毁。
int size_calls() {	
	static int cnt = 0;  //只初始化一次
	return ++cnt;		 
}

int main() {
	for (int i = 0; i < 5; ++i) {
		cout << size_calls() << endl;  //输出1 2 3 4 5
	}
	return 0;
}

函数声明

  • 函数声明也称函数原型,函数名字需要在使用之前声明,函数声明无需函数体,用分号替代。函数声明包含函数三要素(返回类型、函数名、形参类型),描述函数接口。
  • 建议函数在头文件中声明。
void print(vector<int>::const_iterator beg,vector<int>::const_iterator end);

参数传递

  • 引用,实参被引用传递或函数被传引用调用
  • 拷贝赋值,实参被值传递或函数被传值调用

传值

  • 对传入形参的改动不影响实参初始值;
  • 传入指针时,若形参指针地址内的值发生改变,可以改变对应指针地址里的值。实参指针地址不会发生改变。
int count_prtcalls(int* n) {
	int ans = 1;
	for (int i = 1; i <= *n; ++i) {
		ans *= i;
	}
	cout <<"n:" << ans << endl; //1
	++n;
	ans = 1;
	for (int i = 1; i <= *n; ++i) {
		ans *= i;
	}
	cout <<"++n:" << ans << endl; //2
	cout << "++n:" << *n << endl;//2
	*n = 100;
	cout << "++n:" << *n << endl;//100
	return ans;
}

int main() {
	int n[] = {1,2,3,4,5};
	int* p = n;
	count_prtcalls(p);
	cout << *p << endl; //仍然指向1
	++p;
	cout << "++p:" << *p << endl;  //也变为100
	return 0;
}

传引用

  • 通过引用传参,可以实现多个实参值改变;
  • 对于大的类类型对象以及不支持拷贝的对象,建议通过引用形参的访问该类型对象。
  • 当无需改变对象内容时,可以定义成常量引用。
//统计string中字母a出现次数以及位置
void alphacount(int& cnt, vector<int>&pos, const char& c, const string& s)
{
	for (decltype(s.size()) i = 0; i < s.size(); ++i) {
		if (s[i] == c) {
			++cnt;
			pos.push_back(i);
		}
	}
}
int main() {
	string s;
	cout << "input string: ";
	cin >> s;
	int cnt = 0;
	vector<int>pos;
	char c = 'a';
	alphacount(cnt, pos, c, s);
	cout << "count:"<<cnt << endl;
	cout << "position:";
	for (decltype(pos.size()) i = 0; i < pos.size(); ++i) {
		cout << pos[i] << " ";
	}
	cout << endl;
	return 0;
}

const形参与实参

  • 形参是顶层const时,实参初始化形参会忽略掉顶层const。顶层const可以忽略,注意程序编写时重复定义的问题。
void fcn(const int i)
void fcn(int i) //重复定义
  • 对于形参是指向const类型的引用或指针时(底层const),常不能初始化非常。
  • 建议把不会改变的形参定义为常量引用,一方面提醒调用者该形参不能更改,另一方面可以将const对象、字面值以及需要类型转换的对象传给形参。

数组传参

  • 数组特点:不允许拷贝,使用通过指针调用。
void print(const int* arr);//数组元素只读不写
void print(int* arr);//数组元素需要读写
void print(int(&arr)[10]);//数组的引用,引用的是指针,不是数组所以要加括号
void print(const int arr[10]); //维度表示实际希望数组元素数,可不填
void print(const int(*arr)[10]int rowsize);//int**,指向含有10个整数数组的指针

数组尺寸

  • 标记指定:如字符串数组里的’\0‘。
  • 传递首和尾后指针。

函数形参可变

编写能处理不同数量实参的方法:

  • 实参类型相同:传递标准库类型initializer_list
  • 实参类型不同:编写可变参数模板
  • 省略符

initializer_list形参

  • 实参数量未知,全部实参类型相同。
  • 头文件:
#include<initializer_list>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           
  • 操作
initializer_list<T> lst; //类型T的空列表
initializer_list<T> lst{a,b,c,...}; //lst的元素是对应初始值的副本,列表中的元素是const
lst2(lst) //拷贝或赋值initializer_list对象,不拷贝列表里的元素,拷贝后原始列表和副本共享元素
list.size()
lst.begin()
lst.end()
  • 定义时,需要说明列表所含元素类型
  • initializer_list对象中元素永远是常量值,不能改变。
  • 可以用begin()和end()获得指针,来进行列表内数据处理
void error_msg(int i, initializer_list<string>ls) {
	cout << i << ":" << endl;
	for (auto i = ls.begin(); i!=ls.end(); ++i) {
		cout << *i << endl;;
	}
}
void error_msg(int i, initializer_list<int>ls) {
	int sum = 0;
	cout << i << ":" << endl;
	for(auto i = ls.begin(); i != ls.end(); ++i) {
		sum += *i;
	}
	cout << sum << endl;
}

int  main() {
	error_msg(1, { "apple","hhdy","zwhy" });
	cout << endl;
	error_msg(2, {1,2,3,4});  
	return 0;
}

省略符形参

  • 仅用于C和C++通用类型,便于C++访问某些特殊C代码而设置,使用了名为varargs的C标准库功能。
  • 大多数类类型对象在传递给省略符形参时都无法正确拷贝。
  • 省略符形参只能出现在形参列表的最后一个位置
void foo(parm_list,...);
void foo(...);

返回类型和return语句

return

  • 中止当前正在执行的函数并将控制权返回调用该函数的地方
  • 无返回值函数可以隐式推出。有返回值的函数只能通过一条有效return函数退出,函数循环后也要加return。
return; //void类型
return expression; //void类型可以用来返回void函数,或非void类型

调用运算符

  • 优先级:调用运算==点运算符==箭头运算符。

返回函数引用

  • 返回函数引用是左值,返回其他类型是右值。
char &val(string &str,string::size_type index) {
	return str[index];
}
int  main() {
	string s("apple");
	val(s, 0) = 'A';
	cout << s << endl; //Apple
	return 0;
}
  • 不要返回局部对象的引用或指针,局部字面值也不可以(向当局局部临时对象)。
const string &val(string& n) {   //想返回局部对象的引用
	string str= "hhdy";
	if(str == "hhdy"){
		return str; //局部对象的引用,漏!
	}
	return "hhdy";  //漏!
	return n; //ok
}

列表初始化返回值

  • 可以返回花括号包围的列表,类似列表初始化。
  • 对于内置类型,列表内最多包含一个值,所占空间小于目标类型空间。对于类初始化,看类初始化中列表初始化方法。
vector<int> getval(){
	return{1,2,3};
}
int main(){
	vector<int>ans = getval();//相当于{1,2,3}
	return 0;
}

主函数main返回值

  • 允许main没有return语句直接结束,会隐式加上“return 0;”。
  • main函数返回值可以看做是状态指示器。若像使返回值和机器无关,cstdlib头文件定义了两个预处理变量:
return EXIT_FAILURE; //1
return EXIT_SUCCESS; //0

递归

  • main 函数不能调用自己

返回数组指针

指向数组的指针

//类型别名using
int arr[10];
using arrT = int[10];
arrT* p = &arr;
//类型别名 typedef
typedef int arrT1[10];
arrT1* p1 = &arr;
//不使用类型别名
auto p2 = &arr;
int (*p3)[10] = &arr;
decltype(arr)*p4 = &arr;

类型别名返回数组指针

//类型别名using
using arrT = int[10];
arrT* func(int i);
//类型别名 typedef
typedef int arrT[10];
arrT* func(int i);

不使用类型别名返回数组指针

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

函数重载

定义

  • 同一作用域内存在几个函数名字相同但形参列表不同(包括形参数目及类型),称为函数重载
  • main函数不能重载

const形参

  • 区分重载和重复声明,对于const类型,顶层const形参与没有顶层const的形参区分不开。底层const可以区分开。
//这两个会报错,redefination
void foo(const int a){}
void foo(int a){}   
//这两个不会
void foo(const int*p){}
void foo(int *p){} 
//这两个会报错
void foo(int *const p){}
void foo(int *p){}

const_cast

  • 可以使用const_cast实现常量引用到非常量引用的重载。
const string &shorterString(const string &s1, const string &s2) {
	return s1.size() <= s2.size() ? s1 : s2;
}

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

int main() {
	const string s1 = "11";
	const string s2 = "222";
	string s3 = "33";
	string s4 = "444";
	string &s5 = shorterString(s4, s3);
	const string &s6 = shorterString(s1, s2);
	cout << s5 << endl;  //33
	cout << s6 << endl; //11
}

函数匹配

  • 重载调用的函数选择方法

第一步,找重载函数集,与被调用函数同名且调用点可见其声明。
第二步,找可行函数,实参数目与形参相等,且类型相同或能转换。注意由默认实参时,输入实参数量可以小于形参数量。
第三步,寻找最佳匹配,实参形参越接近则匹配的越好。如果出现实参存在两个最优匹配重载函数,则会报告二义性,拒绝其请求。

  • 实参到形参转化优先级

1精确匹配:类型相同、数组函数转到相应指针、添加或删除实参顶层const
2const转换实现匹配
3类型提升
4算数类型、指针转换
5类类型转换

  • 对于类型提升,所有算数类型转换级别一样高
void func(long);
void func(float);
func(3.14)  //3.14是double型,转成long和float级别一样高,出现二义性

重载作用域

  • 如果在内层作用域中声明名字,它将隐藏外层作用域中声明的同名实体。不同作用域中无法重载名字。
string read(string s);
void print(const string &s);
void print(double);
int main() {
	bool read = false;
	void print(int);
	print(1);
	print("s"); //输入应为int型
	string s = read("s"); //read被重载为bool型
}

默认实参

  • 为了使窗口既接纳默认值,也能接纳用户指定的值,可以对形参进行默认值定义。
  • 可以为多个形参进行默认值定义,但一旦形参被赋予默认值,后面所有形参都必须有默认值。
  • 对于省略的形参,会使用默认实参
  • 局部变量不能作为默认实参。只要表达式类型能转换成形参所需的类型,该表达式就能作为默认实参。
int val(int n, int m = 4, int k = 1) {
	return n * m * k;
}

int main() {
	int ans = val(8);        //32
	int ans1 = val(1, 2);    //2
	int ans2 = val(1, 5, 6); //30
	cout << ans << " " << ans1 << " " << ans2 << endl;
}
  • 应在函数声明中指定默认实参
int val(int n, int m = 4, int k = 1);

内联函数

  • 考虑到函数调用一般比求等价表达式的值慢一些,可以使用内联函数避免函数调用的开销
  • 内联机制用于优化规模小、流程直接、频繁调用的函数。很多编译器不支持内联递归函数,对函数长度也有要求。
  • 内联只是向编译器发出一个请求,编译器可以选择忽略这个请求。
  • 通常定义在头文件内
inline int val(int n, int m = 4, int k = 1) {
	return n * m * k;
}

constexpr函数

  • 指能用于常量表达式的函数
  • 返回类型及所有形参必须是字面值类型
  • 函数体中必须只有一条return语句,只进行一次返回
  • constexpr在编译过程中,被隐式指定为内联函数
  • constexpr函数不一定返回常量表达式
  • 通常定义在头文件内
constexpr int new_sz() {
	return 42;
}
int main() {
	constexpr int n = new_sz();
	return n;
}

调试帮助

用于调试代码

assert预处理宏

  • assert作为预处理宏,是预处理变量(不需要std::,不需要using声明所在空间),宏名字在程序内唯一。
  • 通常用来检查"不能发生"的条件。
  • 头文件
#include<cassert>
  • 如果表达式为假(0),assert输出信息并终止程序运行;如果表达式为真(非0),assert什么也不做。
assert(expr);
int new_sz(int i) {
	assert(i<100);
	if (i > 9)
		return 42;
	else
		return 10;
}
int main() {
	int n = new_sz(101);
	return n;
}

在这里插入图片描述

NDEBUG预处理变量

  • assert行为依赖名为NDEBUG的预处理变量状态,定义了NDEBUG,assert什么也不做;没有定义,执行检查。
  • 通过预定义NDEBUG可以关闭assert调试。

#define NDEBUG

#define NDEBUG  //整个程序最开头写
#include<iostream>
#include<cassert>

using namespace std;
int new_sz(int i) {
	assert(i<100);
	if (i > 9)
		return 42;
	else
		return 10;
}
int main() {
	int n = new_sz(101);
	return n;  //返回42
}
  • 也可以使用NDEBUG编写自己的代码调试,如果未定义#NDEBUG,执行#ifndef到#endif之间的程序。如果定义了#NDEBUG则忽略#ifndef到#endif之间的程序。
//#define NDEBUG
#include<iostream>
#include<vector>
#include<string>
#include<cassert>

using namespace std;
void new_sz() {
#ifndef NDEBUG
	cerr << __func__ << " have error at " << __LINE__ 
		<<" in "<<__FILE__<<endl
		<< "compile on " << __DATE__ << " at " << __TIME__ << endl;
#endif
}
int main() {
	new_sz( );
	return 0;
}

在这里插入图片描述

注:c++编译器定义:
__func__ //当前调试函数
__FILE__ //当前文件名
__LINE__ //当前行号
__TIME__//文件编译时间
__DATE__//文件编译日期

函数指针

  • 指向函数的指针。类型由返回类型和形参类型共同决定。

  • 首先创建一个函数:

bool lengthCompare(const string &,const string &){}

声明方式

  • 用指针替换函数名进行该函数声明
bool (*pf)(const string &,const string &); //指向返回值是bool类型函数的指针
bool *pf(const string &,const string &); //返回值是bool类型指针

使用

  • 把函数名作为一个值使用,该函数自动转换成指针。指向不同函数类型或形参类型数目不同时,不存在转换规则。
pf = lengthCompare;
pf = &lengthCompare;

函数调用

  • 使用指向函数的指针调用该函数,无需提前解引用指针:
bool b1 = pf("bad","apple");
bool b2 = (*pf)("bad","apple");
bool b3 = lengthCompare("bad","apple");

函数指针作为形参

  • 形参可以是指向函数的指针
bool lengthCompare(const string& s1, const string& s2) {
	return s1.size() > s2.size() ? 1 : 0;
}

bool len(const vector<int>& n1, const vector<int>& n2, bool pf(const string& s1, const string& s2)) { //函数指针的声明可以在函数里
	cout << pf("abdee", "cdfr") << endl;  //可以直接在函数里使用函数指针
	if (pf("ab","cdfr"))
		return n1.size() > n2.size() ? 1 : 0;
	else
		return 1;
}
bool (*pf)(const string&, const string&);
bool (*pf1)(const vector<int>& n1, const vector<int>& n2, bool pf(const string& s1, const string& s2));

int main() {
	pf = lengthCompare;//实际调用可以使用函数名传参进去,他会自动转化为指针
	pf1 = len;
	bool b1 = pf("bad", "apple");
	bool b2 = (*pf)("bad", "apple");
	bool b3 = lengthCompare("bad", "apple");
	bool b4 = pf1({ 1,2 }, { 1,2,3 }, pf); 
	cout << b1 << " " << b2 << " " << b3 <<" "<<b4<< endl;
	return 0;
}

返回指向函数的指针

  • 使用类型别名
using F = int(int*,int);
using PF = int(*)(int*,int); 
typedef int(*PFF)(int*,int);
typedef int(FF)(int*,int);

PF f1(int); //PF是指向函数的指针,f1返回指向函数的指针
F *f1(int);
PFF f1(int);
FF* f1(int i)
int (*f1(int))(int*,int);
  • auto
auto f1(int) ->int(*)(int*,int);
  • decltype
//当已知指向函数的类型时
int PF1 (int* a, int b)
//可以使用decltype
decltype(PF1)* f1(int i)
using namespace std;
using F = int(int*, int);
using PF = int(*)(int*, int);
typedef int(*PFF)(int*, int);
typedef int(FF)(int*, int);

int PF1 (int* a, int b){
	return 1;
}


int  PF2(int* a, int b) {
	return 2;
}



PF f1(int i) {
	if (i == 1)
		return PF1;
	else
		return PF2;
} 
F* f2(int i) {
	if (i == 1)
		return PF1;
	else
		return PF2;
}
PFF f5(int i) {
	if (i == 1)
		return PF1;
	else
		return PF2;
}

FF* f6(int i) {
	if (i == 1)
		return PF1;
	else
		return PF2;
}

int (*f3(int i))(int* a, int b) {
	if (i == 1)
		return PF1;
	else
		return PF2;
}
auto f4(int i) ->int(*)(int* a, int b) {
	if (i == 1)
		return PF1;
	else
		return PF2;

}
decltype(PF1)* f7(int i) {
	if (i == 1)
		return PF1;
	else
		return PF2;
}

int main(){
	int a = 1;
	int* b = &a;

	cout << f1(1)(b, a) << endl;
	cout << f2(1)(b, a) << endl;
	cout << f3(1)(b, a) << endl;
	cout << f4(1)(b, a) << endl;
	cout << f5(1)(b, a) << endl;
	cout << f6(1)(b, a) << endl;
	cout << f7(1)(b, a) << endl;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值