首页 > 编程学习 > 零基础入门C语言第三课

零基础入门C语言第三课

发布时间:2022/1/17 12:21:48

第三课:函数

目录

第三课:函数

1.1·函数是什么?

1.2·库函数

1.3·函数的声明和定义

1.4·函数的参数

1.5·函数的调用

1.6·函数的递归与迭代

1.7·巩固练习:素数判断、闰年判断


1.1·函数是什么?

什么是函数呢?

函数是完成特定任务的独立程序代码单元。语法规则定义了函数的结构和使用方法。

为什么要用函数呢?

首先,使用函数可以省去编写重复代码的苦差。如果程序要多次完成某项任务,那么只需要编写一个合适的函数,就可以在需要时使用这个函数,或者在不同的程序中使用该函数,就像许多程序中使用putchar()一样。其次,即使程序只完成某项任务一次,也值得使用函数。因为函数让程序更加模块化,从而提高了程序代码的可读性,更方便后期修改、完善。

当我们写过很多代码后,你会发现我们已经离不开函数了,使用函数已经成为了我们写代码必须的一部分。


1.2·库函数

在C语言中我们将函数分为库函数自定义函数

为什么会有库函数呢?

1. 我们知道在我们学习C语言编程的时候,总是在一个代码编写完成之后迫不及待的想知道结果,想把这个结果打印到我们的屏幕上看看。这个时候我们会频繁的使用一个功能:将信息按照一定的格式打印到屏幕上(printf)。
2. 在编程的过程中我们会频繁的做一些字符串的拷贝工作(strcpy)。
3. 在编程是我们也计算,总是会计算n的k次方这样的运算(pow)。

像上面我们描述的基础功能,它们不是业务性的代码。我们在开发的过程中每个程序员都可能用的到,为了支持可移植性和提高程序的效率,所以C语言的基础库中提供了一系列类似的库函数,方便程序员进行软件开发。

学习库函数的地方:①www.cplusplus.com     ②uTools工具里的C函数查询(推荐大家下个uTools非常好用)   ③MSDN(Microsoft Developer Network)  http://zh.cppreference.com(中文版)

 简而言之,C语言常用库函数有:

 IO函数

 字符串操作函数

 字符操作函数

 内存操作函数

 时间/日期函数

 数学函数

 其他库函数

备注:通常使用库函数要使用#include<库函数名字>这种格式。

           通常使用自定义函数要使用#include"自定义函数名字"这种格式。

自定义函数:程序员自己定义和写的函数,为了弥补库函数的不足,程序员可自行发挥写出其作用。

例如:

//请写出一个函数用来求两个整数的最大值
#include <stdio.h>
//get_max函数的设计
int get_max(int x, int y)
{
return (x>y)?(x):(y);//三目运算符,x>y条件成立结果为x,不成立结果为y
}
int main()
{
int num1 = 10;
int num2 = 20;
int max = get_max(num1, num2);
printf("max = %d\n", max);
}

1.3·函数的声明和定义

函数的声明:

1.告诉编译器有一个函数叫什么名字,参数是什么,返回类型是什么。但是具体是不是存在,函数声明决定不了(相当于声明不定义那种)。

2.函数的声明一般出现在函数的使用之前。要满足先声明后定义

3.函数的声明一般放在头文件中。(写项目的时候经常有的head.h和main.c两个或多个文件来写一个项目)

函数的定义:

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

例如:

//test.h的内容

#ifndef _TEST_H
#define _TEST_H
//函数的声明
int Add(int x,int y);
#endif

//test.c的内容
#include<stdio.h>
#include"test.h"
int Add(int x,int y)
{
    return x+y;
}

int main()
{
    int a=5;
    int b=8;
    Add(int a,int b);
    return 0;
}


//补充:有时候我们可以看见代码是没有int Add(int x,int y);
  这种函数声明的,因为在C99后对于一个函数的声明定义可以由定
  义直接涵盖双意思,即写定义就直接声明定义一起了。比如上个博
  客的猜数字小游戏的void game()函数。这种属于不太标准的写法,
  标准写法还是建议声明定义都写。

1.4·函数的参数

函数的参数分为两类:实际参数(实参)和形式参数(形参)

实际参数(实参):

实参即真实传递给函数的参数。

实参可以是:常量、变量、表达式、函数等

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

形式参数(形参):

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

通过以下图我们就可以直观的看出形参与实参空间分配的问题了:

这里可以看到 Swap1 函数在调用的时候, x , y 拥有自己的空间,同时拥有了和实参一模一样的内容。所以我们可以简单的认为:形参实例化之后其实相当于实参的一份临时拷贝

函数声明与定义中参数写法问题:

ANSI C要求在每个变量前都声明其类型。也就是说,函数声明和定义不能像普通变量声明那样使用用一类型的变量列表,应使用多个变量列表。

例如:

void dibs(int x,y,z);//无效的函数头

void dibs(int x,int y,int z);//有效的函数头

ANSI的参数不匹配解决办法:针对参数不匹配的问题,ANSI C标准要求在函数声明时还要声明变量的类型,即使用函数原型来声明函数的返回类型,参数的数量和每个参数的变量。

例如:未标明imax()函数有两个int类型的参数,可以使用以下两种函数原型来声明。

int imax(int ,int);

int imax(int a,int b);

