C语言笔记四——函数

函数

1.函数的概念

C语言中的函数的概念就是子程序,C语言中的函数就是完成某特定任务的小段程序。这小段程序有特殊写法和调用方法。

【C语言的程序就是由数个小函数组合而成】

C语言中的两类函数:

  • 库函数

  • 自定义函数

2.库函数

标准库和头文件

C语言本身并不提供库函数,编译器根据C语言标准给出的一系列可以实现的函数是库函数。

库函数相关头文件网址:C 标准库头文件 - cppreference.com

C/C++官方链接:C 标准库头文件 - cppreference.com

cplusplus.com:C library - C++ Reference

库函数是在标准库中对应的头文件中声明的,所以库函数的使用,务必包含对应的头文件。

3.自定义函数

自定义函数形式:

ret_type name(形式参数)
{
    
}
  • ret_type是函数返回类型;

  • name是函数名;

  • 小括号中是形式参数;

  • 大括号{}括起来的是函数体。

    【ret_type有时的返回类型是void,表示什么都不返回】

//instance:加法函数,实现两数相加操作
#include<stdio.h>
​
int add(int x,int b)
{
    int z = 0;
    z = x + y;
    return z;
}
​
/*也可以这么实现
int add(int x,int y)
{
    return x+y;
}
*/
​
int main()
{
    int a = 0;
    int b = 0;
    scanf("%d %d",&a,&b);
    int r = add(a,b);
    printf("%d\n",r);
    return 0;
}

注:函数的参数部分需要交代清楚参数个数、参数类型、形参名字。

4.形参和实参

  • 实参:真正传递给自定义函数的参数(上面代码中的a、b);

  • 形参:只是形式上存在的参数(上面代码中的x、y),不会向内存申请空间,只有在被调用的过程中为了存放实参传递过来的值,才向内存申请空间,这个过程就是形参的实例化。

【x、y和a、b的地址是不同的,但是x、y在调用过程中得到a、b的值,可以将形参理解为实参的一份临时拷贝。】

5.return语句

  • return 后边可以是一个数值,也可以是一个表达式,如果是表达式则先执行表达式,再返回表达式的结果;

  • return后边可以什么都没有,直接写“return;”,这种写法适合函数返回类型是void的情况;

  • return返回的值和函数返回类型不一致,系统会自动将返回的值隐式转换为函数的返回类型;

  • return语句执行后,函数就彻底返回,后边的代码不再执行;

  • 如果函数中存在if等分支的语句,则要保证每种情况下都有return返回,否则会出现编译错误。

6.数组做函数参数

//写一个函数将一个整型数组的内容全部置为-1,再写一个函数打印数组的内容。基本形式如下:
#include<stdio.h>
int main()
{
    int arr[] = {1,2,3,4,5,6,7,8,9,10};
    set_arr();
    print_arr();
    return 0;
}

set_arr函数要能够对数组内容进行设置,就得把数组作为参数传递给函数,同时函数内部在设置元素时需遍历数组,需要知道元素个数,因此要传递2个参数(数组+数组的元素个数)。print_arr在执行时也同样需要两个参数。

