Jump to: navigation, search

The C Programming Language, 2nd Edition, by Kernighan and Ritchie
Exercise 7.04 on page 159

Write a private version of scanf analogous to minprintf from the previous section.



Solution by Thomas Amundsen

It seems that the real scanf doesn't handle floats and strings the way I expect it to. Having said that, the book's example of a rudimentary calculator on page 141 does not work on my machine.

Here is a solution that only handles integers.

/* Thomas Amundsen - K&R2 Exercise 7-4 - 2009-06-19 */

#include <stdio.h>
#include <stdarg.h>

void minscanf(char *fmt, ...);

int main()
{
  int i;

  minscanf("%d", &i); /* scan integer from stdin */
  printf("scanned %d\n", i); /* print scanning results to stdout */
  
  return 0;
}

/* minscanf: minimal scanf with variable argument list
   only scans integers */
void minscanf(char *fmt, ...)
{
  va_list ap; /* points to each unnamed arg in turn */
  char *p;
  int *ival;

  va_start(ap, fmt); /* make ap point to 1st unnamed arg */

  for (p = fmt; *p; p++) {

    /* skip chars that aren't format conversions */
    if (*p != '%')
      continue;

    /* prev char was %, look for format conversion */
    switch(*++p) {
    case 'd':
      ival = va_arg(ap, int *); /* get integer pointer from args */
      scanf("%d", ival); /* read integer into int pointer */
      break;
     default:
      break;
    }
  }
}


Solution by codybartfast (cat 0)

Supports %d, %f, %s and %%.

#include <ctype.h>
#include <stdarg.h>
#include <stdlib.h>

int minscanf(char *str, char *fmt, ...);

int minscanf(char *str, char *fmt, ...)
{
	va_list ap;
	int matched = 0;
	char *p;

	va_start(ap, fmt);
	for (; *fmt; fmt++) {
		if (isspace(*fmt))
			continue;
		for (; isspace(*str); str++)
			;
		if (*str == '\0')
			break;
		if (*fmt != '%') {
			if (*fmt == *str) {
				str++;
				continue;
			} else {
				break;
			}
		}

		switch (*++fmt) {
		case 'd':
			if (!isdigit(*str) && !((*str == '+' || *str == '-') &&
						isdigit(*(str + 1))))
				break;
			*va_arg(ap, int *) = atoi(str);
			str++;
			while (isdigit(*str) && *str)
				str++;
			matched++;
			continue;
		case 'f':
			*va_arg(ap, double *) = strtod(str, &p);
			if (p == str)
				break;
			str = p;
			matched++;
			continue;
		case 's':
			if (isspace(*str) || !*str)
				break;
			p = va_arg(ap, char *);
			while (!isspace(*str) && *str)
				*p++ = *str++;
			*p = '\0';
			matched++;
			continue;
		case '%':
			if (*str == '%') {
				str++;
				continue;
			} else {
				break;
			}
		default:
			break;
		}
		break;
	}
	va_end(ap);
	return matched;
}

Solution by anonymous

I implemented all of the basic format specifiers for C that I could find except for %n. Also, I didn't include support for field width, length modifiers, assignment suppression, and the [...] and [^…] character sets.

#include <stdio.h>
#include <stdarg.h>

/*
    Exercise 7-4. Write a private version of scanf analogous to minprintf from the previous section.
*/

int minscanf(char *fmt, ...);

int main()
{
    char s[3], c;
    int i1, i2, i3, i4, i5, i6, i7, i8, i9, i10;
    float f1, f2, f3, f4, f5;
    unsigned int u;
    void *p = NULL;
    printf("Copy and paste in the following text to test this program:\n-5 0x6 0X7 7 010 11 11 c D 0xe 0XFg hi 1.920e+001 2.122E+001 2.3 2.4 2.5 %p %%\n", (void *) &c);
    if (minscanf("%d %i %i %i %o %o %u %x %x %x %x %c %s %e %e %f %g %g %p %%", &i1, &i2, &i3, &i4, &i5, &i6, &u, &i7, &i8, &i9, &i10, &c, s, &f1, &f2, &f3, &f4, &f5, &p) != 19)
        printf("Not all inputs were assigned!\n");
    printf("%d %#x %#X %o %#o %o %u %x %X %#x %#X%c %s %.3e %.3E %.1f %g %g %p %%\n", i1, i2, i3, i4, i5, i6, u, i7, i8, i9, i10, c, s, f1, f2, f3, f4, f5, p);
    return 0;
}

// minimal scanf with variable argument list. Returns number of assigned input items
int minscanf(char *fmt, ...)
{
    va_list ap; // points to each unnamed arg in turn
    char *p, *sval, format[3] = { '%', '\0', '\0' };
    int *ival, numFound = 0;
    double *dval;
    unsigned int *uival;
    void **vval; // pointer to a pointer

    va_start(ap, fmt); // make ap point to 1st unnamed arg
    for (p = fmt; *p; p++)
    {
        if (*p != '%')
            continue;
        switch (*++p)
        {
        case 'd': case 'i': case 'c': // char is promoted to int when passed through '...'
            ival = va_arg(ap, int *);
            format[1] = *p;
            numFound += scanf(format, ival);
            break;
        case 'e': case 'f': case 'g':
            dval = va_arg(ap, double *);
            format[1] = *p;
            numFound += scanf(format, dval);
            break;
        case 's':
            sval = va_arg(ap, char *);
            numFound += scanf("%s", sval);
            break;
        case 'x': case 'o':  case 'u':
            uival = va_arg(ap, unsigned int *);
            format[1] = *p;
            numFound += scanf(format, uival);
            break;
        case 'p':
            vval = va_arg(ap, void *);
            numFound += scanf("%p", vval);
        case '%': // no need to do anything
            break;
        default: 
            printf("unsupported format specifier: %%%c\n", *p);
            break;
        }
    }
    va_end(ap); // clean up when done
    return numFound;
}
Personal tools