6 学习总结:函数

目录

一、函数的概念

(一)概念

(二)分类

二、库函数

(一)标准库

(二)库函数

(三)头文件

(四)相关链接

三、自定义函数

四、实参和形参

(一)实参

(二)形参

        1、概念

        2、形参的实例化

(三)实参与形参的关系

五、return 语句

六、数组做函数参数

七、嵌套调用和链式访问

(一)嵌套调用

(二)链式访问

八、函数的声明和定义

(一)在单个文件中

(二)在多文件中   

九、有意思的点

(一)"static" 和 "extern" 关键字

        1、作用域

        2、生命周期

        3、"extern" 关键字

        4、"static" 关键字

                (1)修饰局部变量

                (2)修饰全局变量

                (3)修饰函数


一、函数的概念

(一)概念

        C语言中的函数是【完成某项特定任务的一段代码】;

        函数可以复用,提高开发软件的效率
        
        注:
                函数一定要尽量【功能单一】
 

(二)分类

        ① 库函数,如 printf ,特点是直接使用,不需要自己定义

        ② 自定义函数,由用户自己写的函数

二、库函数

(一)标准库

        C语言标准中规定了C语言的各种语法规则,C语言并不提供库函数;

        C语言的国际标准(ANSI C)规定了一些常用的函数的标准,被称为标准库;


(二)库函数

        不同的编译器厂商根据【ANSI提供的C语言标准】就给出了一系列函数的实现,这些函数就被称为库函数


(三)头文件

        各种编译器的标准库中提供了一系列的库函数,这些库函数根据功能的划分,都在不同的头文件中进行了声明;

        注:

                库函数的使用,务必包含对应的头文件,若不包含则可能会出现一些问题

9a91bc62c7374648a5919ee7008307e9.png

(四)相关链接

库函数相关头文件:https://zh.cppreference.com/w/c/header
C/C++官方的链接:https://zh.cppreference.com/w/c/header
cplusplus.com:https://legacy.cplusplus.com/reference/clibrary/

三、自定义函数

        函数定义的一般形式如下,其中【大括号 { } 】包着的部分又被称为函数体:

94cb2bca9ef64f68b4e34300b2ee2035.png

        注:

                ① 调试窗口按 【F11】 才进的去函数;

                ②【返回类型】是指 return 后面【返回值】的类型;

                ③ 函数【返回值】先放到寄存器,再把寄存器的值给主函数的变量;

                ④ 函数要求【返回值】,但是函数中没写 return 的【返回值】,那具体返回什么就不确定了;

                ⑤ 函数要求没有【返回值】,则【返回类型】要写 void,不能省略不写如果没写函数的【返回类型】,编译器则认为返回的是【int类型】的值

四、实参和形参

(一)实参

        在调用函数的时候,真实传递给函数的参数,称为实际参数,简称实参

(二)形参

        1、概念

        在函数定义部分,函数名后面的参数,称为形式参数,简称形参

        2、形参的实例化

        若不调用函数,函数的参数只会在形式上存在

        形式参数只有在函数被调用的过程中为了【存放实参传递过来的值】,才向内存申请空间,这个过程就是形参的实例化

(三)实参与形参的关系

        ① 形参要和实参的个数匹配

         实参与形参的名字可以一样,也可以不同;

        ③ 实参和形参各自是独立的空间地址是不一样的;所以可以理解为形参是实参的一份临时拷贝

五、return 语句

        函数中经常会出现 return 语句,下面是一些 return 语句的注意事项:

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

        ② return后面可以是数值,也可以是表达式;若是表达式,先执行表达式,再 return 结果;

        ③ return后面可以什么都没有,直接写【return ; 】,这种写法适合函数【返回类型是 void】 的情况;

        ④ 【return 返回值的类型】和函数定义的【返回类型】不⼀致,系统会自动将【return 返回值的类型】隐式转换为函数定义的【返回类型】,但不建议这样写代码;

int add(int x, double y)
{
    return x/y
}
x/y是double类型,会被隐式转换为int类型

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

六、数组做函数参数

        重点知识:

        ① 实参写数组名即可(一维数组和二维数组都一样)形参写数组创建的形式,如果一维数组可以省略数组大小,但千万别省略了【中括号】,如果是二维数组,行可以省略,但是列不能省略 ;

void init_arr1(int arr1[])
{
    函数体
}

void init_arr2(int arr2[][5])
{
    函数体
}

        ② 数组在传参的时候,传的是数组首元素的地址,形参不会创建新的数组;形参操作的数组和实参的数组是同一个数组(指向同一段内存单元)

七、嵌套调用和链式访问

(一)嵌套调用

        概念:函数之间的互相调用

        例如:

        假设我们计算某年某月有多少天?如果要函数实现,可以设计2个函数:

        ① is_leap_year():根据年份确定是否是闰年

        ② get_days_of_month():调用is_leap_year确定是否是闰年后,再根据月计算这个月的天数

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};
    这里数组补0,是为了将输入的月份与下标对应,方便引用数组的天数

    int day = days[m];
    if (is_leap_year(y) && m == 2)  函数调用函数,嵌套使用
        day += 1;
 
    return day;
}
#include <stdio.h>

int main()
{
    int y = 0;
    int m = 0;
    printf("请输入年与月:\n");
    scanf("%d %d", &y, &m);

    int d = get_days_of_month(y, m);
    printf("%d年%d月有:%d天\n", y, m, d);

    return 0;
}

        注:

                函数不能嵌套定义

(二)链式访问

        概念:将一个函数的返回值作为另一个函数的参数,像链条一样将函数串起来就是函数的链式访问

        例如:

