一、设计函数
能拆成函数的尽量拆成函数
函数设计时尽可能考虑健壮性扩展性
二、函数调用的关系
调用者和被调用者
int main(void)
{
printf("%d\n",getMonthDays());
return 0;
}
这个里面:
main ---成为调用者 ---main函数是整个程序的入口,只能调用者
getMonthDays --- 在此处是 被调用者
getMonthDays()
{
isLeapYear();
} //函数的嵌套调用
main -->getMonthDays --> isLeapYear
注:
函数不支持 嵌套定义,但是可以嵌套调用
函数名 --- 函数的入口地址
函数调用的本质:
实际是利用的栈的结构 ---先进后出 --保证了函数可以层层嵌套调用
-
栈的使用:C语言使用栈(stack)来管理函数调用过程中的数据和控制信息。当一个函数被调用时,函数的参数、局部变量以及函数返回地址等信息都会被压入栈中。
-
参数传递:函数调用时,参数的传递通常是通过栈来实现的。在调用函数时,调用者将参数值压入栈中,然后被调用函数将栈中的参数值弹出来使用。
-
函数调用约定:C语言并没有强制规定函数调用的具体方式,而是由编译器和操作系统决定。常见的调用约定包括 cdecl、stdcall 等,它们影响参数的传递方式和堆栈的清理责任。
-
返回值处理:被调用函数执行完毕后,会将返回值存放在事先约定的位置(通常是寄存器或者栈中),然后返回到调用者处。调用者会根据约定从事先约定的位置取得返回值。
-
跳转和恢复:当一个函数被调用时,CPU会保存当前的执行位置(通常是返回地址)并跳转到被调函数的起始位置执行。当被调函数执行完毕后,CPU会根据保存的返回地址跳转回调用函数的执行位置继续执行。
再调用函数前会保存当前位置在栈中(保存现场),调用结束后返回当前位置继续执行(恢复现场)
栈:
数据结构 --- (表示数组组织形式)
特点:
先进后出 (First In Last Out) //FILO
c语言角度的栈:
1.本质上是一块内存空间
2.只是按照 栈 这种数据结构 来处理和使用的
栈:
局部变量 //空间 自动申请 自动释放
C语言程序:
把内存划分了5个区域
栈 //主要 用来存放, 自动变量 或 函数调用的数据
堆 //空间大 堆上的空间 ,手动申请,手动释放
字符串常量区 // "hello" (只读)
静态区(全局区) // 全局变量 和 静态变量
代码区 // 只读的
程序 = 代码 + 数据
三、特殊嵌套调用---递归
递归:
自己调用自己 //
直接递归
间接递归
递归思路:
要求问题n
依赖于问题n-1的解决
递归代码实现思路:
1.递推关系
怎么从 问题 n 到 问题n-1
例如:求前一百个数的和
sum(100) => sum(99)+100
sum(99) => sum(98)+99
sum(n) = sum(n-1)+n;
2.递推结束条件
n = 1
//3.代码
代码实现
#include<stdio.h>
int sum(int n)
{
if(n == 1)
{
return 1;
}else
{
return sum(n-1) + n;
}
}
int main(void)
{
int ret;
scanf("%d",&ret);
ret = sum(ret);
printf("ret = %d\n",ret);
return 0;
}
练习:求前n项的阶乘
factorial(5)
|---factorial(4)*5
|---factorial(3)*4
factorial(n) = factorial(n-1)*n
#include<stdio.h>
int sum(int n)
{
if(n == 1)
{
return 1;
}else
{
return sum(n-1) * n;
}
}
int main(void)
{
int ret;
scanf("%d",&ret);
ret = sum(ret);
printf("ret = %d\n",ret);
return 0;
}
练习:
斐波拉契数列
1 1 2 3 5 8
求斐波拉契数列第n项
fibo(5)
|--fibo(4)+fibo(3)
| |-- fibo(2) + fibo(1)
|--fibo(3) + fibo(2)
|-- fibo(2) + fibo(1)
1 1
#include<stdio.h>
int feibo(int n)
{
if(n==1 || n==2)
{
return 1;
}else
{
return feibo(n-1)+feibo(n-2);
}
}
int main(void)
{
int a;
scanf("%d",&a);
printf("%d\n",feibo(a));
return 0;
}
四、数组作为函数参数
1.数组元素作为函数参数
int a[10] = {1,2,3};
int add(int a,int b)
{
return a + b;
}
add(a[0],a[1]);
2.数组本身作为函数参数
eg:
printArrray(int a[],int len) //形参
//printArrray (int *a,int len) ---编译器最终理解的形式
//调用
printArray(a,len);
int a[10];
数组名 代表类型 ---int[10] 这种数组类型
数组名 代表的值 ---首元素的地址 //(数组所占内存空间的首地址)
一维整型数组 做函数 参数
形参 --写成数组形式 还需要 数组长度
实参 --数组名,数组长度
形参与实参的对应
在C语言中,形参的定义方式可以是数组形式 int a[]
,也可以是指针形式 int *a
。实际上,这两种方式在函数调用时都可以接受数组作为参数,因为在函数调用过程中,数组名会被隐式转换为指向数组首元素的指针。
所以,无论是使用 int a[]
还是 int *a
,在函数调用时都可以传递一个整型数组 a
,同时需要传递数组的长度 len
。
编译器的最终理解
在编译阶段,不论你声明函数时使用 int a[]
还是 int *a
,编译器都会将它们解释为指针形式 int *a
。因此,实际上函数声明可以写成 void printArray(int a[], int len);
或 void printArray(int *a, int len);
,它们在编译器眼中是等效的。
使用函数输出一个数组
#include<stdio.h>
void printArray(int a[])
{
int len = sizeof(a) / sizeof(a[0]);
for (int i = 0; i < len; ++i)
{
printf("%d ", a[i]);
}
printf("\n");
}
int main(void)
{
int a[] = {1,2,3,4,5,6,7,8,9};
printArray(a);
return 0;
}
原因解析
-
数组参数传递为指针: 在C语言中,数组作为函数参数时会自动转换为指向数组首元素的指针。因此,
int a[]
在函数参数中实际上被理解为int *a
。在函数内部,a
是一个指针,它指向传递给函数的数组的首地址。 -
sizeof在函数中的行为: 当你在函数内使用
sizeof(a)
时,它返回的是指针a
的大小,而不是整个数组的大小。在大多数情况下,指针的大小是固定的,通常是4或8个字节(取决于编译器和操作系统)。 -
计算数组长度的错误方式:
int len = sizeof(a) / sizeof(a[0]);
这行代码试图通过指针a
的大小除以a[0]
的大小来计算数组的元素个数。但由于a
是一个指针,这个计算并不会得到你期望的结果,而是得到一个较小的值(通常为2),这解释了为什么你的代码只输出了两个数。
正确做法为
#include<stdio.h>
void printArray(int a[],int len)
{
int i = 0;
for(i = 0;i < len;++i)
{
printf("a[%d] = %d\n",i,a[i]);
}
}
int main(void)
{
int a[] = {1,2,3,4,5,6,7,8,9};
int len = sizeof(a)/sizeof(a[0]);
printArray(a,len);
return 0;
}
练习:
准备一个数组,实现一个函数,找出最大值
#include<stdio.h>
int max(int a[],int len)
{
int max = a[0];
int temp;
int i;
for(i = 1;i < len;++i)
{
if(max < a[i])
{
temp = a[i];
a[i] = max;
max = temp;
}
}
return max;
}
int main(void)
{
int n;
printf("请输入数组长度:");
scanf("%d",&n);
int a[n];
int len = sizeof(a)/sizeof(a[0]);
printf("请输入数组元素:");
int i = 0;
for(i = 0;i < len;++i)
{
scanf("%d",&a[i]);
}
printf("max = %d\n",max(a,len));
return 0;
}
由于传递的是指针地址,为直接在原数组地址上操作,所以函数可以改变数组的值
练习:
实现数组逆序
#include<stdio.h>
int nixu(int a[],int len)
{
int i;
int temp;
for(i = 0;i < len/2;++i)
{
temp = a[i];
a[i] = a[len-i-1];
a[len-i-1] = temp;
}
}
int main(void)
{
int n;
printf("请输入数组长度:");
scanf("%d",&n);
int a[n];
int len = sizeof(a)/sizeof(a[0]);
printf("请输入数组元素:");
int i = 0;
for(i = 0;i < len;++i)
{
scanf("%d",&a[i]);
}
nixu(a,len);
for(i = 0;i < len;++i)
{
printf("a[%d] = %d\n",i,a[i]);
}
return 0;
}
练习:
排序 使用插入排序
#include<stdio.h>
void charu(int a[],int len)
{
int i,j;
int temp;
for(i = 1;i < len;++i)
{
temp = a[i];
j = i;
while(j > 0 && temp < a[j-1])
{
a[j] = a[j-1];
--j;
}
a[j] = temp;
}
}
int main(void)
{
int n;
printf("请输入数组长度:");
scanf("%d",&n);
int a[n]; // = {4,5,3,2,4,7,8,3,2,12,18};
int len = sizeof(a)/sizeof(a[0]);
printf("请输入数组元素:");
int i = 0;
for(i = 0;i < len;++i)
{
scanf("%d",&a[i]);
}
charu(a,len);
for(i = 0;i < len;++i)
{
printf("a[%d] = %d\n",i,a[i]);
}
return 0;
}
练习:
查找
#include<stdio.h>
int chazhao(int a[],int len,int m)
{
int begin = 0;
int mid;
int end = len - 1;
while(begin <= end)
{
mid = (begin + end)/2;
if(a[mid] > m)
{
end = mid -1;
}else if(a[mid] < m)
{
begin = mid + 1;
}else
{
break;
}
}
if(begin <= end)
{
return mid;
}
else
{
return -1;
}
}
void charu(int a[],int len)
{
int i,j;
int temp;
for(i = 1;i < len;++i)
{
temp = a[i];
j = i;
while(j > 0 && temp < a[j-1])
{
a[j] = a[j-1];
--j;
}
a[j] = temp;
}
}
int main(void)
{
int n;
printf("请输入数组长度:");
scanf("%d",&n);
int a[n]; // = {4,5,3,2,4,7,8,3,2,12,18};
int len = sizeof(a)/sizeof(a[0]);
printf("请输入数组元素:");
int i = 0;
for(i = 0;i < len;++i)
{
scanf("%d",&a[i]);
}
charu(a,len);
printf("排序过后数组为:\n");
for(i = 0;i < len;++i)
{
printf("a[%d] = %d\n",i,a[i]);
}
int m;
printf("请输入要查找的数:");
scanf("%d",&m);
int s = chazhao(a,len,m);
if(s >=0)
{
printf("a[%d] = %d\n",s,a[s]);
}
return 0;
}