C语言学习笔记:函数和递归

注:学习参考视频为b站鹏哥教程

C语言中函数分类: 库函数(C语言本身提供) 自定义函数

库函数

常用的库函数:

  • IO函数(scanf/printf) <stdio.h>
  • 字符串操作函数(strlen/strcmp/strcpy) <string.h>
  • 字符操作函数(判断大小写,大小写转换)
  • 内存操作函数(memset)
  • 时间/日期函数(time)
  • 数学函数(sqrt) <math.h>
  • 其他库函数

strcpy - 字符串拷贝

拷贝字符串(包括后面的\0结束标志,故如果原字符串长度大于拷贝过去的字符串,\0后的也不再生效)

memset - 内存设置

int main()
{
    char arr[] = "hello world";
    memset(arr, '*', 5);
    printf("%s\n", arr);
    return 0;
}//***** world

查询资料网址:https://zh.cppreference.com

自定义函数

函数名 返回值类型 函数参数

函数的组成

ret_type fun_name(paral,*)
{
	statement;//语句项
}

返回两数最大值

int max(int a, int b)
{
    return a > b ? a : b;
}

int main()
{
    int a = 10;
    int b = 20;
    int num = max(a, b);
    printf("%d\n", num);
    return 0;
}

交换两个整形变量

void swap(int a, int b)//无返回值
{
    int temp = a;
    a = b;
    b = temp;
}

int main()
{
    int a = 15;
    int b = 20;
    swap(a, b);
    printf("a=%d, b=%d\n", a, b);
    return 0;
}
//以上代码未能完成交换任务,原因是在进入swap函数中,传入函数的参数分配了新的地址存储,原来的a,b所在内存存储的信息并未发生改变,这与自定义函数的函数参数名无关

此处复习一下指针

int a = 10;
int* pa = &a;
*pa = 20;//解引用操作,*pa = a
void swap(int* pa,int* pb)
{
    int temp = *pa;
    *pa = *pb;
    *pb = temp;
}
int main()
{
    int a = 15;
    int b = 20;
    swap(&a,&b);
    printf("a=%d, b=%d\n", a, b);
    return 0;
}

函数的参数

实际参数:真实传给函数的参数。无论是什么类型的量(常量、变量、表达式、函数),在进行函数调用时,他们必须有确定的值,以便传给形参

num = max(100, 300+1);//表达式
num = max(100, max(3,7));//函数

形式参数:函数名后括号中的变量,形式参数只有函数被调用过程中才实例化(分配内存单元),函数调用完成后自动销毁,形参只在函数中有效

swap(&a,&b);//&a,&b为实参
void swap(int* pa,int* pb)//pa,pb为形参

当实参传给形参时,形参是实参的一份临时拷贝,对形参的修改不会改变实参

函数的调用

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

传址调用:把函数外部创建变量的内存地址传递给函数参数,可以让函数和函数外边的变量建立真的联系,函数内部可以直接操作函数外部的变量(可改变实参

swap(a, b);//传值调用
swap(&a,&b);//传址调用

练习

1.判断素数
#include <math.h>
int sushu(int x)
{
    for (int j = 2; j < sqrt(x); j++)
    {
        if (x % j == 0)
        {
            return 0;
        }
    }
    return 1;
}

int main()
{
    int count = 0;
    for (int i = 100; i <= 200; i++)
    {
        if (sushu(i) == 1)
        {
            printf("%d ", i);
            count++;
        }
    }
    printf("\ncount = %d", count);
    return 0;
}
2.判断是否为闰年
int runnian(int year)
{
    if ((year % 400 == 0) || ((year % 4 == 0) && (year % 100 != 0)))
        return 1;
    return 0;
}

int main()
{
    int year = 0;
    int count = 0;
    for (year = 1000; year <= 2000; year++)
    {
        if (runnian(year) == 1 )
        {
            printf("count = %d ", year);
            count++;
        }
    }
    printf("\n%d", count);
    return 0;
}
3.实现一个整形有序数组的二分查找
//以下为一个错误代码,原因是数组在传参时仅仅传入第一个元素的地址,即自定义函数中的形参数组为一个指针,故在函数内部不可使用sizeof求解数组大小,sizeof求出的是指针大小(4/8,此处为4)
int binary_search(int x, int v[])
{
    int n = sizeof(v)/sizeof(v[0])//如果这样写,此处指针(4)/整型(4) = 1,故可以像分支循环篇中的那样,在主函数中计算数组长度n传入自定义函数
    int left_index = 0;
    int right_index = n-1;
    while(left_index <= right_index)//注意查找条件
    {
        int middle_index = (left_index + right_index)/2;
        if(x==v[middle_index])
        {
            return middle_index;
        }
        else if(x<v[middle_index])
        {
            right_index = middle_index-1;
        }
        else if(x>v[middle_index])
        {
            left_index = middle_index+1;
        }
    }
    return -1;
}
int binary_search(int x, int v[]int n)
{
    int left_index = 0;
    int right_index = n-1;
    while(left_index <= right_index)//注意查找条件
    {
        int middle_index = (left_index + right_index)/2;
        if(x==v[middle_index])
        {
            return middle_index;
        }

        else if(x<v[middle_index])
        {
            right_index = middle_index-1;
        }
        else if(x>v[middle_index])
        {
            left_index = middle_index+1;
        }
    }
    return -1;
}
4.每调用一次函数,num值+1
void addone(int* pn)
{
    *pn += 1;//不可写为*pn++,因为++优先级比较高,会先pn++再解引用,可以写为(*pn)++,+=优先级低于*,故可不加括号使用
}

int main()
{
    int num = 0;
    addone(&num);
    printf("%d\n", num);
    return 0;
}

C语言中运算符优先级

  1. ()、[]、后置++/–
  2. 前置++/–、*、&
  3. 赋值运算符=、+=

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

嵌套调用

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

printf("%d\n", strlen("abc"));//链式访问
printf("%d",printf("%d", printf("%d", 43)));
//4321
//printf返回值是打印对象的个数
printf("%d",printf("%d", printf("%s", "abc")));
//abc31

函数的声明和定义

函数声明:先声明后使用,一般放在头文件中

函数定义:函数的具体实现

先写使用部分的代码,再编写自定义函数→TDD(测试驱动开发)

#ifndef __ADD_H__
#define __ADD_H__//避免同一个头文件被引用多次

int Add(int x, int y);//使用前进行声明(.h头文件)

#endif//Add.h
int Add(int x, int y)//函数定义(.c源文件)
{
    return x+y;
}

//使用时
#include "add.h"//自己写的头文件用双引号

函数递归

递归:程序调用自身的编程技巧,一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,通常把一个大型复杂的问题层层转换成一个与原问题相似的规模比较小的问题来求解(大事化小

递归的两个必要条件

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

递归常见的错误:栈溢出(stack overflow)

内存大概分区:

  1. 栈区:局部变量、函数形参、函数调用
  2. 堆区:动态开辟的内存(calloc、malloc)
  3. 静态区(全局变量,static修饰的变量)

练习

1.接收一个整型值(无符号),按照顺序打印它的每一位

例如:输入:1234,输出1 2 3 4.

void print(int num)
{
    if (num > 9)//一定要找到递归条件,避免无条件一直递归,遇到栈溢出
    {
        print(num/10);
    }
    printf("%d ", num%10);
}

int main()
{
    unsigned int num = 0;
    scanf("%d", &num);
    print(num);
    return 0;
}
2.编写函数不允许创建临时变量,求字符串长度
//此为自行编写(使用临时变量)
int stringlength(char* str)
{
    static int length = 0;//使用静态变量,保证length的值可以连续
    if (*str != '\0')
    {
        str++;
        length++;
        stringlength(str);
    }
    return length;
}

//也可使用while循环(使用临时变量)
int stringlength(char* str)
{
    int length = 0;
    while (*str != '\0')
    {
        str++;
        length++;
    }
    return length;
}

//自行编写3.0版本
int stringlength(char* str)
{
    if (*str != '\0')
    {
        str++;
        return 1+stringlength(str);
    }
}

//老师讲解完美4.0版
int stringlength(char* str)
{
    if (*str != '\0')
        return 1+stringlength(str+1);
    else
    	return 0;
}

int main()
{
    char str[100];
    scanf("%s", str);
    int length = stringlength(str);
    printf("%d\n", length);
    return 0;
}
[递归与迭代]

迭代:重复做一个事情,类似于循环,但不一定展现出的形式就是循环(goto可以实现迭代,但不称之为循环)

3.求n的阶乘
int jiecheng(int n)//递归
{
    if(n > 1)
        return n*jiecheng(n-1);
    else
        return n;
}

int jiecheng(int n)//循环
{
    int num = 1;
    while (n >= 1)
    {
        num = num * n;
        n--;
    }
    return num;
}

int main()
{
    int num = jiecheng(3);
    printf("%d\n", num);
    return 0;
}
4.求第n个斐波那契数列(不考虑溢出)

1 1 2 3 5 8 13 21 34 55

前两个数之和等于第三个数

//自行编写1.0
int fbnq(int n)
{
    if(n > 2) return fbnq(n - 1) + fbnq(n - 2);
    else return 1;
}
//使用递归效率很低,2的指数倍数字需要计算且大量计算重复,故考虑使用循环实现

//自行编写2.0
int fbnq(int n)
{
    int num = 1;
    if(n <= 2) return 1;
    else
    {
        for (int i = 3; i <= n; i++)
        {
            num += fbnq(i - 2);
        }
        return num;
    }
}

//老师点拨3.0
int fbnq(int n)
{
    int a = 1;
    int b = 1;
    int c = 0;
    if(n <= 2) return 1;
    for(int i = 3; i <= n; i++)
    {
        c = a + b;
        a = b;
        b = c;
    }
    return c;
}

//老师答案
int fbnq(int n)
{
    int a = 1;
    int b = 1;
    int c = 1;//n=1/2时直接通过return c获得结果
    while(n>2)
    {
        c = a + b;
        a = b;
        b = c;
        n--;
    }
    return c;
}

int main()
{
    int num = fbnq(4);
    printf("%d\n", num);
    return 0;
}

循环和递归的选择

主要考虑到编写的难易程度和递归中的栈溢出及代码效率因素

是不是递归只要写的正确(满足两个必要条件)就不会栈溢出?不是

void test(int n)
{
	if(n<10000)
	{
		test(n+1);
	}
}//该函数递归还是会产生栈溢出问题

经典题目

汉诺塔问题

#include <stdio.h>
void move(char a, char c)
{
    printf("%c -> %c\n", a, c);
}
void hanoi(int n, char a, char b, char c)
{
    if (n == 1) printf("%c -> %c\n", a, c);
    else
    {
        hanoi(n-1, a, c, b);
        move(a,c);
        hanoi(n-1, b, a, c);
    }
}

int main()
{
    hanoi(3, 'A', 'B', 'C');
    return 0;
}

青蛙跳台阶问题

#include <stdio.h>
int FrogJump(int n)
{
    int method = 0;
    if (n <= 0) return 0;//添加小于0的处理,防止无限递归栈溢出
    if (n == 1) method = 1;
    else if (n == 2) method = 2;
    else method = FrogJump(n-1) + FrogJump(n-2);
    return method;
}

int main()
{
    int num = FrogJump(7);
    printf("共%d种方法\n", num);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值