تمرين K&R: يعمل الكود الخاص بي ، ولكنه يشعر بالرائحة الكريهة ؛ نصيحة للتنظيف؟

StackOverflow https://stackoverflow.com/questions/161873

  •  03-07-2019
  •  | 
  •  

سؤال

أنا أعمل على كتاب K&R. لقد قرأت إلى الأمام أكثر مما قمت بالتمارين ، معظمهم لقلة الوقت. أنا ألحق بالركب ، وقد فعلت جميع التمارين تقريبًا من الفصل 1 ، وهو البرنامج التعليمي.

كانت مشكلتي التمرين 1-18. التمرين هو:

اكتب برنامجًا لإزالة الفراغات وعلامات التبويب الزائدة من سطر الإدخال ، وحذف أسطر فارغة تمامًا

الكود الخاص بي (أدناه) يفعل ذلك ، ويعمل. مشكلتي في ذلك هي طريقة القطع التي قمت بتطبيقها. يشعر ... خطأ ... بطريقة ما. مثل إذا رأيت رمزًا مشابهًا في C# في مراجعة التعليمات البرمجية ، فمن المحتمل أن أذهب إلى المكسرات. (C# كونه أحد تخصصاتي.)

هل يمكن لأي شخص تقديم بعض النصائح حول تنظيف هذا الأمر - مع أن النصيحة المذكورة يجب أن تستخدم المعرفة فقط من الفصل 1 من K & R. (أعرف أن هناك طرقًا Zillion لتنظيف ذلك باستخدام مكتبة C الكاملة ؛ نحن "نحن" نحن "نحن" نحن "نحن" نحن "نحن" إعادة فقط التحدث عن الفصل 1 و stdio.h الأساسي هنا.) أيضًا ، عند تقديم النصيحة ، هل يمكنك شرح سبب مساعدة ذلك؟ (أنا ، بعد كل شيء ، أحاول أن أتعلم! ومن الأفضل أن أتعلم من الخبراء هنا؟)

#include <stdio.h>

#define MAXLINE 1000

int getline(char line[], int max);
void trim(char line[], char ret[]);

int main()
{
    char line[MAXLINE];
    char out[MAXLINE];
    int length;

    while ((length = getline(line, MAXLINE)) > 0)
    {
        trim(line, out);
        printf("%s", out);
    }

    return 0;
}

int getline(char line[], int max)
{
    int c, i;

    for (i = 0; i < max - 1 && (c = getchar()) != EOF && c != '\n'; ++i)
        line[i] = c;

    if (c == '\n')
    {
        line[i] = c;
        ++i;
    }

    line[i] = '\0'; 
    return i;
}

void trim(char line[], char ret[])
{
    int i = 0;

    while ((ret[i] = line[i]) != '\0')
        ++i;

    if (i == 1)
    {
        // Special case to remove entirely blank line
        ret[0] = '\0';
        return;
    }

    for (  ; i >= 0; --i)
    {
        if (ret[i] == ' ' || ret[i] == '\t')
            ret[i] = '\0';
        else if (ret[i] != '\0' && ret[i] != '\r' && ret[i] != '\n')
            break;
    }

    for (i = 0; i < MAXLINE; ++i)
    {
        if (ret[i] == '\n')
        {
            break;
        }
        else if (ret[i] == '\0')
        {
            ret[i] = '\n';
            ret[i + 1] = '\0';
            break;
        }
    }
}

تحرير: أقدر كل النصائح المفيدة التي أراها هنا. أود أن أذكر الناس بأنني ما زلت N00B مع C ، وعلى وجه التحديد لم يصل إلى مؤشرات حتى الآن. (تذكر الشيء حول CH.1 من K&R - CH.1 لا يقوم بتأسيس.)

ومعظم ما أبحث عنه هو طريقة القطع نفسها - وتحديداً حقيقة أنني أتحلق 3 مرات (التي تشعر بقذارة جدا). أشعر وكأنني إذا كنت مجرد لمسة أكثر ذكاءً (حتى بدون المعرفة المتقدمة بـ C) ، فقد يكون هذا أكثر نظافة.

هل كانت مفيدة؟

المحلول

لا يوجد سبب لوجود اثنين من المخازن المؤقتة ، يمكنك تقليم خط الإدخال في مكانه

int trim(char line[])
{
    int len = 0;
    for (len = 0; line[len] != 0; ++len)
        ;

    while (len > 0 &&
           line[len-1] == ' ' && line[len-1] == '\t' && line[len-1] == '\n')
        line[--len] = 0;

    return len;
}

من خلال إرجاع طول الخط ، يمكنك التخلص من خطوط فارغة عن طريق اختبار خطوط الطول غير الصفر

