(三)C++基础之函数的一些新特点

1.1、内联函数

c 语言中有宏函数的概念。宏函数的特点是内嵌到调用代码中去,避免了函数调用的开销。但是由于宏函数的处理发生在预处理阶段,缺失了语法检测和有可能带来的语意差错。
比如:

#include <stdio.h>

#define MAX(a,b) \
	((a)>(b)?(a):(b))	

int main(void)
{
	int a = 48;
	int b = 57;
	int c = 0;

	c = MAX(a,b);
	printf("c = %d\r\n",c);
	return 0;
}

运行结果:
在这里插入图片描述
如上面代码我们使用C语言的宏函数来进行大小的比较,一般情况下都是没有什么问题,但是如果传入的参数为:

c = MAX(a++,b++);

那么宏定义展开之后:

((a++)>(b++)?(a++):(b++))

可是我们的目的在比较完后进行参数自增一,因为这里b大于a,所以起码自增2了,因为比较和返回都是b++,所以使用宏定义就特别注意这些细节问题,因为在预编译阶段他是不会替你做这些事情,只是一股脑的展开。
于是基于这些,C++中使用了一种内联函数的定义,该定义和宏函数特别相似,但是不同的是宏函数在预编译阶段,内联函数是在编译阶段,所以就不会产生宏定义那样的错误。

C++提供了  inline 关键字,实现了真正的内嵌,在这之前不管是宏函数还是内联函数,其实最终的目的是为了避免反复出栈和压栈,利用存储节约时间的开销。

#include <iostream>

using namespace std;

inline void printA(void)
{
	cout << "a" << endl;
}

int main(void)
{
	for (int i; i < 1000; i++){
		printA();
	}
	return 0;
}

其实使用inline也是有一些特点的。
1)内联函数声明时inline关键字必须和函数定义结合在一起,否则编译器会直
接忽略内联请求。

#include <iostream>

using namespace std;

inline void printA(void);

int main(void)
{
	for (int i; i < 1000; i++){
		printA();
	}
	return 0;
}

void printA(void)
{
	cout << "a" << endl;
}

这样申明和定义分离,所以最终编译器就会忽略内联要求了。
2) C++编译器直接将函数体插入在函数调用的地方。
在这里插入图片描述
如圈出来的部分,内联函数直接插入调用的地方。
3)内联函数没有普通函数调用时的额外开销(压栈,跳转,返回)。
4)内联函数是一种特殊的函数,具有普通函数的特征(参数检查,返回类型等) 。 
5)  内联函数由  编译器处理,直接将编译后的函数体插入调用的地方,
宏代码片段  由预处理器处理,  进行简单的文本替换,没有任何编译过程。

1)基于以上五个特点,那么对于内联函数也是有一些限制的
2)不能存在任何形式的循环语句     
3)不能存在过多的条件判断语句 
4)函数体不能过于庞大 
5)不能对函数进行取址操作 
6) 函数内联声明必须在调用语句之前
7)编译器对于内联函数的限制并不是绝对的,内联函数相对于普通函数的优
势只是省去了函数调用时压栈,跳转和返回的开销。因此, 当函数体的执行开
销远大于压栈,跳转和返回所用的开销时,那么内联将无意义。

1.2、默认参数和占位参数

一般如下代码编写,因为函数没有传递参数,导致编译器报错。
在这里插入图片描述
那么在C++中是否能解决呢,有的,可以使用默认参数,也就是定义时候如下:

void printA(int a = 89)
{
	cout << "a:" << a<< endl;
}

其中a = 89,就是默认给a一个参数,也就是默认参数,这样就可以进行编译通过了。
在这里插入图片描述
其中需要注意一点,占位参数是从右到左的,什么意思呢,你不能如下面这样写:

void printA(int a = 89,int b)
{
	cout << "a:" << a<< endl;
}

就是默认参数在左边,必须将默认参数放到后面:

void printA(int b, int a = 89)
{
	cout << "a:" << a<< endl;
}

其实我们可以想一下,为什么默认参数放在右边,如果放在左边,那么如下:

printA(10);

那么这个10到底给那个形参?但是默认参数放置在右边,那么我就知道10是给b的,这样也能够满足编译,因为a有默认参数,所以不会报错了。

1.3、重载

函数重载(Function  Overload):用同一个函数名定义不同的函数,当函
数名和不同的参数搭配时函数的含义不同。
重载规则:
1,函数名相同。
2,参数个数不同,参数的类型不同,参数顺序不同,均可构成重载。
3,返回值类型不同则不可以构成重载。

看一下下面这段代码:

#include <iostream>

using namespace std;

void printA(int a, int b)
{
	cout << "fun 1" << endl;
}

void printA(int a, char *b)
{
	cout << "fun 2" << endl;
}

void printA(int a, double b)
{
	cout << "fun 3" << endl;
}

int main(void)
{
	printA(10,20);
	printA('a', 20);
	printA(10, "zhangsan");
	printA(10, 2.4f);
	return 0;
}

运行结果:
在这里插入图片描述
我们一个个来看:
第一个是运行函数1,这个毫无疑问10,20都是int型,所以只有第一个匹配。
第二个也是运行函数1,但是他有一个区别就是第一个参数是char型,由此我们得知,重载也是会找隐式转换的。
第三个运行函数2毫无问题,因为字符串只有第二个重载函数才能匹配的上
第四个运行函数3,这里f代表的是float而不是double,可是这里也能运行,那么和第二个一样可以隐式转换。
所以我们总结出函数可以重载的三个要素:
1、函数名和参数的列表相同,那么可以重载
2、在不满足第一个点的情况下接着寻找隐式转换
3、如果第一和第二点都不满足,那么将不能重载

1.4、函数重载和函数指针

我们先来回顾一下函数指针定义方式有三种,如下面
第一种:

#include <iostream>

using namespace std;

typedef void(fun_way1)(int, int);

void printA(int a, int b)
{
	cout << "a:" << a << endl;
	cout << "b:" << b << endl;
}

int main(void)
{
	fun_way1 *pfun = NULL;
	pfun = printA;
	pfun(10,20);
	return 0;
}

第二种:

#include <iostream>

using namespace std;

typedef void(*fun_way1)(int, int);

void printA(int a, int b)
{
	cout << "a:" << a << endl;
	cout << "b:" << b << endl;
}

int main(void)
{
	fun_way1 pfun = NULL;
	pfun = printA;
	pfun(10, 20);
	return 0;
}

第三种:

#include <iostream>

using namespace std;

void(fun_way1)(int, int);

void printA(int a, int b)
{
	cout << "a:" << a << endl;
	cout << "b:" << b << endl;
}

int main(void)
{
	void(*fun_way1)(int, int) = printA;
	fun_way1(10, 20);
	return 0;
}

我们看这三种,不管哪一种,函数指针定义的形参列表都是固定,那么在这里,函数指针就不可能使用重载,因为构成重载的参数列表这个条件已经被限定死了,所以只能指定特定的函数。
那么重载是怎么运行的呢?
C++利用  name  mangling(倾轧)技术,来改名函数名,区分参数不同的同名函数。  
实现原理:用  v  c  i  f  l  d  表示  void  char  int  float  long  double  及其引用。
比如:

void func(char a);	//func_c(char a)	
  
void func(char a,int b,	double c); //func_cid(char a,int b,double c)

其实重载到最终也是运行不同的函数,只是这一步编译器帮我们实现了。

发布了29 篇原创文章 · 获赞 0 · 访问量 423

猜你喜欢

转载自blog.csdn.net/weixin_42547950/article/details/104307933