C语言:函数详解

1.函数的概念

       其实在C语⾔也引⼊函数(function)的概念,有些翻译为:⼦程序,⼦程序这种翻译更加准确⼀些。 C语⾔中的函数就是⼀个完成某项特定的任务的⼀⼩段代码。这段代码是有特殊的写法和调⽤⽅法的。 C语⾔的程序其实是由⽆数个⼩的函数组合⽽成的。C 语言中我们会见到两类函数:库函数自定义函数。

2. 库函数

       C 语⾔标准中规定了 C 语⾔的各种语法规则, C 语言本身是不提供库函数的,但我们写代码时经常会用到重复的功能,比如输入输出数据等等。C 语言标准委员会提供了一些常用函数的标准,像函数名、参数、返回类型,它的实现需要编译器的厂商自己去实现,这些函数叫做库函数。

2.1 库函数的使用

       库函数是在标准库中对应的头⽂件中声明的,所以库函数的使⽤,务必包含对应的头⽂件,不包含是 可能会出现⼀些问题的。比如 sqrt() 是求平方根的,这个函数使用时就需要包含 这个头文件。

3. 自定义函数

       自定义函数的语法形式如下

ret_type name(形式参数)
{

}

       ret_type 是函数返回类型; name 是函数名;括号中放的是形式参数 ;{ } 括起来的是函数体。ret_type 是⽤来表⽰函数计算结果的类型,有时候返回类型可以是 void ,表⽰什么都不返回 ;name 是为了⽅便使⽤函数,函数名尽量要根据函数的功能起的有意义;函数的参数就相当于,⼯⼚中送进去的原材料,函数的参数也可以是 void ,表⽰函数没有参数。如果有参数,要交代清楚参数的类型和名字,以及参数个数。;{} 括起来的部分被称为函数体,函数体就是完成计算的过程。比如我们要写一个加法函数,其具体代码如下:

C语言:函数详解_第1张图片

 4. 实参和形参

       在刚刚那个代码中 ADD(a,b) 中,a,b 就是实际参数(实参),ADD(int x,int y) 中 x,y 就是形式参数(形参)。当我们不去调用 ADD() 函数是,x,y 只是形式上的存在,并不会创建,因此叫形式参数(形参)。

4.1 实参和形参的关系

       虽然我们实参是传递给形参的,他们之间是有联系的,但是形参和实参各⾃是独⽴的内存空 间。x和y确实得到了a和b的值,但是x和y的地址和a和b的地址是不⼀样的,所以我们可以理解为形参是实参的⼀份临时拷贝。

5. return语句

       return 语句在使用时有很多注意的事项,如下:

       1. return 后边可以是⼀个数值,也可以是⼀个表达式,如果是表达式则先执⾏表达式,再返回表达式的结果

       2. return 后边也可以什么都没有,直接写 return; 这种写法适合函数返回类型是 void 的情况;

       3. return 语句执⾏后,函数就彻底返回,后边的代码不再执⾏;

       4. return 返回的值和函数返回类型不⼀致,系统会⾃动将返回的值隐式转换为函数的返回类型

       5.如果函数中存在 if 等分⽀的语句,则要保证每种情况下都有 return 返回,否则会出现编译错误;

       6.函数的返回类型如果不写,编译器会默认函数的返回类型是 int

       7.函数写了返回类型,但是函数中没有使⽤ return 返回值,那么函数的返回值是未知的。

6. 数组做函数参数

       数组传参时只需要写上数组名即可。实参名字可以和形参名字相同。

       让我们来写一段代码来理解一下:⽐如:写⼀个函数将⼀个整型数组的内容,全部置为 3,再写⼀个函数打印数组的内容,首先我们需要创建一个数组,然后用一个函数来实现值的更改,再用一个函数来实现打印。因此,参考代码如下:

C语言:函数详解_第2张图片

       一维数组中,形参的括号内的 10 可以不写。因为数组中形参操作的数组和实参的数组是同⼀个数组 ,即地址是同一个地址。数组传参时形参是不会创建新的数组的。下面让我们看一下几个数组传参需要注意的点:

       1.函数的形式参数要和函数的实参个数匹配

       2.函数的实参是数组形参也是可以写成数组形式的;

       3.形参如果是⼀维数组数组⼤⼩可以省略不写

       4.形参如果是⼆维数组⾏可以省略,但是列不能省略

       5.数组传参,形参是不会创建新数组的;

       6.形参操作的数组和实参的数组是同⼀个数组

7. 嵌套调用和链式访问

7.1 嵌套调用

       嵌套调⽤就是函数之间的互相调⽤,最后写出来了相对⼤型的程序。比如我们计算某年某⽉有多少天?首先我们要知道年和月,而且要判断是不是闰年,如果是闰年,那二月就是29,如果是平年,二月就是28。因此我们要写一个判断闰年的函数,来决定二月的天数。

