3.函数emm

1. 函数是什么

y = f(x);
给定一个x,有一个与之对应的y

在计算机科学中,子程序(英语:Subroutine, procedure, function, routine, method,subprogram, callable unit),是一个大型程序中的某部分代码, 由一个或多个语句块组成。它负责完成某项特定任务,而且相较于其他代 码,具备相对的独立性。
一般会有输入参数并有返回值,提供对过程的封装和细节的隐藏。这些代码通常被集成为软件库。

2. 库函数

c语言本身提供给我我们的函数,
strcmp,printf,scanf…

为什么存在库函数?

频繁使用的操作,打印,从键盘获取信息…
每个人写的函数都不一样,严重降低开发效率,大家都在造轮子
为了提高代码可移植性,提高程序效率
早期c语言是没有库函数的
http://www.cplusplus.com/reference/
https://zh.cppreference.com/w/cpp
zeal文档
MSDN文档

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

如何去学一个库函数呢

strlen

size_t strlen ( const char * str );
size_t 无符号整型

#include<stdio.h>
#include<string.h>
#include <cstddef>    //重定义了size_t
int main()
{
       char arr[] = "abc";
       size_t len = strlen(arr);
       printf("%u\n",len);//3 不包括/0
       return 0;
}

strcpy

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

#include<stdio.h>
#include<string.h>
int main()
{
       char arr1[20] = { 0 };//目的地
       char arr2[] = "HELLO";//源数据
       strcpy(arr1, arr2);
       printf("%s\n", arr1);//内存中是"HELLO\0"
	//打印出来是HELLO
       printf("%s\n", arr2);
       return 0;
}

Null表示\0
Null-terminated source string以\0结束的源字符串

memset

Sets buffers to a specified character.
void *memset( void *dest, int c, size_t count );

#include<stdio.h>
#include<memory.h>
int main()
{
       char arr[] = "hello world";
       memset(arr, 'x',5);//单个字符本质也是int类型
       printf("%s\n", arr);//xxxxx world
       return 0;
}
#include <memory.h>
#include <stdio.h>

void main( void )
{
   char buffer[] = "This is a test of the memset function";

   printf( "Before: %s\n", buffer );
   memset( buffer, '*', 4 );
   printf( "After:  %s\n", buffer );
}

c注意,库函数使用必须包含它所在的头文件

3. 自定义函数

自己写的
函数名,返回值类型,函数参数\

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

原材料 —> 工厂 —> 产品
a,b —> get_max —> 较大值

int get_max(int x, int y)   //函数定义
{
       return x > y ? x : y;
}
#include<stdio.h>
int main()
{
       int a = 0;
       int b = 0;
       scanf("%d %d", &a, &b);
       int m = get_max(a, b); //函数调用
       printf("max is %d\n", m);
       return 0;
}

get_max 返回一个整型,接受的m变量也得是整型
如果输入的是 100 100 程序是怎么判断?
x>y 不满足了,那就返回y

无返回值函数

#include<stdio.h>
void menu()//无参数,无返回值
{
       printf("*********   1.play  ***********\n");
       printf("*********   0.exit  ***********\n");
}
int main()
{
       menu();
       return 0;
}

交换2变量

#include<stdio.h>
void Swap(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);
       Swap(a,b);
       printf("a = %d b = %d\n", a, b);
       return 0;
}

10 20
a = 10 b = 20
a = 10 b = 20
为什么没实现交换呢?
没有语法问题,但有逻辑上的问题

如何解决?

利用F10 F11调试
a b 地址与 x y地址不一样
x y的值确实交换了,但对a b没影响
image-20220109200440442

如何给别人讲述呢?

当函数调用的时候,实参传给形参,形参其实是实参的一份临时拷贝,所以对形参的修改,不会影响实参

如何让x y 与a b建立联系?
利用指针建立联系! 把a b地址传过去

#include<stdio.h>
void Swap2(int* pa, int* pb)
{
       int z = 0;
       z = *pa;
       *pa = *pb;
       *pb = z;
}
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;
}

结果:
10 20
a = 10 b = 20
a = 20 b = 10

pa变量存放的就是a的地址,pb变量存放的就是b的地址
借助z变量,交换的a b

