计算机中的函数,也叫做子程序,是一个大型程序中的某部分代码,由一个或多个语句块组成。函数负责某项特定功能,相较于其他代码具备相对的独立性。一般会有输入参数并有返回值,提供对过程的封装和细节的隐藏,通常被集成为软件库。有库函数和自定义函数两大类。
·一个简单实现加法功能的自定义函数:
#include<stdio.h>
int Add(int x, int y)
{
int z = 0;
z = x + y;
return z;
}
int main()
{
int a = 10;
int b = 20;
int sum=Add(a, b);
printf("%d\n", sum);
return 0;
}
库函数
由C语言本身提供的一些较为常用的基础功能,例如printf打印、strcpy拷贝、pow运算等等,在开发的过程中每个程序员都可能用到,方便程序员们使用。
- IO函数(Input/Output,输入输出函数):stdio.h
- 数学函数:math.h
- 字符处理函数:ctype.h
- 字符串处理函数:string.h
·strcpy函数使用示例
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "sample string";
char arr2[40];
char arr3[40];
strcpy (arr2, arr1);//将arr1的字符串拷贝给arr2
strcpy (arr3, "copy successful");
//将字符串copy successful拷贝给arr3
printf("arr1:%s\narr2:%s\narr3:%s\n",arr1,arr2,arr3);
return 0;
}
·memset函数使用示例
#include<stdio.h>
#include<string.h>
int main()
{
char arr[] = "sample string";
//memset--memory set内存设置
//memset(被填充的内存块,'值',num个数)
memset(arr, '*', 6);
//将arr的前6个字节的内容设置为'值'
//这里叫值是因为任何字符被存储都会转换为ASCII码的值,int类型
printf("arr:%s\n",arr);
return 0;
}
- 功能函数:stdlib.h
- 时间日期函数:time.h
自定义函数
当我们需要使用一些功能但库函数中不存在的,我们便可以用代码编写自定义函数,方便我们使用。
如果我们创建的自定义函数不需要返回值则需要用到void定义函数类型。
实际参数是指传给函数的参数,可以是常量、变量、表达式甚至是函数。实参在进行函数调用时,必须有确定的值。
形式参数是指自定义函数后面的括号中的变量,在函数被调用时才会创建,分配内存单元,函数调用完成后就自动销毁。因此,形参只在函数中有用。可以理解为,形参是实参的临时拷贝。
形参跟实参是可以相同名字的,拥有各自的内存空间。形参在函数开始时创建,结束销毁。
返回类型 函数名(函数参数)
{
语句;
}
ret_type function_name(paral,* )
{
statement;
}
·示例,比较两个数的大小:
#include<stdio.h>
int get_max (int x,int y)
{
//return(x>y)?(x):(y);
if (x > y)
return x;
else
return y;
}
int main()
{
int a = 10;
int b = 20;
int max = get_max(a, b);
printf("max = %d\n", max);
return 0;
}
传值调用函数,形参和实参拥有各自的内存空间,也就是说,改变形参并不会改变实参。
那么,可以结合指针的运用,传地址调用函数,把被调用的变量的内存地址作为实参传递给函数的形参,让函数与被调用的变量建立联系,就可以在函数内部改变被调用的变量。
数组传参传递的是数组首元素的地址。
·示例,交换两个数的值(错误的,不可行的):
#include<stdio.h>
void swap(int x,int y)//&x:0x00affb30,&y:0x00affb34
{
int tmp = 0;
tmp = x;
x = y;
y = tmp;
//虽然x,y成功交换了值
//但是,a和b的并没有发生改变
}
int main()
{
int a = 10;//&b:0x00affc12
int b = 20;//&b:0x00affc08
printf("%d %d\n", a, b);
swap(a, b);//x,y收到a,b的值
printf("%d %d\n", a, b);
return 0;
}
(可行的):
#include<stdio.h>
void swap2(int* pa, int* pb)
//pa:0x00affc12,pb:0x00ffc08
{
int tmp = 0;
tmp = *pa;
*pa = *pb;
*pb = tmp;
//通过指针的运用,交换*pa和*pb的值
}
int main()
{
int a = 10;//&a:0x00affc12
int b = 20;//&b:0x00affc08
printf("%d %d\n", a, b);
swap2(&a,&b);//a的地址中的值和b
printf("%d %d\n", a, b);
return 0;
}
(更详细的):
#include<stdio.h>
void swap2(int* x, int* y)
//x收到指针pa的地址:0x00affc12
//y收到指针pb的地址:0x00affc08
{
int tmp = 0;
//通过地址的解引用,交换两个地址内的值
tmp = *x;
*x = *y;
*y = tmp;
//
}
int main()
{
int a = 10;//&a:0x00affc12
int b = 20;//&b:0x00affc08
//为了方便理解,多此一举
int* pa = &a;
int* pb = &b;
printf("%d %d\n", a, b);//10 20
swap2(pa,pb);
//通过函数,a和b的内存空间内的值已被交换
printf("%d %d\n", a, b);//20 10
return 0;
}
·打印100-200的素数(函数):
#include<stdio.h>
#include<math.h>
int prime(int x)
{
int y = 0;
for (y = 2; y < x; y++)
{
if (x%y == 0)
return 0;//若不是素数,直接结束本次函数的调用
//break;//若不是素数,只结束本次循环
}
//如果y=x,说明是素数,那么我们直接返回1
return 1;
}
int main()
{
int i = 0;
for (i = 100; i <= 200; i++)
{
if (prime(i) == 1)
printf("%d ", i);
}
return 0;
}
·打印出1000-2000中的闰年(函数):
#include<stdio.h>
int leap_year(int i)
{
if (((i % 4 == 0) && (i % 100 != 0)) || (i % 100 == 0))
return 1;
else
return 0;
}
int main()
{
int y = 0;
for (y = 1000; y <= 2000; y++)
{
if(leap_year(y) == 1)
printf("%d ", y);
}
return 0;
}
·实现一个整形有序数组的二分查找:
#include<stdio.h>
int binary_search(int arr[],int k,int sz)
//形参跟实参是可以相同名字的,拥有各自的内存空间
//形参在函数开始时创建,结束销毁
{
int left = 0;
int right = sz - 1;
while (left<=right)//二分查找循环体
{
int mid = (left + right) / 2;
if (arr[mid] < k)
{
left = mid + 1;
}
else if (arr[mid] > k)
{
right = mid - 1;
}
else
{
return mid;
}
}
return -1;
}
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,10};
int k = 7;
int sz = sizeof(arr) / sizeof(arr[0]);
int ret = binary_search(arr, k,sz);
if (ret == -1)
{
printf("找不到\n");
}
else
{
printf("找到了,下标:%d\n", ret);
}
return 0;
}
函数的嵌套调用和链式访问
嵌套调用
链式访问
把一个函数的返回值作为另一个函数的参数。
#include<stdio.h>
int main()
{
//printf()返回值是所打印字符的个数
printf("%d",printf("%d",printf("%d",43)));
//打印结果:4321
return 0;
}
函数的声明和定义
函数声明:告诉编译器一个函数返回类型是什么,叫什么,参数是什么。具体是否存在无关紧要。函数的声明一般出现在函数的使用前,先声明后使用。函数的声明一般放在头文件中。
函数的定义是指函数的具体实现,交代函数的功能实现。
函数递归
递归指的是函数调用自身的行为,通常把一个大型复杂的问题层层转化为一个与原问题相似但规模较小的问题来求解,递归主要就是大事化小。
递归的两个必要条件:存在限制条件,当满足条件时,递归不再继续;每次递归调用后越来越接近限制条件。
一个简单的递归:
int main()
{
printf("hello\n");//无限循环打印hello直到栈溢出
main();
return 0;
}
·输入一个整型值(无符号),按顺序打印它的每一位:
#include<stdio.h>
void print(int n)
//通过第一次调用n=123
//通过第二次调用n=12
//通过第三次调用n=1
{
if (n > 9)
{
print(n / 10);
//第二次调用传出12
//第三次调用传出1
}
printf("%d ", n % 10);
//根据第三次调用n=1,打印1,并且第三次调用结束
//回到第二次调用继续执行,打印2,并且第二次调用结束
//回到第一次调用继续执行,打印3,并且第一次调用结束
}
int main()
{
unsigned int num = 0;
scanf("%d",&num);//123
print(num);
//第一次调用传出123
return 0;
}
用之前的嵌套帮助理解:
printf("%d",printf("%d",printf("%d",43)));
//p0 p1 p2 p3
//按顺序执行代码
//p0执行,打印p1的值,此时p1还是未知
//那么为了执行p0就得先执行p1,打印p2的值p3:43
//返回值p2=2,p1执行打印:2
//再返回p1=1,p0执行打印:1
就像盗梦空间,首先为了目的0进入第一层梦境中,又为了目的1而进入第二层梦境,为了目的2而进入第三层梦境,第三层的任务目的3。目的3完成后自然要回到第二层梦境继续任务,接着目的2完成了便回到第一层梦境继续,接着目的1也完成了便结束梦境,回到现实世界继续完成目的0。
·利用函数计算字符串长度
#include<stdio.h>
#include<string.h>
int my_strlen(char* str)
{
int count = 0;
while (*str != '\0')
{
count++;
str++;
}
return count;
}
int main()
{
char arr[] = "abc";//'a','b','c','\0'
int len = my_strlen(arr);
//数组传参,传递的是第一个元素的地址
printf("len= %d\n",len);
return 0;
}
·利用函数计算字符串长度(递归)
#include<stdio.h>
#include<string.h>
int my_strlen(char* str)
//通过第一次调用str=0x007dfd04,*str='a'
//通过第二次调用str=0x007dfd05,*str='b'
//通过第三次调用str=0x007dfd06,*str='c'
//通过第四次调用str=0x007dfd07,*str='\0'
{
if (*str != '\0')
return 1 + my_strlen(str + 1);
//第二次调用传出0x007dfd05('b'),返回0+1+1
//第三次调用传出0x007dfd06('c'),返回0+1
//第四次调用传出0x007dfd07('\0'),返回0
else
return 0;
}
int main()
{
char arr[] = "bit";
int len = my_strlen(arr);
//传递数组,传递的是第一个元素的地址
//第一次调用传出0x007dfd04('a'),返回0+1+1+1
printf("len= %d\n",len);
return 0;
}
·求n的阶乘
#include<stdio.h>
int fac1(int n)
{
int i = 0;
int ret = 1;
//for循环
for (i = 1; i <= n; i++)
{
ret *= i;
}
return ret;
}
int fac2(int n)
{
//递归
if (n <= 1)
return 1;
else
return n*fac2(n - 1);
}
int main()
{
int n = 0;
int ret = 0;
scanf("%d", &n);
ret = fac2(n);
printf("%d \n",ret);
return 0;
}
·斐波那契数列
·求第n个斐波那契数(递归):
#include<stdio.h>
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;
}
·求第n个斐波那契数(循环):
#include<stdio.h>
int fib(int n)
{
//斐波那契数列
//1 1 2 3 5 8 13 21
//a b c
// a b c
// a b c
//..........
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);
ret = fib(n);
printf("ret= %d\n", ret);
return 0;
}
递归版容易造成栈溢出,且效率低。