【C语言】关于我回头学的那些错误处理等(六)

前言

我的第一门语言就是C,但是学艺不精,中途跑去学了C#和Java后,感觉到了C的重要性,毕竟是最接近底层的语言,又跑回来学C。

毕竟前两门的控制语句,变量什么的都是类似的,回到C后只需要学习一些特定C的语法,比如宏,预编译指令等等,这些对我来说都是陌生的词汇。

所以边学边记录一下以前的知识。



一、错误处理

C 语言不提供对错误处理的直接支持,但是作为一种系统编程语言,它以返回值的形式允许您访问底层数据。

在发生错误时,大多数的 C 或 UNIX 函数调用返回 1NULL,同时会设置一个错误代码 errno,该错误代码是全局变量,表示在函数调用期间发生了错误。您可以在 errno.h 头文件中找到各种各样的错误代码。

C语言中的错误处理主要有前两种方式:

  • 返回值
  • 错误码
  • 异常处理
  1. 返回值和错误码

C语言中的函数通常会返回一个值来表示函数执行成功还是失败。如果执行成功,该函数通常返回0或其他非负数;如果执行失败,则返回一个负数作为错误码。

例如,标准库函数fopen()用于打开文件。

  • 成功:返回一个指向文件的指针
  • 失败:返回NULL

另外,在errno.h头文件中定义了一些常量来表示不同类型的错误码。

使用这种方式进行错误处理时,C 程序员可以通过检查返回值,然后根据返回值决定采取哪种适当的动作,开发人员应该在程序初始化时,把 errno 设置为 0。

这种方式比较简单但也比较繁琐,容易出错。

  1. 异常处理

除了使用传统的返回值和错误码来进行错误处理外,C语言也可以通过一些异常处理机制来实现更加灵活和清晰的代码结构。

例如,在C++中就提供了try-catch机制来捕获和处理异常。

另外,在C语言中也可以使用setjmp()longjmp()函数组合来模拟异常处理机制。

  • setjmp():用于保存当前程序状态,并将控制流转移到指定位置
  • longjmp():用于恢复保存的程序状态并跳转到之前设置的位置

使用这种方式进行错误处理时,可以将所有可能引起异常的代码放在try块中,并在catch块中处理异常。

这种方式可以使代码更加简洁和易于维护,同时也是现代C程序设计中的一种主流做法。

总之,C语言的错误处理方式有很多,具体选择哪一种需要根据具体情况而定。对于简单的程序,使用返回值和错误码即可;对于复杂的程序,使用异常处理会更加方便和灵活。

实例

以下是几个C语言错误处理的示例:

  1. 返回值和错误码

假设我们要打开一个文件,如果文件打开成功,就输出文件内容;否则输出错误信息。

在这种情况下,我们可以使用fopen()函数返回的指针来判断是否发生了错误,并在发生错误时设置errno变量。

#include <stdio.h>
#include <errno.h>

int main()
{
    
    
    FILE *fp = fopen("test.txt", "r");
    if(fp == NULL)
    {
    
    
        printf("Error opening file: %s", strerror(errno));
        return 1;
    }
    else
    {
    
    
        char buffer[100];
        while(fgets(buffer, sizeof(buffer), fp) != NULL)
            printf("%s", buffer);
        fclose(fp);
        return 0;
    }
}

解析strerror

  1. 异常处理

假设我们要进行一系列复杂的操作,并且有可能会出现各种不同类型的异常。

在这种情况下,我们可以使用setjmp()longjmp()来模拟异常处理机制,并将所有可能出现异常的代码放在try块中。

#include <stdio.h>
#include <setjmp.h>

jmp_buf exception_buffer;

void catch_exception(const char *msg)
{
    
    
    printf("Exception caught: %s", msg);
    longjmp(exception_buffer, 1);
}

void complex_operation()
{
    
    
    int a = 5, b = 0;
    if(b == 0)
        catch_exception("Division by zero");
    else
        printf("Result: %d", a/b);  
}

int main()
{
    
    
   if(setjmp(exception_buffer) == 0)
        complex_operation();
    else
        printf("Program terminated due to exception");

    return 0;
}

在上述示例中,我们定义了一个catch_exception()函数来处理异常,并使用longjmp()跳转到之前保存的位置。

complex_operation()函数中,我们进行了除法操作并判断除数是否为0。如果除数为0,则调用catch_exception()函数抛出异常。

最后,在main函数中使用setjmp()保存当前程序状态,并在发生异常时跳转到之前设置的exception_buffer位置。

如果没有发生异常,则执行完complex_operation()函数后正常退出程序。


二、可变参数

#include <stdio.h>
#include <stdarg.h>
 
