C语言学习笔记(6)之函数

函数

  1. 问题的引入
    有时候,我们经常需要再一个程序中,对一个数组进行输入。
    如:
	int a[10];
	for(i = 0;i < 10;i++)
	{
		scanf("%d",&a[i]);
	}
	
	int b[5];
	for(i = 0;i < 5;i++)
	{
		scanf("%d",&b[i]);
	}
		...
	函数:重复利用代码块。
  1. 函数
    什么是函数?
    function 功能

    函数是完成某一个功能的指令序列的封装。

    C语言除了初始化语句,其他所有的语句必须在函数内部。

    函数可以实现代码复用,以及实现模块化的设计。

    结构化程序设计者主张把一个大的任务分成多个功能函数来实现。

    函数:相同功能的代码块,重复利用的,模块化设计思想。

  2. 如何去设计一个函数?
    函数是用来实现某种功能。

    1. 明确函数的功能是什么?要完成什么样的目标
      函数功能
      比如:
      想去求一个数组的最大值

      就可以给函数进行一个命名
      给函数命令需要注意两点:
      1) 函数名的命名要符合C语言的标识符命名规则
      2) 请在给函数命名是尽量做到“见其名而知其意”。

      find_array_max
      zhao_max
      
    2. 考虑完成该任务/功能需要哪些已知条件?
      完成该任务/功能/函数需要输入哪些参数
      比如:
      需要给我的参数:
      要给我数组名
      数组中的元素的个数

    3. 完成该任务/功能/函数的输出结果

    4. 算法的实现以及代码的调试等。

  3. C语言中函数的语法形式
    函数返回值类型 函数名(输入参数列表)
    {
    C语句;(用来具体实现代码的部分—>决定你函数具体的功能)
    return 表达式;
    }

    “函数返回值类型”:
    return语句后面的那个表达式值的类型。
    一般可以是“单值”类型,即基本类型和指针类型。
    函数也可以没有返回值。
    不指定函数返回值类型,如果不指定的话,则默认的类型为int。

    “函数名”:
    1) 函数名的命名要符合C语言的标识符命名规则
    2) 请在给函数命名是尽量做到“见其名而知其意”。

    “输入参数列表”:
    格式:数据类型1 参数名1,数据类型2 参数2…
    void 表示该函数不带参数

    “return”:
    返回的意思,return只能在函数的内部。
    表示函数结束的意思。
    return ;//表示函数结束,不带返回值
    return 表达式;//表示函数结束,同时也带一个返回值,这个返回值就是
    “表达式”的值,函数的返回值类型,就是return语句后面的表达式的类型。

    注意:
    函数不能嵌套定义,即函数不能定义在函数的内部,只能定义在外部。

    例子:
    设计一个函数,函数功能:求两个整数之和。
    1. 明确函数的功能,根据函数功能起一个“见其名而知其意”的名字。
    求两个整数之和—>sum
    2. 完成该任务/功能/函数需要输入哪些参数
    输入的参数是:两个整数
    sum(int a,int b)
    3. 完成该任务/功能/函数的输出结果
    int sum(int a,int b)//函数头
    4. 算法的实现以及代码的调试等

    		int sum(int a,int b)//函数头
    		{					//函数体
    			int s;
    			s = a + b;
    			return s;
    		}
    
  4. 函数的调用
    函数调用:调用一个已经写好的函数去执行。

    (1) 函数的调用
    a. 指定函数名
    b. 准备好函数所需要的参数
    主调函数:调用其他函数的函数,比如main()
    被调函数:被其他函数调用的函数。
    形式参数:被调函数在定义时的参数
    实际参数:在函数调用过程中,主调函数传递给被调函数的输入参数值
    在调用函数的时候,需要指定实参,并且实参需要和形参一一对应。
    一一对应:形参与实参的个数要一致,并且数据类型必须一致。
    在C语言中,函数的参数的传递只有一种情况,就是“值传递”:就是把实参的值
    传递给相应的形参。

    sum(3,4);
    sum(m,n);
    sum(3+5,4-2);
    sum(sum(3,5),4);
    
    int m = 1,n = 2;
    sum(int m,int n);//error
    函数调用时,只需要指定实参的值,不需要实参的类型。
    
    sum(4);//error 因为实参与形参的个数不一致
    

    (2) 函数调用过程
    sum(3,5);

    1.	把实参的值赋值给相应的形参
    2.	跳转到指定的函数中去执行,直到遇到return或者函数语句执行完毕后再返回
    到函数调用处。return后面表达式的值将作为整个函数调用表达式的值。
    
  5. 数组作为函数的参数
    当一个函数的参数是一个数组时,数组作为形式参数时,该如何描述呢?

    数组元素的类型 数组名[数组元素的个数];

    例子:

    (1)	int a[10]
    	
    	//参数b接收一个int类型的数组的数组名
    	//参数n接收数组中元素的个数
    	xxx(int b[],int n)
    	{
    	
    	}
    	
    	调用:
    		xxx(a,10);
    
    (2)	int a[3][4];
    	
    	//参数b接收一个int[4]类型的数组的数组名
    	//参数n接收数组中元素的个数			
    	yyy(int b[][4],int n)
    	{
    	
    	}
    
  6. 函数的声明
    “声明”:C语言中的声明是一个已经存在的标识符(对象的名字)。

    为什么需要声明?
    C语言中编译源文件时,是从第一行到最后一行,一行一行进行编译的。
    如果项目中存在多个.c文件时,是一个文件到一个文件的编译,有时候我们在一个文件中
    1.c中需要用到2.c中定义的对象(变量,函数。。。。)。在编译1.c的时候,碰到了
    这个对象的名字,编译器根本就不认识这个标识符是什么东西。

    约定: 我们一般将声明语句放在调用的前面。

    声明:
    变量的声明:
    extern 变量的类型 变量名;

    函数的声明:
    外部函数的声明:
    extern 外部函数的头部;

    	int sum(int a,int b)
    	{
    		return a + b;
    	}
    				
    	声明:int sum(int ,int )
    
  7. 变量的作用域与生存期
    (1) 作用域
    一个对象(函数、数组、变量等)起作用的范围。

    全局变量:
    	在函数外面定义的变量就叫全局变量。
    	全局变量没有初始化的话,默认初始化为0.
    	全局变量的作用域:自定义起往下到文件结束(别的文件中也可以调用,但是需要
    				用extern声明)。
    			
    	static如果修饰一个全局变量,那么这个全局变量的作用域就限制在本文件中。
    	static修饰的变量在不初始化的情况下默认初始化为0.
    	同理,如果有一个函数被static修饰,那么这个函数也只能在本文件中使用。
    
    
    局部变量:
    	在函数体内或者复合语句内定义的变量,叫局部变量。
    	局部变量的作用域:自定义起到函数或复合语句结束(即第一个右花括号结束)。
    	
    	不同作用域的两个变量,必然是两个独立的内存空间。
    	即使重名,就近往上找。
    
    形不改实:
    	当主调函数调用被调函数时,且实际参数是变量时,则被调函数里面的形参变量的
    	值的变化,不会影响到实参的值。
    	
    	原因:形参只是被调函数中的局部变量,与实参变量是两个独立的内存空间
    	只是形参变量的值被赋值与实参的值而已。两者互不影响。
    

    (2) 生存期
    是指一个对象从生到死的期间(生存期)。

    一个变量过了它的生存期,则其内存空间将会被系统释放掉。
    
    全局变量:
    	随进程的持续性。你的程序一运行,全局变量就一直存在,直到你的进程退出。
    	一个运行起来的程序就称之为进程。
    	
    
    局部变量:
    	1.	普通局部变量
    		int a;
    		普通局部变量的生存期,是从定义处到第一个右花括号结束。
    		void f()
    		{
    			int a = 7;
    			a++;
    			printf("a = %d\n",a);
    		}
    
    		int main()
    		{
    			f();//8
    			f();//8
    		}
    
    
    	2.	static(静态)局部变量
    		static int a;
    		static局部变量的生存期:随进程的持续性。
    			只初始化一次。
    			
    		void f()
    		{
    			static int a = 7;
    			a++;
    			printf("a = %d\n",a);
    		}
    
    		int main()
    		{
    			f();//8
    			f();//9
    		}					
    
    static在C语言中只有两个作用:
    	1.	static用于修饰全局变量和函数时,作用是:
    		使被修饰的全局变量和函数的作用域,变成仅在本文件中有效。
    	2.	static用于修饰局部变量,作用是:
    		使被修饰的局部变量的生存期,随进程的持续性。
    
  8. 递归函数
    递归函数:直接或者间接的调用函数本身。
    自己调用自己

    什么情况下用递归呢?
    解决一个问题的时候,解决思路化成与问题本身类似的问题时。

    C语句递归函数的设计:
    1. 问题模型本身要符合递归模型(递推关系)。
    2. 先明确函数要实现的功能与参数之间的关系,暂不管功能的具体实现。
    3. 问题的解,当递归到一定的层次时,答案是必须要显而易见的,且能够结束
    函数(不能无限递归)。
    4. 要呈现第n层与第n-1层之间的递推关系。

    例子:
    (1) 写一个函数来求第n个人的年龄。
    第1个人的年龄10岁,第二个人的年龄是12岁,以此例推。
    //普通版

    	int age(int n)
    	{
    		int a[n];
    		a[0] = 10;
    		int i;
    		for(i = 1;i < n;i++)
    		{
    			a[i] = a[i - 1] + 2;
    		}
    		return a[n-1];
    	}
    

    //递归版本

    	int age(int n)
    	{
    		age(n) = age(n - 1) + 2;
    		...
    		age(5) = age(4) + 2;
    		...
    		age(2) = age(1) + 2;
    		age(1) = 10;
    	}
    	
    	int age(int n)
    	{
    		if(n > 1)
    		{
    			return age(n-1) + 2;
    		}
    		else 
    		{
    			return 10;
    		}
    	}
    
    
    	age(4)
    		--->return age(n-1) + 2;
    					age(3)
    					---> return age(n-1) + 2;
    								 age(2)
    									--->return age(n-1) + 2;
    				
    												10
    

