C/C++ 语法教程(8)——函数(1)

C/C++ 语法教程(8)——函数(1)

Contents

函数

因为预处理命令的横插一脚,导致极其重要的函数被置后了一章。

前言

函数是什么

这里的函数,比起数学中的更特殊。

程序中的函数可以认为是 $f:input \to output$。

最常见的就是 main 函数,它(一般)没有输入的参数,它的返回值也就是一般写的 0return 0;)。

一般来说,函数就是输入若干个(可以为 $0$ 个各种类型的变量参数,(一般)返回某种类型的变量参数。

前面的 scanfprintfsqrtabs 等等都是某种函数。

函数有何用

除了有时候需要的输入输出、数学函数以外,我们还可能需要自己写的函数,那么这些函数是用来干什么的呢?

函数直接意义上用于减少重复工作。

比如,你需要交换两个数,你可能会这样写 :

int tmp=a;
a=b;
b=tmp;

但如果你可以自己写个函数(具体怎么写后面会讲):

void Swap(int &a,int &b)
{
    int tmp=a;
    a=b;
    b=tmp;
}

那么你每次需要交换时只需要 swap(a,b); 即可。

除此之外,函数还可以调用自身,形成递归,在一些算法中可能会频繁使用。(递归知识点会详细讲解)

函数的基本使用

函数定义

一般在 C/C++ 中,函数定义如下形式:

return_type function_name( parameter list )
{
   body of the function
}

各个部分的意义如下:

  • 返回类型:一个函数可以返回一个值,通过 return 语句加上返回值实现。return_type 是函数返回的值的数据类型,就像是 intdouble 等等。有些函数执行所需的操作而不返回值,在这种情况下,return_type 是关键字 void,这时退出程序语句变为了 return;
  • 函数名称:这是函数的实际名称,也就是你调用它写的名字,比如 minsqrt 等等。函数名和参数列表一起构成了函数签名。
  • 参数:参数就像是占位符。当函数被调用时,您向参数传递一个值,这个值被称为实际参数。参数列表包括函数参数的类型、顺序、数量。参数是可选的,也就是说,函数可能不包含参数。(比如一般情况下的 main 函数)
  • 函数主体:函数主体包含一组定义函数执行任务的语句,写法一般没有什么限制。

一个实例

int max(int num1, int num2) 
{
   // 局部变量声明
   int result;

   if (num1 > num2)
      result = num1;
   else
      result = num2;

   return result; 
}

这个就是返回较大值的函数 max

函数声明

函数声明会告诉编译器函数名称及如何调用函数函数的实际主体可以单独定义

函数声明包括以下几个部分:

return_type function_name( parameter list );

针对上面定义的函数 max,以下是函数声明:

int max(int num1, int num2);

在函数声明中,参数的名称并不重要,只有参数的类型是必需的,因此下面也是有效的声明:

int max(int, int);

当您在一个源文件中定义函数且在另一个文件中调用函数时,函数声明是必需的。在这种情况下,您应该在调用函数的文件顶部声明函数。

同时如果你需要在一个函数定义前就调用它,你也需要在调用前面某处声明函数。

比如:

void b(int);
void a()
{
    //do something...
    b(1);
}
int main()
{
    a();
}
void b(int x)
{
    //do something...
}

a 中调用尚未定义的 b,就需要在 a 之前声明函数。

函数调用

简单来说就是按顺序传对应的变量,在括号中声明,用逗号隔开。

比如调用上述 max 函数,就应该使用这样的形式 max(x,y)

当然如果没有参数,就可以类似于 f() 这样的方法调用。

对于函数的返回值,也可以将其应用于表达式中,比如 if ((max(a,b)+1)/2>0) 这样类似的形式。

注意,函数调用只能在之前的代码中函数已经声明或者定义的情况下进行。这也就是为什么上面的例子需要声明。

函数参数

首先有实在参数形式参数之分。

实在参数是指调用函数时传入函数的参数。

比如 max(x,y) 的调用中 $x,y$ 就是实在参数。

如果函数要使用参数,则必须声明接受参数值的变量。这些变量称为函数的形式参数

形式参数也就是函数定义中的参数。

比如上面 max 函数的实例中,num1num2 就是形式参数。

形式参数就像函数内的其他局部变量,在进入函数时被创建,退出函数时被销毁。(局部变量相关见后)

当调用函数时,有三种向函数传递参数的方式:

调用类型描述函数定义(声明)时使用方法
传值调用把参数的实际值复制给函数的形式参数,修改函数内的形式参数对实际参数没有影响。形如 int a
指针调用把参数的地址复制给形式参数,该地址用于访问调用中要用到的实际参数。这意味着,修改形式参数会影响实际参数。形如 int *a
引用调用把参数的引用复制给形式参数,该引用用于访问调用中要用到的实际参数。这意味着,修改形式参数会影响实际参数。形如 int &a

上面的 max 函数就使用了传值调用,swap 就使用了引用调用。

指针调用一般较少使用。

当然在调用中,这些调用可以在一个函数的不同形式参数中使用。

比如 int f(int *a,int b,int &c) 也是完全没有问题的。

最后注意一下,在函数声明时,如果遇到了指针调用和引用调用,也应该用相同方法。

比如 int f(int *, int, int &); 或者 int f(int *a,int b,int &c);

数组作为参数

函数定义(声明)时如果需要传输数组参数的时候,可以使用两种方法,注意这里传过去的数组进行的修改都是在原地址上进行的,也就是都相当于引用调用,会对实际数组参数产生影响

int f(int a[])
{
    //do something...
}
int g(int *a)
{
    //do something...
}
int main()
{
    int a[100];
    f(a);
    g(a);
}

其中函数中,可以直接使用 a[i] 的方法调用。

我们会发现这里指针调用可以用于传输数组,这正与我们前面所说数组本质上就是指针的说法相符合。

参数的默认值

当您定义一个函数,您可以为参数列表中后边的每一个参数指定默认值。当调用函数时,如果实际参数的值留空,则使用这个默认值。

这是通过在函数定义中使用赋值运算符来为参数赋值的。调用函数时,如果未传递参数的值,则会使用默认值,如果指定了值,则会忽略默认值,使用传递的值。请看下面的实例:

#include <iostream>
using namespace std;

int sum(int a, int b=20)
{
    int result;

    result = a + b;

    return (result);
}

int main ()
{
    // 局部变量声明
    int a = 100;
    int b = 200;
    int result;

    // 调用函数来添加值
    result = sum(a, b);
    cout << "Total value is :" << result << endl;

    // 再次调用函数
    result = sum(a);
    cout << "Total value is :" << result << endl;

    return 0;
}

输出结果会是:

Total value is :300
Total value is :120

全局变量与局部变量

划重点,这是非常重要的内容。

语句块

在前面接触了用大括号 {\* do something...*\} 中添加若干语句使得其可以代替原来的一个语句的方法。

具体来说,就是 ifforwhile 等语句如果后接超过一个语句的情况。

我们一般称这对大括号和其中的语句为语句块

一个 main 函数的主体也是一个语句块。

全局变量

对于只有一个源文件的程序,一般认为,不在任何语句块中定义的变量称为全局变量

一般这些变量会定义在所有语句块(函数)之前,这样程序的任何位置都可以调用这些变量。

当然,如果是在某些函数之后定义这些变量,这些函数就无法调用这些全局变量。

比如:

#include <bits/stdc++.h>
using namespace std;
void f()
{
    a++;
}
int a=0;
int main()
{
    f();
    return 0;
}

这段程序就会报错。

局部变量

在代码块中定义的变量就称为局部变量

它们只能被函数内部或者代码块内部的语句使用。就像前面说的形式参数一样,它们会在结束程序时被销毁。

#include <iostream>
using namespace std;
int main ()
{
    // 局部变量声明
    int a, b;
    int c;
    // 实际初始化
    a = 10;
    b = 20;
    c = a + b;
    cout << c;
    return 0;
}

return 0 后,$a,b,c$ 这些局部变量就会被自动销毁。

特别注意,局部变量可以与全局变量重名,这时在局部变量的定义域中调用的实际上就是局部变量而不是全局变量。

比如:

#include <iostream>
using namespace std;
// 全局变量声明
int g = 20;
int main ()
{
    // 局部变量声明
    int g = 10;
    cout << g;
    return 0;
}

输出就会是 10

当然如果你一定要在局部变量的作用域中调用同名全局变量,还是可以使用域名解析的方法调用。

比如:

#include <iostream>
using namespace std;
// 全局变量声明
int g = 20;
int main ()
{
    // 局部变量声明
    int g = 10;
    cout << ::g;
    return 0;
}

就会输出 20

其它关于 externstatic 修饰符的相关内容,此处不进行讲解,可以自行学习。

函数习题

  1. 写一个函数 bool isPrime(int x); 用于判断给出的 x 是否是质数,不要求多优秀的算法。
  2. 写一个函数 int getPos(int a[],int n,int x); 用于得出 x 在升序 a[] 数组中的位置,a[] 中元素为 a[1]~a[n]。无论使用朴素算法还是二分法(见数算教程第一章)皆可。

 

点赞 0

No Comments

Add your comment