C Thực hiện atof

Aug 21 2020

Tôi là người mới bắt đầu tại C. Tôi hiện đang triển khai xây dựng một raytracer, tuy nhiên tôi vẫn đang học cách viết chương trình một cách hiệu quả.

Chuyển nhượng

Hướng dẫn

Chương trình lấy một tệp mô tả cảnh làm đối số để tạo các đối tượng. Một số paramaters là phao. Ví dụ về tệp.

Tôi đang phân tích tệp. Vì tôi bị hạn chế về số dòng được phép cho mỗi hàm và tôi hiện đang tìm hiểu cách hoạt động của con trỏ kép, tôi đang sử dụng con trỏ ký tự kép. Ví dụ về một chức năng như vậy bằng cách sử dụng lc_atof.

int    a_parsing(char *str, t_pars *data)
{
    if (*(str++) == 'A')
    {
        if (((data->a_ratio = lc_atof(&str)) >= 0.0) && data->a_ratio <= 1.0 && errno == 0)
        // 
            if (((data->a_R = lc_atoi(&str)) >= 0) && data->a_R <= 255 && errno == 0)
                if (*(str++) = ',' && ((data->a_G = lc_atoi(&str)) >= 0) && data->a_G <= 255 && errno == 0)
                    if (*(str++) = ',' && ((data->a_B = lc_atoi(&str)) >= 0) && data->a_B <= 255 && errno == 0)
                        return (skip_space(&str));
    }
    return (0);
}


Mã hiện tại để xem lại:

#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <limits.h>
#include <errno.h>
#include <float.h>

static float    conversion(char **str)
{
    double    d_nbr;
    double    power;

    d_nbr = 0.0;
    power = 10.0;
    while (isdigit(**str))
    {
        d_nbr = d_nbr * 10.0 + (**str - 48);
        if (d_nbr > FLT_MAX)
        {
            errno = EIO;
            return (-1);
        }
        (*str)++;
    }
    if (**str == '.')
    {
      (*str)++;
      if (isdigit(**str))
      {
        d_nbr = d_nbr * 10.0 + (**str - 48);
        if (d_nbr > FLT_MAX)
        {
            errno = EIO;
            return (-1);
        }
        (*str)++;
        return ((float)(d_nbr / power));
      }
    }
    errno = EIO;
    return (-1);
}

float            lc_atof(char **str)
{
    float    n;
    int        sign;

    n = 0.0;
    sign = 1.0;
    if (!str || !*str)
    {
        errno = EIO;
        return (-1);
    }
    while (isspace(**str))
        (*str)++;
    if (**str == '+' || **str == '-')
    {
        if (**str == '-')
            sign = -1.0;    
        (*str)++;
    }
    if (!isdigit(**str))
    {
        errno = EIO;
        return (-1);
    }
    if ((n = conversion(str)) == 0 && errno != 0)
       return (-1);
    return (sign * n);
}

Các chỉnh sửa duy nhất đối với thực tế mà tôi đã thực hiện là có một con trỏ char kép làm đối số và trả về -1 trong trường hợp có lỗi.

Mọi đầu vào được đánh giá cao.

Trả lời

10 pacmaninbw Aug 21 2020 at 01:38

Tính di động

Không có gì đảm bảo rằng mã này sẽ sử dụng ASCII nên tốt hơn là sử dụng '0'chứ không phải 48là một con số ma thuật. Sử dụng '0'làm cho nó dễ đọc hơn và dễ hiểu hơn.

lc_atof Không xử lý chấm dứt chuỗi hoặc kết thúc dòng một cách chính xác

Mã này không xử lý một chuỗi kết thúc NULL hoặc một ký tự cuối dòng. Hàm isspace()trả về giá truetrị cuối dòng để mã sẽ đi qua nó.

    while (isspace(**str))
        (*str)++;
    if (**str == '+' || **str == '-')
    {
        if (**str == '-')
            sign = -1.0;
        (*str)++;
    }
    if (!isdigit(**str))
    {
        errno = EIO;
        return (-1);
    }

