Using functions in C, declaring a variable and usi

2019-09-22 05:19发布

问题:

I want to make a linked list that is called from my main somewhere else. This linked list is made of nodes.

For example, this isn't my code just simplified version of it.

nodeTest.h

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

struct nodeTest
{
    int data;
    struct nodeTest* next;
};

Then I have another file trying to use that struct:

nodeTest.c

 #include <stdio.h>
 #include <stdlib.h>
 #include "nodeTest.h"

 int main(void) {
     struct nodeTest* first = malloc(sizeof(struct nodeTest));
     first->data = 0;
     return 0;
 }

void addLL(int data){
    if (first.data = 0)
    {
        printf("No head\n");
    }
}

I want first to be the first element in a linked list so how do I define an addElement to it that utilizes first? Right now when I call first I am getting an unrecognized error.

Here is where I get the error inside my linked list file after the main

void addLL(int data){
    if (head.data = 0)

I get the error at the if line, head is not recognized.

回答1:

Right. You get that error because the variable head is only visible from inside main. There are two ways around this:

  1. You can declare head as a global variable, outside of any functions; or
  2. You can pass head to any functions that need it as a pointer

Let's see how these options work:

Using a global variable

#include <stdio.h>
#include <stdlib.h>
#include "nodeTest.h"

/* Declare a global variable. The initialization to NULL is redundant
 * since global variables are automatically initialized to zero. But
 * let's be thorough.
 */

struct nodeTest* head = NULL;

int main(void) {
   head = malloc(sizeof(struct nodeTest));
   /* check if head is NULL here. Just in case. */
   head->data = 0;

   addLL(1);

   return 0;
}

/* other functions here */
void addLL(int data) {
   /* since we allocate head in main it should never be NULL and
    * if it is, something is wrong. So assert.
    */
   assert(head != NULL);

   /* Notice: in your original code you had: if (first.data = 0)
    * which is wrong. First of all, there's no variable named first
    * declared anywhere. If you meant head, that should have been
    * head->first since head is a pointer. And lastly, first.data = 0
    * would set first.data to 0, instead of comparing it for equivalency, 
    * and would then cause the if to never execute because the compiler
    * would first set first.data to zero, then check first.data, and never
    * enter the loop since it was zero.
    */
   if (head->data == 0)
   {
      printf("No head");
      return;
   }

   /* other code here */
}

This version is easy and requires minimal changes. A benefit is that functions that need to modify head can easily do it.

It does require, in non-trivial programs, a lot of global variables though, which is considered bad programming practice and ought to be avoided if possible.

Passing head as a pointer

#include <stdio.h>
#include <stdlib.h>
#include "nodeTest.h"

int main(void) {
   struct nodeTest* head = malloc(sizeof(struct nodeTest));
   /* check if head is NULL here. Just in case. */
   head->data = 0;

   /* we call addLL, passing the head that we just allocated
    * to it.
    */
   addLL(1, head);
   return 0;
}

void addLL(int data, struct nodeTest *head) {
   if (head == NULL) || (head->data == 0))
   {
      printf("No head");
      return;
   }

   /* other code here */
}

This method requires that we pass head into every function that can need it. This complicates the function interface slightly but allows us more flexibility because we don't need to have many global variables for the many lists we'll have in our program.

But there is another problem. If any of those functions needs to modify the head pointer (for example, let's say you're deleting the first element in a list) then you have an issue. The pointer (which is distinct from the thing pointed to) is local to the function so any changes made to it will be invisible outside the function.

There are two ways to get around this issue:

  1. Every function that accepts head as a parameter and may need to change it would have to return a pointer to the new head. This works but it can be cumbersome to implement and very prone to error.
  2. Every function that accepts head as a parameter and may need to change it would accept a double pointer: a pointer to a pointer to the head. This works well but complicates things and may catch some novice programmers off-guard. It's also a bit prone to error but not as much as the previous option.

I hope that this answers your question, explains why you can't do what you did and the workarounds that exist.



回答2:

Issue 0: understanding of linked lists and C

There is tons of material on that. Check out for instance these slides from a lecture on data structures.

Issue 1: 'head undeclared'

Before you use a variable you need to introduce it first. The issue results from head not being in scope of the function (according to C rules). So you would need to pass a pointer to it as an argument:

void addLL(struct nodeTest *head, int data){
  // now you can access head->data, head->next
  // .. 
}

If you use 0 to signify that no data is available then you can't store any zeroes. Another you could be to keep count of list elements and check if the count is zero to determine whther the list is empty.

Issue 2: 'first->data' undeclared

Either declare first (same as with head), or use head->data.

Issue 3: list design and API

Using global state is generally agreed to be bad practice so don't use a global list. I'd recommend you to define another struct for the list and pass a point around ot the other function like list_add() or list_remove(), so you could also save a pointer to the last element to make appen an O(1) operation, e.g.:

struct list {
  struct node *first;
  struct node *last;
  unsigned long length;
};

So then, you can implement a function to check whether the list is empty:

inline bool list_is_empty(struct list* l) {
  assert(l != NULL);
  return l->length;  
}

And use multiple lists in your main:

 struct list *list1 = list_create(); // sets length to 0
 struct list *list2 = list_create(); // return a new list
 //...
 list_add(&list1, data); // you pass a pointer to the list to which you want to add
 //...
 struct node *e = list_search(&list1, data);
 if (e == NULL)
   printf("not found\n");
 //...
 list_remove(&list1, data);
 //
 list_destroy(&list1); 

Adding an element to the list could be implemented like so:

int list_add(struct list* l, int data) {

    if (l == NULL)
        return LIST_FAILURE; // or exit with an error message

    l->last->next = list_elem_create(data); // dynamically creates a new node
    l->last = l->last->next;
    l->length++;
    return LIST_SuCCESS; // symbolc constant defined elsewhere 
}


回答3:

You cannot allocate memory before calling the main function. You can either declare a global variable outside main and use its address as the first. The alloc inside the main should work and thats the best way to do it. but you must typecast the struct something like below to be able to use it otherwise your compile will fail

typedef struct First {

<element 1>
struct first *next

} First;

then you can do this

First *first = NULL
first = (First *) malloc(sizeof(First));


标签: c struct