函数4.23

1.函数的概念

其实在C语言也引⼊函数(function)的概念,有些翻译为:子程序,子程序这种翻译更加准确⼀些。

C语言中的函数就是⼀个完成某项特定的任务的⼀小段代码。这段代码是有特殊的写法和调用方法的。

两类函数

  • 库函数

  • 自定义函数

2.库函数

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

  • C语⾔的国际标准ANSI C规定了⼀些常用的函数的标准,被称为标准库

  • 那不同的编译器厂商根据ANSI提供的C语言标准就给出了⼀系列函数的实现。这些函数就被称为库函数。(封装的函数)

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

  • cplusplus.com:C library - C++ Reference

sqrt() 开平方例子 , 求负数不行

#include <stdio.h>      /* printf */
#include <math.h>       /* sqrt */
​
int main ()
{
  double param, result;
  param = 1024.0;
  result = sqrt (param);
  printf ("sqrt(%f) = %f\n", param, result );
  return 0;
}

2.1阅读和学习库函数文档

  1. 函数原型

  2. 函数功能介绍

  3. 参数和返回类型说明

  4. 代码举例

  5. 代码输出

  6. 相关知识链接

3.自定义函数(重要)

其实自定义函数和库函数是⼀样的,形式如下:

  • ret_type (表示函数计算结果返回的类型) 不需要返回类型就用void

  • 函数的参数就相当于,工厂中送进去的原材料,函数的参数也可以是 void ,明确表示函数没有参

    数。如果有参数,要交代清楚参数的类型和名字,以及参数个数。

/*
ret_type 是函数返回类型
fun_name 是函数名
括号中放的是形式参数
{}括起来的是函数体
*/
ret_type fun_name(形式参数)
{
}
  • 定义一个加法函数

  • 函数内部执行步骤

int add(int x, int y) //定义两个参数传值  返回类型要对应
{
    int z = 0;
    z = x + y; //用一个z来接受x + y的结果
    return z; //然后返回一个值z 给 c 接受 最后输出
}
int main() {
    int a = 0;
    int b = 0;
    scanf("%d", &a, &b);
    int c = add(a, b); //自定义加法函数
    printf("相加的值为:%d", c);
​
    return 0;
}
  • 实现简单的功能,不需要返回值和参数

void print(void) //void 告诉编译器不能传参 没返回值
{
    printf("我爱你");
}
int main() {
    print();
    return 0;
}

4.形参实参

  • 为什么叫形式参数呢?实际上,如果只是定义了 Add 函数,而不去调用的话, Add 函数的参数 x和 y 只是形式上存在的,不会向内存申请空间,不会真实存在的,所以叫形式参数。形式参数只有在函数被调⽤的过程中为了存放实参传递过来的值,才向内存申请空间,这个过程就是形参的实例化。

  • 调⽤Add函数时,传递给函数的参数a和b,称为实际参数,简称实参。

  • x和y确实得到了a和b的值,但是x和y的地址和a和b的地址是不⼀样的,所以我们可以理解为形参是实参的⼀份临时拷贝。

int add(int x, int y) //x,y 是形参 调用add函数的时候才会使用 x,y 
{
    int z = 0;
    z = x + y; //用一个z来接受x + y的结果
    return z; //然后返回一个值z 给 c 接受 最后输出
}
int main() {
    int a = 0;
    int b = 0;
    scanf("%d", &a, &b);
    int c = add(a, b); // a b 是实际传了数值的参数 实参
    printf("相加的值为:%d", c);
​
    return 0;
}

5.return语句

在函数的设计中,函数中经常会出现return语句,讲⼀下return语句使用的注意事项。

不写返回类型的时候,函数默认返回int类型

如果函数要求返回值,但是函数中没有使用return返回值,那具体返回什么就不确定了

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

的结果。

int test() {
    int i = 0;
    scanf("%d", &i);
    if (i > 0) {
        return 1; //i>0时 返回1 输出1
    }
    else {
        return -1;
    }
​
}
int main() {
    int r = test(); //定义一个 r 接收test()函数返回的值 
    printf("%d", r);
    return 0;
}
  • return后边也可以什么都没有,直接写 return; 这种写法适合函数返回类型是void的情况。

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

void test() {
    printf("我爱你");
    if (1);
    return; //直接退出函数 不执行下面的语句
    printf("不爱你");
}
int main() {
    test();
    return 0;
}
  • return返回的值和函数返回类型不⼀致,系统会自动将返回的值隐式转换为函数的返回类型。

