C语言学习笔记之函数

7.1什么是函数

函数:将常用的代码以固定的格式封装成一个独立模块。
常用代码-封装成函数-使用函数

#include<stdio.h>

int func(char *s1, char *s2) 
{
    
    
	int result;
	int i;
	for ( i = 0; (result=s1[i]-s2[i])==0; i++)
	{
    
    
		if (s1[i]=='\0'||s2[i]=='\0')
		{
    
    
			break;
		}
	}
	return result;
}

int main() 
{
    
    
	char str1[] = "hahahaA";
	char str2[] = "hahahaa";
	int result1 = func(str1, str2);
		printf("s1-s2=%d\n",result1);
		return 0;
}

7.2C语言函数定义

函数是一段可以重复使用的代码,用来独立的完成某个功能,他可以接收用户传递的数据,也可以不接收。接收用户数据的函数在定义时要指明参数。进而分为有参函数和无参函数。
函数定义:将代码封装成可以重复使用的函数的过程称为函数定义,所谓函数定义就是定义了一套大家都可以理解的函数规则。

注:变量先定义再使用,函数先定义再使用。

形参:函数定义时给出的参数是形参,此时这个参数未知的,大家都可以拿去用。
实参:函数调用的时候给出的参数是实参,此时这个参数是使用者自己用的。

函数调用时,将实参的值传递给形参,相当于一次赋值操作。

注:自动类型转换和强制类型转换(注意精度)

函数的嵌套定义:函数不能嵌套定义,也就是说不能在一个函数中调用另外一个函数,
注:如何解释递归???

因为函数之间是平级的,所以函数不能嵌套定义,需要使用多个函数需要平行定义。
7.3C语言函数的形参和实参
形参与实参的辨析:
形参只有在函数调用的时候才会分配内存,调用结束后,会立即释放内存,所以形参只能在函数内部有效,不能再函数外部使用
实参可以是常量、变量、表达式、函数,无论实参是何种类型的数据,再进行函数调用时,他们都必须有确定的值,以便把这些值传递给形参,所以应该提前用赋值、输入等方法使得实参获得确定值。
函数调用中发生的数据传递是单向的,只能把实参的值传递给形参。

7.4C语言函数的返回值
函数的返回值:函数的返回值是指函数被调用之后,执行函数体中的代码所得到的结果。
对C语言返回值的说明:
没有返回值的函数为空类型:void
一旦函数的返回值类型被定义为void,就不能再接收它的值了。
return语句可以有多个,可以出现在函数体的任意位置,但是每次调用函数只能有一个return语句被执行。
函数一旦遇到return语句就立即返回,后面的语句不会被执行,因此return语句还有强制结束函数执行的作用。
判断是否是质数:

#include<stdio.h>
int prime(int m) {
    
    
	int ip = 1;
	int i;
	if (m<0)
	{
    
    
		return -1;
	}
	for (int i = 2; i < m; i++)
	{
    
    
		if (m%i == 0)
		{
    
    
			ip = 0;
			break;
		}
	}
	return ip;
}
int main() {
    
    
	int m;
	printf("input m\n");
	scanf_s("%d",&m);
	int len = prime(m);
	if (len>0)
	{
    
    
		printf("is prime\n");

	}
	else if (len==0)
	{
    
    
		printf("not prime\n");
	}
	else if(len<0)
	{
    
    
		printf("illegal\n");
	}
	return len;
}

7.5C语言函数的调用

函数调用的方法:
函数作为表达式的一部分
函数作为的哥单独的语句
函数作为另一个函数的实参

函数的嵌套调用:
函数不能嵌套定义但是可以嵌套使用
阶乘的和示例:

#include<stdio.h>

int factorial(int m) {
    
    

	int result=1;
	for (int i = 1; i <= m; i++)
	{
    
    
		result*=i;
	}
	return result;
}

int sum(int n) {
    
    
	
	int sum=0;
	for (int i = 1; i <=n; i++)
	{
    
    
		sum += factorial(i);
	}
	return sum;
}

int main() {
    
    
	printf("%d",sum(2));
	return 0}

C语言程序的执行过程:
一个C语言程序的执行过程可以认为是多个函数之间的相互调用过程,起点是main重点也是main,当main调用了完了所有的函数,他会返回一个值来结束整个程序。

