// Bank account "objects" in C #include #include #define BASE 0 #define SAVINGS 1 #define CHECKING 2 typedef struct accstruct * account; // basic account type struct accstruct { char tag; double balance; char* name; }; void bless(account A, char dt) // from Perl { A->tag = dt; } /* Why is it necessary to attach a tag to a data structure to indicate its type? Because in C, type names such as account, savings, checking, etc, are only meaningful at compile time. This type information is no longer available at runtime. An account is just a pointer, a memory address, that points to a sequence of types making up the components of the struct. So if we want to call different functions based on the TYPE of a structure, that decision must be made at compile time. UNLESS, I add my own type information in the form of a piece of data that is part of the structure itself. Then I can also decide which function to call during runtime, because now there is runtime type information that's available. The "bless" operation in Perl represents the minimal level of built-in support that a language can have for OOP. It attaches a runtime-available tag to structures, for the purpose of *dynamic dispatch*. */ // constructor - separate account allocacc() { return (account)malloc(sizeof(struct accstruct)); } void initaccount(account A, char *na, double bal) { // account A = (account)malloc(sizeof(struct accstruct)); A->balance = bal; // set init balance A->name = na; bless(A,BASE); // return A; } // base procedures on basic accounts, no overloading in C double binquiry(account A) { return A->balance; } void bdeposit(account A, double amt) { A->balance += amt; } void bwithdraw(account A, double amt) { if (amt<=A->balance) A->balance -= amt; } typedef struct sastruct* savings; typedef struct castruct* checking; struct sastruct { //account base; // makes coding cumbersome char tag; double balance; // need to be in order for upcast to work char* name; char* gift; }; struct castruct { char tag; double balance; char* name; double fee; }; // combine allocation and initialization with a "constructor": savings newsavings(char *name, double initbal, char* gift) { savings A = (savings)malloc(sizeof(struct sastruct)); initaccount((account)A,name,initbal); // "superclass constructor" A->gift = gift; bless((account)A,SAVINGS); return A; } checking newchecking(char *name, double initbal, double fee) { checking A = (checking)malloc(sizeof(struct castruct)); initaccount((account)A,name,initbal); // "superclass constructor" A->fee = fee; bless((account)A,CHECKING); return A; } // "override" methods void sdeposit(savings A, double amt) { if (amt>=10000) printf("Congratulations %s, you won a %s!\n",A->name,A->gift); bdeposit((account)A,amt); } void cwithdraw(checking A, double amt) { if (amt>=10000) amt += A->fee; bwithdraw((account)A,amt); } // can call inquiry((account)A) for savings, checking typedef void (*transaction)(account,double); // type of withdraw,deposit // global dispatch vectors for withdraw,deposits based on tag transaction Ddispatch[3] = {bdeposit,(transaction)sdeposit,bdeposit}; transaction Wdispatch[3] = {bwithdraw,bwithdraw,(transaction)cwithdraw}; ////// functions that use the vectors to dispatch: void deposit(account A, double amt) { switch (A->tag) { case 0: bdeposit(A,amt); break; case 1: sdeposit((savings)A,amt); break; case 2: bdeposit(A,amt); }//switch } void withdraw(account A, double amt) { Wdispatch[A->tag](A,amt); } double inquiry(account A) { return binquiry(A); } ///////// int main(int argc, char** argv) { account myaccount = allocacc(), youraccount = allocacc(); initaccount(myaccount, "me", 100); initaccount(youraccount,"you",200); bwithdraw(myaccount,50); // not myaccount->withdraw(50); bdeposit(youraccount,100); printf("%.2f, %.2f\n",binquiry(myaccount),binquiry(youraccount)); // Now for savings and checking accounts.. Want the following: // end of the months, lots of paychecks to deposit..., some into // checking accounts and others into savings accounts... account bank[4]; bank[0] = (account)newsavings("F. Arman Imal",20000,"toaster"); bank[1] = (account)newchecking("P. Artyan Imal",20000,10); bank[2] = (account)newchecking("Nev Erstudy",30000,5); bank[3] = (account)newchecking("Haten Umbers",15000,10); withdraw(bank[1],10000); // should incur fee deposit(bank[1],10000); // should work like bdeposit (inherited) int i=0; for(;i<4;i++) deposit(bank[i],10000); // deposits! for(i=0;i<4;i++) withdraw(bank[i],10); // processing fee // deposit(bank[0],10000); // should get free gift // withdraw(bank[0],10000); // should work like bwithdraw for(i=0;i<4;i++) printf("balance for %s: %.2f\n", bank[i]->name, inquiry(bank[i])); return 0; }//main /**** What are the disadvantages of this approach to "OOP"? 1. Scalability. What if another type of account (say IRA account) is added with its own attributes. How can we *modularly* extend this program? We would have to at least redo the dispatch portion. 2. Safety. What if we made a mistake in a switch statement, if-elsif sequence, or swapped some values in the dispatch vector. What kind of error message would I get for such a mistake? ****/