Telling Functions About Structures

I l @ ve RuBoard

Telling Functions About Structures

Recall that function arguments pass values to the function. Each value is a number, perhaps int , perhaps float , perhaps ASCII character code, or perhaps an address. A structure is a bit more complicated than a single value, so it is not surprising that older implementations do not allow a structure to be used as an argument for a function. This limitation has been removed in newer implementations, and ANSI C allows structures to be used as arguments. Therefore, modern implementations give you a choice between passing structures as arguments or passing pointers to structures as arguments, or if you are concerned with just part of a structure, you can pass structure members as arguments. We'll examine all three methods , beginning with passing structure members as arguments.

Passing Structure Members

As long as a structure member is a data type with a single value (that is, an int or one of its relatives, a char , a float , a double , or a pointer), it can be passed as a function argument. The fledgling financial analysis program in Listing 14.5, which adds the client's bank account to his or her savings and loan account, illustrates this point.

Listing 14.5 The funds1.c program.
 /* funds1.c -- passing structure members as arguments */ #include <stdio.h> #define FUNDLEN 50 struct funds {     char   bank[FUNDLEN];     double bankfund;     char   save[FUNDLEN];     double savefund; }; double sum(double, double); int main(void) {     struct funds stan = {         "Garlic-Melon Bank",         2024.72,         "Lucky's Savings and Loan",         8237.11     };     printf("Stan has a total of $%.2f.\n",            sum(stan.bankfund, stan.savefund) );     return 0; } /* adds two double numbers */ double sum(double x, double y) {     return(x + y); } 

Here is the result of running this program:

 Stan has a total of 261.83. 

Ah, it works. Notice that the function sum() neither knows nor cares whether the actual arguments are members of a structure; it requires only that they be type double .

Of course, if you want a called function to affect the value of a member in the calling function, you can transmit the address of the member:

 modify(&stan.bankfund); 

This would be a function that altered Stan's bank account.

The next approach to telling a function about a structure involves letting the called function know that it is dealing with a structure.

Using the Structure Address

We will solve the same problem as before, but this time we will use the address of the structure as an argument. Because the function has to work with the funds structure, it, too, has to make use of the funds template. See Listing 14.6 for the program.

Listing 14.6 The funds2.c program.
 /* funds2.c -- passing a pointer to a structure */ #include <stdio.h> #define FUNDLEN 50 struct funds {     char   bank[FUNDLEN];     double bankfund;     char   save[FUNDLEN];     double savefund; }; double sum(const struct funds *);  /* argument is a pointer */ int main(void) {     struct funds stan = {         "Garlic-Melon Bank",         2024.72,         "Lucky's Savings and Loan",         8237.11     };     printf("Stan has a total of $%.2f.\n", sum(&stan));     return 0; } double sum(const struct funds * money) {     return(money->bankfund + money->savefund); } 

This, too, produces the following output:

 Stan has a total of 261.83. 

The sum() function uses a pointer ( money ) to a fund structure for its single argument. Passing the address &stan to the function causes the pointer money to point to the structure stan . Then the -> operator is used to gain the values of stan.bankfund and stan.savefund . Because the function does not alter the contents of the pointed-to value, it declares money as a pointer-to- const .

This function also has access to the institution names , although it doesn't use them. Note that you must use the & operator to get the structure's address. Unlike the array name, the structure name alone is not a synonym for its address.

Passing a Structure as an Argument

For compilers that permit passing structures as arguments, the last example can be rewritten as shown in Listing 14.7.

Listing 14.7 The funds3.c program.
 /* funds3.c -- passing a pointer to a structure */ #include <stdio.h> #define FUNDLEN 50 struct funds {     char   bank[FUNDLEN];     double bankfund;     char   save[FUNDLEN];     double savefund; }; double sum(struct funds moolah);  /* argument is a structure */ int main(void) {     struct funds stan = {         "Garlic-Melon Bank",         2024.72,         "Lucky's Savings and Loan",         8237.11     };     printf("Stan has a total of $%.2f.\n", sum(stan));     return 0; } double sum(struct funds moolah) {     return(moolah.bankfund + moolah.savefund); } 

