The question is at follows
The structure below represents information about students and their list of grades in
all modules.
typedef struct {
char* module; //module, e.g. "COMP1028"
int grade; //numerical grade
} module_grade;
typedef struct {
unsigned int student_ID;
char* student_name;
module_grade* grades; //array of grades
unsigned int grades_len; //length of the array grades
} student;
You are required to implement THREE functions for managing students’ grades.
a) Write a C function called create_student_list that takes a student name
and a student ID and returns a pointer to a new_student. The grades of the
new_student will be initialized to NULL and grade_len is set to 0.
The definition of the function is:
student* create_student_list (char* stu_name, int stu_id) {
b) Write a C function search_grade that takes a pointer to a student and a module
and returns the grade of the student for this module. If the array of grades is
NULL or the module is not in the array of grades, the function shall return -1.
The definition of the function is:
int search_grade(student* s, char* module) {
c) Write a C function adding_grade that takes a pointer to a student and a module
grade and adds the grade to the student’s array of grades.
If student->grades already contain a similar module, the function does nothing.
If student->grades==NULL, you need to create an array of length 1 and add the
new grade into the array.
If student->grades!=NULL, you need to increase the size of the array by ONE,
and add new_grade into the new entry in the array of grades.
The function shall return 1 if a grade is successfully added. Otherwise, the
function shall return 0; this can be because the module is already there or malloc
fails.
The definition of the function is:
int adding_grade(student* s, module_grade new_grade)
in question a) I don't understand if he want it to be a linked list so we would need a loop but i can't manage to make a node and link it together so i only assigned the data to the pointer but it is not reading it correctly. outputting only one character and not the whole string.
typedef struct {
char* module; //module, e.g. "COMP1028"
int grade; //numerical grade
} module_grade;
typedef struct {
unsigned int student_ID;
char student_name[50];
module_grade* grades; //array of grades
unsigned int grades_len; //length of the array grades
} student;
student* create_student_list (const char *stu_name, int stu_id);
int main(){
student Student;
module_grade Module;
printf("enter the student name?\t");
scanf("%s",Student.student_name);
printf("%s",Student.student_name);
printf("\nenter the student ID?\t");
scanf("%d",&Student.student_ID);
printf("%d",Student.student_ID);
create_student_list( &Student.student_name, Student.student_ID);
//search grade
printf("enter the module wanted to know the grade?");
scanf("%s",Module.module);
search_grade(&Student,Module.module);
return 0;
}
student* create_student_list (const char *stu_name, int stu_id) {
student student1 = {.student_ID = stu_id,.student_name =
*stu_name};
student *newstudent = &student1;
printf("\n%s\t%d",&newstudent->student_name,newstudent->student_ID);
newstudent->grades = NULL;
newstudent->grades_len = 0;
}
for question b and c i can't manage to make it work because i don't know if i need a linked list or how to link it with the given structures in the question.
please i need help my future and progress in uni depends on it so i will really appreciate any answers or explanation.
Whether to use a linked list or not
You are right that the objects of type module_grade could be stored as a linked list. However, this would require the struct definition of module_grade to contain a pointer to the next node, i.e. the struct definition would have to be changed to the following:
typedef struct module_grade
{
char* module;
int grade;
//pointer to the next node in the linked list
struct module_grade *next;
} module_grade;
However, your teacher probably does not want you to change the struct definition, since it is part of your assignment to use the struct definitions provided by your teacher. Also, in the text of the assignment, the teacher uses the word "array". This probably means that the grades member of the struct definition of student should point to the start of an array, not the first node of a linked list.
The information above only applies to storing the module_grade objects. Whether your teacher wants you to store the individual student objects in a linked list is another question. Since the information you provided in the question does not provide any indication that you need to create more than one student object, there is no reason to create such a linked list. However, I suspect that your teacher may ask you to create such a linked list in a future assignment.
Errors in your code
Errors in the function main
The line
scanf("%s",Student.student_name);
will not work, because scanf requires you to pass a pointer to the memory address of a memory buffer which has sufficient space to store the string. However, you are instead passing a pointer which is not pointing to anything, because it has an indeterminate value.
The simplest way to allocate sufficient memory for storing the string would be to declare a local array.
You are making the same error in the following line:
scanf("%s",Module.module);
Also, in the line
create_student_list( &Student.student_name, Student.student_ID);
it is wrong to pass &Student.student_name, as that will pass the address of the pointer Student.student_name, but you instead want to pass its value. Also, you are discarding the return value of that function call, which you should also not do. Instead, you should use it.
You should also not declare a variable
student Student;
in the function main, because it is the responsibility of the function crate_student_list to create that object.
In order to fix these bugs, you could use the following code:
int main()
{
char student_name[100];
int student_ID;
char module_name[100];
student* p_student;
printf( "Enter the student name: " );
scanf( "99%s", student_name );
printf( "%s\n", student_name );
printf( "Enter the student ID: ");
scanf( "%d", &student_ID );
printf( "%d\n", student_ID );
p_student = create_student_list( student_name, student_ID );
if ( p_student == NULL )
{
fprintf( stderr, "Error creating student!\n" );
exit( EXIT_FAILURE );
}
printf("enter the module wanted to know the grade?");
scanf( "%99s", module_name );
search_grade( p_student, module_name );
return 0;
}
Note that I am limiting the number of matched characters by scanf to 99 characters, so that scanf will not try to write more than 100 characters (99 matched characters plus the terminating null character) to any of the buffers. Otherwise a buffer overflow will occur if the user enters too many characters, which will invoke undefined behavior (i.e. your program may crash).
It is also worth noting that it does not make much sense to call search_grade before calling adding_grade, because if there are no grades stored in Student, then the search will always fail.
Errors in the function create_student_list
The function create_student_list is supposed to return a value. You probably forgot to write
return newstudent;
at the end of this function.
However, this would be wrong, because the lifetime of the object student1, to which the pointer newstudent is pointing, will end as soon as the function create_student_list returns, because you declared it as a variable with automatic storage duration. Therefore, if you return a pointer to that object at the end of the function, then you will be returning a pointer to an object that no longer exists, i.e. a dangling pointer. Any attempt to dereference this pointer in the function main will invoke undefined behavior.
In order to prevent the lifetime of the object to automatically end when the function returns, you should not declare the object as a local variable. Instead, you should use malloc and free to have full control of the lifetime of the object.
Also, you will probably want to create a copy of the string that stu_name is pointing to. Otherwise, if the memory containing this string is later modified (for example used for something else), then you will lose the string. However, if the student object has its own copy of the string, then there is no danger of this happening.
Therefore, it would be better to write the function create_student_list like this:
student* create_student_list ( const char* stu_name, int stu_id )
{
//allocate memory for new_student
student* new_student = malloc( sizeof *new_student );
if ( new_student == NULL )
{
fprintf( stderr, "Warning: malloc failed!\n" );
return NULL;
}
//set struct members except for student_name
new_student->student_ID = stu_id;
new_student->grades = NULL;
new_student->grades_len = 0;
//allocate memory for student_name and copy it
new_student->student_name = malloc( strlen(stu_name + 1 ) );
if ( new_student->student_name == NULL )
{
fprintf( stderr, "Warning: malloc failed!\n" );
free( new_student );
return NULL;
}
strcpy( new_student->student_name, stu_name );
return new_student;
}
Related
i have been given a structure and a pointer to an array.
each index of the array is a letter of the alphabet. i need to receive a name, last name and phone number, and allocate memory to a struct (phonebook).
then, each struct needs to be accessed from the array using the last name's first letter.
if the function is called again i need to use linked list to add another contact.
i dont know how to allocate memory for a certain index of an array. when i try to do
phonebook[letter] = (Contact**)malloc(sizeof(Contact));
i keep having de reference warnings, and i cant seem to figure out how to point the address of phonebook[letter] to a structure properly.
this is what i have tried:
typedef struct Contact {
char* firstName;
char* lastName;
char* phoneNum;
struct Contact* next;
} Contact;
int main(){
Contact* phonebook[26];
addNewContact(phonebook)
}
int addNewContact(Contact** phonebook) {
char newFirstName[SIZE], newLastName[SIZE], newPhoneNum[SIZE];
int letter;
printf("Enter a contact details \
(<first name> <last name> <phone number>):\n");
scanf("%s%s%s", newFirstName, newLastName, newPhoneNum);
//get number of the letter in the alphabet
letter = newLastName[0] - 'A';
//allocate memory to pointer
Contact *current;
phonebook = (Contact**)malloc(sizeof(Contact));
if (phonebook == NULL) {
printf("The addition of the contact has failed!");
//free
exit(1);
}
current = phonebook[letter];
//check if details are being used
do {
//if the name already exists
if (phonebook[letter]->firstName == newFirstName \
&& phonebook[letter]->lastName == newLastName) {
printf("The addition of the contact has failed, \
since the contact %s %s already exists!\n", newFirstName, newLastName);
//free
return 0;
}
//if the phone number already exists
if (phonebook[letter]->phoneNum == newPhoneNum) {
printf("The addition of the contact has failed, \
since the phone number %s already exists!", newPhoneNum);
//free
return 0;
}
current = current->next;
} while (current != NULL);
//assigning
phonebook[letter]->firstName = newFirstName;
phonebook[letter]->lastName = newLastName;
phonebook[letter]->phoneNum = newPhoneNum;
return 0;
}
in addition, i havent figured out the linked list part at all, i managed before to enter the details to the structure (though im not sure if i even pointed the struct to the right place) but i dont know how to iterate after the first addition of the name. if i intialize current->next to be NULL for the first time, it will also happen the next time i call the function.
currently the code stops due do access violation but it seemed that after the first name i had error with reading the inputs that only occured after the second time.
You are actually pretty close to making it work. Probably the main problem with this code is that it has only pointers to the data, doesn't have space to store the data. You are pointing to phonebook[letter]->lastName = newLastName, a local variable that is destroyed when the function returns. This will make your strings a dangling pointer; when you try to access this data, undefined things happen. Probably the easiest way to fix that is to make a char array of maximum length instead of the pointers. You don't need to typedef in most cases, and sometimes it's confusing. I would recommend that you take the typedef out and just have,
#define SIZE 128
struct Contact {
char firstName[SIZE];
char lastName[SIZE];
char phoneNum[SIZE];
struct Contact* next;
};
Double-pointer Contact** phonebook is valid, but a recipe for confusion. Instead of naked pointers, use struct to encapsulate a phone book, using a list of struct Contact.
struct Phonebook {
struct Contact *letter[26];
};
printf is defined in stdio.h. strcmp is defined in string.h. malloc and exit are stdlib.h.
See What's the correct declaration of main()? C doesn't scan ahead; switch the order of the functions or have a prototype above.
Perhaps in the future you will read from a file? It's easier do if you read the input separately from the interface. Maybe split it up into a separate function. This should be static, since it doesn't need to be published to other translation units.
static struct Contact *inputContact(void)
To allocate memory, current = malloc(sizeof *current);. Did you have a typo using phonebook? If you allocate it first, you can read directly into the allocated memory and not do any copying. Make sure you limit the size. (scanf is not meant for this.)
#define STR(n) STR2(n)
#define STR2(n) #n
if(scanf("%" STR(SIZE) "s%" STR(SIZE) "s%" STR(SIZE) "s",
current->firstName, current->lastName, current->phoneNum) != 3)
exit(EXIT_FAILURE);
Then,
static int addNewContact(struct Phonebook* phonebook, struct Contact *contact)
do-while checks that the current is valid after you use it; you want while.
//get number of the letter in the alphabet
int letter = contact->lastName[0] - 'A'; // might want to check that
struct Contact *current = phonebook->letter[letter];
//check if details are being used
while (current != NULL) {
In C, equality means they are the same memory location, not in general the same text. You probably want a library function strcmp. (But are you sure your logic is correct in that it prevents the same number in appearing with the same first letter of the last name?)
if (!strcmp(current->phoneNum, contact->phoneNum))
Assigning will just overwrite the one letter that is there. You probably meant for it to be appended to the list.
contact->next = phonebook->letter[letter];
phonebook->letter[letter] = contact;
return 1;
And lastly, be sure to initialize any data that is of automatic duration.
struct Phonebook phonebook = {0}; /* Initialize null lists. */
addNewContact(&phonebook, inputContact());
--edit
This can be seen as creating a static-26-bucket separately-chained hash-table, with your hash function being hash(struct Contact *entry) { return entry->lastName[0] - 'A'; }.
I think you are doing this, which also makes sense. Make sure that you allocate Contact and 3 text fields, for a total of 4 mallocs per entry. If you read the data into a temporary buffer, you can use strcpy to transfer it to a longer-lived string that you've just allocated.
I am trying to work on an exercise for a class in which we are learning C. We have to create a singly linked list of struct STUDENT_RECORDs. Each student record is designed to be a node in a singly linked list. Here's what the definition is:
struct STUDENT_RECORD{
char *name;
float gpa;
int age;
struct STUDENT_RECORD *next;
};
The program I'm supposed to write takes input from the user and creates a singly linked list. Each node in the list is created from user inputs at runtime.
One of the fields of this struct is name. Since we receive input from the user from a loop that overwrites the name variable that stores the name input from the user, I will have to copy each name in order to have it saved in the list.
Here is what I have so far:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "student.h"
struct STUDENT_RECORD* addNode(char* name, int anAge, float aGPA){
//this is the function that creates the new STUDENT_RECORD and returns a reference to it
//copy the input name in another string
char nameCopy [strlen(name)+1];
int i = 0;
while(name[i] != '\0'){
nameCopy[i] = name[i];
i++;
}
nameCopy[strlen(name)] = '\0';
//create a new node/STUDENT_RECORD
struct STUDENT_RECORD *ttemp = (struct STUDENT_RECORD *)malloc (sizeof(struct STUDENT_RECORD));
//fill the new STUDENT_RECORD with input arguments
ttemp->name = nameCopy;
ttemp->age = anAge;
ttemp->gpa = aGPA;
ttemp->next = NULL;
puts("Test to see if the data is copied right:");
printf("%s, %d, %f\n", ttemp->name, ttemp->age, ttemp->gpa);
return ttemp;
}
int main() {
struct STUDENT_RECORD *head = {"dummy", 0, 0, NULL};
char selection='Y', aName[50], garbage;
int anAge;
float aGPA;
while (toupper(selection) == 'Y') {
// prompt the user for aName[], anAge, and aGPA
puts("Enter the student's name (up to 49 characters), age, and GPA: ");
scanf("%s", aName);
scanf("%d", &anAge);
scanf("%f", &aGPA);
struct STUDENT_RECORD *temp = addNode(aName, anAge, aGPA);
printf("Student created: %s, %d, %f\n", temp->name, temp-> age,temp-> gpa); //prints everything but the student name
printf("Continue? (Y/N): ");
//clear the buffer of the newline from the previous entry newline
garbage = getc(stdin);
scanf("%c", &selection);
}
//printNodes(head);
}
Now, one among the many issues I've been having with this is the fact that the pointer *temp does not seem to be able to see the name field of the STUDENT_RECORD. I am able to view all the others from the main, without problems. i.e., when I try to print all the fields of a returned STUDENT_RECORD, I get them all except the name. I don't see why this doesn't work: as far as I understand,after calling the addNode function, I am returned a reference to a node, and I should be able to print all its fields from the main, no?
I'm sure there are other issues too, but for now it would be a good start to at least be able to access all the fields of a given STUDENT_RECORD from the main function.
Thanks!!
You did not store a copy of the name. You made a local copy and then stored a pointer to it, which became invalid the moment your function returned.
This is why your test inside the function showed no problem. After the function returned, using that pointer resulted in undefined behavior.
Allocate memory for your string with malloc:
char *nameCopy = malloc(strlen(name)+1);
if (nameCopy) strcpy(nameCopy, name);
Remember to free this memory later when you get around to deleting your nodes.
Let's say I have this student struct defined:
struct student {
char *name;
};
typedef struct student Student
Now I have the following function:
void add_student(const char *student_name) {
// create new student
Student *new_s;
new_s = malloc(sizeof(Student));
strncpy(new_s->name, student_name, sizeof(new_s->name) - 1)
}
I want to add the student_name to the name of the new student struct. However because const char and char are different I have to use strncpy.
I tried it this way, however I get a segmentation fault, what's wrong?
You are only allocating memory for the structure new_s in this line
new_s = malloc(sizeof(Student));
This includes the variable char* name, which is a pointer to a char. Although, you also need memory to which this pointer will point at.
So, you need to allocate memory for the character pointer name inside the structure.
// create new student
Student *new_s;
new_s = malloc(sizeof(Student));
new_s->name = malloc(100); //assuming you need to store a string of len 100
As Johan Wentholt correctly outlined in his answer, you must allocate memory for both the Student structure and the string its member name points to, but you must return the new structure so the caller can do something with it:
Student *add_student(const char *student_name) {
Student *new_s = malloc(sizeof(Student));
if (new_s) {
new_s->name = strdup(student_name);
}
return new_s;
}
You code invokes undefined behavior because you did not allocate memory for the string, worse even, you left the name member uninitialized (malloc does not initialize the memory it returns).
Furthermore, you should not use strncpy. It is not some safe version of strcpy, it is a very error prone function, whose semantics are poorly understood by most programmers. NEVER use this function. If you see it used, there are chances you are either in front of a bug or there is a better method to replace it.
For completeness, your code:
strncpy(new_s->name, student_name, sizeof(new_s->name) - 1);
Would attempt to copy at most sizeof(char*)-1 characters from student_name into the array pointer to by new_s->name.
If the student_name is longer, the destination will not be null terminated,
If it is shorter, the destination will be padded with null bytes upto the given size.
Here the destination pointer is uninitialized and the size information is bogus anyway: you really want to copy all characters in the string plus the null terminator, which is exactly what strcpy does. But you need to allocate enough memory for that. You could use:
new_s->data = malloc(strlen(student_name) + 1);
strcpy(new_s->data, student_name);
The Posix function strdup() does both operations in one call:
new_s->data = strdup(student_name);
Haris gave a good solution. But like Florian Zwoch said in the comments you can also use strdup like so:
void add_student(const char *student_name)
{
Student *new_s = malloc(sizeof(Student));
new_s->name = strdup(student_name);
}
Keep in mind that you have to free new_s->name and than free new_s.
You should also check the return value of malloc and strdup for NULL value. Since it returns NULL if insufficient memory is available.
As side note, you can shorten up the struct and typedef to one statement like so:
typedef struct student {
char *name;
} Student;
Learning C through "Learning C the hard way", and doing some of my own exercises. I stumbled upon the following problem.
Let's say I have the following structure:
struct Person {
char name[MAX_INPUT];
int age;
}
In main(), I have declared the following array:
int main(int argc, char *argv[]) {
struct Person personList[MAX_SIZE];
return 0;
}
Now let's say 2 functions away (main calls function1 which calls function2) I want to save a person inside the array I declared in the main function like so:
int function2(struct Person *list) {
struct Person *prsn = malloc(sizeof(struct Person));
assert(prsn != NULL); // Why is this line necessary?
// User input code goes here ...
// Now to save the Person created
strcpy(prsn->name, nameInput);
ctzn->age = ageInput;
list = prsn; // list was passed by reference by function1, does main need to pass the array by
// reference to function1 before?
// This is where I get lost:
// I want to increment the array's index, so next time this function is called and a
// new person needs to be saved, it is saved in the correct order in the array (next index)
}
So if I return to my main function and wanted to print the first three persons saved in it like so:
...
int i = 0;
for(i = 0; i < 3; i++) {
printf("%s is %d old", personList[i].name, personList[i].age);
}
...
Basically how to reference the array across the application while keeping it persistent. Keeping in mind that main does not necessarily call the function directly that makes use of the array. I'm suspecting someone might suggesting declaring it as a global variable, then what would be the alternative? Double pointers? How do double pointers work?
Thank you for your time.
Here are a few pointers (no pun intended!) to help you along:
As it stands, the line struct Person personList[MAX_SIZE]; allocates memory for MAX_SIZE number of Person structs. You don't actually need to allocate more memory using malloc if this is what you are doing.
However, you could save some memory by only allocating memory when you actually need a person. In this case, you want the personList array to contain pointers to Person structs, not the structs themselves (which you create using malloc).
That is: struct Person * personList[MAX_SIZE];
When you create the person:
struct Person * person = (struct Person *) malloc(sizeof(struct Person));
personList[index] = person;
And when you use the person list: printf("%s", personList[index]->name);
Arrays don't magically keep a record of any special index. You have to do this yourself. One way is to always pass the length of the array to each function that needs it.
void function1(struct Person * personList, int count);
If you wanted to modify the count variable when you returned back to the calling function, you could pass it by reference:
void function1(struct Person * personList, int * count);
A possibly more robust way would be to encapsulate the count and the array together into another structure.
struct PersonList { struct Person * list[MAX_SIZE]; int count; }
This way you can write a set of functions that always deal with the list data coherently -- whenever you add a new person, you always increment the count, and so on.
int addNewPerson(struct PersonList * personList, char * name, int age);
I think that much should be helpful to you. Just leave a comment if you would like something to be explained in more detail.
First of all, malloc does not guarantee to allocate new space from the memory and return it. If it cannot allocate the requested memory, it returns a NULL value. That's why it is necessary to check the pointer.
While you are calling function two, you can pass the address of the next element by using a variable that holds the current count of the array in function1;
function2(&personList[count++]);
then you return the current count from function1 to the main function;
int size=function1(personList);
Greetings again,
I have this problem again on C but now using struct.
Having this structure of student
struct student {
char *name;
int age;
}
I wanted to a list where I could add a number of Student and can also view all of its elements. Here's the code I have done so far.
#include<stdio.h>
#include<stdlib.h>
// struct student ...
void add(student **list, char* name, int age) {
student* temp = (student *)malloc(sizeof(student));
temp->name = name
temp->age = age;
*list = temp;
*(list++) = (student *)malloc(sizeof(student));
}
void view(student **list) {
student* data = *list;
while(data != '\0') { printf("%s%i", data->name, data->age); *(data++); }
}
main() {
student* list = (student *)malloc(sizeof(student));
char* name = (char *)malloc(sizeof(char));
int age=0;
// inputs for name and age
// do-while(option != EXIT_VALUE);
// inside do-while are the following below
add(&list, name, age);
view(&list);
}
I only get the newest student upon the view method.
It makes sense, since you are allocation space for 1 single student structure:
student* list = (student *)malloc(sizeof(student));
You should do something like:
int list_size = 20;
student* list = (student *)malloc(sizeof(student) * list_size);
The name variable suffers from the same problem.
A dynamic linked list should have a reference to the next and previous elements. You'll have to change your program to work with:
struct student {
char *name;
int age;
struct student* next;
struct student* previous;
}
Also you are doing *(data++) which isn't necessary. data++ is just fine. You really shouldn't be needing the double pointers everywhere, it only complicates things. For allocation, fine (if you think that's the best way), but for passing to other functions that only READ the pointer, there's no need.
view expects list to be an array (null-terminated) of pointers to student. However, you allocate it in main as a pointer to a single student. Then, you just reassign that student pointer every time you call add. As Chris said, it would probably be fine (and simpler) just to have a list of students.
If you want to implement the list as an array, you need a strategy to re-allocate the list when it gets larger than the initial array size. A typical strategy is to choose an arbitrary size that will handle most cases without wasting tons of memory, and then double that amount when the bound is reached. So, say you start with an 8-element list. When you add the 9th element, it would reallocate the array to a 16-element list.
The other strategy is to use a linked list, where you add a struct pointer (typically named "next") to your structure and use that to iterate through your list. This makes allocation and traversal a lot simpler, although list retrieval becomes an O(n) operation instead of an O(1) operation, which means it takes longer to get a particular element from the list as the list gets larger.
You have two options.
Create linked list and you will be able to insert as many students you want. (each student has also pointer on next student if any else it is NULL)
Create array of pointers on students as suggested by karlphillip, but method to add new student will need to implement position searching or storing this postion. (you have to figure out where should you store pointer that points on student)
I only get the newest student upon the view method.
void add(student **list, char* name, int age) {
student* temp = (student *)malloc(sizeof(student));
temp->name = name
temp->age = age;
*list = temp;
*(list++) = (student *)malloc(sizeof(student));
}
Notice that you are making list point to the newly allocated memory location. The previous value it was pointing to is lost and also causes memory leak. And so only you are getting the last entry. You need to implement a linked list like structure.
struct student {
char *name;
int age;
struct student *next ;
}
// ....
void add(student **list, char* name, int age) {
// Make sure that every new location is saved in next
// And also the next time when you call this method, it should be
// location of "next" pointing to being passed as parameter.
}
int main()
{
student *list = malloc(sizeof(student)); // No need to type cast malloc
student *preserveHeadNode = list ;
add(&list, name, age);
// .. While viewing pass the "preserveHeadNode" and run the loop until
// student::next == NULL
}