C语言函数基础速成笔记

本文介绍了函数的概念,重点讲解了自定义函数的定义、参数传递、返回值类型、全局变量的作用以及其带来的命名空间污染、函数耦合性和并发安全问题。通过实例展示了如何创建和使用自定义函数,并讨论了避免全局变量滥用的最佳实践。
摘要由CSDN通过智能技术生成

1,什么是函数(函数的定义)

        在维基百科中,函数的定义叫做子程序

        你可以把他理解为你程序的四肢。一个人要做事情,需要手脚一起配合完成,函数就是程序的手脚,可以帮助程序完成很多的事情。

我们经常使用的打印数据printf()就是一个函数,又比方我们能够让我们输入数据的scanf()也是一个函数,这些大大小小的函数让我们写代码更加的简单方便。

2,自定义函数

函数有很多种类,这篇文章主要讲解的是自定义函数的用法。

自定义函数顾名思义就是由我们自己创建的一个函数。

先从一个简单的函数开始

#include <stdio.h>

int add_num(int a,int b)
{
    int c=a+b;
    return c;
}

int main(int argc, char **argv)
{
    int a=1,b=2;
    int c=add_num(a,b);
    
    printf("c=%d",c);

    return 0;
}

从头开始看,int 是函数的类型,add_num是函数的名称 。(int a,int b)是函数需要的数据类型

函数需要的数据类型是什么意思?

我们使用scanf("%d",&a)的时候,为什么要写成&a而不是a ,因为scanf函数规定了那个位置你必须要填写地址,a是变量不是地址,&a是地址,所以你可以使用scanf函数了。

在上面这个add_num函数中也一样,这是一个两个相加的函数,那肯定是你给我两个数(a,b),放到我的函数里进行加工,我加工好了把东西(c)给你。

return c?

首先我们要清楚一个知识点,add_num函数里的a和main函数里的a 不是同一个!

add_num函数里的a是一个形参;main函数里的a是一个实参;

add_num函数运行完,里面的a,b就会被摧毁。等待下一次我们调用函数,它再生成a,b;

#include <stdio.h>

int add_num(int a,int b)
{
    int c=a+b;
    return c;
}
int main(int argc, char **argv)
{
    int e=1,h=2;
    int c=add_num(e,h);
    printf("c=%d",c);

    return 0;
}

现在我们把main函数里的a,b改成e,h,结果还是一样可以运行。可见add_num()里不一定要写a,b. 我们只需要把正确的数据给传进去就可以了,至于是谁传都无所谓,同名也是可以的。

居然可以同名,那数据不会混乱吗?答案是不会的,因为如果你不使用特殊方法,函数之间的数据是不会流通的,因此你就算在函数add_num中加减乘除一万遍,add_num函数结束,a,b被摧毁,外边依旧风平浪静。                   

return c 就是这个让数据逃离被摧毁的特殊方法之一

int c=add_num(a,b);
在这段代码里,我们首先定义了一个int c。 然后给c赋值 c=add_num(a,b);

为什么函数的值是a+b的值呢?就是我们在函数中返回了c,而c就是a+b的值。

现在大家知道了自定义函数的基本用法,我们来总结一下。

一个自定义函数需要给它数据,给它需要的数据。

void printf_table(int a)
{
    printf("这是一个目录!\n");
    printf("这是一个目录!\n");
}

int main(int argc, char **argv)
{
    printf_table(1);
  
    return 0;
}

我创建了一个名叫printf_table的函数,它的功能是打印出目录,但是它需要一个int 类型的变量,反正函数里也不需要用到 变量干嘛,我在调用函数printf_table(1),给了个1,函数一样可以调用。

当然函数也是可以不需要参数的,当函数不需要参数时,它将会无条件直接运行函数体里的代码

函数的类型

函数的返回值的类型是由函数名旁边的数据类型来决定的

#include <stdio.h>

int add_num(int a,int b)
{
    int c=a+b;
    return c;
}

int main(int argc, char **argv)
{
    int a=1,b=2;
    int c=add_num(a,b);
    
    printf("c=%d",c);

    return 0;
}

