在C语言中,函数是执行特定任务的功能模块。我们需要函数是为了解决复杂问题,将其拆成小问题,对应到一个功能模块上,并且增加代码的复用性,提高开发效率。
一 函数:
1.语法:
类型标识符 函数名(形式参数列表)
{
函数体; //实现具体功能 对应代码
}
(1)类型标识符 --- 数据类型
(2)函数名 --- 名字,标识符
(3)形式参数 --- 变量的定义,主要用来接收 实际的数据
(4)函数体 --- 实现具体功能 对应代码
2.返回值类型
返回结果对应的数据类型 函数名(数据入口)
{
函数体; //实现具体功能 对应代码 ---处理数据
return 表达式; //--带出结果
}
3.函数调用:函数名(实际参数):如add(1,2);,c = c + add(1,2);
4.函数的返回值:
(1)返回值类型:如函数不需要带出结果使用void 类型
(2)返回值类型也可以与返回结果类型 不一致,此时,以规定的返回值类型为准
(3)返回值也可以不写类型,默认是 int型
5.函数的声明:函数定义在main函数之后,在main函数中使用前,需要声明函数定义的位置:函数头+分号
6.局部变量和全局变量:处于 {} 范围内的都叫局部变量 ,{} 这个范围叫做局部变量的作用域
二 数组作为函数参数:
1.传数组名作为实参过去形参写成:void printArray(int a[10]),形式上,说明准备接收一个数组
void printArray(int *a),本质上,指针类型的变量,用来接收数组的名字 (数组名代表的是数组首元素的地址)
{ 在函数内部可以访问到数组元素 ---(指针再说)
}
2.整型二维数组做函数参数:
形参:数组形式 行数;int A(int(*a)[n],int row)
实参:数组名 行数 ;A(a,row)
3.字符型一维数组做函数参数:
形参:数组形式;char A(char a)
实参:数组名(不需要传数组长度 ---- 字符串本身有结束标志 );A(a)
4.字符型二维数组:与整型 二维数组作函数参数 书写方式相同
形参:数组形式 行数
实参:数组名 函数
三 函数的嵌套调用:
1.在了解嵌套调用前,先了解一些数据结构的一些基本知识
(1)栈 --- 函数调用过程中的数据 以及 局部变量
特点:自动申请自动释放(局部变量)
(2)堆 --- 特点:空间大,但需要程序员手动申请手动释放
(3)全局区(静态区):全局的数据
(4)字符串常量区:如char s[] = "hello";
(5)代码区:程序 = 数据 + 代码
2.特殊的嵌套调用:递归,即自己调用自己
(1)递归本质上还是一种循环
(2)与for while do-while 循环有区别:栈的空间有限,一定会结束
(3)在效率上递归并不高效
(4)递归解决问题的思路:问题n,解决依赖,问题n-1,的解决
递归的代码结构:
int sum(int n)
{
什么情况下 要递推下去
什么情况下 要结束
if (n==1)
{
return 1;
}else
{
return sum(n-1) + n;
}
}
四 标识符的作用域 和 可见性问题
1.作用域:即变量的作用范围,分为局部作用域和全局作用域
2.可见性: 从程序的角度看,在运行到某句代码时,哪些标识符,可以被用
标识符的可见性的规则:
(1)先定义,后使用
(2)同一作用域中,不能有同名标识符
(3)在不同的作用域,同名标识符,相互之间没有影响
(4)如果是不同的作用域,但是作用域之间存在嵌套关系,则内层的作用域的同名标识符,会屏蔽外层的作用域的同名标识符。(就近原则)
3.变量的生命周期:
(1)局部变量的生命期:从定义处开始,到作用域结束
(2)静态变量(全局变量)的生命周期:从程序运行开始,到程序结束
修饰的关键字:static,静态修饰局部变量,表示将局部变量,放到静态区
(1)static 修饰的变量不能用变量初始化 ,需要用常量
(2) static 修饰的局部变量值, 只需要初始化一次,具有继承性;static 修饰全局变量,限定该变量只能被本文件使用(限定了它的作用域 static 修饰的全局变量 ,不能再用extern)
auto int a:局部变量 默认就是auto 存储类别的关键,一般都省略不写
register int a:寄存器,建议性的关键字,register 修饰的变量,不能 & (取地址)
extern int a:表示这个a不在本文件中,而是在外部的文件中定义的,需要到外部的文件中寻找,extern 扩展了变量的作用域
五 gcc编译代码的流程:
预处理:预编译 ---- 为编译阶段做准备---- 将.c代码中预处理命令执行---- 得到全是c代码的文件
eg:gcc -E hello.c -o hello.i
编译阶段:将c代码的文件,编译成汇编代码
eg: gcc -S hello.c -o hello.s
gcc -S hello.i -o hello.s
汇编阶段:将.s文件编译成机器指令
eg:gcc -c hello.s -o hello.o
链接:需要将用到的别的函数的代码实现链接到最终的可执行程序中
eg:gcc hello.o -o hello
预处理需要注意的点:
(1)预处理本身不是c语言的一部分
(2)预处理命令都是#开头
(3)预处理阶段要做的事:执行预处理的命令,完成文本原样替换
五 宏定义:#define
1.宏定义语法:#define 标识符 字符串;#define 宏名 宏值
eg.define N 10 宏定义在预处理阶段 ,用宏值将出现宏名的统统原样替换
2.带参宏: #define 宏名(参数) 宏值;define SUM(a,b) a+b,又叫宏函数,但它不是函数,只是形式上有些相似。区别:函数形式参数带类型,函数往往有返回值,函数返回值往往有类型
(1)带参宏:预处理阶段发挥作用
(2)函数:程序运行时发挥作用
(3)但是宏替换可能导致代码体积偏大,而函数在代码中只保留一份
3.宏的副作用:完完全全的文本替换,涉及运算符优先级时,能加括号都加括号;宏的定义要求必须写在同一行(续航符: \ 符号后面不能空格)
4.宏作用域:从宏定义处开始到本文件结束,但是可以使用#undef强制结束宏作用域
六 文件包含:在预处理阶段,表示将文件中的内容,展开在这个位置(文本替换)
(1)#include <文件名>:系统的默认(头文件)路径下寻找文件 --- 系统下,一般都用<>
(2)#include "文件名":首先,先在当前位置下寻找,如果没找到再到系统默认路径下寻找
六 条件编译:
#if 0
#endif
形式1:
#ifdef 表达式
程序段1
#else
程序段2
#endif
形式2:
#ifndef 表达式
程序段1
#else
程序段2
#endif
Linux编译时,gcc -D宏名可以快捷定义宏
条件编译的用途:debug版本 --- 调试版本,release版 --- 去除掉调试信息;实现头文件,可以避免,因重复包含,带来重复定义的问题