云计算百科
云计算领域专业知识百科平台

C 语言学习历程:(第六章)函数・程序中的逻辑封装艺术

第六章:函数

文章目录

  • 第六章:函数
    • 1. 库函数
    • 2. 自定义函数
      • 2.1 形参和实参
      • 2.2 return 语句
      • 2.3 数组做函数参数
    • 3. 嵌套调用和链式访问
      • 3.1 嵌套调用
      • 3.2 链式访问
    • 4. 函数声明和定义
      • 4.1 单个文件
      • 4.2 多个文件
      • 4.3 关键字使用
        • 4.3.1 extern
        • 4.3.2 static
  • 声明

C 语言中的函数就是一个完成某项特定任务的一小段代码。

1. 库函数

不同编译器提供的一系列函数的实现,就被称为库函数。这些库函数根据功能的划分,都在不同的头文件中进行声明。

库函数相关头文件:C Standard Library headers – cppreference.com

2. 自定义函数

语法形式

函数返回类型 函数名(形式参数)
{

}

2.1 形参和实参

  • 形参:在自定义函数的括号中写的参数,称为形式参数
  • 实参:真是传递给函数的参数
  • 函数的形式参数要和和实参个数匹配

只有在函数被调用的过程中存放实参传递过来的值,才会向内存申请空间

加法函数,完成两个整形变量的加法操作:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdbool.h>

//第六章:函数

//此时是形参,在不调用此函数时,不会分配内存空间,
//当调用函数时,形参便会分配内存空间(和实参各自有独立的空间)
int add(int a, int b)
{
return a + b;//将a+b的值返回出去
}

int main()
{
int a = 0;
int b = 0;

int c = add(a, b);//调用函数,将变量传递进去,此时是实参
scanf("%d", &c);

printf("两个数之和是:%d", c);

return 0;
}

2.2 return 语句

  • return后边可以是⼀个数值,也可以是⼀个表达式,如果是表达式则先执⾏表达式,再返回表达式 的结果。
  • return后边也可以什么都没有,直接写 return; 这种写法适合函数返回类型是void的情况。
  • return语句执⾏后,函数就彻底返回,后边的代码不再执⾏。
  • return返回的值和函数返回类型不⼀致,系统会⾃动将返回的值隐式转换为函数的返回类型。
  • 如果函数中存在if等分⽀的语句,则要保证每种情况下都有return返回,否则会出现编译错误。
  • 函数的返回类型如果不写,编译器会默认函数的返回类型是int。
  • 函数写了返回类型,但是函数中没有使⽤return返回值,那么函数的返回值是未知的。

2.3 数组做函数参数

  • 函数的实参是数组,形参也是可以写成数组形式的
  • 形参如果是⼀维数组,数组⼤⼩可以省略不写
  • 形参如果是⼆维数组,⾏可以省略,但是列不能省略
  • 数组传参,形参是不会创建新的数组的
  • 形参操作的数组和实参的数组是同⼀个数组

写一个函数将一个整形数组的内容,全部置为 -1,再写一个函数打印数组内容

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdbool.h>

//数组做函数参数,数组的实参和形参是同一块内存空间
//将数组内容全部置换为 -1
void set_arr(int arr[], int sz)//此时不设置长度,根据传过来的大小底层自动推断
{
for (int i = 0; i < sz; i++)
{
arr[i] = 1;
}

}

//打印数组
void print_arr(int arr[], int sz)
{
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}

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

3. 嵌套调用和链式访问

3.1 嵌套调用

函数之间的互相调用称为嵌套调用。

实现计算某年某月有多少天:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdbool.h>

//嵌套调用
//定义函数判断是否为闰年
bool is_leap_year(int year)
{
if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)
{
return true;
}
return false;
}

//定义函数计算某年某月有多少天
int get_days(int year, int month)
{
// 0 1 2 3 4 5 6 7 8 9 10 11 12
int days[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };//前面补0,方便月份和下标对应

if (is_leap_year(year) && month == 2) //嵌套调用判断是否是闰年和2月
{
days[month] += 1;//如果是闰年2月,则天数加1

return days[month];
}

return days[month];//不是闰年,则直接返回对应月份的天数
}

