Linux C: 函数
本篇进入了函数的部分,也是C语言进阶阶段的第二个重难点,本文主要从函数的定义、声明、调用三个方面来认识和学习函数的使用,并学习了计算机的底层原理的实现、局部变量、全局变量、变量的生存期来更好的理解代码的存储,函数的递归调用举例了汉诺塔游戏来加以证明,希望大家有更好的理解,还有少部分内容(数组作为参数传递等问题)没有记录上,明天会在文后加以补充
一、 函数基础常识
在C语言中,子程序的作用是由函数来完成的。一个C程序可由一个主函数和若干个其他函数构成。由主函数调用其他函数,其他函数也可以互相调用。同一个函数可以被一个或多个函数调用任意多次。
函数的好处:①降低了程序的耦合性
②提高代码的复用性
eg:
说明:
(1)一个C程序由一个或多个程序模块组成,每一个程序模块作为一个源程序文件对较大的程序,一般不希望把所有内容全放在一个文件中,而是将它们分别放在若干个源文件中,再由若干个源程序文件组成一个C程序。
(2)一个源程序文件由一个或多个函数以及其他有关内容(如命令行、数据定义等)组成。一个源程序文件是一个编译单位,在程序编译时是以源程序文件为单位进行编译的,而不是以函数为单位进行编译的。
(3)C程序的执行是从main函数开始的,如是在main函数中调用其他函数,在调用后流程返回到 main函数,在main 函数中结束整个程序的运行。
(4)所有函数都是平行的,即在定义函数时是分别进行的,是互相独立的。一个函数并不从属于另一个函数,即函数不能嵌套定义。函数间可以互相调用,但不能调用main
函数。main函数是系统调用的。 //函数的定义和函数的声明是不同的
(5)从用户使用的角度看,函数有两种。
①标准函数。标准函数即库函数,它是由系统提供的,用户不必自己定义而直接使用它们。应该说明,不同的C语言编译系统提供的库函数的数量和功能会有一些不同当然许多基本的函数是共同的。
②用户自己定义的函数。它是用以解决用户专门需要的函数。
(6)无参函数:参数个数为0个就可以被称为无参函数
二、函数的定义
2.1 有参函数定义的一般形式
类型标识符 函数名(形式参数表列) //函数的首部
{
声明部分
语句部分
}
注:类型标识符:所有数据类型都可使用
函数名: 标识符 ,需符合标识符的一般规则
形式参数表列:简称形参列表,该部分填写的是 函数在使用或被调用时所需要提供的额外条件
2.2 函数的调用
函数名(实际参数)
//站在两个角度
int add(int a, int b) //函数的设计者
{int ret;ret = a + b;return ret;
}int main(void) //函数的使用者
{int i= 10,j= 20;int sum; sum = add(i, j); //add(i,j)即为:函数名(实际参数)printf("%d\n", sum);printf("%d\n", add(11, 33));return 0;
}
注意:
①实参名和形参名可以相同,但二者代表的含义不同
②C语言语法要求函数的形参和实参类型匹配(可以进行隐式转换即可),个数相同
③以上述代码为例:主函数(main函数)被称为主调函数,add函数被称为被调函数; 写代码时将被调函数写于主调函数之前
return语句的作用:
①程序执行到return语句时会立即终止函数并将函数返回到函数的调用并继续向下执行
注意:被调函数中必须通过return语句进行返回,且返回类型应与前所定义的数据类型相同
注意:返回值不能是数组,但返回值和形参可以是void,直接使用return;向void进行返回
若未定义返回值类型则在C语言当中默认返回值为int型
在写程序时,每个形参都要单独定义数据类型,中间用逗号隔开
2.3值传递
输出结果i的值不变,仍为10;
被调函数中无法修改由主调函数所定义的变量
当所设置的函数有两个或两个以上的参数时,该函数传参的顺序是自右向左传参
2.4 函数的声明
三、底层实现原理
PC:指向所执行的下一条地址
栈:先进的后出,后进的先出
入栈:保护现场 出栈:恢复现场
栈区(stack) | 栈区空间自动分配 |
堆区 (heap) | 空间需要主动申请且需要释放 |
静态区(全局区) | 在程序当中定义的静态变量(全局变量) |
字符串常量区 | 存放字符串常量 |
文本区(代码区) | 存放代码本身 |
四、函数的递归调用
在调用一个函数的过程中又出现直接或间接地调用该函数本身,称为函数的递归调用。C语言的特点之一就在于允许函数的递归调用。例如:
int f(int x)
{int y,z;z = f(y);return(2*z);
}
Linux下栈区空间为8M,直接调用会造成崩溃(短时间内迅速堆满栈区),并不会呈现死循环现象
//直接调用
void f1(void)
{f1();
}
int main(void)
{f1();return 0;
}//间接调用
void f2(void)
{f1();
}void f1(void)
{f2();
}int main(void
{f1();return 0;
}
递归调用实现循环(若数据很大时)仍会导致栈区堆满,以消耗大量的内存空间为代价,造成程序崩溃【每次调用都需要保护现场和恢复现场,实际效率更差,低于for循环】
使用推荐:编写程序要求函数内部不的定义变量、不得使用循环(For/do While /While)/使用循环无法完成
汉诺塔游戏(递归)
void move(char pole1, char pole2) {printf("%c->%c\n", pole1, pole2); }void hanoi(int n, char pole1, char pole2, char pole3) {if(1 == n){move(pole1, pole3);}else{hanoi(n-1,pole1,pole3,pole2); move(pole1,pole3); hanoi(n-1, pole2, pole1, pole3):} }int main(void) {hanoi(64,'A','B','C');return 0; }
五、 局部变量、全局变量、变量的生存期
标识符的作用域和可见性问题
局部变量:在一对花括号内部起作用
特殊情况:所定义的函数的形参也具有局部作用的效果;作用域定义的函数内部
全局作用域:所有在花括号外部的区间:在所定义的行数及以后行数直到文件结束区间都有效
全局变量(g)
eg: int g_i;
C语言当中的所有的函数名都属于全局变量
全局变量的作用:为了实现类似于函数的传参
int g_a; int g_b;int add(void) {return g_a + g_b; }int main(void) {g_a = 10;g_b = 20;printf("%d\n",add());return 0; }
该程序中g_a和g_b存在于全局区(静态区),其他定义的变量存放于栈区,而存在于全局区的变量如果没有进行初始化,C语言在开辟空间时会自动清零(打印结果必然为0!!),若进行初始化,则该值为所初始化的值
局部作用域包含于全局作用域
可见性:当程序运行到某一点时,对某个标识符是否可以访问或使用,称之为该标识符在程序某一点的可见性
总结:
1.标识符必须先定义再使用;
2.在同一作用域中不得定义同名标识符;
3.在没有包含关系的不同作用域中定义的同名标识符互不影响;
4.在两个或多个具有包含关系的作用域中定义的同名标识符,外层标识符在内层不可见;(就近原则)
变量的生存期:
1.静态生存期:该变量的生存期与程序运行的周期相同
所有的全局变量就具有静态生存期,全局变量的生存期与所在程序的运行周期是相同的
特殊情况:
void fn(void)
{
static int s_i;
}
"static"会使s_i的动态生存期强制变为静态生存期
static + 全局变量:限制该变量的适用范围,只能在当前文件中被使用不能被其他.c文件所使用
2.动态生存期:
所有的局部变量具有动态生存期
总结记忆:存在与栈区的变量具有动态生存期;
存在于全局区(静态区)的变量具有静态的生存期;
用static修饰的静态局部变量具有静态的生存期;(具有静态生存期但存在于局部作用域)
auto变量 : 变量空间的开辟和销毁是自动的
register变量(寄存器):开辟变量空间时从RAM中开辟到CPU内部 (建议) 目的 :提高对变量的读写速率 注意:被register修饰的变量不能被取地址
extern函数:对函数声明,声明的函数属于外部程序中的函数
在vi中实现多文件编写:
按esc后,输入 :vsp +加新文件名 回车(左右)
按esc后,输入 :sp +加新文件名 回车(上下)
再进行编译时所有参与的文件都要一起进行编译
eg:
gcc -oapp main.c func.c
在所在程序中调用其他文件程序:在所调用程序前要对所用函数进行声明
eg:
extern int add(int a , int b); //注意要加分号,加分号为声明
//int add(int a , int b) //不加分号为定义
再多文件编写时解除vi对鼠标的限制:
按esc后,输入 :set mouse=a
可以另开一个xxxx.h的头文件进行函数的声明 ,该头文件中只放声明,不放定义(否则会造成重复定义)