[2024深圳市考][计算机素质测试考纲](二)C语言编程
前言
因篇幅有限,本文仅对考纲中的考点做基本介绍。
更详细的内容请自行学习:【Udemy排名第一的C语言课程】 完整C语言编程入门课程(中英字幕)
一、程序基本组成
程序是一组计算机能识别和执行的指令,运行于电子计算机上,满足人们某种需求的信息化工具。
C语言程序通常由干个源程序文件组成,每一个源文件可以由预处理命令、全局变量以及若干个函数组成。每一个函数则包含函数首部和函数体。
如下图所示:
以下是一段简单的C语言程序代码:
#include<stdio.h>
int main()
{
printf("This is a C program.\n");
return 0;
}
运行后将在控制台中输出:This is a C program.
让我们简单介绍一下这段代码:
- 第一行,预处理命令
#include
主要用来引入头文件。这是一种代码复用的方式,可以减少重复造轮子的窘境,加快开发效率。 - 第二行,
main()
是代码中唯一的一个主函数,程序执行时的入口点。int
标识的是函数的返回值类型。 - 第三行和第六行表示
main
函数的边界,整个函数实现是包含在这一对大括号中的。 - 第四行,调用引入的
stdio.h
文件中的printf
函数,在控制台上输出字符串。 - 第五行,函数返回值为0。
C语言程序源代码通常保存在".c"后缀的文件中。下图为一个C语言程序的编译执行过程:
二、控制结构
程序由三种基本控制结构组成: 顺序结构、选择结构、和循环结构。三种结构的执行过程如下图所示:
顺序结构:接收输入后,依次输出。
选择结构:通常根据if和switch语句的条件判定,进入对应分支。
循环结构:重复执行某块代码若干次。
三、模块设计
1.什么是模块化?
模块化编程就是把我们的一整个项目,分成很多功能模块。比如一个学生成绩查询系统可以分为,登陆,查询,修改保存,退出等多个模块。
2.为何要使用模块化?
- 在实际应用中,如果你将所有的代码都只写在一个程序文件中,那这个文件会变得十分臃肿,不方便查询、维护、交接。
- 在实际开发中,往往存在多人协同的情况。不同的人在不同的PC上同时编辑同一个代码文件时很容易出错。因此我们通常是各自开发相应的模块,再进行集成。
模块化设计的核心思想也正是如此:将系统的各个功能进行封装,变成一个个独立的模块。只需要他人引用提供的函数和变量等,就可以使用相对应的功能。
通常我们在.h
文件里存储类型的定义,函数的声明等,而把对应的实现由同名的.c
文件来做。当他人需要调用你的模块功能的时候,只需要用#include
语句引入相应的.h
文件即可。
当然
.h
文件里也可以封装函数实现,不用非得建立.c
文件。这涉及到不同团队的不同开发规范。
四、数组
C语言支持数组数据结构,它可以存储一个固定大小的相同类型元素的顺序集合。
数组的声明并不是声明一个个单独的变量,比如 runoob0、runoob1、...、runoob99,而是声明一个数组变量,比如 runoob,然后使用 runoob[0]、runoob[1]、...、runoob[99] 来代表一个个单独的变量。
所有的数组都是由连续的内存位置组成,最低的地址对应第一个元素,最高的地址对应最后一个元素。
数组中的特定元素可以通过索引访问,第一个索引值为 0
。
C语言还允许我们使用指针来处理数组,这使得对数组的操作更加灵活和高效。
五、指针
指针也就是内存地址,指针变量是用来存放内存地址的变量。就像其他变量或常量一样,您必须在使用指针存储其他变量地址之前,对其进行声明。指针变量声明的一般形式为:
type *var_name;
在这里,type
是指针的基类型,它必须是一个有效的 C 数据类型,var_name
是指针变量的名称。用来声明指针的星号 *
与乘法中使用的星号是相同的。但是,在这个语句中,星号是用来指定一个变量是指针。
以下是有效的指针声明:
int *ip; /* 一个整型的指针 */
double *dp; /* 一个 double 型的指针 */
float *fp; /* 一个浮点型的指针 */
char *ch; /* 一个字符型的指针 */
所有实际数据类型,不管是整型、浮点型、字符型,还是其他的数据类型,对应指针的值的类型都是一样的,都是一个代表内存地址的长的十六进制数。不同数据类型的指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同。
可使用&
运算符获取变量的访问地址,它代表了在内存中的一个地址。请看下面的实例:
#include <stdio.h>
int main ()
{
int var_runoob = 10;
int *p; // 定义指针变量
p = &var_runoob; // 将变量var_runoob的内存地址赋值给指针变量p
printf("var_runoob 变量的地址: %p\n", p);
printf("p 变量的地址: %p\n", &p);
return 0;
}
当上面的代码被编译和执行时,它会产生类似如下的结果:
var_runoob 变量的地址: 0x7ffeeaae08d8
p 变量的地址: 0x7fff5cc109de
更直观一点,如下图所示:
六、结构体
C语言里的数组允许定义可存储相同类型数据项的变量,而结构体是C编程中另一种用户自定义的可用的数据类型,它允许您同时存储不同类型的数据项。
结构体定义由关键字struct
和结构体名组成,结构体名可以根据需要自行定义。
struct
语句定义了一个包含多个成员的新的数据类型,struct
语句的格式如下:
struct tag {
member-list
member-list
member-list
...
} variable-list ;
tag
是结构体标签。
member-list
是标准的变量定义,比如 int i;
或者 float f;
,或者其他有效的变量定义。
variable-list
结构变量,定义在结构的末尾,最后一个分号之前,您可以指定一个或多个结构变量。
例如,下面是声明 Book 结构的方式:
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
} book;
在一般情况下,tag
、member-list
、variable-list
这 3 部分至少要出现 2 个。
七、文件
一个文件,无论它是文本文件还是二进制文件,都是代表了一系列的字节。C 语言不仅提供了访问顶层的函数,也提供了底层(OS)调用来处理存储设备上的文件。
1.打开文件
您可以使用 fopen( )
函数来创建一个新的文件或者打开一个已有的文件,这个调用会初始化类型 FILE
的一个对象,类型 FILE
包含了所有用来控制流的必要的信息。下面是这个函数调用的原型:
FILE *fopen( const char *filename, const char *mode );
在这里,filename
是字符串,用来命名文件,访问模式 mode
的值可以是下列值中的一个:
模式 | 描述 |
---|---|
r | 打开一个已有的文本文件,允许读取文件。 |
w | 打开一个文本文件,允许写入文件。如果文件不存在,则会创建一个新文件。在这里,您的程序会从文件的开头写入内容。如果文件存在,则该文件原有内容会被覆盖。 |
a | 打开一个文本文件,以追加模式写入文件。如果文件不存在,则会创建一个新文件。在这里,您的程序会在已有的文件内容中追加内容。 |
r+ | 打开一个文本文件,允许读写文件。 |
w+ | 打开一个文本文件,允许读写文件。如果文件已存在,则文件内容会被覆盖,如果文件不存在,则会创建一个新文件。 |
a+ | 打开一个文本文件,允许读写文件。如果文件不存在,则会创建一个新文件。读取会从文件的开头开始,写入则只能是追加模式。 |
如果处理的是二进制文件,则需使用下面的访问模式来取代上面的访问模式:
"rb", "wb", "ab", "rb+", "r+b", "wb+", "w+b", "ab+", "a+b"
2.关闭文件
为了关闭文件,请使用 fclose( )
函数。函数的原型如下:
int fclose( FILE *fp );
如果成功关闭文件,fclose( )
函数返回零,如果关闭文件时发生错误,函数返回 EOF
。这个函数实际上,会清空缓冲区中的数据,关闭文件,并释放用于该文件的所有内存。EOF
是一个定义在头文件 stdio.h
中的常量。
C 标准库提供了各种函数来按字符或者以固定长度字符串的形式读写文件。
3.写入文件
下面是把字符写入到流中的最简单的函数:
int fputc( int c, FILE *fp );
函数 fputc()
把参数 c 的字符值写入到 fp 所指向的输出流中。如果写入成功,它会返回写入的字符,如果发生错误,则会返回 EOF
。您可以使用下面的函数来把一个以 null 结尾的字符串写入到流中:
int fputs( const char *s, FILE *fp );
函数 fputs()
把字符串 s
写入到 fp
所指向的输出流中。如果写入成功,它会返回一个非负值,如果发生错误,则会返回 EOF
。您也可以使用 int fprintf(FILE \*fp,const char \*format, ...)
函数把一个字符串写入到文件中。
请看下面的实例:
#include <stdio.h>
int main()
{
FILE *fp = NULL;
fp = fopen("/tmp/test.txt", "w+");
fprintf(fp, "This is testing for fprintf...\n");
fputs("This is testing for fputs...\n", fp);
fclose(fp);
}
注意:请确保您有可用的 tmp 目录,如果不存在该目录,则需要在您的计算机上先创建该目录。
/tmp 一般是 Linux 系统上的临时目录,如果你在 Windows 系统上运行,则需要修改为本地环境中已存在的目录,例如: C:\tmp、D:\tmp等。
当上面的代码被编译和执行时,它会在 /tmp 目录中创建一个新的文件 test.txt,并使用两个不同的函数写入两行字符串。下一小节里让我们来读取这个文件。
4.读取文件
下面是从文件读取单个字符的最简单的函数:
int fgetc( FILE * fp );
fgetc()
函数从 fp 所指向的输入文件中读取一个字符。返回值是读取的字符,如果发生错误则返回 EOF
。下面的函数允许您从流中读取一个字符串:
char *fgets( char *buf, int n, FILE *fp );
函数 fgets()
从 fp 所指向的输入流中读取 n - 1 个字符。它会把读取的字符串复制到缓冲区 buf
,并在最后追加一个 null
字符来终止字符串。
如果这个函数在读取最后一个字符之前就遇到一个换行符 \n
或文件的末尾 EOF
,则只会返回读取到的字符,包括换行符。您也可以使用 int fscanf(FILE \*fp, const char \*format, ...)
函数来从文件中读取字符串,但是在遇到第一个空格和换行符时,它会停止读取。
请看以下实例:
#include <stdio.h>
int main()
{
FILE *fp = NULL;
char buff[255];
fp = fopen("/tmp/test.txt", "r");
fscanf(fp, "%s", buff);
printf("1: %s\n", buff );
fgets(buff, 255, (FILE*)fp);
printf("2: %s\n", buff );
fgets(buff, 255, (FILE*)fp);
printf("3: %s\n", buff );
fclose(fp);
}
当上面的代码被编译和执行时,它会读取上一部分创建的文件,产生下列结果:
1: This
2: is testing for fprintf...
3: This is testing for fputs...
首先,fscanf()
方法只读取了This
,因为它在后边遇到了一个空格。其次,调用 fgets()
读取剩余的部分,直到行尾。最后,调用 fgets()
完整地读取第二行。
5.二进制 I/O 函数
下面两个函数用于二进制输入和输出:
size_t fread(void *ptr, size_t size_of_elements,
size_t number_of_elements, FILE *a_file);
size_t fwrite(const void *ptr, size_t size_of_elements,
size_t number_of_elements, FILE *a_file);
这两个函数都是用于存储块的读写,通常是数组或结构体。