C语言——函数

1. 函数基本用法

1.1 定义和三要素

函数是一个可以完成特定功能的代码模块,其程序代码是独立的,通常有返回值,也可以没有。

函数的三要素:

功能:函数要实现的功能。

参数:在函数声明和调用时定义的遍历,它用于传递数据给函数。

【1】形参:就是在函数声明的时候定义的变量,用于传递信息给函数。此时它没有具体的数值。

【2】实参:就是在调用函数时传递给函数的实际数值。

返回值:函数调用后留下的右值。

1.2 函数的声明和定义

1.2.1 函数声明

存储类型 数据类型 函数名(数据类型 形参1,数据类型 形参2,…);

1.2.2 函数定义格式

存储类型 数据类型 函数名(数据类型 形参1,数据类型 形参2,…)

{

​ 函数体;

}

函数名:是一个标识符,要符合标识符命名规则。

数据类型:是整个函数的返回值类型,如果没有返回值为void。

形式参数说明:是逗号分隔的多个变量的形式说明,简称形参。

形参就是函数声明或定义时括号中的变量,因为形式参数只有在函数调用时也被传递真实的数值。

大括号对{语句序列},称为函数体,函数体由大于等于零个语句组成的。

函数的数据总结:

(1)没有参数:括号中的参数列表可以省略,也可以写void。

(2)没有返回值:数据类型为void,函数内没有return语句。

(3)有返回值:要跟根据返回值的数据类型定义函数的数据类型,可以用return接收返回值。

(4)定义子函数时可以直接在主函数上面,如果想在主函数下面定义需要在主函数上面事先声明。

1.3 函数调用

(1)没有返回值:直接调用

​ 函数名(实参);

(2)有返回值:要在函数内定义一个与返回值类型相同的变量用return接收。如果不需要接收返回值,就直接调用函数。如果想拿到返回值来用,可以在调用函数前设一个同类型变量去接收。

实参:在调用有参数函数时,函数名后面括号中的参数称为“实参”,此时是我们传递给函数的真实数值。实参可以是:常量、变量、表达式、函数等。

代码示例:

#include <stdio.h>
void fun()                //实现打印功能
{
    printf("in fun\n");
}

void add(int a,int b)    //实现两数相加函数,无返回值。
{
    int c=a+b;
    printf("a+b=%d\n",c);
}
int add2(int a,int b)    //实现两数相加函数,有返回值。
{
    return a+b;
}

int dec(int a,int b);    //实现两数相减函数,子函数在主函数下面定义需要在上面事先声明。
int main(int argc, char const *argv[])
{
    int n1=2,n2=3;
    int rel=0,diff=0;
    fun();
    add(2,3);
    add(n1,n2);
    rel=add2(n1,n2);
    printf("in main add:%d\n",rel);
    diff=dec(6,5);
    printf("in main dec :%d\n",diff);
    return 0;
}

int dec(int a, int b)
{
    return a-b;
}

练习:定义求x的n次方值的函数( x是实数, n为正整数)。

#include <stdio.h>
float mypow(float x, int n)
{
    float rel=1;
    if(n<0)
    {
        printf("error\n");
        return -1;
    }
    for(int i=0;i<n;i++)
        rel *=x;
    return rel;
}

int main(int argc, char const *argv[])
{
    float result=mypow(1.5,2);
    printf("result= %f\n",result);
    return 0;
}

练习:编写一个函数,函数的2个参数,第一个是一个字符,第二个是一个char *,

返回字符串中该字符的个数。

#include <stdio.h>

int str_fang(char c,char *p)
{
    int n=0;
    while (*p)
    {
        if(*p == c)
            n++;
        p++;
    }
    return n;
}

int main(int argc, char const *argv[])
{
    char s[100]="helloooooo";
    int val=str_fang('o',s);
    printf("%d\n",val);
  
    return 0;
}

练习:编程实现strlen函数的功能,strlen计算字符串实际长度,不包含’\0’

#include <stdio.h>
int mystrlen(char *s)
{
    int n=0;
    while(*s != '\0')
    {
        n++;
        s++;
    }
    return n;
}

int main(int argc, char const *argv[])
{
    int len=mystrlen("hello");
    printf("%d\n",len);
  
    return 0;
}

1.4 函数传参

1.4.1 值传递

单向传递,将实参传递给形参使用,改变形参实参不会受影响。

void fun(int a, int b)
{
    a++;
    b++;
    printf("in fun: a=%d b=%d\n", a, b);
}
int main(int argc, char const *argv[])
{
    int n1 = 10, n2 = 20;
    fun(n1, n2);
    printf("in main: a=%d b=%d\n", n1, n2);
    return 0;
}