C语言:函数详解_第3张图片C语言:函数详解_第4张图片

       如果我们不想减 1 ,可以定义数组大小为 13 ,让下标为 0 的数组元素为0(任何值都可以),让天数向后移动一个位置,这样下标和月份就对应上了。 

       未来的稍微⼤⼀些代码都是函数之间的嵌套调⽤,但是函数是不能嵌套定义的。

 7.2 链式访问

       所谓链式访问就是将⼀个函数的返回值作为另外⼀个函数的参数,像链条⼀样将函数串起来就是函数的链式访问。

 #include 
 int main()
{
 printf("%d\n", strlen("abcdef"));
 return 0;
}

       strlen() 函数是求字符串长度,此时 strlen() 函数的返回值是6,则 printf() 函数最终输出的值为 6。

8. 函数的声明和定义

8.1 单个文件

       像刚刚我们写的输入年月求天数的代码:main() 函数中的 days(year,month) 是函数的调用;而上面的 int days(int x,int y) 是对函数的定义。我们上面是将定义写在主函数的上面,但是如果我们将函数的定义写在主函数的下面时,再次编译就会出现错误,要消除这样的错误,我们就需要在调用之前声明一下这个函数,函数声明中参数只保留类型,省略掉名字也是可以的。因此函数的调用一定要满足先声明后使用。

8.2 多个文件

       ⼀般在企业中我们写代码时候,代码可能⽐较多,不会将所有的代码都放在⼀个⽂件中;我们往往会 根据程序的功能,将代码拆分放在多个⽂件中。 ⼀般情况下,函数的声明、类型的声明放在头⽂件(.h)中,函数的实现是放在源⽂件(.c)⽂件中。注意,自己定义的头文件在使用时用双引号 " " 括起来而不是 < > 。

8.3 extern 和 static

       extern 和 static 都是 C 语言中的关键字。static 时静态的意思可以修饰局部变量、全局变量和函数 extern 是用来修饰外部符号的。在这之前让我们先来了解两个概念:

       作用域:

       作⽤域(scope)是程序设计概念,通常来说,⼀段程序代码中所⽤到的名字并不总是有效(可⽤)的,⽽限定这个名字的可⽤性的代码范围就是这个名字的作⽤域。

       1. 局部变量的作⽤域是变量所在的局部范围。

        2. 全局变量的作⽤域是整个⼯程(项⽬)。

       生命周期:

       ⽣命周期 指的是变量的创建(申请内存)到变量的销毁(收回内存)之间的⼀个时间段。

       1. 局部变量的⽣命周期是:进⼊作⽤域变量创建,⽣命周期开始,出作⽤域⽣命周期结束。

       2. 全局变量的⽣命周期是:整个程序的⽣命周期。

8.3.1 static 修饰局部变量

       static修饰局部变量改变了变量的⽣命周期,⽣命周期改变的本质是改变了变量的存储类型,本来⼀个局部变量是存储在内存的栈区的,但是被 static 修饰后存储到了静态区。存储在静态区的变 量和全局变量是⼀样的,⽣命周期就和程序的⽣命周期⼀样了,只有程序结束,变量才销毁,内存才回收。但是作⽤域不变的。未来⼀个变量出了函数后,我们还想保留值,等下次进⼊函数继续使⽤,就可以使⽤static 修饰。

8.3.2 static 修饰全局变量

       ⼀个全局变量被static修饰,使得这个全局变量只能在本源⽂件内使⽤,不能在其他源⽂件内使⽤。本质原因是全局变量默认是具有外部链接属性的,在外部的⽂件中想使⽤,只要适当的声明就可以使⽤;但是全局变量被 static 修饰之后,外部链接属性就变成了内部链接属性,只能在⾃⼰所在的源⽂件内部使⽤了,其他源⽂件,即使声明了,也是⽆法正常使⽤的。如果⼀个全局变量,只想在所在的源⽂件内部使⽤,不想被其他⽂件发现,就可以使⽤ static修饰。

8.3.3 static 修饰函数

       其实 static 修饰函数和 static 修饰全局变量是⼀模⼀样的,⼀个函数在整个⼯程都可以使⽤, 被static修饰后,只能在本⽂件内部使⽤,其他⽂件⽆法正常的链接使⽤了。本质是因为函数默认是具有外部链接属性,使得函数在整个⼯程中只要适当的声明就可以被使⽤。但是被 static 修饰后变成了内部链接属性,使得函数只能在⾃⼰所在源⽂件内部使⽤。⼀个函数只想在所在的源⽂件内部使⽤,不想被其他源⽂件使⽤,就可以使⽤ static 修饰。

你可能感兴趣的:(c语言,开发语言)