函数

讲讲函数机制!
为了实现函数的功能,我们可以使用最简单的语法,最简单的思维,但这有一个后果!代码冗余!体力活!可读性极差!所以我们才需要理解C++语言在我们编写函数时,为我们提供了那些可用的机制,帮助我们去实现简洁的代码!!!请记住!这些个函数机制是帮助我们实现简洁的函数!(题外话:就像人不能忘记初心,我们也不能忘记设计函数机制的初衷,这样才能时时刻刻想着法子优化我们的代码)

参数传递方式:传值和传址,区别在于,形参是实参的副本还是能够间接操作实参。
说起函数调用,有件事不能不提,就是调用函数时,会将其形参、内部定义的对象通通存到栈中,而当函数调用结束时,会释放这片栈空间。
讲到栈空间,不得不提变量的生存空间这件事。
函数内所定义的对象,一般情况下都属于自动变量,随着函数的终止,自动变量所占用的内存空间也会被释放。
其实,函数中声明的变量也不全都是自动变量。C++为我们提供了新的类型,局部静态变量。这种类型无外非增强了函数变量的灵活性,这种类型的对象不会随着函数终止而被释放,但又保证了只在该函数内的可见性,这就很好地解决了静态变量打破函数间独立性的问题。由于静态局部变量的空间不会被释放,因此我们可以返回其地址。

但除了自动变量外,还有静态变量,在函数外声明的变量,只要整个程序运行就存在,而且从声明开始,到文件结尾前,对所有函数都是可见的(这就带来新的问题,会打破函数间的独立性),且编译器会帮助我们为其进行初始化,而自动变量编译器不会对其进行初始化。
显然,这两种对象的存储对我们来说都是透明的,我们无法掌控他,但是C++人性化的为我们提供了一种能控制变量的存储时间的机制,即动态变量。动态变量的实现机制呢,就是编译器为我们预留一个堆,我们可以在堆上获取指定大小的存储空间,直到我们不需要的时候,自己手动的释放这片空间。言外之意,忘记释放,将导致内存泄露。
关于自动变量作为函数返回值,其实这里发生了对象复制。因为自动变量在函数终止时就已经被释放了,调用程序根本无法访问到自动变量,使用时该自动变量的副本。
在这里插入图片描述
传值:在栈中为该函数分配一片空间,将实参的值复制到栈空间中,因而形参是实参的副本,在函数对形参的操作完全不会影响实现,都不是一个内存空间,谈何影响???
传址:
引用:与指针不同,指针可以为空,不知向任何对象,但是引用不可以,一定是存在对象的别名,而且从一而终!不能为其赋予新的引用对象!为引用赋值的结果是修改内存空间的数据值。就把引用对象视作所引对象一样处理。那么传址给程序栈传递的是什么呢?传递的是地址,在程序栈的内存空间存的是实参的地址,因而我们在函数中对形参的操作相当于对实参进行。
如何在传值和传址间进行抉择?
对于基本数据类型,除非需要修改其内存中存储的值,否则别用引用。
当需要直接修改实参的值,或是对象所占的内存较多,为了效率出发不希望复制内容时都可以选用传址!在阅读程序的时候,我们会经常发现有时候声明引用类型的参数时,加了const修饰,而有时没有加。这是为什么呢?为了区别目的,引用参数有两种常见的使用目的,修改实参值和不修改,加了const可以区分开这两种目的,便于阅读代码的人更好的理解函数的含义。传址方式中,我们可以将参数声明为指针类型,与使用引用类型有所不同,在函数内使用指针进行操作前,我们需要确保不是空指针。而引用就不需要,因为引用一定是有关联的对象。

#include<iostream>
#include<algorithm>
#include<vector>

void bubble(std::vector<int > & old);
void swap(int &x, int &y);

int main()
{
	std::vector<int> a = { 22,33,55,66,1,3,7,6,3 };
	bubble(a);
	for (auto x : a)
		std::cout << x << " ";
	std::cout << std::endl;
	std::cin.get();
    return 0;
}

void bubble(std::vector<int > & old)
{
	for (int i = 0; i < old.size();i++)
	{
		for (int j = i + 1; j < old.size();j++)
		{
			if (old[j] < old[i])
				swap(old[i], old[j]);
		}
	}
}

