清华大学C++语言程序设计(第三单元笔记)

函数(第三章)

函数的定义与使用

调用其他函数的被称为主调函数,被其他函数调用的称为被调函数

#函数的调用可以是自身调用,或者是其他函数调用,也可能调用其他函数的同时也被其他函数调用。

函数的的定义

1.函数定义的语法形式

类型说明符 函数名(含类型说明符的形参){

语句序列

}

2.形式参数

如:type1 name1 其中type1为类型表示符,表示形参的类型。name为形参名。

##main函数也可以有形参,其形参称为命令行参数,由操作系统在启动程序时初始化。**注意:命令行参数的数量和类型有特殊要求。##

3.函数的返回

return 表达式;

这里表达式可以是函数,变量,或者常量。

#如果标识符为void则代表不返回任何值,可以用一个单独的:

return; 来表示返回,或者直接不写。

函数的调用

1.函数的调用形式

函数原型声明的形式如下:

类型说明符 函数名(含类型说明的形参表);

deltype(a) 这个语句返回与a的类型相同的表示符。

所以为了简化函数返回值类型的定义可以采取以下方法:

int a=10;
decltype(a) myMax(decltype(a) lhs){
    return lhs;
}

声明函数时,形参名可以不写,例如:

int a=10;
decltype(a) myMax(decltype(a)){   //
    return lhs;
}

但是,这种形式不推荐因为形参名可以间接表示这个参数的含义,可以使你的程序更加清晰。

例:编写一个求x的n次方的函数:

法一:
#include<iostream>
double power_my(double x, double n) {
	double val = 1.0;
	while (n--)val *= x;
	return val;
}
法二:
double power_my(double x, double n) {
	if (!n)
		return 1;
	return x*power_my(x, n - 1);
}

例:3-2输入一个8位二进制数,将其转换为十进制输出

#include<iostream>
using namespace std;
//double power_my(double x, double n) {
//	double val = 1.0;
//	while (n--)val *= x;
//	return val;
//}
double power_my(double x, double n) {
	if (!n)
		return 1;
	return x*power_my(x, n - 1);
}
int main() {
	double val=0.0;
	cout << "请输入一个长度为8的二进制数据"<<endl;
	int num;
	for (int i = 0; i < 8; i++, cin >> num) {
		if (num)
			val +=power_my(2.0, (double)(7-i));
	}
	cout << "二进制数据的值为:" << val;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eEJOEGdg-1615982393138)(C:\Users\mzy\AppData\Roaming\Typora\typora-user-images\image-20210316170712508.png)]

例:3-3 编写程序求圆周率的值,公式如下:

圆周率=16arctan(1/5)-4arctan(1/239)

#其中arctan用泰勒展开形式的级数计算:

#include<iostream>
using namespace std;
double my_caluate(double x, double error) {
	double val = 0.0,e=x,shizi;
	int num=1;
	do {
		shizi = e / (2 * num - 1);
		num++;
		e *= -x*x;
		val +=shizi;
	} while (shizi > error);
	return val;
}
int main() {
	double a = 16.0*my_caluate(1 / 5.0, 0.00001);
	double b = 4.0*my_caluate(1 / 239.0, 0.00001);
	//这里有个小细节:因为整数相除结果取整,如果写1/5结果为0
	cout << "PI=" << a - b << endl;
}

]

#这里不为3.1415的原因是精度的问题#

例3-4:一个更加巧妙的方法判断回文数列

寻找并输出11~999的数m,它满足m,mxm,mxmxm,为回文数

#include<iostream>
using namespace std;
//这里用bool值的形式返回函数的判断类型
bool symm(int x) {
	int i = x;
	int m = 0;
	while (i > 0) {
		m = m * 10 + i % 10;
		i /= 10;
	}
	return m == x;  //这里是一个判断返回的是bool值成立返回真
}
int main() {
	for (int i = 11; i < 1000; i++) {
		if (symm(i) && symm(i*i) && symm(i*i*i))
			cout << "m=" << i << i*i << i*i*i << endl;
	}
}

例3-5 计算如下公式,并输出结果:
k = ( s i n 2 r + s i n 2 s ) ( 1 / 2 ) = = = = = ( r 2 < = s 2 ) k=(sin^2r+sin^2s)^(1/2)=====(r^2<=s^2) k=(sin2r+sin2s)(1/2)=====(r2<=s2)

k = 1 / 2 ( s i n ( r s ) ) = = = = = ( r 2 > s 2 ) k=1/2(sin(rs))=====(r^2>s^2) k=1/2(sin(rs))=====(r2>s2)

其中r,s的值由键盘输入,sinx的近似值按泰勒展开算:

