《C语言程序设计现代方法》note-6 函数

文章目录

    • 助记提要
    • 9章 函数
      • 9.1 函数定义
        • 返回类型
        • 形式参数
        • 函数体
      • 9.2 函数调用
      • 9.3 函数声明
        • 定义前调用函数的问题
        • 函数声明
      • 9.4 实际参数
        • 实际参数的转换
        • 数组型实参
        • 变长数组做形式参数
        • 数组参数声明时使用static
        • 复合字面量
      • 9.5 return语句
      • 9.6 程序终止
      • 9.7 泛型选择
      • 9.8 递归

助记提要

  1. 函数定义的4个组成;
  2. 函数声明和函数原型;
  3. 实参提升;
  4. 数组型实参的特点;
  5. 星号表示数组长度;
  6. static声明数组参数;
  7. 使用复合字面量避免定义变量;
  8. return和exit;
  9. 泛型选择;

9章 函数

函数是C程序的构建块,是自带声明和语句的小程序。
可以用函数把程序分为很多小块,便于理解和修改程序。
函数可以复用。

9.1 函数定义

函数定义的格式:

返回值类型 函数名(形式参数列表)
函数体
返回类型
  • 返回类型不能是数组类型;
  • void类型返回表示函数没有返回值;
  • C89可以省略返回类型,默认返回int;C99不允许省略返回类型。

返回类型过长的话,可以放在单独的一行:

unsigned long int
func(...)
{
    ...
	return ...;
}
形式参数
  • 形式参数可以有多个,逗号分开;
  • 每个形式参数前面必须说明其类型;
  • 如果没有形式参数,需要在括号里写void
函数体
  • 函数体是一堆花括号包起来的复合语句;
  • 函数体内声明的变量专属于此函数,其他函数无法调用或修改;
  • C89中变量声明必须在语句之前,C99中声明和语句可以混一起;
  • 返回类型是void的函数,函数体可以只有一对花括号;这样的操作可以在开发中预留空间,后续再来编写。
  • void函数必须在函数体中使用return语句指定返回值。

9.2 函数调用

函数调用语法:

函数名(实际参数列表);

注意 调用时只写函数名,不写圆括号和实参,也是合法语句,但是不起任何作用。

void函数调用没有返回值。非void函数调用会产生一个值,可以存储在变量中。
这个返回值在不需要时,允许被丢弃。可以在函数前加强制转换操作(void),让别人知道这个函数的返回值是故意被丢弃的。

// 函数返回打印的字符数
n = printf("Hi!\n");
// 程序用不到打印字符数,丢弃这个返回值
(void) printf("Hi\n");

9.3 函数声明

定义前调用函数的问题

编译器在main函数中遇到未知的函数调用时,不知道这个函数的形参数目和类型、返回值类型。编译器会先假设这个函数返回int类型,即为该函数做一个隐式声明
函数的实参个数和类型仍无法确定,编译器只能进行默认实参提升。
在后面遇到该函数的定义时,如果返回类型不符合,就会产生错误。

函数声明

有时无法在调用之前把函数定义好。提前定义也会使程序顺序不自然,难以阅读。

函数声明可以让编译器在调用前先浏览函数的信息,具体的函数定义可以后面再给。
函数声明的格式类似于函数定义的第一行,但是结尾要写分号:

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

函数声明必须和函数的定义一致。

函数声明中形式参数只写类型就够了,参数名可以省略不写。
但是最好写上,因为名字可以说明形参的作用,并提醒程序员实参的传递顺序。

C99中在调用函数前必须声明或定义,否则会出错。

老式的函数声明不提供形式参数的信息,为了作区分,将提供返回类型、形参数目和类型的的函数声明称为函数原型

有时会出于防御性目的在函数原型中省略参数名字。因为在很多人编写的大型应用程序中,可能刚好有一个宏的名字和参数名一样,在预处理时参数名会被换掉。

