C语言基础级——函数声明与定义

C语言基础级——函数基本概念

转载请标明出处
大家好,我叫Home。在工作当中,发现自己可以写一篇关于C语言函数的知识,自己在复习的同时,也能够总结当前对函数的理解与使用是否到位。那本篇博客主要讲解函数的定义和声明,带大家真正了解函数,并且真正的懂得如何使用函数。那这也是我个人的第一篇博文,如果有不妥之处我们可以共同交流学习,希望读完此博文之后能有收获。

1.1 基本思想

首先我们要知道通常在做大型的项目时候,我们是将一个大的程序按功能分割成一些小模块。

打个比方,我们看任何一本书籍,都会先去看下书籍的目录,了解这本书籍都涉及了哪些内容,对这本书籍先有一个大体的方向之后,再着手看你想要了解的内容。这里面其实是不是目录起到了一定作用,第一个作用是,能够对整本书籍的各个章节内容有着大致划分且结构清晰,让读者心里有一个明确的方向。第二个作用是,再更新迭代书籍过程中,笔者对某章节内容想要进行优化的时候,就不需要大范围改动其它章节内容。

那其实程序相当于是"一本书","目录"相当于程序的结构,"章节"相当于程序的函数。
理解这个过程之后,再去了解函数的特性就好理解了,那我们来看下C语言函数的几大特性:

  • 各个模块相对独立、功能单一(一个函数完成一个任务)
  • 结构清晰、接口简单
  • 控制了程序设计的复杂性
  • 提高元件的可靠性
  • 缩短开发周期
  • 避免程序开发的重复劳动
  • 易于维护和功能扩充

2.1 定义与声明

2.1.1 定义

函数的定义是具体实现函数的功能,它的格式为:

[类型标识符] [函数名]([形式参数]){}

2.1.1.1 部分源码

这是ANSI C的函数定义的标准形式:

// 类型标识符 函数名(形式参数)
int fun(int param_a)
{
    
    
    printf("Hello Home!\n");
   	return 0; 
}

ANSI C之前的标准:

int fun(int param_a)
int param_a;
{
    
    
    printf("Hello Home!\n");
   	return 0; 
}

ANSI C的函数定义标准与之前的标准,那我们采用哪种形式的来些呢?答案是前者,因为本身前者的形式是新的标准,意味着后者形式可能会在今后当中被淘汰掉。即便没被淘汰,新的标准所展现的格式更加合理及人性化。

2.1.2 声明

  • 无需函数体,只需要的格式为[函数返回值] [函数名][函数参数列表]
void fun(void);
  • 记得加上;哦!
  • 这种声明的格式,也被称为函数原型。
  • 同样,如果函数带有参数列表的,有两种形式。
void fun(int, int);
void fun(int a, int b);

前者函数声明并没有实际地创建变量,所以我们可以使用int代表了一个int类型的变量。

  • 除此之外,如果调用函数与被调用函数在同一个文件当中,我们把被调用函数放到调用函数之前时,此时函数定义相当于为函数声明,函数的额外声明可不用写。
#include<stdio.h>

// 函数定义
int fun(void)
{
    
    
    // 返回3
    return 3;
}

int main()
{
    
    
    int res;
    // 函数调用
    res = fun();
    
    // 打印函数整形返回值
    printf("res = %d\n", res);
    return 0;
}

在这里插入图片描述

输出结果是正确的,再这里呢,要提一点,其实这种写法个人认为是一种“偷懒”的写法。因为当你的项目足够庞大的时候,自己想要找到main函数,显得格外浪费时间。为了让我们代码设计更加人性化一些,我们还是不要“偷懒”了。老老实实的把函数定义放在main函数之后,并且在所有函数前面统一放置函数声明。

2.1.3 命名

  • C语言当中,我们以小写字母,如果有多个单词,用 _ 下划线连接单词,这是C语言标准写法,

  • 注意

    • 对于大型的项目来说,我们最好再main函数内不要涉及太多的代码。

2.1.4 空函数(不推荐使用)

我们先思考,a() {}函数,是否可以通过可运行成功?我们来试一试。

2.1.4.1 实验
源码
#include<stdio.h>
// 声明空函数
a();

int main()
{
    
    
    // 调用空函数
    a();
    return 0;
}


// 定义空函数
a()
{
    
    
    printf("Hello World!\n");
}

结果

在这里插入图片描述

空函数当中,在gcc编译器中并不会报错,而是报警告且能够运行。如上图可知空函数默认为int类型。但是在老版本编译器当中或者C++当中,编译程序是不通过,原因是新版本的编译器是经过了一定的优化,而C++对代码格式比C严格了。感兴趣的,可以去试一试,再这里就不再做扩展。

3.1 返回值与类型

可以是任意类型,在函数体当中使用return关键字返回一个确定值,如果定义的函数类型为void,不需要调用函数从被调用函数带回函数值的,可以不要return语句或者直接写return;

3.1.1 实验

源码

#include<stdio.h>

// 函数声明
void fun(void);

int main()
{
    
    
    int res;
    // 函数调用
    res = fun();
    
    // 打印函数整形返回值
    printf("res = %d\n", res);
    return 0;
}