void swap(int &x, int &y)
{
	int temp;
	temp = x;
	x = y;
	y = temp; //xy是引用变量 但此时改变的是数据值 而不是指向
}

编写函数时有个不成文的规定,尽可能少使用静态变量,这会降低函数的可移植性,增加阅读代码的困难性。函数之间的沟通的方式应通过参数传递来进行!
默认参数:
C++允许我们为部分、所有参数提供默认值,以达到为用户提供多种选择。
关于使用默认参数有两个注意点:
1、默认参数从最右侧开始提供
2、函数声明中、函数定义中,默认参数的值只能出现一次,而通常我们会使用头文件包含函数声明,因而将提供的默认值书写在函数声明中更好。函数定义书写在程序文件中,只被编译一次,当需使用函数定义时,该文件将会被链接到我们的程序中。

void show(std::vector<int> & a, std::ostream & os = std::cout);
void show(std::vector<int> & a, std::ostream & os)
{
	for (auto x : a)
	{
		os << x << " ";
		
	}
	os << std::endl;
}
show(a);
	std::ofstream fout("1.txt");
	show(a, fout);

内联函数:
为了提高函数执行效率所提出的改进,将函数的调用操作以一个函数副本形式取而代之。相当于将被调用函数的代码加入到函数中。
在函数声明时就需要指出内联性,并在声明时就需要给出定义。

函数重载:
为什么一定需要函数重载呢?这个概念是为了编写和阅读上逻辑的方便,保证函数名称相同,但是参数个数和类型不同,函数名称突出函数功能,这是很常见的情况,函数实现的功能是类似的,但是参数个数类型是不同的。当然没有重载,我们无外非是给这些函数取不同的函数名称,程序能正常执行,但阅读和维护修改起来就很困难了。

模板函数:
这个概念是对函数重载的进一步升级,试想,当多个函数希望实现的功能和方式都是一样的,仅仅在数据类型上有所差异(代码主体完全一致,只是数据类型不同)。C++提供了模板函数机制,免去了程序猿总是做相同的工作,将毫无技术的复制粘贴交给编译器来实现。仅是类型不一样,为此我们需要抽象出类型,在参数列表和函数主体中使用该类型参数替换具体的类型,便可生成模板参数。在实例化时根据传递的参数来生成具体函数。此外,模板函数也可以有重载函数。模板函数中的参数到底是使用抽象类型参数还是使用具体类型,这得看需要替代的函数定义。

函数指针:
指针的最大用途就是提高程序的弹性(透明度)!
通过创建一个函数指针,就可以使用多个相同返回类型、参数类型的函数!更进一步,将这些返回类型和参数类型个数相同的函数地址放在一个数组中,通过改变索引值获取不同的函数地址进而调用函数,而不是显示的使用函数名调用函数。也许原来程序需要6条使用函数名的调用语句,而现在可通过循环,改变函数指针指向进而调用函数。甚至一个函数实现的功能相同的,但是需要根据不同的函数的返回值来进行某种操作,原先我们可能需要重载这些个函数,在函数内部通过函数名调用函数得到返回值才能进行下一步的操作。但现在我们知道这些个函数的参数和返回值类型一样进而我们可以使用一个函数指针表示这些个函数,因此可以通过增加函数参数,利用该参数改变函数指针的指向获取不同的函数进而函数,于是一个函数就能达到6个冗余的函数。可以看出函数指针也是解决了代码冗余的问题。
函数指针的定义,记得加小括号改变运算符的优先级:
在这里插入图片描述
使用函数指针调用函数的用法和函数名调用函数一致,与使用指向普通类型的指针类似,函数指针也可设置为空,不指向任何函数,同理,有空函数指针的概念,在使用函数指针前,为了安全起见,我们需要检验指针是否为空:
在这里插入图片描述在这里插入图片描述该函数指针赋初始值的时候,以何种方式能够表达出函数的地址?函数名就是函数地址!
在这里插入图片描述函数指针数组的写法,[]优先级高于*,因而可表示数组:
在这里插入图片描述枚举类型的一个常见用法,通过枚举常量取代数组下标,更加人性化!每个枚举值都对应一个整型值,默认从0开始对应,后一个枚举值比前一个枚举值多1。因此可使用枚举值表示整形值,用于获取数组元素。