注意 函数声明放在函数体外会更好。如果放在函数体内,那另一个函数要调用这个函数时,还需要再次声明。后续修改过程中想添加或移除函数声明会很麻烦。

注意 返回类型相同的函数可以放在一起声明,甚至函数声明可以和变量声明放在一起。但是这样做会让程序显得乱。

// 函数声明合并
void print_num(void), print_count(int n);
// 函数声明和变量声明混合
double x, y, average(double a, double b);

9.4 实际参数

形式参数出现在函数定义当中,使用假名字表示函数执行时需要的值。
形式参数的改变不会影响实际参数的值,所以可以把形式参数做为函数内的变量使用。

实际参数是出现在函数调用中的表达式。
实际参数是值传递的,函数调用时先计算出实际参数的值,然后将值赋给相应的形式参数。

实际参数的转换

实际参数的类型与形式参数的类型不匹配时,也能进行函数调用。编译器会对实际参数进行转换:

  • 若编译器调用前遇到原型,实参的值会被隐式转换为形参的类型。
  • 若编译器调用前没有遇到原型,编译器执行默认实参提升:把float类型的实参转为double;非浮点型实参执行整值提升(C99为整数提升)。

注意 默认实参提升可能无法产生期望的结果。

int main(void) {
    double x = 3.0;
    printf("%d\n", square(x));
    return 0;
}

int square(int n)
{
    return n * n;
}

上述程序打印结果为1。函数square需要int类型的实参,拿到double类型后会产出无效的结果。

数组型实参

形式参数是一维数组时,一般不说明数组的长度。

// 一维数组形参,不写长度
int f(a[])
{ ... }

即使数组形参写了长度,也会被编译器忽略。

该函数的实际参数可以是类型对应的任意的一维数组。
函数执行时,无法确定数组的元素数目。sizeof(a) / sizeof(a[0])只能计算数组总长度,而不是数组的元素数。
一维数组型的实际参数,如果函数需要知道数组元素数,就必须把元素数做为第二个参数提供出来。这个参数可以告知函数,数组的长度比实际长度小。
注意 元素数不要填写大于实际情况的值。访问超出末尾的元素,会导致未定义的行为。

注意 函数改变数组型形式参数的元素时,相应的实际参数也会被改变。

如果形式参数是多维数组,声明参数时只能省略第一维的长度,后续的维度必须指定。

C语言是按行主序存储数组的。
二维数组的行数可以由总长度除以行长推断,而行长是由这一行的列数决定的。

// 多维数组形参,第二维之后的长度必须写
int f2(a[][10])
{ ... }
变长数组做形式参数

C99允许使用非常量表达式指定数组长度,这样的变长数组也可以做参数。

int f(int n, int a[n])
{ ... }

显式地说明数组a的长度是n。
注意 参数要先写n,后写数组a。

函数声明时,参数名是可以省略的,如果第一个参数定义被省略了,就无法把a的长度和n联系起来。这时用星号来表示数组的长度与前面的参数相关:

// 省略参数名
int f(int, int [*]);

声明时方括号为空也是可以的:

int f(int, int []);

形式参数是多维变长数组时,可以指定任意的列数。

// 二维数组的全部元素相加
int sum(int n, int m, int a[n][m])
{
    int i, j, sum = 0;
    for (i = 0; i < n; i++)
        for (j = 0; j < n; j++)
            sum += a[i][j];
    return sum;
}

这个函数的声明可以是以下几种:

int sum(int n, int m, int a[n][m]);
int sum(int n, int m, int a[*][*]);
int sum(int n, int m, int a[][m]);
int sum(int n, int m, int a[][*]);
数组参数声明时使用static

C99可以在数组参数声明时使用static

int f(int a[static 3], int n)
{ ... }

static 3表示这个数组至少有3个元素。
使用static可以使编译器在函数调用时预先取出最小数目的元素值,而不是在函数内部需要用到元素时才去取。
对于多维数组参数,static仅可用于第一维。

复合字面量

调用函数时,需要传递数组的名字:

