第三节-函数 (上)


1. 函数是什么?

数学中我们常见到函数的概念。但是你了解C语言中的函数吗?

  • 在计算机科学中,子程序(英语:Subroutine, procedure, function, routine, method, subprogram, callable unit),是一个大型程序中的某部分代码, 由一个或多个语句块组成。它负责完成某项特定任务,而且相较于其他代 码,具备相对的独立性。

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


2. C语言中函数的分类:

  1. 库函数
  2. 自定义函数

2.1 库函数:

❓为什么会有库函数

  1. 我们知道在我们学习C语言编程的时候,总是在一个代码编写完成之后迫不及待的想知道结果,想 把这个结果打印到我们的屏幕上看看。这个时候我们会频繁的使用一个功能:将信息按照一定的格 式打印到屏幕上(printf)。

  2. 在编程的过程中我们会频繁的做一些字符串的拷贝工作(strcpy)。

  3. 在编程是我们也计算,总是会计算n的k次方这样的运算(pow)。

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

❓那怎么学习库函数呢

这里我们简单的看看:www.cplusplus.com

简单的总结,C语言常用的库函数都有:

  • IO函数
  • 字符串操作函数
  • 字符操作函数
  • 内存操作函数
  • 时间/日期函数
  • 数学函数
  • 其他库函数

我们参照文档,学习几个库函数:

[strcpy](strcpy - C++ Reference (cplusplus.com))

char * strcpy ( char * destination, const char * source );

[memset](memset - C++ Reference (cplusplus.com))

void * memset ( void * ptr, int value, size_t num );

⭐️⭐️ ⭐️ 注:

但是库函数必须知道的一个秘密就是:使用库函数,必须包含 #include对应的头文件。 这里对照文档来学习上面几个库函数,目的是掌握库函数的使用方法。

2.1.1 如何学会使用库函数?

❓需要全部记住吗?No

需要学会查询工具的使用:

MSDN(Microsoft Developer Network)

www.cplusplus.com http://en.cppreference.com(英文版)

http://zh.cppreference.com(中文版)

英文很重要。最起码得看懂文献。

要慢慢接受英文阅读文件。


2.2 自定义函数

如果库函数能干所有的事情,那还要程序员干什么?

所有更加重要的是自定义函数

自定义函数和库函数一样,有函数名,返回值类型和函数参数。

但是不一样的是这些都是我们自己来设计。

这给程序员一个很大的发挥空间。

函数的组成:

ret_type fun_name(para1, * )
{
    statement;//语句项
}
ret_type 返回类型
fun_name 函数名
para1    函数参数

我们举一个例子:

写一个函数可以找出两个整数中的最大值。

#include <stdio.h>
//get_max函数的设计
int get_max(int x, int y)
{
    return (x>y)?(x):(y);
}
int main()
{
    int num1 = 10;
    int num2 = 20;
    int max = get_max(num1, num2);
    printf("max = %d\n", max);
    return 0;
}

再举个例子:

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

#include <stdio.h>
//实现成函数,但是不能完成任务
void Swap1(int x, int y)
{
   int tmp = 0;
   tmp = x;
   x = y;
   y = tmp;
}
//正确的版本
void Swap2(int *px, int *py)
{
   int tmp = 0;
   tmp = *px;
   *px = *py;
   *py = tmp;
}
int main()
{
   int num1 = 1;
   int num2 = 2;
   Swap1(num1, num2);
   printf("Swap1::num1 = %d num2 = %d\n", num1, num2);
   Swap2(&num1, &num2);
   printf("Swap2::num1 = %d num2 = %d\n", num1, num2);
   return 0;
}

3. 函数的参数


3.1 实际参数(实参):

真实传给函数的参数,叫实参。

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

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


3.2 形式参数(形参):

形式参数是指函数名后括号中的变量,因为形式参数 只有 在函数被调用的过程中才实例化(分配内 存单 元),所以叫形式参数。

形式参数当函数调用完成之后就自动销毁了。因此形式参数只在函数中有效。

