零基础学C语言——函数

这是一个C语言系列文章,如果是初学者的话,建议先行阅读之前的文章。笔者也会按照章节顺序发布。

上一篇我们介绍了表达式和语句,这一篇我们介绍C语言中的函数。

首先,我们先来看个例子:

int add(int num1, int num2)
{
  return num1 + num2;
}

上面这段内容就是一个完整的C语言函数。

函数的功能

函数与我们在数学中了解到的函数概念比较相似。粗略地讲,在数学中,函数是用来完成某种功能的,例如,sin、cos等。在C语言中,与之类似,都是用于封装某种功能的。函数不但将功能封装成小的个体函数,还可以在需要的地方直接调用,避免通篇复制功能细节代码。

上面的例子就是计算两数相加的结果。当我给出具体的数值,例如1和2,那么这个函数就会返回3,其中1和2我们称作输入参数,3称作函数的输出(或者 返回值)

读过码哥前面几篇文章的读者可能记得,在表达式一节中,函数调用表达式的值就是函数返回值,我们将在下面进一步介绍。

函数命名规范

在C语言中,函数的命名规范与变量的命名规范是一样的,都是以下划线(_)或字母开头,后续字符可以是字母、数字、下划线。下面给出一些例子:

_abc     符合命名规范
123abc   不符合规范
abc      符合规范
~abc     不符合规范

函数定义的一般形式

由于在C语言中有多种基础数据类型,因此,函数的参数类型、参数数量、返回值类型都有所不同。除去这些不同,其他部分则是有统一形式的。

返回值类型 函数名 (参数1, ...)
{
    各种语句
    return 返回值;
}

其中:

函数参数

根据功能需要可有参数也可以没有参数。例如:

int get10(void)
{
  return 10;
}
//或者
int get10()
{
  return 10;
}

对于不需要参数的函数,可以在参数列表处写void,也可以省略不写。

返回值

如果函数不需要返回值,那么return语句可以省略,但是函数 返回值类型 处要写void。

如果不写返回值类型,那么编译器默认为int型。

void print_help(void)
{
  printf("Help information\n");
}
或
void print_help(void)
{
  printf("Help information\n");
  return;
}

其中,printf是C标准IO库中定义的终端输出函数,初学者暂时不必纠结于此,只记住这个函数可以将里面的字符串部分输出到屏幕上。

可能有的读者会发现,笔者从来没说过基础数据类型中有字符串类型。是的,C语言中确实没有这个基础类型,字符串在C中都是以字符数组出现的,关于数组相关内容,笔者将会在后面文章中讲解。

函数的声明

函数的声明是用来告知编译器,函数在当前代码文件或者其他代码文件中有定义。

为何有这种需求呢?我们来看下面这个例子:

int main(void)
{
  foo();
}

void foo(void
        )
{
}

这个例子的功能是,在main函数中调用foo函数。

将代码直接写入文件然后编译,编译器会报错,大致是说main中的foo和外面的foo类型冲突。一般导致这样的报错有两种原因:

  1. 函数名称重复了,即定义了两个同名函数,且返回值类型或参数类型、参数数目不同
  2. 函数没重复但是缺少函数声明

这里,是因为第二个原因导致的。为何是第二个呢?

由于因为C语言编译器在处理源文件(即代码文件)时,是由上至下,由左至右的读取和处理文件内容的,因此就会对函数定义的位置、顺序较为敏感。在上面的这个例子中,当处理到main时,编译器并不知道这个文件中定义了foo函数,因此当看到main函数中调用了foo函数时就会误将调用当函数声明。而后当读取到foo函数定义时,发现和之前的声明不一致(返回值类型)。

解决方法很容,见下例:

void foo(void);

int main(void)
{
  foo();
}

void foo(void)
{
}

再次编译即可通过。

我们在main函数定义前加入的内容就是foo函数的声明。

函数声明也有其一般形式

返回值类型 函数名(参数列表);

其中,参数列表部分,若无参数可以省略不写或者写void,与定义一样。但如果有参数,则可省略参数名,亦可不省略。例如:

int add(int num1, int num2);
或
int add(int, int);

都是可行的,因为编译器在读取声明时只关心函数参数的类型,而不关心具体名字。

函数调用

上面我们其实已经见过函数的调用了。下面先说其一般形式:

函数名(输入参数列表);

例如,

add(1, 2); //调用最开始定义的add函数,函数有返回值,但是并未使用到

int result = add(1, 2); //调用add函数,并将返回值赋给变量result,result的值为3

函数参数

在C语言中,我们将函数定义中的参数称做形式参数(形参),函数调用中的参数称作实际参数(实参)

上面的例子中,1和2就是实参,而定义中的num1和num2就是形参。

在一些关于C语言的文章中,有人说参数的传递包含两种方式,这点笔者不敢苟同。在C语言中只有一种传参方式——值传递

其中,参数传递是指:调用函数时,将实际参数传递给被调用函数的形式参数,也可以看作是一种映射过程。

值传递(此处内容建议初学者在阅读后续数组和指针文章后再来阅读

值传递最直接的理解就是传递数值。

在C语言中,基础数据类型的参数传递比较显而易见,例如上面add调用的例子,传递的是两个整数值。但是数组的传递,对于一些写惯脚本语言的开发者来说会不太习惯。看一个例子:

int new_add(int *nums)
{
  return nums[0] + nums[1];
}
int main(void)
{
  int nums = {1, 2};
  int result = new_add(nums);
  return 0;
}

这里,在main中定义了一个整型数组,其中含有两个元素分别是1和2。我要将数组传给new_add函数,然后让函数返回两个元素相加的结果。此时,我们传递给new_add函数的参数并非完整数组的拷贝,而是数组的首地址,即指针。而指针也是一个值(可被看作无符号长整型),而非一种结构。

main函数

main函数也被叫做主函数,每一个生成可执行程序的C语言工程都一定有一个main函数,缺失主函数,编译器会报错。所有C程序都会从main函数开始执行

在UNIX系统下(Linux、OSX等),主函数的形式如下:

int main(void)
{
  ...//具体内容
  return 0;//具体返回值可自己定但一定是整数
}
或
int main(int argc, char *argv[])
{
  ...//具体内容
  return 0;//具体返回值可自己定但一定是整数
}

其中,argc是命令行参数个数,而argv则是每一个命令行参数内容组成的字符指针数组,例如这个源文件生成的可执行程序叫a,那么在命令行下执行:

$ ./a argument1 2 3

此时,argc为4,而argv的内容为: "./a", "argument1", "2", "3"这4个字符数组,即可执行程序名和其命令行参数。关于数组和指针的话题我们后面文章中会介绍。

递归函数

下面我们介绍一种特殊的函数调用方式。先来看个例子:

void recursive_func(int i)
{
  if (i > 3)
    return;
  recursive_func(++i);
}
int main(void)
{
  recursive_func(0);
  return 0;
}

在这个例子中,main函数中调用了一个名为recursive_func的函数,而recursive_func函数就是一个递归函数,即在函数中调用其自身。在main中传给recursive_func的参数i的值为0,然后在recursive_func内部每次调用自身时都将函数参数自增1,如此一层层调用下去。当i增加到3时,函数停止自身调用,然后逐层返回。



本篇涉及一些后续文章中的内容,实在无法避免,建议初学者在通读后,先继续阅读后续文章,然后再返回本篇复读一遍。


喜欢的读者可以关注码哥,也可以在下方留言评论,如果有不清楚的地方也可以私信码哥,我会第一时间回复。

感谢阅读!

举报
评论 0