文章目录
Linux-C P8 函数
本文主要讲解有关C语言函数的功能,包括函数的概念和基本使用,函数与数组、指针函数、函数指针;学会了有关递归与回调的相关知识点;并了解GNU C的attribute机制和库函数
函数基础
在学会如何使用函数之前,先来看看函数是什么,如何被定义,函数的运作原理以及main函数参数的使用
声明与定义
什么是函数?
函数是一个能完成特点功能的代码模块
每个C程序都至少有一个函数,即主函数mian(),所有简单的程序都可以定义其额外的函数
可以把代码划分到不同的函数中,如何划分由自己决定;但在逻辑上,划分通常是根据每个函数执行一个特定的任务来进行的
为什么要定义函数?
1.随着程序规模的变大,会让main函数的内容变得相当长且不易处理、可读性差等问题
2.程序复杂不断提高,代码前后关联太强,不具有好的移植和扩展性
3.还有代码重复的等等问题
这些都能在合理的使用函数后得到较好的解决
函数的定义
函数是一个特定的代码块,可以有参数,也可以无参数
函数定义的基本形式
<数据类型> <函数名称>(<形式参数说明>)
{
语句;
return(<表达式>);
}
数据类型:整个函数返回值的类型
函数名称:是一个标识符,需要符合命名规则
形式参数说明:是形式参数的列表,可以是任何类型的变量,用逗号分隔
return(<表达式>):返回值的内容,类型与返回值的数据类型需相同
以main函数为例
int main(int argc, const char *argv[])
{
printf("hello");
return 0;
}
函数的声明
函数声明是告诉编译器函数的名称、返回类型和参数,给函数定义提供了函数实际主体
意思就是说你可以先声明一个函数而先不编辑它实现功能的代码部分
对于C语言有两种方法来进行函数声明
1.如果函数调用前,没有对函数作声明,且同一源文件的前面出现了该函数的定义,那么编译器就会记住它的参数变量和类型以及函数的返回值类型,即把它作为函数的声明,并将函数返回值的类型默认为int型
2.如果在同一源文件的前面没有该函数的定义,则需要提供该函数的函数原型。自定义的函数原型通常可以一起写在头文件中,通过头文件引用的方式来声明
函数声明基本形式
函数类型 函数名(参数类型1, .....);
函数类型 函数名(参数类型1 参数名1, .....);
运作原理
函数调用
定义和声明好函数,就需要来调用它,可以在main()里调用其他函数,也可以在其他函数里调用其他函数,但是只有在主函数调用后才能运行该函数里的内容
函数调用基本形式
函数名称(<实际参数>)
举个栗子
#include <stdio.h>
void fun2(){
printf("world!\n");
}
void fun1(){
printf("Hello ");
fun2();
}
int main(int argc, const char *argv[])
{
fun1();
return 0;
}
函数返回值
如果一个函数需要将功能实现的结果或者功能是否实现成功传递回主函数,就需要定义返回值的类型, 并return(<表达式>);将值传递回主函数
举个栗子
sum函数实现1~10的累加,并将累加的结果传递回主函数,通过主函数进行打印输出
#include <stdio.h>
int sum(){
int i,s = 0;
for(i = 1;i <= 10;i++){
s += i;
}
return s;
}
int main(int argc, const char *argv[])
{
int s;
s = sum();
printf("sum = %d\n",s);
return 0;
}
这里累加的结果是int类型的,因此返回值的类型也为int
函数传递
有时候,函数需要接收用户传入的数据,那么就需要使用函数的参数,函数的参数分为形式参数和实际参数
形式参数:在函数定义中定义的参数变量,简称形参
实际参数:出现在主调函数中,实际引用的变量,简称实参
#include <stdio.h>
void fun(int c ,int d){
prntf("%d\n",a+b);
}
int main(int argc, const char *argv[])
{
int a,b;
fun(a,b);
return 0;
}
这里的程序中,a和b就是实参,c和d就是形参
关于参数的传递主要有三种形式
1.值传递方式
举个栗子
#include <stdio.h>
void fun1(int a,int b){
int k;
k = a;
a = b;
b = k;
printf("a = %d ,&a = %p\n",a,&a);
printf("b = %d ,&b = %p\n",b,&b);
}
int main(int argc, const char *argv[])
{
int x = 50 ,y = 100;
fun1(x,y);
printf("x = %d,&x = %p\n",x,&x);
printf("y = %d,&y = %p\n",y,&y);
return 0;
}
这里很容易发现通过值传递参数,形参只在函数内有效,因此虽然形参直接进行了数据交换,但是x和y却并没有进行交换
函数的形参和实参的特点
1.形参变量只有在被调用时才分配内存单元,在调用结束时,即刻释放所有分配的内存单元。因此形参只有在函数内部有效。调用结束返回主调函数后则不能再使用该形参变量
2.实参可以是常量、变量、表达式、函数等,无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值,以便把这些值传送给形参。因此应先用赋值、输入等办法使实参获得确定值
3.实参和形参在数量上、类型上、顺序上应严格一致,否则会发生“类型不匹配”的错误
2.地址传递方式
地址传递方式和值传递方式正好相反,这种方式是将调用函数的参数本身传给被调用函数。因此,被调用函数中对形参的操作,将直接改变实参的值。调用函数将实参的地址传送给被调用函数,被调用函数对该地址的目标操作,相当于对实参本身的操作。
举个栗子
对于值传递方式的例子做一些修改
#include <stdio.h>
void fun2(int *a,int *b){
int k;
printf("&a = %p,a = %p\n",&a,a);
printf("&b = %p,b = %p\n",&b,b);
k = *a;
*a = *b;
*b = k;
printf("*a = %d\n",*a);
printf("*b = %d\n",*b);
}
int main(int argc, const char *argv[])
{
int x = 50 ,y = 100;
fun2(&x,&y);
printf("x = %d,&x = %p\n",x,&x);
printf("y = %d,&y = %p\n",y,&y);
return 0;
}
这里直接获取的是变量的地址作为实参的内容传递给形参,所以形参改变的是地址对应的内容,因此就算形参在函数结束之后就消失了,但是地址的改变是不会消失的
3.全局变量传参
全局变量就是在函数体外说明的变量,它们在程序中的每个函数都是可见的。使用全局变量传递数据的先后顺序的不同会影响计算结果,应用顺序不当,会导致错误。
main函数的参数
在之前的使用中就发现了,main参数是带参数的,也就是argc和*argv[]
argc是传递给main函数的参数个数,而argv指具体的参数内容
#include <stdio.h>
int main(int argc, const char *argv[])
{
return 0;
}
下面通过具体的例子来详细的了解一下main函数的参数
举个栗子
这里通过程序将argc和argv[]都输出来查看里面的值
#include <stdio.h>
int main(int argc, const char *argv[])
{
int i;
printf("argc = %d",argc);
for(i = 0;i < argc;i++){
printf("argv[%d] = %s\n",i,argv[i]);
}
return 0;
}
函数和数组
传递数组
当形参是数组时,其本质上也是一个指针
当实参是数组时,形参并没有赋值其实参的所有的内容,而是复制了数组的首地址
举个栗子
#include <stdio.h>
int sum(int a[],int n){
int i,s = 0;
for(i = 0;i < n;i++){
s += a[i];
}
return s;
}
int main(int argc, const char *argv[])
{
int x[] = {15,6,5,62,15,36},s,n;
n = sizeof(x)/sizeof(int);
s = sum(x,n);
printf("sum = %d\n",s);
return 0;
}
传递指针
传递地址,即传递的参数是地址,在上面已经讲到了这种地址传递方法,这里就不做赘述
指针函数
定义与声明
关于指针函数,说明这是一个函数,而指针指的是函数的返回值是指针,因此成为指针函数
指针函数的一般形式
<数据类型> *<函数名称>(<参数说明>)
{
语句序列;
}
在实现一个指针函数时,应该特别注意,指针函数返回的地址,在主调函数中,必须是有效的,也就是返回值指针不是函数内定义的局部变量
函数使用
举个栗子
#include <stdio.h>
#include <string.h>
char *myprintf(){
static char str[20];
strcpy(str,"Hello World!");
return str;
}
int main(int argc, const char *argv[])
{
char s[20];
strcpy(s,myprintf());
printf("%s\n",s);
return 0;
}
函数指针
定义与声明
函数指针是专门用来存放函数地址的指针
函数地址是一个函数的入口地址,函数名代表了函数的入口地址
函数指针的一般形式
<数据类型> (*<函数指针名称>)(<参数说明列表>);
数据类型:是函数指针所指向的函数的返回值类型
函数指针名称:符合标识符命令规则
参数说明列表:函数指针所指向的函数的形参说明保存一致
(*<函数指针名称>)中,*说明为指针,()表明指向函数的指针
函数使用
举个栗子
#include <stdio.h>
int add(int a,int b){
return (a+b);
}
int sub(int a,int b){
return (a-b);
}
int mul(int a,int b){
return (a*b);
}
int fun(int a,int b,int (*func)(int,int)){
return ((*func)(a,b));
}
int main(int argc, const char *argv[])
{
int x = 100,y = 50;
int (*func)(int,int);
func = add;
printf("a + b = %d\n",(*func)(x,y));
func = sub;
printf("a - b = %d\n",(*func)(x,y));
func = mul;
printf("a * b = %d\n",(*func)(x,y));
printf("10 + 5 = %d\n",fun(10,5,add));
printf("50 - 20 = %d\n",fun(50,20,sub));
printf("15 * 6 = %d\n",fun(15,6,mul));
return 0;
}
函数指针数组
函数指针数组是一个包含若干个函数指针变量的数组
基本形式
<数据类型> (*<函数指针数组名称>[<大小>])(<参数说明列表>)
举个栗子
还是之前的例子,作出一定的修改
#include <stdio.h>
int add(int a,int b){
return (a+b);
}
int sub(int a,int b){
return (a-b);
}
int mul(int a,int b){
return (a*b);
}
int main(int argc, const char *argv[])
{
int x = 100,y = 50;
int (*func[3])(int,int);
func[0] = add;
printf("a + b = %d\n",(*func[0])(x,y));
func[1] = sub;
printf("a - b = %d\n",(*func[1])(x,y));
func[2] = mul;
printf("a * b = %d\n",(*func[2])(x,y));
return 0;
}
递归与回调
递归函数
递归函数是指一个函数的函数体中直接调用或间接调用了该函数自身的函数
1.递推阶段:从原问题出发,按递归公式递推从未知到已知,最终达到递归终止条件
2.回归阶段:按递归终止条件求出结果,逆向逐步代入递归公式,回归到原问题求解
函数调用机制
任何函数之间不能嵌套定义,调用函数与被调用函数之间相互独立。发生函数调用时,被函数中保护了调用函数的运行环境和返回地址,使得调用函数的状态可以在被调函数运行返回后完全恢复
被调函数运行的代码虽是同一个函数的代码体,但由于调用点,调用时状态,返回点的不同,可以看作是函数的一个副本,与调用函数的代码无关
递归的优势和劣势
优势:递归的思考角度跟通常的迭代不同,所以有时候使用迭代思维解决不了的问题
劣势:递归的执行效率通常比迭代低很多,所以递归程序要更消耗时间;由于递归函数是不断调用函数本身,在最底层的函数开始返回之前,程序都是一致在消耗栈空间的,所以递归程序需要更多的内存空间
举个栗子
还是以累加1~10为例
#include <stdio.h>
int sum(int n){
if(n <= 1){
return 1;
}else{
return (n+sum(n-1));
}
}
int main(int argc, const char *argv[])
{
int s;
s = sum(10);
printf("s = %d\n",s);
return 0;
}
回调函数
回调函数就是一个通过函数指针调用的函数,在前面的栗子其实已经有接触到
回调函数一般形式
<数据类型> <函数名称> (<参数说明列表>)
{
语句序列;
}
回调函数实现机制
1.定义一个回调函数
2.提供函数实现的一方法在初始化的时候,将回调函数的函数指针注册给调用者
3.当特定的事件或条件发生的时候,调用者使用函数指针调用回调函数对事件进行处理
举个栗子
#include <stdio.h>
typedef int (*func)(int a,int b);
int add(int a,int b){
return (a+b);
}
int sub(int a,int b){
return (a-b);
}
int mul(int a,int b){
return (a*b);
}
int test(func fun,int a,int b){
return (fun(a,b));
}
int main(int argc, const char *argv[])
{
int x = 100,y = 50;
printf("a + b = %d\n",test(add,x,y));
printf("a - b = %d\n",test(sub,x,y));
printf("a * b = %d\n",test(mul,x,y));
return 0;
}
attribute机制
attribute是GNU-C特有的内容,也就是Linux-C里面的内容,如果是在window下的C是没有这一部分的
attribute 可以设置函数属性(Function Attribute)、变量属性(Variable Attribute)和类型属性(Type Attribute)
attribute 语法格式为: attribute ((attribute-list))
当__attribute__ 用于修饰对象时,它就如同C 语言语法体系结构的类型限定符
当__attribute__ 用于修饰函数时,它就相当于一个函数说明符
当__attribute__ 用于修饰一个结构体,联合体或者枚举类型
关于attribute机制总结的表格
函数属性(Function Attribute) | 类型属性(Type Attribute) | 变量属性(Variable Attribute) |
---|---|---|
noreturn | aligned | alias |
noinline | packed | at(address) |
always_inline | bitband | aligned |
flatten | deprecated | |
pure | noinline | |
const | packed | |
constructor | weak | |
destructor | weakref(“target”) | |
sentinel | section(“name”) | |
format | unused | |
format_arg | used | |
section | visibility(“visibility_type”) | |
used | zero_init | |
unused | ||
deprecated | ||
weak | ||
malloc | ||
alias | ||
warn_unused_result | ||
nonnull | ||
nothrow |
C语言库函数
头文件
C语言中包含了许多已经被定义的系统函数,这些函数分别被定在不同的头文件中
头文件 | 名称 | 功能 |
---|---|---|
assert.h | 程序验证头文件 | 用于验证程序做出的假设,并在假设为假时输出诊断消息 |
ctype.h | 测试映射头文件 | 用于测试和映射字符 |
errno.h | 错误事件头文件 | 在错误事件中的某些库函数表明了什么发生了错误 |
float.h | 浮点相关头文件 | 包含了一组与浮点值相关的依赖于平台的常量 |
limits.h | 宏限制头文件 | 决定了各种变量类型的各种属性 |
locale.h | 特定地域设置头文件 | 定义了特定地域的设置,比如日期格式和货币符号 |
math.h | 数学函数头文件 | 定义了各种数学函数和一个宏 |
setjmp.h | jmp相关头文件 | 定义了宏 setjmp()、函数 longjmp() 和变量类型 jmp_buf,该变量类型会绕过正常的函数调用和返回规则 |
signal.h | 信号处理头文件 | 定义了一个变量类型 sig_atomic_t、两个函数调用和一些宏来处理程序执行期间报告的不同信号 |
stdarg.h | 函数参数获取头文件 | 定义了一个变量类型 va_list 和三个宏,这三个宏可用于在参数个数未知(即参数个数可变)时获取函数中的参数 |
stddef.h | 变量宏定义头文件 | 定义了各种变量类型和宏。这些定义中的大部分也出现在其它头文件中 |
stdio.h | 输入输出头文件 | 定义了三个变量类型、一些宏和各种函数来执行输入和输出 |
stdlib.h | 通用工具头文件 | 定义了四个变量类型、一些宏和各种通用工具函数 |
string.h | 字符串处理头文件 | 定义了一个变量类型、一个宏和各种操作字符数组的函数 |
time.h | 时间和时间头文件 | 定义了四个变量类型、两个宏和各种操作日期和时间的函数 |
库函数
stdio.h相关库函数
函数 | 功能 |
---|---|
fclose | 关闭流stream |
clearerr | 清除给定流 stream 的文件结束和错误标识符 |
feof | 测试给定流 stream 的文件结束标识符 |
ferror | 测试给定流 stream 的错误标识符 |
fflush | 刷新流 stream 的输出缓冲区 |
fgetpos | 获取流 stream 的当前文件位置,并把它写入到 pos |
fopen | 使用给定的模式 mode 打开 filename 所指向的文件 |
fread | 从给定流 stream 读取数据到 ptr 所指向的数组中 |
freopen | 把一个新的文件名 filename 与给定的打开的流 stream 关联,同时关闭流中的旧文件 |
fseek | 设置流 stream 的文件位置为给定的偏移 offset,参数 offset 意味着从给定的 whence 位置查找的字节数 |
fsetpos | 设置给定流 stream 的文件位置为给定的位置。参数 pos 是由函数 fgetpos 给定的位置 |
ftell | 返回给定流 stream 的当前文件位置 |
fwrite | 把 ptr 所指向的数组中的数据写入到给定流 stream 中 |
remove | 删除给定的文件名 filename,以便它不再被访问 |
rename | 把 old_filename 所指向的文件名改为 new_filename |
rewind | 设置文件位置为给定流 stream 的文件的开头 |
setbuf | 定义流 stream 应如何缓冲 |
setvbuf | 另一个定义流 stream 应如何缓冲的函数 |
tmpfile | 以二进制更新模式(wb+)创建临时文件 |
tmpnam | 生成并返回一个有效的临时文件名,该文件名之前是不存在的 |
fprintf | 发送格式化输出到流 stream 中 |
printf | 发送格式化输出到标准输出 stdout |
sprintf | 发送格式化输出到字符串 |
vfprintf | 使用参数列表发送格式化输出到流 stream 中 |
vprintf | 使用参数列表发送格式化输出到标准输出 stdout |
vsprintf | 使用参数列表发送格式化输出到字符串 |
fscanf | 从流 stream 读取格式化输入 |
scanf | 从标准输入 stdin 读取格式化输入 |
sscanf | 从字符串读取格式化输入 |
fgetc | 从指定的流 stream 获取下一个字符(一个无符号字符),并把位置标识符往前移动 |
fgets | 从指定的流 stream 读取一行,并把它存储在 str 所指向的字符串内 |
fputc | 把参数 char 指定的字符(一个无符号字符)写入到指定的流 stream 中,并把位置标识符往前移动 |
fputs | 把字符串写入到指定的流 stream 中,但不包括空字符 |
getc | 从指定的流 stream 获取下一个字符(一个无符号字符),并把位置标识符往前移动 |
getchar | 从标准输入 stdin 获取一个字符(一个无符号字符) |
gets | 从标准输入 stdin 读取一行,并把它存储在 str 所指向的字符串中 |
putc | 把参数 char 指定的字符(一个无符号字符)写入到指定的流 stream 中,并把位置标识符往前移动 |
putchar | 把参数 char 指定的字符(一个无符号字符)写入到标准输出 stdout 中 |
puts | 把一个字符串写入到标准输出 stdout,直到空字符,但不包括空字符。换行符会被追加到输出中 |
ungetc | 把字符 char(一个无符号字符)推入到指定的流 stream 中,以便它是下一个被读取到的字符 |
perror | 把一个描述性错误消息输出到标准错误 stderr。首先输出字符串 str,后跟一个冒号,然后是一个空格 |
snprintf | 格式字符串到 str 中 |
新系列预告
关于库函数,会专门开设一个新的系列来讲解,敬请期待
更多内容
来源:CSDN
作者:CagePan
链接:https://blog.csdn.net/qq_41030935/article/details/103998816