在C语言中,这个函数时必不可少的,没有函数没有灵魂,要不然代码就会乱成一团,所以我们要学函数,接下来就开始函数之旅~~
数学中我们其实就见过函数的概念,比如:一次函数y=kx+b ,k和b都是常数,给一个任意的x,就得到一个y值。其实在C语言也引入函数(function) 的概念,有些翻译为:子程序,子程序这种翻译更加准确一些。C语言中的函数就是一个完成某项特定的任务的一小段代码。
这段代码是有特殊的写法和调用方法的。C语言的程序其实是由无数个小的函数组合而成的,也可以说:一个大的计算任务可以分解成若干个较小的函数(对应较小的任务)完成。同时一个函数如果能完成某项特定任务的话,这个函数也是可以复用的,提升了开发软件的效率。
在C语言中我们一般会见到两类函数:
我们前面内容中学到的printf 、scanf 都是库函数,库函数的也是函数,不过这些函数已经是现成的,我们只要学会就能直接使用了。有了库函数,一些常见的功能就不需要程序员自己实现了,一定程度提升了效率;同时库函数的质量和执行效率上都更有保证。
有数学相关的,有字符串相关的,有日期相关的等,每一个头文件中都包含了,相关的函数和类型等信息,库函数的学习不用着急一次性全部学会,慢慢学习,各个击破就行。
- IO函数
- 字符串操作函数
- 字符操作函数
- 内存操作函数
- 时间/日期函数
- 数学函数
- 其他库函数
接下来,我会参照文档,给大家将两个常用的库函数,来教会大家如何入阅读英文文档
strcpy
char * strcpy ( char * destination, const char * source );
#include
#include
int main()
{
char arr1[] = "###########";
char arr2[] = "hello word";
//strcpy在拷贝的时候'\0'也会被拷贝过来
strcpy(arr1, arr2);
printf("%s", arr1);
return 0;
}
接下去再来看一库函数【memset】
memset
void * memset ( void * ptr, int value, size_t num );
char arr[] = "hello bit";
memset(arr, 'x', 5);
printf("%s\n", arr);
注:
但是库函数必须知道的一个秘密就是:使用库函数,必须包含 #include 对应的头文件。
这里对照文档来学习上面几个库函数,目的是掌握库函数的使用方法。
如何学会使用库函数?
需要全部记住吗?
No 需要学会查询工具的使用:
MSDN(Microsoft Developer Network)
en.cppreference.com【英文版】
zh.cppreference.com【中文版】
函数的组成:
ret_type fun_name(para1, * )
{
statement;//语句项
}
我们首先来举一个例子:
写一个函数可以找出两个整数中的最大值。
int Get_Max(int x, int y)
{
return (x > y ? x : y);
}
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
int max = Get_Max(a, b);
printf("二者中的较大值为:%d\n", max);
return 0;
}
void swap(int x, int y)
{
int t = x;
x = y;
y = t;
}
int main()
{
int a = 10;
int b = 20;
printf("交换前:a = %d, b = %d\n",a, b);
swap(a, b);
printf("交换后:a = %d, b = %d\n", a, b);
return 0;
}
结论: 形参实例化之后其实相当于实参的一份临时拷贝
上面这一个,叫做【传值调用】,函数内部形参的修改是不会影响实参的,接下来我们来讲讲【传址调用】~~
先看一下代码,然后我们再通过DeBug来展开分析一下
void swap(int* px, int* py)
{
int t = *px;
*px = *py;
*py = t;
}
真实传给函数的参数,叫实参
对于实参而言,它可以是【常量】、【变量】、【表达式】、【函数】等。我们到VS里来测试一下
无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参
所以我们可以总结出来一句话:
函数调用时,实参传递给形参,形参是实参的一份临时拷贝,形参的改变不影响实参
函数的形参和实参分别占有不同内存块,对形参的修改不会影响实参
int main()
{
for (int i = 100; i <= 200; ++i)
{
if (IsPrime(i) == 1)
{
printf("%d是素数\n", i);
}
}
return 0;
}
接下去我们来看看函数的部分
如果i能够被**2, sqrt(i)**之间的任意数据整除,则i不是素数
原因:如果 m 能被 2 ~ m-1 之间任一整数整除,其二个因子必定有一个小于或等于 sqrt(m),另一个大于或等于 sqrt(m)。
注意: 在使用sqrt的时候需要引入头文件【math.h】
/*素数判断*/
int IsPrime(int n)
{
int j = 0;
for (int j = 2; j < sqrt(n); j++)
{
if (n % j == 0)
return 0;
}
return 1;
}
for (int i = 1000; i <= 2000; ++i)
{
if (Isleap(i) == 1)
{
printf("%d ", i);
}
}
int Isleap(int year)
{
if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
{
return 1;
}
return 0;
}
int Isleap(int year)
{
if (year % 4 == 0)
{
if (year % 100 != 0)
{
return 1;
}
}
if (year % 400 == 0)
return 1;
}
(year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
,如果为真,就返回1
,如果为假,就返回0
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
int arr[] = { 1,2,3,4,5,6,7,8,9 };
int sz = sizeof(arr) / sizeof(arr[0]);
int k = 7;
int pos = BinarySearch(arr, sz, k);
//int BinarySearch(int* a, int n, int k) //指针接收地址
int BinarySearch(int a[], int n, int k) //数组接收数组
{
int left = 0;
int right = n - 1;
while (left <= right)
{
int mid = left + (right - left) / 2;
if (k > a[mid])
left = mid + 1;
else if (k < a[mid])
right = mid - 1;
else
return mid;
}
return -1;
}
void change(int* px)
{
(*px)++;
}
int num = 0;
change(&num);
printf("num = %d\n", num);
(*px)++
,不能写成*px++
,因为【++】的优先级比【*】来得高,所以会先对这个指针变量进行一个++,然后再对其进行一个解引用的操作,但是当这个指针后移的时候,就已经变成了野指针,此时再去访问这个野指针就会出现问题*px
外的括号去掉再看看函数和函数之间可以根据实际的需求进行组合的,也就是互相调用的
void print()
{
printf("haha\n");
}
void three()
{
for (int i = 0; i < 3; ++i)
{
print();
}
}
int main()
{
three();
return 0;
}
函数可以嵌套调用,但是不能嵌套定义
把一个函数的返回值作为另外一个函数的参数
char a[] = "hello world";
int len = strlen(a);
printf("len = %d\n", len);
这里要注意一点的是strlen()去求字符串长度的时候是不算’\0’的
接着再来介绍一个函数【strcat】
char a[10] = "hello";
int len = strlen(strcat(a, "bit"));
printf("len = %d\n", len);
printf("%d", printf("%d", printf("43")));
printf("43")
输出的字符个数有2个,然后再看外层的printf("%d", printf("43"))
便会在输出一个2,它的返回值就是输出了一个字符,那么整体的这个printf("%d", printf("%d", printf("43")))
就会在输出一个1,作为返回值int Add(int x, int y);
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
int sum = Add(a, b);
printf("sum = %d\n", sum);
return 0;
}
int Add(int x, int y)
{
return x + y;
}
函数的定义是指函数的具体实现,交待函数的功能实现
接下去我们来说说函数递归,这也是函数这一块最难理解的内容
程序调用自身的编程技巧称为递归( recursion)
只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量
递归的主要思考方式在于:把大事化小
【要求】:输入1234,打印1 2 3 4
在之前我们学习分支和循环的时候有做过类似的题目,那个时候我们是利用取余【%】和整除【/】倒着打印一个数字的每个数位,也就是下面这段代码
void print1(int num)
{
while (num > 0)
{
printf("%d ", num % 10);
num /= 10;
}
}
int main()
{
print1(1234);
return 0;
}
运行结果如下
print(num)
,即print(1234)
,那既然上面讲到了这个分割数字的思路,这里我们也可以使用这个思路来完成,print(1234)
我们可以拆成print(123) 4
print(123)
又可以拆成print(12) 3
print(12)
又可以拆成print(1) 2
print(1234)
就被拆成了print(1) 2 3 4
,也就是当这个num < 10为一个个位数时,就做一个打印,否则的话就不断将其以十分之一倍得进行缩小。我们将其转化为代码的形式就【一目了然】了void print2(int num)
{
if (num > 9)
{
print2(num / 10);
}
printf("%d ", num % 10);
}
运行结果如下
【要求】:输入abc,输出其长度为3
上面有讲到过strlen()
这个函数,其可以求出一个字符串的长度,我们首先通过这个函数来试试
int main()
{
char str[] = "abc";
int len = strlen(str); //利用库函数进行求解
printf("len = %d\n", len);
return 0;
}
int len = my_strlen(str);
,我们传入了字符数组str的首元素地址,那上面有讲到过对于地址要使用指针来进行接收,最后还要返回求出的长度,所以对于函数我们可以定义成这样int my_strlen(char* str)
对于str这个字符指针现在是指向传入字符数组的首元素地址,也就是【a】,【\0】是一个字符串的结束标志,可以让这个字符指针不断后移,每一次对其进行解引用看看是否为【\0】即可,有了思路我们就可以写出代码了
在这里的话还需要设置计数器,我们我们要去求解这个字符串的长度,因此判断到它不为【\0】的时候就count++,然后让这个字符指针进行后移即可。
int my_strlen(char* str)
{
int count = 0;
while ((*str) != '\0')
{
count++; //计数器累加
str++; //字符指针后移
}
return count;
}
my_strlen(abc)
我们可以拆成1 + my_strlen(bc)
my_strlen(bc)
我们可以拆成1 + my_strlen(c)
my_strlen(c)
我们可以拆成1 + my_strlen('\0')
my_strlen(abc)
就相当于是1 + 1 + 1 + 0
。也是使用字符指针去进行一个后移,若其不为【\0】时,就不断对这个字符串进行拆分,然后直到遇到【\0】时便return 0
。接下去就可以写出代码了int my_strlen(char* str)
{
if (*str != '\0')
{
return 1 + my_strlen(str + 1);
}
return 0;
}
int Func1(int n)
{
if (n <= 1)
return 1;
else
return n * Func1(n - 1);
}
int Func2(int n)
{
int ret = 1;
for (int i = 1; i <= n; ++i)
{
ret *= i;
}
return ret;
}
这里再对上面的递归实现做一个递归展开图的分析
【要求】:输入一个数,输出从1到这个数的斐波那契数
斐波那契数就是前两个数加起来等于第三个数
这里给出从1~10的斐波那契数列 【1 1 2 3 5 8 13 21 34 55】,可以看出对于1,2两个数来说都是1
,后面就是数就是前两个数之和,因此我们可以列出下面的公式
然后根据这个公式写出代码
int Fib1(int n)
{
if (n <= 2)
return 1;
else
return Fib1(n - 2) + Fib1(n - 1);
}
本内容请看这篇文章——> 反汇编深挖【函数栈帧】的创建和销毁【制作中】
最后,函数的所有内容就到这里就结束了~~
如果有什么问题可以私信我或者评论里交流~~
感谢大家的收看,希望我的文章可以帮助到正在阅读的你