double average(int num,...)//num表示总数量
{
    
    
 	//用于存储可变参数列表
    va_list valist;
    //累加num个参数
    double sum = 0.0;
 
    /* 为 num 个参数初始化 valist */
    va_start(valist, num);
 
    /* 访问所有赋给 valist 的参数 */
    for (int i = 0; i < num; i++)
    {
    
    
       //通过va_arg宏逐个访问valist中的值,并转换为int类型,再加到sum中
       sum += va_arg(valist, int);
    }
    /* 清理为 valist 保留的内存 */
    va_end(valist);
 
 	//返回所有输入整数的平均数
    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));
}

结果如下:

结果

下面是对该函数中每一步的解析:

  1. 首先,在函数头部声明了一个可变参数的函数average,它接收两个参数:num和省略号(…)。其中num代表后续要计算平均数的整数数量,而省略号表示可以包含任意多个整数作为计算平均数的输入。

  2. 在函数内部定义一个va_list类型的变量valist,用于存储可变参数列表。

  3. 后面通过调用va_start宏来初始化valist。这个宏可以设置valist指向参数列表中第一个可变参数所在位置。

  4. 然后,使用for循环遍历所有传递进来的整数,并将它们累加到sum总和中去。这里通过va_arg宏逐个访问valist中存储的参数值,并将其转换为int类型,再加到sum中。

  5. 循环结束后,使用va_end宏清理valist所占用的内存空间。注意,在调用完va_end之后就不能再使用valist了。

  6. 最后返回所有输入整数的平均数。

需要注意一下点:

  • 必须在包含stdarg.h头文件之后才能编译此代码。
  • 必须在调用average函数时至少传递一个整数作为参数。

三、动态内存管理

C 语言提供了一些函数和运算符,使得程序员可以对内存进行操作,包括分配、释放、移动和复制等,这些函数可以在 <stdlib.h> 头文件中找到。

内存是通过指针变量来管理的。指针是一个变量,它存储了一个内存地址,这个内存地址可以指向任何数据类型的变量,包括整数、浮点数、字符和数组等。

序号 函数和描述(从分配到释放,依次往下)
1 void *malloc(int num); 在堆区分配一块指定大小的内存空间,用来存放数据。这块内存空间在函数执行完成后不会被初始化,它们的值是未知的。
2 void calloc(int num, int size); 在内存中动态地分配 num 个长度为 size 的连续空间,并将每一个字节都初始化为 0。所以它的结果是分配了 numsize 个字节长度的内存空间,并且每个字节的值都是 0。
3 void *realloc(void *address, int newsize); 该函数重新分配内存,把内存扩展到 newsize。
4 void free(void *address); 该函数释放 address 所指向的内存块,释放的是动态分配的内存空间。

calloc和malloc区别:

  • calloc会被初始化
  • malloc不会被初始化

注意:void * 类型表示未确定类型的指针。C、C++ 规定 void * 类型可以通过类型转换强制转换为任何其它类型的指针。

1)分配内存

学过数组的人,都知道在定义的时候能给定一个大小,比如:

//代码1
int arr1[10];
char arr2[10];
float arr3[1];
double arr4[20];
//代码2
//用宏定义的方式
#define X 3
int arr5[X];

但是,如果您不知道需要存储的长度。那么我们需要定义一个指针,该指针指向未定义所需内存大小的字符,后续再根据需求来分配内存,如下所示:

//定义指针,该指针指向未定义所需内存大小的字符
char *description;
...
//动态分配内存
description = (char *)malloc( 200 * sizeof(char) );

上面的程序也可以使用 calloc() 来编写,只需要把 malloc 替换为 calloc 即可,如下所示:

calloc(200, sizeof(char));

那些从一开始就定义了大小的数组,一旦定义则无法改变大小。

而当动态分配内存时,您有完全控制权,可以传递任何大小的值。

2)调整内存

您可以通过调用函数 realloc() 来增加或减少已分配的内存块的大小。

char *description;
...
//动态分配内存
description = (char *)malloc( 30 * sizeof(char) );
...
//假设您想要存储更大的描述信息
description = (char *) realloc( description, 100 * sizeof(char) );

您可以尝试一下不重新分配额外的内存,strcat() 函数会生成一个错误,因为存储 description 时可用的内存不足。

3)释放内存

当程序退出时,操作系统会自动释放所有分配给程序的内存,但是,建议您在不需要内存时,都应该调用函数 free() 来释放内存。

//使用 free() 函数释放内存
free(description);

4)综合案例

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
int main()
{
    
    
   char name[100];
   char *description;
 
   strcpy(name, "Zara Ali");//复制
 
   //动态分配内存
   description = (char *)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 = (char *) 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() 函数释放内存 */
   free(description);
}

猜你喜欢

转载自blog.csdn.net/qFAFAF/article/details/129893546