int main()
{
int year = 0;
int month = 0;

scanf("%d%d", &year, &month);

int days = get_days(year,month);

printf("%d年%d月有%d天\\n", year, month, days);

return 0;
}

3.2 链式访问

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

//链式访问
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

int main()
{
//printf的返回值是输出字符的个数,包括换行符和空格
//输出 43 3 2
printf("%d ", printf("%d ", printf("%d ", 43)));

return 0;

}

4. 函数声明和定义

4.1 单个文件

当我们将自定义的函数放在 main 函数后,也就是调用此函数之后,需要在函数调用前声明一下此函数。函数的调用要满足先声明后使用,函数的定义是一种特殊的声明。

  • 函数声明:函数返回类型 函数名(函数参数)

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdbool.h>

bool is_leap_year(int year); //函数声明

int main()
{
int year = 0;
int month = 0;

scanf("%d%d", &year, &month);

int days = get_days(year,month);

printf("%d年%d月有%d天\\n", year, month, days);

return 0;
}

//定义函数判断是否为闰年
bool is_leap_year(int year)
{
if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)
{
return true;
}
return false;
}

4.2 多个文件

函数的声明和类型的声明放在头文件中 (.h) ,函数的实现放在其他源文件中 (.c) ,此时 main 函数所在的源文件要包括自定义的头文件。

add.h

#pragma once
//头文件用于声明函数、类、变量等,防止重复包含

int add(int a, int b);

add.c

#define _CRT_SECURE_NO_WARNINGS

//函数的定义

int add(int a, int b)
{
//将a+b的值返回出去
return a + b;

}

test.c

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

//包含自定义头文件,注意使用双引号
#include "add.h"

int main()
{
int a = 0;
int b = 0;
scanf("%d%d", &a, &b);

int c = add(a,b);
printf("和是:%d\\n", c);
}

4.3 关键字使用

作用域:一段程序中所用到的名字并不是总有效,可用性的代码范围称为作用域

  • 局部变量:变量所在的局部范围
  • 全局变量:整个项目

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

  • 局部变量:进入作用域变量创建,生命周期开始,出作用域生命周期结束
  • 全局变量:整个程序的声明周期
4.3.1 extern

用于声明外部符号,如果一个全局的符号在A文件中定义,在B文件中想使用,就需要使用 extern 进行声明。

4.3.2 static

修饰局部变量

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

修饰全局变量或函数

⼀个全局变量被static修饰,使得这个全局变量只能在本源⽂件内使⽤,不能在其他源⽂件内使⽤。 本质原因是全局变量默认是具有外部链接属性的,在外部的⽂件中想使⽤,只要适当的声明就可以使⽤;但是全局变量被 static 修饰之后,外部链接属性就变成了内部链接属性,只能在⾃⼰所在的源⽂件内部使⽤了,其他源⽂件,即使声明了,也是无法正常使⽤的。函数同理

test2.c

#define _CRT_SECURE_NO_WARNINGS

//定义全局变量
//全局变量默认具有链接属性,其他文件可以通过extern进行访问
//如果此时加上static,则变为内部链接属性,其他源文件无法访问
//函数原理同上
int num = 10;

test.c

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

//static 和 extern关键字的使用

void test();//函数声明
extern int num;//声明外部变量

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

printf("num = %d\\n", num);

return 0;
}

void test()
{
//静态变量,只初始化一次(在程序编译时),仅改变生命周期,生命周期为整个程序运行期间
//如果不加入static关键字,则每次调用变量时,变量都会重新初始化,即局部变量
static int a = 0;
a++;

printf("%d\\n", a);
}


数组和函数实践:简易扫雷游戏实现

声明

本文是在鹏哥 C 语言集训营学习过程中所记录的学习笔记,梳理了核心知识点,同时也记录了本人实操验证的代码案例,供后续学习复盘使用。

赞(0)
未经允许不得转载:网硕互联帮助中心 » C 语言学习历程:(第六章)函数・程序中的逻辑封装艺术
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!