if (trim(line) != 0)
    printf("%s\n", line);

تحرير: يمكنك جعل حلقة الوقت أكثر بساطة ، على افتراض ترميز ASCII.

while (len > 0 && line[len-1] <= ' ')
    line[--len] = 0;

نصائح أخرى

إذا كنت متمسكًا بالفصل 1 ، فهذا يبدو جيدًا بالنسبة لي. إليك ما أوصي به من وجهة نظر مراجعة التعليمات البرمجية:

عند التحقق من المساواة في C ، ضع دائمًا الثابت أولاً

if (1 == myvar)

بهذه الطريقة لن تفعل شيئًا مثل هذا عن طريق الخطأ:

if (myvar = 1)

لا يمكنك الابتعاد عن ذلك في C#، ولكنه يجمع بشكل جيد في C ويمكن أن يكون شيطانًا حقيقيًا للتصحيح.

trim () كبير جدا.

ما أعتقد أنك تحتاجه هو وظيفة Strlen-ish (تابع واكتبها int stringlength (const char *s)).

ثم تحتاج إلى وظيفة تسمى int scanback (const char *s ، const char *matches ، int start) والتي تبدأ في البداية ، تنخفض إلى z طالما أن الشخصية التي يتم مسحها في المعرف الوارد في المباريات ، ارجع الفهرس الأخير حيث تم العثور على مباراة.

ثم تحتاج إلى وظيفة تسمى int scanfront (const char *s ، const char *matches) والتي تبدأ من 0 ومسح للأمام طالما أن الشخصية التي يتم مسحها في S موجودة في المباريات ، وإرجاع الفهرس الأخير حيث يتم العثور على المباراة.

ثم تحتاج إلى وظيفة تسمى int charinstring (char c ، const char *s) التي تُرجع غير صفرية إذا كانت C موجودة في S ، 0 خلاف ذلك.

يجب أن تكون قادرًا على كتابة القطع من حيث هذه.

شخصيا للبناء:

أنا أفضل ما يلي:

while( (ret[i] = line[i]) )
        i++;

إلى:

while ((ret[i] = line[i]) != '\0')
        ++i;

كلاهما يتحقق ضد! = 0 لكن الأول يبدو أنظف قليلاً. إذا كان Char هو أي شيء آخر من Thah 0 ، فسيقوم جسم الحلقة بتنفيذ آخر ، فسوف ينفجر من الحلقة.

أيضًا بالنسبة للبيانات "لـ" ، في حين أني ساري المفعول ، أجد أن ما يلي:

for (  ; i >= 0; --i)

فقط تبدو "غريبًا" بالنسبة لي وهي في الواقع حل كابوس محتمل للحشرات المحتملة. إذا كنت أراجع هذا الرمز ، فسيكون ذلك مثل تحذير أحمر متوهج مثل. عادةً ما تريد استخدام الحلقات لتكرار عدد معروف من المرات ، وإلا (كما هو الحال دائمًا ، هناك استثناءات للقاعدة ولكن وجدت أن هذا ينطبق بشكل عام). ما سبق للبيان يمكن أن يصبح:

while (i)
{
        if (ret[i] == ' ' || ret[i] == '\t')
        {
            ret[i--] = '\0';
        }
        else if (ret[i] != '\0' && ret[i] != '\r' && ret[i] != '\n')
        {
            break;
        }
}

أولا قبل كل شيء:

int main (void)

أنت تعرف المعلمات إلى main (). انهم لا شيء. (أو argc & argv ، لكنني لا أعتقد أن هذا الفصل 1 مادة.)

Stylewise ، قد ترغب في تجربة أقواس K & R. إنها أسهل بكثير في المساحة العمودية:

void trim(char line[], char ret[])
{
    int i = 0;

    while ((ret[i] = line[i]) != '\0')
        ++i;

    if (i == 1) { // Special case to remove entirely blank line
        ret[0] = '\0';
        return;
    }

    for (; i>=0; --i) { //continue backwards from the end of the line
        if ((ret[i] == ' ') || (ret[i] == '\t')) //remove trailing whitespace
            ret[i] = '\0';

        else if ((ret[i] != '\0') && (ret[i] != '\r') && (ret[i] != '\n')) //...until we hit a word character
            break;
    }

    for (i=0; i<MAXLINE-1; ++i) { //-1 because we might need to add a character to the line
        if (ret[i] == '\n') //break on newline
            break;

        if (ret[i] == '\0') { //line doesn't have a \n -- add it
            ret[i] = '\n';
            ret[i+1] = '\0';
            break;
        }
    }
}

