C++语言中的函数在其他编程语言中也称为过程或子例程。 我们可以创建函数来执行任何任务。 一个函数可以调用多次。
函数提供模块化和代码可重用性。
函数有很多优点,但这里主要介绍以下两点:
通过在C++中创建函数,可以调用函数多次。 所以实现相同的功能不需要一遍又一遍地编写 相同的代码。
函数能使代码优化,我们不需要写很多代码。假设,要检查多个数字(111,663和781等)是否是 素数。 如果不使用函数,需要编写计算质数逻辑程序3次。 所以,这里就产生了不必要的重 复代码。
C++编程语言有两种类型的函数
定义函数
type funcName(paramlist)
{
// todo
return value;
}
声明函数
type funcName(paramlist);
区别:
直接定义函数一般放在main函数之前,定义完即可使用
另外,可以在main函数前先声明,在mian之后定义函数
语法
void funcName(paramType1 param1, paramType2 param2)
{
// 执行语句...
}
定义了一个函数 funcName
,该返回的返回值类型是 type,如果没 有返回值,则写 void
。
该函数有两个参数,分别为 paramType1
类型的参数 param1
和 paramType2
类型的参数 param2
,函数的返回值为 void
。
引用即为某个已存在的变量取别名,引用变量与被引用变量共用一块内存空间,比如土豆和马铃薯都是同一种东西的不同命名。
通过在数据类型后、变量名前添加“&”符号来定义引用类型。
语法:
type &name=data;
int num = 11;
int &num2 = num; // num2
就是num的别名,num2就是num的引用
引用特性
一个变量可以有多个引用,就像人一样,有可能有多个昵称!
引用使用场景
做参数
void change (int &v1,int &v2){}
在函数被调用时,就相当于传递实参
#include
using namespace std;
void change(int & num)
{
num *= 2;
}
int main()
{
int x = 100;
change(x);
cout <<"x&#61; " <
}
语法
type& funcName(){}
当函数返回一个引用时&#xff0c;则返回一个指向返回值的隐式指针
当返回一个引用时&#xff0c;要注意被引用的对象不能超出作用域。所以返 回一个对局部变量的引用是不合法的&#xff0c;但是&#xff0c;可以返回一个对静态
变量的引用。
int& func(int& v1) {
v1 *&#61;100;
return v1;
}
int main() {
int num &#61; 2;
cout <
}
形参与实参
形参&#xff1a;函数括号里面的变量参数&#xff0c;叫形式参数
type funcName(type v1,type v2);
实参&#xff1a;调用函数时给的实值&#xff0c;叫实际参数
funcName(2,3);
语法
type funcName(paramType1 param1, ...)
{
// 执行语句...
return value&#xff1b;
}
可变参数&#xff0c;即函数参数的个数是任意的&#xff0c;最典型的可变参数的就是 系统内置的 scanf 函数和 printf 函数。
可变参数的函数必须至少有一个强制参数&#xff0c;可选参数的类型可以变 化。可选参数的数量由强制参数的值决定&#xff0c;或由用来定义可选参数 列表的特殊值决定。
对于每一个强制参数来说&#xff0c;函数头部都会显示一个适当的参数&#xff0c;像 普通函数声明一样。参数列表的格式是强制性参数在前&#xff0c;后面跟着 一个逗号和省略号&#xff08;…&#xff09;&#xff0c;这个省略号代表可选参数。
可变参数函数要获取可选参数时&#xff0c;必须通过一个类型为 va_list 的对象&#xff0c;它包含了参数信息。这种类型的对象 也称为参数指针&#xff08;argument pointer&#xff09;&#xff0c;它包含了栈中至少一个参数的位置。
可以使用这个参数指针从一个可选参数移动到下一个可选参数&#xff0c;由此&#xff0c;函数就可以获取所有的可选参数。 va_list 类型被定义在头文件 stdarg.h 中。
当编写支持参数数量可变的函数时&#xff0c;必须用 va_list 类型定义参数指针&#xff0c;以获取可选参数。当我们处理可变参 数时&#xff0c;主要涉及到以下几个宏函数&#xff1a;
va_start
语法
void va_start(va_list argptr, lastparam);
argptr
定义好的 va_list
变量。
lastparam
强制参数。
宏 va_start 使用第一个可选参数的位置来初始化 argptr 参数指针。该宏的第二个参数必须是该函数最后一个有 名称参数的名称。必须先调用该宏&#xff0c;才可以开始使用可选参数
va_arg
语法
type va_arg(va_list argptr, type);
argptr
定义好的 va_list 变量。
type
可变参数的具体类型。
展开宏 va_arg 会得到当前 argptr 所引用的可选参数&#xff0c;也会将 argptr 移动到列表中的下一个参数。宏 va_arg 的第二个参数是刚刚被读 入的参数的类型。
va_end
语法
void va_end(va_list argptr);
argptr定义好的 va_list 变量。
当不再需要使用参数指针时&#xff0c;必须调用宏 va_end。如果想使用宏 va_start 或者宏 va_copy 来重新初始化一个之前用过的参数指针&#xff0c; 也必须先调用宏 va_end。
va_copy
语法
void va_copy(va_list dest, va_list src);
dest
目的 va_list 变量。
src
源 va_list 变量。
宏 va_copy 使用当前的 src 值来初始化参数指针 dest。然后就可以 使用 dest 中的备份获取可选参数列表&#xff0c;从 src 所引用的位置开始。
函数可变参数 案例
用函数可变参数&#xff0c;实现求任意变量的和。
#include
#include
#include
using namespace std;
int mulSum(int n, ...)
{
int sum &#61; 0;
va_list argptr;
va_start(argptr, n);
for (int i &#61; 0; i < n; i&#43;&#43;)
{
// 初始化argptr
// 对每个可选参数&#xff0c;读取类型为int
sum &#43;&#61; va_arg(argptr, int); //累加到 sum 中
}
va_end(argptr);
return sum;
}
int main()
{
int result &#61; mulSum(4, 1, 3, 5, 8);
cout << "result &#61; " << result << endl;
return 0;
}
定义 函数 时可以给形参指定一个默认的值&#xff0c;这样调用函数时如果 没有给这个形参赋值&#xff08;没有对应的实参&#xff09;&#xff0c;那么就使用这个默认的 值。也就是说&#xff0c;调用函数时可以省略有默认值的参数。如果用户指 定了参数的值&#xff0c;那么就使用用户指定的值&#xff0c;否则使用参数的默认值。
所谓默认参数&#xff0c;指的是当函数调用中省略了实参时自动使用的一个 值&#xff0c;这个值就是给形参指定的默认值。
语法
type funcname(paramterType param1, paramterType2 param2 &#61; value)
{
return [val];
}
默认参数案例
#include
using namespace std;
int sum(int num1, int num2 &#61; 100)
{
int ret &#61; num1 &#43; num2;
cout << ret << endl;
}
int main()
{
int num1 &#61; 1;
int num2 &#61; 2;
sum(num1, num2);
sum(num1);
return 0;
}
C&#43;&#43; 中 函数 可以不返回任何值&#xff0c;也可以返回一个值&#xff0c;但 C&#43;&#43; 的函数不支持返回多个值。同时&#xff0c;C&#43;&#43; 函数的返回值需要显式的声明其 类型。
语法
type funcName(paramType1 param1, paramType2 param2)
{
// 执行语句...
return value;
}
函数可以嵌套调用&#xff0c;即&#xff0c;我们可以在一个函数里面调用另一个函数。
语法
void funcName()
{
funcName1();
funcName2();
[return]
}
函数递归就是一个 函数在函数体内又调用了自身&#xff0c;称为函数的递 归调用。
语法
type funcName(param)
{
if (param &#61;&#61; cond)
{
return;
}
funcName(param2);
}
递归条件
执行一个函数时&#xff0c;就创建一个新的受保护的独立空间(新函数栈)。
函数的局部 变量 是独立的&#xff0c;不会相互影响。
递归必须向退出递归的条件逼近&#xff0c;否则就是无限递归了。
当一个函数执行完毕&#xff0c;或者遇到 return&#xff0c;就会返回&#xff0c;遵守谁调用&#xff0c; 就将结果返回给谁&#xff0c;同时当函数执行完毕或者返回时&#xff0c;该函数本身也会被系统销毁。
**递归图示 **
递归案例
求1&#43;2&#43;3&#43;4&#43;5&#43;…&#43;n的和
int sum(int n){
int total &#61; 0;
for(int i&#61;1; i<&#61;n;i&#43;&#43;){
total &#43;&#61; i;
}
return toatl;
}
简单算法实现代码
int sum(int n){
return &#xff08;1&#43;n&#xff09;*n /2.0;
}
递归实现代码
int sum(int n){
if(n&#61;&#61;1){
return 1;
}
return sum(n-1) &#43; n;
}
递归案例2
递归实现斐波那契数列&#xff08;fibo&#xff09;的第n项。
int fibo(int n){
if(n <&#61; 2){
return 1;
}
else{
return fibo(n-1) &#43; fibo(n-2);
}
}
递归案例3 汉罗塔
移动过程中必须遵守以下规则&#xff1a;
每次只能移动柱子最顶端的一个圆盘&#xff1b;
每个柱子上&#xff0c;小圆盘永远要位于大圆盘之上.
分析&#xff1a;
只有一个盘子
两个盘子
对于 n 个圆盘的汉诺塔问题&#xff0c;移动圆盘的过程是&#xff1a;
void hanoi(int num, char sou, char tar,char aux) {
static int count &#61; 1;//统计移动次数
//如果圆盘数量仅有 1 个&#xff0c;则直接从起始柱移动到目标柱
if (num &#61;&#61; 1) {
cout << "第” <<count << “次:从”<< sou << “ 移动至”<<tar << endl;
count&#43;&#43;;
}
else {
//递归调用 hanoi() 函数&#xff0c;将 num-1 个圆盘从起始柱移动到辅助柱上
hanoi(num - 1, sou, aux, tar);
//将起始柱上剩余的最后一个大圆盘移动到目标柱上
cout << "第” <<count << “次:从”<< sou << “ 移动至”<<tar << endl;
count&#43;&#43;;
//递归调用 hanoi() 函数&#xff0c;将辅助柱上的 num-1 圆盘移动到目标柱上
hanoi(num - 1, aux, tar, sou);
}
}
int main()
{
//以移动 3 个圆盘为例&#xff0c;起始柱、目标柱、辅助柱分别用 A、B、C 表示
hanoi(3, &#39;A&#39;, &#39;B&#39;, &#39;C&#39;);
return 0;
}
在 C 语言 中&#xff0c;如果一些 函数 被频繁调用&#xff0c;不断地有函数入栈&#xff0c;即函数栈&#xff0c;会造成栈空间或栈内存的大量消耗。在 C&#43;&#43; 中&#xff0c;为了解决 这个问题&#xff0c;特别的引入了 inline
修饰符&#xff0c;表示为内联函数。
栈空间就是指放置程式的局部数据也就是函数内数据的内存空间&#xff0c;在系统下&#xff0c;栈空间是有限的&#xff0c;假如频繁大量的使用就会造成因栈空间不足所造成的程式出错的问题&#xff0c;函数的死循环 递归调用 的最终结果就是导致栈内存空间枯竭。
使用场景
函数是一个可以重复使用的代码块&#xff0c;CPU 会一条一条地挨着执行其中的代码。CPU 在执行主调函数代码时如果遇到了被调函数&#xff0c;主调函数就会暂停&#xff0c;CPU 转而执行被调函数的代码&#xff1b;被调函数执行完毕后再返回到主调函数&#xff0c;主调函数 根据刚才的状态继续往下执行。
一个 C/C&#43;&#43; 程序的执行过程可以认为是多个函数之间的相互调用过程&#xff0c;它们形成了一个或简单或复杂的调用链条&#xff0c;这个链条的起点是 main()&#xff0c;终点也是 main()。当 main() 调用完了所有的函数&#xff0c;它会返回一个值&#xff08;例如 return 0;&#xff09;来结 束自己的生命&#xff0c;从而结束整个程序。
函数调用是有时间和空间开销的。程序在执行一个函数之前需要做一些准备工作&#xff0c;要将实参、局部变量、返回地址以及若干寄存器都压入栈中&#xff0c;然后才能执行函数体中的代码&#xff1b;函数体中的代码执行完毕后还要清理现场&#xff0c;将之前压入栈中的数据都出栈&#xff0c;才能接着执行函数调用位置以后的代码。
如果函数体代码比较多&#xff0c;需要较长的执行时间&#xff0c;那么函数调用机制占用的时间可以忽略&#xff1b;如果函数只有一两条语句&#xff0c; 那么大部分的时间都会花费在函数调用机制上&#xff0c;这种时间开销就就不容忽视。
为了消除函数调用的时空开销&#xff0c;C&#43;&#43; 提供一种提高效率的方法&#xff0c;即在编译时将函数调用处用函数体替换&#xff0c;类似于 C 语言中的宏展开。这种在函数调用处直接嵌入函数体的函数称为内联函数&#xff08;Inline Function&#xff09;&#xff0c;又称内嵌函数或者内置函数。
内联函数定义
关键字 inline 必须与函数定义体放在一起才能使函数成为内联&#xff0c;仅将 inline 放在函数声明前面不起任何作用。
语法
inline returntype funcname(paramterType params) // inline 与函数定义体放在一起
{
return [val];
}
技术细节
如下的函数风格&#xff0c;不是内联函数&#xff0c;因为内联函数的 inline 关键字&#xff0c;只能放在函数定义的地方&#xff1a;
inline void Foo(int x, int y); // inline 仅与函数声明放在一起
void Foo(int x, int y)
{
}
inline关键字
inline 是一种 “用于实现的关键字”&#xff0c;而不是一种 “用于声明的关键字”。一般地&#xff0c;用户可以阅读函数的声明&#xff0c;但是看不到函数的定义。尽管在大多数教科书中内联函数的声明、定义体前面都加了 inline 关键字&#xff0c;但我认为 inline 不应该出现在函数的声明中。
这个细节虽然不会影响函数的功能&#xff0c;但是体现了高质量 C&#43;&#43;/C 程序设计风格的一个基本原则&#xff1a;声明与定义不可混为一谈&#xff0c;用户没有必要、也不应该知道函数是否需要内联
使用限制
inline 的使用是有所限制的&#xff0c;inline 只适合涵数体内代码简单的函数数使用&#xff0c;不能包含复杂的结构控制语句例如 while**、switch 并且内联函数本身不能是直接递归函数(自己内部还调用自己的函数)**
内联函数 案例
判断多个数是否为偶数&#xff1f;
#include
using namespace std;
inline bool isEven(int num)
{
return num % 2 &#61;&#61; 0;
}
int main()
{
int num1 &#61; 100;
int num2 &#61; 101;
bool b1 &#61; isEven(num1);
bool b2 &#61; isEven(num2);
cout << “b1 &#61; “ << b1 <<endl;
cout << "b2 &#61; " << b2 << endl;
return 0;
}
内联函数 和 普通函数 最大的区别在于内部的实现方面&#xff0c;当普通函数在被调用时&#xff0c;系统首先跳跃到该函数的入口地址&#xff0c;执行函数体&#xff0c;执行完成后&#xff0c;再返回到函数调用的地方&#xff0c;函数始终只有一个拷贝。
而内联函数则不需要进行一个寻址的过程&#xff0c;当执行到内联函数时&#xff0c;此函数展开&#xff08;很类似 宏 的使用&#xff09;&#xff0c;如果在 N 处调用了此内联函数&#xff0c;则此函数就会有 N 个代码段的拷贝。
在 C中&#xff0c;我们定义的 变量名 和函数名在同一作用域一定是不允许 重复的&#xff0c;因此&#xff0c;如果我们需要定义几个功能类似的函数&#xff0c;那么我们必须想办法给它们做不同的命名。
在 C&#43;&#43; 中&#xff0c;允许我们为函数设置同样的名字&#xff0c;但这些函数的参数列表的顺序或者类型必须不一致&#xff0c;这就是 C&#43;&#43; 中的函数重载。
函数重载是 C&#43;&#43; 允许在同一作用域中声明几个类似的同名函数&#xff0c;这些同名函数的形参列表&#xff08;参数个数&#xff0c;类型&#xff0c;顺序&#xff09;必须不同&#xff0c;常用来处理实现功能类似数据类型不同的问题。在 C&#43;&#43; 中不仅函数可以重载&#xff0c;运算符也可以重载。
函数重载规则
函数名称必须相同。
参数列表必须不同&#xff08;个数不同、类型不同、参数排列顺序不同等&#xff09;。
函数的返回类型可以相同也可以不相同。
仅仅返回类型不同不足以成为函数的重载。
**如何做到函数重载 **
C&#43;&#43; 代码在编译时会根据参数列表对函数进行重命名&#xff0c;例如 void Swap(int a, int b)
会被重命名为 _Swap_int_int&#xff0c;void Swap(float x, float y)
会被重命名为_Swap_float_float
。
当发生函数调用时&#xff0c;编译器会根据传入的实参去逐个匹配&#xff0c;以选择 对应的函数&#xff0c;如果匹配失败&#xff0c;编译器就会报错&#xff0c;这叫做重载决议&#xff08;Overload Resolution&#xff09;。从这个角度讲&#xff0c;函数重载仅仅是语法层面的&#xff0c;本质上它们还是不同的函数&#xff0c;占用不同的内存&#xff0c;入口地址也 不一样。
案例
利用函数重载&#xff0c;实现求两个数的和
int sum(int a,int b){
return a&#43;b;
}
float sum(float a,float b){
return a&#43;b;
}
double sum(double a,double b){
return a&#43;b;
}
利用函数求出num1-num2之间的所有素数&#xff08;num1 bool isPrime&#xff08;int n){ // 判断是否为素数
bool flag &#61; 1;
for(int i&#61;2; i<&#61;n/2;i&#43;&#43;){
if(n % i &#61;&#61; 0){
return 0;
}
}
return 1;
}void prime&#xff08;int num1,int num2){ // 输出num1 -num2之间的素数
for(int i &#61; num1; i<&#61;num2; i&#43;&#43;){
if(isPrime(i)){
cout << i << “ “;
}
}
}int main(){
int num1 ,num2;
cin >> num1 >> num2;
prime(num1,num2);
return 0;
}