零基础学C++,函数篇~
C++基础学习(DAY_06)
- 函数
- 1. 函数的定义与使用
- 2. 函数参数传递
- 3. 变量的声明周期
- 4. 函数的其他特性
- 5. 函数的嵌套与递归
函数
1. 函数的定义与使用
在设计程序时,如果一段代码重复进行某种操作或者完成一个特定的功能,就应该将这些代码封装组织成函数,以实现代码复用。
- 定义:函数类型 函数名(形式参数列表)
{
函数体中的语句组;
}
以上,函数由函数类型、函数名、函数参数组成,这部分称为函数头。函数名遵守标识符命名规则。
大括号称为函数体,里面写函数执行的具体代码。
函数定义时只能在全局范围定义,不能在局部定义,不能在函数内部再定义函数。 - 函数类型与返回值:
函数可以有返回值,也可以没有。
函数类型就是指函数的返回值类型,可以是C++支持的任意类型。
函数返回值是由return语句给出的。
当函数不需要返回值的时候,返回值类型要用void空类型指定 - 函数参数:
形参(形式参数):定义函数时括号中的参数
实参(实际参数):调用函数时括号中的参数 - 函数调用:函数名(实参);
函数声明:
声明一个函数,就是写出函数的原型(函数头部),但没有写出函数体(实现部分)。主要用于告诉编译器,它的实现放在其他地方。
如果函数没有声明,调用的时候就要按照顺序书写,就要在前面定义好函数,后面才能调用函数。否则就会找不到函数报错。
2. 函数参数传递
函数实参可以是常量、变量、表达式。在函数调用时,系统为形参分配内存空间并且将实参的值传到形参的空间中。
根据函数的给你需要,可以选择三种不同的传参方式:值传递、指针传递、引用传递
1)值传递:实参的值传递给形参
2)指针传递:指针作为函数的参数
3)引用传递:引用传递本质上也是传递的地址
//值传递:用来交换主调函数中俩个变量的值。
void swap(int x,int y)
{cout << "&x=" << &x << " &y=" << &y << endl;int temp = x;x = y;y = temp;
}void test02()
{int n1 = 10, n2 = 20;cout << "&n1=" << &n1 << " &n2=" << &n2 << endl;swap(n1,n2);//交换n1,n2的值cout << "调用swap函数之后:" << endl;cout << n1 << " " << n2;//交换失败,原因:值传递只是把n1,n2实参的值传递给形参x和y,函数内部交换的是x、y,不会对n1、n2进行操作
}
//用指针传递,交换主调函数中俩个变量的值。
void swap_p(int* x,int* y)
{cout << "x=" << x << " y=" << y << endl;int temp = *x;*x = *y;*y = temp;
}void test03()
{int n1 = 10, n2 = 20;cout << "&n1=" << &n1 << " &n2=" << &n2 << endl;swap_p(&n1, &n2);//交换n1,n2的值cout << "调用swap_p函数之后:" << endl;cout << n1 << " " << n2;//交换成功,因为指针传递的是地址,函数内部操作的是n1和n2的地址。
}//用引用传递,交换主调函数中俩个变量的值。
void swap_r(int& x, int& y)
{cout << "&x=" << &x << " &y=" << &y << endl;int temp = x;x = y;y = temp;
}void test04()
{int n1 = 10, n2 = 20;cout << "&n1=" << &n1 << " &n2=" << &n2 << endl;swap_r(n1, n2);//交换n1,n2的值cout << "调用swap_r函数之后:" << endl;cout << n1 << " " << n2;//交换成功
}
引用传递与值传递用法上的区别:
- 引用传递:调用函数时,实参只能是变量。主要用在复杂数据类型(自定义类型)传递时,可以节省空间和时间。
- 值传递:调用函数时,实参可以使变量、常量或者表达式。
//俩个函数,分别是值传递和引用传递,计算圆的面积
double area1(double radius)
{double a = 3.14 * radius * radius;return a;
}
double area2(double& radius)
{double a = 3.14 * radius * radius;return a;
}void test05()
{double r = 10;//变量cout << "值传递:(变量)" << area1(r) << endl;cout<<"值传递:(常量)"<< area1(100) << endl;cout<<"值传递:(表达式)" << area1(r*2) << endl;cout << "引用传递:(变量)" << area2(r) << endl;//以下不支持//cout << "引用传递:(常量)" << area2(100) << endl;//cout << "引用传递:(表达式)" << area2(r * 2) << endl;
}
数组作为函数参数:
数组名实际上是数组的首元素地址,所以数组作为函数参数传递其实传递的是首元素地址,无法将整个数组传递。
同样如果想返回一个数组,也是返回一个指针。
//编写一个函数,将整型数组中的元素按照相反的顺序来存放,即翻转数组
void my_reverse(int* p,int n)//第一个元素传递的是数组的首地址,第二个参数传递的是数组的元素个数;仅凭数组的首地址,无法获取元素个数
{//用俩个指针实现交换,分别指向第一个元素和最后一个元素,然后一个指针往后走,一个指针往前走,直到他俩相遇。int* i, * j, temp;for (i = p, j = p + n - 1;i < j;i++, j--){temp = *i;*i = *j;*j = temp;}
}void test06()
{int a[10] = { 0,1,2,3,4,5,6,7,8,9 };int n = sizeof(a)/sizeof(int);my_reverse(a, n);for (size_t i = 0; i < n; i++){cout << a[i] << " ";}
}
3. 变量的声明周期
每个标识符都有确定的作用域,决定他们在什么范围内能被访问。
整个作用域又决定了他们的生命周期。
栈内存中的局部变量在进入它的作用域时在内存中创建,在离开它的作用域后,被编译器自动回收释放。
堆内存中的变量需要程序员自己管理,它的生命周期由程序员决定。
- 局部变量和全局变量:
函数内定义的变量是局部变量,他只在函数范围内有效。
在函数外定义的变量是全局变量,它在整个文件内有效。
全局变量在当前文件的任何函数中都可以访问。
如果我们在不同的作用域下定义了多个同名变量的话,访问规则是优先访问作用域最小的那个(离你最近的那个)
如果要在某个作用域下访问同名的全局变量,那么可以在变量前面加域运算符::
变量的存储方式:
1)静态存储
静态存储是指在程序运行期间,给变量分配的内存一直有效。
定义:static 数据类型 变量名;
2)动态存储
是指在程序运行期间,根据需要动态管理变量的内存空间。如局部变量,当执行到该函数时,就为局部变量分配空间,
当离开函数时,局部变量就被释放。下次在执行到该函数时,再重新分配。这些是通过编译器在栈内存自动完成的。
堆内存也属于动态存储,但是变量的生命周期由程序员自己决定。
4. 函数的其他特性
内联函数==(inline==):
就是将函数嵌入到可执行程序中,而不需要去调用的函数。
有一些函数需要频繁调用而且函数内部的代码规模很小,逻辑简单。为了减少函数调用的开销(包括寻找函数地址,传参…),
可以将这种函数设置为内联函数,相当于将他嵌入到函数的调用处。
注意:只有简单的函数才能成为内联函数,如果函数复杂,含有循环分支等结构,即使定义为内联函数,编译器也会当做普通函数处理。
定义:
inline 函数类型 函数名(形参列表);
- 函数重载:
可以定义多个同名,只要参数不同就可以(参数的个数或者类型不同即可)。
调用时,编译器会根据实际参数来选择匹配的函数去调用。这称为重载。 - 带默认参数值的函数:
函数定义时,可以预先给某些参数指定默认值,如果函数调用时没有给出实际参数,就会采用默认值,如果给出实参就会替换默认值。
注意:默认值必须从右往左依次给出。如果某个参数有默认值,那么它右边的所有参数都必须有默认值。
错误示范:int max(int a,int b=10,int c);
5. 函数的嵌套与递归
函数的嵌套指的是函数调用函数,是函数的嵌套。函数定义是不能嵌套。
递归:递归就是函数调用自身,一个函数可以通过直接或者间接的方式来调用自己实现递归。
写递归函数的关键点:
1)明确函数的功能
明确函数接收什么参数,返回什么结果,完成什么逻辑。例如阶乘,功能就是接收1个整数n,计算并返回n的阶乘。
2)确定递归的终止条件
递归函数必须有终止条件,否则会陷入无线递归,最终会导致栈内存被沾满,叫做栈溢出。
终止条件是递归函数停止调用自身的条件,通常对应着最简单的情况。
3)找出递归关系
递归关系是如何将一个大问题分解为多个小问题的过程。在阶乘例子中,n的阶乘是个大问题,分解为小问题,就是n的阶乘=n*(n-1)的阶乘
4)要确保递归调用的参数能逐渐逼近终止条件
每次递归调用时,传递给递归函数的参数要向终止条件逼近,阶乘例子中,每次调用时,n都-1,保证了最终达到终止条件
5)处理递归的返回值
在递归函数中,要将每次调用的返回值用于当前问题的求解。
//用递归的方式求n的阶层
long power(int n)
{if(n==1 || n==0){return 1;}return n * power(n - 1);
}void test13()
{cout << "请输入一个整数,小于10" << endl;int n;cin >> n;cout << "它的阶乘=" << power(n) << endl;
}