将上面形式丰富一下(补全参数

#include<stdio.h>
int main()
{
    int arr[] = {1,2,3,4,5,6,7,8,9,10};
    int sz = sizeof(arr)/sizeof(arr[0]);
    set_arr(arr,sz);
    print_arr(arr,sz);
    return 0;
}

数组传参的重要知识:

  • 函数的形式参数要和实际参数个数匹配;

  • 函数的实参是数组,形参也可以写成数组形式;

  • 形参如果是一位数组,数组大小可以忽略不写;

  • 形参如果是二维数组,行数可以忽略,列不能忽略;

  • 数组传参,形参不会创建新数组;

  • 形参操作的数组和实参的数组是同一个数组。

实现上述函数:

void set_arr(int arr[],int sz)
{
    int i = 0;
    for(i=0; i<sz; i++)
    {
        arr[i] = -1;
    }
}
​
void print_arr(int arr[],int sz)
{
    int i = 0;
    for(i=0; i<sz; i++)
    {
        printf("%d ",arr[i]);
    }
    printf("\n");
}

7.嵌套调用和链式访问

嵌套调用就是函数之间的互相调用。

//计算某年某月有多少天
#include<stdio.h>
​
int is_leap_year(int y)//闰年判断函数
{
    if(((y%4==0)&&(y%100!=0))||(y%400==0))
        return 1;
    else 
        return 0;
}
​
int get_days_of_month(int y,int m)
{
    int days[] = {0,31,28,31,30,31,30,31,31,30,31,30,31};
    int day = days[m];
    if(is_leap_year(y) && m==2)//特殊情况:闰年
        day += 1;
    return day;
}
​
int main()
{
    int y = 0;
    int m = 0;
    scanf("%d %d",&y,&m);
    int d = get_days_of_month(y,m);
    printf("%d\n",d);
    return 0;
}

在上面的例子中出现的函数调用:

  • main函数调用了scanf、printf、get_days_of_month函数;

  • get_days_of_month函数调用了is_leap_year函数。

链式访问:将一个函数的返回值作为另外一个函数的参数,像链条一样地将函数串起来。

//简单举例
#include<stdio.h>
int main()
{
    printf("%d\n",strlen("abcdef"));
    return 0;
}
//第二个例子^.^
#include<stdio.h>
int main()
{
    printf("%d\n",printf("%d",printf("%d",43)));
    return 0;
}

第二个例子输出结果是:4321

【printf函数的返回值是打印在屏幕的字符个数】

首先最里面的printf函数输出“43”,两个字符使第二层的printf输出“2”,最外层printf输出“1”.

8.函数的声明和定义

在单个函数使用时,直接将函数写出来就可以使用

例如闰年判断:

#include<stdio.h>
​
int is_leap_year(int y)//闰年判断函数
{
    if(((y%4==0)&&(y%100!=0))||(y%400==0))
        return 1;
    else 
        return 0;
}
​
int main()
{
    int y =0;
    scanf("%d",&y);
    int r = is_leap_year(y);
    if(r == 1)
        printf("闰年\n");
    else
        printf("不是闰年\n");
    return 0;
}

这样是可以正常运行该操作的.

#include<stdio.h>
​
int main()
{
    int y =0;
    scanf("%d",&y);
    int r = is_leap_year(y);
    if(r == 1)
        printf("闰年\n");
    else
        printf("不是闰年\n");
    return 0;
}
​
int is_leap_year(int y)//闰年判断函数
{
    if(((y%4==0)&&(y%100!=0))||(y%400==0))
        return 1;
    else 
        return 0;
}

但是,如果顺序相反,编译器会报错。因为编译器在对源代码进行编译时是从第一行向下扫描,在扫描到函数调用时没有发现该调用函数的定义,就会报错。

解决方法:在函数调用前声明函数,交代清楚函数名、函数的返回类型和函数参数。

#include<stdio.h>
​
int is_leap_year(int y);//函数声明
​
int main()
{
    int y =0;
    scanf("%d",&y);
    int r = is_leap_year(y);
    if(r == 1)
        printf("闰年\n");
    else
        printf("不是闰年\n");
    return 0;
}
​
int is_leap_year(int y)//闰年判断函数
{
    if(((y%4==0)&&(y%100!=0))||(y%400==0))
        return 1;
    else 
        return 0;
}

在使用的代码在多个文件中时,将函数的声明、类型声明放在头文件(.h)中,函数实现放在源文件中(.c)。

//add.c,函数定义
int add(int x,int y)
{
    return x+y;
}
//add.h,函数声明
int add(int x,int y)
//test.c
#include<stdio.h>
#include"add.h"
​
int main()
{
    int a = 10;
    int b = 20;
    int c = add(a,b);
    printf("%d\n",c);
    return 0;
}

static和extern

static和extern是C语言中的关键字。

作用域的概念:一段程序代码所用到的名字并非持续有效,而限定这个名字可用性的代码范围就是这个名字的作用域。

  • 局部变量的作用域就是变量所在的局部范围;

  • 全局变量的作用域是整个工程。

生命周期指的是变量创建(申请内存)到变量的销毁(收回内存)之间的一个时间段。

  • 局部变量的生命周期是:进入作用域变量创建,生命周期开始,出作用域生命周期结束;

  • 全局变量的生命周期是:整个程序的生命周期。

static是静态的意思,用来:

  • 修饰局部变量

  • 修饰全局变量

  • 修饰函数

extern是用来声明外部符号的。

static修饰局部变量:

//代码1
#include<stdio.h>
​
void test()
{
    int i = 0;
    i++;
    printf("%d ",i);
}
​
int main()
{
    int i = 0;
    for(i=0; i<5; i++)
    {
        test();
    }
    return 0;
}
//代码2
#include<stdio.h>
​
void test()
{
    static int i = 0;
    i++;
    printf("%d ",i);
}
​
int main()
{
    int i = 0;
    for(i=0; i<5; i++)
    {
        test();
    }
    return 0;
}

代码1中test函数中的局部变量i是每次进入test函数先创建变量(此时生命周期开始)并赋值0,然后++,再打印,出函数的时候生命周期将要结束(释放内存)。

代码2中i值有累加的效果,因为test函数中的i创建好后,出函数时不会销毁,重新进入函数也不会重新创建变量,直接用上次累积的数值继续计算。

【总结:static修饰局部变量改变了变量的生命周期,生命周期改变的本质是改变了变量的存储类型,存储位置由栈区变为静态区,生命周期和程序的生命周期一样。作用域不变。】

static修饰全局变量:

代码1:

add.c

int g_val = 2024;

test.c

#include<stdio.h>
extern int g_val;
int main()
{
    printf("%d\n",g_val);
    return 0;
}

代码2:

add.c

static int g_val = 2024;

test.c

#include<stdio.h>
extern int g_val;
int main()
{
    printf("%d\n",g_val);
    return 0;
}

代码1正常,代码2在编译时出现链接性错误。

extern用来声明外部符号,如果一个全局符号在A文件中定义,在B文件中想使用,就可以用extern进行声明再进行使用。

【结论:⼀个全局变量被static修饰,使得这个全局变量只能在本源文件内使用,不能在其他源文件内使用。

本质原因是全局变量默认是具有外部链接属性的,在外部的文件中想使用,只要适当的声明就可以使用;但是全局变量被 static 修饰之后,外部链接属性就变成了内部链接属性,只能在自己所在的源文件内部使用了,其他源文件,即使声明了,也是无法正常使用的】

static修饰函数:

static 修饰函数和 static 修饰全局变量是⼀模⼀样的,⼀个函数在整个工程都可以使用,被static修饰后,只能在本文件内部使用,其他文件无法正常的链接使用了。

本质是因为函数默认是具有外部链接属性,具有外部链接属性,使得函数在整个工程中只要适当的声明就可以被使用。但是被 static 修饰后变成了内部链接属性,使得函数只能在自己所在源文件内部使用

  • 23
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值