1.C程序是由无数个实现对应功能的函数组成的,因此函数又有翻译叫子程序.
子程序是为实现某项特定功能而创造的,创造出来后可以大大降低这个功能代码的重复编写,可以复用.所以函数的功能越单一越好,不要什么功能都想糅合搓进一个子程序,这就很难复用.
2.分类
函数又分为库函数和自定义函数
2.1库函数
C语⾔的国际标准ANSIC规定了⼀ 些常⽤的函数的标准,被称为标准库,那不同的编译器⼚商根据ANSI提供的C语⾔标准就给出了⼀系列函数的实现。这些函数就被称为库函数。
库函数相关头⽂件:https://zh.cppreference.com/w/c/header
库函数包含于头文件中
没必要背,库函数慢慢练慢慢会
2.2自定义函数
语法形式
ret_type fun_name(形式参数)
{
}
ret_type是函数返回类型(如void什么都不返回,只需要函数实现相应的功能)
fun_name是函数名(最好跟函数的功能有关,不然函数写多了就有的找了)
形式参数在程序调用函数的时候会为其分配内存,有多少参数就给你分配多少内存,所以写形式参数要写清楚类型和个数,名字看心情写(不是不写的意思)
大括号括起的函数体内就是函数完成计算的过程
3.形参实参
形参是在函数内的参数,形参在函数被调用时才会申请内存来存放实参传过来的值(所以形参实参内存空间不同),没被调用就没有内存只是形式上存在,所以叫形参
实参在主调用函数中,已经分配了内存且有确定值
实参形参类型要一样,实参单向传值给形参
函数的形式参数要和函数的实参个数匹配
4.return语句
return后边可以是⼀个数值,也可以是⼀个表达式,如果是表达式则先执⾏表达式,再返回表达式 的结果。
return后边也可以什么都没有,直接写 return; 这种写法适合函数返回类型是void的情况。
return语句执⾏后,函数就彻底返回,后边的代码不再执⾏。
return返回的值和函数返回类型不⼀致,系统会⾃动将返回的值隐式转换为函数的返回类型。
int add()
{
return 3.14;
}
这时候会返回个3给你
如果函数中存在if等分⽀的语句,则要保证每种情况下都有return返回,否则会出现编译错误。
函数的返回类型如果不写,编译器会默认函数的返回类型是int。
函数写了返回类型,但是函数中没有使⽤return返回值,那么函数的返回值是未知的。
5.数组做函数参数
学到后面我们就会发现数组作为实参传入函数并非传值,数组会直接把地址传过去,所以我们在主函数中操作的数组与被调用的数组是同一个数组.所以数组传参,形参不会创建新的数组.
函数的实参是数组,形参也是可以写成数组形式的
形参如果是⼀维数组,数组⼤⼩可以省略不写
形参如果是⼆维数组,⾏可以省略,但是列不能省略
作者提醒:
void change(int arr)
void change(int arr[])
数组做函数参数时如果按照第一种写法会报错:
int arr声明的是一个整型变量,而传递给被调用函数的实参是数组名(即指针值,也可以理解为地址),而int arr期望接受的是一个整型值,会类型不匹配.也可以用int*arr表明形参接收的是指针值(地址)
总结:数组名可以直接传地址,传递的是数组首地址,类型为int*arr[0],如果不想写全数组名也得把地址加上,不然形参接收的不是指针值而是整型值就会给你重新分配个内存然后你再把你实参的指针值传过去,包报错的.
6.嵌套调用和链式访问
6.1嵌套调用
稍微⼤⼀些代码都是函数之间的嵌套调⽤,但是函数是不能嵌套定义的。
嵌套调用例子,设计一个计算输入某年某月然后返回多少天的程序:
#define _CRT_SECURE_NO_WARNINGS 610
#include<stdio.h>
int leapyear(int y)
{
if ((y % 4 == 0) && (y % 100 != 0) || y % 400 == 0)
{
return 1;
}
else return 0;
}
int monthdate(int y, int m)
{
int arr[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
int day = arr[m];
if (leapyear(y) && m == 2)
{
day+1;//day+1没有等式程序运行不了,计算机不知道
//day+1的值赋给谁,会报错
//如day=day+1值赋予day
//day++自带等式,
//或改写成day+=1;
}
return day;
}
int main()
{
int y, m;
scanf("%d %d", &y, &m);
int date=monthdate(y, m);
printf("本月有%d天", date);
}
自定义了month函数来计算对应月份有几天,为应对闰年二月多一天的问题再自定义一个leapyear函数来判断是否为闰年,用month函数调用leapyear函数,main函数调用month函数,这就是函数的嵌套调用
6.2链式访问
所谓链式访问就是将⼀个函数的返回值作为另外⼀个函数的参数,像链条⼀样将函数串起来就是函数的链式访问
如:
printf("%s",strlen("abcdef");
还有:
printf("%d", printf("%d", printf("%d", 43)));
printf返回的是打印在屏幕上的字符的个数。第⼀个printf打印的是第⼆个printf的返回值,第⼆个printf打印的是第三个 printf的返回值。
第一个printf返回值43返回给第二个printf解析出两个字符返回2,2返回给第一个字符返回1
结果为4321
7.函数的声明与定义
定义最好不要放在调用后面,不然编译器不懂你什么意思,要放就先声明
函数的定义也是⼀种特殊的声明,所以如果函数定义放在调⽤之前也是可以的
int leapyear(int y)//此为函数声明,函数体内为函数定义
{
if(y%4==0&&y%100!=0 || y%400==0)
{
return 1;
}
else return 0;
}
函数声明只要求保留类型,名字省略也可以
8.多文件
把代码都放一个文件不好操作,可协调性差,还很冗余,甚至没有保密性
⼀般情况下,函数的声明、类型的声明放在头⽂件(.h)中,函数的实现(函数的定义)是放在源⽂件(.c)⽂件中。
头文件:函数的声明(add.h)
int Add(int x, int y);
源文件:函数的定义(add.c)
int Add(int x, int y)
{
return x+y;
}
主源文件:调用函数及程序运行(test.c or main.c)
#include <stdio.h>
#include "add.h"
int main()
{
int a = 10;
int b = 20;
//函数调⽤
int c = Add(a, b);
printf("%d\n", c);
return 0;
}
9.static与extern函数
9.1 static函数
1.用于修饰局部变量
如
#include <stdio.h>
void test()
{
static int i = 0;
i++;
printf("%d ", i);
}
int main()
{
int i = 0;
for(i=0; i<5; i++)
{
test();
}
return 0;
加上static后i的内存将从局部变量变为全局变量
本来⼀个局部变量是存储在内存的栈区的,但是被 static 修饰后存储到了静态区。存储在静态区的变量和全局变量是⼀样的,⽣命周期就和程序的⽣命周期⼀样了,只有程序结束,变量才销毁,内存才 回收。但是作⽤域不变的(和全局变量唯一区别)。
使⽤建议:未来⼀个变量出了函数后,我们还想保留值,等下次进⼊函数继续使⽤,就可以使⽤static 修饰。
2.用于修饰全局变量与函数
全局变量和函数都具有外部链接性,函数在整个⼯程中只要适当的声明就可以被使⽤。但是被 static 修饰后变成了内部链接属性,使得函数只能在⾃⼰所在源⽂件内部使⽤。
[⼀个全局变量被static修饰,使得这个全局变量只能在本源⽂件内使⽤,不能在其他源⽂件内使⽤
函数同理]
使⽤建议:如果⼀个全局变量或函数,只想在所在的源⽂件内部使⽤,不想被其他⽂件发现,就可以使⽤ static修饰。
9.2 extern函数
用来声明外部符号
比如说外来变量或函数,本文件无法识别,这时候在外来变量或函数前加上extern,编译器就会自动从别的文件中获取函数或变量信息
9.3关于这两个函数同时使用
static int g_val = 2018;
#include <stdio.h>
extern int g_val;
int main()
{
printf("%d\n", g_val);
return 0;
}
这时候就会出现编译会出现链接性错误。static在另一个文件修饰过的变量或函数是无法被编译器识别的,所以就算extern声明了也没用