IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++(四):函数 -> 正文阅读

[C++知识库]C++(四):函数

四、函数

函数的使用

以一个求阶乘的函数为例讲述如何编写和使用函数。

1.头文件,函数的声明

我们提倡 分离式编译 (separate compilation) ,因此我们会将程序分割到数个文件中。头文件包含了该函数的声明。

#ifndef FACT_H
#define FACT_H
#include <iostream>

using namespace std;

unsigned int fact(int a);
#endif

fact.h是我们定义的头文件的名称。其中ifndef和endif语句块是一种预处理机制,防止重复包含头文件带来的潜在危害。

在头文件中需要包含哪些库是视其所生命的函数而定的,在这里只用到了一些iostream的内容,因此只include它一个。

在这个头文件中我们声明了一个会返回unsigned int类型的、名称为fact,形参为一个整型变量的函数。在此处仅仅是声明,并不设计该函数的具体实现。

将函数的声明直接放在使用该函数的源文件中也是合法的,但是这么做也许会很繁琐并且容易出错。如果将函数的声明放在头文件中,既能保证同意函数的所有声明保持一致,在将来万一我们想要修改函数的接口也会更加的方便。

2.源文件,函数的定义

在这个例子中,函数的定义如下:

#include "fact.h"
unsigned int fact(int a)
{
	unsigned int temp = 1;
	while (a > 1)
	{
		temp *= a--;
	}
	return temp;
}

首先,为了将源文件与头文件关联起来,我们必须将含有函数声明的头文件包含进定义函数的源文件中,即第一行 include 语句。

随后我们开始定义这个计算阶乘的函数。它的返回,函数名,形参与头文件中一致,大括号内是函数体,涉及到函数的具体实现,最后返回了一个temp,其类型必须与函数的返回类型一致。

3.主程序,函数的调用

#include "fact.h"
using namespace std;


int main()
{
	int a = 0;
	cin >> a;
	cout << fact(a) << endl;
	return 0;
}

要调用fact函数,必须先在源文件中include声明其的头文件。

我们用调用运算符() 来执行函数。函数的调用完成两项工作:一是用实参初始化函数的形参,二是将控制权转移给被调用函数。

用实参初始化函数的形参是指程序会首先创建一个名字为a的int变量,并用调用运算符内的实参初始化这个变量。该变量的生命周期仅在fact函数被调用期间,一旦函数执行结束,该变量就会被“销毁”。

参数传递

1.值传递

如上文所示的程序,主函数中定义的变量 a 的值被拷贝后传递给了fact函数中的形参 a ,在这种情况下形参和实参是两个相互独立的对象,这样的传递方式称为 值传递

当初始化一个非引用类型的变量时,初始值被拷贝给变量,此时对变量的改动不会影响初始值。

unsigned int fact(int a)
{
	unsigned int temp = 1;
	while (a > 1)
	{
		temp *= a--;
	}
	return temp;
}
int main()
{
	int a = 0;
	cin >> a;
	cout << fact(a) << endl;
	cout << a << endl;
	return 0;
}

现在分析:虽然在fact中我们对形参a做了更改:a-- ,但是如同上文所说,在值传递的情况下程序是通过建立副本的方式实现功能的,因此主函数中的变量a并没有被改变。

在这里插入图片描述

2.引用传递

当形参是引用类型的时候,这时的实参被 引用传递 了。此时的形参被绑在了对应的实参上,也就是说我们可以通过形参更改实参的值。

void reset(int &i)
{
	i = 0;
}
int main()
{
	int a = 10;
	reset(a);
	cout << a << endl;
	return 0;
}

根据值传递的结论,倘若reset的形参为非引用类型,则这段代码打印出来的值应该是10。实际上由于在这里使用了引用形参,因此改变 i 的值就是改变了 a 的值。

在这里插入图片描述
由于拷贝大的类类型对象和容器是比较低效的行为,甚至有的类不支持拷贝操作,因此建议在这种情况使用引用形参访问该类型对象。

一个实例:

#include "bubblesort.h"

void bubblesort(int (&r)[10])
{
	for (int i = 0; i != 9; ++i)
	{
		for (int j = 0; j != 10 - i - 1; ++j)
		{
			int temp;
			if ( r[j] > r[j + 1])
			{
				temp = r[j];
				r[j] = r[j + 1];
				r[j + 1] = temp;
			}
		}
	}
}

#include "bubblesort.h"
int main()
{
	int arr[] = {1,10,2,7,9,4,6,3,8,5};
	bubblesort(arr);
	for (auto r : arr)
	{
		cout << r << " ";
	}
	cout << endl;
	return 0;
}

