老汤回味——C语言函数

函数是C语言的重要组成部分,通过函数我们可以将复杂逻辑进行封转,缩减程序员在编码和维护中的关注点数量,提高代码质量,方便对代码进行维护。


函数声明用来说明我们的代码中包含一个什么样的函数,函数声明可以放在头文件(.h)或者源文件(.c)中,函数声明的格式如下:

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


函数定义是函数的实现,函数定义的格式如下:

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

        C语言语句

}


函数声明不是必须的,如果省略了函数声明,我们必须将函数定义按照调用顺序进行组织,比如下面的伪代码:

扫描二维码关注公众号,回复: 1852481 查看本文章

void foo1() {

        ........

        // foo3();   //编译错误,找不到函数

        // foo2();   //编译错误,找不到函数

}


void foo3() {

        foo1();     //正确,foo1()在foo3()前面定义

        ........

        // foo2();   //编译错误,找不到函数

}


void foo2() {

        foo3();  //正确,foo3在foo2前面定义

        .......

}


所以,一般把开放给外部的函数声明放在.h文件中,然后在使用这些函数的.c文件中通过#include导入,


演示一个简单的函数,function.h中进行声明

#ifndef FUNCTION_H
#define FUNCTION_H

int add(int a, int b);

#endif

function.c中定义

#include "function.h"

int add(int a, int b) {
	return a + b;
}

该函数实现两个整型数的相加,通过两个参数a和b,将计算结果a+b通过return语句返回。main.c中使用该函数

#include <stdio.h>
#include "function.h"

int main(void) {
	int a = 10;
	int b = 20;
	printf("a + b = %d\n", add(a, b));
}

函数的输出为

a + b = 30


下面我们着重讨论函数的参数,在上面函数定义中,参数列表中的a和b被称为形参,他们是函数作用域内的局部变量,如果函数外部还有变量a和b(如main中的变量a和b),形参将会隐藏外部的同名变量。


函数的参数按值传递,什么叫按值传递呢?如果参数是基本类型的变量,如int, float等,则将变量值进行传递,我们对形参的改变不会改变外部变量的值,如果传入的参数是指针,则传入的参数是指针变量的值,也就是一个内存地址,这样,我们基于形参的指针变量,是可以改变内存地址指向的内存中的数值的,看下面改版的add函数,在function.h中加入一个新的函数声明

int add(int a, int b);
void add_use_pointer(int a, int b, int *result);


add_use_pointer函数不返回值,而是通过一个指针变量result返回值。function.c中的实现如下

void add_use_pointer(int a, int b, int *result) {
	*result = a + b;
}

不再调用return语句,而是将a+b的值赋值给了一个指针变量所指向的内存。main.c中的调用

int result = 0;
add_use_pointer(a, b, &result);
printf("a + b = %d\n", result);

输出结果为

a + b = 30


可以看到,main中定义的result,初始化为0,但是执行函数add_use_pointer之后,值通过指针被设置为了30。使用指针返回值,可以让我们的函数轻松返回多个值,不过,使用指针返回多个值,不应该返回过多的值,否则会加长参数列表,这是非常不好的代码形式。如果采用return,希望返回多个值,则需要将返回值绑为一个结构体(后面的文章会介绍),而使用结构体,是C语言实现面向对象的一种手段。


介绍完函数,我们补充看一个问题,C语言中{}之间的部分被称之为代码块,在代码块中定义的变量,其作用域就在该代码块中,也就是说,在代码块之外无法访问该变量,另外,如果没有使用动态内存分配,局部变量在离开代码块时,占用的内存会被释放,生存期仅限于代码块。如果一个变量不属于任何函数,那么该变量就是一个全局变量,如果在任何代码块中都没有同名变量,则我们可以在代码的所有位置访问到该变量,生存期为整个程序的运行时间。看下面的例子,我们在function.c中定义了一个全局变量

int global_int = 100;

在function.h中,我们使用extern关键字导出该全局变量

extern int global_int;

main.c中我们包含了头文件function.h,所以也就导出了全局变量,我们可以直接使用

printf("global_int = %d\n", global_int);

main.c中我们包含了头文件function.h,所以也就导出了全局变量,我们可以直接使用


输出为

global_int = 100


C语言还提供了一个static关键字,用来定义静态变量或静态函数,对于静态变量,可以分为静态全局变量和静态局部变量。给一个简单的静态全局变量的例子

static int global_int_static = 100;


我们提到了作用域的问题,静态全局变量和静态函数作用域为文件作用域,也就是说,变量或函数仅在本文件中有效。这里就为我们提供了一种封装机制,如果我们想隐藏某个函数或全局变量不被外部访问,我们可以在.c文件中将其定义静态全局变量或静态函数。但是,如果在.h文件中定义了静态全局变量和静态函数,则由于#include会将头文件复制到引用点,实际上就将静态全局变量和静态函数所在的文件改变了,造成了可以在其他文件访问的假象。


对于静态局部变量,变量仅在第一次被使用时会被初始化,之后每次进入其作用域,都会保留之前在其作用域中的运行值,也就是说生存期被延长为整个程序运行阶段,但是作用域依然是局部的。


static是我们使用C语言做出面向对象程序的工具之一,static相关的内容比较抽象,希望大家多去了解一些。


猜你喜欢

转载自blog.csdn.net/yjp19871013/article/details/80631128