🚀write in front🚀 🔎大家好,我是泽En,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流🔎 🏅2021年度博客之星物联网与嵌入式开发TOP5→周榜43→总榜3343🏅 🆔本文由 泽En 原创 CSDN首发🐒 如需转载还请通知⚠ 📝个人主页:打打酱油desu_泽En_CSDN博客🎓 🎁欢迎各位→点赞👍 + 收藏⭐️ + 留言📝 📣系列专栏:【C】系列_打打酱油desu-CSDN博客📢 ✉️我们并非登上我们所选择的舞台,演出并非我们所选择的剧本📩
目录
🔥习题①→接收一个整型值(无符号),顺序打印出每一位。例如 1234,输出 1 2 3 4!
🔥习题②→模拟实现字符串函数,用递归的形式,不能创建临时变量。
构成程序的基本单元是函数,函数中包含程序可执行代码。 函数是指一段可以直接被另一段程序或代码引用的程序或代码,也叫做子程序。 一个较大的程序一般应分为若干个程序块,每一个模块用来实现一个特定的功能。所有的高级语言中都有子程序这个概念,用子程序实现模块的功能。 在C语言中,子程序是由一个主函数和若干个函数构成的。由主函数调用其他函数,其他函数也可以互相调用。同一个函数可以被一个或多个函数调用任意多次。 函数可以提高软件的开发效率,在main函数当中调用其它函数这些函数执行完毕被调用函数执行完毕之后又回到main函数当中。通常把这些被调用的函数称为下层函数。函数调用发生时候,立即执行被调用函数,而调用者则进入等待状态,直到被调用函数执行完毕。函数可以又参数(void&int)和返回值。 举例说明→盖一栋房子,在这项工程当中,在工程师的指挥下,有工人搬运盖楼材料,有建筑工人造房子,还有工人在楼房外粉刷涂料。编写程序于盖这个楼房实际是一个道理,主函数就像是工程师一样,其功能是控制每一步程序的执行,其中定义的其他函数就好比盖楼中的每一道步骤,分别去完成自己所对应的特殊功能。
降低复杂性!用函数的最首要原因是为了降低程序的复杂性,可以使用函数来隐含信息,从而使你不必再考虑这些信息。 避免重复代码段!如果在两个不同函数中的代码很相似,这往往意味着分解工作有误。这时,应该把两个函数中重复的代码都取出来,把公共代码放入一个新的通用函数中,然后再让这两个函数调用新的通用函数。通过使公共代码只出现一次,可以节约许多空间。 限制改动带来的影响:由于在独立区域进行改动,因此,由此带来的影响也只限于一个或最多几个区域中。 隐含顺序:如果程序通常先从用户那里读取数据,然后再从一个文件中读取辅助数据,在设计系统时编写一个函数,隐含哪一个首先执行的信息。 改进性能:把代码段放入函数也使得用更快的算法或执行更快的语言(如汇编)来改进这段代码的工作变得容易些。 进行集中控制:专门化的函数去读取和改变内部数据内容,也是一种集中的控制形式。 隐含数据结构:可以把数据结构的实现细节隐含起来。 隐含指针操作:指针操作可读性很差,而且很容易引发错误。通过把它们独立在函数中,可以把注意力集中到操作意图而不是集中到的指针操作本身。 隐含全局变量:参数传递。
C语言函数的分类👇
为什么在程序当中会存在有库函数? C语言在发布时已经为我们封装好了很多函数,它们被分门别类地放到了不同的头文件中,使用函数时引入对应的头文件即可。这些函数都是专家编写的,执行效率极高,并且考虑到了各种边界情况,可以在写代码时候放心使用。
像上面我们描述的基础功能,它们不是业务性的代码。我们在开发的过程中每个程序员都可能会用的到,为了支持可移值性和提高程序的效率,所以C语言的基础库中有提供一系列的库函数,放别程序员进行软件的开发! 那么怎么学习库函数?
例如 strcpy() → 字符串拷贝函数。
参数说明:
代码示例如下👇
#include<stdio.h>
#include<string.h>
int main(void)
{
char arr1[20] = { 0 };
char arr2[] = "hello C";
strcpy(arr1, arr2);
printf("%s\n", arr1);
return 0;
}
简单总结,C语言常用的库函数!
如果库函数能干所有的事情,那还要程序员干什么? 除了库函数,我们还可以编写自己的函数,拓展程序的功能。自己编写的函数称为自定义函数。所以更加重要的是自定义函数,如何去实现一个自定义函数。 自定义函数和库函数一样,有函数名,返回值类型(int & void)和函数参数(有参无参),但是不一样的是这些都是我们自己来设计的,这从而就给了程序员一个很大的发挥空间。 那我们举出一个例子:写一个函数判断两个数字大小!
#include<stdio.h>
int Max(int x, int y)
{
if (x > y)
return x;
else
return y;
}
int main(void)
{
int a = 10;
int b = 20;
int ret = Max(a, b);
printf("ret = %d\n", ret);
return 0;
}
🖊运行结果↓ ret = 20
那我们再举出一个无返回值类型例子:写一个函数交换两个值的变量!
#include<stdio.h>
void swap(int *x, int *y)
{
int tep = *x;
*x = *y;
*y = tep;
}
int main(void)
{
int a = 10;
int b = 20;
printf("交换之前:a=%d,b=%d\n", a, b);
swap(&a, &b);
printf("------------------\n");
printf("交换之后:a=%d,b=%d\n", a, b);
return 0;
}
🖊运行结果↓ 交换之前:a=10,b=20 交换之后:a=20,b=10 注意→在你交换值的时候需要取出它们的地址,因为相当于你以及改变它们的内存编号了!我们指望它能够把a和b的值进行交换,也就是说我们在这个过程中会把swap()函数的值进行交换。所以,我们的外部函数和内部函数是必须要建立联系。那么我们就要把地址给传递过去,通过地址的内存单元的编号我们才能够找回来。
真实传递给函数的参数,叫做实参。实参的参数可以是:常量、变量、表达式、函数等。无论实参是何种类型的量,在进行函数调用时,它们都必须要有确定的值,以便把这些值传递给到形参当中去。 像上面示例当中 swap(&a,&b); 这里的swap()括号里面的变量就是实际参数。
形式参数是指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(分配内存单元)调用一瞬间才会开辟内存空间,所以叫做形式参数。形式参数当函数调用完成之后就会自动销毁了。因此形式参数只是在函数当中有效!声明周期范围有限。 像上面的示例当中void swap(int *x, int *y) 这里的无返回类型swap函数里面就是形式参数。 形参和实参的功能是传递数据,发生函数调用时,实参的值会传递给形参。
创建 C 函数时,会定义函数做什么,然后通过调用函数来完成已定义的任务。 当程序调用函数时,程序控制权会转移给被调用的函数。被调用的函数执行已定义的任务,当函数的返回语句被执行时,或到达函数的结束括号时,会把程序控制权交还给主程序。就像我们上面举例说明的代码一样! 说的简单点,就是让这个函数帮我们做事情,叫做函数调用! C语言中,函数的调用的一般形式为:
函数的调用方式!!!
当调用函数时,有两种向函数传递参数的方式,如下↓
向函数传递参数的传值调用方法,把参数的实际值复制给函数的形式参数。在这种情况下,修改函数内的形式参数不会影响实际参数。
void swap(int x, int y)
{
int temp;
temp = x; /* 保存 x 的值 */
x = y; /* 把 y 赋值给 x */
y = temp; /* 把 temp 赋值给 y */
}
现在,让我们通过传递实际参数来调用函数 swap() ↓
#include <stdio.h>
void swap(int x, int y)
{
int temp;
temp = x; /* 保存 x 的值 */
x = y; /* 把 y 赋值给 x */
y = temp; /* 把 temp 赋值给 y */
}
int main (void)
{
int a = 100;
int b = 200;
printf("交换前,a 的值: %d\n", a );
printf("交换前,b 的值: %d\n", b );
// 调用函数来交换值
swap(a, b);
printf("交换后,a 的值: %d\n", a );
printf("交换后,b 的值: %d\n", b );
return 0;
}
🖊运行结果↓ 交换前,a 的值: 100 交换前,b 的值: 200 交换后,a 的值: 100 交换后,b 的值: 200 可以从上面的代码发现程序并没有达到交换的效果!
通过指针传递方式,形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作。
void swap(int *x, int *y)
{
int temp;
temp = *x; /* 保存地址 x 的值 */
*x = *y; /* 把 y 赋值给 x */
*y = temp; /* 把 temp 赋值给 y */
}
现在,让我们通过传递实际参数来调用函数 swap():
#include <stdio.h>
void swap(int *x, int *y)
{
int temp;
temp = *x; /* 保存地址 x 的值 */
*x = *y; /* 把 y 赋值给 x */
*y = temp; /* 把 temp 赋值给 y */
}
int main (void)
{
int a = 100;
int b = 200;
printf("交换前,a 的值: %d\n", a );
printf("交换前,b 的值: %d\n", b );
/* 调用函数来交换值
* &a 表示指向 a 的指针,即变量 a 的地址
* &b 表示指向 b 的指针,即变量 b 的地址
*/
swap(&a, &b);
printf("交换后,a 的值: %d\n", a );
printf("交换后,b 的值: %d\n", b );
return 0;
}
🖊运行结果↓ 交换前,a 的值: 100 交换前,b 的值: 200 交换后,a 的值: 200 交换后,b 的值: 100 在讲以上知识点的时候,希望大家再写自定义函数的时候,我们自定义函数只需要完成我们的功能即可,不需要进行很多无谓的操作,这样只会显得多此一举。 在这里拓展一个知识点,假设你实参里面有数组名,那么我们把数组名传递给到形参里去的话。注意:我们传递的仅仅是数组首元素的地址!
函数的声明就是告诉编译器我这里是有一个函数的,它的参数和返回类型也要告诉编译器,那么这就够了。这个时候编译器就知道你这个函数已经声明了,就不会再不知道你没有这个函数。因为我们已经告知编译器我们这个函数是实实在在是存在的。所以,编译器就不会报错!
📢函数的声明组成
下面的程序为大家用代码举个例子如下所示↓
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main(void)
{
int a = 10;
int b = 20;
Add(a, b);
return 0;
}
void Add(int x, int y)
{
int z = 0;
z = x + y;
printf("ret = %d\n", z);
}
代码编译运行结果:"Add"重定义,不同类型报错!
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
void Add(int x, int y);
int main(void)
{
int a = 10;
int b = 20;
Add(a, b);
return 0;
}
void Add(int x, int y)
{
int z = 0;
z = x + y;
printf("ret = %d\n", z);
}
🖊运行结果↓ ret = 30
从上面代码不同的编译结果我们就可以看出有函数的声明和无函数声明的一个区别! 说明:当然如果你把函数的定义放在主函数前面这个时候我们函数的声明其实都是无所谓的,因为我们的编译器都是从前往后开始的执行的。因为我编译器在前面已经见过了你这个函数是存在的。那么在主函数调用你这个函数自然而然是一点问题都是没有的。 静态库
可以实现把某个源文件.c删除就好比上述的sub.c但是只要导入头文件并且声明.h在主函数当中却依旧可以运行程序并且实现sub函数的功能。
例如:示例代码如下↓
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main(void)
{
void Display() //错误不能在函数内定义函数
{
printf("Hello C!\n");
}
return 0;
}
从上面的代码中可以看到,在主函数 main 中定义了一个 Display 函数,目的是输出语句(Hello C!)。但是,C语言是不允许进行嵌套定义的。因此就会提示错误!❌
虽然,C语言不允许嵌套定义,但是可以嵌套调用函数,也就是说,在一个函数体内可以调用另外一个函数。例如:下面代码进行函数的嵌套调用↓
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
void nesting()
{
printf("hello C!\n");
}
void Display()
{
nesting();
}
int main(void)
{
Display();
return 0;
}
上述代码是正确的,在函数体内进行函数的嵌套调用形式☑ 🖊运行结果↓ hello C
概述:一个函数在它的函数体内调用它自身称为递归调用,这种函数称为递归函数。执行递归函数将反复调用其自身,每调用一次就进入新的一层,当最内层的函数执行完毕后,再一层一层地由里到外退出。
程序调用自身的编程技巧称为递归。递归作为一种算法在程序设计语言中是广泛应用的。一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题转换位一个与原问题相似的规模较小的问题来去进行求解,递归策略只需少了的程序就可以描述出解题的过程所需要的多次重复计算,大大减少了程序的代码量。递归的人主要思考方式就在于:把大事化小。递归之所以能够实现,是因为函数的每个执行过程在栈中都有自己的形参和局部变量的副本,这些副本和和该函数的其它执行过程是不会发生关系的。 注意→当递归进入到最内层的时候,递归就结束了,就开始逐层退出了,也就是逐层执行 return 语句。当执行最后层的 return 之后,就执行上面一层的递归!
每一个递归函数都应该只进行有限次的递归调用,否则它就会进入死胡同,永远也不能退出了,这样的程序是没有意义的。
那么下面举出一个最简单的例子,示例代码如下↓
#include<stdio.h>
int main(void)
{
printf("hello C!\n");
main();
return 0;
}
C语言的递归是什么?不就是函数体内自身调用自己称之为递归吗。 如上述代码中可以看到,这里主函数里面有个打印库函数,其语句hello C。那么后面有个main();函数。那么当我们执行完语句,执行main();函数,此时执行由回到主函数开头又执行打印库函数,再执行main();函数进行调用。这样就构成了一个死循环,直到系统自己退出中这个死循环为止。不然就会一直存在这个死递归。
解题思路🖊
%u → 无符号10进制整数。 最后:1 / 10 = 0,当我这个数为 0 的时候,就得到了所有输出的数字。但是你会发现,我们这里得到数字都是倒着打印的,当然用数组也可以实现正向打印。不过麻烦,所以我们这里带大家实现用递归如何输出上述程序! 注意:当函数递归完之后是会继续再次从递归的函数开始执行,直到你满足限制条件!
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
void print(unsigned int number)
{
if (number > 9) //限制条件
{
print(number/10); //调用这个函数,直到表达式为假 执行下面语句
}
printf("%d ", number % 10);
}
int main(void)
{
unsigned int number = 0;
printf("请输入数字:");
scanf("%u", &number);
print(number);
return 0;
}
简单说明:栈(stack)又名堆栈,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。 拓展,栈:当你递归调用的次数多了,那么每一次都要开辟一次内存空间,当开辟到空间存储不够的时候,那么就会发生栈溢出的情况!所以我们在写递归的时候需要注意下:
解题思路🖊 这个题目是求字符串长度,那我们要求一个字符串函数,不就是模拟strlen吗?就是实现这个字符串函数的功能,让我们自己创建一个自定义函数来去实现。
代码示例如下↓
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int My_strlen(char *str)
{
if (*str != '\0')
return 1 + My_strlen(1 + str);
else
return 0;
}
int main(void)
{
char arr[20] = {0};
printf("请输入字符:");
scanf("%s", &arr);
printf("str = %d\n", My_strlen(arr));
return 0;
}
🖊运行结果↓ 请输入字符:123456 str = 6
代码示例如下↓
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int Fib(n)
{
int a = 1;
int b = 1;
int c = 1;
int count = 0; //计数
while (n > 2)
{
c = a + b; // 1 1 2 3 5 8 13 21 34 55
a = b; // 1 2 3 4 5 6 7 8 9 10
b = c;
n--;
count = count + 1;
}
printf("count = %d\n", count);
return c;
}
int main(void)
{
int n = 0;
printf("请输入数字:");
scanf("%d", &n);
int c = Fib(n);
printf("c = %d\n", c);
return 0;
}
那么这里我再把递归的代码给大家看看!来用递归的形式做斐波那契数列,大家可以看下区别,但是如上所说这里使用递归做不合适。
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int Fib(int n)
{
if (n <= 2)
return 1;
else
return Fib(n - 1) + Fib(n - 2);
}
int main(void)
{
int n = 0;
printf("请输入数字:");
scanf("%d", &n);
int ret = Fib(n);
printf("ret = %d\n", ret);
return 0;
}
那么这里我们需要知道为什么用递归的方法不合适做斐波那契数列。 从上面的代码可以看出用递归的方法代码比前面的方法简短了不少,直接将斐波那契数列的递推公式带入即可实现。但是,用递归实现的话会有一个非常大的缺点:效率低下(算法的运行速度比较慢),尤其是当我们输入的n较大时。那么程序计算的时候需要的时间很长,这是因为程序在每次递归调用自己时都需要算一遍(n-1)和(n-2)项,存在很多重复计算。不考虑栈溢出的情况。
看到这里你是否对函数&递归有了一个了解,实际上这些东西都需要自己上手自己敲代码去实践的。这样对你理解的帮助很大,尤其是在这里所说的递归。不懂的话多去F10进行调试,调试对你学习帮助是非常大的(o=^•ェ•)o┏━