C++函数

函数

一个C++程序是由若干个源程序文件构成,而一个源程序是由若干个函数构成。函数将一段逻辑封装起来便于复用。

从用户的角度看,函数分成两种:

  • 库函数:标准函数,由C++系统提供。比如strcpy_s
  • 用户自定义函数:需要用户定义后使用

函数的组成部分:

  • 返回类型:一个函数可以返回一个值;
  • 函数名称:这是函数的实际名称,函数名称和参数列表一起构成了函数签名,函数签名才是函数被调用时使用的真正的名字;
  • 参数:参数列表包含函数参数的类型、顺序、数量。参数是可选的,也就是说函数可能不包含参数。

函数重载(overload)与签名

  • int test(int)
  • int test(double a)
  • int test(int a,double d)

以上三个函数构成函数重载:函数名称一样,但是函数参数不一样。

程序内部存储的是函数签名,正是由于函数签名不同,编译器才会区分出这些程序。

指向函数的指针和返回指针的函数

int(*p)(int);
//一个指针p,指向参数是int、返回值是int类型的函数  
int test(int index);
p = test;
int result = (*p)(1);

每一个函数都占用一段内存单元,它们有一个一个起始地址,指向函数入口地址的指针称为函数指针。
一般形式:数据类型(*指针变量名)(参数列表),数据类型表示函数返回的数据类型
比如:int(*p)(int)

我们要区分返回指针的函数的区别

  • int(*p)(int):指针,指向一个函数的入口地址
  • int* p(int):函数,返回值是一个指针
int MaxValue(int x,int y)
{
	return (x>y)?x:y;
}

int MinValue(int x,int y)
{
	return (x<y)?x:y;
}

int add(int x,int y)
{
	return x+y;
}

bool ProcessNum(int x,int y,int(*p)(int a,int b))  //函数指针仅仅把函数传递进来
{
	cout<< p(x,y) << endl;  //这个时候才真正调用了函数
	return true;
}

int main()
{
	int x = 10;
	int y = 20;
	//三个函数的返回值和参数列表都一样,所有都可以使用函数名传递
	cout<<ProcessNum(x,y,MaxValue)<<endl;  //参数是函数指针,直接传递函数名即可
	cout<<ProcessNum(x,y,MinValue)<<endl; 
	cout<<ProcessNum(x,y,Add)<<endl; 
	
}

上述例子中bool ProcessNum(int x,int y,int(*p)(int a,int b))中的参数是个函数指针,我们传递进去函数名后,真正调用函数的地方在这个函数体内部,我们把这种调用的方式称为回调函数。我们无法控制函数什么时候调用,我们仅仅把函数给他,由它决定什么时候使用。

命名空间

软件开发过程中有可能会有多个程序员开发了相同函数签名的函数。就像一个班级里出现了同名的人。这时候可以用到命名空间的概念。

命名空间可以作为附加信息来区分不同库中相同名称的函数、类、变量等。命名空间即定义了上下文。本质上命名空间就是定义了一个范围。

关键词:using和namespace

//声明
int test(int a);

namespace mytest
{
	int test(int a);
}


//定义
int test(int a)
{
	return a;
}

namespace mytest
{
	int test(int a)
	{
		return a+1;
	}
}


//调用
int(*p)(int);
p = test;
int result = (*p)(1);

result = mytest::test(0);

内联函数

如果一个函数是内联的,那么在编译时,编译器会把该函数的代码副本放置在每个调用该函数的地方。

inline int MaxValue(int x,int y)//求最大数
{
	return (x>y)?x:y;
}

引入内联函数的目的是为了解决程序中函数调用的效率问题,即用空间换时间。在以往的传统函数调用的过程中涉及到了call的过程,这里的汇编代码相对比较复杂。我们可以节省成本,直接把函数的执行体拿过来,copy到调用的位置,而把函数的参数、压栈出栈的过程全部忽略掉。

注意内联函数内部不能有太复杂的逻辑,编译器有时候会有自己的优化策略,所以内联函数不一定起作用。