// 函数定义
void fun(void)
{
    
    
    // 可写可不写
    return;
}

函数值的类型最好和返回值的类型一致。如果函数值的类型和返回值语句中表达的值不一致,以函数类型为准,如果不信,那我们可以打印来看看。

3.1.2 实验

源码

#include<stdio.h>

// 函数声明
int fun(void);

int main()
{
    
    
    int res;
    // 函数调用
    res = fun();
    
    // 打印函数整形返回值
    printf("res = %d\n", res);
    return 0;
}

// 函数定义
int fun(void)
{
    
    
    // 小数型
    return 3.14;
}

结果

在这里插入图片描述

不难看出,fun函数返回的是int类型的数值,也就是最终结果3,而不是3.14

4.1 函数参数

函数之间想要实现,不同参数下,共享同一个变量的情况下,有两种方法,第一种就是在[函数返回值] [函数名][函数参数列表],使用函数的参数传递方式。第二种就是采用全局变量方法也能够完成值的传递。后者方法涉及到变量修饰符,不属于本节知识点,所以在这里我们只讲前者函数的参数传递方式。

  • 函数定义时的参数称为形参参数,简称为形参,在函数当中可分为带形式参数和不带形式参数两种。

表达形式:

// 带形参
int fun(int param_a, int param_b) {
    
    }
// 不带形参
int fun(void) {
    
    }

从内存角度来考虑一下,在定义函数形参,如果函数未被调用,形参并不会占用内存空间。只有当函数调用的时候,形参才会被分配内存单元。

  • 实参也叫做实际参数,要求在调用之前,它们是一个给定的值,这个值可以是常量也可以是一个地址,并且把它传递给形参。

表达形式:

// 对应上图-1, 实参
fun(3, 10);
// 对应上图-2, 实参
fun();

函数参数有两种传递方式,第一种是值传递,第二种是地址传递。值传递方式就是上述我们采用的方式,我们传递的形参是两个int类型的变量。

4.1.1 值传递

值传递的方式,就是使用变量作为形参,实际上传递过程当中,是将实参的值拷贝一份给形参相应的存储单元中。本质上形参和实参是存在不同的存储空间。

值传递的特点是单向传递的,当主调函数调用结束之后,形参的存储单元被释放,实参单元仍保留并维持原值。

4.1.1.1 实验

源码
#include<stdio.h>

// 函数声明
void swap(int p, int q);

int main()
{
    
    
    int a = 10;
    int b = 20;
    // 值传递
    swap(a, b);
    
    // 打印函数整形返回值
    printf("a = %d\n", a);
    printf("b = %d\n", b);
    return 0;
}

// 函数定义
void swap(int p, int q)
{
    
    
    
    int temp = p;
    p = q;
    q = temp;
}

结果

在这里插入图片描述

4.1.2 地址传递

地址传递的方式使用数组名或者指针作为形参,数组的首地址或者指针的值(值可理解为地址)作为实参。这就意味着形参接收到是地址,即形参指针实参的存储单元,形参和实参本质上占用相同的存储单元。

地址传递的特点是形参与实参共用一段内存空间,意味着形参的变化也就是实参的变化。

4.1.2.1 实验

源码
#include<stdio.h>


void swap(int *p, int *q);

int main()
{
    
    
    int a = 10;
    int b = 20;

    // 函数调用
    swap(&a, &b);
    
    printf("a = %d\n", a);
    printf("b = %d\n", b);
    return 0;
}

// 函数定义
void swap(int *p, int *q)
{
    
    
    // 值交换
    int temp;

    temp    = *q;
    *q      = *p;
    *p      = temp;
}

结果

在这里插入图片描述

由实验结果可知,初始值a = 10; b = 20;,经过地址传递之后,a = 20; b = 10;

5.1 提升!

5.1.1 函数无声明实验

  • 如果函数只定义,不声明。

    将预处理文件转换成汇编文件。

源文件

#include<stdio.h>

int main()
{
    
    
    // fun函数调用
    fun();
    return 0;
}

// fun函数只定义, 无声明
void fun(void)
{
    
    

}

结果:

高版本gcc将预处理文件转换成汇编期间会报警告(低版本会报错)。
在这里插入图片描述

5.1.2 函数无定义实验

  • 如果函数只声明、无定义,gcc编译报程序连接期间出现错误。
#include<stdio.h>

// fun函数只声明, 无定义
void fun(void);

int main()
{
    
    
    // fun函数调用
    fun();
    return 0;
}

结果

预处理、汇编、转换目标文件都没有问题,在链接期间,编译器会报错,undefined reference无定义引用。
在这里插入图片描述

  • 规定放在文件头文件后面,肯定有同学问,为什么要把函数声明放在函数定义前面?
    • 从语法来说,如果函数定义与声明在同一文件内,规定声明要在函数定义前面。
    • 从编译器角度来说,函数定义在编译期间会警告,告诉你fun函数是隐式声明。

如果对程序编译过程存在着疑虑,可查看(还未更新,看心情咯)。

猜你喜欢

转载自blog.csdn.net/qq_43125185/article/details/110121571