前言
对于任何的编程语言来说,函数都是其绕不开的一大重点。我们来了解一下函数相关知识(会持续更新函数系列)
初识函数第一弹
一、函数是什么?
函数是一段独立的代码,我们一般会封装一个函数用来完成特定的任务,在某些需要她的场景使用,多数情况下函数是独立于程序存在的,对于C语言来说,函数可以是独立于main函数的。
为什么会出现函数这个概念呢?在过去几年的学习中,我们所了解的函数并不相同,编程中出现的函数是用来封装功能的,前大佬们将某个功能的代码块封装成一个部分,那么我们就可以对其进行调用,以免我们书写同样功能的代码,这大大节约了我们的时间。另一个点,如果我们在开发将所有代码全都放在main函数中,不难想象main函数会是怎样庞大,但是使用函数就可以避免这一点,将整个项目模块化,便于维护,也增加可读性。
二、函数
1.函数的组成
函数名,参数,函数体,返回类型,这些是组成函数必不可少的部分。
定义一个函数,其实可以类比我们定义一个变量的方式
变量的定义 :数据类型 变量名
函数也是如此,只是多了参数。首先确定函数是否有返回值,有返回值就需要按照返回值来定义其数据类型,然后后面紧跟函数名,然后跟(参数)
函数体就是双括号里面的内容
返回值是return后面的内容
在这个地方可能会对void所定义的函数产生疑问,首先标准C语言允许其写return,但没有返回值。也就是说我们的函数可以定义成
void add(int a,int b){
函数内容
}//像这种
这时候我们函数体中就不要返回值了,也就是说不需要写return 0;但是我们可以写return;没有返回值的return
也就是说写成:
void add(int a,int b){
函数内容
return;
}
其实一般我们使用void这种形式定义函数,就是为了让它做一些不需要返回值的事情,比如自己封装一个打印函数,或者改变一块内存中的数据等等
2.调用函数
接下来我们讨论函数调用问题,还是以前那个Add函数
记住我们调用函数的模式是得紧紧看着我们定义它的形式。(这里讨论的是有返回值的形式
如果使用int定义了一个函数,那么像下图这样使用一个c接受这个函数值的时候,就不能将c定义成别的形式,也得是int。
即使我们这么定义了,还是有上面这种报错,这是怎么回事。我们知道程序自上到下进行,进入main函数执行到Add函数的时候,程序中还不知道有Add呢,这样就会报找不到标识符的问题。所以我们要进行函数声明,如下:(声明也可以写在main之上的。)
分析:函数从main开始读取,读到声明的时候,它就知道了我们定义过这个Add函数,接下来读到Add(10,20)的时候,就会自动去寻找这个函数,调用它。
另外一种方式就是直接将函数定义写在main函数之上就不用写函数声明了,一样可以运行
函数总结第二弹
库函数
每个程序员在开发中可能用到的某种业务代码,为了支持C语言地可移植性和提高效率,所以c语言基础库中提供了一系列类似地库函数,方便程序员调用
基础地库函数有
- IO函数 ----------------->输入输出函数
- 字符串操作函数
- 字符操作函数
- 内存操作函数
- 时间/日期操作函数
- 数学函数
- 其他库函数
学习一些基础函数:(学会去读文档是十分重要的http://www.cplusplus.com/reference/)
-
strlen函数
首先我们看返回值,是size_t类型(表示一个无符号整型),作用是得到一个字符串地长度。参数是传递一个字符指针,指针传递的是地址,也就是可以使用一个字数组传进去嘛
#include<string.h> int main() { char arr[] = "abc"; size_t len = strlen(arr); printf("%u\n", len); }
-
strcpy函数
拷贝一个c的字符串从源到目的地
#include<string.h> int main() { char arr1[20] = {0};//destination char arr2[20] = "HELLO";//source strcpy(arr1, arr2); printf("%s\n", arr1); return 0; }
-
memset
设置内存块函数
设置从ptr所指向位置开始后面的元素,设置成为value所代表的值,num是设置改变的个数
#include<string.h> int main() { char arr[] = "hello world"; memset(arr, 'q', 2);//传递的是字符,但是应该是int,是因为ASCII的 //传递了数组的首元素地址,value表示想要替换成为什么元素,这里表示想要换成q,并且替换2个 printf("%s", arr); }
自定义函数
库函数只是说满足了大多数程序员共有的业务逻辑,但是还有一些代码功能是需要我们自行封装的,这些就是自定义函数
函数需要先定义后调用,这是逻辑上的顺序
写一个交换数值的代码
void Swap(int x, int y)
{
int z = 0;
z = x;
x = y;
y = z;
}
int main()
{
int a = 0;
int b = 0;
scanf("%d%d", &a, &b);
printf("交换前:a=%d b=%d\n", a, b);
Swap(a, b);
printf("交换前:a=%d b=%d\n", a, b);
}
代码并没有改变两个数的值,我们要注意,函数中x,y和a,b甚至不是同一个地址,怎么会影响ab的值呢
传递给swap为实参,函数接受的是形参,当函数调用的时候实参传递给形参,形参其实是实参的一份临时拷贝,所以对于形参的修改不会影响实参
那么怎么改动呢?我们要让函数内部链接到main函数中的ab,而指针就可以完美做到
void Swap(int *x, int *y)
{
int z = 0;
z = *x;
*x = *y;
*y = z;
}
int main()
{
int a = 0;
int b = 0;
scanf("%d%d", &a, &b);
printf("交换前:a=%d b=%d\n", a, b);
Swap(&a, &b);
printf("交换前:a=%d b=%d\n", a, b);
}
当我们需要改变主函数中的数时,传址调用
-
一些注意点
-
在进行函数调用的时候,实参必须是确定的值,以便于传递给形参
-
如果我们不调用函数,他内部的形参是不会有内存空间的,只有调用才真实分配空间,并且调用后自动销毁了
-
形式参数是存储在栈区的
栈区:局部变量,(形式参数也是局部的阿)
堆区:动态内存分配(malloc free realloc calloc)
静态区:全局变量,静态变量
-
函数的调用
函数是可以嵌套调用的,但是不能嵌套定义------->我们可以在函数中调用其他的函数,但是不能在一个函数中定义一个函数
函数的链式访问
int main()
{
printf("%d", printf("%d", printf("%d", 43)));//打印4321
return 0;
}
printf的返回值介绍:返回打印字符个数或者打印错误返回负数
内层打印43,这是两个数字组成的所以第二层打印2,最外层因为这个2所以打印1
函数声明和定义
先去思考怎么使用想要实现的函数然后定义它
一般工程中我们将代码模块化会将一个功能放置在.h跟.c函数中,.h中放置函数声明,.c放置实现,然后再其他.c函数中引用头文件.h,就能使用相应功能
如果希望不让别人看到自己的代码,但是又想让别人使用自己的函数可以考虑给别人lib和.h文件就可以
函数递归
程序调用自身的编程技巧叫做递归 函数自己调用自己就叫递归。
递归非常重要的一点就是找到递归条件,每一次都有限制条件的。其次每一次递归之后都要接近这个限制条件(不然那不就又死递归嘛)
函数递归是最容易造成栈溢出(stackoverflow)问题的,每一次函数调用都会在内存的栈区申请一块空间,但是注意递归的如果没有进行到最后一层是会不断调用的,我们设想一个函数没有限制条件,那么就会无限开辟空间且由于外层的每一层函数都没有结束,空间不会回收,栈迟早得炸
下面我们利用递归实现一个正序打印数字的每一位
我们作图分析这个递归的过程。
下面是代码实现
void Print(int n)
{
if (n > 9)
{
Print(n / 10);
}
printf("%d", n % 10);
}
int main()
{
int n = 0;
scanf("%d ", &n);
Print(n);
return 0;
}
最后
以上就是我对于函数的一些学习,希望可以帮助到大家!