Again, the output is this:

 Stan has a total of 261.83. 

We replaced money , which was a pointer to struct funds , with moolah , which is a struct funds variable. When sum() is called, an automatic variable moolah is created according to the funds template. The members of this structure are then initialized to be copies of the values held in the corresponding members of the structure stan . Therefore, the computations are done by using a copy of the original structure, whereas the preceding program used the original structure itself. Because moolah is a structure, the program uses moolah.bankfund , not moolah->bankfund . Listing 14.6 used money->bankfund because money is a pointer, not a structure.

More on the New, Improved Structure Status

Under modern C, including ANSI C, not only can structures be passed as function arguments, they can also be returned as function return values. To make this return mechanism workable , the values in one structure can be assigned to another. That is, if n_data and o_data are both structures of the same type, you can do the following:

 o_data = n_data;    /* assigning one structure to another */ 

This causes each member of o_data to be assigned the value of the corresponding member of n_data . Also, you can initialize one structure to another of the same type:

 struct names right_field = {"Ruthie", "George"}; struct names captain = right_field;  /* initialize a structure */ 

Using structures as function arguments enables you to convey structure information to a function, and using functions to return structures enables you to convey structure information from a called function to the calling function. Structure pointers also allow two-way communication, so you can often use either approach to solve programming problems. Let's look at another set of examples illustrating these two approaches.

To contrast the two approaches, we'll write a simple program that handles structures by using pointers; then we'll rewrite it by using structure passing and structure returns. The program itself asks for your first and last names and reports the total number of letters in them. This project hardly requires structures, but it offers a simple framework for seeing how they work. Listing 14.8 presents the pointer form.

Listing 14.8 The nameln1.c program.
 /* nameln1.c -- uses pointers to a structure */ #include <stdio.h> #include <string.h> struct namect {     char fname[20];     char lname[20];     int letters; }; void getinfo(struct namect *); void makeinfo(struct namect *); void showinfo(const struct namect *); int main(void) {     struct namect person;     getinfo(&person);     makeinfo(&person);     showinfo(&person);     return 0; } void getinfo (struct namect * pst) {     printf("Please enter your first name.\n");     gets(pst->fname);     printf("Please enter your last name.\n");     gets(pst->lname); } void makeinfo (struct namect * pst) {     pst->letters = strlen(pst->>fname) +                    strlen(pst->>lname); } void showinfo (const struct namect * pst) {     printf("%s %s, your name contains %d letters.\n",         pst->fname, pst->lname, pst->letters); } 

Compiling and running the program produces results like the following:

 Please enter your first name.  Viola  Please enter your last name.  Plunderfest  Viola Plunderfest, your name contains 16 letters. 

The work of the program is allocated to three functions called from main() . In each case, the address of the person structure is passed to the function.

The getinfo() function transfers information from itself to main() . In particular, it gets names from the user and places them in the person structure, using the pst pointer to locate it. Recall that pst->lname means the lname member of the structure pointed to by pst . This makes pst->lname equivalent to the name of a char array, hence a suitable argument for gets() . Note that although getinfo() feeds information to the main program, it does not use the return mechanism, so it is type void .

The makeinfo() function performs a two-way transfer of information. By using a pointer to person , it locates the two names stored in the structure. It uses the C library function strlen() to calculate the total number of letters in each name and then uses the address of person to stow away the sum. Again, the type is void . Finally, the showinfo() function uses a pointer to locate the information to be printed. Because this function does not alter the contents of an array, it declares the pointer as const .

In all these operations, there has been but one structure variable, person , and each of the functions used the structure address to access it. One function transferred information from itself to the calling program, one transferred information from the calling program to itself, and one did both.