这是一个冒泡排序的例子,只能传递长度为10的数组, 扩展性太差 对于可变数组的传递以后会涉及。

3.关于const形参

由于普通的引用形参会改变实参的值,因此在不需要改变的情况下建议优先使用带有const的引用形参。否则会给使用者一个错觉,即函数可以修改它的实参的值。此外,使用引用而非常量引用也会极大地限制函数所能接受的实参类型。

string size_type find_char(const string &s,char c);

这个函数的目的是在字符串 s 中寻找字符 c 。此时定义的 s 类型为对字符串的常量引用,此时的函数既能接受 const 对象,也能接受字面值。

//在这种定义下,编译器会报错,因为无法接受字面值常量作为输入
//只能使用普通的string类型
string size_type find_char(string &s,char c);
find_char("hello world",'o');

除此以外还会有更加隐蔽的问题,当在另外一个函数中调用这个函数的时候:

bool is_sentence(const string &s)
{
	return find_char(s,'.') == s.size()-1;
}

在这个新函数中形参为常量引用,而 find_char 则为普通引用,因此会报错。结局办法可以是把 is_sentence 函数的形参写成普通引用(但这只是掩耳盗铃,在下一次调用时依然无法避免类似的问题),也可以在 is_sentence 函数内部定义一个 s 的副本,亦或者更改 find_char 的形参为常量引用。

4.指针形参

不想写了,类似于引用传递。形参中输入的应该时实参的地址,可以实现实参的修改。

指针类型的形参目前是在传递数组的时候遇到。由于数组的名字表示的是首元素的地址,是一个指针,因此可以用指针形参传递。

局部对象

形参和函数体内部定义的变量统称为 局部变量 。它们仅在函数的作用域内可见,因此是“局部”的。

1.自动对象

这是普通的局部变量,当程序读到变量定义的语句时创建该变量,到达定义所在的块的末尾时销毁它。这样只存在于块执行期间的对象称为 自动对象

2.局部静态变量

某些时候有必要让变量贯穿始终,使用 static 类型从而获得这样的对象。

size_t count_calls()
{
	static size_t ctr = 0;
	return ++ctr;
}
int main()
{
	for(size_t i = 0; i != 10; ++i)
	{
		cout << count_calls() << endl;
 	}
 	return 0;
}

在 count_calls 函数结束后 ctr 对象依然存在,不会消失。

函数的返回

1.引用作为返回

  1. 不要返回局部变量的引用
int & test1()
{
	int a = 10;
	return a;
}

int main()
{
	int &r = test1();
	cout << r << endl;
	return 0;
}

由于局部变量 a 在 test1 运行结束后被释放了,所以函数无法正确返回一个不存在的变量的引用。

  1. 引用可以作为左值
    如果一个函数的返回值是引用,那么这个函数的调用可以作为左值使用
int & test2()
{
	static int a = 10;//这里的变量不是局部变量
	return a;
}

int main()
{
	int &r = test2();//r是a的别名
	cout << r << endl;
	test2() = 100;//在这里给a赋值100
	cout << r << endl;//再次利用别名使用a
	return 0;
}

结果为:
在这里插入图片描述

函数的参数

1.默认参数

默认参数可以有多个,但是必须统一放在形参列表尾部。

int func(int a, int b, int c)//合法的写法,无默认参数
{
	return a + b + c;
}

int func1(int a, int b = 0, int c = 0)//合法的写法,有默认参数
{                                  //在调用是可以不输入b和c的实参
	return a + b + c;
}

int func2(int a, int b = 0, int c)//非法,因为默认参数放在了形参c前面
{
	return a + b + c;
}

默认参数只能存在于函数声明或者函数实现其中之一。

int func3(int a, int b, int c = 10);
int func(int a, int b, int c = 10)//非法,因为在声明和定义中都出现了默认参数
{
	return a + b + c;
}

2.占位参数

void func(int a, int)
{/*
函数体
*/}

其中的第二个参数就是占位参数,在调用时必须补全这个参数。

int main()
{
	int a = 10;
	func(a,8);//此处不把占位参数补全的话会报错
}

占位参数还可以有默认参数值,这样在调用时就可以不用补了。

void func(int a, int = 8)
{/*
函数体
*/}

int main()
{
	int a = 10;
	func(a);//此处可以不补全参数
}
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-11-14 21:25:37  更:2021-11-14 21:28:47 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 7:32:07-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码