Phức tạp

Tôi tin rằng bạn không yêu cầu xem xét điều này, nhưng độ phức tạp của mỗi ifcâu lệnh trong hàm gọi ví dụ là quá nhiều và đã khiến tôi mắc lỗi trong bài đánh giá của mình trước đây:

int a_parsing(char* str, t_pars* data)
{
    if (*(str++) == 'A')
    {
        if (((data->a_ratio = lc_atof(&str)) >= 0.0) && data->a_ratio <= 1.0 && errno == 0)
            // 
            if (((data->a_R = lc_atoi(&str)) >= 0) && data->a_R <= 255 && errno == 0)
                if (*(str++) = ',' && ((data->a_G = lc_atoi(&str)) >= 0) && data->a_G <= 255 && errno == 0)
                    if (*(str++) = ',' && ((data->a_B = lc_atoi(&str)) >= 0) && data->a_B <= 255 && errno == 0)
                        return (skip_space(&str));
    }
    return (0);
}

Tôi sẽ viết lại mã dưới dạng:

#define MAX_COLOR   0xFF
int a_parsing_prime(char* str, t_pars* data)
{
    if (*(str++) == 'A')
    {
        data->a_ratio = lc_atof(&str);
        if (!errno && data->a_R <= MAX_COLOR)
        {
            if (*(str++) = ',')
            {
                data->a_G = lc_atoi(&str);
                if (!errno && data->a_G <= MAX_COLOR)
                {
                    if (*(str++) = ',')
                    {
                        data->a_B = lc_atoi(&str);
                        if (!errno && data->a_B <= MAX_COLOR)
                        {
                            return (skip_space(&str));
                        }
                    }
                }
            }
        }
    }
    return (0);
}

cho thấy một cách chân thực sự phức tạp của hàm.

10 vnp Aug 21 2020 at 06:19
  • Lựa chọn EIObáo cáo lỗi là rất khó hiểu. lc_atofkhông thực hiện bất kỳ đầu vào hoặc đầu ra; tại sao nó phải báo lỗi IO? Nếu kiểu trả về không thể đại diện cho kết quả (ví dụ d_nbr > FLT_MAX), một lựa chọn hợp lý là ERANGEhoặc EOVERFLOW. Nếu quá trình chuyển đổi không thể hoàn tất do đối số không đúng định dạng (ví dụ !isdigit(**str)), lựa chọn hợp lý có lẽ sẽ là EINVAL.

    Điều đó nói rằng, tôi không xác nhận cài đặt errnotrong chức năng thư viện. Một truyền thống lâu đời là chỉ đặt errnotrong các lệnh gọi hệ thống. Tôi biết rằng truyền thống này ngày càng bị vi phạm nhiều hơn trong những ngày này, nhưng vẫn còn. Nếu bạn có các phương tiện thông báo lỗi khác, hãy sử dụng chúng.

  • Sử dụng tham số inout ( strtrong trường hợp của bạn) là không nên. Nó làm phức tạp mã một cách không cần thiết, cả ở phía người gọi và phía người gọi. Callee bị buộc phải sử dụng thêm hướng dẫn quá nhiều lần và phải lo lắng về việc đặt dấu ngoặc đơn (**str)++. Đổi lại, người gọi mất theo dõi nơi có thể phân tích cú pháp bắt đầu (giả sử, nó cần ghi lại số không đúng định dạng). Hãy xem cách strtofxử lý điều này:

      float strtof(const char *restrict nptr, char **restrict endptr);
    

    Đây nptrlà chỉ trong và endptrngoài.

  • Tôi rất ngạc nhiên khi bạn quyết định giới hạn tiện ích của hàm bằng cách chỉ xử lý một chữ số sau dấu chấm thập phân. Việc xử lý tất cả chúng không phải là một nỗ lực lớn và lợi ích thu được còn lớn hơn nhiều.

  • Không cần đặt giá trị trả về trong ngoặc đơn. returnlà một toán tử, không phải là một hàm.