void Swap1 (int x,int y)
{
    int z = 0;
    z = x;
    x = y;
    y = z;
}

int main()
{
    int a = 0;
    int b = 0;
    scanf ("%d%d",&a, &b);
    printf("交换前:a = %d b = %d\n",a,b);
    Swap1 (a,b);
    printf("交换后:a = %d b = %d\n",a,b);
    
    return 0;
}

⭐️

  1. x 和 y 都有自己独立的空间
  2. x 和 y 的交换与原来的 a 和 b 无关

当实参传给形参时,形参是实参的一份临时拷贝

对形参的修改不会影响到实参。

void Swap2 (int* px,int* py)
{
    int z = *px;  //a
    *px = *py;    //a=b
    *py = z;      //b=a
}

交换地址所指向的值,而没有交换地址。
px ,py是指针变量,*pa 是取出存放在地址里的值。

int main()
{
    int a = 0;
    int b = 0;
    scanf ("%d%d",&a, &b);
    printf("交换前:a = %d b = %d\n",a,b);
    Swap2 (&a, &b);
    printf("交换后:a = %d b = %d\n",a,b);
    
    return 0;
}

&a, &b :取地址

上面 Swap1 和 Swap2 函数中的参数 x,y,px,py 都是形式参数。

在main函数中传给 Swap1 的 num1 , num2 和传 给 Swap2 函数的 &num1 , &num2 是实际参数。5

这里我们对函数的实参和形参进行分析:

代码对应的内存分配如下: 这里可以看到 Swap1 函数在调用的时候, x , y 拥有自己的空间,同时拥有了和实参一模一样的内容。

所以我们可以简单的认为:形参实例化之后其实相当于实参的一份临时拷贝。

⭐️⭐️⭐️

  • 若想要改变实参就要传地址
  • 函数的书写要遵循高内聚低耦合原则,函数功能要单一。
  • 传过去的值是什么类型,函数接受时就要用什么类型接收。

4. 函数的调用

4.1传值调用


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

4.2传址调用

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

4.3练习

4.3.1

写一个函数,打印100-200之间的素数

素数 :只能被一和他本身整除的数。
  
    
int main()
{
    int i = 0;
    for (i = 100;i <= 200;i++)
    {
        int flag = 0;
        int j = 0;
        for (j = 2;j < i;j++)
        {
            if (i % j ==0)
            {
                flag = 0;
                break;
            }
        }
        if (flag)
        {
            printf("%d ", i);
        }
    }
}
   
int main()
{
    int i = 0;
    for (i = 100;i <= 200;i++)
    {
        int flag = 0;
        int j = 0;
        for (j = 2;j < sqrt(i);j += 2) 
// 2*8 <= 4*4            
//sqrt 是一个数学函数,开平方,头文件为 <math.h>
//返回值为double
//素数都是奇数
        {
            if (i % j ==0)
            {
                flag = 0;
                break;
            }
        }
        if (flag)
        {
            printf("%d ", i);
        }
    }
}
int is_prime( int n )
{
     int j = 0;
     for (j = 2;j < sqrt(i);j += 2)
     {
         if (i % j ==0)
         {
            return 0;
         }
     }
    
    return 1;
}

int main()
{
    int i = 0;
    for (i = 100;i <= 200;i++)
    {
        
        if (is_prime(i))
        {
            printf("%d ", i);
        }
    }
}

4.3.2 写一个函数判断一年是不是闰年

闰年:

  1. 可以被4整除,并且不能被100整除.
  2. 可以被四百整除.

❓ 判断并输出 1000 到 2000 的闰年。


方法一:无函数

int main ()
{
    int i = 0for(i = 1000;i <= 2000;i++)
    {
         if ((i % 4 == 0 && i % 100 != 0) || (i % 400 == 0))
         {
             printf("%d ", i);
         }
    }
    return 0;
}

方法二 :有函数