int test() {
    return 3.5;  //浮点型 函数返回的类型是整形 所以会强行转换成整形 输出3
}
int main() {
​
    int r = test();
    printf("%d", r);
    return 0;
} 
​
int test() {
    return (int) 3.5;  //不会被强制转换
}
int main() {
​
    int r = test();
    printf("%d", r);
    return 0;
} 
  • 如果函数中存在if等分⽀的语句,则要保证每种情况下都有return返回,否则会出现编译错误。

int test() {
    int a = 0;
    scanf("%d", &a);
    if (a)
        return 1;  //错误的语法 if语句的每一种情况都需要有返回值
}
int main() {
    int r = test();
    printf("%d", r);
    return 0;
}

6.数组做函数参数

在使用函数解决问题的时候,难免会将数组作为参数传递给函数,在函数内部对数组进行操作。

比如:写⼀个函数将⼀个整型数组的内容,全部置为-1,再写⼀个函数打印数组的内容。

void set_arr(int arr[10], int sz,int a) {  //int a 可以更灵活的修改数组的值
                                        //在调用的时候自己输入需要修改的值即可
    int i = 0;
    for (i = 0; i < sz; i++) {
        arr[i] = a;
    }
}
​
void print_arr(int arr[10], int sz) {
    int i = 0;
    for (i = 0; i < sz; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}
​
int main() {
    int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
    int sz = sizeof(arr) / sizeof(arr[0]);
    print_arr(arr, sz); //调用函数 传参
    set_arr(arr, sz, -1);//数组传参 写个数组名就行 在调用的时候自己输入需要修改的值即可
    print_arr(arr, sz);
    return 0;
}

我们需要注意:

  • 数组在传参的时候,实参就写数组名就行,形参也是数组的形式

  • 实参和形参的名字是可以一样的,也可以一样的

  • 函数的在设计的时候,一定要尽量功能单一

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

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

  • 形参如果是⼀维数组,数组大小可以省略不写

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

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

  • 形参如果是⼆维数组,行可以省略,但是列不能省略

void print_arr(int arr[3][5], int a, int b) { //行可以省略 列不可以
    int i = 0;
    for (i = 0; i < a; i++) {
        int j = 0;
        for (j = 0; j < b; j++) {
            printf("%d ", arr[i][j]);
        }printf("\n");
    }
}
​
int main() {
    int arr[3][5] = { 1,2,3,4,5,  6,7,8,9,0,  1,2,34,3,5 };
    print_arr(arr, 3, 5);
    return 0;
}

7.嵌套调用和链式调用

7.1嵌套调用

嵌套调用就是函数之间的互相调用,每个函数就像⼀个乐高零件,正是因为多个乐高的零件互相无缝的配合才能搭建出精美的乐高玩具,也正是因为函数之间有效的互相调用,最后写出来了相对大型的程序。

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

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

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

#include <stdbool.h>
bool is_leap_year(int y) { //判断闰年函数 只有两种情况 用bool类型
    if (((y % 4 == 0) && (y % 100 != 0) || (y % 400 == 0))) 
        return true;
    else 
        return false;
​
}
​
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++; //二月份的28号加一天 因为闰年的二月份有29天
    }
    return day;//如果不是闰年 或者 不是二月份的时候返回天数;
}
​
int main() {
    int year = 0;
    int month = 0;
    scanf("%d %d", &year,&month);
    int day = get_days_of_month(year, month);
    printf("%d", day);
    return 0;
}

7.2链式访问

所谓链式访问就是将⼀个函数的返回值作为另外⼀个函数的参数,像链条⼀样将函数串起来就是函数的链式访问。

  • 正常访问

#include <string.h>
int main()
{
    int len = strlen("abcdef");//1.strlen求⼀个字符串的⻓度
    printf("%d\n", len);//2.打印⻓度 
    return 0;
}
  • 可以改成链式访问

#include <string.h>
int main()
{
    printf("%d\n", strlen("abcdef"));//2.打印⻓度 
    return 0;
}
  • 有趣的例子,输出什么呢? 分析一下 第一个打印输出43 然后返回 2 最后一个输出 返回1(2是一个字符)

int main()
{
 printf("%d", printf("%d", printf("%d", 43)));//打印几个字符就返回什么 打印43 2 1
 return 0;
}

