以下是C语言函数相关知识点的总结,涵盖你提到的各个小节内容:
8.2.1 无参函数定义的一般形式
无参函数定义的一般形式为:
函数类型 函数名() {
函数体(声明部分;执行语句;)
}
- 函数名后的括号中无参数,表示主调函数与被调函数之间不传递数据。
- 若函数无返回值,函数类型用
void
声明(可省略,但建议显式声明)。 - 示例:
void printStar() { // 打印星号的无参函数 printf("*********\n"); }
8.2.2 有参函数定义的一般形式
有参函数定义的一般形式为:
函数类型 函数名(形式参数列表) {
函数体(声明部分;执行语句;)
}
- 形式参数列表:由“类型名 参数名”组成,多个参数用逗号分隔,用于接收主调函数传递的数据。
- 函数类型:指定函数返回值的类型,若为
void
则无返回值。 - 示例:
int max(int a, int b) { // 求两数最大值的有参函数 return a > b ? a : b; }
8.2.3 空函数
空函数是指函数体为空的函数,定义形式为:
函数类型 函数名(参数列表) { }
- 特点:不执行任何操作,仅占一个函数名。
- 作用:作为“占位符”,用于模块化设计中预留功能位置,便于后续扩展。
- 示例:
void func() { } // 空函数,暂未实现功能
8.3 函数参数和函数的值
8.3.1 形式参数和实际参数
-
形式参数(形参):
定义函数时括号中的参数,仅在函数内部有效,用于接收主调函数传递的值。函数调用结束后,形参所占内存被释放。 -
实际参数(实参):
调用函数时括号中的参数,可以是常量、变量、表达式等,必须有确定的值。
要求:实参与形参的数量、类型、顺序必须一一对应,否则会导致类型不匹配错误。 -
示例:
int add(int x, int y) { // x、y是形参 return x + y; } int main() { int a = 3, b = 5; int sum = add(a, b); // a、b是实参 return 0; }
8.3.2 函数的返回值
- 函数的返回值是被调函数执行后向主调函数传递的结果,通过
return
语句实现。 - 规则:
return
语句的一般形式:return 表达式;
或return;
(无返回值时)。- 函数返回值的类型由函数定义时的“函数类型”指定,若返回值类型与函数类型不一致,会自动转换为函数类型(可能导致精度损失)。
- 无返回值的函数(
void
类型)中,return
语句可省略,或仅用return;
表示提前结束函数。
8.4 函数的调用
8.4.1 函数调用的一般形式
函数调用的一般形式为:
函数名(实际参数列表); // 无参函数调用时,括号内为空
- 若函数有返回值,可将其作为表达式的一部分(如赋值给变量、参与运算等);无返回值的函数调用通常作为独立语句。
8.4.2 函数调用的方式
-
函数语句:将函数调用作为一条独立语句,不关注返回值,仅执行函数操作。
示例:printStar();
-
函数表达式:函数调用作为表达式的一部分,依赖返回值参与运算。
示例:int sum = add(3, 5) * 2;
-
函数参数:函数调用作为另一个函数的实参,此时返回值会传递给被调用函数。
示例:int maxVal = max(add(2, 3), 10);
8.4.3 被调用函数的声明和函数原型
- 函数声明:若被调函数定义在主调函数之后,需在主调函数中先声明被调函数,告知编译器函数的类型、参数数量和类型。
- 函数原型:声明的一般形式(两种等价):
函数类型 函数名(参数类型1, 参数类型2, ...); // 只声明参数类型 函数类型 函数名(参数类型1 参数名1, 参数类型2 参数名2, ...); // 声明参数类型和名称
- 作用:确保函数调用时参数类型匹配,避免编译错误。
- 示例:
// 函数声明(原型) int max(int, int); // 或 int max(int a, int b); int main() { int result = max(3, 5); // 调用前已声明,编译通过 return 0; } // 函数定义(在主调函数之后) int max(int a, int b) { return a > b ? a : b; }
8.5 函数的嵌套调用
C语言不允许函数嵌套定义,但允许嵌套调用(一个函数调用另一个函数,被调函数再调用其他函数)。
- 执行流程:主调函数暂停执行,进入被调函数;被调函数执行完毕后,返回主调函数继续执行。
- 示例:
void func2() { printf("This is func2\n"); } void func1() { printf("Calling func2...\n"); func2(); // func1嵌套调用func2 } int main() { func1(); // main调用func1 return 0; }
8.6 函数的递归调用
函数的递归调用是指函数在执行过程中直接或间接调用自身。
- 核心条件:
- 递归终止条件:当满足某个条件时,停止递归(否则会无限递归导致栈溢出)。
- 递归表达式:将原问题分解为与原问题结构相似的子问题,逐步简化。
- 示例(计算n的阶乘):
int factorial(int n) { if (n == 1) // 终止条件:n=1时返回1 return 1; else return n * factorial(n - 1); // 递归调用:n! = n × (n-1)! }
8.7 数组作为函数参数
8.7.1 数组元素作为函数实参
数组元素可作为实参传递给函数,用法与普通变量相同,属于值传递(将元素值传给形参,形参修改不影响原数组元素)。
- 示例:
void printNum(int x) { printf("%d ", x); } int main() { int arr[] = {1, 2, 3}; printNum(arr[0]); // 数组元素arr[0]作为实参 return 0; }
8.7.2 数组名作为函数参数
数组名作为实参传递时,传递的是数组首元素的地址(而非整个数组),属于地址传递(形参可通过地址修改原数组元素)。
- 规则:
- 形参需声明为数组形式(如
int arr[]
)或指针形式(如int *arr
)。 - 通常需额外传递数组长度作为参数(因数组名传递的是地址,函数无法直接获取数组长度)。
- 形参需声明为数组形式(如
- 示例:
// 形参为数组形式,接收数组首地址 void printArray(int arr[], int len) { for (int i = 0; i < len; i++) { printf("%d ", arr[i]); } } int main() { int arr[] = {1, 2, 3, 4}; int len = sizeof(arr) / sizeof(arr[0]); printArray(arr, len); // 数组名arr作为实参 return 0; }
这是关于 C语言数组作为函数参数传递 的笔记,核心是讲数组传参时“长度丢失”问题及解决思路,整理如下:
一、核心问题:数组传参丢失长度信息
C语言中,数组作为函数参数传递时,数组名退化为指针(传递的是数组首元素地址 ),函数内部无法直接通过数组参数获取原数组的元素个数(丢失长度信息 )。
示例代码逻辑:
// 函数声明,意图接收数组并遍历打印
void printArray(int a[10]) {
// 这里a实际是指针,sizeof(a) 不再是数组总字节数,而是指针自身大小(如64位系统占8字节 )
int len;
// 错误思路:试图用sizeof(a)/sizeof(a[0])算长度,结果失效
len = sizeof(a) / sizeof(a[0]);
for (int i = 0; i < len; i++) {
printf("%d ", a[i]);
}
}
int main(void) {
int a[10] = {1,2,3,4,5,6,7,8,9,10};
// 调用函数传数组,实际传递的是数组首地址
printArray(a);
return 0;
}
问题表现:
- 函数内用
sizeof(a)/sizeof(a[0])
计算长度失效,因为a
已退化为指针(比如64位系统sizeof(a)
是8,而非数组总字节数10*4=40
)。 - 若按错误长度遍历,可能导致访问越界(比如误判长度为2,只打印前2个元素 )。
二、解决方法:主动传递数组长度
由于数组传参丢失长度,需手动将长度作为参数传递,让函数明确数组元素个数,常见两种方式:
方式1:单独传长度参数
void printArray(int a[], int len) {
for (int i = 0; i < len; i++) {
printf("%d ", a[i]);
}
}
int main(void) {
int a[10] = {1,2,3,4,5,6,7,8,9,10};
// 主动传数组长度10
printArray(a, 10);
return 0;
}
方式2:指针传参(进阶,通过地址回传长度 )
若需函数内部修改长度相关逻辑,或主调函数也需动态获取,可通过指针间接传递长度(类似“双向传参” ),不过日常场景用方式1更直接。
三、延伸:字符数组(字符串)传参同理
字符数组(字符串)作为函数参数时,也会退化为指针,sizeof
无法直接获取字符串长度(需用 strlen
或手动传长度 )。
示例:
// 错误用法:sizeof(s) 是指针大小,不是字符串长度
void printStr(char s[]) {
// 正确用strlen(s),需包含<string.h>
int len = strlen(s);
for (int i = 0; i < len; i++) {
printf("%c ", s[i]);
}
}
int main(void) {
char s[] = "Hello";
printStr(s);
return 0;
}
四、总结关键知识点
- 数组传参本质:数组名退化为指针,传递首元素地址,丢失长度信息。
- 长度获取失效:函数内用
sizeof(数组参数)
无法得到原数组总字节数,需手动传长度。 - 通用解决:调用函数时主动传递数组长度(如
printArray(a, 10)
),让函数明确遍历/操作边界。 - 字符数组补充:字符串长度用
strlen
(需<string.h>
),或同样手动传长度,避免sizeof
误用。
手写笔记的知识点总结与补充:
一、内存分区(RAM 相关)
- 栈区(stack)
- 特点:空间大小有限,Linux 下一般 8MB(
1024*1024*8
字节 ),Windows 下一般 1MB 左右;用于存储函数调用时的局部变量、函数参数、返回地址等,遵循 “先进后出(FILO)” 原则,函数调用结束自动释放空间。 - 相关补充:栈溢出(stack overflow),当递归调用过深、局部数组过大等,会耗尽栈空间,导致程序崩溃 。
- 特点:空间大小有限,Linux 下一般 8MB(
- 堆区(heap)
- 特点:动态申请和释放内存(如 C 中
malloc
、C++ 中new
系列操作),空间相对灵活,若不手动释放(如 C 中未free
、C++ 中未delete
),可能造成内存泄漏。 - 补充说明:堆内存管理涉及内存分配算法(如伙伴系统等 ),频繁动态分配释放可能产生内存碎片 。
- 特点:动态申请和释放内存(如 C 中
- 字符串常量区
- 说明:存储字符串常量(如
char* s = "Hello";
里的"Hello"
),内容只读,程序结束由系统回收空间 。
- 说明:存储字符串常量(如
- 代码区
- 说明:存储程序的二进制机器指令,由操作系统加载,只读,保障程序指令安全执行 。
- 静态区(全局区)
- 说明:存储全局变量、静态变量(
static
修饰的变量 ),程序运行期间一直存在,程序结束由系统回收,全局变量默认初始化为 0,静态变量也有相应初始化规则 。
- 说明:存储全局变量、静态变量(
二、函数递归调用
- 核心要点
- 函数直接或间接调用自身;需有递归终止条件(否则会因不断入栈导致栈溢出,比如无限制递归调用使栈区装满,不会像循环可能死循环,而是直接 “栈溢出” 崩溃 );通过递归逐步逼近终止条件求解问题(像计算
1+2+…+n
,递归公式f(n) = f(n - 1) + n
,终止条件n == 1
时返回 1 )。 - 补充:递归调用过程中,每次递归调用都会在栈区创建新的栈帧(保存当前函数的参数、局部变量、返回地址等 ),递归返回时依次出栈恢复现场 。
- 函数直接或间接调用自身;需有递归终止条件(否则会因不断入栈导致栈溢出,比如无限制递归调用使栈区装满,不会像循环可能死循环,而是直接 “栈溢出” 崩溃 );通过递归逐步逼近终止条件求解问题(像计算
三、函数参数传递
- 值传递
- 示例:
i = 20; foo(i);
,函数foo
内对参数的修改不影响主调函数里的i
,因为传递的是i
的值拷贝,形参和实参各自独立内存空间 。 - 补充:要修改主调函数变量,需用指针传递(传递变量地址 )或引用传递(C++ 特性 )。
- 示例:
- 指针传参
- 说明:主调函数向被调函数传递变量地址,被调函数通过指针操作可修改主调函数对应变量的值,实现数据回传,解决值传递无法修改实参的问题 。
- 补充:指针传参要注意指针的有效性(避免空指针、野指针操作 )。
- 参数传递顺序
- 提到 “主调函数向被调函数传参,自右向左传参” ,比如函数调用
func(a, b, c);
实际传递顺序是先处理c
相关、再b
、再a
(和编译器实现的参数入栈等机制有关 )。
- 提到 “主调函数向被调函数传参,自右向左传参” ,比如函数调用
四、编译相关(以 GCC 为例 )
- debug 编译与 release 编译
gcc -o test test.c -g
:-g
选项生成带调试信息的可执行文件,方便用gdb
等调试工具调试;release
编译一般会开启优化(如gcc -O2 -o test test.c
,-O2
是优化级别 ),去除调试信息,让程序运行更高效 。
五、指令与程序计数器(PC)
- PC(Program Counter ,程序计数器 )
- 作用:存放下一条要执行指令的地址,CPU 依据它取指令、执行指令,实现程序的顺序执行、分支跳转、函数调用返回等流程;函数调用时,PC 会指向被调函数入口地址,函数返回时恢复主调函数后续指令地址,配合栈操作(保护现场、恢复现场 )保障程序调用流程 。
- 补充:在多任务操作系统中,进程切换时 PC 等寄存器状态会被保存(上下文保存 ),恢复进程执行时再恢复这些状态 。
课上代码:
#include<stdio.h>
#if 0
//用递归计算累乘:
int fn(int n)
{
if(n==1)
{
return 1;
}
else
{
return fn(n-1)*n;
}
}
int main(void)
{
printf("%d\n",fn(10));
return 0;
}
#endif
- 递归计算累乘(阶乘)
功能:使用递归算法计算整数n的阶乘(n! = n × (n-1) × … × 1)。
实现逻辑:
递归终止条件:当n=1时,返回 1(1! = 1)。
递归关系:n! = n × (n-1)!,通过调用自身计算fn(n-1)并与n相乘。
示例:主函数调用fn(10),输出10!的结果(3628800)。
#if 0
//用递归计算斐波那契数列:
int fn(int n)
{
if(n==1||n==2)
{
return 1;
}
else
{
return fn(n-1)+fn(n-2);
}
}
int main(void)
{
printf("%d\n",fn(8));
return 0;
}
#endif
- 递归计算斐波那契数列
功能:使用递归算法计算斐波那契数列的第n项(数列定义:F(1)=1,F(2)=1,F(n)=F(n-1)+F(n-2))。
实现逻辑:
终止条件:当n=1或n=2时,返回 1。
递归关系:F(n) = F(n-1) + F(n-2),通过递归调用前两项求和。
示例:主函数调用fn(8),输出第 8 项结果(21)。
#if 0
//求一维整型数组的总值:
int sumofTheArray(int a[],int len)
{
int i;
int sum=0;
for(i=0;i<len;++i)
{
sum+=a[i];
}
return sum;
}
int main(void)
{
int a[]={1,2,3,4,5,6,7,8,9,10};
int len=sizeof(a)/sizeof(a[0]);
printf("%d\n",sumofTheArray(a,len));
return 0;
}
#endif
- 求一维整型数组的总和 • 功能:计算一维整型数组所有元素的总和。 • 实现逻辑: ◦ 定义变量sum初始化为 0,遍历数组并累加每个元素到sum。 ◦ 函数参数为数组和长度,返回累加结果。 • 示例:数组{1,2,…,10}的总和为 55,主函数输出 55。
#if 0
//查找一维整型数组的最大值:
int maxofTheArray(int a[],int len)
{
int i;
int max=a[0];
for(i=1;i<len;++i)
{
max=max<a[i]?a[i]:max;
}
return max;
}
int main(void)
{
int len;
int a[]={1,2,3,4,9,7,16};
len=sizeof(a)/sizeof(a[0]);
printf("%d\n",maxofTheArray(a,len));
return 0;
}
#edif
- 查找一维整型数组的最大值
功能:找出一维整型数组中的最大元素。
实现逻辑:
假设第一个元素为最大值(max = a[0]),遍历数组剩余元素,若遇到更大值则更新max。
函数返回最终的max。
示例:数组{1,2,3,4,9,7,16}的最大值为 16,主函数输出 16。
//一维数组的逆序函数:
int reverse(int a[],int len)
{
int i;
for(i=0;i<len/2;++i)
{
int t=a[i];
a[i]=a[len-1-i];
a[len-1-i]=t;
}
}
int printarry(int a[],int len)
{
int i;
for(i=0;i<len;++i)
{
printf("%d\n",a[i]);
}
}
int main(void)
{
int len;
int a[]={1,2,3,4,5};
len=sizeof(a)/sizeof(a[0]);
reverse(a,len);
printarry(a,len);
return 0;
}
#endif
- 一维数组的逆序
功能:将一维整型数组的元素反转(首尾交换)。
实现逻辑:
遍历数组前半部分(i从 0 到len/2 - 1),将a[i]与对称位置的a[len-1-i]交换。
函数无返回值,直接修改原数组。
示例:数组{1,2,3,4,5}逆序后为{5,4,3,2,1},主函数调用打印函数输出结果。
#if 0
//利用二分法查找一维整型数组元素:
int choiceSort(int a[],int len)
{
int i;
for(i=0;i<len-1;++i)
{
int j;
for(j=i+1;j<len;++j)
{
if(a[i]>a[j])
{
int t=a[i];
a[i]=a[j];
a[j]=t;
}
}
}
}
void printarry(int a[],int len)
{
int i;
for(i=0;i<len;++i)
{
printf("%d",a[i]);
}
printf("\n");
}
int binaryFind(int a[],int len,int n)
{
int begin=0;
int end=len-1;
while(begin<=end)
{
int mid=(begin+end)/2;
if(n>a[mid])
{
begin=mid+1;
}
else if(n<a[mid])
{
end=mid-1;
}
else
{
break;
}
}
if(begin<=end)
{
return n;
}
else
{
return -1;
}
}
void printfIfFound(int n)
{
if(n==-1)
{
printf("nofound");
}
else
{
printf("%d\n",n);
}
}
int main(void)
{
int a[]={1,8,2,9,4,6};
int len=sizeof(a)/sizeof(a[0]);
choiceSort(a,len);
printarry(a,len);
int n=6;
printfIfFound(binaryFind(a,len,n));
return 0;
}
#endif
- 利用二分法查找一维整型数组元素
功能:先对数组排序,再用二分法查找指定元素是否存在。
实现逻辑:
排序:使用选择排序(choiceSort)对数组升序排列(每次选最小元素放到前面)。
二分查找:在有序数组中,通过不断缩小查找范围(begin和end指针),判断中间元素与目标值的大小,直到找到或范围无效。
查找成功返回目标值,失败返回 - 1,主函数通过printfIfFound打印结果。
示例:数组{1,8,2,9,4,6}排序后为{1,2,4,6,8,9},查找6时输出 6,查找不存在元素时输出nofound。
#if 0
//判断一维字符型数组的有效长度:
int strlen(char s[])
{
int i;
while(s[i]!='\0')
{
++i;
}
return i;
}
int main(void)
{
char s[]="hello";
printf("%d\n",strlen(s));
return 0;
}
#endif
- 判断一维字符型数组的有效长度(模拟strlen)
功能:计算字符串(字符数组)的有效长度(不含结束符’\0’)。
实现逻辑:
遍历字符数组,直到遇到’\0’停止,计数器i的值即为长度。
函数返回i。
示例:字符串"hello"的长度为 5,主函数输出 5。
#if 0
//一维字符型数组的拼接:
void Strcat(char dest[],char src[])
{
int i=0,j=0;
while(dest[i])
{
++i;
}
while(src[j])
{
dest[i++]=src[j++];
}
dest[i]='\0';
}
int main(void)
{
char dest[100]="hello";
char src[100]="world";
Strcat(dest,src);
puts(dest);
return 0;
}
#endif
- 一维字符型数组的拼接(模拟strcat)
功能:将源字符串(src)拼接到目标字符串(dest)的末尾。
实现逻辑:
先遍历dest找到结束符’\0’的位置(i记录),再将src的字符从该位置开始复制到dest。
最后在dest末尾添加’\0’,确保字符串有效。
#if 0
//将两个字符型一维数字比较大小
int strcmp(char s1[],char s2[])
{
int i=0;
while(s1[i]==s2[i]&&s1[i]&&s2[i])
{
++i;
}
return s1[i]-s2[i];
}
void printcmp(int n)
{
if(n>0)
{
printf("s1>s2\n");
}
else if(n<0)
{
printf("s1<s2\n");
}
else
{
printf("s1=s2\n");
}
}
int main(void)
{
char s1[100]="hello";
char s2[100]="world";
printcmp(strcmp(s1,s2));
return 0;
}
#endif
- 比较两个字符型数组的大小(模拟strcmp)
功能:按字典序比较两个字符串的大小。
实现逻辑:
遍历两个字符串,直到遇到不同字符或其中一个结束。
返回两个字符串第一个不同字符的 ASCII 码差值(s1[i] - s2[i]):正数表示s1 > s2,负数表示s1 < s2,0 表示相等。
示例:"hello"与"world"比较,第一个不同字符’h’(104)小于’w’(119),输出s1 < s2。
#if 0
//字符型一位数组的复制:
void strcpy(char dest[],char src[])
{
int i=0;
while(src[i])
{
dest[i]=src[i];
++i;
}
dest[i]='\0';
}
int main(void)
{
char dest[100];
char src[100]="hello";
strcpy(dest,src);
puts(dest);
return 0;
}
#endif
- 字符型数组的复制(模拟strcpy)
功能:将源字符串(src)复制到目标字符串(dest)。
实现逻辑:
遍历src的每个字符,依次复制到dest的对应位置,直到src的结束符’\0’。
最后在dest末尾添加’\0’,确保字符串完整。
示例:src为"hello",复制后dest也为"hello",主函数输出dest。
#if 0 //将字符串转换为整数。
#include<string.h>
int atoi(char s[])
{
int i=0;
int n=0;
while(s[i]!='\0')
{
n=n*10+s[i]-'0';
++i;
}
return n;
}
int main(void)
{
char s[100]="1234";
scanf("%s",&s[0]); //或者用scanf("%s",s)
printf("%d\n",atoi(s));
return 0;
}
#endif
- 将字符串转换为整数(模拟atoi)
功能:将数字字符串(如"1234")转换为对应的整数(1234)。
实现逻辑:
遍历字符串的每个字符,通过公式n = n × 10 + (s[i] - ‘0’)累加(s[i] - '0’将字符转换为数字)。
函数返回转换后的整数。
示例:字符串"1234"转换为整数 1234,主函数输出 1234。 - 冒泡排序
//冒泡排序
#if 0
int fn(int a[],int len)
{
int i,j;
for(j=len-1;j>0;--j)
{
for(i=0;i<j;++i)
{
if(a[i]>a[i+1])
{
int t=a[i];
a[i]=a[i+1];
a[i+1]=t;
}
}
}
}
void printarry(int a[],int len)
{
int i;
for(i=0;i<len;++i)
{
printf("%d",a[i]);
}
printf("\n");
}
int main(void)
{
int a[]={1,7,4,2,9,0};
int len=sizeof(a)/sizeof(a[0]);
fn(a,len);
printarry(a,len);
return 0;
}
#endif
- 冒泡排序
功能:对一维整型数组进行升序排序(冒泡排序算法)。
实现逻辑:
外层循环控制排序轮次(j从len-1到 1),内层循环遍历未排序部分(i从 0 到j-1)。
若a[i] > a[i+1],则交换两元素,每轮将最大元素 “冒泡” 到末尾。
示例:数组{1,7,4,2,9,0}排序后为{0,1,2,4,7,9},主函数输出排序结果。
//#if 0 //插入法排序
void IS(int a[],int len)
{
int i;
for(i=0;i<len;++i)
{
int j=i;
int t=a[i];
while(j>0&&a[j-1]>t)
{
a[j]=a[j-1];
--j;
}
a[j]=t;
}
}
void printarry(int a[],int len)
{
int i;
for(i=0;i<len;++i)
{
printf("%d",a[i]);
}
printf("\n");
}
int main(void)
{
int a[]={1,7,4,2,9,0};
int len=sizeof(a)/sizeof(a[0]);
IS(a,len);
printarry(a,len);
return 0;
}
//#endif
- 插入排序
功能:对一维整型数组进行升序排序(插入排序算法)。
实现逻辑:
从第二个元素开始(i从 1 到len-1),将当前元素a[i]视为 “待插入元素”。
与前面已排序部分比较,若前面元素更大则后移,直到找到合适位置插入a[i]。
示例:数组{1,7,4,2,9,0}排序后为{0,1,2,4,7,9},主函数输出排序结果。