目录
1.函数是什么
- 维基百科中函数的定义是子程序。它负责完成某项特定任务,具备相对独立性。
- 一般会有输入参数与返回值,提供对过程的封装和细节的隐藏。
2.c语言中函数的分类
- 库函数
- 自定义函数
2.1库函数
- 编程语言提供的预定义函数,存储在库文件中,供人们在编写代码时直接调用使用。库函数封装了许多常用的功能,如输入输出操作、数学计算、字符串操作、内存管理等,不需要重新编写这些功能的具体实现
2.1.1strcpy
- 用法:
char* strcpy(*des,*src);
- 示例:
#include<stdio.h>
int main()
{
char arr1[20] = { 0 };
char arr2[] = { "人类就是愚蠢" };
strcpy(arr1, arr2);
printf("%s ", arr1);
return 0;
}
- 库函数的学习,可以参考链接https://cplusplus.com/,使用参考书学习。
- 我更推荐使用zh.cppreference.com中文网站学习。
2.2自定义函数
2.2.1函数的组成
ret_type func_name(para1 , * )//ret_type是返回类型,func_nume是函数名
{
statement;//语句项
}
其中函数的参数可以有一个、多个,也可以是0个。
实例1:写一个函数找到两个整数的较大值。
#include<stdio.h>
int get_max(int a, int b)
{
if (a >= b)
return a;
else
return b;
}
int main()
{
int i = 10;
int j = 20;
int res = 0;
res = get_max(i, j);
printf("最大数为%d ", res);
return 0;
}
2.2.2函数的调用
2.2.2.1实参与形参
实参
- 定义:实参是指在函数调用时实际传递给函数的值或变量。
- 作用:实参的值会传递给形参,作为函数执行的输入。
- 位置:出现在函数调用部分。
形参
- 定义:形参是指函数定义中用于接收值的变量,是函数的一部分,定义时写在函数名后面的括号中。
- 作用:形参起到占位符的作用,在函数被调用时用于接收传递进来的实参值,在函数调用结束后自动销毁。
- 位置:出现在函数定义部分。
在函数调用过程中,形参相当于对实参的拷贝,其运行结果对不会影响到实参!又称为传参不传址
2.2.2.2传值与传址
传值
- 定义:传值是指在调用函数时,将实参的值复制一份传递给形参。在函数内部,形参是实参值的副本,对形参的修改不会影响实参。
- 特点:函数内部操作的是值的副本,而不是原始变量本身。
传址
- 定义:传址是指在调用函数时,将实参的地址传递给形参,这样函数内部可以直接操作实参的内存地址,从而直接修改实参的值。
- 特点:函数内部操作的是原始变量的内存地址,修改会直接影响实参。
实例2:写一个函数交换两个整型变量的内容。
#include<stdio.h>
void change(int* pa, int* pb)
{
int reg = 0;
reg = *pa;
*pa = *pb;
*pb = reg;
}
int main()
{
int i = 10;
int j = 20;
printf("交换前i=%d,j=%d\n", i, j);
change(&i, &j);
printf("交换后i=%d,j=%d\n", i, j);
return 0;
}
实例3:写一个函数判断一个数是不是素数
#include<stdio.h>
#include<math.h>
#include<stdbool.h>
bool is_prime(int a)
{
int i = 2;
if (a % 2 == 0 || a == 1)
return false;
else
{
while (i < sqrt(a))
{
if (a % i == 0)//这里出现报错:”表达式不是一个可以修改的左值“,意味着我在对a进行赋值,显然是漏了一个”=“。
{
return false;
break;
}
i++;
}
if (i >= sqrt(a))
{
return true;
}
}
}
int main()
{
int num = 107;
if (is_prime(num) == false)
printf("no");
else if (is_prime(num) == true)
printf("yes");
return 0;
}
- 对代码的优化:不需要使用布尔类型,只需要让函数返回1和0,在打印时,if(sqrt(a)),即可自动判断真与假。
#include<stdio.h>
#include<math.h>
int is_prime(int a)
{
int i = 2;
if (a % 2 == 0 || a == 1)
return 0;
else
{
while (i < sqrt(a))
{
if (a % i == 0)//这里出现报错:”表达式不是一个可以修改的左值“,意味着我在对a进行赋值,显然是漏了一个”=“。
{
return 0;
break;
}
i++;
}
if (i >= sqrt(a))
{
return 1;
}
}
}
int main()
{
int num = 107;
if (is_prime(num))
printf("yes");
else
printf("no");
return 0;
}
2.2.3函数的嵌套调用
- 嵌套调用是指一个函数在其内部调用另一个函数的情况,或者函数在自身内部调用自身(递归)的情况。函数可以嵌套调用,但是不能嵌套定义。
2.2.4函数的链式访问
- 链式访问依赖函数的返回值,将函数的返回值作为另一个函数的参数
3.函数的声明与定义
3.1函数的声明
- 函数的声明一般出现在函数的使用之前,先声明后使用。
- 函数的声明一般要放在头文件里。库函数中的头文件使用尖括号<>,自己定义的头文件使用双引号""。
- 告诉编译器函数叫什么,参数是什么,返回类型是什么。参决定不了函数是否存在。
- 在使用函数的前方没有函数的定义时,需要声明函数。
#include "add.h"
3.2函数的定义
- 函数的定义时指函数的具体实现,交代函数的功能实现。
3.3函数的递归
什么是递归
- 程序调用自身的变成技巧称为递归。递归是一种方法,可以将复杂大规模问题层层转化为一个与原问题相似的简单问题。
实例1:编写一个函数,输出一个无符号整数的每一位。
#include<stdio.h>
#include<math.h>
//输入一个整数,按顺序打印它的每一位
typedef unsigned int uint;
void print(uint a)
{
if (a >= 10)
{
print(a / 10);
}
printf("%d", a % 10);
}
int main()
{
uint num = 1234;
print(num);
return 0;
}
递归的两个必要条件
- 存在限制条件,当条件满足时,递归不再继续。
- 每次递归调用后越来越接近这个条件。
实例2:编写函数,不创建临时变量,求字符串长度。
#include<stdio.h>
#include<math.h>
//编写函数不创建临时变量,求字符串长度。
int str_long(char* str)
{
if (*str != 0)
return 1 + str_long(str+1);//这里最开始错误的使用了str++,使得每层的形参值不变,产生死循环
else
return 0;
}
int main()
{
char arr[] = "abc";
printf("%d",str_long(arr));
return 0;
}
- 理解:一个巧妙的返回值递归,后一层函数的返回值嵌套在前一层函数,实现不需要临时变量count++的累计计数。同时巧妙地运用str+1来执行指针的位移。
实例3:实现一个数的阶乘计算。
#include<stdio.h>
#include<math.h>
//编写函数不创建临时变量,求字符串长度。
int fac(int a)
{
if (a > 1)
return a * fac(a - 1);
else
return 1;
}
int main()
{
int num = 4;
printf("%d",fac(num));
return 0;
}
实例4:计算第n个斐波那契数。
#include<stdio.h>
int fibo(int a)
{
if (a == 1||a == 2)
return 1;
else
return fibo(a-1)+fibo(a-2);
}
int main()
{
int num = 10;
printf("%d",fibo(num));
return 0;
}
- 函数的嵌套技巧性很强,需要多理解!!
补充知识点1:数组的初始化
- 数组初始化的性质:部分初始化后的数组,未初始化的数组元素会自动填充0,即‘\0’.注意,
- 在字符中,‘0’和0,即\0是不同的,常见的初始化数组为空的定义方式为:
char arr[20] = {0};
char arr[20] = {'a'};//第一个元素为a,其余为空字符.
补充知识点2:main()函数冲突
- 一个项目(多个.c文件中只能有一个main()函数)中只能有一个main函数
补充知识点3:常用的缩写变量名
- i, j, k - 通常用作循环控制变量(iterators)。
- n - 常用于表示数量(number)、大小(size)或计数(count)。
- ptr - 指针(pointer)的缩写。
- len - 表示长度(length)。
- idx - 索引(index)。
- tmp - 临时变量(temporary)。
- cnt - 计数(count)。
- buf - 缓冲区(buffer)。
- str - 字符串(string)。
- ch - 字符(character)。
- val - 值(value)。
- num - 数字(number)。
- max, min - 最大值和最小值。
- avg - 平均值(average)。
- sum - 求和(sum)。
- res - 结果(result)。
- flg - 标志位(flag)。
- arg - 参数(argument)。
- err - 错误(error)。
- src, dest - 来源和目标(source, destination)
补充知识点4:监测调试的方法
- 通过F10(step over)进入调试模式下“逐过程”执行代码,每次按下F10,程序就会执行下一步,需要注意的是,左侧的箭头是指程序下一步执行的语句,而不是已经执行完的语句。
- F10会自动跳过函数内部的执行细节,想要查看函数的运行,需要在箭头指向函数时,按下F11(step into),使执行跳转到函数内部。
补充知识点5:指针与地址
1.*代表解引用操作
- 作用:解引用一个指针,访问指针指向的变量的值。
- 使用场景:当你想要访问或修改指针所指向的内存位置上的值时,使用
*
操作符。
2.&代表取地址操作
- 定义:
&
是取地址操作符,用于获取变量的内存地址。 - 作用:它将返回一个指向该变量的指针,即该变量在内存中的位置。
- 使用场景:常用于指针初始化、传递参数给需要指针的函数等。
3.区分 *
的不同作用
- 声明指针时的
*
:在int *ptr;
中,*
只是用来定义ptr
是一个指针变量,而不是对ptr
的解引用。即*
用来表示ptr是一个指针变量 - 解引用操作时的
*
:当*
用在已经声明的指针变量前时(如*ptr
),才表示对该指针的解引用,即访问指针指向的内存中的值。
4.字符数组
- 在字符数组str[20]中,str是指向字符数组的首字符的指针。当对str执行str++;操作时,可以将指针指向下一个存储单元,也就是下一个字符。