#include<iostream>
#include<cmath>
using namespace std;
const double error =1e-100;   //这里有一个好习惯:对于绝对不变的数用const修饰
double sinx_my(double x,int n) {   //这个n是递归时使用的一个累加参量恒放入为1
	double c = x,shizi;
	shizi = c / (2 * n - 1);
	if (fabs(shizi)<error)     //只计算十项
		return 0;
	c *= (-1)*x*x;
	return sinx_my(c, n + 1)+ shizi;
}
int main() {
	double k, r, s;
	cout << "r=";
	cin >> r;
	cout << "s=";
	cin >> s;
	if (r*r <= s*s)
		k = sqrt(sinx_my(r, 1)*sinx_my(r, 1) + sinx_my(s, 1)*sinx_my(s, 1));
	else
		k = sinx_my(r*s,1);
	cout << "k=" << k;

}

#递归看起来很厉害但是性能远远不如while,for循环,且难度较高,在解决特定问题的时候有奇效,在编写程序的时候不要刻意的使用递归

例3-6投色子的随机游戏

投掷为7或11胜利,投掷为2,3,12为负,其余的再来一次。

#include<iostream>
#include<cstdlib>
#include<time.h>
using namespace std;

int rolldice() {
	int die1 = 1 + rand() % 6;
	int die2 = 1 + rand() % 6;
	cout << "投掷的点数:" << die2 + die1;
	return die2 + die1;
}
int main() {
	unsigned int output,sum;
	int flag = 0;
	do {
		flag = 0;
		cout << "请随意输入一个数字作为种子";
		cin >> output;
		srand(output);
		sum = rolldice();
		switch (sum)
		{
		case 7:
		case 10:
			cout << "你胜利的";
			break;
		case 2:
		case 3:
		case 12:
			cout << "你失败了";
			break;
		default:
			cout << "再来一次";
			flag = 1;
			break;
		}
	} while (flag);
}

或者更改一下:当不为前两者的时候,以这个和数为自己的点数,然后第二轮,第三轮,直到某轮出现前两者的情况游戏结束。

#include<iostream>
#include<cstdlib>
#include<time.h>
using namespace std;

int rolldice() {
	int die1 = 1 + rand() % 6;
	int die2 = 1 + rand() % 6;
	cout << "投掷的点数:" << die2 + die1;
	return die2 + die1;
}
enum GameStatus{win,lose,playing};   //定义枚举变量
int main() {
	unsigned int output,sum,my_point;
	int flag = 0;
	GameStatus status;
	cout << "请随意输入一个数字作为种子";
	cin >> output;
	srand(output);
	sum = rolldice();
	switch (sum)
	{
	case 7:
	case 10:
		status = win;
		break;
	case 2:
	case 3:
	case 12:
		status = lose;
		break;
	default:
		status = playing;
		my_point = sum;
		cout << "the point is:" << sum;
		break;
	}
	while (status == playing) {
		sum = rolldice();
		if (sum = my_point)
			status = win;
		else
			status = lose;
	}
	if (status == win) {
		cout << "你胜利了";
	}
	else {
		cout << "你输了";
	}
}

2.嵌套调用

就是函数一调用函数二,函数二调用函数三·······这样形成的嵌套结构就是嵌套调用。

例3-7输入两个整数求其平方和:

#include<iostream>
using namespace std;
int fun1(int x) {
	return x*x;
}
int fun2(int x,int y) {
	return fun1(x) + fun1(y);
}
int main() {
	int a, b;
	cout << "请输入两个数字a,b:";
	cin >> a >> b;
	cout << fun2(a, b);
}

3.递归调用

递归调用简单来说就是找出前一项和后一项的关系,并且用公式表示出来。

例3-8 求n的阶乘

#include<iostream>
using namespace std;
int fac(int n) {
	if (n == 1) {
		return 1;
	}
	return fac(n - 1)*n;
}
//或者换一种累加的方法
int fac1(int n) {
	int f;
	if (n == 0)
		f = 1;
	else
		f = fac1(n - 1)*n;
	return f;
}
int main() {
	int num;
	cout << "请输入你要求的阶乘:" << endl;
	cin >> num;
	cout << "n!=" << fac(num) << endl;
	cout << "n!=" << fac1(num);
}

例3-9:计算从n个人中选择k个人做出一个委员会的不同的组合数

这个有不同的算法:就像如果我们用种类进行递归,委员会的第一个人有n种情况,第二个人有n-1种情况····直到第k个人这样可以用递归算出有多少种情况。

这里我们换一种思路:

由n个人里面选k个人的组合数=由n-1个人里面选k个人的组合数+由n-1个人里面选择k-1个人的组合数。

