C语言函数讲解(一)

0.415字数 4373阅读 234

C语言函数(一)

谨记

短暂的人生中,注定有很多人陪伴着你度过,陪你一起欢呼,陪你一起伤心流泪,陪你一起走过一条又一条的街道,或许他们并不是真真切切的,但是至少他们还在你身边。这个世界很大,但是却又很小,大的时候你要找到的你想要的却始终也找不到的,小的时候你要逃避或者不想要的却始终在你身边徘徊着,曾经有一段时间,自己很堕落,不知道干嘛,每天就浑浑噩噩的过完一天又一天,还把自己的笔名换成“徘徊的小青年”,那个时候,整个人生已经失去意义了,知道后来有一天看到了一个画面,我才明白,其实还有很多东西是需要自己努力去奋斗的,请你一定要记住,不管你做什么,身处何境,请不要抱怨,相信自己,你会成功的。

前言

非常的抱歉,由于其他事耽误了,文章没有及时的更新,望各位读者体谅。今天给大家带来的是咋们C语言中很重要的一个知识点——函数,前面我们对指针已经有一个深刻的理解了,那么,下面我们来看看函数吧,通过本篇文章你可以学到:

  • 函数的定义和声明
  • 函数的调用、传参和返回值
  • 函数的分类展示
  • 函数和数组
  • main函数机制

函数的定义和声明

1、函数的定义
函数是一个完成特定功能的代码模块,通常有参数,也可以没有参数;通常要求有返回值,也可以是空值。一般形式如下:

<数据类型>  <函数名称>( <形式参数说明> )
{
            语句序列;
            return[(<表达式>)];
} 

函数名称是一个标识符,要求符合标识符的命名规则。
数据类型是整个函数返回值的类型。这里可以包括存储类型说明符、数据类型说明符以及时间域说明符。如果函数不需要有返回值时,函数类型说明符可以写为void。
形式参数说明就是形式参数的列表,简称形参。形参可以是任意类型的变量,各参数之间用逗号分隔。在进行函数调用时,调用函数将赋予这些形式参数实际的值。
函数名后有一对花括弧。“{}”中的内容称为函数体。函数体中有若干条语句(大于或等于零个),实现特定的功能。注意:在函数体中,表达式语句里使用的变量必须事先已有说明,否则不能使用。
return[(<表达式>)]语句中表达式的值,要求和函数名前的数据类型保持一致。如果数据类型为void,可以省略,也可以写成“return;”。
例如定义一个简单的函数

无形参的函数:
void test(){

    printf("这是void一个函数\n");
}
有形参的函数
void test(int a){
    
    int b = 20;
    b = b + a;
    printf("%d\n",b);
}

2、函数的声明
函数的声明就是把函数名、返回值类型以及形参类型、个数和顺序通知编译系统,以便在调用该函数时,编译系统进行对照检查,包括函数名是否正确、传递参数的类型、个数是否与形参一致。如若出现不对应的情况,编译会有语法错误。
在C语言中,用户可以通过两种方法来进行函数声明。
① 如果函数调用前,没有对函数作声明,且同一源文件的前面出现了该函数的定义,那么编译器就会记住它的参数数量和类型以及函数的返回值类型,即把它作为函数的声明,并将函数返回值的类型默认为int型。
② 如果在同一源文件的前面没有该函数的定义,则需要提供该函数的函数原型。用户自定义的函数原型通常可以一起写在头文件中,通过头文件引用的方式来声明。

提示;实际上,如果在调用函数之前没有对函数进行声明,则编译系统会把第一次遇到的该函数形式(函数定义或函数调用)作为函数的声明,并将函数返回值的类型默认为int型。
在前面的内容中介绍过变量的声明。对于全局变量的声明可以加上extern标识,同样对于函数的声明,也可以使用extern。如果函数的声明中带有关键字extern,是告诉编译器这个函数是在别的源文件里定义的。还有一个关键字static,若全局变量前有static修饰,则变量只能在当前文件中被使用,同样若函数的声明前面有static限制,是告诉编译器,该函数只能在当前文件中被调用,外部文件不能调用。