在这段代码中,我们函数add_num的类型是int ,所以它返回的是一个int类型的值,所以我们int类型的变量c 可以接受到add_num返回的int值。如果c是char类型的,就无法接收到函数返回的int类型的值了。

还有很多类型的函数。

char fun(int n)
{
	return xx;//xx的类型必须是char类型(返回一个字节的数据)
}
short fun(int n)
{
	return xx;//xx的类型必须是short类型(返回两个字节的数据)
}
float fun(int n)
{
	return xx;//xx的类型必须是float类型(返回四个字节的数据)
}
int fun(int n)
{
	return xx;//xx的类型必须是int类型(返回四个字节的数据)
}
   //函数是可以没有返回值的
void fun(int n)
{
	return;//void对应的是没有返回值;建议还是要将return写上
}

现在我们做个练习整合一下:

封装一个函数,描述你女朋友或男朋友的属性
调用的时候分别使用变量和常量的方法,调用完之后输出他(她)的属性。
姓名、性别、身高、游戏等级 等等

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

void girl_friend(char name[],char sex,int height,char level)//定义形参,数组需要的数据
{
    char girl_name[20]={0};//定义局部变量用来存储接收的数据
    strcpy(girl_name,name);//把接收的数组name放入我们定义的girl_name数组中

    char girl_sex=sex;//把接收到的sex赋值给girl_sex中
    int girl_height=height;//把接收到的height赋值给girl_height中
    char girl_level=level;//把接收到的level赋值给girl_level中

    printf("girl_name=%s\n",girl_name); //打印
    printf("girl_sex=%c\n",girl_sex);
    printf("girl_height=%d\n",girl_height);
    printf("girl_level=%c\n",girl_level);
}

int main(int argc, char **argv)
{
    girl_friend("qqq",'W',165,'S');

    return 0;
}

结果:

girl_name=qqq

girl_sex=W

girl_height=165

girl_level=S

其实这代码可以更加简便:

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

void girl_friend(char name[],char sex,int height,char level)//定义形参,数组需要的数据
{
    
    printf("girl_name=%s\n",name); //打印
    printf("girl_sex=%c\n",sex);
    printf("girl_height=%d\n",height);
    printf("girl_level=%c\n",level);
}

int main(int argc, char **argv)
{
    girl_friend("qqq",'W',165,'S');

    return 0;
}

不需要赋值,我们直接调用我们输入的信息。

现在有一个新问题,我们定义一个函数,输入一个数组,想把数组里的最大值和最小值给取出来。 如果只是取一个值,我们用return 返回这个最大值完全可以,但现在我们需要返回两个值出来,应该怎么办?

这就需要用到取址传参了。

取址传参

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

// 函数用于找出数组中的最大值和最小值
void max_min(int arr[], int size, int *max, int *min)
{
    *max = *min = arr[0]; // 将首元素设为最大值和最小值的初始值
    for (int i = 1; i < size; i++)
    { // 从第二个元素开始遍历数组
        if (arr[i] > *max)
        {                  // 如果当前元素大于最大值
            *max = arr[i]; // 更新最大值
        }
        if (arr[i] < *min)
        {                  // 如果当前元素小于最小值
            *min = arr[i]; // 更新最小值
        }
    }
}

int main()
{
    int arr[] = {1, 2, 3, 4, 5};
    int arrLength = sizeof(arr) / sizeof(arr[0]);//获取数组的长度

    int max, min;
    max_min(arr, arrLength, &max, &min);

    printf("数组的长度为:%d\n", arrLength);
    printf("最大值:%d\n", max);
    printf("最小值:%d\n", min);

    return 0;
}

我们首先来看看这个函数

void max_min(int arr[], int size, int *max, int *min)

这个函数需要一个数组,一个int类型的数据size,两个int * 类型的数据

数组好理解,就是我们来求最大值最小值需要的数组,而这个size就是数组的长度,我们已经在main里使用sizeof计算出来了。为什么不在函数里算数组长度直接用呢?这样多传一个参数size不是很麻烦吗?