#include <stdio.h>

int main()
{
    printf("%d\n", strlen("abcdef"));
    return 0;
}

八、函数的声明和定义

        函数和变量都遵循 " 先声明,后使用 " 的原则,定义是一种特殊的声明

(一)在单个文件中

1、函数的定义
void print()
{
    printf("I like pizza");
}

2、函数的声明
void print();

3、函数的调用
print();

        ① 把【函数定义】的第一行拿过来,末尾加个分号,然后把整个函数体( { } 包着的部分)去掉,得到的这一行,就是函数声明;

        ② 【函数定义】或者【函数声明】要放在【函数调用】的前面

(二)在多文件中   

        一般在企业中我们写代码时候,代码可能比较多,不会将所有的代码都放在一个文件中;我们往往会根据程序的功能,将代码拆分放在多个文件中;

        一般情况下,函数的声明、类型的声明放在头文件(.h)中,函数的实现是放在源文件(.c)文件中。

        如下:

add.c 文件:

函数的定义(实现)
int Add(int x, int y)
{
    return x + y;
}

add.h 文件:

函数的声明
int Add(int x, int y);

        【函数定义(.c)】和【函数声明(.h)】作为一个模块 

        

        【函数调用(.c)】作为另一模块 

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;
}

        注:

                ① 头文件包含:#include "文件名.h",目的是为了解决函数声明的问题 ; 
                ② C语言标准库中的头文件用 < >
                ③ 自己创造的头文件用 "  "

九、有意思的点

(一)"static" 和 "extern" 关键字

        在说明 static 和 extern 前,先了解【作用域】和【生命周期】,这是理解 static 和 extern 的关键

        1、作用域

        是程序设计理念,理解为代码的有效范围

        注:        

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

                全局变量的作用域是:整个工程(项目)

        2、生命周期

        是变量的创建(申请内存)到变量销毁(收回内存)之间的一个时间段

        注:

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

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

        3、"extern" 关键字

        在C语言中,"extern" 关键字用于声明一个本文件之外的【符号(全局变量函数等)】,表示该【符号(全局变量函数等)】是在其他文件中定义的,当前文件中只是引用它;

        简单来说:一个全局的符号在A文件中定义,想在B文件中使用,就可以使用 " extern "进行声明,然后使用, 如下演示:

add.c 

int a = 2018;
test.c
#include <stdio.h>
extern int a; 进行声明

int main()
{
    printf("%d\n", a);
    return 0;
}

        这样做可以避免重复定义,同时让多个文件可以共享同一个符号(全局变量函数等)

        注:

                ① 变量的声明和创建的格式一样:

ab5b933ba27249d08c997ce2aa1b4e4f.png

                ② 每个源文件(.c)都是分开编译的,所以在一个文件定义的全局变量,被另一个文件的函数直接引用,会显示未声明,若要使用,则用 " extern " 来声明;


                ③ 全局变量可以单独放在一个源文件里面(在编译器链接的时候会找全局变量的出处)

                ④ "extern" 是让【函数、全局变量等符号】在其他文件发扬光大的

        4、"static" 关键字

        " static " 关键字主要起限制作用

                (1)修饰局部变量

        先看两段代码:

代码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;
}

输出:1 1 1 1 1

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

代码2:

#include <stdio.h>

void test()
{
    static int i = 0;  static修饰局部变量
    i++;
    printf("%d ", i);
}

int main()
{
    int i = 0;
    for(i=0; i<5; i++)
    {
        test();
    }
    return 0;
}

输出:1 2 3 4 5

        从输出结果来看,i 的值有累加的效果,其实 test 函数中的 i 创建好后,出函数的时候是不会销毁的,重新进入函数也就不会重新创建变量,直接上次累积的数值继续计算

        结论: 

        ① static 修饰局部变量改变了变量的【生命周期】,生命周期改变的本质是改变了变量的【存储类型】,本来一个局部变量是存储在内存的栈区的,但是被 static 修饰后存储到了静态区。存储在静态区的变量和全局变量是⼀样的,生命周期就和程序的生命周期⼀样了,只有程序结束,变量才销毁,内存才回收,但是作用域不变!!

        ② 编译器在编译代码的时候,就为静态变量分配了地址,而不是进入函数创建变量(栈区:存放临时的变量,进函数就创建,出函数就销毁)
 

baac701aad434c96898a80552c0ae0c0.png

                (2)修饰全局变量

代码1:

文件1:add.c

int g_val = 2018;
文件2:test.c

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

        代码 1 可以正常运行

代码2:

文件1:add.c

static int g_val = 2018;
文件2:test.c

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

        代码 2 在编译时会出现链接性错误

        总结:

        ① 一个全局变量被static修饰,使得这个全局变量只能在本身所处的源文件内使用,不能在其他源文件内使用;

        本质是全局变量默认带有外部链接属性,适当声明后另一个文件也可以使用,在 static 修饰全局变量的时候,改变了全局变量的链接属性,使得外部链接属性变成了内部链接属性,只能被自己所处的文件使用,看似是作用域变了,本质是连接属性变了

        ② 如果一个全局变量,只想在本身所处的源文件内部使用,不想被其他文件发现,就可以用static 修饰

                (3)修饰函数

        总结:

        ① static 修饰函数和static修饰全局变量的作用是一模一样的,一个函数在整个工程都可以使用,被static修饰后,只能在自身所在的文件使用,其他文件无法正常的链接使用了;

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

        ② 一个函数只想在本身所处的源文件内部使用,不想被其他源文件使用,就可以使用 static 修饰


       

        以上博客仅供分享

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值