How to pass a string from a function to main?

2020-05-10 09:10发布

问题:

I tried looking for a solution but wasn't able to find one. Is it possible to return a string? I want to pass a string back from the function below to main. I want to pass the listofdeatils string.

Here's my code:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void readFile();

int main()
{
    readFile(); 
    getchar();
    printf("%s \n", listofdetails[1]);
    getchar();

    return 0;
}

void readFile()
{
    int i;
    FILE *fp;
    fp = fopen("specification.txt", "r");

    char ** listofdetails; 
    listofdetails = malloc(sizeof(char*)*6);

    for(i=0;i<6;i++)
    {
        listofdetails[i] = malloc(sizeof(char)*40);
        fgets(listofdetails[i], 40, fp);
        printf("%s \n", listofdetails[i]);
        free(listofdetails[i]);
    }

    free(listofdetails);
    fclose(fp);
}

回答1:

Try this:

void readFile(char listofdetails[6][40]);

int main()
{
    char listofdetails[6][40]; 
    readFile(listofdetails); 
    printf("%s \n", listofdetails[1]);
    return 0;
}

void readFile(char listofdetails[6][40])
{
    int i;
    FILE *fp;
    fp = fopen("specification.txt", "r");

    for(i=0;i<6;i++)
    {
        fgets(listofdetails[i], 40, fp);
        printf("%s \n", listofdetails[i]);
    }

    fclose(fp);
}


回答2:

So just return the string. Obviously don't free() it in the function then.

Instead of void, your function could return the char**, or you could change to allocate a single array of all the characters and index it like a matrix.



回答3:

Several options:

First option - you could return listOfDetails directly:

#define NUM_DETAILS    6
#define DETAIL_LENGTH 40
...
char **readFile( void )
{
  char **listOfDetails = 
    malloc( sizeof *listOfDetails * NUM_DETAILS ); // note operand of sizeof

  if ( listOfDetails )
  {
    ...
    listOfDetails[i] = 
      malloc( sizeof *listOfDetails[i] * DETAIL_LENGTH ); // note operand of sizeof
    ...
  }

  return listOfDetails;
}

int main( void )
{
  ...
  char **details = readFile();
  if ( details )
  {
     ...

    for ( int i = 0; i < NUM_DETAILS; i++ )
    {
      free( details[i] );
    }
    free( details );
  }
}

This is probably the straightest path from what you currently have written. Note that in the malloc calls, I use sizeof *listOfDetails and sizeof *listOfDetails[i]. The type of the expression *listOfDetails is char *, so sizeof *listOfDetails is equivalent to sizeof (char *). Similarly, the type of the expression *listOfDetails[i] is char, so sizeof *listOfDetails[i] is equivalent to sizeof (char).

This helps minimize maintenance headaches if you ever decide to change the type of listOfDetails. It's also a little easier to read IMO.

Note that main is now responsible for freeing the memory allocated by readFile; ideally, you'd like to avoid sharing memory management responsibility like this, but sometimes it can't be helped.

Second option - you could pass listOfDetails as an argument to your function:

void readFile( char ***listOfDetails ) // note extra level of indirection
{
   ...
   *listOfDetails = malloc( sizeof **listOfDetails * NUM_DETAILS );
   if ( *listOfDetails )
   {
     ...
     (*listOfDetails)[i] = 
       malloc( sizeof *(*listOfDetails)[i] * DETAIL_LENGTH ); // parens are required!
     ...
   }
}

int main( void )
{
  char **listOfDetails
  ...
  readFile( &listOfDetails ); // note & operator; I want readFile to modify the value
                              // stored in listOfDetails, so I must pass a 
                              // pointer to it; since the type of listOfDetails
                              // is char **, the resulting pointer will have type
                              // char ***, which is seen in the declaration of
                              // readFile above.
  ...
  // free as above
}

There isn't a ton of difference between this option and the first, except that it adds an extra level of indirection (which some people will find objectionable). You still have shared memory management responsibility. Note that the parentheses are required in the expression (*listOfDetails)[i]; we don't want to subscript listOfDetails directly, we want to subscript what it points to. Since [] has higher precedence than unary *, we have to explicitly group operators and operands.

Third option - Create separate functions to allocate and free the memory, call both from main:

char **createDetailList( size_t numDetails, size_t detailLength )
{
  char **details = malloc( sizeof *details * numDetails );
  if ( details )
  {
    for ( size_t i = 0; i < NUM_DETAILS; i++ )
    {
      details[i] = malloc( sizeof *details[i] * detailLength );
    }
  }
  return details;
}

void freeDetailList( char **detailList, size_t numDetails )
{
  for ( size_t i = 0; i < numDetails; i++ )
    free( detailList[i] );
  free( detailList );
}