C语言函数:
C语言函数是一个可以重复使用的代码块,CPU会一条条挨执行其中的代码,当遇到函数调用时,CPU首先要记录当前代码中下一条代码的地址,然后跳转出去执行另一个代码块,执行完后再回来继续执行刚刚记录位置的代码。

7.6函数声明以及函数原型

声明:广而告之,就是告诉编译器我要是用这个函数,现在没有找到它的定义不要紧,后面我会补上函数的定义。
函数原型:函数声明给出了函数名、返回值类型、参数类型等与函数有关的信息,成为函数原型。函数原型的作用就是告诉编译器与函数有关的信息,让编译器知道函数的存在以及存在形式,即使函数暂时没有定义,编译器也知道如何使用它。
注:对于单个源文件的程序,通常将函数定义放在main的后面,函数声明放在main的前面。
函数参考:
函数原型给出了使用该函数的所有细节,当我们不知道如何使用某个函数时,需要找的是函数原型而不是函数定义。

7.7全局变量和局部变量

形参变量:形参变量需要等到函数被调用时才分配内存,调用结束后立即释放内存,因此形参变量的作用域有限,只能在函数内部使用,离开函数就无效了。
作用域: 所谓作用域就是变量的有效范围,C语言所有的变量都有作用域,决定变量作用域的是变量定义的位置。
全局变量:全局变量的作用域是变量定义之后的整个程序???

7.8C语言变量的作用域

作用域:所谓作用域,就是变量的有效范围,有些变量可以在所有的代码文件中使用,有些变量只能在当前的文件中使用,有些变量只能在函数内部使用,有些变量只能在for循环内部使用,变量的作用域要结合局部变量和全局变量进行理解。
全局变量:全局变量的默认作用域是整个程序,也就是所有的代码文件,如果给全局变量加上static关键字,那么该全局变量的作用域就是当前文件,在其他文件中就无效了。
在一个函数内部修改全局变量的值会影响其他函数,全局变量的值在函数内部被修改后并不会自动恢复,他会一直保留该值,知道再次被修改。
注:变量的使用遵循就近原则

7.9C语言语句块块级变量(在代码块内部定义的变量)

代码块:有{ }包起来的代码。
C语言允许在代码块内部定义变量,这样的变量具有块级作用域,也就是说在代码块内部定义的变量只能在代码块内部使用,出了代码块就无效了。
求最大公约数:

#include<stdio.h>

int gcd(int a, int b);

int main() {
    
    
	int a, b;
	printf("input a and b\n");
	scanf_s("%d %d",&a,&b);

	int result = gcd(a,b);
	printf("gcd=%d\n",result);
	return 0;
}

int gcd(int a, int b) {
    
    

	if (a < b)
	{
    
    
		int temp1;
		temp1 = a;
		a = b;
		b = temp1;
	}

	while (b != 0)
	{
    
    
		int temp2;
		temp2 = b;
		b = a % b;
		a = temp2;
	}
	return a;
}

判断字符串中是否存在目标字符:

#include<stdio.h>
#include<string.h>

int strchar(char *str, char c);

int main() {
    
    
	char *str = "hahahanihao";
	char c = 'o';

	if (strchar(str, c)<0)
	{
    
    
		printf("no\n");
	}
	else
	{
    
    
		printf("yes\n");
	}
	return 0;
	

}

int strchar(char *str, char c)
{
    
    


	for (int i = 0; i < strlen(str); i++)
	{
    
    
		if (str[i] == c)
		{
    
    
			return 0;
		}
	}
	return -1;


}

7.10C语言递归函数
递归:一个函数在他的函数体内调用他自身称为递归调用,这种函数称为递归函数。
递归函数实现阶乘:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int factorial(n);
int main() {
    
    
	int n;
	printf("input n\n");
	scanf_s("%d",&n);
	int result = factorial(n);
	printf("%d",result);
	return result;
}

int factorial(int n) {
    
    
	if (n==0||n==1)
	{
    
    
		return 1;
	}
	else
	{
    
    
		return n * factorial(n - 1);
	}
}

递归的条件:
每一个递归函数都应该进行有限次的递归调用。
实现递归函数的逐层进入和逐层退出:
设定限制条件
每次递归调用之后更接近限制条件

递归的分类:
尾部递归:递归调用位于函数体的结尾
中间递归:递归调用位于函数体的中间
多层递归:在一个函数多次调用自己

注:递归会导致时间开销增大和内存开销增加
7.11中间递归
利用中间定义实现字符串反转:

