IE盒子

搜索
查看: 121|回复: 1

C语言学习教程(八)

[复制链接]

4

主题

6

帖子

14

积分

新手上路

Rank: 1

积分
14
发表于 2022-12-30 17:06:20 | 显示全部楼层 |阅读模式
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("unexist.txt", "rb");
     if(fp == NULL){
         fprintf(stderr, "Value of errno : %d\n", errno);
         
         errnum = errno;
         perror("Error printed by perror");
         fprintf(stderr, "Error opening file : %s", 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, "Division by zero! Exiting...\n");
         exit(-1);
     }
     quotient = dividend / divisor;
     printf("Value of quotient : %d\n", 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, "Division by zero! Exiting...\n");
         exit(EXIT_FAILURE);
     }
     quotient = dividend / divisor;
     printf("Value of quotient : %d\n", 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("Factorial of %d is %d\n", i, factorial(i));

     return 0;
}运行结果:
$ gcc -o test1 test1.c
$ ./test1
Factorial of 15 is 2004310016(2)斐波那契数列

斐波那契数列(Fibonacci sequence),又称黄金分割数列,因数学家莱昂纳多·斐波那契(Leonardo Fibonacci)以兔子繁殖为例子而引入,故又称为"兔子数列",指的是这样一个数列: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("%d\n", 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_startva_argva_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("Average of 2, 3, 4, 5 = %f\n", average(4, 2,3,4,5));
     printf("Average of 5, 10, 15 = %f\n", 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, "Zara Ali");

     description = malloc(200 * sizeof(char));

     if(description == NULL){
         fprintf(stderr, "Error - unable to allocate required memory\n");
     }else{
         strcpy(description, "Zara Ali a DPS student in calss 10th");
     }

     printf("Name = %s\n", name);
     printf("Description : %s\n", 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, "Zara Ali");

     description = malloc(30 * sizeof(char));

     if(description == NULL){
         fprintf(stderr, "Error - unable to allocate required memory\n");
     }else{
         strcpy(description, "Zara Ali a DPS student.");
     }

     //假设你需要存储更大的描述信息
     description = realloc(description, 100 * sizeof(char));

     if(description == NULL){
         fprintf(stderr, "Error - unable to allocate required memory\n");
     }else{
         strcat(description, "She is in class 10th");
     }


     printf("Name = %s\n", name);
     printf("Description : %s\n", 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("The argument supplied is : %s\n", argv[1]);
     }else if(argc > 2){
         printf("Too many arguments supplied.\n");
     }else{
         printf("One argument expected.\n");
     }

     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。
多个命令行参数之间应该用空格分隔开,但是如果参数本身带有空格,那么传递参数的时候就应该把参数放置在双引号""或单引号 '' 内部。
让我们重新编写上面的示例,我们将打印当前程序名称,并且我们还通过在双引号内输入命令行参数:
#include <stdio.h>


int main(int argc, char *argv[]){

     printf("Program name is : %s\n", argv[0]);

     if(argc == 2){
         printf("The argument supplied is : %s\n", argv[1]);
     }else if(argc > 2){
         printf("Too many arguments supplied.\n");
     }else{
         printf("One argument expected.\n");
     }

     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 "hello Alice"
Program name is : ./test2
The argument supplied is : hello Alice
回复

使用道具 举报

0

主题

2

帖子

4

积分

新手上路

Rank: 1

积分
4
发表于 昨天 15:36 | 显示全部楼层
占位编辑
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表