void readFile( char **listOfDetails )
{
  ...
  fread( listOfDetails[i], DETAIL_LENGTH, fp );
  ...
}

int main( void )
{
  char **listOfDetails = createDetailList( NUM_DETAILS, DETAIL_LENGTH );
  ...
  readFile( listOfDetails ); // note no & operator in this case
  ...
  freeDetailList( listOfDetails, NUM_DETAILS );
}

This way, main is responsible for both allocating and deallocating the array. Breaking the memory management code out into separate functions serves two purposes: one, it reduces clutter in main; two, it allows us to re-use this code. Suppose you wanted to read multiple lists of details from multiple files for some reason:

int main ( void )
{
   char **list1 = createDetailList( NUM_DETAILS, DETAIL_LENGTH );
   char **list2 = createDetailList( NUM_DETAILS, DETAIL_LENGTH );
   ...
   readFile( list1, "file1.txt" ); // Note that we're passing the name of the file
   readFile( list2, "file2.txt" ); // as an argument; the definition of readFile
                                   // will need to change accordingly.
   ...
   freeDetailList( list1, NUM_DETAILS );
   freeDetailList( list2, NUM_DETAILS );
}

You could also create lists of different sizes (10 records of 80 characters each, say), although you'd need to convey that information to the readFile function somehow (usually as function arguments).

This works because you know ahead of time how big your array needs to be. If you don't know ahead of time how many detail records you need to read or the maximum length of each detail line, then this approach won't work, and you're back to allocating memory as you go in the readFile function.

Note that in this case, I don't expect readFile to modify listOfDetails, so I'm not passing a pointer to it.

Fourth option - if your array is fixed in size and not terribly big (which seems to be the case here), don't bother with dynamic memory management at all. Declare it in main as a regular array and pass it to the function as an argument:

void readFile( char (*listOfDetails)[DETAIL_LENGTH], size_t numDetails ) // or char listOfDetails[][DETAIL_LENGTH]
{
  ...
  for(i=0;i<numDetails;i++)
  {
    fgets(listofdetails[i], DETAIL_LENGTH, fp);
    printf("%s \n", listofdetails[i]);
  }
  ,,,
}

int main( void )
{
  char details[NUM_DETAILS][DETAIL_LENGTH];
  ...
  readFile( details, NUM_DETAILS ); // note no & operator; when an array expression
                                    // appears in most contexts, it will be converted
                                    // ("decay") to a pointer to the first element.  
                                    // In this case, details will be converted to a
                                    // pointer to an array of char, as you can see in
                                    // the declaration of the readFile function.
                                    // Note that I'm also passing the number of rows 
                                    // explicitly.
  ...
}

Advantages: no memory management hijinks, no multiple dereferences, etc. The main drawback is that it can only work with arrays where the number of columns is DETAIL_LENGTH; you can't use it to read a 6x50 array, for example. If you're only using the one array, this isn't a problem.

Pointers don't know whether they're pointing to a single object or a sequence of objects in an array, so you usually need to specify the number of rows as a separate parameter.

You could use the NUM_DETAILS constant, but ideally functions should communicate exclusively through parameter lists and return values; they should not share state indirectly by relying on global variables or magic constants. This "decouples" the function from the larger program, making it easier to reuse. For example, I'd define your readFile function as

void readFile( char **listOfDetails, 
               size_t numDetails, 
               size_t detailLength, 
               const char *filename )
{
  FILE *fp = fopen( filename, "r" );
  if ( fp )
  {
    for( size_t i = 0; i < numDetails; i++ )
    {
      fread( listOfDetails[i], detailLength, fp ); // no error checking for brevity
    }
  }
  fclose( fp );
}

This function doesn't rely on any shared information; it can easily be re-used by any other program, or used to read multiple lists of different sizes in the same program.

Note that in all four cases I replace the literals 6 and 40 with symbolic constants. This serves two purposes: first, the symbol names are more meaningful than the raw numbers; second, if you decide you want to read 7 detail records, or that the records now need to be 80 characters long, all you need to do is change the definition of the constants - you don't have to dig through the code and replace every instance of 6 or 40.



回答4:

In C you should think of strings as pointers to characters, that continue to a terminating '\0' character.

Then you have basically have two ways to return a 'string' to main.

  1. let readFile return a char* (or char** if it should be a list of strings)
  2. pass a storage container to readFile, e.g. readFile(char** strVal) and set it inside the function.


回答5:

You could modify your function to

char ** readFile()

and then at the end of the function definition add

return listofdetails;

As pointed out below, this works provided that the size of listofdetails is fixed and therefore you can write a counterpart function that frees the allocated memory.



回答6:

Pass a string to function and copy your string to that string using strcpy() Another way is to return string and make return type char*



标签: c function