Now let's see how you can program the same task using structure arguments and return values. First, to pass the structure itself, use the argument person rather than &person . The corresponding formal argument, then, is declared type struct namect instead of being a pointer to that type. Second, to provide structure values to main() , you can return a structure. Listing 14.9 presents the nonpointer version.

Listing 14.9 The nameln2.c program.
 /* nameln2.c -- passes and returns structures */ #include <stdio.h> #include <string.h> struct namect {     char fname[20];     char lname[20];     int letters; }; struct namect getinfo(void); struct namect makeinfo(struct namect); void showinfo(struct namect); int main(void) {     struct namect person;     person = getinfo();     person = makeinfo(person);     showinfo(person);     return 0; } struct namect getinfo(void) {     struct namect temp;     printf("Please enter your first name.\n");     gets(temp.fname);     printf("Please enter your last name.\n");     gets(temp.lname);     return temp; } struct namect makeinfo(struct namect info) {     info.letters = strlen(info.fname) + strlen(info.lname);     return info; } void showinfo(struct namect info) {     printf("%s %s, your name contains %d letters.\n",         info.fname, info.lname, info.letters); } 

This version produces the same final result as the preceding one, but it proceeds in a different manner. Each of the three functions creates its own copy of person , so this program uses four distinct structures instead of just one.

Consider the makeinfo() function, for example. In the first program, the address of person was passed, and the function fiddled with the actual person values. In this second version, a new structure called info is created. The values stored in person are copied to info , and the function works with the copy, so when the number of letters is calculated, it is stored in info , but not in person . The return mechanism, however, fixes that. The makeinfo() line

 return info; 

combines with the main() line

 person = makeinfo(person); 

to copy the values stored in info into person . Note that the makeinfo() function had to be declared type struct namect because it returns a structure.

Structures or Pointer to Structures?

Suppose you have to write a structure- related function. Should you use structure pointers as arguments, or should you use structure arguments and return values? Each approach has its strengths and weaknesses.

The two advantages of the pointer argument method are that it works on older as well as newer C implementations and that it is quick; you just pass a single address. The disadvantage is that you have less protection for your data. Some operations in the called function could inadvertently affect data in the original structure. However, the ANSI C addition of the const qualifier solves that problem. For example, if you put code into the showinfo() function that changes any member of the structure, the compiler will catch it as an error.

One advantage of passing structures as arguments is that the function works with copies of the original data, which is safer than working with the original data. Also, the programming style tends to be clearer. Suppose you define the following structure type:

 struct vector = {double x; double y;}; 

You want to set the vector ans to the sum of the vectors a and b . You could write a structure-passing and returning function that would make the program like this:

 struct vector ans, a, b, sum_vect(); ... ans = sum_vect(a,b); 

The preceding version is more natural-looking to an engineer than a pointer version, which might look like this:

 struct vector ans, a, b; void sum_vect(); ... sum_vect(&a, &b, &ans); 

Also, in the pointer version, the user has to remember whether the address for the sum should be the first or the last argument.

The two main disadvantages to passing structures are that older implementations might not handle the code and that it wastes time and space. It's especially wasteful to pass large structures to a function that uses only one or two members of the structure. In that case, passing a pointer or passing just the required members as individual arguments makes more sense.

Typically, programmers use structure pointers as function arguments for reasons of efficiency, using const when needed to protect data from unintended changes. Passing structures by value is most often done for structures that are small to begin with.

Character Arrays or Character Pointers in a Structure

The examples so far have used character arrays to store strings in a structure. You might have wondered if you can use pointers-to- char instead. For example, Listing 14.3 had this declaration:

 #define LEN 20 struct names {     char first[LEN];     char last[LEN]; }; 

Can you do this instead?

 struct pnames {     char * first;     char * last; }; 