1.4.2 地址传递

双向传递,在函数中修改形参,实参也会随之变化。

void fun2(int *a, int *b)
{
    *a = 40;
    *b = 90;
    printf("in fun2: a=%d b=%d\n", *a, *b);
}

int main(int argc, char const *argv[])
{
    int n1 = 10, n2 = 20;
    fun2(&n1, &n2);
    printf("in main: a=%d b=%d\n", n1, n2);
    return 0;
}

1.4.3 数组传递

和地址传递一样,参数中存在数组的定义,也认为是指针。

void fun3(int *arrp, int n)
{
    int *p = arrp;
    printf("in fun3 array:\n");
    *(p + 1) = 100;
    for (int i = 0; i < n; i++)
    {
        printf("%d %d\n", p[i], *(p + i));
    }
    printf("in fun3: *(p+1)= %d\n", *(p + 1));
}
int main(int argc, char const *argv[])
{
    int arr[5] = {1, 2, 3, 4, 5};
    fun3(arr, 5);
    printf("in main array:\n");
    for (int i = 0; i < 5; i++)
    {
        printf("%d %d\n", arr[i], *(arr + i));
    }
    printf("in main: *(arr+1)= %d\n", *(arr + 1));
    return 0;
}

1.5 函数和栈区

栈用来存储函数内部(包括main()函数)的变量。它是一个FILO(First In Last Out),先进后出的结构。当一个函数运行结束后,这个函数所有在栈中的变量都会被删除,并且它们所占的空间将会被释放。这就产生了函数内的局部变量。栈区由CPU自动管理,不需要手动申请和释放。

p9IQRHI.png

2. 开辟堆空间

2.1 堆的概念

申请的空间种分为五个区域栈区(堆栈区),堆区,全局区,字符常量区,代码区,我们之前讲的这些定义局部变量、数组都是在内存的栈区存储。

栈区和堆区的区别

栈区:是由CPU自动申请和释放的,不需要我们手动申请。

堆区:需要我们自己随时申请,由我们自己去释放的。随用随取,用完释放。

2.2.malloc函数

2.2.1 定义

 #include <stdlib.h>
 void *malloc(size_t size);
  • 功能:在堆区开辟大小为size的空间

  • 参数:size:开辟空间的大小,单位为字节。

  • 返回值

    • 成功:返回开辟空间的首地址

    • 失败:空指针NULL

    • malloc( ) 要和free( )搭配使用

2.2.2 用法

malloc内的参数是需要动态分配的字节数,而不是可以存储的元素个数!

代码格式:

type* var_name = (type *)malloc(sizeof(type)*n);

2.3 free()函数定义

 #include <stdlib.h> 
 void free(void *ptr);
  • 功能:释放之前用malloc、calloc和realloc所分配的内存空间。

  • 参数:ptr:堆空间的首地址。

  • 返回值:无

可以释放完堆空以后,把指针赋值为空指针:

free(p);
p=NULL;

malloc和free搭配用法👇:

#include <stdio.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
    int *p=(int *)malloc(sizeof(int)*100);

    if(p==NULL)
        printf("lost!\n");
    else 
        printf("success\n");
    free(p);
    p=NULL;
    return 0;
}

注意📢:

  1. 手动开辟堆区空间,要注意内存泄漏

当指针指向开辟堆区空间后,又对指针重新赋值,则没有指针指向开辟的堆区空间,就会造成内存泄漏。

  1. 使用完堆区空间后及时释放空间

例如:

#include <stdio.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
    char *p=(char *)malloc(sizeof(char)*32);
   // p="hello"; //错误,指针指向别的地方,没有对堆空间进行操作
    scanf("%s",p);
    printf("%s\n",p);
    free(p);
    p=NULL;
    return 0;
}

2.4 函数中开辟堆空间

思考🤔:如下代码输出结果

#include <stdio.h>
#include <stdlib.h>

void fun(char *p)
{
    p= (char *)malloc(32);
    scanf("%s",p);
}

int main(int argc, char const *argv[])
{
    char *m=NULL;
    fun(m);
    printf("%s\n",m);
    //free(m);
    //m=NULL;
    return 0;
}

运行结果:Segmentation fault (core dumped)出现段错误。

p9I314P.png

原因是,函数执行完堆空间会被销毁,不会被保留函数内开辟堆空间的地址,并不会保留m的指向。此时m还指向空。

🎯解决方法1:通过返回值

#include <stdio.h>
#include <stdlib.h>

