函数:
函数是一个大型程序的子程序,是部分负责一个特定任务的代码,相较于其他代码而言具有一定的独立性。可以实现过程的封装和细节的隐藏。一般都会有返回值和输入参数。
库函数:
C语言中的标准库提供了C语言的库函数,有些基础功能并不是业务性的代码,每一次编程时都可能会用到,为了提高效率,C语言则提供了标准库中的库函数。(cplusplus.com网站可以查询所有库函数以及各种细节)
一些C语言常用的库函数
1⃣️ IO函数:input、output、scanf、printf、getchar、putchar等
2⃣️ 字符串操作函数:strlen、strcpy、strcmp等
3⃣️ 字符操作函数:大小写转换、字符分类等
4⃣️ 内存操作函数:memcpy、memmove、memset等
5⃣️ 时间/日期函数:time等
6⃣️ 数学函数:pow、sqrt等
7⃣️ 其他
自定义函数:
相比于库函数,自定义函数的重要性要大很多。因为库函数的功能有限,我们基本上都需要自定义函数来实现我们的需求。在C语言中,任何一个函数都有以下特征:
ret_type fun_name(paral,*) //ret_type 返回类型,fun_name 函数名
{ //paral 函数参数(可能不止一个)
statement; //语句项
}
比如,写一个函数求两个数的较大值:
int Add(int x,int y)
{
return (x>y) ? (x):(y);
}
int main()
{
int x=0,y=0;
scanf(“%d %d”,&x,&y);
printf(“%d”,Add(x,y));
return 0;
}
这个函数很简单,但分析一下:用函数比较两个数的大小首先要让函数知道我要比较哪两个数的大小,故将我要比较的两个数字作为参数传递给函数,而函数就要有两个参数接收我传过去的值,在函数的内部进行比较然后将比较的结果作为返回值返回给主调函数即可。
那么再看一个函数,在函数的内部实现两个数的交换:
void Swap1(int x,int y)
{
int temp=x;
x=y;
y=temp;
}
int main()
{
int x=0,y=0;
scanf(“%d %d”,&x,&y);
printf(“交换前:a=%d,b=%d”,a,b);
Swap1(x,y);
printf(“交换后:a=%d,b=%d”,a,b);
return 0;
}
运行结果:
可以发现并没有像我们想的那样交换两个数的值,这里我们可以打开VS的监视窗口观察一下程序当中涉及到的变量:
可以发现,在Swap1函数的内部,确实完成了两个数字间的交换,但交换的是函数的参数x,y,并不是a和b,这里就涉及到了传值调用的概念。我们可以发现,a,b和x,y的内存地址并不是一致的,这是因为,在函数调用时,实参传递给形参,形式参数是实际参数的一份临时拷贝,对形参的操作不影响对实参的改变。也就是说,在函数调用的时候,内存为函数的形式参数x,y分别分配一块新的内存空间,在函数调用结束时,x,y的内存空间被回收。
那么我们如果非要用这个函数实现两个数字间的交换的话,该怎么做?
通过上述分析我们可以知道,问题就是出在函数的参数上,形式参数不能改变实际参数,那么我们怎么可以通过实际参数间接改变实参呢,这就要用到C语言最富特色的指针和地址内容了。
void Swap2(int *pa,int *pb)
{
int temp=*pa;
*pa=*pb;
*pb=temp;
}
int main()
{
int a=0,b=0;
scanf(“%d %d”,&a,&b);
printf(“交换前:a=%d,b=%d”,a,b);
Swap2(&a,&b);
printf(“交换后:a=%d,b=%d”,a,b);
return 0;
}
这里的Swap2函数用两个指针作为参数,主调函数将a和b的地址,作为实际参数传递给Swap的两个指针参数。pa和pb这两个指针变量里面存的分别是a和b的地址,通过解引用操作符取到各自指针指向的内容,从内存地址的角度交换a和b的值。
函数参数:
实际参数(实参):
实参可以是常量,变量表达式,函数,但无论实参是何种形式,在调用函数时,它们都必须要有一个确定的值。
形式参数(形参):
函数定义时,()里面的参数就是形式参数,形式参数可以和实际参数同名。
形参,在不调用函数的时候,并不为其分配内存空间,在调用时才分配内存单元(实例化,即给函数传递实参),形参在函数调用时创建,调用结束时销毁。
函数的调用
传值调用:
上述的Add和Swap1函数就是传值调用的例子,形参和实参分别占用不同的内存单元,操作形参不会影响实参。
传址调用:
上述的Swap2就是一个传址调用的例子,该方法在函数的内部实现与函数外部变量的联系,并且可以直接操作甚至是影响外部的变量。
函数的嵌套调用和链式访问
嵌套调用:
void new_line()
{
printf(“hehe\n”);
}
void three_lines()
{
for(int i=0;i<3;i++)
{
new_line();
}
}
int main()
{
three_lines();
return 0;
}
上述即嵌套调用的例子,main函数中调用three_line函数,而three_lines函数中调用了三次new_line函数,打印三个hehe。
但注意,函数可以嵌套调用,但不允许嵌套定义,即不能在一个函数的内部定义另一个函数。
链式访问:
把一个函数的返回值作为另一个函数的参数
int main()
{
int len = strlen("abcdef");
printf("%d\n", strlen("abcdef"));
printf("%d\n",len);
return 0;
}
这里将strlen函数的返回值作为printf的参数打印出来,strlen函数的返回值就是传入字符串去除 \0 字符后的长度。
再看一个例子:
int main()
{
printf("%d", printf("%d", printf("%d", 43)));
return 0;
}
这段程序的运行结果是:4321
为什么输出4321,就要知道printf函数的返回值是什么。printf函数的返回值是打印在屏幕上字符的个数,最内部的printf打印43,返回2,作为参数给中间的printf;中间的printf打印2,返回1,作为参数给最外面的printf,最外面的printf则打印1.
函数的声明与定义:
声明:告诉编译器函数的返回值,函数名,参数名和个数(声明时,可以省略参数名,参数类型,返回值不要省)
定义:描述清楚函数内部的所有细节,在一个大型工程中,在.h文件中声明函数,变量这些;在.c文件中实现函数,即定义。
且函数必须要满足先声明,再使用的原则,如果在使用之前就已经定义了,则不需声明,可以讲定义理解为就是一个最有力的声明。