在C语言中,函数可以按照功能和用途进行分类。主要分为:标准库函数、用户自定义函数、主函数、递归函数、内联函数。
一、常用库函数
- IO函数
- 字符串操作函数
- 字符操作函数
- 内存操作函数
- 数学函数
- 时间/日期函数
- 其他库函数
这里介绍三个常用的字符串操作函数:strcpy()函数、strlen()函数、memset()函数
它们都在<string.h>头文件中声明。
1.strcpy()函数
将一个字符串复制到另一个字符串中。
原型:char *strcpy(char *dest , const char *src);
dest--目标字符串的指针
src--源字符串的指针
dest指向的字符串必须足够大,以容纳复制的字符串。否则,该函数可能会导致缓冲区溢出的问题。另外,源字符串必须以‘\0’结束,否则该函数可能会导致未定义的行为。
const关键字可以确保函数在执行时不会修改src指向的字符串,提高代码的安全性和可读性。
当目标字符串的长度大于源字符串的长度时:
strcpy()函数会将源字符串的所有内容复制到目标字符串中,并在源字符串的末尾添加一个结束符‘\0’。
目标字符串中剩余部分将保留原来的内容,不会被修改!
2.strlen()函数
计算字符串的长度,即字符串中字符的个数(不包括结尾的空字符 '\0')。
默认返回无符号整数类型(unsigned)。
原型:size_t strlen(const char *str);
参数说明:
str
:要计算长度的字符串,类型为const char*
。
返回值:
size_t
类型,表示字符串的长度。
辨析strlen()函数与sizeof操作符 :
1.sizeof--计算数组所占空间大小
2.strlen()--计算字符串长度
- 两者并没有什么关联
- strlen()只用于求字符串长度 -- 属于库函数
- sizeof计算变量、数组、类型的大小 -- 单位是字节 -- 属于操作符
为了确保strlen运行结果不是随机值,应该在字符数组末尾添加空字符‘\0’,以确保字符串的结束。
3.memset()函数
用于对内存块进行初始化、清空或设置特殊值的操作。
原型:void *memset(void *ptr, int value, size_t num);
参数解释:
- ptr:指向要设置的内存块的指针。
- value:要设置的值,以整数形式表示,通常是无符号字符或整数。
- num:要设置的字节数
二、自定义函数
程序员根据自己的需求和逻辑编写的函数。
自定义函数由函数头和函数体组成。
- 函数头包含函数的返回类型,函数名和参数列表。
- 函数体包含了函数的具体实现,即一系列的语句和操作。
下面是一个简单的自定义函数的示例:
#include <stdio.h>
// 自定义函数,计算两个整数的和
int addNumbers(int a, int b) {
int sum = a + b;
return sum;
}
int main() {
int num1 = 5;
int num2 = 7;
int result = addNumbers(num1, num2);
printf("The sum of %d and %d is %d\n", num1, num2, result);
return 0;
}
在这个示例中,我们定义了一个名未addNumbers的自定义函数,用于计算两个整数的和。
函数头中的int表示函数的返回类型为整数,addNumbers是函数的名称,参数列表(int a,int b)指定了函数接收两个整数类型的参数a和b。
注意:
在函数内部,对参数的修改不会影响到函数外部的实际参数。
正确的做法是:使用指针
- 通过引用传递来修改实际参数的值,而不仅仅在函数内部进行操作。
- 在原本a,b变量的内存地址上修改变量的值,使其在函数外仍然有效。
举例:
三、形参与实参
形参与实参是函数调用中的概念,用于在函数定义和函数调用之间传递数据。
形参其实是实参的副本。
在函数调用时,实参的值被复制到形参中,形参在函数内部使用的是这个复制的值,任何对形参的修改都不会影响到原始的实参。
-
实际参数(实参)
实际参数是函数调用时提供的值,真实传给函数的参数,叫实参。
实参可以是:常量、变量、表达式、函数等。在进行函数调用时,它们必须要有确定的值,以便把这些值传给形参。
-
形式参数(形参)
形式参数是函数定义中声明的参数,指函数名后括号内的变量。
形式参数只会在函数被调用的过程中才实例化(分配内存单元)。当函数调用完成之后形参自动销毁,故形参只在函数中有效。
四、函数的调用
分为传值调用和传址调用。
-
传值调用
函数调用时,实参的值被复制到形参中。
-
传址调用
函数调用时,实参的地址被传递给形参。
那怎么选择呢?
传值调用适用于不需要修改实参的情况,传址调用适用于需要修改实参或者传递大型对象的情况。
五、函数的嵌套使用和链式访问
函数与函数之间也可以有机组合~
1.嵌套
#include <stdio.h>
void new_line()
{
printf("haha\n");
}
void three_line()
{
int i = 0;
for(i=0;i<3;i++)
{
new_line();//嵌套
}
}
int main()
{
three_line();
return 0;
}
2.链式访问
将一个函数的返回值作为另一个函数的参数。
int len = 0;
//1.原版
len = strlen("abc");
printf("%d\n",len);
//2.链式访问
printf("%d\n",strlen("abc"));
注意:
printf()函数的返回值为一个整数类型,表示成功打印的字符数。
六、函数的声明和定义
价值:
便于多人协作,提升效率
引用自己定义的头文件时,格式为:#include “xxx.h” (使用双引号)
函数的声明:
- 指在使用函数之前,提前声明函数的存在及其特征(函数名、参数类型和返回类型)。但是具体存不存在无关紧要。
- 函数的声明一般要出现在函数的使用之前,要满足先声明后使用。
- 函数的声明一般要放在头文件中。
函数的定义:
指函数的具体实现,交代函数的功能实现。
七、递归函数
递归:指一个函数在执行过程中调用自身的行为。
递归可以被看做一种自我调用的技术,它通过将一个大问题分解成多个小问题的解决方案来解决复杂的问题。
在计算机科学中,递归是一种非常常见的算法设计技巧,大大减小了代码量。(大事化小)
递归的两个必要条件
- 存在限制条件,当满足这个限制条件是,递归将不再继续。
- 每次递归调用后,越来越接近这个限制条件。
以下举两个例子来说明递归为什么好用:
例1:自定义strlen()函数
- 循环法
int my_strlen(char* str)
{
int count = 0;
while (*str != '\0')
{
count++;
str++;//指针变量str可以进行算术运算
}
}
int main()
{
char arr[] = "hello";
int len = my_strlen(arr);
print("len=%d\n",len);
return 0;
}
- 递归法
int my_strlen(char* str)
{
if (*str != '\0')
return 1 + my_strlen(str + 1);//递归
else
return 0;
}
int main()
{
char arr[] = "bit";
int len = my_strlen(arr);
printf("len=%d\n", len);
return 0;
}
例2: 计算斐波那契数列
- 循环法(当n>40时,计算机计算时间很长,且浪费大量资源)
int Fib(int n)
{
int a = 1;
int b = 1;
int c = 1;
while(n>2)
{
c = a + b;
a = b;
b = c;
n--;
}
return c;
}
int main()
{
int n = 0;
int ret =0;
scanf("%d",&n);//TDD-测试开发的思路
ret =Fib(n);
printf("ret = %d\n", ret);
return 0;
}
- 递归法
int Fib(int n)
{
if (n <= 2)
return 1;
else
return Fib(n - 1) + Fib(n - 2);
}
int main()
{
int n = 0;
int ret = 0;
scanf("%d",&n);
ret =Fib(n);
printf("ret=%d\n",ret);
return 0;
}
比较可得,递归法更简便,计算次数更少,节约了资源,并且将问题简化为了一个个的小问题,便于理解。
总结:
在使用某些方法计算时,需要考虑效率!比如求斐波那契数列时,当所求项过大,计算次数太多,计算难度高,循环法浪费资源和时间。