#include<iostream>
using namespace std;
int comm(int n, int k) {
	if (k==1)
		return n;
	if (n ==k)
		return 1;
	return comm(n - 1, k) + comm(n - 1, k - 1);
}
//或者我们换一种递归方式
int comm(int n,int k){
    if(k>n)
        return 0;
    else if(n==k||k==0)
        return 1;
    else 
        return comm(n-1,k)+comm(n-1,k-1);
}
int main() {
	int n, k;
	cout << "请输入两个数字n,k" << endl;
	cin >> n >> k;
	cout << comm(n,k);
}

例3-10汉诺塔问题:

这个是一个十分经典的问题,这里有一个十分好的博客可以帮助我们理解这个问题

汉诺塔的简单总结

函数的参数传递

函数在未被调用的时候,函数的形参并不占有内存单元,也没有值,只有在函数调用的时候才会为形参分配储存单元,并且将实参和形参结合。每一个实参可以看成一个表达式,其类型与形参相符,函数的参数传递实质上就是形参和实参结合的过程。

值传递:

就是很简单的来看,函数中的参数是局部变量,对整个程序没有影响,如:

#include<iostream>
using namespace std;
void swap(int x, int y) {
	int a = x;
	x = y;
	y = a;
}
int main() {
	int x = 5, y = 10;
	cout << "x=" << x <<"y="<<y<< endl;
	swap(x, y);
	cout << "x=" << x << "y=" << y << endl;
}

2.引用传递

引用是一个特殊类型的变量,可以被认为是另一个变量的别名,通过引用名和通过被引用名的变量名是一样的如:

int i,j;

int &ri=i;

j=10;

ri=j //相当于i=j

##使用引用时注意的问题:

~引用的同时对他进行初始化,使其指向一个已经存在二点对象

~一旦一个引用被初始化后,就不能改为指向其他对象。

##引用也可以作为形参,如果将引用作为形参,情况便稍有不同。这是因为形参初始化不在类型说明时进行,而是在执行主调函数中的调用表达式时,才为形参分配内存空间,同时用实参初始化形参。这样形参的任何操作也会直接作用于形参##

用引用作为形参,在函数调用时发生的参数传递,称为引用传递。

例3-12

#include<iostream>
using namespace std;
void swap(int &x, int &y) {
	int a = x;
	x = y;
	y = a;
}
int main() {
	int x = 5, y = 10;
	cout << "x=" << x <<"y="<<y<< endl;
	swap(x, y);
	cout << "x=" << x << "y=" << y << endl;
}

#引用传递的本质是地址进行操作,&的意为取地址符,int &a 初始化一个指针变量,并对它这个地址的值进行赋值,改变;这个地址在函数结束后这个地址不会消亡,而会留在内存中直到程序结束,地址消亡。

3.含有可变数量的函数

为了能编写可以处理不同数量实参的函数,c++标准提供了两种方法:如果所有的实参类型相同,可以 传递一个initializer_list的标准库类型,如果实参类型不同,可以编写可变参数模板的类(后面会讲到)

initializer_list是一种标准库类型,用于表示某种特定类型的值的数组,该定义在同名的头文件中。

这个库的常用操作:

在这里插入图片描述

针对lst2(lst)的使用:

initializer_list<T>lst2(lst);
initializer_list<T>lst2 = lst;

.initializer_list的初始化方式

**直接初始化空list**
initializer_list<string> ls;
initializer_list<int>li;

**初始化时赋值**
initializer_list<string> str{ "hello","my","dear" };
initializer_list<int> str{ 1,2,3,4 };

initializer_list的遍历

//和vector相同,可用迭代器来遍历
initializer_list<string> str{ "hello","my","dear" };

    for (auto it = str.begin(); it != str.end(); it++)
    {
        cout << *it << endl;
    }

initializer_list的使用

//函数参数定义为initializer类型
void show_name(initializer_list<string> list)
{
    for (auto it = list.begin(); it != list.end(); it++)
    {
        cout << *it << endl;
    }
    
}

//在传递参数时可以将多个参数用“{a,b....}”的形式传入,参数的个数可以改变。
show_name({"Mike"});
show_name({ "Mike","Bob" });

这个转载于:C++学习笔记------initializer_list

内联函数

适用于功能简单,规模较小又使用频繁的函数可以设计为内联函数(这个目的是降低重复调用的时,执行效率的降低,减少时间方面的开销)。内联函数不是在调用时发生控制转移,而是在编译时将函数体嵌入在每一个调用处。这样节省了参数传递,控制转移的开销。

格式:

inline 类型说明符 函数名(含类型说明符的形参表){

​ 语句序列;

}

#编译器是很聪明的,如果内联函数过于复杂,编译器会自动将其转换为普通函数来处理(不同编译器有不同的处理方式)所以,inline只是一个表示要求,编译器不一定会把inline关键字修饰的函数定义为内联函数