4. 函数参数

实参

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

形参

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

5. 函数调用

传值调用

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

Swap1(a, b);

传址调用

Swap2(&a, &b);

这种传参方式可以让函数和函数外边的变量建立起真正的联系,也就是函数内部可以直接操作函数外部的变量。

练习

1.写一个函数判断素数

#include<stdio.h>
#include<math.h>
int is_prime(int n)
    //是素数返回1,不是素数返回0
{
       //2 ~ n-1数字试除
       for (int j = 2; j < sqrt(n); j++)
       {

               if (n % j == 0)
               {
                      return 0;
               }
       }
       return 1;
}
int main()
{
       int i = 0;
       int count = 0;
       for (i = 101; i <= 200; i+=2)//偶数不可能是素数
       {
               //判断i是否为素数
               if (is_prime(i))
               {
                      count++;
                      printf("%d ", i);
               }
       }
       printf("\n%d\n", count);
       return 0;
}

2.写函数判断闰年

#include<stdio.h>
int is_leap_year(int n)
{
       if ((n % 4 == 0) && (n % 100 != 0) || (n % 400 == 0))
       {
               return 1;
       }
       else
       {
               return 0;
       }
}
int main()
{
       int y = 0;
       int count = 0;
       for (y = 1000; y <= 2000; y++)
       {
               if (is_leap_year(y))
               {
                      count++;
                      printf("%d ", y);
               }
       }
       printf("\n总共%d个\n", count);//243个闰年
       return 0;
}

函数功能要纯粹,不要在is_leap_year加循环或打印
函数只需要返回1/0,
要让别人也能随心所欲调用你写的函数

//is_leap_year()可以写的更简便
int is_leap_year(int n)
{
       return ((n % 4 == 0) && (n % 100 != 0) || (n % 400 == 0));
       //表达式成立返回1,不成立返回0
}

3.写函数,整形有序数组二分查找

#include<stdio.h>
//找到了就返回下标,找不到返回-1(不返回0,0也有可能是下标)
int binary_search(int arr[], int k)
{
       int sz = sizeof(arr) / sizeof(arr[0]);//一直为1
       int left = 0;
       int right = sz - 1;
       while (left <= right)
       {
               int mid = (left + right) / 2;
               if (arr[mid] < k)
               {
                      left = mid + 1;
               }
               else if (arr[mid] > k)
               {
                      right = mid - 1;
               }
               else
               {
                      return mid;
               }
       }
       if (left > right)
       {
               return -1;
       }
}
int main()
{
       int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
       int k = 7;
       int ret = binary_search(arr,k);
       if (-1 == ret)//老司机写法
       {
               printf("找不到\n");
       }
       else
       {
               printf("找到了,下标是%d\n", ret);
       }
       return 0;
}

为啥不对呢??
F10调试起来
sz = 1
int sz = sizeof(arr) / sizeof(arr[0]);
数组arr进行传参,传过去的是arr数组首元素地址,
地址是放在指针里面的,
int binary_search(int* arr[], int k)
sizeof(arr)结果就是4
image-20220109203614439

解决:
sz在外面算好了再传进去

#include<stdio.h>
//找到了就返回下标,找不到返回-1(不返回0,0也有可能是下标)
int binary_search(int* arr[], int k,int sz)
{
       int left = 0;
       int right = sz - 1;
       while (left <= right)
       {
               int mid = (left + right) / 2;
               if (arr[mid] < k)
               {
                      left = mid + 1;
               }
               else if (arr[mid] > k)
               {
                      right = mid - 1;
               }
               else
               {
                      return mid;
               }
       }
       if (left > right)
       {
               return -1;
       }
}
int main()
{
       int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
       int k = 7;
       int sz = sizeof(arr) / sizeof(arr[0]);
       int ret = binary_search(arr,k,sz);
       if (-1 == ret)
       {
               printf("找不到\n");
       }
       else
       {
               printf("找到了,下标是%d\n", ret);
       }
       return 0;
}
void test1()
{
    ...
    if(...)
    {
		return;
    }
    ...
}
//void可以写return,当满足某种条件就退出test,不继续执行