1、函数的分类
对于函数的分类,我们可以从返回值、形参进行一个分类,一般分为4类:

  • 没有返回值和形参
  • 没有返回值,有形参
  • 有返回值没有形参
  • 有返回值,有形参
    接下来分别写一个简单的示例代码看看这四种函数的写法
#include <stdio.h>
//=======函数的声明部分========
void test();
void test1(int a);
int test2();
int test3(int a,int b);
//========主函数===========
int main(int argc, const char * argv[]) {
    //======这里可以调用函数
    printf("Hello, World!\n");
    return 0;
}
//=======函数的实现部分========
void test(){
    printf("这是没有返回值,没有参数的函数\n");
}
void test1(int a){
    int k = 10;
    k = k + a;
    printf("%d\n",k);
    printf("这是没有返回值,有形参的函数\n");
}
int test2(){
    printf("有函数返回值,没有形参的函数\n");
    return 10;
}
int test3(int a,int b){
    
    printf("有函数返回值,有形参的函数\n");
    return a + b;
}

在实际开发中,需要哪一种函数,就可以去自己按照函数的形式进行声明和实现那种函数。

函数的调用、参数的传递和返回值

1、函数的调用
函数的使用也叫函数的调用,形式如下:
函数名称(实际参数)
注意:
<函数名称>是一个标识符,符合标识符的命名规则;
(实际参数)需要确切的数据,也可以是具有确定值的表达式。实参就是在使用函数时,调用函数传递给被调用函数的数据,用以完成所要求的任务。
函数的参数分为形式参数和实际参数两种。
形式参数指的是出现在函数定义中的参数列表,简称形参。实际参数简称实参,出现在主调函数中。发生函数调用时,主调函数把实参的值传送给被调函数的形参,从而实现主调函数向被调函数的数据传送。

提示
函数调用可以作为一个运算量出现在表达式中,也可以单独形成一个语句。对于无返回值的函数来讲,只能形成一个函数调用语句。
如果是调用无参函数,则实参列表可以没有,但括弧不能省略。如果实参列表包含多个实参,则各参数间用逗号隔开。实参与形参的个数应相等,类型应一致。实参与形参按顺序对应,一一传递数据。这里,对实参列表取值的顺序并不是确定的,有的系统按自左至右顺序求实参的值,有的系统则按自右至左顺序。

示例代码:

#include <stdio.h>
//=======函数的声明部分========
void test();
//========主函数===========
int main(int argc, const char * argv[]) {
    //======这里可以调用函数
    test();
    return 0;
}
//=======函数的实现部分========
void test(){
    printf("这是没有返回值,没有参数的函数\n");
}
上面的写法,相当于把函数的定义写在了调用语句之前,test函数没有参数,也没有返回值。当被调用的函数有参数时,就涉及了参数的传递问题。

注意事项:被调函数必须是已经声明了的函数,或者被调函数的位置位于调用函数之前。
在有形参的时候,函数调用的时候就会传一个确切的实参来替代形参,这里要注意的是函数调用一般有3种用途:
① 函数语句:把函数调用作为一个语句。这时不要求函数带返回值,只要求函数完成一定的操作,如:
printf("Hello C world\n");
② 函数表达式:函数出现在一个表达式中,这种表达式称为函数表达式。这时要求函数返回一个确定的值以参加表达式的运算,如:
sum1 = sum(a,b);
③ 函数参数:函数调用作为一个函数的实参。函数调用作为函数的参数,实质上也是函数表达式形式调用的一种,因为函数的参数本来就要求是表达式形式,如:
printf("a和b的和是: %d\n",sum(a, b));

2、函数的参数传递
函数的参数传递,可以分为3种方式:

  • 值传递
  • 地址传递
  • 全局变量传递

2.1、值传递

#include <stdio.h>
void exchange(int a, int b){
    int t;
    printf("&a=%p &b=%p\n", &a, &b);
    t = a;
    a = b;
    b = t;
    printf("a=%d b=%d\n\n", a, b);
}
int main(int argc, const char * argv[]) {
    int m = 10, n = 20;
    exchange(m, n);
    printf("&m=%p &n=%p\n", &m, &n);
    printf("m=%d n=%d\n", m, n);
    return 0;
}
输出结果:
&a=0xbfcff4f0 &b=0xbfcff4f4
a=20 b=10
&m=0xbfcff50c &n=0xbfcff508
m=10 n=20
Program ended with exit code: 0