int a[] = {1, 2, 3, 4};
s = sum(a, 4);

数组a是一个变量,调用前需要初始化。如果后续不再用到它,可以使用符合字面量避免定义变量a。
复合字面量是使用给定的元素创建的没有名字的数组。

s = sum((int []){1, 2, 3, 4}, 4);

复合字面量的格式:

(数组类型 [数组长度]){数组初始化}
  • 数组长度可以不写。写长度时,初始化列表不足的部分会补0。
  • 初始化时也可以使用指示器。
  • 函数内部创建的复合字面量可以包含任意的表达式,不只是常量。
  • 复合字面量中元素的值可以被改变。如果不允许改变,可以在类型前面加const。如(const int []){2, 3}

9.5 return语句

  • void函数必须使用return语句指定返回值。
  • renturn语句的类型和函数的返回类型不符合时,会把表达式的类型隐式转换为返回类型。
  • void函数中也可以使用return语句,但是后面不能跟表达式。否则会编译错误。

return语句不是必须的,因为执行完最后一条语句之后,函数会自动返回。C89中非void函数不写return,程序在尝试使用函数返回值时,会出现未定义的行为。C99中不写return,编译器会报错。

9.6 程序终止

一些操作系统在程序终止时可以检测到状态码。
main函数的返回值就是状态码,是int类型。程序正常终止,返回0,异常终止返回非0值。

C89中不允许把main函数的返回类型定义为void。
C99中main函数返回值可以不是int类型,但是这么做降低了程序的可移植性。

想要终止程序,除了在main函数中使用return语句外,还可以调用exit函数。
exit函数是<stdlib.h>头中的函数。

// 正常终止程序
exit(0);

为了语义清晰,<stdlib.h>提供了两个宏EXIT_SUCCESSEXIT_FAILURE,通常分别是0和1。

// 正常终止程序
exit(EXIT_SUCCESS);
// 异常终止
exit(EXIT_FAILURE);

return函数只在被main函数调用时才终止程序。exit函数不论被哪个函数调用,都会终止程序。

9.7 泛型选择

有时希望使用同一个名字编写多个函数。这些函数实现的功能相似,只是参数和返回值的类型不相同。

C99使用泛型宏来统一数学函数的各个版本。如正弦函数,标准库针对不同的参数类型定义了多种相应的函数:sin、sinf、sinl、csin、csinf、csinl。使用泛型宏之后,可以直接用sin来调用它们。泛型宏可以根据传入的参数选择对应版本。

C11提供了泛型选择表达式:

_Generic(表达式, 泛型关联列表)

使用泛型表达式:

#define sin(x) _Generic(x, \
                        float: sinf, \
                        double: sin, \
                        long double: sinl, \
                        float _Complex: csinf, \
                        double _Complex: csin, \
                        long double _Complex: csinl))(x)

泛型表达式中的第一个表达式是控制表达式,它并不求值,C语言只会提取它的类型信息。后续的某个泛型的类型名和控制表达式的类型兼容,则将其作为泛型选择的结果表达式。

  • 泛型表达式的控制表达式不允许匹配多个泛型关联的类型名。
  • 可以使用一个default泛型关联,做为控制表达式都不匹配时的默认选项。
  • 泛型表达式在预处理期间被识别和替换。
  • 泛型选择不能选择数组类型,因为数组类型的表达式传过来的是指向其首元素的指针。

9.8 递归

递归是指一个函数调用它本身的情况。
C语言允许递归,但是大部分程序不需要用到递归。

函数1调用函数2,函数2又调用函数1,这种写法相当于间接的递归,也是合法的,但是确保它们可以终止。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/920572.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

网络安全之接入控制

身份鉴别 ​ 定义:验证主题真实身份与其所声称的身份是否符合的过程&#xff0c;主体可以是用户、进程、主机。同时也可实现防重放&#xff0c;防假冒。 ​ 分类:单向鉴别、双向鉴别、三向鉴别。 ​ 主题身份标识信息:密钥、用户名和口令、证书和私钥 Internet接入控制过程 …