4.任意乘法口诀表

#include<stdio.h>
void multiplication_table(int n)
{
       for (int i = 1; i <= n; i++)
       {
               for (int j = 1; j <= i; j++)
               {
                      printf("%2d*%2d=%2d ", i, j, i * j);
               }
               printf("\n");
       }
}
int main()
{
       int n = 0;
       scanf("%d",&n);
       multiplication_table(n);
       return 0;
}

5

#include<stdio.h>
void add(int* p)
{
       *p = *p + 1;
}
//通过指针建立联系
int main()
{
       int num = 0;
       add(&num);
       printf("%d\n", num);
       add(&num);
       printf("%d\n", num);
       add(&num);
       printf("%d\n", num);
       add(&num);
       printf("%d\n", num);
}
#include<stdio.h>
int add(int n)
{
       n++;
       return n;
}
//通过指针建立联系
int main()
{
       int num = 0;
       num = add(num);
       printf("%d\n", num);
       num = add(num);
       printf("%d\n", num);
       num = add(num);
       printf("%d\n", num);
       num = add(num);
       printf("%d\n", num);
}

不用指针也可以,就是比较麻烦
int add(int n)

{

   //n++;

   return ++n; //前置++,先加再使用,如果是n++ 那就是先使用了再++,也就是先返回了n,n再++ 但此时n的生命周期已经结束
   所以return n++ 最终输出都是0

}

也可以
#include<stdio.h>

int add(int n)

{

   //n++;

   return n+1;

}
return ++n; //前置++,先加再使用,如果是n++ 那就是先使用了再++,也就是先返回了n,n再++ 但此时n的生命周期已经结束,所以return n++ 最终输出都是0

//函数返回类型

//void - 无返回

//char int float 具体某种类型



#include<stdio.h>

void test1()

{

       int n = 5;

       printf("hehe\n");

       if (n == 5)

       {

               return; //可以只写return 不返回i任何值

       }

       printf("haha\n");

}

int test2()

{

       return 1;

}

int main()

{

       test1();

       return 0;

}

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

嵌套调用

函数可以嵌套调用,但不能嵌套定义

链式访问

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

#include<string.h>
int main()
{
       printf("%d\n", strlen("abc"));//3
        //strlen返回整形
       return 0;
}
#include<stdio.h>
int main()
{
       printf("%d\n", printf("%d",printf("%d",43)));//4321
       return 0;
    //打印的是函数的返回值
}
//Return Value
//Each of these functions returns the number of characters printed, or a negative value if an error occurs.

//printf("%d\n", printf("%d",printf("%d ",43)));
结果变为43 31
43空格-三个字符长度

7. 函数的声明和定义

#include<stdio.h>
int Add(int x, int y)
{
       return x + y;
}
int main()
{
       int a = 10;
       int b = 20;
       int ret = Add(a, b);
       printf("%d", ret);
       return 0;
}

函数的定义如果放在主函数后面,则需要在前面声明一下

#include<stdio.h>
int Add(int x, int y);//函数声明
int main()
{
       int a = 10;
       int b = 20;
       int ret = Add(a, b);
       printf("%d", ret);
       return 0;
}
int Add(int x, int y)
{
       return x + y;
}

函数声明:

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

按模块写代码
add.h
add.c
test.c

image-20220110180111012

函数定义:

函数的具体实现

静态库:

在公司写代码,不会把所有代码放在test.c
需要分模块去写

牛B的程序员,自己写了一个软件库,卖钱,卖多份,不能公开源代码
把代码分成几部分,只给看函数声明,卖头文件
函数的声明必须暴露给别人
把add.c add.h —>编译产生add.lib 静态库
add.lib打开是乱码,二进制文件
卖add.liib 和add.h
要想运行程序,还需导入静态库
image-20220110180555501

image-20220110180631010

导入静态库:
image-20220110180820173

#include<stdio.h>
#include"add.h"//相当于声明,把add.h文件拷贝一份放到当前源文件
//导入静态库
#pragma comment(lib,"test_21_11_8_add.lib")
int main()
{
       int a = 10;
       int b = 20;
       int ret = Add(a, b);
       printf("%d", ret);
       return 0;
}