在该程序中,main函数调用了exchange函数。在exchange函数的定义中,参数a、b是形参,它在exchange函数体内都可以使用,离开函数体,则不能使用。主调函数是main函数,调用语句是“exchange(m,n)”,其中的m就是实参,且m是局部变量,只能在main函数体内使用。在执行了函数调用后,m的值就传给了形参a,这个传参过程自动进行,相当于执行了代码“int a = m; int b = n”。形参和实参是两个不同的变量,占用不同的存储空间,因此,当形参的值发生变化时,并不影响实参的值。

函数的形参和实参具有以下特点:
a.形参变量只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元。因此,形参只有在函数内部有效。调用结束返回主调函数后则不能再使用该形参变量。
b.实参可以是常量、变量、表达式、函数等,无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值,以便把这些值传送给形参。因此应先用赋值、输入等办法使实参获得确定值。
c.实参和形参在数量上、类型上、顺序上应严格一致,否则会发生“类型不匹配”的错误。
从图8-1中可以看出,实参和形参所占用的存储单元完全是独立的。在函数调用时,实参把存储单元中的数据赋值给形参的存储单元;而在函数调用后,若形参的值发生了改变,它也无法传递给实参(由于参数的传递是单向的,只能从实参传递给形参)。
2.2、地址传递
当你希望通过形参来改变实参的值的时候,那么你可以用地址传递,因为涉及到地址,其实就是我们以前说的指针,对于操作指针,是不是就相当于在操作指针指向的内存地址里面的对象。

#include <stdio.h>
void exchange(int *p, int *q){
    int t;
    printf("&p=%p &q=%p p=%p q=%p\n", &p, &q, p, q);
    t = *p;
    *p = *q;
    *q = t;
    printf("*p=%d *q=%d\n\n", *p, *q);
}
int main(int argc, const char * argv[]) {
    int m = 10, n = 20;
    exchange (&m, &n);
    printf ("&m=%p &n=%p\n", &m, &n);
    printf ("m=%d n=%d\n", m, n);
    return 0;
}
输出结果:
&p=0xbfeb67c0 &q=0xbfeb67c4 p=0xbfeb67dc q=0xbfeb67d8
*p=20 *q=10
&m=0xbfeb67dc &n=0xbfeb67d8
m=20 n=10
Program ended with exit code: 0

在该程序中,实参是&m和&n,形参是两个指针。传参的过程相当于两条语句“int p = &m; int q = &n;”。在函数exchange中,交换了p和q,即交换了p和q的目标,交换了变量m和n的值。在C程序中,通过传递变量的地址,可以起到改变主调函数中变量的作用。

2.3、全局变量传参
全局变量就是在函数体外说明的变量,它们在程序中的每个函数里都是可见的。实际上,全局变量也是一种静态型的变量。将它初始化为0。全局变量一经定义后就会在程序的任何地方可见。使用全局变量传递数据的先后顺序的不同会影响计算结果,应用顺序不当,会导致错误,这种方式尽量少用。(建议不要用)。

#include <stdio.h>
int n;//全局变量
double factorial();
int main(int argc, const char * argv[]) {
    double s = 0;
    printf("input:");
    scanf("%d", &n);
   s = factorial();
    printf("%e\n", s);
    return 0;
}
double factorial(){
    double ret = 1;
    int i;
    for (i = 1; i <= n; i++)
        ret *= i;
    return ret;
}
输出结果:
input:15
1.307674e+12
Program ended with exit code: 0

3、函数的返回值
函数的返回值是指被调用函数返回给调用函数的值。
对于函数的返回值,可以通过以下几点解释:
① 函数的返回值只能通过return语句返回主调函数,return 语句的一般形式为
return 表达式;
或者
return (表达式);
该语句的功能是计算表达式的值,并返回给主调函数。在函数中允许有多个return语句,但每次调用只能有一个return语句被执行,因此只能返回一个值。
② 函数返回值的类型和函数定义中函数的类型应保持一致。如果两者不一致,则以函数定义中的类型为准,自动进行类型转换。
③ 如函数返回值为整型,在函数定义时可以省去类型说明。
④ 没有返回值的函数,可以明确定义为空类型,类型说明符为void。

