期末复习⚡考试月来临!C语言复习,这一篇带你逃离挂科区!(完结)
Posted 小丞同学
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了期末复习⚡考试月来临!C语言复习,这一篇带你逃离挂科区!(完结)相关的知识,希望对你有一定的参考价值。
我又回来啦!距离上一篇博文发出不到4个小时,这篇博文又启动了,希望顺顺利利,快逃离挂科区!😢
如果没有看过上节的可以链接跳转噢~C语言复习(上),这篇会接着上一篇写噢~
如果需要源文件,可以私信我噢!
4. 函数
函数是具有一定功能的一个模块,所谓函数名就是给该功能起了一个名字。
注意:函数就是功能。每一个函数用来实现一个特定的功能。函数的名字应反映出它代表的功能,这样代码的可读性会大大提升
记得在上一篇中有这么一句话,“一个C程序可由一个主函数和若干个其他函数构成。” C语言是一门完全面向过程的语言,在程序设计中要善于利用函数,以减少重复代码的编写,尽量的减少代码冗余,这样也能提高代码的可维护性,也更便于实现模块化的程序设计。
4.1 定义一个函数
通过了上面的解释,相信已经加深了对函数的理解以及对函数的作用有了进一步的认识。
定义一个函数需要包括以下几个内容:
- 指定函数的名字,以便以后的函数调用
- 指定函数的类型,即函数的返回值的类型
- 指定函数的参数的名字和类型,以便在调用函数的时候传递参数
- 书写函数的功能,这是函数的核心内容
4.1.1 定义一个无参函数
当函数不需要接受用户传递的数据时,不需要带参数
返回值类型 函数名() {
功能
}
下面我们来定义一个计算1加到100的函数
int sum()
{
int i, sum = 0;
for (i = 1; i <= 100; i++)
{
sum += i;
}
return sum;
}
通过将计算的结果保存到sum
中,最后通过return
语句返回给函数的调用者,返回值的类型是int
4.1.2 无返回值函数
有些函数不需要返回值,只需要执行代码即可,我们可以通过void
来指定返回值的类型
void hello() }{
printf("hello world");
}
这个函数没有返回值,它的功能就是输出hello world
,类型void
表示空类型或者无类型,大多数意味着无return语句
4.1.3 定义有参函数
当函数需要接收用户传递的数据,那么定义时就需要带上参数
int max(int x, int y) {
int z;
z = x > y ? x : y;
return z;
}
上面的函数功能是求两个数中较大的那个数。在主函数调用时,将数据传递给形参x,y,在函数体内判断两个书中较大的数,并通过return语句返回值返回给函数的调用者
注意:
- 参数的数据说明可以省略,默认值是
int
类型 - 函数名称需要遵循标识符命名规范
- 函数需要先定义后使用,如果函数写在了
main
函数后面,需要将函数的声明写到main
中函数调用之前 - 在C语言中不允许函数的嵌套定义
4.2 调用函数
调用函数的方式非常简单,以调用上面计算两数中最大值为例
c = max(a, b);
这样我们就能实现了函数的调用,将 a,b 传给max
函数,函数执行完毕后返回值z
的值赋值给c,这样c就得到了a,b中较大数的值
下面我们编写一个程序来练练手
输入两个整数,要求输出其中值较大者,使用函数来实现
首先我们先编写max函数,用来返回两个数中的较大者
int max(int x, int y) {
int z;
z = x > y ? x : y;
return z;
}
接下来我们编写主函数
int main()
{
int a, b, c;
printf("请输入两个数\\n");
scanf("%d,%d", &a, &b);
c = max(a, b);
printf("max is %d", c);
return 0;
}
在主程序中接收两个用户输入的数据a,b,通过函数返回大的数,实现功能
注意:程序从上向下执行,当碰到函数名后,把值传给调用函数,当程序得到了返回值或调用函数结束,再继续往下执行。
4.3 形参和实参的区别
在上一部分中,我们复习了如何定义和调用函数。
如果函数是一个加工厂的话,那么函数的参数就是工厂的原材料,返回值就是经过加工的产品。
重要
-
形参只有在函数被调用时才会分配内存,调用结束后,会立刻释放内存,因此形参变量只有在函数内部有效,不能在函数外部使用。
-
实参和形参在数量上、类型上、顺序上必须严格一致,否则会发生“类型不匹配”的错误。如果会自动类型转换,或者进行了强制类型转换,那么实参类型也可以与形参类型不同。
-
函数调用中发生的数据传递是单向的,只能把实参的值传递给形参,而不能把形参的值反向地传递给实参。也就是改变形参不会影响实参的值。
注意:传数值,形参的变化不会改变实参的变化;传地址,形参的变化就有可能改变实参所对应的值
4.4 函数的嵌套调用
函数不能嵌套定义,但可以嵌套调用,也就是在一个函数的定义或调用过程中允许出现对另外一个函数的调用。这部分的内容过于简单就不过多阐述,就是在一个函数里调用另一个函数,无限套娃
4.5 函数的递归调用
在调用一个函数的过程中又出现直接或间接地调用该函数本身,称为递归调用,这也是C语言的特点之一。递归函数在解决许多数学问题上起了至关重要的作用,比如计算一个数的阶乘、生成斐波那契数列,等等。
注意:递归必须要有一个结束的条件,否则可能会无限的调用死循环
下面通过递归来输出斐波那契数列的第n项
#include <stdio.h>
int Fib(int n)
{
if (n == 0)
{
return 1;
}
else if (n == 1)
{
return 1;
}
else
return Fib(n - 1) + Fib(n - 2);
}
int main()
{
int n, ret;
printf("请输入n:");
scanf("%d", &n);
ret = Fib(n);
printf("%d\\n", ret);
return 0;
}
重要语句return Fib(n - 1) + Fib(n - 2);
通过return再次调用这个函数,直至到达结束的条件。
4.6 全局变量和局部变量
4.6.1 局部变量
定义在函数内部的变量称为局部变量,它的作用域仅限于函数内部, 离开该函数后就是无效的,再使用就会报错。
int name(int a)
{
int b, c;
return a + b + c;
}
int main()
{
int x, y;
return 0;
}
- 在 main 函数中定义的变量也是局部变量,只能在函数中使用。main 函数也是一个函数,与其他函数平等地位
- 实参给形参传值的过程也就是给局部变量赋值的过程
- 可以在不同的函数中使用相同的变量名,它们表示不同的数据,分配不同的内存,互不干扰。(我偷偷的把它理解为js中的块级作用域)
4.6.2 全局变量
声明在函数外部的变量称为全局变量,它的作用域是整个作用域,也就是整个文件
4.6.3 练习题
输入长方体的长宽高求它的体积以及三个面的面积。(编写一个函数实现)
#include <stdio.h>
int s1, s2, s3; //面积
int volume(int a, int b, int c)
{
int v; //体积
v = a * b * c;
s1 = a * b;
s2 = b * c;
s3 = a * c;
return v;
}
int main()
{
int v, length, width, height;
printf("输入长宽高: ");
scanf("%d %d %d", &length, &width, &height);
v = volume(length, width, height);
printf("v=%d, s1=%d, s2=%d, s3=%d\\n", v, s1, s2, s3);
return 0;
}
采用了三个全局变量,来记录三个面的面积,这样通过main函数可以直接通过访问全局变量来获取到对应面积的值,通过返回值来得到体积v
注意:建议在不必要的情况下不要使用全局变量(这个在其他语言中也是同样的)
原因:
- 全局变量在程序的全部执行过程中都要占用存储单元,而不是仅在需要时才开辟单元
- 它使函数的通用性降低了,不利于模块化编程
- 降低了代码的可读性
4.7 内部函数和外部函数
- 不能被其他源文件调用的函数称谓内部函数 ,内部函数由
static
关键字来定义,因此也叫静态函数,如static int max()
- 能被其他源文件调用的函数称谓外部函数 ,外部函数由extern关键字来定义,形式为
extern int max()
- 在没有指定函数的作用范围时,系统会默认为外部函数
// hello.c文件
void hello()
{
printf("hello world");
}
//main.c文件
#include<stdio.h>
#include "hello.c"
int main() {
hello();
return 0;
}
4.8 练习题
第一题
在 C 语言中,有关函数的说法,以下正确的是 。
A. 函数可嵌套定义,也可嵌套调用 B. 函数可嵌套定义,但不可嵌套调用
C. 函数不可嵌套定义,但可嵌套调用 D. 函数不可嵌套定义,也不可嵌套调用
答案:C
第二题
有一个函数原型如下所示,则该函数的返回类型为( ) 。
abc(float x,float y)
A. void B. double C. int D. float
答案:C 默认int
第三题
C语言中,以下叙述中错误的是( )。
A) 主函数中定义的变量是全局变量
B) 同一程序中,全局变量和局部变量可以同名
C) 全局变量的作用域从定义处开始到本源程序文件结束
D) 局部变量的作用域被限定在其所定义的局部范围中
答案:A 函数同级
5. 指针
每一个变量都有一个内存位置,可使用 & 取地址符来访问它的内存地址,它表示了在内存中的一个地址。🍟
指针也就是内存地址,指针变量是用来存放内存地址的变量。就像其他变量或常量一样,必须在使用指针存储其他变量地址之前,对其进行声明。
格式为:类型 *变量名
int *ip; /* 一个整型的指针 */
double *dp; /* 一个 double 型的指针 */
float *fp; /* 一个浮点型的指针 */
char *ch; /* 一个字符型的指针 */
不同数据类型的指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同。
形象的说:一个房间的门口挂了一个房间号1304,这个1304就是房间的地址,或者说1304指向这个房间。因此把地址形象化的称为指针。
指针变量可指向任意一种数据类型,但不管指向的数据占用多少字节,一个指针变量占用四个字节。
5.1 指针变量
存放地址的变量是指针变量,它用来指向另一个对象
注意:在定义指针变量时,必须确定指针类型。例如:int
变量的指针需要用int
类型指针来存储。
区分指针变量和指针,指针是一个地址,而指针变量是存放地址的变量
5.1.1 使用指针变量
通过指针变量访问整型变量
#include <stdio.h>
int main()
{
int a = 100;
int *pointer;
pointer = &a;
printf("*pointer的值是%d", *pointer);
return 0;
}
先定义一个整型变量,再定义一个指针变量,指向这个整型变量,通过访问指针变量可以找到它所指向的变量,从而得到变量的值
5.1.2 定义指针变量
定义指针变量需要在变量名前面加星号*
,例如
int *try;
*
表示这是一个指针变量,int
表示该指针变量所指向的数据的数据类型是int
类型
int a = 100;
int *p = &a;
在定义指针变量p
时同时对它进行初始化,将变量a的地址赋予它,此时p
就指向了a
注意:
- 指针变量
p
需要接收的是一个地址,因此需要使用&
取地址符,来获取a
的地址 - 定义指针变量时必须带
*
号,给指针变量赋值时不能带*
号 - 指针变量
p
的类型是int *
而不是int
噢,它们是完全不同的,考试避坑噢~
5.1.3 引用指针变量
- 给指针变量赋值
p = &a; //a的地址赋值给指针变量p
指针变量 p 的值是变量 a 的地址,p 指向 a
- 引用指针变量指向的变量
p = &c;
printf("%d",*p);
printf("%d",*p);
的意思是:一整数形式输出指针变量 p 指向的变量的值
*p = 1;
表示将整数1
赋值给 p 所指向的变量,即c = 1
- 引用指针变量的值
printf("%o",p);
作用是以八进制形式输出指针变量 p 的值,如果 p 指向了 a ,输出的就是 a 的地址
*和&:这两个符号的作用是相对的,可以理解为&
是取变量的内存地址,*
号是取地址上的变量值,也就是解引的作用
int a;
*&a = a;
*&a
可以理解为*(&a)
,&a
表示取变量 a 的地址,*(&a)
表示取这个地址上的数据
5.2 指针变量作为函数参数
在前面的函数部分中,我们有说到
“传数值,形参的变化不会改变实参的变化;传地址,形参的变化就有可能改变实参所对应的值”
指针变量作为函数参数就是传地址的情况,这能帮助我们解决一些问题。一个典型的例子就是交换两个变量的值
当我们想要交换两个变量时,我们可以声明一个swap
交换函数,交换两个变量的值,但是,采用常规的值传递的方式是行不通的
#include <stdio.h>
void swap(int a, int b){
int temp;
temp = a;
a = b;
b = temp;
}
int main(){
int a = 1, b = 2;
swap(a, b);
printf("a = %d, b = %d", a, b);
return 0;
}
因为函数内部的a,b都是局部变量,它们占用着不同的内存,改变swap
函数中的a,b值,不会影响到main
函数中a,b的值
采用用指针变量作为函数参数,就可以解决这个问题,因为参数的传递是内存地址,外部函数,直接通过修改内存地址上的值,来完成操作
#include <stdio.h>
void swap(int *p1, int *p2){
int temp;
temp = *p1;
*p1 = *p2;
*p2 = temp;
}
int main(){
int a = 1, b = 2;
swap(&a, &b);
printf("a = %d, b = %d", a, b);
return 0;
}
5.3 通过数组引用指针
5.3.1 数组元素的指针
🎉所谓数组元素的指针就是数组元素的地址
可以用一个指针变量指向一个数组元素,例如
int a[3] = {1, 2, 3};
int *p;
p = &a[0]
引用数组元素可以采用下标法a[1]
,也可以采用指针法,指针法占用内存更少,运行速度更快
在数组部分,我们知道数组名称代表了首元素的地址,因此我们可以直接写p = a
,来指向数组的第一个元素
5.3.2 在引用数组元素时的运算
在指针已经指向一个数组元素的情况下,可以进行下列运算
p + 1
:指向同一数组中的下一个元素
p - 1
:指向同一数组的上一个元素
注意:p + 1
不是简单的数值上的加一,而是加上一个数组元素所占的字节数,如float类型数组一个元素占4个字节,p的值就加4,也就指向了下一个元素
如果p的初值指向的是数组的第一个元素a[0]
那么可想而知p + i
就能访问到数组元素a[i]
的地址,例如p + 9
的值是&a[9]
,同样的我们获取到了了数组元素的地址,就可以采用*
号来得到它的值,如*(p + i) = a[i]
5.3.3 练习题
🎉通过指针变量输出整型数组a的10个元素
#include <stdio.h>
int main()
{
int *p, i, a[10];
p = a; //a的地址
printf("请输入10个整数:\\n");
for (i = 0; i < 10; i++)
{
scanf("%d", p++);
}
p = a;//由于指针p在遍历的过程改变了,因此要重新赋值
for (i = 0; i < 10; i++, p++)
{
printf("%d ", *p);
}
return 0;
}
在遍历时,采用了p++
,在每一次遍历结束后,将指针p
指向数组的下一位
5.4 指针数组和数组指针
指针数组:它是一个数组,数组的元素都是指针,数组占多少个字节由数组本身决定。也叫做存放指针的数组
数组指针:它是一个指针,它指向一个数组。在32 位系统下永远是占4 个字节,也叫做指向数组的指针
5.5 通过指针引用字符串
在前面我们也有提到过“c语言中没有字符串变量”,但是可以通过字符数组和字符指针的方式存储字符串
我们先从一个简单的题目入手
通过字符指针变量输出一个字符串
#include <stdio.h>
int main()
{
//定义一个字符指针来存储字符串
char *string = "i am ljc";
// 输出
printf("%s", string);
return 0;
}
在上面的代码中成功的输出了i am ljc
,我们可以知道,在输出string时,字符指针最开始指在了字符串的第一个字符,又因为字符串在内存中占据的是连续的内存空间,在输出控制符%s
下,系统会自动的输出字符串的第一个字符,然后让字符指针指向下一个字符,直至遇到\\0
结束。
字符指针变量和字符数组的比较
- 字符数组由若干个元素组成,每个元素中放一个字符,而字符指针变量中存放的是地址
- 赋值方式不同,可以对字符指针变量赋值,而不能对数组名赋值
- 存储单元不同,编译时字符数组分配若干存储单元,以存放各元素的值,而对字符指针变量,只占4各字节(不同编译器可能不同)
- 指针变量的值是可以改变的,而字符数组名代表一个固定的值,不能改变
5.6 指针作为函数返回值
当函数的返回值是一个指针时,把这个函数称为指针函数
#include <stdio.h>
#include <string.h>
char *longStr(char *str1, char *str2){
if(strlen(str1) >= strlen(str2)){
return str1;
}else{
return str2;
}
}
上面的代码定义了一个返回长度较长字符串的函数
在使用指针函数时要注意,函数运行结束后会销毁在它内部定义的所有局部数据,函数返回的指针不要指向这些数据
5.7 练习题
🎋第一题
有定义:int x,*p;,能使指针变量p指向变量x的语句是( )。
A)
*p=&x;
B)p=&x;
C)*p=x;
D)p=*&x;
答案:B
🎄第二题
若有语句int *p, a=10; p=&a; 下面均代表地址的一组选项是()。
A. a, p, *&a
B. &*a, &a, *p
C.
*&p, *p,&a
D.&a, &*p, p
答案:D
🎍第三题
若已定义char s[10];则在下面表达式中不表示s[1]地址的是()。
A. s+1 B. s++ C. &s[0]+1 D. &s[1]
答案:B
🧶第四题
若定义:int a=511, *b=&a;则printf("%d\\n", *b);的输出结果为:
A. 无确定值 B. a的地址 C. 512 D. 511
答案:D
🔋第五题
下面程序中输出几个
*
A. 9 B. 5 C. 6 D.7
#include <stdio.h>
int main()
{
char *s = "\\ta\\018bc";
for (; *s != '\\0'; s++)
{
printf("*");
}
}
答案:C \\0
是转义字符表示后面是个8进制数
定 义 | 含 义 |
---|---|
int *p; | p 可以指向 int 类型的数据 |
int **p; | p 为二级指针,指向 int * 类型的数据 |
int *p[n]; | p 为指针数组。[ ] 的优先级高于 *,所以应该理解为 int *(p[n]); |
int (*p)[n]; | p 为二维数组指针 |
int *p(); | p 是一个函数,它的返回值类型为 int * |
int (*p)(); | p 是一个函数指针,指向原型为 int function() 的函数 |
6. 结构体
结构体从本质上来讲是一种自定义的数据类型,但是这种数据类型比较复杂,它是由 int
、char
、float
等多种基本类型组成的
从前端js的角度去思考,我会把结构体形象为js中的对象
这部分没有写链表的内容,在我之前的博文中有写到了用js实现链表的完整操作思路,实际上思路都一致,只是语法不同,就不过多阐述,有兴趣的可以看之前的博文:一文带你拿下前端必备数据结构 – 链表 !!
6.1 定义和使用结构体
6.1.1 建立结构体类型
可以使用结构体struct
来存放一组不同的数据类型
下面采用结构体来存储一个成员的个人信息
struct people
{
char *name; //姓名
int num; //学号
int age; //年龄
float score; //成绩
};
people
为结构体的名字,里面包含了4个成员。结构体成员的定义方式与变量和数组的定义方式相同,只是不能初始化。
特别注意:结构体后的花括号需要打分号
6.1.2 定义结构体变量
在上面我们定义了一个结构体类型,我们可以用它来定义变量
struct people s1, s2;
这样我们就定义了两个变量s1,s2,它们的类型都是people类型,它们都有4个成员组成。形象的说:定义出来的people
就相当于一个模板,该类型的变量都会有它的特性
struct people
{
char *name; //姓名
int num; //学号
int age; //年龄
float score; //成绩
} s1, s2;
也可以直接在结构体最后定义变量
结构体的各个成员中在内存中是连续存储的,和数组相似,但是由于结构体中的数据类型复杂,各个成员间存在着间隙,因此存在着结构体内存对齐的问题!
6.1.3 读写结构体成员的值
使用点号.
获取单个成员,也可以给成员赋值。.
号叫做成员访问运算符!
学前端的现在可以舒一口气了,这个和对象太像了,其实学习一门编程语言当你学到了它的思想后,学其他的语言都会很轻松的,所以各位一定要先学踏过门槛~冲冲冲
通过这样的方式可以获取成员的值,也可以赋值
#include <stdio.h>
int main()
{
struct people
{
char *name; //姓名
int num; //学号
int age; //年龄
float score; //成绩
};
struct people my;
my.name = "LJC";
my.num = 1023;
my.age = 19;
my.score = 100;
printf("姓名:%s,年龄:%d,学号:%d,分数:%.1f", my.name, my.age, my.num, my.score);
return 0;
}
运行结果:姓名:LJC,年龄:19,学号:1023,分数:100.0
这样我们就实现了对结构体变量的赋值。
🎃 结构体是一种自定义的数据类型,是创建变量的模板,不占用内存空间。只有在创建结构体变量的时候才会占用内存空间!!!
6.2 结构体数组
结构体数组指数组中的每个元素都是结构体,这样就非常的方便了,我们可以把一个班级的学生放在一个结构体数组里,这样一个班级学生都绑定上了结构体中的成员
struct stu{
char *name; //姓名
int num; //学号
int age;以上是关于期末复习⚡考试月来临!C语言复习,这一篇带你逃离挂科区!(完结)的主要内容,如果未能解决你的问题,请参考以下文章
期末复习考试月来临!☀️C语言复习,这一篇带你逃离挂科区!(上)⭐️