char *fun()
{
    char *p= (char *)malloc(32);
    scanf("%s",p);   
    return p;
}

int main(int argc, char const *argv[])
{
    char *m=fun();
    char *n=fun();
    printf("%s\n",m);
    printf("%s\n",n);
    free(m);
    m=NULL;
    free(n);
    n=NULL;
    return 0;
}

🎯解决方法2:通过传参

#include <stdio.h>
#include <stdlib.h>
void fun(char **p)
{
    *p = (char *)malloc(32);
    scanf("%s", *p);
}

int main(int argc, char const *argv[])
{
    char *m=NULL;
    fun(&m);
    printf("%s\n",m);
    free(m);
    m=NULL;
    return 0;
}

p9I344x.png

3.string函数族

3.1 strcpy

#include <string.h>
char *strcpy(char *dest, const char *src);
  • 功能:实现字符串的复制,包括\0
  • 参数:
    • dest:目标字符串首地址
    • src:源字符串首地址
  • 返回值:目标字符串首地址
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char const *argv[])
{
    char s1[32]="hello";
    char s2[32]="world";
    strcpy(s1,s2);
    printf("%s\n",s1);
    return 0;
}

3.2 strlen

#include <string.h>
size_t strlen(const char *s);
  • 功能:计算字符串长度
  • 参数:s:字符串的首地址
  • 返回值:返回字符串实际长度,不包括‘\0’在内。

3.3 strcat

#include <string.h>
char *strcat(char *dest, const char *src);
  • 功能:用于字符串拼接

  • 参数:

    • dest:目标字符串首地址
    • src:源字符串首地址
  • 返回值:目标字符串首地址

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

int main(int argc, char const *argv[])
{
    char s1[32]="hello ";
    char s2[32]="world";
    strcat(s1,s2);
    printf("%s\n",s1);
    return 0;
}
#include <string.h>
char *strncat(char *dest, const char *src, size_t n);

拼接src的前n个字符

char s1[32]="hello ";
char s2[32]="world";
strncat(s1,s2,4);
printf("%s\n",s1);  //hello worl

3.4 strcmp

#include <string.h>
int strcmp(const char *s1, const char *s2);
  • 功能:用于比较字符串
  • 参数:s1和s2是比较的字符串的首地址
  • 返回值:
    • 1:s1>s2
    • 0: s1==s2
    • -1: s1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char const *argv[])
{
    int a=strcmp("hello","world");
    if(a==0)
        printf("相等!\n");
    else 
        printf("不相等!\n");
    return 0;
}
#include <string.h>
int strncmp(const char *s1, const char *s2, size_t n);

比较两个字符串前n个字符的大小

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char const *argv[])
{
    int a=strncmp("hello world","hello",5);
    if(a==0)
        printf("相等!\n");
    else 
        printf("不相等!\n");
    return 0;
}

练习1:封装函数实现两数交换

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

void swap(int *a, int *b)
{
	int c=0;
	c=*a;
	*a=*b;
	*b=c;
}
int main()
{
	int n1=5,n2=6;
	printf("n1=%d, n2=%d\n",n1,n2);
	swap(&n1,&n2);
	printf("n1=%d, n2=%d\n",n1,n2);
	return 0;
}

练习2:封装函数实现strcpy功能

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

char *myStrcpy(char *dest, char *src)
{
	while(*src != '\0')
	{
		if(*dest!=*src)
			*dest=*src;
		dest++;
		src++;
	}
	*dest='\0';
}
int main()
{
	char s1[32]="hello";
	char s2[32]="world~~";

	myStrcpy(s1,s2);
	printf("%s\n",s1);
}

练习3:封装函数实现strcat功能

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

char *myStrcat(char *dest, char *src)
{
    int len = strlen(dest);
    char *p = dest + len;
    while (*src != '\0')
    {
        *p = *src;
        p++;
        src++;
    }
    *p = '\0';
}

int main()
{
    char s1[32] = "hello ";
    char s2[32] = "world~";
    myStrcat(s1, s2);
    printf("%s\n", s1);
}

练习4: 封装函数实现冒泡排序

#include <stdio.h>