#include <stdio.h>
int fun(int n);
int main(int argc, const char * argv[]) {
    int sum = 0, n;
    printf("input:");
    scanf("%d", &n);
    s = fun(n);
    printf("1+2+…+%d=%n\n", n, sum);
    return 0;
}
int fun(int n){
    int i, sum = 0
    for (i = 1; i <= n; i++)
        sum += i;
    return sum;
}
输出结果:
input:100
1+2+…+100=5050
Program ended with exit code: 0

当然,对于函数,在C99标准下,系统还给我们提供了很多函数,对于那些函数我们成为库函数,我们在程序中可以直接调用,对于库函数,可以参考https://baike.baidu.com/item/C语言函数参考手册/3866969?fr=aladdin

函数和数组之间的联系

1、传递数组
当形参是数组形式时,其本质上也是一个指针。数组作为实参传递时,形参并没有复制实参所有的内容,而是复制了实参数组的首地址。由于数组的特殊性,只要知道了数组的首地址,就可以依次访问数组中的所有元素。

#include <stdio.h>
int test_array(int a[], int n, int *p){
    int i, sum = 0;
    *p = 0;
    for (i = 0; i < n; i++)
    {
        sum += a[i];
        if (a[i] % 2)
            (*p)++;
    }
    return sum;
}
int main(int argc, const char * argv[]) {
    int a[] = {9, 12, 2, 3, 29, 31, 40, 80}, n;
    int sum = 0,  odd= 0;
    n = sizeof(a) / sizeof(int);
    sum = test_array(a, n, &odd);
    printf("sum=%d add numbers count =%d\n",
            sum, odd);
    return 0;
}
输出结果:
sum=206 add numbers count =4
Program ended with exit code: 0

在该程序中,子函数用来计算主调函数中的整型数组的元素的和,并统计数组中奇数的个数。实参传的是数组名、数组的元素个数及一个整型的指针(除了传递数组名以外,还要传递数组中元素的个数)。形参是一维数组的形式,在这里,读者要特别注意:当形参是数组形式时,本质是同级别的指针。该程序中的形参int a[],实际上是int *a。上面程序演示的是一维数组,对于二维数组这里就不演示了,其实差不多。读者可以自行去摸索。

温馨提示:当形参是数组形式时,本质是同级别的指针。

2、传递指针
前面介绍了函数形参是数组形式的用法,下面介绍另一种很常见的写法即传递指针。其实,读者如果熟练掌握了上一章中数组和指针部分的内容,这里就很容易理解。若需要给子函数传递一维数组,可以写成下面的形式:
int test_array(int *a, int n, int p)
若需要给子函数传递二维数组,应该写成下面的形式:
int test_array(int n, int m, int (
a)[m], int p)
在这种形式中,int (
a)[m]是一种数组指针或称为行指针,指针加1,移动m个数据,和m列的二维数组名是类似的。这就就提这么多,下篇文章会详细讲解。

main函数的参数介绍

普通函数可以带参数,其实,main函数也可以带参数。当执行程序时,也可以在命令行上给main函数传参。完整的main函数原型如下:
int main(int argc, const char *argv[])
上面是数组的形式,也可以写成指针的形式:
int main(int argc, char **argv)
其中,argc是传给main函数的参数的个数,argv指向具体的参数。

#include <stdio.h>

int main(int argc,const char *argv[]){
    int i;
    printf("argc=%d\n", argc);

    for (i = 0; i < argc; i++)
        printf("argv[%d]=%s\n", i, argv[i]);
    return 0;
}
输出结果:
argc=1
argv[0]=...(这里打印的是路径)Build/Products/Debug/函数的简单示例
Program ended with exit code: 0

总结

这篇文章对刚接触C语言函数的读者应该有一个很好的帮助,这篇文章将的是基础和理论,下一篇文章将讲解函数的重点,当然也希望读者能够认真的阅读,这里面有很多细节的,望读者揣摩。

结尾

希望读者真诚的对待每一件事情,每天都能学到新的知识点,要记住,认识短暂,开心也是过一天,不开心也是一天,无所事事也是一天,小小收获也是一天,所以,你的路你自己选择。

推荐阅读更多精彩内容