下面是个使用模板函数、函数指针、传址、内联、枚举、局部静态变量的综合例子,给我最大的感受是学习这么多的函数设计机制,就是为了减少冗余代码,增强函数的灵活性!能否使用一些机制让一个函数实现更多功能?而不是使用简单思维编程。

#include<vector>
#include<iostream>
#include<string>
using namespace std;
vector<int> * fibon_seq(int );
vector<int> * jishu_seq(int );
bool get_value(int pos, int &value, int function_id);

enum {
	fibon,jishu
};

template <class T>
void show(vector<T> a)
{
	for (auto x : a)
	{
		cout << x << " ";
	}
	cout << endl;
}

inline  bool is_size_ok(int pos) {
	if (pos <= 0 || pos > 1024)
		return false;
	else
		return true;
}
int main()
{
	int result = 0;
	if (get_value(3, result, jishu))
		cout << result << endl;
	vector<string> words = { "hello","world","iii","miss " };
	show(words);
	vector<int> *numbers = fibon_seq(5);
	if (numbers)
	{
		for (auto x : *numbers)
			std::cout << x << " ";
		cout << endl;
	}
	
	
	
	numbers = jishu_seq(5);
	if (numbers)
	{
		for (auto x : *numbers)
			std::cout << x << " ";
	}
	
	cin.get();
	cin.get();
    return 0;
}

vector<int> *  fibon_seq(int pos)
{
	static vector<int> a;//利用局部静态变量 将所有的值都存储在a中 没有则添加 有则免去生成!
	if (is_size_ok(pos))
	{
		for (int i = a.size(); i < pos; i++)
		{
			if (i == 0 || i == 1)
				a.push_back(1);
			else
			{
				a.push_back(a[i - 2] + a[i - 1]);
			}
		}
		return &a;
	}
	else {
		return nullptr;
	}

}
vector<int> * jishu_seq(int pos)
{
	static vector<int> a;//利用局部静态变量 将所有的值都存储在a中 没有则添加 有则免去生成!
	if (is_size_ok(pos))
	{
		for (int i = a.size(); i < pos; i++)
		{
			if (i == 0 )
				a.push_back(1);
			else
			{
				a.push_back( a[i - 1]+2);
			}
		}
		return &a;
	}
	else {
		return nullptr;
	}
}

/*bool get_value(int pos, int& value) 没有加函数指针前 我们只能显示通过函数名称调用函数  为什么要加入那么多语法 使得程序灵活性更高 一个函数可以解决的问题就别写6个,累死了
{
	vector<int > *p = fibon_seq(pos);
	if (p)
	{
		value = (*p)[pos];
		return true;
	}
	else {
		return false;
	}
}*/
bool get_value(int pos, int &value , int function_id)
{
	vector<int> * (*generate_seq[])(int) = { fibon_seq ,jishu_seq };
	vector<int> *p = generate_seq[function_id](pos);
	if (p)
	{
		show(*p);
		value = (*p)[pos-1];
		return true;
	}
	else
		return false;
	
}

头文件:
多个文件调用同一个函数前,需要在调用前声明该函数。每个文件都声明一次岂不太麻烦?为此我们将函数的声明写入到头文件中,即便同一个函数声明出现在多个头文件中,在程序文件中包含了多个头文件(相同的函数声明)也无妨,因为函数声明可以出现多次,但函数定义只能出现一次,因此我们可以在头文件中,定义函数。但内联函数例外,因为内联函数需要在调用的时候就进行函数替换。
头文件除了包含函数声明外,也可包含对象声明,这使得同一对象可在不同的文件被访问到。对于一个const 类型的对象,可在头文件中定义它,因为定义可见性仅仅存在于程序文件中,文件间不会相互干扰。而对于非const类型的对象,要使用extern来修饰,使得编译将其理解为声明而不是定义对象。函数指针数组,这种情况在多文件中很有可能常被使用,写在头文件中也是人之常情!但由于函数指针数组没有被修饰为const,因而必须使用extern修饰!
在这里插入图片描述在这里插入图片描述
包含头文件有两种形式:“ ” 和 <> ,第一种针对用户定义的头文件,而第二种针对标准类库,通过这两种区别性的写法,可以提高编译器搜寻头文件的速度,也是很有必要的!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值