int is_leap_year(int n)
{
    if ((n % 4 == 0 && n % 100 != 0) || (n % 400 == 0))
    {
        return 1;
    }
    return 0;
}
int main()
{
    int i = 0;
    for (i = 1000; i <= 2000; i++)
    {
        if (is_leap_year(i))
        {
            printf("%d ", i);
        }
    }
    return 0;
}

⭐️⭐️⭐️

  1. 定义的变量名和函数名要有意义。
  2. 函数的功能尽量单一,要求做到高内聚低耦合。

4.3.3 写一个函数,实现二分查找

//

int binary_search(int arr[], int k, int n)
{
	int left = 0;
	int right = n - 1;
    // 下标为数组长度减一

	while (right >= left)
	{
        
		int mid = left + (right - left) / 2; //求中间的值
        
		if (arr[mid] > k)
		{
			right = mid - 1;
		}
		else if (arr[mid] < k)
		{
			left = mid + 1;
		}
		else
			return mid;
	}
    
	return -1;
}


int main()
{
	int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
	int k = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);

	scanf("%d", &k);

	//找到了,返回下标
	//没找到,返回-1
	int ret = binary_search(arr,k,sz);
    
    //若没找到 返回值为-1,而不能为0,1,2...... 不能和数组下标重复,不然会混淆。
	if (ret == -1)
	{
		printf("没找到");
	}
	else
	{
		printf("找到了,下标为: %d ", ret);
	}

	return 0;
}

⭐️⭐️⭐️

❓ 为什么不把整个数组传到函数中去,计算数组长度

如果数组过大,则需要占用的空间会更大。

所以选择传数组首元素的地址,而不是整个数组

所以在函数内部计算一个函数参数部分的数组个数是不靠谱的。

形参 arr 看起来是数组,实际上是指针变量

⭐️⭐️⭐️

bool 类型讲解

  1. c99中引入布尔变量

  2. 用来表示真假的变量

flag = 1 ;真 return true;

flag = 0 ;假return flase;

  1. 大小为一个字节

  2. 头文件 <stdbool.h>


4.3.4 写一个函数,每调用一次这个函数,就会将num增加1

#include<stdio.h>
void Add(int* p)
{
	(*p)+1;
}
int main()
{
	int num = 0;
	printf("%d ", num);
	Add(&num);
	printf("%d ", num);
	return 0;
}

⭐️ 写函数最好不要用全局变量 ,全局变量不安全。

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


#include <stdio.h>

函数和函数之间可以根据实际的需求进行组合的,也就是互相调用的。

5.1 嵌套调用

⭐️ 函数可以嵌套调用但是不能前台定义。

int Add (int x, int y)
{
    return x + y;
    
    int Sub (int x, int y);//写法错误,不可以嵌套定义!
    {
        return x + y;
    }
}

5.2 链式访问

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

int main ()
{
    int len = strlen ("abcdef");
    printf("%d ", len);
    return o;
}

链式访问:

int main ()
{
    printf("%d ", strlen ("abcdef"););
    return o;
}
#include <stdio.h>
int main()
{
    printf("%d", printf("%d", printf("%d", 43)));
    //结果是啥?
    //注:printf函数的返回值是打印在屏幕上字符的个数
    return 0;
}

⭐️ printf 函数 返回值为打印的字符数

⭐️ 返回值类型和函数类型要对应!!!

int main (void)
{
    return 0;
}

⭐️此函数明确地说明,main函数不需要参数.

⭐️本质上,main函数是有参数的

int main ( int argc, char* argv[], char *envp[])
{
    return 0;
}

6.函数的声明和定义


6.1 函数的声明:

  1. 告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,函数声明决定不了。
  2. 函数的声明一般出现在函数的使用之前。要满足先声明后使用。
  3. 函数的声明一般要放在头文件中的。

6.2 函数的定义:

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

test.h的内容

放置函数的声明

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

#endif //__TEST_H__

test.c的内容

放置函数的实现

#include "test.h"
//函数Add的实现
int Add(int x, int y)
{
  return x+y;
}

未完待续…

评论 26
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值