8.函数的声明和定义

8.1单个文件

  • 函数和变量必须先声明后使用

  • 当我们把函数声明放在主函数的后面时,因为c语言是从上往下顺序执行的,当执行到int r = is_leap_year(y); 因为没有声明该函数会报错 所以我们在前面声明一下即可

  • 函数定义--->是一种特殊的声明

int is_leap_year(int y);//函数声明  两种方法
int is_leap_year(int);  //函数声明 形参可以省略
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) { //判断闰年函数 只有两种情况 用bool类型
    if (((y % 4 == 0) && (y % 100 != 0) || (y % 400 == 0))) 
        return 1;
    else 
        return 0;
​
}

8.2多个文件

⼀般在企业中我们写代码时候,代码可能比较多,不会将所有的代码都放在⼀个⽂件中;我们往往会

根据程序的功能,将代码拆分放在多个⽂件中。

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

  • 包含头文件也是声明函数

  • 好处:方便协同合作开发大型项目,模块化逻辑更清晰; 封装起来别人看不到代码怎么写的

//add.h
//函数的声明
int Add(int x, int y);
​
​
//add.c
//函数的定义
 int Add(int x, int y)
{
     return x + y;
}
​
//test.c
#include "add.h"  //引用头文件的时候 用" "   包含头文件也是声明函数
int main()
{
    int a = 10;
    int b = 20;
    //函数调⽤
    int c = Add(a, b);
    printf("%d\n", c);
    return 0;
}
  • 假设自己写了一个程序,然后卖个公司,但又不想公司知道自己的源码怎么实现的(只将lib文件和头文件卖给公司)

  • 应该怎么操作?

1.首先创建一个新的项目

2.

3.生成了一个静态库文件, (lib 和 头文件卖给公司) 打开文件里面是乱码,别人无法看到是什么内容

4.头文件导入到项目中,并在项目中写上这句 #pragma comment(lib,"add.lib") 导入静态库文件 即可

9. static 和 extern

9.1 static

使用建议:

1.⼀个函数只想在所在的源文件内部使用,不想被其他源文件使用,就可以使⽤ static 修饰。

2.如果⼀个全局变量,只想在所在的源文件内部使用,不想被其他文件发现,就可以使用static修饰。

static 是 静态的 的意思,可以用来:

• 修饰局部变量

• 修饰全局变量

• 修饰函数

  • 不加static a 是局部变量 每次循环完都会自动销毁

void test() {
    int a = 1; //a 是局部变量 每次循环完都会自动销毁
    a++;
    printf("%d ", a); //输出 2 2 2 2 2
}
​
int main() {
    int i = 0;
    for (i = 0; i < 5; i++) {
        test();
    }
    return 0;
}
  • 如果加上static,每次循环完不会自动销毁 会保存局部变量a 这条代码不运行直接跳过

void test() {
    static int a = 1; //每次循环完不会自动销毁 会保存局部变量a 这条代码不运行直接跳过
    a++;
    printf("%d ", a); //输出 2 3 4 5 6
}
​
int main() {
    int i = 0;
    for (i = 0; i < 5; i++) {
        test();
    }
    return 0;
}

结论:static修饰局部变量改变了变量的⽣命周期,⽣命周期改变的本质是改变了变量的存储类型,本来⼀个局部变量是存储在内存的栈区的,但是被 static 修饰后存储到了静态区。存储在静态区的变量和全局变量是⼀样的,生命周期就和程序的⽣命周期⼀样了,只有程序结束,变量才销毁,内存才回收。但是作用域不变的(只在该函数内可以用该变量)

9.2 extern

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

  • 图中在一个文件中定义的全局变量的时候,然后我在另外一个文件中使用这个全局变量的时候,用extern关键字,就可以正常使用

  • 如果不想另外一个源文件使用,那就在定义变量前加static,该变量不能在另外一个文件中使用

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

1.extern关键字

//add.c
int Add(int x, int y)
{
    return x+y;
}
//test.c
#include <stdio.h>
extern int Add(int x, int y);//test.c中使用Add函数 那就用extern关键字
int main()
{
 printf("%d\n", Add(2, 3));
 return 0;
}

2.static关键字

//add.c
static int Add(int x, int y)
{
 return x+y;
}
​
//test.c
#include <stdio.h>
extern int Add(int x, int y);
int main()
{
 printf("%d\n", Add(2, 3));
 return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值