|
C语言学习教程(八):本系列教程第26-30章。
本篇是本系列教程的最后一篇文章,希望大家能够通过本系列教程的学习有所收获。
完结,撒花。。。
26-Error Handling
C 语言不提供对错误处理的直接支持,但是作为一种系统编程语言,它以返回值的形式允许你访问底层数据。在发生错误时,大多数的 C 或 UNIX 函数调用返回 1 或 NULL,同时会设置一个错误代码 errno,该错误代码是全局变量,表示在函数调用期间发生了错误。您可以在 errno.h 头文件中找到各种各样的错误代码。
所以,C 程序员可以通过检查返回值,然后根据返回值决定采取哪种适当的动作。开发人员应该在程序初始化时,把 errno 设置为 0,这是一种良好的编程习惯。0 值表示程序中没有错误。
(1)perror()和strerror()
C 语言提供了 perror() 和 strerror() 函数来显示与 errno 相关的文本消息。
- perror() 函数输出显示你传给它的字符串。并自动在后面跟一个冒号、一个空格和当前 errno 值的文本表示形式。
- strerror() 函数,返回一个指针,指针指向当前 errno 值的文本表示形式。
让我们来模拟一种错误情况,尝试打开一个不存在的文件。你可以使用多种方式来输出错误消息,在这里我们使用函数来演示用法。另外有一点需要注意,你应该使用 stderr 文件流来输出所有的错误。
#include <stdio.h>
#include <errno.h>
#include <string.h>
extern int errno;
int main(){
FILE *fp = NULL;
int errnum;
fp = fopen(&#34;unexist.txt&#34;, &#34;rb&#34;);
if(fp == NULL){
fprintf(stderr, &#34;Value of errno : %d\n&#34;, errno);
errnum = errno;
perror(&#34;Error printed by perror&#34;);
fprintf(stderr, &#34;Error opening file : %s&#34;, strerror(errnum));
}else{
fclose(fp);
}
return 0;
}运行结果:
$ gcc -o test1 test1.c
$ ./test1
Value of errno : 2
Error printed by perror: No such file or directory
Error opening file : No such file or directory%(2)除以零错误
一个常见的问题是,在除任何数时,程序员不检查除数是否为零,最后会产生运行时错误。
下面的代码通过在除法之前检查除数是否为零来解决此问题:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int dividend = 20; //被除数
int divisor = 0; //除数
int quotient; //商
if(divisor == 0){
fprintf(stderr, &#34;Division by zero! Exiting...\n&#34;);
exit(-1);
}
quotient = dividend / divisor;
printf(&#34;Value of quotient : %d\n&#34;, quotient);
exit(0);
}运行结果:
$ gcc -o test2 test2.c
$ ./test2
Division by zero! Exiting...(3)程序退出状态
通常情况下,程序成功执行完一个操作正常退出的时候会带有值EXIT_SUCCESS。在这里,EXIT_SUCCESS是宏,它被定义为 0。如果程序中存在一种错误情况,当退出程序时,会带有状态值EXIT_FAILURE,被定义为 -1。所以,上面的程序可以写成:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int dividend = 20; //被除数
int divisor = 5; //除数
int quotient; //商
if(divisor == 0){
fprintf(stderr, &#34;Division by zero! Exiting...\n&#34;);
exit(EXIT_FAILURE);
}
quotient = dividend / divisor;
printf(&#34;Value of quotient : %d\n&#34;, quotient);
exit(EXIT_SUCCESS);
}运行结果:
$ gcc -o test3 test3.c
$ ./test3
Value of quotient : 4
27-Recursion
递归(Recursion)指的是在函数的定义中使用函数自身的方法。
递归的语法格式如下:
void recursion()
{
recursion(); //function calls itself
}
int main() {
recursion();
}C 语言支持递归,即一个函数可以调用其自身。但在使用递归时,程序员需要注意定义一个从函数退出的条件,否则会进入死循环。
递归函数对于解决许多数学问题非常有用,例如计算数字的阶乘、生成斐波那契数列等。
(1)数的阶乘
下面的示例使用递归函数计算一个给定的数的阶乘:
#include <stdio.h>
int factorial(unsigned int i){
if(i <= 1){
return 1;
}
return i * factorial(i-1);
}
int main(){
int i = 15;
printf(&#34;Factorial of %d is %d\n&#34;, i, factorial(i));
return 0;
}运行结果:
$ gcc -o test1 test1.c
$ ./test1
Factorial of 15 is 2004310016(2)斐波那契数列
斐波那契数列(Fibonacci sequence),又称黄金分割数列,因数学家莱昂纳多·斐波那契(Leonardo Fibonacci)以兔子繁殖为例子而引入,故又称为&#34;兔子数列&#34;,指的是这样一个数列:1、1、2、3、5、8、13、21、34、……在数学上,斐波那契数列以如下被以递推的方法定义:F(0)=0,F(1)=1, F(n)=F(n - 1)+F(n - 2)(n ≥ 2,n ∈ N*)在现代物理、准晶体结构、化学等领域,斐波那契数列都有直接的应用,为此,美国数学会从 1963 年起出版了以《斐波那契数列季刊》为名的一份数学杂志,用于专门刊载这方面的研究成果。

示例代码:
#include <stdio.h>
int fibonaci(int i){
if(i == 0){
return 0;
}
if(i == 1){
return 1;
}
return fibonaci(i-1) + fibonaci(i-2);
}
int main(){
int i;
for (int i = 0; i < 10; i++)
{
printf(&#34;%d\n&#34;, fibonaci(i));
}
return 0;
}运行结果:
$ gcc -o test2 test2.c
$ ./test2
0
1
1
2
3
5
8
13
21
34
28-Variable Arguments
有时,你可能会碰到这样的情况,你希望函数带有可变数量的参数,而不是预定义数量的参数。C 语言为这种情况提供了一个解决方案,它允许你定义一个函数,能根据具体的需求接受可变数量的参数。下面的示例演示了这种函数的定义。
int func(int, ... )
{
. . .
}
int main() {
func(1, 2, 3);
func(1, 2, 3, 4);
}请注意,函数 func() 最后一个参数写成省略号,即三个点号(...),省略号之前的那个参数是 int,代表了要传递的可变参数的总数。为了使用这个功能,您需要使用 stdarg.h 头文件,该文件提供了实现可变参数功能的函数和宏。
具体步骤如下:
步骤1:定义一个函数,最后一个参数为省略号(...),省略号前面可以设置自定义参数。
步骤2:在函数定义中创建一个 va_list 类型变量。
步骤3:使用 int 参数和 va_start 宏来初始化 va_list 类型的变量为一个参数列表。
步骤4:使用 va_arg 宏和 va_list 变量来访问参数列表中的每个项。
步骤5:使用宏 va_end 来清理赋予 va_list 变量的内存。
数据类型 va_list 和宏 va_start、va_arg、va_end都是在 stdarg.h 头文件中定义的。 现在让我们按照上面的步骤,来编写一个带有可变数量参数的函数,并返回它们的平均值:
#include <stdio.h>
#include <stdarg.h>
double average(int num, ...){ //步骤1
int i;
double sum = 0.0;
va_list valist; //步骤2
va_start(valist, num); //步骤3
for (i = 0; i < num; i++) {
sum += va_arg(valist, int); //步骤4
}
va_end(valist); //步骤5
return sum / num;
}
int main(){
printf(&#34;Average of 2, 3, 4, 5 = %f\n&#34;, average(4, 2,3,4,5));
printf(&#34;Average of 5, 10, 15 = %f\n&#34;, average(3, 5,10,15));
return 0;
}建议本段程序通过IDE来编写,放置鼠标在相应的函数名(eg:va_start)上面来查看函数的详细信息。 运行结果:
$ gcc -o test1 test1.c
$ ./test1
Average of 2, 3, 4, 5 = 3.500000
Average of 5, 10, 15 = 10.000000
29-Memory Management
本章将解释 C 中的动态内存管理。 C 编程语言提供了多种内存分配和管理功能。这些函数可以在 <stdlib.h> 头文件中找到。
函数名 | 描述 | void *calloc(int num, int size); | 在堆中,分配一块 num 个长度为 size 的连续空间,并将每一个字节都初始化为 0。所以它的结果是分配了 num*size 个字节长度的内存空间,并且每个字节的值都是0。 | void *malloc(int num); | 在堆中,分配一块 num 字节数组大小的内存空间,用来存放数据。这块内存空间在函数执行完成后不会被初始化,它们的值是随机数据。 | void free(void *address); | 释放由 address 指针所指向的一块内存块。 | void *realloc(void *address, int newsize); | 重新分配 address 指针所指向的内存块,把内存块扩展到 newsize 大小。 |
注意:void * 类型表示未确定类型的指针。C、C++ 规定 void * 类型可以通过类型转换强制转换为任何其它类型的指针。 (1)动态分配内存
编程时,如果预先知道数组的大小,那么定义数组时就比较容易。
例如,一个存储人名的数组,它最多容纳 100 个字符,所以您可以定义数组,如下所示:
char name[100];但是,如果预先不知道需要存储的文本长度,例如你想存储有关一个主题的详细描述。在这里,我们需要定义一个指针,该指针指向未定义所需内存大小的字符,后续再根据需求来分配内存,如下所示:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(){
char name[100];
char *description;
strcpy(name, &#34;Zara Ali&#34;);
description = malloc(200 * sizeof(char));
if(description == NULL){
fprintf(stderr, &#34;Error - unable to allocate required memory\n&#34;);
}else{
strcpy(description, &#34;Zara Ali a DPS student in calss 10th&#34;);
}
printf(&#34;Name = %s\n&#34;, name);
printf(&#34;Description : %s\n&#34;, description);
return 0;
}运行结果:
$ gcc -o test1 test1.c
$ ./test1
Name = Zara Ali
Description : Zara Ali a DPS student in calss 10th上面的程序也可以使用 calloc() 来编写,只需要把 malloc 替换为 calloc 即可,如下所示:
calloc(200, sizeof(char));当动态分配内存时,你有完全控制权,可以传递任何大小的值。而那些预先定义了大小的数组,一旦定义则无法改变大小。
(2)重新调整内存的大小和释放内存
当程序退出时,操作系统会自动释放所有分配给程序的内存,但是,建议在不需要内存时,都应该调用函数 free() 来释放内存。或者,通过调用函数 realloc() 来增加或减少已分配的内存块的大小。下面让我们使用 realloc() 和 free() 函数,再次查看上面的实例:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(){
char name[100];
char *description;
strcpy(name, &#34;Zara Ali&#34;);
description = malloc(30 * sizeof(char));
if(description == NULL){
fprintf(stderr, &#34;Error - unable to allocate required memory\n&#34;);
}else{
strcpy(description, &#34;Zara Ali a DPS student.&#34;);
}
//假设你需要存储更大的描述信息
description = realloc(description, 100 * sizeof(char));
if(description == NULL){
fprintf(stderr, &#34;Error - unable to allocate required memory\n&#34;);
}else{
strcat(description, &#34;She is in class 10th&#34;);
}
printf(&#34;Name = %s\n&#34;, name);
printf(&#34;Description : %s\n&#34;, description);
free(description);
return 0;
}运行结果:
$ gcc -o test2 test2.c
$ ./test2
Name = Zara Ali
Description : Zara Ali a DPS student.She is in class 10th
30-Command Line Arguments
执行程序时,可以从命令行传值给 C 程序。这些值被称为命令行参数,它们对程序很重要,特别是当你想从外部控制程序,而不是在代码内对这些值进行硬编码时,就显得尤为重要了。
命令行参数是使用 main() 函数参数来处理的。其中,argc 是指传入参数的个数,argv[] 是一个指针数组,指向传递给程序的每个参数。下面是一个简单的示例,它会检查命令行是否有提供参数,并根据参数执行相应的动作:
#include <stdio.h>
int main(int argc, char *argv[]){
if(argc == 2){
printf(&#34;The argument supplied is : %s\n&#34;, argv[1]);
}else if(argc > 2){
printf(&#34;Too many arguments supplied.\n&#34;);
}else{
printf(&#34;One argument expected.\n&#34;);
}
return 0;
}运行结果:
$ gcc -o test1 test1.c
$ ./test1
One argument expected.
$ ./test1 hello
The argument supplied is : hello
$ ./test1 hello Alice
Too many arguments supplied.需要指出的是,argv[0] 默认存储当前程序的名称,argv[1] 是一个指向第一个命令行参数的指针,*argv[n] 是最后一个参数。如果没有提供任何参数,argc 将被设置为 1。如果只传递了一个参数,argc 将被设置为 2。
多个命令行参数之间应该用空格分隔开,但是如果参数本身带有空格,那么传递参数的时候就应该把参数放置在双引号&#34;&#34;或单引号 &#39;&#39; 内部。
让我们重新编写上面的示例,我们将打印当前程序名称,并且我们还通过在双引号内输入命令行参数:
#include <stdio.h>
int main(int argc, char *argv[]){
printf(&#34;Program name is : %s\n&#34;, argv[0]);
if(argc == 2){
printf(&#34;The argument supplied is : %s\n&#34;, argv[1]);
}else if(argc > 2){
printf(&#34;Too many arguments supplied.\n&#34;);
}else{
printf(&#34;One argument expected.\n&#34;);
}
return 0;
}运行结果:
$ gcc -o test2 test2.c
$ ./test2
Program name is : ./test2
One argument expected.
$ ./test2 hello
Program name is : ./test2
The argument supplied is : hello
$ ./test2 hello Alice
Program name is : ./test2
Too many arguments supplied.
$ ./test2 &#34;hello Alice&#34;
Program name is : ./test2
The argument supplied is : hello Alice |
|