(أضيفت أيضًا تعليقات وثابتة خطأ واحد.)

القضية الكبيرة هي استخدام ثابت Maxline - Main () على وجه الحصر ل خط و خارج المتغيرات؛ TRIM () ، الذي يعمل عليها فقط لا يحتاج إلى استخدام الثابت. يجب أن تمرر الحجم (الحجم) كمعلمة تمامًا كما فعلت في GetLine ().

أنا شخصياً سأضع رمزًا مثل هذا:

ret[i] != '\0' && ret[i] != '\r' && ret[i] != '\n'

في وظيفة منفصلة (أو حتى ماكرو تحديد)

  1. يجب أن تستخدم TRIM بالفعل 1 عازلة فقط (كما يقول Ferruccio).
  2. يجب تقسيم القطع ، كما يقول plinth
  3. لا يحتاج القطع إلى إرجاع أي قيمة (إذا كنت تريد التحقق من سلسلة فارغة ، فاختبر سطر [0] == 0)
  4. للحصول على نكهة C إضافية ، استخدم المؤشرات بدلاً من الفهارس

-التواء إلى نهاية الخط (إنهاء 0 ؛ -في حين أن بداية الخط والشخصية الحالية هو الفضاء ، استبدله بـ 0. -Back Off char

char *findEndOfString(char *string) {
  while (*string) ++string;
  return string; // string is now pointing to the terminating 0
}

void trim(char *line) {
  char *end = findEndOfString(line);
   // note that we start at the first real character, not at terminating 0
  for (end = end-1; end >= line; end--) {
      if (isWhitespace(*end)) *end = 0;
      else return;
  }
}

مثال آخر على فعل نفس الشيء. هل بعض الانتهاكات البسيطة باستخدام الأشياء الخاصة بـ C99. لن يتم العثور عليها في K&R. تستخدم أيضًا وظيفة Assert () التي تعد جزءًا من مكتبة Starndard ، ولكن ربما لا يتم تغطيتها في الفصل الأول من K&R.

#include <stdbool.h> /* needed when using bool, false and true. C99 specific. */
#include <assert.h> /* needed for calling assert() */

typedef enum {
  TAB = '\t',
  BLANK = ' '
} WhiteSpace_e;

typedef enum {
  ENDOFLINE = '\n',
  ENDOFSTRING = '\0'
} EndofLine_e;

bool isWhiteSpace(
  char character
) {
  if ( (BLANK == character) || (TAB == character ) ) {
    return true;
  } else {
    return false;
  }
}

bool isEndOfLine( 
  char character
) {
 if ( (ENDOFLINE == character) || (ENDOFSTRING == character ) ) {
    return true;
  } else {
    return false;
  }
}   

/* remove blanks and tabs (i.e. whitespace) from line-string */
void removeWhiteSpace(
  char string[]
) {
  int i;
  int indexOutput;

  /* copy all non-whitespace character in sequential order from the first to the last.
    whitespace characters are not copied */
  i = 0;
  indexOutput = 0;
  while ( false == isEndOfLine( string[i] ) ) {
    if ( false == isWhiteSpace( string[i] ) ) {
      assert ( indexOutput <= i );
      string[ indexOutput ] = string[ i ];
      indexOutput++;
    }
    i++; /* proceed to next character in the input string */
  }

  assert( isEndOfLine( string[ i ] ) );
  string[ indexOutput ] = ENDOFSTRING;

}

ها هي طعنتي في التمرين دون معرفة ما هو في الفصل 1 أو K & R. أفترض المؤشرات؟

#include "stdio.h"

size_t StrLen(const char* s)
{
    // this will crash if you pass NULL
    size_t l = 0;
    const char* p = s;
    while(*p)
    {
        l++;
        ++p;
    }
    return l;
}

const char* Trim(char* s)
{
    size_t l = StrLen(s);
    if(l < 1)
        return 0;

    char* end = s + l -1;
    while(s < end && (*end == ' ' || *end == '\t'))
    {
        *end = 0;
        --end;
    }

    return s;
}

int Getline(char* out, size_t max)
{
    size_t l = 0;
    char c;
    while(c = getchar())
    {
        ++l;

        if(c == EOF) return 0;
        if(c == '\n') break;

        if(l < max-1)
        {
            out[l-1] = c;
            out[l] = 0;
        }
    }

    return l;
}

#define MAXLINE 1024

int main (int argc, char * const argv[]) 
{
    char line[MAXLINE];
    while (Getline(line, MAXLINE) > 0)
    {
        const char* trimmed = Trim(line);
        if(trimmed)
            printf("|%s|\n", trimmed);

        line[0] = 0;
    }

    return 0;
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top