#include<stdio.h>
#include<string.h>

char *reverse(char *str) {
    
    
	int len = strlen(str);
	if (len > 1)
	{
    
    
		char ctemp;
		ctemp = str[0];
		str[0] = str[len - 1];
		str[len - 1] = '\0';
		reverse(str + 1);
		str[len - 1] = ctemp;
	}
	return str;
}

int main() {
    
    
	char str[6] = "12345";
	printf("%s\n",reverse(str));
	return 0;
}

7.12多层递归
所谓多层递归就是在一个函数多次调用自己
利用多层递归求斐波那契数列:

#include<stdio.h>

int fib(int n);

int main() {
    
    
	int n;
	printf("input n\n");
	scanf_s("%d",&n);
	int result = fib(n);
	printf("%d",result);
	return 0;
}

int fib(int n) {
    
    
	if (n<=2)
	{
    
    
		return 1;
	}
	else
	{
    
    
		return fib(n-1) + fib(n - 2);
	}
}

7.13递归函数的缺点

递归的空间开销:
在程序占用的整个内存中,有一块内存区域叫做stack,这个区域专门用来给函数分配内存,每次调用函数,都会将相关数据压入栈中,包括局部变量、局部数组、形参、寄存器、冗余数据等;
栈是针对线程来说的,每个线程都有一个栈,如果一个程序包含了多个线程,那么他就有多个栈。
对于每个线程来说,栈能使用的内存是有限的,这在编译期间就已经决定了,程序运行期间这块内存的大小不会发生改变。因此,如果程序使用的栈内存超过最大值,就会发生栈移除stack overflow错误。
栈内存的大小和编译器有关,编译器会为栈内存指定一个最大值。发生函数调用的时候会将相关数据压入栈中,函数调用结束就会释放这一部分内存,对于一般函数来说,栈的内存时够用的,但是对于递归函数,这回导致严重问题。因为递归函数内部嵌套了对自身的调用,除非等到最内层函数调用结束,否则外层的所有函数都不会调用结束,也就是说,递归的时候相当于外层函数卡住了,需要等待所有得内层函数调用完成后,他自己才能调用完成。
(每一层的递归调用都会在栈上分配一块内存,有多少层递归调用就会分配多少块相似的内存,如果递归次数过多会超过栈内存的最大值,进而导致程序崩溃)
栈溢出:

#include<stdio.h>

int test(int n);

int main() {
    
    
	int n;
	printf("input n\n");
	scanf_s("%d",&n);
	int result = test(n);
	printf("%d",result);
	return 0;
}

int test(int n) {
    
    
	int arr[250];
	if (n<=1)
	{
    
    
		return 1;
	}
	else
	{
    
    
		return n + test(n - 1);
	}
}

递归的时间开销:
递归的每次调用都会在栈上分配内存,函数调用结束后再释放这一部分内存,内存的分配和释放都需要时间,每次调用函数函数修改寄存器的值,函数调用结束后还需要找到上层函数的位置再往下执行,这些也需要时间。
利用迭代实现斐波那契数列:

#include<stdio.h>
#include<time.h>

int fib2(int n)
{
    
    
	int r = 1;
	int pr = 1;
	int ppr;
	while (n>2)
	{
    
    
		n = n - 1;
		
		ppr = pr;
		pr = r;
		r = pr + ppr;
	
	}
	return r;

}

int main() {
    
    
	int n;
	clock_t s, e;
	printf("input n\n");
	scanf_s("%d",&n);
	s = clock();

	printf("%d\n",fib2(n));
	e = clock();

	printf("%lf s",(double)(e-s)/CLOCKS_PER_SEC);

}

7.14整体理解C语言函数

从整体上看,C语言代码有一个个的函数构成,定义和说明类的语句可以放在函数外边,其他具有运算和逻辑处理能力的语句都应该放在能够执行的函数内部。
在所有函数中,main函数是入口函数,有且只能有一个,C语言程序从这里开始运行。
标准C语言ANSI C共定义了15个常用的头文件,称为C标准库。
注:C语言的main函数是主函数,他可以调用其他函数,而其他函数不能调用main函数,因此main函数是C语言的入口函数,C语言程序的执行总是从main函数开始,完成对其他函数的调用后再返回到main函数,最后由main函数的return语句结束整个程序。

猜你喜欢

转载自blog.csdn.net/BJTUYBYUAN/article/details/113860450