The answer is that you can, but you might get into trouble unless you understand the implications. Consider the following code:

 struct names veep = {"Talia", "Summers"}; struct pnames treas = {"Brad", "Fallingjaw"}; printf("%s and %s\n", veep.first, treas.first); 

This is valid code, and it works, but consider where the strings are stored. For the struct names variable veep , the strings are stored inside the structure; the structure has allocated a total of 40 bytes to hold the two named. For the struct pnames variable treas , however, the strings are stored wherever the compiler stores string constants. All the structure holds are the two addresses, which, on our system, take a total of 8 bytes. In particular, the struct pnames structure allocates no space to store strings. It can be used only with strings that have had space allocated for them elsewhere, such as string constants or strings in arrays. In short, the pointers in a pnames structure should be used only to manage strings that were created and allocated elsewhere in the program.

Let's see where this restriction is a problem. Consider the following code:

 struct names accountant; struct pnames attorney; puts("Enter the last name of your accountant:"); scanf("%s", accountant.last); puts("Enter the last name of your attorney:"); scanf("%s", attorney.last);   /* here lies the danger */ 

As far as syntax goes, this code is fine. But where does the input get stored? For the accountant, the name is stored in the last member of the accountant variable; this structure has an array to hold the string. For the attorney, scanf() is told to place the string at the address given by attorney.last . Because this is an uninitialized variable, the address could have any value, and the program could try to put the name anywhere . If you were lucky, the program might work, at least some of the time. Or the attempt could bring your program to a crashing halt. Actually, if the program works, you're unlucky, for the program will have a dangerous programming error of which you are unaware.

So if you want a structure to store the strings, use character array members. Storing pointers-to- char has its uses, but has the potential for serious misuse.

Functions Using an Array of Structures

Suppose you have an array of structures that you want to process with a function. The name of an array is a synonym for its address, so it can be passed to a function. Again, the function needs access to the structure template. To show how this works, Listing 14.10 expands our monetary program to two people so that it has an array of two funds structures.

Listing 14.10 The funds4.c program.
 /* funds4.c -- passing an array of structures to a function */ #include <stdio.h> #define FUNDLEN 50 #define N 2 struct funds {     char   bank[FUNDLEN];     double bankfund;     char   save[FUNDLEN];     double savefund; }; double sum(const struct funds *money, int n); int main(void) {     struct funds jones[N] = {         {             "Garlic-Melon Bank",             2024.72,             "Lucky's Savings and Loan",             8237.11         },         {             "Honest Jack's Bank",             1834.28,             "Party Time Savings",             2903.89         }     };     printf("The Joneses have a total of $%.2f.\n",            sum(jones,N));     return 0; } double sum(const struct funds *money, int n) {     double total;     int i;     for (i = 0, total = 0; i < n; i++, money++)         total += money->bankfund + money->savefund;     return(total); } 

The output is this:

 The Joneses have a total of 000.00. 

(What an even sum! One would almost think the figures were invented.)

The array name jones is the address of the array. In particular, it is the address of the first element of the array, which is the structure jones[0] . Therefore, initially the pointer money is given by this expression:

 money = &jones[0]; 

Then the -> operator enables you to add the two funds for the first Jones. This is very much like Listing 14.6. Next, the for loop increments the pointer money by 1. Now it points to the next structure, jones[1] , and the rest of the funds can be added to total .

These are the main points:

  • You can use the array name to pass the address of the first structure in the array to a function.

  • You can then use pointer arithmetic to move the pointer to successive structures in the array. Note that the function call

     sum(&jones[0],N) 

    would have the same effect as using the array name because both refer to the same address. Using the array name is just an indirect way of passing the structure address.

  • Because the sum() function isn't supposed to alter the original data, we used the ANSI C const qualifier.

I l @ ve RuBoard


C++ Primer Plus
C Primer Plus (5th Edition)
ISBN: 0672326965
EAN: 2147483647
Year: 2000
Pages: 314
Authors: Stephen Prata

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net