【网站推荐】the top trending open-source startups, every quarter

每季度最热门的开源初创公司 我们根据 GitHub 存储库自 2020 年以来的明星增长情况发布热门开源项目&#xff0c;并将其称为 Runa 开源初创公司 (ROSS) 指数。 una Capital actively invests in open-source startups (like Nginx and MariaDB) and considers an active deve…

java学习记录11

异常 在java中提供了处理异常的机制&#xff0c;能够帮助我们避免程序崩溃。 Throwable可以用来表示任何可以作为异常抛出的类&#xff0c;分为两种&#xff1a; Error和Exception。其中Error用来表示JVM无法处理的错误。程序被强制终止。 Exception又分为两种&#xff1a; 受…

IDEA如何导入项目,包括从git仓库(github)导入项目

前言 大家好&#xff0c;我是小徐啊。自从使用了IDEA开发Java应用后&#xff0c;我再也不想使用eclipse了。IDEA的好处真的太多了。今天小徐就来介绍下IDEA的入门知识&#xff0c;也就是如何导入一个项目。 IDEA如何导入项目 首先&#xff0c;打开IDEA&#xff0c;点击上方的…

GitLab|数据迁移

注意&#xff1a;新服务器GitLab版本需和旧版本一致 在旧服务器执行命令进行数据备份 gitlab-rake gitlab:backup:create 备份数据存储在 /var/opt/gitlab/backups/ 将备份数据传输到新服务器的/var/opt/gitlab/backups/下&#xff0c;并修改文件权限&#xff08;下载前和上传…

SSRF漏洞利用

2.漏洞利用 2.1 SSRF中URL的伪协议 file:// 从⽂件系统中获取⽂件内容&#xff0c;如&#xff0c;file:///etc/passwd dict:// 字典服务器协议&#xff0c;访问字典资源&#xff0c;如dict://ip:6379/info sftp:// ssh⽂件传输协议或安全⽂件传输协议 ldap:// 轻量级⽬录访问…

flux代码解析

https://zhuanlan.zhihu.com/p/714150390https://zhuanlan.zhihu.com/p/714150390 flux.1[pro] 1.版本 flux.1 [pro] api收费版本 flux.1 [dev] flux.1 蒸馏版本 guidance-distilled模型 flux.1 [schell] 1-4步版本 2.通读代码框

Scala之Array数组

可修改的Array import scala.collection.mutable.ArrayBuffer //Array:数组 //可修改的&#xff1a;ArrayBuffer //不可修改的&#xff1a;Array object Test1 {//可修改的&#xff1a;ArrayBufferdef main(args: Array[String]): Unit {//1.新建val arr1 ArrayBuffer(1,2,3)…

PostgreSQL常用字符串函数与示例说明

文章目录 coalesce字符串位置(position strpos)字符串长度与大小写转换去掉空格(trim ltrim rtrim)字符串连接(concat)字符串替换简单替换(replace)替换指定位置长度(overlay)正则替换(regexp_replace) 字符串匹配字符串拆分split_part(拆分数组取指定位置的值)string_to_array…

Elasticsearch 中的热点以及如何使用 AutoOps 解决它们

作者&#xff1a;来自 Elastic Sachin Frayne 探索 Elasticsearch 中的热点以及如何使用 AutoOps 解决它。 Elasticsearch 集群中出现热点的方式有很多种。有些我们可以控制&#xff0c;比如吵闹的邻居&#xff0c;有些我们控制得较差&#xff0c;比如 Elasticsearch 中的分片分…

unity3d——基础篇小项目(开始界面)

示例代码&#xff1a; using System.Collections; using System.Collections.Generic; using UnityEngine;public class BeginPanel : BasePanel<BeginPanel> {public UIButton btnBegin;public UIButton btnRank;public UIButton btnSetting;public UIButton btnQuit; …

不用手绘不用PS!如何一键生成波谱风插画?两个方法

