函数(第三章)
函数的定义与使用
调用其他函数的被称为主调函数,被其他函数调用的称为被调函数。
#函数的调用可以是自身调用,或者是其他函数调用,也可能调用其他函数的同时也被其他函数调用。
函数的的定义
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;
}
例: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++语言系统函数
这里不做过多阐述,不过推荐几个网站: