(入门C语言)二、函数

goto语句

C语言中提供了可以随意滥用的 goto语句标记跳转的标号

从理论上 goto语句是没有必要的,实践中没有goto语句也可以很容易的写出代码。 但是某些场合下goto语句还是用得着的,最常见的用法就是终止程序在某些深度嵌套的结构的处理过程

例如:

一次跳出两层或多层循环。 多层循环这种情况使用break是达不到目的的。它只能从最内层循环退出到上一层的循环。

goto语句真正适合的场景如下:

for(...)
    for(...)
      {
        for(...)
         {
               if(disaster)
                  goto error;
         }
      }
  ...

error:
     if(disaster)
       //处理错误情况

例:

关机程序:程序运行起来后,倒计时60秒后关机,如果输入:我是猪,就取消关机。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
	char arr[20] = { 0 };
	system("shutdown -s -t 60");
again:
	printf("请注意,你的电脑在一分钟内关机,如果输入:我是猪,就取消关机\n");
	scanf("%s", arr);
	if (strcmp(arr, "我是猪") == 0)
	{
		system("shutdown -a");
	}
	else
	{
		goto again;
	}
	return 0;
}
//注意要点
//1.关机使用命令:shutdown -s -t 60
//2.取消关机 shutdown -a

1.函数是什么?

函数是子程序,是一个大型程序中的某部分代码,由一个或多个语句块组成。它负责完成某项特定任务,而且相较于其他代码,具备相对的独立性。

一般会有输入参数并有返回值,提供对过程的封装和细节的隐藏。这些代码通常被集成为软件库。

2.C语言中函数的分类

1.库函数

2.自定义函数

2.1 库函数:

为什么会有库函数?是因为在开发的过程中,每个程序员可能用的到,为了支持可移植性和提高程序的效率, 所以C语言的基础库中提供了一系列的库函数,方便程序员进行软件开发。

举例strcpy

char arr1[]="abcdef";
char arr2[20]={0};
strcpy(arr2,arr1);
printf("%s\n",arr2);

结果会输出abcdef

库函数的使用要引用头文件,并且要注意用法和格式。同时要学会对照文档学习库函数。

2.2自定义函数

自定义函数和库函数一样,有函数名,返回值类型和函数参数。与库函数不同的是需要我们自己设计。

函数的组成:

返回类型  函数名(函数参数)
{
    //语句项
}

例子:找出两个数字的较大值

#include <stdio.h>
int get_max(int x,int y)
{
	if (x > y)
		return x;
	else
		return y;
}
int main()
{
	int a = 0;
	int b = 0;
	scanf("%d %d", &a, &b);
	int m = get_max(a,b);
	printf("%d\n", m);
	return 0;
}

关于函数的命名,可以把每个单词首字母大写或者用下划线隔开。比如Max()/get_max()/GetMax()

例子2:写一个函数可以交换两个整形变量的内容。

//代码1
#include <stdio.h>
void Swap(int x,int y)
{
	int tmp = x;
	x = y;
	y = tmp;
}
int main()
{
	int a = 0;
	int b = 0;
	scanf("%d %d", &a, &b);
	printf("交换前:a=%d b=%d\n", a, b);
	Swap(a,b);
	printf("交换后:a=%d b=%d\n", a, b);
	return 0;
}

 原因:a,b传值到函数内部(分别传给x和y),而函数内部交换了x,y的值,但是交换的过程中,只改变了x和y的值,并没有改变a,b的值,所以a和b的值没有发生改变。

所以,实参传递给形参的时候,形参是实参的一份临时拷贝,对形参的修改是不会影响实参的。

//代码2
#include <stdio.h>
void Swap(int* pa,int* pb)
{
	int tmp = *pa;
	*pa = *pb;
	*pb = tmp;
}
int main()
{
	int a = 0;
	int b = 0;
	scanf("%d %d", &a, &b);
	printf("交换前:a=%d b=%d\n", a, b);
	Swap(&a,&b);
	printf("交换后:a=%d b=%d\n", a, b);
	return 0;
}

 传入的地址在函数中发生了交换,因此两个数也得到了交换。

 3、函数的参数

3.1实际参数(实参)

真实传给函数的参数,叫实参。实参可以是:常量、变量、表达式、函数等。

无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参。

3.2形式参数(形参)

形式参数是指函数后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(分配内存单元),所以叫形式参数。形式参数当函数调用完成后就自动销毁了。因此形式参数只在函数内有效。

 4、函数的调用

1、传值调用

2、传址调用

3、练习

1.

2.闰年

#include<stdio.h>
int is_leap_year(int y)
{
	if ((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0))
		return 1;//或者只写return((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0)
	else
		return 0;
}

int main()
{
	int y = 0;
	int count = 0;
	for (y = 1000; y <= 2000; y++)
	{
		if (is_leap_year(y) == 1)
		{
			printf("%d ", y);
			count++;
		}
	}
	printf("\n%d\n", count);
}

提示:如果一个函数返回类型忘写了,函数默认会返回一个整数。

5、函数的嵌套调用和链式访问

函数和函数之间可以相互组合,相互调用。但是不可以嵌套定义,即在一个函数中再定义另一个函数。

1、嵌套调用

2、链式访问

把一个函数的返回值作为另一个函数的参数。

printf("%d", printf("%d", printf("%d", 43)));//printf返回值是打印字符的个数

 6、函数的声明和定义

6.1函数声明

1.告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,函数声明决定不了。

2.函数的声明一般出现在函数的使用之前,要满足先声明后使用。变量和函数都得满足先定义(声明)后使用。

3.函数的声明一般要放在头文件中的。

注释:

在源文件中添加函数的文件(选择的是c++文件),在头文件中选择添加头文件(选择的是头文件)如下图

之后就会创建成如

那么,头文件.h声明,add.c文件定义,test.c里面使用。

这样书写的原因是因为把大的工程进行分割,每个人负责不同的模块。提高实现程序的效率。

补充:这里提供一个不显示函数代码提供其他人使用的方法(隐藏定义函数的代码)

 1、在选择解决方案的模块——找到属性页面——配置类型修改成静态库。

2、之后会在创建的该项目文件下的debug文件中生成一个比如叫add.lib等等的.lib后缀的文件,如果用记事本打开发现会有很多乱七八糟的东西,其实是二进制导致的。

3、之后可以将.lib的后缀文件,添加给需要的新项目的文件夹中,但是还是需要把函数声明的文件也一并加入到需要添加的新项目中。(直接拖入即可)

4、需要将头文件引入工程中,打开项目文件,选择头文件——现有项——找到我们需要添加的文件

5、对于静态库文件的使用是需要声明:#pragma comment(lib,“add.lib”)

 

6.2函数的定义:

是指函数具体实现,交代函数的功能实现。

7、函数递归

程序调用自身的编程技巧称为递归。

它通常把一大型复杂的问题层层转化为一个与原问题相似的规模较小的习题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。
递归的主要思考方式在于,把大事化小。

例题1、接受一个整形值(无符号),按顺序打印它的每一位。

输入1234,使得打印从头到尾依次输出成1 2 3 4。

#include<stdio.h>
//输入一个四位数1234,并从头到尾各自输出成1 2 3 4,思路:
//Print(1234)
//Print(123) 4
//Print(12) 3 4
//Print(1) 2 3 4
	void Print(unsigned int n)
{
	if (n > 9)//if很关键
	{
		Print(n / 10);
	}
	printf("%d ", n % 10);
}

int main()
{
	unsigned int num = 0;
	scanf("%u", &num);//%u - 无符号的整数
					  //%d - 有符号的整数
	Print(num);
	return 0;
}

这里运用的递归方法,一般我们从后往前分开数字比较容易,但是要求的是从头到尾输出,所以我们运用递归的方法,先对数字进行拆分,到最后完成拆分后再对数字进行打印。

同时要注意

递归的两个必要条件:

1、存在限制条件,当满足这个限制条件的时候,递归便不在继续。

2、每次递归调用之后越来越接近这个限制条件。

例题2、编写函数,求字符串长度。

int my_strlen(char* str)
{
	int count = 0;
	while (*str != '\0')
	{
		count++;
		str++;
	}
	return count;
}

int main()
{
	char arr[10] = "abcdef";
	int len = my_strlen(arr);
	printf("%d\n", len);
	return 0;
}

 例题3、不允许创建临时变量,编写函数求字符段长度。

想法是:把字符段拆成1+“bcdef”——1+1+“cdef”...之后就有了递归的解法

int my_strlen(char* str)
{
	if (*str != '\0')
		return 1 + my_strlen(str + 1);
	else
		return 0;
}

int main()
{
	char arr[10] = "abc";

	int len = my_strlen(arr);
	printf("%d\n", len);
	return 0;
}

注:str+1这样写不会改变str的地址,如果写成str++的话,就没办法使用原来的地址了。

7.3递归与迭代

例题1:求n的阶乘

解法一(迭代/循环):

int fac(int n)
{
	int i = 0;
	int ret = 1;
	for (i = 1; i <= n; i++)
	{
		ret = ret * i;
	}
	return ret;
}
int main()
{
	int n = 0;
	scanf("%d", &n);
	int ret = fac(n);
	printf("%d\n", ret);
	return 0;
}

解法二(递归):

int fac(int n)
{
	if (n <= 1)
		return 1;
	else
		return n * fac(n - 1);
}
int main()
{
	int n = 0;
	scanf("%d", &n);
	int ret = fac(n);
	printf("%d\n", ret);
	return 0;
}

例题2、编写一个函数,求第n个斐波那契数。(不考虑溢出)

解法一(递归):

int count = 0;

int Fib(int n)
{
	//统计的是第3个斐波那契数被重复计算的次数,目的是体现他的效率低
	if (n == 3)
		count++;

	if (n <= 2)
		return 1;
	else
		return Fib(n - 1) + Fib(n - 2);
}
int main()
{
	int n = 0;
	scanf("%d", &n);//40
	int ret = Fib(n);
	printf("%d\n", ret);
	printf("%d\n", count);
	//printf("count = %d\n", count);
	return 0;
}

 

递归也存在以下的问题:

1.在使用fib这个函数的时候如果我们要计算第50个斐波那契数字的时候特别耗费时间。

2.使用factorial函数求10000的阶乘(不考虑结果的正确性),程序会崩溃。

解法二(迭代):

int Fib(int n)
{
	int a = 1;
	int b = 1;
	int c = 1;
	while (n>=3)
	{
		c = a + b;
		a = b;
		b = c;
		n--;
	}
	return c;
}
int main()
{
	int n = 0;
	scanf("%d", &n);//40
	int ret = Fib(n);
	printf("%d\n", ret);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值