void *maopao(int *arr,int n)
{
    int c=0;
    for (int i=1;i
    {
        for(int j=0;j
        {
            if(arr[j]>arr[j+1])
            {
                c=arr[j];
                arr[j]=arr[j+1];
                arr[j+1]=c;
            }
        }
    }
}
int main(int argc, char const *argv[])
{
    int a[5]={5,4,3,2,1};
    maopao(a,5);
    for(int i=0;i<5;i++)
        printf("%d ",a[i]);
    printf("\n");
    return 0;
}

4.递归函数

4.1 概念

所谓递归函数是指一个函数体中直接或者间接调用了该函数本身,这里的直接调用是指一个函数的函数体中含有调用自身的语句,间接调用是指一个函数在函数体里有调用了其它函数,而其它函数又反过来调用了该函数的情况。

4.2 执行过程

递归函数调用的执行过程分为两个阶段:

(1)递归阶段:从原问题出发,按递归公式递推从未知到已知,最终达到递归终止条件。就是从最里层开始算,然后一层层算,直到终止。

(2)回归阶段:按递归终止条件求出结果,逆向逐步带入递归公式,回到原问题求解。

例子:求5的阶乘5!

#include <stdio.h>

int fac(int n)
{
    if(n==1)
        return 1;
    else
        return n*fac(n-1);
}
int main(int argc, char const *argv[])
{
    int n=5,ret=fac(n);
    printf("ret=%d\n",ret);
    return 0;
}

练习:

斐波那契数列 指的是这样一个数列:1、1、2、3、5、8、13、21、34、……在数学上,斐波纳契数列以如下被以递归的方法定义:F(1)=1,F(2)=1, F(n)=F(n-1)+F(n-2)(n>=2,n∈N*) 求第五个数。

F(n) :

1 n<=2

F(n-1)+F(n-2) n>2

#include <stdio.h>

int fun(int n)
{
    if (n <= 2)
        return 1;
    else
        return fun(n - 1) + fun(n - 2);
}
int main()
{
    int rel = fun(5);
    printf("rel=%d\n", rel);
    return 0;
}

递归函数调用的执行过程分为两个阶段:

(1)递归阶段:从原问题出发,按递归公式递推从未知到已知,最终达到递归终止条件。就是从最里层开始算,然后一层层算,直到终止。

(2)回归阶段:按递归终止条件求出结果,逆向逐步带入递归公式,回到原问题求解。

例子:求5的阶乘5!

#include <stdio.h>

int fac(int n)
{
    if(n==1)
        return 1;
    else
        return n*fac(n-1);
}
int main(int argc, char const *argv[])
{
    int n=5,ret=fac(n);
    printf("ret=%d\n",ret);
    return 0;
}

练习:

斐波那契数列 指的是这样一个数列:1、1、2、3、5、8、13、21、34、……在数学上,斐波纳契数列以如下被以递归的方法定义:F(1)=1,F(2)=1, F(n)=F(n-1)+F(n-2)(n>=2,n∈N*) 求第五个数。

F(n) :

1 n<=2

F(n-1)+F(n-2) n>2

#include <stdio.h>

int fun(int n)
{
    if (n <= 2)
        return 1;
    else
        return fun(n - 1) + fun(n - 2);
}
int main()
{
    int rel = fun(5);
    printf("rel=%d\n", rel);
    return 0;
}

5、指针函数

本质:是函数,函数的返回值为指针
格式:

数据类型 *函数名(参数)
{
       return 地址;//return NULL;
       //return 0;
       //return -1;
}

经常用到的一个指针函数malloc👇
void *malloc(size_t size);

  • 功能:手动申请空间,申请的空间在堆区
  • 参数:申请的空间大小
  • 返回值:任意类型指针(调用时需强制类型转换)

6、函数指针

  • 本质:指针,是指向函数的指针
  • 格式:数据类型 (*函数指针变量名)(参数);(其中,参数只加数据类型就行)
  • 用法:
    1)当一个函数指针指向了一个函数,就可以通过这个指针来调用该函数
    在这里插入图片描述
    2)函数指针可以将函数作为参数传递给其他函数调用。
    在这里插入图片描述

👉指针函数和函数指针在什么地方用👇:

  • 指针函数
    当需要这个函数返回地址时,用指针函数
  • 函数指针
    signal函数参数 (信号处理函数)
    pthread_create函数的参数 (线程的处理函数)
    在这里插入图片描述

📢要求:

  • 1.知道函数指针,能倒退还原函数
  • 2.有函数,要让指针指向这个函数,要能定义出函数指针

总之,要注意🚨:数据类型(返回值的数据类型,参数的数据类型)

7、函数指针数组

  • 本质:数组,数组的内容为函数指针
  • 格式:数据类型 (*函数指针数组名[下标])(参数);
  • 例:
    在这里插入图片描述
    注:函数指针数组用的比较少,因为该数组中的元素全部为定义好的同类函数(具有相同返回值,函数参数个数及函数参数类型)的首地址,所以在使用时有一定的限制。

img

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Sunqk5665

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值