##递归函数不能以内联的方式处理##

例:3-14计算圆的周长

#include<iostream>
using namespace std;
const double pi = 3.14;
inline double my_size(double r) {
	return 2.0 * pi*r;
}
int main() {
	double r = 3.0;
	//调用内联函数求圆的周长,编译的时候此处被替换为CalArea函数体语句
	//展开为area=pi*2.0*r;
	double area = my_size(r);
	cout << "圆的周长为:" << area;
}

constexpr函数

constexpr函数是指能用于常量表达式的函数。但是,函数的返回类型以及所有的形参类型是常量,且必有且仅有一个return语句。

constexpr函数内也可以包含其他语句,只要这些语句在函数中不执行任何操作就可以。编译器把对constexpr函数的调用直接替换成其结果值,为了能在编译的过程中随时展开,constexpr函数被定义为内联函数。constexpr也可以返回常量表达式。

带默认形参值的函数

**函数在定义的时候可以预先声明默认的形参值。**调用时候如果给出实参,则用实参初始化形参,如果没有给出实参,则采用预先声明的默认形参值。这里我们拿一下以前的例子:

#include<iostream>
#include<cmath>
using namespace std;
const double error =1e-15;   //这里有一个好习惯:对于绝对不变的数用const修饰
double sinx_my(double x,int n=1) {   //这个n是递归时使用的一个累加参量恒放入为1
	double c = x,shizi;
	shizi = c / (2 * n - 1);
	if (fabs(shizi)<error)     //只计算十项
		return 0;
	c *= (-1)*x*x;
	return sinx_my(c, n + 1)+ shizi;
}
int main() {
	double k, r, s;
	cout << "r=";
	cin >> r;
	cout << "s=";
	cin >> s;
	if (r*r <= s*s)
		k = sqrt(sinx_my(r, 1)*sinx_my(r) + sinx_my(s)*sinx_my(s));
	else
		k = sinx_my(r*s);
	cout << "k=" << k;

}

##有默认值的形参必须放在形参列表的最后,也就是说在有默认形参右面,不能出现无默认值的形参##

##在相同的作用域内,不允许在同一个函数的多个声明中对同一个参数的默认值重复定义,即使前后定义的值相同也不行。##

注意:函数定义也属于声明,这样,如果一个函数在定义之前又有原型声明,默认形参值需要在原型声明中给出,定义中不能再出现

int add(int x=5,int y=6);    //默认形参值在函数原型中给出
int main(){
    add();
}
int add(int x/*=5*/,int y/*=6*/){
    //这里不能再出现默认形参,但是为了清晰,可以通过注释说明默认形参
    cout<<x+y;  
}

例3-15 带默认形参值的函数举例:

#include<iostream>
using namespace std;
int getvolume(int length, int width = 2, int height = 3) {
	return length*width*height;
}
int main() {
	const int x = 5, y = 10, z = 20;
	const int x1 = 5;
	cout << "这个长方体体积为:" << getvolume(x, y, z) << endl;
	cout << "第二个长方体体积为:" << getvolume(x1) << endl;
	cout << "第三个长方形体积为:" << getvolume(x1, 3) << endl;
}

函数重载

两个以上的函数,具有相同的函数名,但是形参的个数或者类型不同,编译器根据实参和形参的类型及个数的最佳匹配,自动确定调用哪一个函数,这就是函数的重载

例如,定义函数的加法的时候,加法可能是浮点数的加法,也可以是float类型的加法,这时候用以下方法:

float add_float(float x,float y){

return x+y;

}

int add_int(int x,int y){

return x+y;

}

明显感觉调用的时候太过麻烦,这时候就用到了函数的重载。c++允许功能相近的函数在相同的作用域内以相同的函数名定义,从而形成重载。

注意:重载很熟的参数必须不同,个数不同,或者类型不同。编译程序对实参和形参进行比对进行最佳匹配,来选择调用哪个函数。如果,函数名相同,形参类型相同(无论返回值是否相同),在编译的时候会被认为语法错误。

##不要把不同功能的函数定义为重载函数,这里可能会造成误用。##

例3-16 重载函数举例

#include<iostream>
#include<cmath>
using namespace std;
int sumofsquare(int x, int y) {
	return x*x + y*y;
}
double sumofsquare(double x, double y) {
	return x*x + y*y;
}
int main() {
	cout << "3和5的平方为:" << sumofsquare(3, 5) << endl;
	cout << "0.3和0.5的平方为:" << sumofsquare(0.3, 0.5);
}

使用c++语言系统函数

这里不做过多阐述,不过推荐几个网站:

c++开发者文档

菜鸟教程

scdn

高效码农

博客园

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值