本文为 C 语言学习第十天的知识点整理,涵盖函数三种传参方式、递归函数原理与实现、预处理命令(宏定义、条件编译、头文件包含)
文章目录
- 前言
- 1. 函数的三种传参方式
- 1.1 赋值传递(复制传递 / 值传递)
- 1.3 数组传递
- 1.3.1 一维整型数组传递
- 1.3.2 一维字符数组传递
- 2. 递归函数
- 2.1 什么是递归
- 2.2 递归的两个必要条件
- 2.3 注意事项
- 2.4 经典示例:递归实现斐波那契数列
- 3. 预处理命令
- 3.1 宏定义
- 3.1.1 不带参宏
- 3.1.2 带参宏(宏函数)
- 3.1.3 带参宏与函数的区别
- 3.2 条件编译
- 3.2.1 #if / #elif / #else / #endif
- 3.2.2 #ifdef / #endif
- 3.2.3 #ifndef / #endif
- 3.3 头文件包含
- 核心作用
- 头文件通常存放的内容
- 两种包含方式的区别
- 3.1 宏定义
- 今日知识点总结
1. 函数的三种传参方式
函数传参是主调函数向被调函数传递数据的核心方式,常见有赋值传递、全局变量传递、数组传递三种形式,各自适用场景与特性不同。
1.1 赋值传递(复制传递 / 值传递)
核心原理
实参将自身的值拷贝一份传递给形参,形参是实参的独立副本,二者存储在不同的内存空间中。
核心特点
- 形参的值发生改变,不会影响实参本身的值。
- 仅能将外部数据传入函数内部,无法通过形参修改外部变量。
适用场景
- 函数体内只需要读取外部变量的值,不需要修改外部变量时使用。
- 如果需要修改函数外部变量的值,不能使用值传递。
二、使用步骤
1.引入库
代码如下(示例):
import numpy as np import pandas as pd import matplotlib.pyplot as plt import seaborn as sns import warnings warnings.filterwarnings('ignore') import ssl ssl._create_default_https_context = ssl._create_unverified_context2.读入数据
代码如下(示例):
data = pd.read_csv( 'https://labfile.oss.aliyuncs.com/courses/1283/adult.data.csv') print(data.head())该处使用的url网络请求的数据。
总结
提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。
全局重名 printf("局部范围内:num=%d\n", num); // 优先使用局部变量 } int main() { test(); printf("全局范围内:num=%d\n", num); return 0; }
1.3 数组传递
数组传递的本质是传递数组首元素的地址,函数内部操作的数组与外部数组是同一块内存空间,因此函数内修改数组元素,外部数组会同步变化。
1.3.1 一维整型数组传递
传递时需要传入两个参数:数组名+数组长度。 函数形参中int array[]本质等价于指针形式,不会额外创建数组副本。
语法示例:
int a[5]; // 外部数组 int fun(int array[], int len); // 函数声明1.3.2 一维字符数组传递
字符数组即字符串,传递时可以只传数组名,依靠结束符\0判断字符串结束;也可以额外传递长度参数。
语法示例:
char str[32] = {"hello"}; int fun(char pstr[], int len); // 带长度 int fun(char pstr[]); // 依靠\0判断2. 递归函数
2.1 什么是递归
在定义一个函数时,函数体内直接或间接调用函数本身,这种函数称为递归函数。 递归的核心思想是:把一个大规模的复杂问题,拆解为一个或多个与原问题结构相同的小规模问题,逐步缩小问题规模,直到触达结束条件。
2.2 递归的两个必要条件
- 递归出口(结束条件):必须有明确的终止条件,否则会无限递归,最终导致栈溢出、程序崩溃。
- 递归递推公式:问题可以拆解为更小的同类子问题,逐步向结束条件靠近。
2.3 注意事项
- 必须设置合理的结束条件,避免死递归。
- 避免使用深层次的递归,每一层递归都会占用栈空间,层级过深会导致栈溢出。
2.4 经典示例:递归实现斐波那契数列
斐波那契数列规则:第 1、2 项为 1,从第 3 项开始,每一项等于前两项之和:1 1 2 3 5 8 13 21 ...
递归公式:
- n=1 或 n=2 时,返回 1
- n>2 时,返回
fun(n-1) + fun(n-2)
代码实现:
#include <stdio.h> int fib(int n) { // 递归出口 if (n == 1 || n == 2) { return 1; } // 递归递推 return fib(n - 1) + fib(n - 2); } int main() { int n = 6; printf("第%d项斐波那契数:%d\n", n, fib(n)); // 输出8 return 0; }3. 预处理命令
C 语言程序在正式编译之前,会先执行预处理操作。所有预处理命令都以#开头,不属于 C 语句,不占运行时间,仅在编译阶段生效。 主要包含三类:宏定义、条件编译、头文件包含。
3.1 宏定义
宏定义的核心是纯文本替换,预处理阶段会把代码中所有的宏名替换成对应的文本,替换过程不做任何计算、不做类型检查。 约定俗成:宏名全部使用大写,与普通变量名做区分。
3.1.1 不带参宏
语法:
#define 宏名 替换文本作用:
- 提高代码可读性,用有意义的名字替代魔法数字。
- 统一管理常量参数,修改一处、全局生效,实现快速调整。
- 可作为功能开关,控制代码是否生效。
示例:
#define MAX_SIZE 100 // 数组最大长度 #define PI 3.14159 // 圆周率3.1.2 带参宏(宏函数)
带参宏看起来像函数,但本质依然是文本替换,没有参数类型、返回值的概念。语法:
#define 宏名(参数列表) 替换文本注意事项:由于是纯文本替换,容易出现运算符优先级问题,建议给参数和整体都加上括号。
错误示例与正确示例:
// 错误写法:优先级问题 #define SQUARE(x) x * x // SQUARE(3+2) 会被替换成 3+2 * 3+2 = 3+6+2 = 11,不是预期的25 // 正确写法:参数加括号,整体加括号 #define SQUARE(x) ((x) * (x))3.1.3 带参宏与函数的区别
| 对比维度 | 带参宏 | 函数 |
|---|---|---|
| 本质 | 纯文本替换,编译期处理 | 完整的函数调用,运行期执行 |
| 类型检查 | 无参数类型、返回值类型,不做类型检查 | 有严格的参数类型、返回值类型,会做类型检查 |
| 执行效率 | 高,无传参、栈帧开辟等开销 | 相对低,有函数调用的额外开销 |
| 代码体积 | 每调用一次就展开一次,会增大代码体积 | 只有一份函数代码,不增加代码体积 |
| 适用场景 | 封装短小、高频的简单运算 | 逻辑复杂、代码量较大的功能模块 |
3.2 条件编译
条件编译的作用是:满足条件时,对应代码才参与编译;不满足条件时,代码直接从编译结果中移除。 常用于跨平台适配、调试开关、功能裁剪等场景。
3.2.1 #if / #elif / #else / #endif
根据常量表达式的值,选择编译哪一段代码。语法格式:
#if 常量表达式1 代码块1 #elif 常量表达式2 代码块2 #else 代码块3 #endif示例:
#define VERSION 2 int main() { #if VERSION == 1 printf("使用版本1功能\n"); #elif VERSION == 2 printf("使用版本2功能\n"); #else printf("使用默认功能\n"); #endif return 0; }3.2.2 #ifdef / #endif
判断某个宏是否已经被定义,如果已定义,则编译中间的代码。语法:
#ifdef 宏名 代码段 #endif3.2.3 #ifndef / #endif
判断某个宏是否没有被定义,如果未定义,则编译中间的代码。 最常用的场景是头文件防重复包含。语法:
#ifndef 宏名 代码段 #endif3.3 头文件包含
核心作用
预处理时找到指定的头文件,并将头文件的全部内容展开、插入到当前文件中。
头文件通常存放的内容
- 构造数据类型的定义(结构体、枚举等)
- 全局变量、全局函数的声明
- 宏定义
两种包含方式的区别
#include <文件名>- 查找路径:直接在系统指定的标准库目录(如
/usr/include)下查找头文件。 - 适用场景:包含系统标准头文件,如
stdio.h、stdlib.h。
- 查找路径:直接在系统指定的标准库目录(如
#include "文件名"- 查找路径:先在当前源文件所在目录下查找,找不到再去系统标准库目录查找。
- 适用场景:包含自定义的头文件。
今日知识点总结
函数传参:
- 值传递是拷贝副本,形参修改不影响实参,只能读取、不能修改外部变量。
- 全局变量可跨函数共享数据,局部重名时优先使用局部变量,不建议滥用。
- 数组传递本质是传递首地址,函数内修改会影响外部数组;整型数组建议传数组名 + 长度,字符串可依靠
\0判断结束。
递归函数:
- 核心是函数调用自身,必须同时具备结束条件和递推公式。
- 斐波那契数列是递归经典案例,深层递归存在栈溢出风险。
预处理命令:
- 宏定义是纯文本替换,不做计算、不检查类型;带参宏建议给参数和整体加括号,避免优先级问题。
- 带参宏执行效率高但会增大代码体积,适合短小逻辑;函数适合复杂功能逻辑。
- 条件编译可按需编译代码,
#ifndef最常用于头文件防重复包含。 <>用于包含系统头文件,""用于包含自定义头文件,二者查找路径不同。