8. 函数递归

什么是递归

程序调用自身的编程技巧称为递归
函数自己调用自己(直接或间接)

栈溢出

#include<stdio.h>
int main()
{
       printf("hehe");
       main();//main函数自己调用自己
       return 0;
}

死递归,直到程序崩溃为止
递归作为一种算法,把一个大型复杂问题转化为一个小问题
image-20220110181520687

报错:栈溢出

练习1

接受一个整数(无符号),按顺序打印每一位

//Print 1234
//最后一位最容易得到,%10即可
//Print (123) + 4  打123每一位和4
//Print (12) + 3 + 4  打12每一位再打3打4
//Print (1) + 2 + 3 4
#include<stdio.h>
void Print(unsigned int n)
{
       if (n > 9)
       {
               Print(n / 10); //打印123 每一位 1 2 3
       }
       printf("%d ", n % 10); //打印4
}
int main()
{
       unsigned int num = 0;
       scanf("%u", &num);//1234
    //%u表示的就是无符号整数
       //%10/10 拿到的是倒序
       Print(num);//1 2 3 4
       return 0;
}

函数调用完之后要回归,从哪来回到哪去
递归的过程:
image-20220110182452819

如果没有if语句 n > 9的判断
那么就会无限调用自己,从而栈溢出
每一次函数调用都会在内存的栈区申请一块空间
image-20220110182931045

  • StackOverflow
  • GitHub
  • 写博客

提高个人影响力,让猎头主动找

递归的2个必要条件

  1. 存在限制条件,当满足限制条件,递归不再继续(if(n>9))

  2. 每次递归调用之后越来越接近限制条件(n/10)

必要条件是必须写,但不代表写了就一定对

练习2

编写函数不允许创建临时变量,求字符串的长度。

//创建临时变量模拟实现strlen
#include<stdio.h>
int My_strlen(char* s)
    //数组名是首元素地址也就是a的地址,而a是char类型变量,因此也就是char*
{
       int count = 0;//count 是临时变量 不满足要求
       while (*s != '\0')//\0数值上=数字0
       {
               count++;
               s++;//char*指针加一 跳过一个字符
       }
       return count;
}
int main()
{
       //求字符串长度 strlen
       char arr[] = "abc";
       //a b c \0 数组存放的内容 首元素地址
       printf("%d\n", My_strlen(arr));
       return 0;
}
//不创建临时变量模拟实现strlen
//my_strlen("abc")
//1 + my_strlen("bc")
//1 + 1 + my_strlen("c")
//1 + 1 + 1 + my_strlen("")
//1 + 1 + 1 + 0 = 3
//看第一个字符
int My_strlen2(char* s)
{
       if (*s == '\0')
               return 0;
       else
               return 1 + My_strlen2(s+1);
}
int main()
{
       //求字符串长度 strlen
       char arr[] = "abc";
       printf("%d\n", My_strlen2(arr));
       return 0;
}

s+1 写成++s 也可以
image-20220110184417463

递归与迭代

求n的阶乘

#include<stdio.h>
int main()
{
       int n = 0;
       scanf("%d", &n);
       int i = 0;
       int ret = 1;
       for (i = 1; i <= n; i++)
       {
               ret *= i;
       }
       printf("%d\n", ret);
       return 0;
}
//递归实现
#include<stdio.h>
int fac(int n)
{
       if (n <= 1)
       {
               return 1;
       }
       else
       {
               return n * fac(n - 1);
       }
}
int main()
{
       int n = 0;
       scanf("%d", &n);
       int i = 0;
       int ret = fac(n);
       printf("%d\n", ret);
       return 0;
}

求斐波那契数列

1 1 2 3 5 8 13 21 34 55…
fib(n) = fib(n-1) + fib(n-2) n>2
fib(n) = 1 n<=2

#include<stdio.h>
int fib(int n)
{
       if (n <= 2)
               return 1;
       else
               return fib(n - 1) + fib(n - 2);
}
int main()
{
       int n = 0;
       scanf("%d", &n);
       int ret = fib(n);
       printf("%d\n", ret);
       return 0;
}

递归写代码很简单,照着公式就能直接写了
但求的n很大时,耗时很长

