Thread: Looking for constructive criticism

  1. #1
    Registered User
    Join Date
    May 2008
    Posts
    11

    Looking for constructive criticism

    I am finished with my first assignment in a C prog 2 class. I am going to post it here and hopefully get some constructive criticism. This program is nothing really to shout about...adds and subtracts arbitrarily large numbers, not designed to handle negative numbers. I know that there is nothing earthshattering in here, but I have definitely learned a lot in the past two weeks. If some of you programming masters can take a look and provide me with some constructive criticism it would be much appreciated.

    I will also attach this as a .c file

    I am trying to improve my knowledge of C and programming in general, feel free to be brutal.


    Code:
    // Wes Kendrick
    // BIGINT.C
    // THIS PROGRAMS ALLOWS FOR SIMPLE MANIPULATION OF LARGE INTEGERS.
    // THE USER CAN ADD, OR SUBTRACT TWO LARGE INTEGERS INPUT FROM THE KEYBOARD
    // THIS PROGRAM DOES NOT ALLOW FOR A NEGATIVE RESULT. THE SMALLER NUMBER WILL
    // ALWAYS BE SUBTRACTED FROM THE LARGER NUMBER WHEN SUBTRACTION IS THE OPERATION
    // DESIRED
    
    // This program was written in Dev-C++ 4.9.9.2, and is written strictly for use
    // on a PC. Not all functions used are assumed to be ANSI C
    
    #include <stdio.h>
    #include <stdlib.h>
    #define TRUE 1
    #define FALSE 0
    #define ADD 1
    #define SUBTRACT 2
    #define QUIT 3
    #define MAX 200
    
    
    //Preconditions: the first parameter is string that stores
    //               only contains digits, doesn't start with
    //               0, and is 200 or fewer characters long.
    //Postconditions: The function will read the digits of the
    //	large integer character by character, 
    //	convert them into integers and place them in 
    //	nodes of a linked list. The pointer to the 
    //	head of the list is returned.
    
    struct integer* read_integer();
    
    //Preconditions: p is a pointer to a big integer, stored in
    //               reverse order, least to most significant 
    //               digit, with no leading zeros.
    //Postconditions: The big integer pointed to by p is 
    //                printed out.
    void print_bigint(struct integer *p);
    
    //Preconditions: p and q are pointers to big integers, 
    //               stored in reverse order, least to most 
    //               significant digit, with no leading zeros.
    //Postconditions: A new big integer is created that stores
    //                the sum of the integers pointed to by p
    //                and q and a pointer to it is returned.
    struct integer* add(struct integer *p, struct integer *q);
    
    //Preconditions: p and q are pointers to big integers, 
    //               stored in reverse order, least to most 
    //               significant digit, with no leading zeros.
    //Postconditions: A new big integer is created that stores
    //                the absolute value of the difference 
    //                between the two and a pointer to this is
    //                returned.
    struct integer* subtract(struct integer *p, struct integer *q);
    
    //Preconditions: Both parameters of the function are 
    //	pointers to struct integer. 
    //Postconditions: The function compares the digits of two 
    //	numbers recursively and returns: 
    //    -1 if the first number is smaller than the second, 
    //     0 if the first number is equal to the second number,
    //   1 if the first number is greater than the second.  
    int compare(struct integer *p, struct integer *q);
    
    //Preconditions: Both parameters point to struct integer
    //Postconditions: Prints spaces so that the numbers are 
    //                aligned to the right
    
    //Preconditions: p, q, and answer point to struct integer, operation is
    //               the operation to be performed
    //Postconditions: Formats the output
    
    void format_output(struct integer *p, struct integer *q, struct integer *answer, int operation);
    
    //Preconditions: choice, low, and high are all int...low is low bound of
    //               acceptable input, high is high bound
    //Postconditions: Returns TRUE if input is valid, otherwise returns FALSE
    
    int valid_choice(int choice, int low, int high);
    
    //Preconditions: None
    //Postconditions: Retrieves the user choice from the keyboard and returns it
    int get_user_choice();
    
    //Preconditions: how_many is an int, character is a char
    //Postconditions: prints out character how_many times
    
    void print_characters(int how_many, char character);
    
    //Preconditions: answer is of type struct integer, it has at least one
    //               leading zero (because method of complements is used for 
    //               subtraction.)
    //Postconditions: All leading zeroes are removed, and the size
    //                of answer is adjusted accordingly
    
    void remove_zeroes(struct integer *answer);
    
    struct integer
    {
        int *digits;
        int size;
    };
    
    
    int main()
    {
        int choice, operation;
        struct integer *bigint1,*bigint2,*answer;
    
    // Run until user chooses to quit
    
        do
        {
            choice=get_user_choice();
            if(choice !=QUIT)
            {
                system("CLS");
                printf("Please enter the first number: ");
                bigint1=read_integer();
                printf("\nPlease enter the second number: ");
                bigint2=read_integer();
    
    // Executes users choice
    
                switch(choice)
                {
                    case ADD:
                         answer = add(bigint1,bigint2);
                         operation=ADD;
                         break;
                    
                    case SUBTRACT:
                         answer = subtract(bigint1,bigint2);
                         operation=SUBTRACT;
                         break;
                } // End switch
                
                format_output(bigint1,bigint2,answer,operation);
                system("PAUSE");
                
    // Frees memory
                
                free(bigint1);
                free(bigint2);
                free(answer);
            }
        }
        while(choice!=QUIT);
    
    // End program
    
        printf("\n\nThank you for using the Big Int calculator!\n\n\n");
        system("PAUSE");
        return 0;
    }
    
    int get_user_choice()
    {
        int choice;
        system("CLS");
        do
        {
            printf("1. Add two large integers\n2. Subtract two large integers\n3. Quit\n");
            printf("\n\nEnter the number corresponding to your choice: ");
            scanf("%d",&choice);
           
        }
    
    // Checks for valid input in the while loop
    
        while(valid_choice(choice,1,3)==FALSE);
        return choice;
    }
    
    int valid_choice(int choice, int low, int high)
    {
        
    // Checks entry against bounds
        
        if(choice<low || choice > high)
        {
            printf("\n\nThat is invalid input.\n\n\n");
            return FALSE;
        }
        else
            return TRUE;
    }
    
    struct integer* read_integer()
    {
    
    // Temporary string is created to read in the number from the user
    
       char string_num[MAX];
    
    // Allocates memory for the struct integer, the address of this will be returned
    
       struct integer *temp= (struct integer *)malloc(sizeof(struct integer));
       int counter;
    
    // Reads string
    
       scanf("%s",string_num);
    
    // Sets the size of the "integer"
    
       temp->size=strlen(string_num);
    
    // Dynamically allocates memory for the array of int
    
       temp->digits=(int *)(malloc((temp->size*sizeof(int))));
    
    // Subtracts '0' from the character to get the digit...numbers are stored
    // with the least signifigant digit in index 0...this makes the code
    // slightly simpler 
    
       for(counter=temp->size-1;counter>=0;counter--)
           temp->digits[temp->size-1-counter]=string_num[counter]-'0';
       return temp;
    }
    
    struct integer* add(struct integer *p, struct integer *q)
    {
    
    // Allocates integer
    
        struct integer *answer=malloc(sizeof(struct integer));
        int counter, carry=0;
    
    // If p is bigger than q, size the array in answer to the size of p
    
        if(compare(p,q)==1)
        {
            answer->size=p->size;
    
    // To simplify the code, we reallocate memory to the smaller number and set its
    // size to the size of the bigger one (if they are the same number of digits, nothing
    // happens
    
            q->digits=realloc(q->digits,p->size*sizeof(int));
    
            for(counter=q->size; counter < p-> size; counter++)
            {
                q->digits[counter]=0;
            }
        }
    
    // Q is bigger, repeat process as above, but switch p and q
    
        else
        {
            answer->size=q->size;
            if(q->size != p->size)
            {
                p->digits=realloc(p->digits,q->size*sizeof(int));
                for(counter=p->size;counter< q->size;counter++)
                    p->digits[counter]=0;
            }
        }
    
    // Allocates enough memory for the array of int inside answer
    
        answer->digits=(int *) malloc(answer->size*sizeof(int));
    
    // Runs counter for entire length of answer
    
        for(counter=0;counter< (answer->size);counter++)
        {
        
    // Sets answer->digits[counter] to the sum of the digit in the same index position
    // of q and p, as well as "carry" which is initialized to zero    
        
            answer->digits[counter]=p->digits[counter]+q->digits[counter] + carry;
    
    // Number is greater than 9...must be reduced and reset carry
    
            if(answer->digits[counter]>9)
            {
                answer->digits[counter]%=10;
                carry=1;
            }
            else
    
    // Carry is zero, answer->digits[counter] is less than 10
    
                carry=0;
        }
    
    // End of the for-loop, if carry is still 1, reallocate the size of answer->digits
    // to answer->size+1, and increment answer->size
    
        if(carry==1)
        {
    
    // turns digit from > 10
    
            answer->size++;
            answer->digits=realloc(answer->digits,answer->size*sizeof(int));
    
    // Will never be greater than one...(worst case example: 999+999 = 1998 (4 digits, leading 1)
    
            answer->digits[(answer->size-1)]=1;
        }
        
    // Reallocate p and q to their original sizes, only the added zeroes will be removed
    
        p->digits=realloc(p->digits,p->size*sizeof(int));
        q->digits=realloc(q->digits,q->size*sizeof(int));
    
    // address of answre is returned
    
        return answer;
    }
                
    struct integer* subtract(struct integer *p, struct integer *q)
    {
        
    // COMPLEMENTS stores the nines complement of a digit 0-9    
        
        int COMPLEMENTS[10], counter, difference;//, not_a_leading_zero, how_big_answer;
        
    // Required for complement method of subtraction, to add one to the result
    // This segment sets the attributes of one
        
        struct integer *one= (struct integer *)(malloc(sizeof(struct integer)));
        one->size=1;
        one->digits=(int *)(malloc(one->size*sizeof(int)));
        one->digits[0]=1;
    
    // Nines complement will hold the nines complement of the smaller number
    
        struct integer *nines_complement=(struct integer *)(malloc(sizeof(struct integer)));
        struct integer *answer;
    
    // Initializes the nines complement of digits 0-9
    
        for(counter=0;counter<10;counter++)
        {
            COMPLEMENTS[counter]=9-counter;
        }
        
    // If p > q    
        
        if(compare(p,q)==1)
        {
    
    // Difference will represent the number of additional digits that must
    // be added to make both numbers the same size
    
            difference=p->size - q->size;
    
    // sets nines_complement->size to the bigger numbers size
    
            nines_complement->size=p->size;
    
    // Allocates memory for the int array inside nines_complement
    
            nines_complement->digits=(int *)(malloc(nines_complement->size*sizeof(int)));
    
    // Sets all "unaccounted for digits" to 9...counter stops when it equals the difference in 
    // size between nines_complement and the smaller number
    
            for(counter=nines_complement->size-1;counter>=(nines_complement->size-difference);counter--)
            {
                nines_complement->digits[counter]=9;
            }
    
    // sets the rest of the "accounted for digits" to the respective digit in the smaller of
    // the two numbers (in this case q)
    
            for(counter=nines_complement->size-difference-1;counter>=0;counter--)
            {
                nines_complement->digits[counter]=COMPLEMENTS[q->digits[counter]];
            }
            
    // This is the method of complements, the intermediate step answer is set to 
    // the sum of the larger number and the nines complement of the smaller number
            
            answer=add(nines_complement,p);
        }
    
    // In this case, p is smaller (or equal to) q...same logic as above except the 
    // p's and q's have been switched
    
        else
        {
            difference=q->size - p->size;
            nines_complement->size=q->size;
            nines_complement->digits=(int *)(malloc(nines_complement->size*sizeof(int)));
            for(counter=nines_complement->size-1;counter>=(nines_complement->size-difference);counter--)
            {
             
                nines_complement->digits[counter]=9;
            }
            for(counter=nines_complement->size-difference-1;counter>=0;counter--)
            {
                nines_complement->digits[counter]=COMPLEMENTS[p->digits[counter]];
            }
            
            answer=add(nines_complement,q);
        }
    
    // Second to last step of complements method, one is added to answer
    
        answer=add(answer,one);
    
    // Finished with nines_complement and one
    
        free(nines_complement);
        free(one);
    
    // We know that the size of answer has been incremented by one, and this new 
    // digit holds a 1...final step of this algorithm is to remove the one.
    
        answer->digits[(answer->size-1)]=0;
        
    // Removes leading zeroes from answer    
        
        remove_zeroes(answer);
        return answer;
    }
    
    void remove_zeroes(struct integer *answer)
    {
        
    // First digit will be a zero
        
        int not_a_leading_zero=0, counter;
    
    // Runs until counter > 0 because if answer is 0 we want it left that way
    
        for(counter=answer->size-1;counter>0;counter--)
        {
            if(answer->digits[counter]==0 && not_a_leading_zero==0)
    
    // Size of answer is smaller
    
                answer->size--;
            else
                not_a_leading_zero=1;
        }
    
    // Reallocate memory for answer, only the leading zeroes will be removed
    
        answer->digits=realloc(answer->digits,answer->size*sizeof(int));
    }
    
    void print_bigint(struct integer *p)
    { 
        int counter;
     
    // Since the number is stored in reverse order, we print it out in reverse order
    // to get the number as it should appear
     
        for(counter=p->size-1;counter>=0;counter--)
        {
            printf("%d",p->digits[counter]);
        }
    }        
            
    int compare(struct integer *p, struct integer *q)
    {
        int counter;
    
    // If p has more digits then it is larger
    
        if(p->size>q->size)
            return 1;
    
    // if p has fewer digits then p is smaller
    
        if(p->size<q->size)
            return -1;
    
        for(counter=q->size-1;counter>=0;counter--)
        {
    
    // If any digit from p is greater than the digit in the same place of q
    // is larger, then p is larger
    
            if(p->digits[counter]>q->digits[counter])
                return 1;
    
    // If any digit from q is greater than the digit in the same place of p
    // then q is larger
    
            if(q->digits[counter]>p->digits[counter])
                return -1;
        }
    
    // Digits are equal
    
        return 0;
    }
    
    void format_output(struct integer *p, struct integer *q, struct integer *answer, 
                       int operation)
    {
         int counter, difference=0;    
         printf("\n");
         switch(operation)
         {
             case ADD: 
          
    // If p is greater than q, then print p and print spaces equal to the 
    // difference in size of p and q...this is strictly aesthetic      
          
                  if(compare(p,q)==1)
                  {
                      print_bigint(p);
                      printf("\n+\n");
                      print_characters(p->size-q->size,' ');
                      print_bigint(q);
                  }
                  else
                  {
    
    // If q is larger, then we print spaces, then print p, then print q
    
                      print_characters(q->size-p->size, ' ');
                      print_bigint(p);
                      printf("\n+\n");
                      print_bigint(q);
                  }
                  printf("\n");
                  print_characters(answer->size,'-');
                  break;
    
    // The specs for the program do not allow for negative numbers...this uses
    // the same logic as above, only prints the larger number on top of the smaller
    // number. Since the result of subtraction can have a variety of sizes, we set
    // difference to the larger numbers size minus the size of the answer
    
             case SUBTRACT:
                  if(compare(p,q)==1)
                  {
                      print_bigint(p);
                      printf("\n-\n");
                      print_characters(p->size-q->size,' ');
                      print_bigint(q);
                      printf("\n");
                      print_characters(p->size,'-');
                      difference=p->size-answer->size;
                  }
                  else
                  {
                      print_bigint(q);
                      printf("\n-\n");
                      print_characters(q->size-p->size, ' ');
                      print_bigint(p);
                      printf("\n");
                      print_characters(q->size,'-');
                      difference=q->size-answer->size;                  
                  }
                  break;
    
        } // End Switch
    
        printf("\n");
    
    // Prints spaces to format the answer, this will always be zero
    // in the case of addition
    
        print_characters(difference,' ');
        print_bigint(answer);
        printf("\n\n");
    
    }
    
    void print_characters(int how_many, char character)
    {
         int counter;
    
    // Prints character n times
    
         for(counter=0;counter<how_many;counter++)
             printf("%c",character);
    }

  2. #2
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    You're doing this is an extremely complicated way, way more than necessary.
    C has built-in types for integers. You can use int or long long for numbers instead and it would be easier.
    Note also that some structs have pointers as members where you allocate memory to, but you never free those. Freeing the struct does not free the memory associated with its pointers.

    You also read strings with scanf in the wrong way. See http://cpwiki.sourceforge.net/Buffer_overrun
    Also take a look at http://www.cse.scu.edu/~tschwarz/COE...es/Strings.ppt if you can, as it highlights the pitfalls and security issues of C.
    Last edited by Elysia; 05-24-2008 at 03:04 PM.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  3. #3
    Registered User
    Join Date
    May 2008
    Posts
    11

    Specs

    You are right about the scanf, I was just reading someone elses post about that....thanks for pointing that out.


    In the assignment we are to create a the type "integer" that is meant to handle arbitrarily large integers, say hundreds of digits long. Although the output may not be pretty with a 199 digit number, the answer should still be correct.

    Could you elaborate a little further about how to free the memory associated with the structs pointers?

    Thanks for the help

  4. #4
    Registered User
    Join Date
    May 2008
    Posts
    11

    Data type

    Typical academia type stuff, do something the hard way just to make you learn something

  5. #5
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    I mean if you do
    Code:
    struct s
    {
        char* p;
    }
    
    s* s1 = malloc(sizeof(*s1));
    s1.p = malloc(10);
    free(s1);
    /* You never free p; it is not automatically freed */
    And btw, never cast the return of malloc.
    And I still think you can simplify it. Just treat the whole number as a string and convert it to a number when doing archimethic operations.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  6. #6
    Registered User
    Join Date
    May 2008
    Posts
    11

    in your example

    Is there a way to free "p" ?

    say would

    Code:
    free(s1->p);
    work?
    Last edited by wd_kendrick; 05-24-2008 at 03:24 PM.

  7. #7
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    Yes, that would be the way to do it.
    Each allocated pointer must be separately freed.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  8. #8
    Registered User ssharish2005's Avatar
    Join Date
    Sep 2005
    Location
    Cambridge, UK
    Posts
    1,732
    As said before, you making things more complicated than it should be and perhaps most of the thing can be done more easily. With that i could that you have learnt quite a lot of logics and stuff which you could impalement them in C. But which are not really necessary at all here.

    I can also see few thing which, like casting malloc and realloc which you don't have to do them, since the return type is of void * type for both two function. And you read integer as a string and then you convert them using a standard procedure, as said before you could use int data type or you could still read them as a string and them convert them using atoi or using sscanf function as well.

    And keeping things simple will give your code more clarity and more marks indeed .

    ssharish

  9. #9
    Registered User
    Join Date
    May 2008
    Posts
    11

    Right on

    I have made the changes recommended above...Most of the function prototypes were provided by the instructor, and we had to implement them using his defined pre and post conditions. Some of the code may seem overly complex because of this.


    As far as atoi goes...I could not get it to work...

    Code:
    p->digits[counter]=atoi(stringInt[counter]);
    so I decided to subtract a '0' and get the same result.

    This code was quite a leap from the topics covered in Programming 1 (where we barely even touched on pointers, and covered what a struct was in the last day of class)

    Maybe some of the code can help someone who i having trouble passing structs to functions, returning pointers to structs from functions, using malloc, or realloc.

    It would have certainly saved me a lot of time!

    Thanks again, any other advice is welcome.

  10. #10
    Registered User ssharish2005's Avatar
    Join Date
    Sep 2005
    Location
    Cambridge, UK
    Posts
    1,732
    atoi usage

    Code:
    #include <stdio.h>
    #include <stdlib.h>
    
    int main()
    {
        char *numinstr = "1234";
        int numinint;
        
        numinint = atoi( numinstr);
        
        if( numinint != 0 )
            printf("Converted number - &#37;d\n", numinint );
        else
            printf("Conversion failed\n");
        
        getchar();
        return 0;
        
    }
    
    /* my output
    Converted number - 1234
    */
    ssharish

  11. #11
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    Atoi or strtol takes a string, and not a single char.
    The fact that you have troubles with pointers suggests that you should make the program less complex.

    Btw, ssharish2005, don't forget that atoi returns 0 if the string is "0"!
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  12. #12
    Registered User
    Join Date
    Oct 2007
    Posts
    21

    Hello

    Hello Wes, i am a Novice in C programming and eager to learn more.
    Do you have any advice how to understand C better since i am having a hard time understanding the whole concept.
    This is my understanding so far:
    1 Declare variable so compiler will have variables to use
    2. Write the main function
    3. Larger programs have to have multiple functions that will be called by main

    Any other suggestions??

  13. #13
    Registered User
    Join Date
    Sep 2006
    Posts
    8,868
    kirksson, you need to google search for C tutorials. Go through them on-line, and you will learn a lot, free.

    You can't beat that!

    They will cover a lot more info than we can cover in a forum, and the information is already there, so no need to re-do it, here.

    Kendrick - I'm tempted to show you how easily this job can be done - but I don't want to send you into terminal depression.

    In RL, don't ever do a program in such a Rube Goldberg, way. I know you had a lot of conditions from your instructor, that had to be met. Just letting you know.
    Last edited by Adak; 05-27-2008 at 06:30 PM.

  14. #14
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    Ah, but books are usually more in-depths than tutorials, so one of those may be suggested. See the recommended books thread.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  15. #15
    Registered User
    Join Date
    May 2008
    Posts
    53
    Quote Originally Posted by wd_kendrick View Post
    Typical academia type stuff, do something the hard way just to make you learn something
    I think "implement an arbitrarily big number data type along with its basic operators" is part of virtually every programming course in universities. I think it's pretty useful too.

    --
    Computer Programming: An Introduction for the Scientifically Inclined

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Rewrote my cMap class. Looking for more criticism
    By Raigne in forum C++ Programming
    Replies: 18
    Last Post: 11-07-2008, 01:58 PM
  2. Looking for criticism on my GameState class
    By Raigne in forum Game Programming
    Replies: 1
    Last Post: 03-21-2008, 09:45 AM
  3. Constructive criticism highly appreciated!
    By muggizuggi in forum C++ Programming
    Replies: 17
    Last Post: 08-01-2005, 11:54 AM
  4. Alpha Criticism: Part One......
    By Alphacentric666 in forum C++ Programming
    Replies: 11
    Last Post: 01-28-2003, 11:40 PM
  5. a little OT, but, criticism and avice wanted !
    By Shadow in forum C++ Programming
    Replies: 2
    Last Post: 10-01-2001, 08:38 AM