C การใช้งาน atof

Aug 21 2020

ฉันเป็นมือใหม่ที่ C. ฉันกำลังใช้ atof เพื่อสร้าง raytracer แต่ฉันยังคงเรียนรู้วิธีการเขียนโปรแกรมอย่างมีประสิทธิภาพ

การมอบหมาย

คำแนะนำ

โปรแกรมใช้ไฟล์คำอธิบายฉากเป็นอาร์กิวเมนต์เพื่อสร้างวัตถุ บางส่วนของพารานี้ลอยได้ ตัวอย่างไฟล์

ฉันกำลังแยกวิเคราะห์ไฟล์ เนื่องจากฉันถูก จำกัด ในแง่ของบรรทัดที่อนุญาตต่อฟังก์ชันและฉันกำลังเรียนรู้ว่าพอยน์เตอร์คู่ทำงานอย่างไรฉันจึงใช้ตัวชี้ถ่านคู่ 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);
}


รหัสปัจจุบันที่จะตรวจสอบ:

#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);
}

การปรับแต่งเพียงอย่างเดียวของจริงที่ฉันทำคือการมีตัวชี้ถ่านสองเท่าเป็นอาร์กิวเมนต์และส่งคืน -1 ในกรณีที่เกิดข้อผิดพลาด

ทุกข้อมูลชื่นชมมาก

คำตอบ

10 pacmaninbw Aug 21 2020 at 01:38

การพกพา

ไม่มีการรับประกันว่ารหัสนี้จะใช้ ASCII ดังนั้นจึงควรใช้'0'มากกว่า48ที่จะเป็นตัวเลขวิเศษ การใช้'0'ทำให้อ่านง่ายขึ้นและเข้าใจง่ายขึ้น

lc_atof ไม่จัดการการสิ้นสุดสตริงหรือจุดสิ้นสุดของบรรทัดอย่างถูกต้อง

รหัสนี้ไม่รองรับสตริงที่สิ้นสุดเป็น NULL หรืออักขระสิ้นสุดบรรทัด ฟังก์ชันisspace()จะส่งกลับtrueสำหรับจุดสิ้นสุดของบรรทัดดังนั้นรหัสจะเดินผ่านมันไป

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

ความซับซ้อน

ฉันยอมรับว่าคุณไม่ได้ขอให้ตรวจสอบ แต่ความซับซ้อนของแต่ละifคำสั่งในฟังก์ชันการโทรตัวอย่างนั้นมากเกินไปและทำให้ฉันเกิดข้อผิดพลาดในการตรวจสอบก่อนหน้านี้:

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);
}

ฉันจะเขียนรหัสใหม่เป็น:

#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);
}

ซึ่งแสดงให้เห็นถึงความซับซ้อนของฟังก์ชันอย่างแท้จริง

10 vnp Aug 21 2020 at 06:19
  • การเลือกEIOรายงานข้อผิดพลาดเป็นเรื่องที่น่าสงสัยมาก lc_atofไม่ทำอินพุตหรือเอาต์พุตใด ๆ เหตุใดจึงควรรายงานข้อผิดพลาด IO ถ้าพิมพ์กลับไม่สามารถเป็นตัวแทนผล (เช่นd_nbr > FLT_MAX) เป็นทางเลือกที่ตรรกะหรือERANGE EOVERFLOWหากการแปลงไม่สามารถดำเนินการให้เสร็จสิ้นได้เนื่องจากอาร์กิวเมนต์ผิดรูปแบบ (เช่น!isdigit(**str)) ตัวเลือกเชิงตรรกะอาจเป็นEINVALได้

    ที่กล่าวว่าฉันไม่รับรองการตั้งค่าerrnoในฟังก์ชันไลบรารี ประเพณีที่สืบทอดกันมายาวนานคือการตั้งค่าerrnoในการโทรระบบเท่านั้น ฉันรู้ว่าประเพณีนี้ถูกละเมิดมากขึ้นเรื่อย ๆ ในทุกวันนี้ แต่ก็ยังคง หากคุณมีวิธีอื่นในการรายงานข้อผิดพลาดให้ปฏิบัติตาม

  • strไม่แนะนำให้ใช้พารามิเตอร์ inout ( ในกรณีของคุณ) ทำให้โค้ดซับซ้อนโดยไม่จำเป็นทั้งฝั่งผู้โทรและฝั่งผู้โทร ผู้ถูกเรียกถูกบังคับให้ต้องใช้ตรงพิเศษหลายครั้งเกินไปและกังวลเกี่ยวกับ (**str)++parenthesizing ในทางกลับกันผู้โทรจะสูญเสียการติดตามว่าการแยกวิเคราะห์เริ่มต้นที่ใด (เช่นต้องบันทึกหมายเลขที่ผิดรูปแบบ) ดูวิธีstrtofจัดการสิ่งนี้:

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

    นี่nptrคือเฉพาะในและendptrนอกเท่านั้น

  • ฉันแปลกใจที่คุณตัดสินใจ จำกัด ยูทิลิตี้ของฟังก์ชันนี้โดยจัดการเพียงหนึ่งหลักหลังจุดทศนิยม ไม่ใช่ความพยายามครั้งใหญ่ในการจัดการทั้งหมดและผลประโยชน์นั้นยิ่งใหญ่กว่ามาก

  • ไม่จำเป็นต้องวงเล็บค่าส่งคืน returnเป็นตัวดำเนินการไม่ใช่ฟังก์ชัน