文章目录
函数的概念
数学中我们其实就⻅过函数的概念,⽐如:⼀次函数 y=kx+b ,k和b都是常数,给⼀个任意的x,就得到⼀个y值。
其实在C语⾔也引⼊函数(function)的概念,有些翻译为:⼦程序,⼦程序这种翻译更加准确⼀些。C语⾔中的函数就是⼀个完成某项特定的任务的⼀⼩段代码。这段代码是有特殊的写法和调⽤⽅法的。C语⾔的程序其实是由⽆数个⼩的函数组合⽽成的,也可以说:⼀个⼤的计算任务可以分解成若⼲个较⼩的函数(对应较⼩的任务)完成。同时⼀个函数如果能完成某项特定任务的话,这个函数也是可以复⽤的,提升了开发软件的效率。
库函数
C语⾔标准中规定了C语⾔的各种语法规则,C语⾔并不提供库函数;C语⾔的国际标准ANSIC规定了⼀些常⽤的函数的标准,被称为标准库,那不同的编译器⼚商根据ANSI提供的C语⾔标准就给出了⼀系列函数的实现。这些函数就被称为库函数。
printf 、 scanf 都是库函数,库函数的也是函数,不过这些函数已经是现成的,我们只要学会就能直接使⽤了。有了库函数,⼀些常⻅的功能就不需要程序员⾃⼰实现了,⼀定程度提升了效率;同时库函数的质量和执⾏效率上都更有保证。
库函数相关头⽂件:https://zh.cppreference.com/w/c/header
库函数介绍网站https://legacy.cplusplus.com/reference/clibrary/
库函数的使⽤⽅法
库函数是在标准库中对应的头⽂件中声明的,所以库函数的使⽤,务必包含对应的头⽂件,不包含是可能会出现⼀些问题的。
例如:
#include <stdio.h>
#include<math.h>//sqrt函数的头文件
int main()
{
double d = 16.0;
double r = sqrt(d);//sqrt函数求d的算术平方根
printf("%lf\n", r);
return 0;
}
⾃定义函数
其实⾃定义函数和库函数是⼀样的,形式如下:
ret_type fun_name(形式参数)
{
}
1、ret_type 是⽤来表⽰函数计算结果的类型,有时候返回类型可以是 void ,表⽰什么都不返回
2、fun_name 是为了⽅便使⽤函数;就像⼈的名字⼀样,有了名字⽅便称呼,函数有了名字⽅便调⽤,所以函数名尽量要根据函数的功能起的有意义。
3、 函数的参数就相当于,⼯⼚中送进去的原材料,函数的参数也可以是 void ,明确表⽰函数没有参数。如果有参数,要交代清楚参数的类型和名字,以及参数个数。
4、 {}括起来的部分被称为函数体,函数体就是完成计算的过程。
举个例子:
#include <stdio.h>
int Add(int x, int y)//int表示返回类型是int型的
{
int z = 0;
z = x+y;
return z;
}
int main()
{
int a = 0;
int b = 0;
//输入a和b
scanf("%d %d", &a, &b);
//调用加法函数,完成a和b的相加
//求和的结果放在r中
int r = Add(a, b);
//输出
printf("%d\n", r);
return 0;
}
注意如果自定义函数写在了主函数的后面就需要对函数进行声明。
例如
#include <stdio.h>
int Add(int x, int y);
int main()
{
int a = 0;
int b = 0;
//输入a和b
scanf("%d %d", &a, &b);
//调用加法函数,完成a和b的相加
//求和的结果放在r中
int r = Add(a, b);
//输出
printf("%d\n", r);
return 0;
}
int Add(int x, int y)//int表示返回类型是int型的
{
int z = 0;
z = x+y;
return z;
}
int Add(int x, int y); 这就是函数的声明,如果函数不事先进行声明的话,程序就会报错。
形参和实参
Add(a, b);传给Add的参数a,b,称为实际参数,简称实参,实际参数就是真实传递给函数的参数。
Add(int x, int y)在函数名 Add 后的括号中写的 x 和 y ,称为形式参数,简称形参。
实参和形参的关系
我们在调试的可以观察到,x和y确实得到了a和b的值,但是x和y的地址和a和b的地址是不⼀样的,所以我们可以理解为形参是实参的⼀份临时拷⻉.
接下来通过函数的传值调用和传址调用充分观察一下形参和实参的区别。
传值调用
#include <stdio.h>
void swap(int a,int b);
int main()
{
int a=10,b=20;
printf("交换前%d %d\n",a,b);
swap(a,b);
printf("交换后%d %d\n",a,b);
return 0;
}
void swap(int a,int b)
{
int temp=a;
a=b;
b=temp;
}
传址调用
#include <stdio.h>
void swap(int* a,int* b);
int main()
{
int a=10,b=20;
printf("交换前%d %d\n",a,b);
swap(&a,&b);
printf("交换后%d %d\n",a,b);
return 0;
}
void swap(int* a,int* b)
{
int temp=*a;
*a=*b;
*b=temp;
}
传值调用只是只是用形参改变了函数体里面X,Y的值,而没有真正改变实参a,b的值。所以说形参是实参的⼀份临时拷⻉。只有传址调用,传的是地址,真正意义上传的实参本身这样就可以达到我们想要的结果。
基本的传参类型
前面写了传值和传址的函数类型
下面写一些别的传参
涉及动态内存开辟,和二级指针传参,数组传参
这也是力扣上面四数之和一题的源代码。
如果对题目感兴趣的也可以看一下四数之和解题过程
int compare(void const *str1,void const *str2)
{
return *((int*)str1)-*((int*)str2);
}
void fourSum(int* nums, int numsSize, int target, int* returnSize, int** returnColumnSizes)
{
qsort(nums,numsSize,sizeof(int),compare);
int **ret=(int**)malloc(sizeof(int*)*numsSize*numsSize);
*returnColumnSizes=(int*)malloc(numsSize*numsSize*sizeof(int));
*returnSize=0;
for(int i=0;i<numsSize;)
{
for(int j=i+1;j<numsSize;)
{
int left=j+1;
int right=numsSize-1;
long aim=(long)target-nums[i]-nums[j];
while(left<right)
{
int sum=nums[left]+nums[right];
if(sum>aim) right--;
else if(sum<aim) left++;
else
{
(*returnColumnSizes)[*returnSize] = 4;
ret[*returnSize] = (int*)malloc(sizeof(int) * 4);
ret[*returnSize][0]=nums[i];
ret[*returnSize][1]=nums[j];
ret[*returnSize][2]=nums[left++];
ret[*returnSize][3]=nums[right--];
(*returnSize)++;
while(left<right&&nums[left]==nums[left-1]) left++;
while(left<right&&nums[right]==nums[right+1]) right--;
}
}
j++;
while(j<numsSize&&nums[j]==nums[j-1]) j++;
}
i++;
while(i<numsSize&&nums[i]==nums[i-1]) i++;
}
for(int i=0;i<*returnSize;i++)
{
for(int j=0;j<4;j++)
{
printf("%d ",ret[i][j]);
}
printf("\n");
}
//return ret;
}
int main()
{
int nums[6]={-1,0,1,-2,0,2};
int row=0;
int* col=(int*)malloc(sizeof(int)*100);
fourSum(nums,6,0,&row,&col);
return 0;
}
一维数组和二维数组的传参
void fun(int arr[2][3],int arr1[],int i,int j,int k)
{
}
int main()
{
int arr[2][3]={{1,2,3},{4,5,6}};
int arrlenrow=sizeof(arr)/sizeof(arr[0]);//行
int arrlencol=sizeof(arr[0])/sizeof(int);//列
int arr1[]={1,2,3,4,5};
int arr1len=sizeof(arr1)/sizeof(arr1[0]);
fun(arr,arr1,arrlenrow,arrlencol,arr1len);
}
嵌套调⽤
嵌套调⽤就是函数之间的互相调⽤,每个函数就⾏⼀个乐⾼零件,正是因为多个乐⾼的零件互相⽆缝的配合才能搭建出精美的乐⾼玩具,也正是因为函数之间有效的互相调⽤,最后写出来了相对⼤型的程序。
假设我们计算某年某⽉有多少天?如果要函数实现,可以设计2个函数:
1、 is_leap_year():根据年份确定是否是闰年
2、get_days_of_month():调⽤is_leap_year确定是否是闰年后,再根据⽉计算这个⽉的天数
int is_leap_year(int y)
{
if(((y%4==0)&&(y%100!=0))||(y%400==0))
return 1;
else
return 0;
}
int get_days_of_month(int y, int m)
{
int days[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
int day = days[m];
if (is_leap_year(y) && m == 2)
day += 1;
return day;
}
int main()
{
int y = 0;
int m = 0;
scanf("%d %d", &y, &m);
int d = get_days_of_month(y, m);
printf("%d\n", d);
return 0;
}
main 函数调⽤ scanf 、 printf 、 get_days_of_month
get_days_of_month 函数调⽤ is_leap_year
链式访问
所谓链式访问就是将⼀个函数的返回值作为另外⼀个函数的参数,像链条⼀样将函数串起来就是函数的链式访问。
int add(int a,int b)
{
return a+b;
}
int main()
{
int a=10,b=20,c=30;
int ret=add(add(a,b),c);
printf("%d",ret);
return 0;
}
多个⽂件
代码可能⽐较多,不会将所有的代码都放在⼀个⽂件中;我们往往会根据程序的功能,将代码拆分放在多个⽂件中。
⼀般情况下,函数的声明、类型的声明放在头⽂件(.h)中,函数的实现是放在源⽂件(.c)⽂件中。
我们引用自己写的头文件时用""。
感谢您的阅读,欢迎留言,点赞,收藏。