1.Review
2.库函数标例rands、in、localtime
3.其它库函数示例
4.自定义函数的声明_定义_调用
c语言又称为是函数语言
,将具体功能函数化,然后组织整个架构逻辑,这样亦是软件的设计原则,自顶向下,逐步细化
。
比如下面的代码结构,就可以看的出来,整个程序实现的功能从main开始,分了三个大的模块。每个模块下,又有子模块。
自定义函数必须满足如下格式,样式跟我们前面使用的库函数基本一致,只不过多了一个函数体,需要去实现。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
//定义和声明的关系
//定义在前,调用在后
//定义在后,调用在前,此时需要前向声明
int myMax(int i,int j)
{
// if(i>j){
// return i;
// }
// else {
// return j;
// }
return i>j?i:j;
}
int main(int argc, char *argv[])
{
int a = 3;
int b = 5;
int iMax = myMax(a,b);
printf("max value %d\n",iMax);
return 0;
}
#include <stdio.h> //将printf的声明放到调用之前
#include <stdlib.h>
#include <time.h>
//定义和声明的关系
//定义在前,调用在后
//定义在后,调用在前,此时需要前向声明,函数一个特点,先声明后使用
//入参中如果没有参数,可以使用void表示无入参。通常省略
//如果没有返回值,即返回类型是void,void不可以省
int myMax(int i,int j);
int main(int argc, char *argv[])
{
int a = 3;
int b = 5;
int iMax = myMax(a,b);
printf("max value %d\n",iMax);
return 0;
}
int myMax(int i,int j)
{
// if(i>j){
// return i;
// }
// else {
// return j;
// }
return i>j?i:j;
}
实参与形参
1.形参
在定义或声明函数中指定的形参,在未出现函数调用时,它们并不占内存中的存储单元。只有在发生函数调用时,形参才被分配内存单元。在调用结束后,形参所占的内存单元也被释放。
2.实参
实参可以是常量、变量或表达式,但要求它们有确定的值。在调用时将实参的值赋给形参。
5.传值与传址和传递一维数组
传值与传址
传值与传址,本质是都是传递一个数值而己。
1.传值
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
//进程空间
//函数在被调用之前,其内所有的变量尚未开辟空间。
//空间的开辟起始于函数调用,空间消失结束于函数调用完毕
//传值
void func(int a);
int main(int argc, char *argv[])
{
int a=10;
func(10);
printf("main : a = %d\n",a);
return 0;
}
void func(int a)
{
a++;
printf("func : a = %d\n",a);
}
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
//能过传值的方式,达不到修改变量a的内容的目的。
//地址对于不同的作用域来说,是开放的。
void mySwap(int* pa,int* pb);
int main(int argc, char *argv[])
{
int a=10,b=20;
printf("a = %d b = %d\n",a,b);
mySwap(&a,&b);
printf("a = %d b = %d\n",a,b);
return 0;
}
void mySwap(int* pa,int* pb)
{
int t = * pa;
*pa = *pb;
*pb =t;
}
如何来传递一个一维数组
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
//数组的传递不可能通过拷贝的方式来传递。c语言基于效率的原因,只传递首地址
//数组有三要素,起始地址,步长(刻度),范围
//数组名,是一个指针包含了起始地址,步长(刻度),但是没有范围
//所以在传递一维数组的时候,要传数组名和范围
void disArray(int* p,int n);
int main(int argc, char *argv[])
{
int arr[] ={1,2,3,4,5,6,7,8,9,10,12,12,12,22};
printf("main sizeof(arr) = %d\n",sizeof(arr));
disArray(arr,sizeof (arr)/sizeof (arr[0])); // == disArray(&arr[0]);
return 0;
}
void disArray(int* p,int n)
{
printf("main sizeof(arr) = %d\n",sizeof(p));
for (int i=0;i<n;i++) {
printf("%d\n",*p++);
}
}
6.函数在结构设计和逻辑设计中的应用
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
//数组的传递不可能通过拷贝的方式来传递。c语言基于效率的原因,只传递首地址
//数组有三要素,起始地址,步长(刻度),范围
//数组名,是一个指针包含了起始地址,步长(刻度),但是没有范围
//所以在传递一维数组的时候,要传数组名和范围
void initRandArr(int *arr,int n)
{
srand(time(NULL));
for (int i=0;i<n;i++) {
*arr++ = rand()%100;
}
}
void display(int *arr,int n)
{
for (int i=0;i<n;i++) {
printf("%2d\n",*arr++);
}
}
int smallextIdx(int startIdx,int *arr,int n)
{
int idx = startIdx;
for(int i = startIdx+1;i<n; i++){
if(arr[i]<arr[idx])
idx = i;
}
return idx;
}
void mySwap(int *pa,int *pb)
{
int t = *pa;
*pa = *pb;
*pb = t;
}
void selectSort(int *arr,int n)
{
int idx;
for (int i=0;i<n-1;i++) {
idx = smallextIdx(i,arr,n);
if(idx!=i)
{
mySwap(&arr[i],&arr[idx]);
}
}
}
int main(int argc, char *argv[])
{
int arr[10];
initRandArr(arr,10);
display(arr,10);
selectSort(arr,10);
printf("after sort \n");
display(arr,10);
return 0;
}
如何来传递一个二维数组
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
void func(int (*p)[4],int n)
{
for (int i=0;i<n;i++) {
for (int j=0;j<4;j++)
{
printf("%3d",p[i][j]);
}
putchar(10);
}
}
int main(int argc, char *argv[])
{
int arr[3][4] = {{1},{2,3},{4,5,6}};
func(arr,3);
return 0;
}
7.递归程序设计与提高
普通调用
所有函数都是平行的
即在定义函数时是分别进行的,是互相独立的。函数间可以互相调用。常见有,平行调用,嵌套调用。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
//嵌套调用,多了会挂掉,栈溢出
void foo();
void func()
{
printf("func()\n");
foo();
//main();
}
void foo()
{
printf("foo()\n");
func();
}
int main(int argc, char *argv[])
{
func();
foo();
return 0;
}
递归调用
既然函数是平行的,可以相互调用,如果自己调用自己会如何呢?
函数,自身直接调用
自身,或是间接调用
自身的的现象,称为递归。比如生活中,经常有这样的推理过程。
直接或是或是间接调用自己的情形,就递归调用。recursive
递归,是比较接近自然语言特性的一种调用方式。递归必须要用合理的出口,不然会挂掉
练习:
有5个人坐在一起,问第5个人多少岁?
他说比第4个人大5岁。问第4个人岁数,他说比第3个人天5岁。问第5个人,又说比第2个人大5岁。问第2个人,说比第1个人大5岁。最后问第1个人,他说是10岁
。
请问第5个人多大?
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
// 直接或是或是间接调用自己的情形,就递归调用。recursive
// 递归,是比较接近自然语言特性的一种调用方式。递归必须要用合理的出口,不然会挂掉
int getAge(int n)
{
if(n == 1)
return 10;
else
return getAge(--n)+5;
}
int main()
{
int age = getAge(5);
printf("age =%d \n",age) ;
return 0;
}
每次开辟一个和自己一样的空间,一旦返回,空间逐个消失
能用循环做的,就能用递归做,在算法比赛中,一般只写递归,不写循环
循环 for (起始条件;循环终止条件;循环条件变化 ) === 》
递归:递归起始条件:getAge(n) ;有使递归趋于结束的语句:getAge(–n) ; 递归终止条件 n == 1
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
// 直接或是或是间接调用自己的情形,就递归调用。recursive
// 递归,是比较接近自然语言特性的一种调用方式。递归必须要用合理的出口,不然会挂掉
//循环 === 》 递归:递归起始条件:getAge(n) 有使递归趋于结束的语句:getAge(--n) 递归终止条件 n == 1
int func_recursive(int n)
{
if(n == 9)
{
printf("%d \n",n) ;
return 0;
}
else
{
printf("%d \n",n) ;
return func_recursive(++n);
}
}
int main()
{
// for循环
for (int i =0;i<10;i++) {
printf("%d \n",i) ;
}
printf("**********一样一样的*****************\n");
// 迭代
int n = 0;
int ret = func_recursive(n);
return 0;
}
猴子吃桃子的问题
猴子第一天摘下若干个桃子当即吃了一半,还不过瘾,又多吃了一个。
第二天早上又将剩下的桃子吃掉一半,又多吃了一个。
以后每天早上都吃了前一天剩下的一半零一个。
到第10天早上想再吃时,见只剩下一个桃子了
。
求第一天共摘了多少?
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
// 直接或是或是间接调用自己的情形,就递归调用。recursive
// 递归,是比较接近自然语言特性的一种调用方式。递归必须要用合理的出口,不然会挂掉
//循环 === 》 递归:递归起始条件:getAge(n) 有使递归趋于结束的语句:getAge(--n) 递归终止条件 n == 1
int peachCount(int day)
{
if(day == 10)
return 1;
else {
return (peachCount(++day)+1)*2;
}
}
int main()
{
int ret = peachCount(1);
printf("%d",ret);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
// 直接或是或是间接调用自己的情形,就递归调用。recursive
// 递归,是比较接近自然语言特性的一种调用方式。递归必须要用合理的出口,不然会挂掉
//循环 === 》 递归:递归起始条件:getAge(n) 有使递归趋于结束的语句:getAge(--n) 递归终止条件 n == 1
//注意,递归条件参不参与运算。
int factorial(int n)
{
if(n == 0)
{
return 1;
}
else {
return n*factorial(n-1);
}
}
int main()
{
int ret = factorial(3);
printf("%d",ret);
return 0;
}
递归的书写结构
递归返回func(递归条件)
{
if(递归终止条件)
终止处理;
else
func(趋于递归终结的条件);
}
递归和循环,有共同的特点,有起点,有终点,重复作同样的事情。
所以很多情况,两者是可以相互转换的。
如果上升一下理论高度,作一个重复面有明确起点和终点的事,有递归和迭代两种选择。
循环其实就是一种迭代。
递归的方式,写法比较简洁,符合正常逻辑,但代码理解难度大,内存消耗大(易导致栈溢出)
所以能用迭代(lterative)解决的问题,不要用递归来完成。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
// 直接或是或是间接调用自己的情形,就递归调用。recursive
// 递归,是比较接近自然语言特性的一种调用方式。递归必须要用合理的出口,不然会挂掉
//循环 === 》 递归:递归起始条件:getAge(n) 有使递归趋于结束的语句:getAge(--n) 递归终止条件 n == 1
//注意,递归条件参不参与运算。
int fun(int i)
{
if(i>0)
{
fun(i/2);
}
printf("%d\n",i);
}
int main()
{
fun(10);
return 0;
}