这个算法效率太低了
55
54 53
53 52 52 51
52 51 51 50 51 50 50 49

2^49,而且大量的重复计算
占用大量的CPU资源

image-20220110193310445

#include<stdio.h>
int count = 0;
int fib(int n)
{
       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);
       int ret = fib(n);
       printf("%d\n", ret);
       printf("%d\n", count);
       return 0;
}

40
102334155
39088169
算第40个斐波那契数时,fib(3)算了3900w次

#include<stdio.h>
int fib(int n)
{
       int a = 1;
       int b = 1;
       int c = 1;//n = 1/2 不走循环直接返回1
       while (n > 2)
       {
               c = a + b;
               a = b;
               b = c;
               n--;
       }
       return c;
}
int main()
{
       int n = 0;
       scanf("%d", &n);
       int ret = fib(n);
       printf("%d\n", ret);
       return 0;
}

从前往后算,一瞬间算出来了(虽然结果可能是错的)
但如果n太大了 会超出限制
如果想进行大数运算(几百位),需要专门写代码不能仅靠内置的数据类型
long long也有它的限制

递归程度不够深,没有达到触发栈溢出的深度
如果用递归写代码很简单,且没有明显问题,就用递归解决
如果递归有明显的问题,要用非递归写

练习

1/1-1/2+1/3-1/4 …… - 1/100

int main()
{
	int i = 0;
	double sum = 0.0;
	int flag = 1;
	for (i = 1; i <= 100; i++)
	{
		sum = sum + flag*(1.0 / i);
		flag = -flag;
	}
	printf("%lf\n", sum);

	return 0;
}

9*9乘法口诀表

int main()
{
	int i = 0;
	for (i = 1; i <= 9; i++)
	{
		//打印一行
		int j = 0;
		for (j = 1; j <= i; j++)
		{
			printf("%d*%d=%-2d\t", i, j, i * j);
		}
		printf("\n");
	}
	return 0;
}

image-20220110220929081

函数设计应该追求高内聚低耦合(自己足够独立)
尽可能少地使用全局变量
尽量做到谁申请的资源由谁来释放
image-20220110222342620

字符串逆序

#include<stdio.h>
int My_strlen2(char* s)       //递归实现
{
       if (*s == '\0')
               return 0;
       else
               return 1 + My_strlen2(++s);
}
void reverse_string(char* arr,int len)
{
       if (len >= 2)
       {
               int tmp = arr[0];
               arr[0] = arr[len - 1];
               arr[len - 1] = tmp;
               reverse_string(++arr, len-=2);
       }
}
int main()
{
       char arr[] = "abcdef";
       printf("%s\n", arr);
       int len = My_strlen2(arr);
       reverse_string(arr,len);
       printf("%s\n", arr);
       return 0;
}

递归2个经典项目

1汉诺塔问题

image-20220110201426323

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
static int count = 0;//计数器
void move(int n,char A,char C)
{
       printf("将%d号盘子从%c移动到%c\n", n, A, C);
       count++;
}
void hannio(int n, char A, char B, char C)
{
       int count = 0;
       int i = 0;
       if (n == 1)
       {
               move(n,A,C);
               printf("将%d号盘子从%c移动到%c\n", n,A,C);
       }
       else
       {
               hannio(n - 1,A,C,B);//借助C将n-1个从A移到B
               move(n, A, C);//将最大那个从A移到C
               hannio(n - 1, B, A, C);//借助A将n-1个从B移到C
       }
}
int main()
{
       char ch1 = 'A';
       char ch2 = 'B';
       char ch3 = 'C';
       int n = 0;
       scanf("%d", &n);
       hannio(n,'A','B','C');
       printf("总共移动%d次\n", count);
       return 0;
}


将1号盘子从A移动到C
将1号盘子从A移动到C
将2号盘子从A移动到B
将1号盘子从C移动到B
将1号盘子从C移动到B
将3号盘子从A移动到C
将1号盘子从B移动到A
将1号盘子从B移动到A
将2号盘子从B移动到C
将1号盘子从A移动到C
将1号盘子从A移动到C
总共移动7次

image-20220110201529858

2青蛙跳台阶

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值