sizeof运算符无法用于计算数组函数参数的长度,因为在函数参数中,数组会被转换为指向数组元素类型的指针。所以,sizeof(arr)将返回指针的大小,而不是数组的大小。

接下来看看int *max ,int *min   

    int max, min;
    max_min(arr, arrLength, &max, &min);

这是在main函数里,我们定义了int max,int min 两个int类型的数据。 我们又给它添加了&取址符,加了&,它就从int 变成 int *了。

那max变成&max 有没事影响吗?当然有,假设max是一座房子,&max就是这座房子的地址,人家知道了你家地址,是不是就可以往你家寄快递什么的。

所以在别的函数里,int max被修改后的数据传出不来,但是&max一但被修改,你这max也要被修改。但是怎么通过地址来修改你的数据我们待会再说。

&max是一个指针,指向的地址是max变量所占据的内存空间。

void max_min(int arr[], int size, int *max, int *min)
{
    *max = *min = arr[0]; // 将首元素设为最大值和最小值的初始值
    for (int i = 1; i < size; i++)
    { // 从第二个元素开始遍历数组
        if (arr[i] > *max)
        {                  // 如果当前元素大于最大值
            *max = arr[i]; // 更新最大值
        }
        if (arr[i] < *min)
        {                  // 如果当前元素小于最小值
            *min = arr[i]; // 更新最小值
        }
    }
}

我们传递了一个数组,一个数组长度,两个指针进去。

 *max = *min = arr[0]; // 将首元素设为最大值和最小值的初始值

max现在就是一个地址,不是变量,所以我们需要通过*解运算来让它代表它所指向的内存区。

*max=1,2,3,4,5 都可以,因为*max指向的就是max的内存区,你可以理解现在*max就是变量max。

接下来就是通过循环把最大值和最小值传给*max和*min,刚刚说了这个*max和*min同等与远在天边main函数里的变量max和min,因为它们的所占据的内存空间都一样,当*max被赋值后,也传回主函数了,同理*min也是一样。

int main()
{
     。。。。。。//省略

    printf("数组的长度为:%d\n", arrLength);
    printf("最大值:%d\n", max);
    printf("最小值:%d\n", min);

    return 0;
}

所以我们直接打印max和min的值

数组的长度为:5

最大值:5

最小值:1

 全局变量

#include <stdio.h>

int count(int a,int b)
{
    int c=a+b;
    return c;
}

int main(int argc, char **argv)
{
    int a=18;
    int b=18;
    int c=count(a,b);
    printf("a+b=%d\n",c);

    return 0;
}

我们知道函数中定义的c,a,b这些都是不会冲突的,因为函数中的变量都是不互通的,除非你传进去。你在函数中定义的变量都是局部变量,在count函数中int c 是count函数的局部变量,在main函数中定义的a,b,c也是main函数中的局部变量。

那有没有一直变量可以在每一个函数中进出自如呢?答案是有的,那就是全局变量。

#include <stdio.h>

int c=0;

int count(int a,int b)
{
    c=a+b;
    return 0;
}

int main(int argc, char **argv)
{
    int a=18;
    int b=18;

    count(a,b);
    printf("a+b=%d\n",c);

    return 0;
}

在头文件的下面,我们定义了一个int c,这就是全局变量。

它在函数count中被修改后,又在main函数被调用,从始至终它都是那个c,这就是贯彻全局的全局变量。当然还是希望大家能少用全局变量就少用全局变量。

  1. 命名空间污染: 全局变量会增加命名空间的复杂性。全局变量的作用域是全局的,可能会与其他文件中的全局变量产生命名冲突,尤其在大型项目中更加严重。

  2. 可维护性差: 全局变量的值可以被任意函数修改,使得程序的状态难以追踪。这会增加代码的复杂性,减少代码的可读性和可维护性。

  3. 函数之间的耦合性增加: 使用全局变量的函数和数据之间的依赖性会增加,导致函数更难以复用和测试。

  4. 并发安全性: 多线程环境下,全局变量的修改可能会带来并发安全性问题,需要额外的同步机制来保证线程安全。

  • 30
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值