在VS编译器中,右键项目属性,找到C/C++栏中的高级设置,会有调用约定选项。默认的是_cdecl方式,即C语言的调用方式。这种方式参数是从右到左的进栈过程,这些参数都存在栈内。我们可以换其他方式,这时候会有一些变化。

递归调用

数学归纳法

数学归纳法是整明当n等于任意一个自然数时某命题成立。整明步骤分2步:

  1. 整明当n=1时命题成立;
  2. 假设当n=m时命题成立,那么可以推导出在n=m+1时命题也成立(m代表任意自然数)

经典问题:斐波那契数列

1,1,2,3,5,8,13,21,34……
Fib(n) = 1            n=1、2
   = Fib(n-1)+Fib(n-2)     n>2

int Fib(int n)
{
	if(n == 0)
		return 0;
	else if(n == 1)
		return 1;
	else
		return Fib(n-1)+Fib(n-2);		//函数自己调用自己
}

递归调用

函数自己调用自己称为递归调用。这个调用自己不是真的调用自己,而是参数有递进,不然就会是死循环了。
递归的四个基本法则:

  1. 基准情形:要有无须递归就能解出的情况(斐波那契数列问题中就是当n=0或1的时候,这时候不需要递归调用也能返回),这是递归退出的条件;
  2. 不断推进:每一次递归调用都必须使求解状况朝接近基准情形的方向推进;
  3. 设计法则:假设所有的递归调用都能运行;
  4. 合成效益法则:求解一个问题的同一个实例时,切记不要进行重复性工作,这就牵扯到下面所说的递归的问题

递归的问题

斐波那契数列求解的过程会有问题。递归运算中有大量的重复运算,对计算机有大量时间和空间上的浪费。这种算法在工程中是有很大的问题。n比较小的时候程序还能正常运行,一旦n值非常大,函数栈会变得极其复杂(类似于前序访问二叉树),整个程序就面临很大的问题甚至崩溃。
在这里插入图片描述
由此可见,使用递归来计算此类注入斐波那契数列问题并不是好主意。

递归的优化

递归是一种重要的编程思想,很多算法都包含这种思想(如归并排序),但是递归有/缺陷:

  • 空间上需要开辟大量的栈空间
  • 时间上可能需要大量重复运算

递归的优化:

  • 尾递归:所有递归形式的调用都出现在函数的末尾;
  • 使用循环替代;
  • 使用动态规划,空间换时间。
//循环优化
//用一个循环等效于递归,拿一个中间变量来存储上次的值。避免了递归调用中不停的调用函数栈
int Fib2(int n)
{
	if(n<2)
		return n;
	
	int n0 = 0,n1 = 1;
	int temp;
	for(int i=2;i<=n;i++)
	{
		temp = n0;
		n0 = n1;
		n1 = temp + n1;
	}
	return n1;
}
//尾递归优化
//仍然使用递归,但是递归仅在函数的末尾发生,即递归推进的过程放在最后完成
//传统递归中return的是个表达式,而这个尾递归中return的是个函数调用。所以传统递归的堆栈很复杂,需要保存多个函数的堆栈
//尾递归并没有保存太多的堆栈信息,编译器可以进行优化。
//从汇编层面来看,因为是最后一行代码做递归调用,寄存器没有必要把前面的空间信息进行保存。
//汇编代码中可以看到编译器把这个递归优化成了一个循环操作
int Fi3(int n,int ret0,int ret1)
{
	if(n == 0)
		return ret0;
	else if(n == 1)
		return ret1;
	
	return Fi3(n-1,ret1,ret0+ret1);
}
//动态规划优化
//从传统递归可以看到我们进行了大量的重复计算,那么我们完全可以把第一次计算出来的值存起来
//后面再遇到这个计算直接查表即可

int myArr[1000];   //全局的数组,记录前1000个值。在全局区中默认初始化为全0
int Fi4(int n)
{
	myArr[0] = 0;
	myArr[1] = 1;
	for(int i=2;i<=n;i++)
	{
		if(myArr[i] == 0)  //没有被初始化
		{
			myArr[i] = myArr[i-1]+myArr[i-2];
		}
	}
	return myArr[n];
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值