第一种形式使用以逗号分隔的类型列表,第二种形式在类型后面添加了变量名。这里的变量名是假名,不必与函数定义的形式参数名一致。


1.5·函数的调用

函数的调用分为:传值调用和传址调用

传值调用:函数的形参和实参分别占有不同内存块,对形参的修改不会影响实参。

传址调用:传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方法。这种传参方式可以让函数和函数外边的变量建立起真正的联系,也就是函数内部可以直接操作函数外部的变量。

函数的嵌套调用:即函数可以调用自己(函数可以嵌套调用但不可以嵌套定义)

例如:

#include <stdio.h>
void new_line()
{
printf("hehe\n");
}
void three_line()
{
int i = 0;
for(i=0; i<3; i++)
{
new_line();
}
}
int main()
{
three_line();
return 0;
}

函数的链式访问:把一个函数的返回值作为另一个函数的参数

例如:

#include <stdio.h>
#include <string.h>
int main()
{
char arr[20] = "hello";
int ret = strlen(strcat(arr,"bit"));//这里介绍一下strlen函数
printf("%d\n", ret);
return 0;
}


#include<stdio.h>
int main()
{
    printf("%d",printf("%d",printf("%d",43)));
    return 0;
}



1.6·函数的递归与迭代

什么是递归:

程序调用自身的编程技巧称为递归( recursion)。
递归做为一种算法在程序设计语言中广泛应用。 一个过程或函数在其定义或说明中有直接或间接
调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。递归的主要思考方式在于:把大事化小

递归的使用条件:

存在限制条件,当满足这个限制条件的时候,递归便不再继续。
每次递归调用之后越来越接近这个限制条件。

例如:

/*接受一个整型值(无符号),按照顺序打印它的每一位。
  例如:输入:1234,输出 1 2 3 4*/

#include <stdio.h>
//递归开始
void print(int n)
{
if(n>9)
{
print(n/10);//C中print自动换行,printf手动换行
}
printf("%d ", n%10);
}

int main()
{
int num = 1234;
print(num);
return 0;
}
//编写函数不允许创建临时变量,求字符串的长度

#incude <stdio.h>
int Strlen(const char*str)
{
if(*str == '\0')
return 0;
else
return 1+Strlen(str+1);
}

int main()
{
char *p = "abcdef";
int len = Strlen(p);
printf("%d\n", len);
return 0;
}

什么是迭代:

对计算机特定程序中需要反复执行的子程序(一组指令),进行一次重复,即重复执行程序中的循环,直到满足某条件为止,亦称为迭代。

举例:

//求n的阶乘。(不考虑溢出)

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

//求第n个斐波那契数。(不考虑溢出)

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

/*但是我们发现有问题;在使用 fib 这个函数的时候如果我们
要计算第50个斐波那契数字的时候特别耗费时间。使用 factor
ial函数求10000的阶乘(不考虑结果的正确性),程序会崩溃。*/


/*改进方法:
在调试 factorial 函数的时候,如果你的参数比较大,那就会报错:
stack overflow(栈溢出)这样的信息。系统分配给程序的栈空间是
有限的,但是如果出现了死循环,或者(死递归),这样有可能导致一
直开辟栈空间,最终产生栈空间耗尽的情况,这样的现象我们称为栈溢出*/


//建议改成

//求n的阶乘
int factorial(int n)
{
int result = 1;
while (n > 1)
{
result *= n ;
n -= 1;
}
return result;
}

//求第n个斐波那契数
int fib(int n)
{
int result;
int pre_result;
int next_older_result;
result = pre_result = 1;
while (n > 2)
{
n -= 1;
next_older_result = pre_result;
pre_result = result;
result = pre_result + next_older_result;
}
return result;
}

//大家可以在通过了解汉诺塔和青蛙跳台阶问题
//来深入学习递归于迭代



1.7·巩固练习:素数判断、闰年判断

①素数判断练习题要求:

实现一个函数,判断一个数是不是素数。利用上面实现的函数打印100到200之间的素数

参考答案:

#include<stdio.h>
#include<math.h> 
int main()
{
	int i, j, 
		t = 0, //记录素数的个数
		leap = 1;//leap用来判断是否为素数 
	
	for(i = 101; i <= 200; i++){
		for(j = 2; j <= sqrt(i); j++) //判断i是否为素数
			if(i % j == 0){
				leap = 0;
				break;
			}
		if(leap == 1){
			printf("%-5d", i);	//左对齐,为了好看
			t++;
		}
		leap = 1;
		if(t % 10 == 0) printf("\n");   //一行就放10个,为了好看
	}
	
	return 0;
}

②闰年判断练习题要求:实现函数判断year是不是润年。

参考答案:

#include <stdio.h>

bool judgeRunNian(int year)
{
    if(((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0))//如果是闰年返回ture
        return true;
    else
        return false;
}
int main()
{
    int year = 0;
    while(1)
    {
        printf("请输入要进行判别的年份: (输入数字1退出)\n");
        scanf("%d", &year);
        if(year == 1)
            break;
        else if(judgeRunNian(year))
            printf("%d是润年\n", year);
        else
            printf("%d不是润年\n", year);
    }
    return 0;
}

Copyright © 2010-2022 ngui.cc 版权所有 |关于我们| 联系方式| 豫B2-20100000