typedef union { int i; long l; float f; double d; void *v; char *s; char c; unsigned char uc; short sh; unsigned short ush; unsigned int ui; int iarray[2]; float farray[2]; char carray[8]; unsigned char ucarray[8]; } Jval; |
In case you don't know, a union is like a struct, except you can only use one of the fields at a time. In other words, if you have a Jval named jv, then you may set and use jv.i as an integer, and jv.d as a double, but not both at the same time.
I've made use of Jval's when I write generic data structures such as lists, and trees. For the purposes of this class, don't worry about most of the fields in the union. The only important ones are:
int i; float f; double d; void *v; char *s; char c;The nice thing about a Jval is that you can hold one piece of data in a Jval, regardless of what the type of that piece of data is. Moreover, the Jval will always be 8 bytes.
Here's a very simple example, which is pretty unmotivated, except that it demonstrates the union (src/simple_jval.c):
/* This program shows the very simple use of a jval. */ #include <stdio.h> #include <stdlib.h> #include "jval.h" int main() { Jval jv; /* Set and use as an integer. */ jv.i = 10; printf("Using the jval as an integer: %d\n", jv.i); /* Set and use as a double. */ jv.d = 5.6; printf("Using the jval as a double: %.2lf\n", jv.d); /* Set and use as a char *. */ jv.s = "Fred"; printf("Using the jval as a char *: %s\n", jv.s); /* Since I've set it as a (char *), I shouldn't use it as a double. */ printf("If I now print the jval as a double, I get garbage: %lg\n", jv.d); return 0; } |
Pay attention to that last printf() -- you should only use a jval using the type that you used when you set its value. That last printf() is treating the 8 bytes that were set to be a pointer as a double, and accordingly you get a really weird number:
UNIX> bin/simple_jval Using the jval as an integer: 10 Using the jval as a double: 5.60 Using the jval as a char *: Fred If I now print the jval as a double, I get garbage: 2.13063e-314 UNIX>
Jval j; j.i = 4;You can freely pass Jval's to and from procedure calls. A Jval can be the return value of a procedure call.
Jval.h defines a whole bunch of prototypes for "constructor functions":
extern Jval new_jval_i(int); extern Jval new_jval_f(float); extern Jval new_jval_d(double); extern Jval new_jval_v(void *); extern Jval new_jval_s(char *);These return Jval's to you when you give them arguments of a specific type. For example, if you want to initialize a Jval so that it is in integer whose value is 4, you can do it as shown above, or you can do:
Jval j; j = new_jval_i(4);Now j.i will be the integer 4. This is convenient when you are using a procedure that takes a Jval as a parameter, because you can call the procedure and never actually declare the Jval. Here's an example, in src/jval_ex2.c:
/* A program that demonstrates the "constructor" functions, and how they help when you are using a procedure that takes a jval as a parameter. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include "fields.h" #include "jval.h" /* Print a jval as an integer, double or string. */ void print_jval(Jval jv, char how) { switch (how) { case 'i': printf("%d\n", jv.i); break; case 'd': printf("%lg\n", jv.d); break; case 's': printf("%s\n", jv.s); break; default: fprintf(stderr, "Bad 'how' variable in print_jval: %c\n", how); exit(1); } } /* Call print_jval. Note that I never have to declare a jval, because I'm using the constructors. */ int main() { print_jval(new_jval_i(10), 'i'); print_jval(new_jval_d(5.6), 'd'); print_jval(new_jval_s("Fred"), 's'); return 0; } |
UNIX> bin/jval_ex2 10 5.6 Fred UNIX>
Specifically, you are not to say, use a Jval instead of an int in your code just because it works. That makes your code unreadable, and unreadability is majorly bad. Here is an example of bad code to average all of the integers on standard input (src/badavg.c):
/* A program that abuses jvals. It averages integers on standard input. */ #include <stdio.h> #include <stdlib.h> #include "jval.h" int main() { Jval total; /* You'll note, no types other than Jvals. */ Jval j; Jval n; n.i = 0; /* n and j are always treated as ints. They should be ints. */ total.i = 0; /* Total starts as an int */ while (scanf("%d", &(j.i)) == 1) { total.i += j.i; n.i++; } /* And then total is used as a double. You should simply use a double.... */ total.d = ((double) total.i) / ((double) n.i); printf("Average = %lf\n", total.d); return 0; } |
Yes, it works, and yes, it's a cute way to use total as both an int and a double. But it is revolting -- every use of Jval's is bad, and if you use them in ways like these, you should be punished.
(In case you care, the code should look as in src/goodavg.c):
/* The proper version of this program just uses doubles for total and n. It's not like there are ints that can't be stored in a double, and you're using floating point division, so you need the doubles. */ #include <stdio.h> #include <stdlib.h> int main() { double total; double n; int j; n = 0; total = 0; while (scanf("%d", &j) == 1) { total += j; n++; } if (n == 0) exit(1); printf("Average = %lf\n", total/n); return 0; } |
extern int jval_i(Jval); extern long jval_l(Jval); extern float jval_f(Jval); extern double jval_d(Jval); extern void *jval_v(Jval); extern char *jval_s(Jval); extern char jval_c(Jval); ...So, for example, calling jval_i(j) is the same as using j.i.