​ 以前我们制作一张波谱风插画既要手绘又要用ps处理&#xff0c;现在我们直接用AI一键生成。接下来我用两个方法带你快速生成波谱风插画&#xff0c;一个是通过Midjourney&#xff0c;另一个是利用ComfyUI的工作流。话不多说&#xff0c;直接上干货。 波谱风插画是什么&#x…

推荐一款专业电脑护眼工具:CareUEyes Pro

CareUEyes Pro是一款非常好用的专业电脑护眼工具&#xff0c;软件小巧&#xff0c;界面简单&#xff0c;它可以自动过滤电脑屏幕的蓝光&#xff0c;让屏幕显示更加的不伤眼&#xff0c;更加舒适&#xff0c;有效保护你的眼睛&#xff0c;可以自定义调节屏幕的色调&#xff0c;从…

Ubuntu ESP32开发环境搭建

文章目录 ESP32开发环境搭建安装ESP-IDF搭建一个最小工程现象 ESP32开发环境搭建 最近有个小项目需要用到能够联网的mcu驱动&#xff0c;准备玩玩esp的芯片&#xff0c;记录下ESP32开发环境搭建的过程。 ESP-IDF 是乐鑫科技为其 ESP32 系列芯片提供的官方开发框架。这个框架主…

更改ArduSub水平位置控制器为ADRC

水平位置控制器的函数为update_xy_controller(),位于libraries/AC_AttitudeControl/AC_PosControl.cpp,现在的控制器为p-pid,p控制器将位置信息转化为速度信息,pid控制器将速度信息转化为加速度信息,然后在送给姿态控制器。 现在将当前的P控制器转化为ADRC控制器,其他的更…

ubuntu中使用ffmpeg和nginx推流rtmp视频

最近在测试ffmpeg推流rtmp视频&#xff0c;单独安装ffmpeg是无法完成推流的&#xff0c;需要一个流媒体服务器&#xff0c;常用nginx&#xff0c;可以直接在ubuntu虚拟机里面测试一下。 测试过程不涉及编译ffmpeg和nginx&#xff0c;仅使用基本功能&#xff1a; 1 安装ffmpeg …

图像处理 之 凸包和最小外围轮廓生成

“ 最小包围轮廓之美” 一起来欣赏图形之美~ 1.原始图片 男人牵着机器狗 2.轮廓提取 轮廓提取 3.最小包围轮廓 最小包围轮廓 4.凸包 凸包 5.凸包和最小包围轮廓的合照 凸包和最小包围轮廓的合照 上述图片中凸包、最小外围轮廓效果为作者实现算法生成。 图形几何之美系列&#…

【Nginx从入门到精通】05-安装部署-虚拟机不能上网简单排错

文章目录 总结1、排查步骤 一、排查&#xff1a;Vmware网关二、排查&#xff1a;ipStage 1 &#xff1a;ping 127.0.0.1Stage 2 &#xff1a;ping 宿主机ipStage 3 &#xff1a;ping 网关 失败原因解决方案Stage 4 &#xff1a;ping qq.com 总结 1、排查步骤 Vmware中网关是否…

Python Turtle召唤童年:喜羊羊与灰太狼之懒羊羊绘画

Python Turtle召唤童年&#xff1a;喜羊羊与灰太狼之懒羊羊绘画 &#x1f438; 前言 &#x1f438;&#x1f41e;往期绘画&#x1f41e;&#x1f40b; 效果图 &#x1f40b;&#x1f409; 代码 &#x1f409; &#x1f438; 前言 &#x1f438; 小时候&#xff0c;每次打开电视…

机器学习问题之一:协变量偏移(Covariate Shift)

协变量偏移&#xff08;Covariate Shift&#xff09;是机器学习和深度学习中的一个重要概念&#xff0c;指的是在模型训练和应用时&#xff0c;输入数据&#xff08;特征&#xff09;的分布发生了变化&#xff0c;但输出标签的分布保持不变。这会导致模型在训练集上表现良好&am…