(2) 写一个递归函数,实现n!

			int jiechen(int n)
			{
				if(n == 0 || n == 1)
				{
					return 1;
				}
				else
				{
					return jiechen(n-1)*n;
				}
			}
	(3)	Hanio塔	
		把n个盘子从A移动到C,中间可以利用B。
		把移动的步骤以及移动的步数打印出来。
		
		2
		A->B
		A->C
		B->C
		3steps
		int step;
		//把n个盘子从A移动到C,中间可以利用B。
		void hanio(int n,char A,char B,char C)
		{
			//当盘子只有两个时,答案是显而易见的。
			if(n == 2)
			{
				printf("%c-->%c\n",A,B);
				step++;
				printf("%c-->%c\n",A,C);
				step++;
				printf("%c-->%c\n",B,C);
				step++;
				return ;
			}
			else
			{
				//找出第n层与第n-1层之间的关系
				//step1:把位置A的第n-1层以及以上的盘子放置到B,可以利用C
				hanio(n-1,A,C,B);

				//step2:把位置A上的最后一个大盘子直接移动到C
				printf("%c-->%c\n",A,C);
				step++;
				
				//step3:把位置B的第n-1层以及以上的盘子放置到C,可以利用A
				hanio(n-1,B,A,C);
			}
		}
		
		hanio(3,'A','B','C')
		{
			hanio(2,'A','C','B');
			{			
				if(n == 2)
				{
					printf("%c-->%c\n",A,B);//A-->C
					step++;
					printf("%c-->%c\n",A,C);//A-->B
					step++;
					printf("%c-->%c\n",B,C);//C-->B
					step++
					return ;
				}	
			}
			
			printf("%c-->%c\n",A,C);//A-->C
			
			hanio(2,'B','A','C');
			{
				if(n == 2)
				{
					printf("%c-->%c\n",A,B);//B-->A
					step++;
					printf("%c-->%c\n",A,C);//B-->C
					step++;
					printf("%c-->%c\n",B,C);//A-->C
					step++
					return ;
				}					
			}
		}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值