In: Computer Science
Write a program in C language
1- Define a struct for students with the
aforementioned attributes, test it by populating one
initialized struct variable with arbitrary input and take
screenshots of the
output.
2- For each student, struct add an array
of
number grades for a class the students are enrolled in such as S E
185. Then
write functions which find the max, average, and minimum score for
a specified
assignment identified by a number, for example, Assignment 0 will
be found at
index 0 of each student.
Create a new struct called university with the following
attributes: university
name, an array of struct students, built year, and location. Create
some random
students and assign those students to an arbitrary university.
Create three
different universities with different students
3- Create a struct pointer variable for a
student. You can initialize a struct pointer
variable in a similar way to a primitive variable type pointer
initialization. We are
going to do some simple pointer to struct accesses. This part can
be
counterintuitive and confusing so make sure you take your time on
this part. The
difference between accessing structs and pointer structs is the
notation used to
access the inherent attributes of the struct. To access an
attribute of a pointer
struct, we use “->” and we do it as follows: Suppose we have
a pointer struct
called menu that has attributes food and drink. We can access an
attribute like
this: menu -> drink or menu -> food. Your task for
this problem of the lab is to
initialize some pointer structs for students and populate them
using pointer
notation.
We use structures to store data of different types. For example, you are a student. Your name is a string and your phone number and roll_no are integers. So, here name, address and phone number are those different types of data. Here, structure comes in picture.
Defining a Structure
The syntax for structure is:
struct structure_name
{
data-type member-1;
data-type member-2;
data-type member-3;
data-type member-4;
};
In our case, let's name the structure as 'student'. The members of the structure in our case are name, roll_no and phone_number.
So, our structure will look like:
struct student
{
int roll_no;
char name[30];
int phone_number;
};
Just as we declare variables of type int, char etc, we can declare variables of structure as well.
Suppose, we want to store the roll no., name and phone number of three students. For this, we will define a structure of name 'student' (as declared above) and then declare three variables, say 'p1', 'p2' and 'p3' (which will represent the three students respectively) of the structure 'student'.
struct student
{
int roll_no;
char name[30];
int phone_number;
};
main()
{
struct student p1, p2, p3;
}
Here, p1, p2 and p3 are the variables of the structure 'student'.
We can also declare structure variables at the time of defining structure as follows.
struct student
{
int roll_no;
char name[30];
int phone_number;
}p1, p2, p3;
Now, let's see how to enter the details of each student i.e. roll_no, name and phone number.
Suppose, we want to assign a roll number to the first student. For that, we need to access the roll number of the first student. We do this by writing
p1.roll_no = 1;
This means that use dot (.) to use variables in a structure. p1.roll_no can be understood as roll_no of p1.
If we want to assign any string value to a variable, we will use strcpy as follows.
strcpy(p1.name, "Brown");
Now let's store the details of all the three students.
#include <stdio.h> #include <string.h> int main() { struct student { int roll_no; char name[30]; int phone_number; }; struct student p1 = {1,"Brown",123443}; struct student p2, p3; p2.roll_no = 2; strcpy(p2.name,"Sam"); p2.phone_number = 1234567822; p3.roll_no = 3; strcpy(p3.name,"Addy"); p3.phone_number = 1234567844; printf("First Student\n"); printf("roll_no : %d\n", p1.roll_no); printf("name : %s\n", p1.name); printf("phone_number : %d\n", p1.phone_number); printf("Second Student\n"); printf("roll_no : %d\n", p2.roll_no); printf("name : %s\n", p2.name); printf("phone_number : %d\n", p2.phone_number); printf("Third Student\n"); printf("roll_no : %d\n", p3.roll_no); printf("name : %s\n", p3.name); printf("phone_number : %d\n", p3.phone_number); return 0; }
Store Information in Structure and Display it
#include <stdio.h>
struct student {
char firstName[50];
int roll;
float marks;
} s[10];
int main() {
int i;
printf("Enter information of students:\n");
// storing information
for (i = 0; i < 5; ++i) {
s[i].roll = i + 1;
printf("\nFor roll number%d,\n", s[i].roll);
printf("Enter first name: ");
scanf("%s", s[i].firstName);
printf("Enter marks: ");
scanf("%f", &s[i].marks);
}
printf("Displaying Information:\n\n");
// displaying information
for (i = 0; i < 5; ++i) {
printf("\nRoll number: %d\n", i + 1);
printf("First name: ");
puts(s[i].firstName);
printf("Marks: %.1f", s[i].marks);
printf("\n");
}
return 0;
}
Output
Enter information of students: For roll number1, Enter name: Tom Enter marks: 98 For roll number2, Enter name: Jerry Enter marks: 89 . . . Displaying Information: Roll number: 1 Name: Tom Marks: 98 . . .
In this program, a structure student
is created.
The structure has three members: name (string), roll (integer) and
marks (float).
Then, we created an array of structures s having 5 elements to store information of 5 students.
Using a for
loop, the program takes the information
of 5 students from the user and stores it in the array of
structure. Then using another for
loop, the
information entered by the user is displayed on the screen.
Code examples on this page can be copied over from my public/cs31/C_examples directory:
# if you don't have a cs31 subdirectory, create one first:
mkdir cs31
# copy over my C example files into your cs31 subdirectory:
cd cs31
cp -r /home/newhall/public/cs31/C_examples .
# cd into your copy, run make to compile
cd C_examples
make
ls
Structs
C is not an object-oriented language, and thus does not have support for classes. It does, however, have support for defining structured types (like the data part of classes).
A struct is a type used to represent a heterogeneous collection of data; it is a mechanism for treating a set of different types as a single, coherent unit. For example, a student may have a name, age, gpa, and graduation year. A struct type can be defined to store these four different types of data associated with a student.
In general, there are three steps to using structured types in C programs:
Defining a struct type
struct type definitions should appear near the top of a program file, outside of any function definition. There are several different ways to define a struct type but we will use the following:
struct <struct name> {
<field 1 type> <field 1 name>;
<field 2 type> <field 2 name>;
<field 3 type> <field 3 name>;
...
};
Here is an example of defining a new type 'struct studentT' for storing student data:
struct studentT {
char name[64];
int age;
int grad_yr;
float gpa;
};
// with structs, we often use typedef to define a shorter type name
// for the struct; typedef defines an alias for a defined type
// ('studentT' is an alias for 'struct studentT')
typedef struct studentT studentT;
Declaring variables of struct types
Once the type has been defined, you can declare variables of the structured type:
struct studentT student1; // student1 is a struct studentT
studentT student2; // student2 is also a struct studentT
// (we are just using the typedef alias name)
studentT cs31[50]; // an array of studentT structs: each bucket
// stores a studentT struct
Accessing field values
To access field values in a struct, use dot notation:
<variable name>.<field name>
It is important to think very carefully about type when you use structs to ensure you are accessing field values correctly based on their type. Here are some examples:
student1.grad_yr = 2017;
student1.age = 18 + 2;
strcpy(student1.name, "Joseph Schmoe");
student2.grad_yr = student1.grad_yr;
cs31[0].age = student1.age;
cs31[5].gpa = 3.56;
structs are lvalues, meaning that you can use them on the left-hand-side of an assignment statement, and thus, can assign field values like this:
student2 = student1; // student2 field values initialized to the value of
// student1's corresponding field values
cs31[i] = student2;
Question: For each expression below, what is its type? Are any invalid? (here are the answers)
(1) student1
(2) student1.grad_yr
(3) student1.name
(4) student1.name[2]
(5) cs31
(6) cs31[4]
(7) cs31[4].name
(8) cs31[4].name[5]
Passing structs to functions
When structs are passed to functions, they are passed BY VALUE. That means that the function will receive a COPY OF the struct, and that copy is what is manipulated from within the function. All field values in the copy will have the exact same values as the field values of the original struct - but the original struct and the copy occupy different locations in memory. As such, changing a field value within the function will NOT change the corresponding field value in the original struct that was passed in.
If one of the fields in a struct is a statically declared array (like the name field in the studentT struct), the parameter gets a copy of the entire array (every bucket value). This is because the complete statically declared array resides within the struct, and the entire struct is copied over as a unit. You can think of the struct as a chunk of memory (0's and 1's) that is copied over to the parameter without anything being added to it or taken out. So, a function passed student1 CANNOT change any of the contents of the student1 variable (because the function is working with a COPY of student1, and thus the student.name array in the copy starts at a different memory location than the student.name array of the original struct). This may seem odd given how arrays are passed to functions (an array parameter does not get a copy of every array bucket of its argument, instead it REFERS to the same array as the argument array). This seemingly different behavior is actually consistent with the rule that a parameter gets THE VALUE of its argument. It is just that the value of an array argument (the base address of the array) is different than the value of an int, float, struct, ..., argument. For example, here are some expressions and their values:
Argument Expression Expression's Value (Parameter gets this value)
-------------------- --------------------------------------------
student1 {"Joseph Schmoe", 20, 2017, 3.56}
student1.gpa 3.56
cs31 base address of the cs31 array
student1.name base address of the name field array
student1.name[2] 's'
Only when the value passed to a function is an address of a memory location can the function modify the contents of the memory location at that address: a function passed student1 (a struct value) CANNOT change any of the contents of the student1 variable; but a function passed student1.name (the base address of an array) CAN change the contents of the buckets of the name field - because when student1.name is passed in, what is being passed in is the memory location of the array, NOT a copy of the entire array.
Example: Here is an example function call with a stack drawing showing how different types are passed.
lvalues
An lvalue is an expression that can appear on the left hand side of an assignment statement. In C, single variables or array elements are lvalues. The following example illustrates valid and invalid C assignment statements based on lvalue status:
struct studentT student1;
studentT student2;
int x;
char arr[10], ch;
x = 10; // valid C: x is an lvalue
ch = 'm'; // valid C: ch is an lvalue
student1 = student2; // valid C: student1 is an lvalue
arr[3] = ch; // valid C: arr[3] is an lvalue
x + 1 = 8; // invalid C: x+1 is not an lvalue
arr = "hello there"; // invalid C: arr is not an lvalue
arr = student1.name; // invalid C: arr is not an lvalue
student1.name = student2.name; // invalid C: name (an array of char) is not an lvalue
See
struct.c for more examples.
Exercise: implement and test two functions in this
file: printStudent and initStudent.
C pointer variables
A pointer variable stores the address of a memory location that stores a value of the type to which it points ("a level of indirection"). Here is a picture of a pointer variable ptr pointing to a memory storage location that stores value an int value 12:
----- -----
ptr | *-|--------->| 12 |
----- -----
Through a pointer variable (ptr) the value stored in the location it points to (12) can be indirectly be accessed.
Pointer variables are used most often in C programs for:
Rules for using pointer variables
The rules for using pointer variable are similar to regular variables, you just need to think about two types: (1) the type of the pointer variable; and (2) the type stored in the memory address to which it points.
int *ptr; // stores the memory address of an int (ptr "points to" an int) char *cptr; // stores the memory address of a char (cptr "points to" a char)Think about Type
int x; char ch; ptr = &x; // ptr get the address of x, pointer "points to" x ------------ ------ ptr | addr of x|--------->| ?? | x ------------ ------ cptr = &ch; // ptr get the address of ch, pointer "points to" ch cptr = &x; // ERROR! cptr can hold a char address only (it's NOT a pointer to an int)All pointer variable can be set to a special value NULL. NULL is not a valid address but it is useful for testing a pointer variable to see if it points to a valid memory address before we access what it points to:
ptr = NULL; ------ ------ cptr = NULL; ptr | NULL |-----| cptr | NULL |----| ------ ------
int *ptr1, *ptr2, x, y; x = 8; ptr1 = NULL; ptr2 = &x; ------------ ------ ptr2 | addr of x|--------->| 8 | x ------------ ------ *ptr2 = 10; // dereference ptr2: "what ptr2 points to gets 10" ------------ ----- ptr2 | addr of x|--------->| 10 | x ------------ ----- y = *ptr2 + 3; // dereference ptr2: "y gets what ptr2 points to plus 3" ----- ----- ptr1 = ptr2; // ptr1 gets address value stored in ptr2 ------------ ----- ptr2 | addr of x |--------->| 10 | x ------------ ----- /\ ------------ | ptr1 | addr of x |-------------- ------------ // TODO: finish tracing through this code and show what is printed *ptr1 = 100; ptr1 = &y; *ptr1 = 80; printf("x = %d y = %d\n", x, y); printf("x = %d y = %d\n", *ptr2, *ptr1);
Be careful about type when using pointer variables (drawing pictures helps):
ptr = 20; // ERROR? this assigns ptr to point to address 20
*ptr = 20; // this assigns 20 the value pointed to by ptr
What happens if you dereference an pointer variable that does not contain a valid address:
ptr = NULL;
*ptr = 6; // CRASH! your program crashes with a segfault (a memory fault)
ptr = 20;
*ptr = 6; // CRASH! segfault (20 is not a valid address)
This is one reason to initialize pointer variables to NULL: you can test for NULL and not dereference in your program:
if(ptr != NULL) {
*ptr = 6;
}
Pointers and Functions "pass by reference"
Pointers allow a way to write functions that can modify their arguments' values: the C way of implementing Pass by Reference. We have actually already seen this with array parameters: the function parameter gets the value of the base address of the array (it points to the same array as its argument) and thus the function can modify the values stored in the array buckets. We can use this same idea in general to write a function that can modify its argument's value. The steps are:
int change_value(int *input>) {
int x; change_value(&x);
*input = 100; // what input points to (x's location) gets 100
Try out:
vim passbyreference.cTry running it and see if you understand what is happening and why.
Draw the call stack for the first call to this function:
Here are the answers
(note: technically, everything in C is passed by value; C-style pass-by-reference is just passing the value of an address (a pointer) to a function as opposed to passing the value of an int or float or ...)
Dynamic Memory Allocation
A common uses of pointer variables is to use them to point to memory that your program allocates at runtime. This is very useful for writing programs where the size of an array or other data structure is not know until runtime, or that may grow or shrink over the lifetime of a run of a program.
malloc and free
malloc and free are functions for allocating and deallocating memory in the Heap. The Heap is a portion of program memory that is separate from the stack. No variables are allocated in the heap, but chunks of anonymous memory can be allocated and its address can be assigned to a global or local pointer variable.
Heap memory must be explicitly allocated and deallocated by your program.
int *p; p = (int *)malloc(4); // allocate 4 bytes of heap memory and assign addr to p *p = 6; // the heap memory p points to gets the value 6malloc's return type is a bit odd. It is a void * which means it is a pointer to a non-specific type (or to any type). Because of this, we re-cast the return type to be a pointer to the specific type we are using (int *) in the example above.
p = (int *)malloc(4); if(p == NULL) { printf("Bad malloc error\n"); exit(1); // or return from this function or ... } *p = 6;
p = (int *)malloc(sizeof(int));
int *arr; char *c_arr; // allocate an array of 20 ints on the heap: arr = (int *)malloc(sizeof(int)*20); // allocate an array of 10 chars on the heap: c_arr = (char *)malloc(sizeof(char)*10);
arr[0] = 8; // these two statements are identical: both put 8 in bucket 0 *arr = 8; arr[3] = 10; // puts 10 in bucket 3 of the array pointed to by arrHere is a picture of what this looks like in memory. Note that the Stack and the Heap are separate parts of memory.
free(p); free(arr); free(c_arr); p = NULL; arr = NULL; c_arr = NULL;
See my Strings in C page for some examples of dynamically allocated strings and the string library (when dynamically allocating space for strings it is important to allocate enough space to store all chars in the string including the terminating null char)
Pointers, the Heap, and Functions
Passing a pointer value to a function has the same semantics as passing the address of a variable to a function: the function can modify the value pointed to.
As you write functions that take pointer values as parameters, it if very important to think about the type of argument you are passing. This will help you figure out the syntax for how to pass the argument value and the correct matching function parameter type.
Here is an example of passing a malloc'ed array to a function:
int main() {
int *arr1;
arr1 = malloc(sizeof(int)*10);
if(!arr1) {
printf("malloc error\n");
exit(1);
}
init_array(arr1, 10);
...
}
void init_array(int *arr, int size) {
int i;
for(i=0; i< size; i++) {
arr[i] = i;
}
}
Here is a picture of what this looks like in memory. This should look very familiar to passing statically declared arrays, just notice the difference in which part of memory the array is located.
Try out: