C语言学习笔记(三)函数和递归

arongsec / 2023-07-22 / 原文

函数和递归

库函数

strcpy()

​ 使用之前要先包含<string.h>

​ 拷贝时会将\0一起拷贝(注意:/0是字符串结束的标志,但计算长度时不计入)

memset() 内存设置

​ 使用之前要先包含<string.h>

#include<stdio.h>
#include<string.h>
int main()
{
	char arr[]="hello world";
	memset(arr,'*',5);//arr为要修改的地址 中间的内容不能为字符串 5表示修改内存中的5位
	printf("%s\n",arr);
	return 0;
}

自定义函数

函数的基本组成与使用

image-20230714165944914
//定义函数,要写在主函数外面

返回值类型 函数名(函数类型 参数,)

{
	函数体;
}
  1. 返回值类型写为void,代表这个函数没有返回值

  2. 实参可以是常量、变量、表达式、函数等,反正必须要有确切的值

形式参数当函数调用完成后就会销毁,调用函数时才实例化,为形式参数分配内存空间

int* p=&a; //&a代表地址,把该地址赋给指针变量p
*p=20;//*p代表该地址所存储的内容,此处即将a的内容改为20
  1. 利用自定义函数来改变主函数中变量的值(传地址)
//自定义函数交换变量的值
#include<stdio.h>
#include<string.h>

void SWAP(int* pa,int* pb)//pa,pb是形参
{
    int tmp=0;
    tmp=*pa;
    *pa=*pb;
    *pb=tmp;
}
int main()
{
	int a=3,b=5;
    printf("a=%d,b=%d\n",a,b);
    SWAP(&a,&b); //&a,&b是实参
    printf("a=%d,b=%d",a,b);
	return 0;
}
  1. 形参是实参的一份临时拷贝,形参改变不会影响实参

函数的调用

传值调用

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

传址调用

​ 传址调用是把自定义函数外部创建变量的内存地址传递给自定义函数的参数的一种函数调用方式

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

​ 即当我想要利用自定义函数改变主函数中传入自定义函数的变量的值时,使用传址调用

数组传参

  1. 数组在函数间传递时,传递的不是数组本身,而是数组的首元素的地址。

​ 例:

#include<stdio.h>
#include<string.h>

int a_Search(int arr[],int l,int b)//arr[]本质是一个指针
{
    int left=0;
    int right=l-1;
   //错误写法:int l=sizeof(arr)/sizeof[0]; 
   //此处无法用这种方法计算数组长度,因为此时arr代表的是首元素的地址,而不是整个数组
    int x;

    while(left<=right)
    {
        x=(left+right)/2;
        if(arr[x]<b)
        {
            left=x+1;
        }
        else if(arr[x]>b)
        {
            right=x-1;
        }
        else if(arr[x]==b)
        {return x;}
    }

    while(left>right)
    {
        return -1; //找不到,返回-1
    }
}

int main()
{
    int arr[]={1,2,3,4,5,6,7,8,9,10};//整型数组(不是char类型)
    int k = 7;
    int l=sizeof(arr)/sizeof(arr[0]);
    //错误写法:int ret = a_Search(arr,k);   此处传递的实际上是数组首元素的地址
    int ret=a_Search(arr,l,k);  //正确写法:在主函数里计算出数组长度,一并传递给自定义函数
    if(ret==-1)
    {
        printf("can't find!");
    }
    else
    printf("the number is at %d",ret);
    return 0;
}

嵌套调用

例:

​ 主函数中调用自定义函数A();

​ A函数的定义里调用了自定义函数B();

​ 再定义函数B();

链式访问

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

函数的声明和定义

函数声明

  1. 告诉编译器:函数叫什么、参数是什么、返回类型是什么。但函数是否具体存在无关紧要。

  2. 先声明后使用

  3. 函数的声明一般放在头文件(.h)中,引用自己写的头文件用双引号 include "add,h"

​ 例:

int Add(int x,int y);//声明有一个Add()函数

函数定义

例:

int Add(int x,int y)
{
	int z=x+y;
	return z;
}

如果函数的定义在函数调用的前面,则不需要函数声明

函数的递归

一个过程或函数在其定义或说明的过程中直接或间接调用自身的一种方法

递归的主要思考方式:把大事化小

递归的两个必要条件

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

例题

不创建临时变量,求字符串的长度

#include<stdio.h>

int my_strlen(char* arr)
{
    if(*arr != '\0')
    {
        return 1+my_strlen(arr+1);
    }
    else
        return 0;
}   
int main()
{
    char arr[]="abcdefg";
    int len=my_strlen(arr);//传递的是arr的首个元素的地址
    printf("Len=%d",len);
    return 0;
}

有时候递归会导致运算结果很慢。可以考虑使用循环或者迭代。

其他

  1. 判断是否是闰年:年份能被400整除则是闰年

  2. ++的优先级比*高,即先执行++

    int* p;
    *p++;//无法成功实现对p+1
    (*p)++;//成功
    
  3. printf()函数的返回值打印的字符的个数