Sololearn C
Sololearn C
Basic Concepts
C has been used to write operating systems (like windows), complex programs like the Python
interpreter, Git, Oracle database, and more. C is designed to be versatile, it relates closely to the
way machines work while still being easy to learn. Understanding how computer memory works is
an important aspect of C.
#include <stdio.h>
int main() {
printf("Hello, World!\n");
return 0;
}
This program prints Hello, World! To the console.
#include <stdio.h> the function used for generating output is defined in stdio.h. In order to
use the printf function we need to include the required file, also called a header file.
Int main() is the main function, entry point to a program. Uses curly brackets to form a code
block.
Printf() function is used to generate output. C uses semicolons to indicate end of statements.
return 0; terminates the main() function and returns the value 0 to the calling process. The
number 0 generally means that the program was successfully executed. Any other number
indicates failure.
C supports:
int: integer, whole number
float: floating point number
double: double-precision floating point value
char: single character.
The amount of storage required for each of these types varies by platform.
int: 4 bytes
float: 4 bytes
double: 8 bytes
char: 1 byte
The built-in sizeof operator gives the memory requirements for a particular data type:
#include <stdio.h>
int main() {
printf("int: %d \n", sizeof(int)); /* in bytes */
printf("float: %d \n", sizeof(float));
printf("double: %d \n", sizeof(double));
printf("char: %d \n", sizeof(char));
return 0;
}
The printf statements in this program have two arguments, first is the output string with a format
specifier (%d), while the next argument returns the sizeof value. %d is for decimal numbers.
C does not have a Boolean type. A printf statement can have multiple format specifiers with
corresponding arguments to replace the specifiers. They are also referred to as conversation
specifiers.
The name of a variable must begin with either a letter or an underscore and can be composed of
letters, digits and the underscore character. Using lowercase letters with an underscore to
separate words is a common convention.
int my_var;
my_var = 42;
//or:
int my_var = 42;
Defining and outputting variables:
#include <stdio.h>
int main() {
int a, b;
float salary = 56.23;
char letter = 'Z';
a = 8;
b = 34;
int c = a+b;
return 0;
}
Declare multiple variables on a single line using a comma. %f is for float, %c is for char.
C is case sensitive.
Constants are declared using the const keyword and as convention use uppercase letters for
names. They must be initialized when declared.
#include <stdio.h>
int main() {
const double PI = 3.14;
printf("%f", PI);
return 0;
}
Another way to define a constant is with the #define pre-processor directive. The #define directive
uses macros for defining constant values.
#include <stdio.h>
#define PI 3.14
int main() {
printf("%f", PI);
return 0;
}
Before compilation, the pre-processor replaces every macro identifier in the code with its
corresponding value from the directive. In this case every occurrence of PI is replaced with 3.14.
Const uses memory for storage but #define does not. However with const you can control its
scope, like making it private or protected, whereas you cannot do the same with #define.
int main() {
char a = getchar();
return 0;
}
gets() function is used to read input as an ordered sequence of characters, a string. A string is
stored in a char array.
#include <stdio.h>
int main() {
char a[100];
gets(a);
return 0;
}
Here the input is stored in an array of 100 characters.
scanf() scans input that matches format specifiers
#include <stdio.h>
int main() {
int a;
scanf("%d", &a);
return 0;
}
The & sign before the variable name is the address operator. It gives the address, or location in
memory, of a variable. Scanf places an input value at a variable address.
#include <stdio.h>
int main() {
int a, b;
printf("Enter two numbers:");
scanf("%d %d", &a, &b);
return 0;
}
Prompting for two integer inputs and outputting the sum.
Scanf() stops reading as soon as it encounters a space, so “Hello world” is two separate inputs for
scanf().
putchar() outputs a single character.
#include <stdio.h>
int main() {
char a = getchar();
return 0;
}
puts() function is used to display output as a string, which is stored in a char array.
#include <stdio.h>
int main() {
char a[100];
gets(a);
return 0;
}
printf() outputs in a formatted way
#include <stdio.h>
int main() {
int a;
scanf("%d", &a);
return 0;
}
& sign is the address operator, gives the address in memory of a variable.
scanf() function is used to assign input to variables. A call scans input according to format
specifiers that convert input as necessary. If input can’t be converted, then the assignment isn’t
made. scanf() waits for input and then makes assignments:
int x;
float num;
char text[20];
scanf("%d %f %s", &x, &num, text);
// Typing 10 22.5 abcd and then pressing Enter assigns 10 to x, 22.5 to
num, and abcd to text.
& must be used to access the variable addresses. The & isn’t needed for a string because a string
name acts as a pointer.
Format specifiers begin with % and are used to assign values to corresponding arguments after
the control string. Blanks, tabs and newlines are ignored. A format specifier can include several
options along with a conversion character:
%[*][max_width]conversion character
The optional * will skip the input field. The optional max_width gives the maximum number of
characters to assign to an input field.
The conversion character converts the argument, if necessary to the indicated type:
d decimal, c character, s string, f float, x hexadecimal.
int x, y;
char text[20];
Comments:
starts with /* and ends with */
#include <stdio.h>
/* A simple C program
* Version 1.0
*/
int main() {
/* Output a string */
printf("Hello World!");
return 0;
}
C++ introduced double slash comment // as a way to comment single lines. Some C compilers
also support this comment style.
#include <stdio.h>
int main() {
int x = 42; //int for a whole number
//%d is replaced by x
printf("%d", x);
return 0;
}
int main() {
int length = 10;
int width = 5;
int area;
return 0;
}
The division operators performs differently depending on the data types of the operands. When
both operands are integers, truncated division happens and any remainder is removed. If one or
both are float or double type (real number) the result is a real number.
% returns only the remainder of integer division, not float or double.
#include <stdio.h>
int main() {
int i1 = 10;
int i2 = 3;
int quotient, remainder;
float f1 = 4.2;
float f2 = 2.5;
float result;
quotient = i1 / i2; // 3
remainder = i1 % i2; // 1
result = f1 / f2; // 1.68
return 0;
}
int main() {
int a = 6;
int b = 4;
int c = 2;
int result;
result = a - b + c; // 4
printf("%d \n", result);
result = a + b / c; // 8
printf("%d \n", result);
result = (a + b) / c; // 5
printf("%d \n", result);
return 0;
}
C may not evaluate a numeric expression as desired when the associative property allows any
order. For example, x*y*z may be evaluated as (x * y) * z or as x * (y * z). If order is important,
break the expression into separate statements.
When a numeric expression contains operands of different data types, they are automatically
converted as necessary in a process called type conversion.
#include <stdio.h>
int main() {
float price = 6.50;
int increase = 2;
float new_price;
return 0;
}
4.2 indicates the float is to be printed in a space at least 4 characters wide with 2 decimal places.
Type casting is done with curly brackets ().
float average;
int total = 23;
int count = 4;
int main() {
int score = 89;
return 0;
}
If there are multiple statements you use curly braces to contain the code block.
There are six relational operators used to form a Boolean expression: <, <=, >, >=, ==, !=.
int num = 41;
num += 1;
if (num == 42) {
printf("You won!");
}
A value that evaluates to a non-zero value is considered true.
int in_stock = 20;
if (in_stock)
printf("Order received.\n");
The if statement can include an optional else clause:
#include <stdio.h>
int main() {
int score = 89;
return 0;
}
You can use the ?: operator to form a ternary operator (short if else statement):
#include <stdio.h>
int main() {
int y;
int x = 3;
y = (x >= 5) ? 5 : x;
return 0;
}
The switch statement matches the result of an expression with a constant case value:
switch (expression) {
case val1:
statements
break;
case val2:
statements
break;
default:
statements
}
example:
int num = 3;
switch (num) {
case 1:
printf("One\n");
break;
case 2:
printf("Two\n");
break;
case 3:
printf("Three\n");
break;
default:
printf("Not 1, 2, or 3.\n");
}
There can be multiple cases with unique labels. The optional default case is executed when no
case matches. A break statement is needed to branch to the end of the switch statement.
Otherwise the program will check the next case statement.
Logical operators && and || are used to form a compound Boolean expression that tests multiple
conditions. ! is used to reverse the state of a Boolean expression.
&& AND operator:
if (n > 0 && n <= 100)
printf("Range (1 - 100).\n");
Logical operators can be used to join multiple expressions. A compound Boolean expression is
evaluated from left to right. Evaluation stops when no further test is needed for determining the
result, so be sure to consider the arrangement of operands.
|| OR operator:
if (n == 'x' || n == 'X')
printf("Roman numeral value 10.\n");
Any number of expressions can be joined:
if (n == 999 || (n > 0 && n <= 100))
printf("Input valid.\n");
Parentheses are used for clarity even though && has higher precedence than || and will be
evaluated first.
! NOT operator:
if (!(n == 'x' || n == 'X'))
printf("Roman numeral is not 10.\n");
reverses the value.
In C any non-zero value is considered true and 0 is false. Therefore it converts a true value to 0
and a false value to 1.
While statement:
while (expression) {
statements
}
Example:
#include <stdio.h>
int main() {
int count = 1;
return 0;
}
Be careful of infinite loops.
Do-While loop:
do {
statements
} while (expression);
Example:
#include <stdio.h>
int main() {
int count = 1;
do {
printf("Count = %d\n", count);
count++;
} while (count < 8);
return 0;
}
Notice the semicolon after the while statement. It always executes at least once.
printf("%d\n", num);
}
The break and continue statements should not substitute for a better algorithm.
For statement:
for (initvalue; condition; increment) {
statements;
}
Example:
int i;
int max = 10;
x = 5;
result = square(x);
printf("%d squared is %d\n", x, result);
return 0;
}
In order to use the square function, we need to declare it. This takes the form:
return_type function_name(parameters);
A return type of void can be used if no value is returned.
When the parameter types and names are included in a declaration, the declaration is called a
function prototype.
#include <stdio.h>
/* declaration */
int square (int num);
int main() {
int x, result;
x = 5;
result = square(x);
printf("%d squared is %d\n", x, result);
return 0;
}
The last step is defining the function. Function definitions appear after main() function.
#include <stdio.h>
/* declaration */
int square (int num);
int main() {
int x, result;
x = 5;
result = square(x);
printf("%d squared is %d\n", x, result);
return 0;
}
/* definition */
int square (int num) {
int y;
y = num * num;
return(y);
}
Values passed to a functions parameters are called arguments. By default arguments are passed
by value.
#include <stdio.h>
int main() {
int x, y, result;
x = 3;
y = 12;
result = sum_up(x, y);
printf("%d + %d\ = %d\n", x, y, result);
return 0;
}
Variable scope refers to the visibility of variables within a program. Variables declared in a function
are local to that block of code and cannot be referred to outside the function. Variables declared
outside all functions are global to the entire program. Constants declared with #define are global.
Program that uses both local and global variables:
#include <stdio.h>
int global1 = 0;
int main() {
int local1, local2;
local1 = 5;
local2 = 10;
global1 = local1 + local2;
printf("%d \n", global1); /* 15 */
return 0;
}
Parameters of a function act as local variables. When exiting a function parameters and local
variables are destroyed. Use global variables with caution.
Static variables have a local scope but are not destroyed when a function is exited. A static
variable retains its value for the life of the program and can be accessed every time the function is
re-entered. It is initialized when declared and requires the prefix static.
#include <stdio.h>
void say_hello();
int main() {
int i;
return 0;
}
void say_hello() {
static int num_calls = 1;
//function declaration
int factorial(int num);
int main() {
int x = 5;
return 0;
}
//function definition
int factorial(int num) {
An array is a data structure that stores a collection of related values that are all the same type.
They are useful because they can represent related data with one descriptive name rather than
using several separate variables.
int test_scores[25]; /* An array size 25 */
float prices[3] = {3.2, 6.55, 10.49}; /* initialize when declared */
Missing values are set to 0.
An array is stored in contiguous (in sequence) memory locations and cannot change size after
being declared.
An element of an array can be accessed through its index number:
int x[5] = {20, 45, 16, 18, 22};
printf("The second element is %d\n", x[1]); /* 45 */
In C index numbers start at 0.
Changing value of element:
int x[5] = {20, 45, 16, 18, 22};
x[1] = 260;
printf("The second element is %d\n", x[1]); /* 260 */
The index of an array is also referred to as the subscript.
You can traverse the array using a loop, such as the for loop as its loop control variable
corresponds to the arrays indexes.
float purchases[3] = {10.99, 14.25, 90.50};
float total = 0;
int k;
C is designed to be a low-level language that can easily access memory locations and perform
memory-related operations. The scanf() function places the value entered by the user at the
location, or address, of the variable. Accomplished using the & symbol:
int num;
printf("Enter a number: ");
scanf("%d", &num);
printf("%d", num);
&num is the address of variable num.
Each memory address is given as a hexadecimal number. Hex is a base-16 number that uses 0-9
and letters A through F (16 characters) to represent a group of four binary digits that can have the
value from 0 to 15. It’s much easier to read a hex number that is 8 characters long for 32 bits of
memory. Program to display memory addresses:
void test(int k);
int main() {
int i = 0;
return 0;
}
void test(int k) {
printf("The address of k is %x\n", &k);
}
Uses %x because it is in hex. Output may vary run to run.
The address of a variable stays the same from the time it is declared until the end of its scope.
Pointers are very important in C as they allow you to work with memory locations. They are
fundamental to data structures and algorithms. A pointer is a variable that contains the address of
another variable. Pointers are declared using the * symbol:
pointer_type *identifier
The actual pointer data type is a hexadecimal number, but when declaring a pointer, you must
indicate what type of data it will be pointing to.
int j = 63;
int *p = NULL;
p = &j;
y = *p + 2; /* y is assigned 7 */
y += *p; /* y is assigned 12 */
*p = y; /* x is assigned 12 */
(*p)++; /* x is incremented to 13 */
With pointers we can point to the first element in an array and then use address arithmetic to
traverse the array: + to move forward, - to move back.
int a[5] = {22, 33, 44, 55, 66};
int *ptr = NULL;
int i;
ptr = a;
for (i = 0; i < 5; i++) {
printf("%d ", *(ptr + i));
}
The array name acts as a pointer to the first element of the array. Therefore ptr = a is the same
as ptr = &a[0].
Using assignment operators to change the address the pointer contains:
int a[5] = {22, 33, 44, 55, 66};
int *ptr = NULL;
With pointer parameters, your functions can alter actual data rather than a copy of data. To
change the actual values of variables, the calling statement passes addresses to pointer
parameters in a function. For example the following swaps two variables:
void swap (int *num1, int *num2);
int main() {
int x = 25;
int y = 100;
return 0;
}
temp = *num1;
*num1 = *num2;
*num2 = temp;
}
An array cannot be passed by value to a function. However an array name is a pointer, so just
passing an array name to a function is passing a pointe to the array.
int add_up (int *a, int num_elements);
int main() {
int orders[5] = {100, 220, 37, 16, 98};
return 0;
}
return (total);
}
int main() {
int *a;
int k;
return 0;
}
int * get_evens() {
static int nums[5];
int k;
int even = 0;
return (nums);
}
A pointer is declared to store the value returned by the function. When a local variable is being
passed out of a function, you need to declare it as static in the function.
A string in C is an array of characters that ends with a NULL character ‘\0’. It can be declared in
several ways:
char str_name[str_len] = "string";
When you provide a string literal to initialize the string, the compiler automatically adds a NULL
character ‘\0’ to the char array. For this reason you must declare the array size to be at least one
character longer than the expected string length. If the declaration does not include a char array
size, then it will be calculated based on the length of the string in the initialization plus one for ‘\0’:
char str1[6] = "hello";
char str2[ ] = "world"; /* size 6 */
A string can also be declared as a set of characters:
char str3[6] = {'h', 'e', 'l', 'l', 'o', '\0'};
fputs(city, stdout);
printf(" is a fun city.");
return 0;
}
puts() takes only a single argument and can be used to display output but adds a newline to the
end of output:
#include <stdio.h>
int main()
{
char city[40];
printf("Enter your favorite city: ");
gets(city);
// Note: for safety, use
// fgets(city, 40, stdin);
puts(city);
return 0;
}
Use sprintf() to create a formatted string. Used to build a string from other data types:
#include <stdio.h>
int main()
{
char info[100];
char dept[ ] = "HR";
int emp = 75;
sprintf(info, "The %s dept has %d employees.", dept, emp);
printf("%s\n", info);
return 0;
}
sscanf() is used to scan a string for values. It reads values from a string and stores them at the
corresponding variable addresses.
#include <stdio.h>
int main()
{
char info[ ] = "Snoqualmie WA 13190";
char city[50];
char state[50];
int population;
sscanf(info, "%s %s %d", city, state, &population);
printf("%d people live in %s, %s.", population, city, state);
return 0;
}
The string.h library contains numerous string functions.
#include <string.h> gives you access to these:
strlen(str) returns length of string not including NULL character ‘\0’
strcat(str1, str2) appends str2 to end of str1 and returns a pointer to str1.
strcpy(str1, str2) copies str2 to str1. Useful for assigning a string a new value.
#include <stdio.h>
#include <string.h>
int main()
{
char s1[ ] = "The grey fox";
char s2[ ] = " jumped.";
strcat(s1, s2);
printf("%s\n", s1);
printf("Length of s1 is %d\n", strlen(s1));
strcpy(s1, s2);
printf("s1 is now %s \n", s1);
return 0;
}
Additional functions:
strncat(str1, str2, n) Appends first n characters to str2 to the end of str1 and returns a pointer to
str1.
strncpy(str1, str2, n) Copies fist n characters of str2 to str1.
strcmp(str1, str2) Returns 0 when str1 is equal to str2, less than 0 when str1<str2 and greater
when str1>str2.
strncmp(str1, str2, n) Returns 0 when first n characters of str1 is equal to first n characters of str2,
less than 0 when str1<str2 and greater when str1>str2.
strchr(str1, c) Returns a pointer to the first occurrence of char c in str1, or NULL if character not
found.
strrchr(str1, c) Searches str1 in reverse and returns a pointer to the position of char c in str1, or
NULL if not found.
strstr(str1, str2) Returns a pointer to the first occurance of str2 in str1, or NULL if str2 not found.
The stdio.h library contains the following funcitons for converting a string to a number:
int atoi(str) Stands for ASCII to integer. 0 is returned if the first character is not a number or no
numbers are encountered.
double atof(str) Stands for ASCII to float. 0.0 is returned if the first character is not a number or no
numbers are encountered.
long int atoll(str) Stands for ASCII to long int. 0 returned if the first character is not a number or no
numbers are encountered.
#include <stdio.h>
int main()
{
char input[10];
int num;
return 0;
}
atoi() lack error handling, use strtol() if you want error handling
long int strtol(str, endptr, base) returns the converted integral number as a long int value, else zero
is returned. str is the string,
endptr is reference to an object of type char, whose value is set by the function to the next
character in str after the numerical value.
base must be between 2 and 36 inclusive or be special value 0.
#include <stdio.h>
#include <stdlib.h>
int main () {
char str[30] = "2030300 This is test";
char *ptr;
long ret;
return(0);
}
A two-dimensional array can be used to store related strings:
char trip[3][15] = {
"suitcase",
"passport",
"ticket"
};
The string length must be large enough to hold the longest string and it can be very cumbersome
to access the elements.
An easier, more intuitive way to deal with a collection of related strings is with an array of pointers:
char *trip[ ] = {
"suitcase",
"passport",
"ticket"
};
printf("Please bring the following:\n");
for (int i = 0; i < 3; i++) {
printf("%s\n", trip[ i ]);
}
The array of string pointers has a more ragged (uneven) structure and there is no limit to the string
length. Items can be referred to by a pointer to the first character of each string.
A declaration like char *items[3]; only reserves space for three pointers; the actual strings are
being referenced by those pointers.
Function pointers point to executable code for a function in memory. They can be stored in an
array or passed as arguments to other functions. It uses * as you would with any pointer:
return_type (*func_name)(parameters)
The parentheses around (*func_name) are important. Without them, the compiler will think the
function is returning a pointer.
After declaring the function pointer, you must assign it to a function.
#include <stdio.h>
void say_hello(int num_times); /* declare function */
int main() {
void (*funptr)(int); /* declare function pointer */
funptr = say_hello; /* assign function pointer to function */
funptr(3); /* call function using pointer */
return 0;
}
An array of function pointers can replace a switch or an if statement for choosing an action:
#include <stdio.h>
return 0;
}
A void pointer is used to refer to any address type in memory and has a declaration that looks like:
void *ptr;
Following program uses the same pointer for three different data types:
int x = 33;
float y = 12.4;
char c = 'a';
void *ptr;
ptr = &x;
printf("void ptr points to %d\n", *((int *)ptr));
ptr = &y;
printf("void ptr points to %f\n", *((float *)ptr));
ptr = &c;
printf("void ptr points to %c", *((char *)ptr));
When dereferencing a void pointer, you must first type cast the pointer to the appropriate data type
before dereferencing with * like:
#include<stdio.h>
int main()
{
int a = 10;
void *ptr = &a;
printf("%d", *(int *)ptr); /* notice cast */
return 0;
}
You cannot perform pointer arithmetic.
Void pointers are often used for function declarations, like:
void * square (const void *);
This return type allows for any return type. Similarly, parameters that are void* accept any
argument type. If you want to use the parameter without changing it declare it const.
You can leave out the parameter name to further insulate the declaration from its implementation.
This allows a function to be customized as needed without changing the declaration. Consider:
#include <stdio.h>
int main() {
int x, sq_int;
x = 6;
sq_int = square(&x);
printf("%d squared is %d\n", x, sq_int);
return 0;
}
int main() {
int arr[5] = {52, 23, 56, 19, 4};
int num, width, i;
num = sizeof(arr)/sizeof(arr[0]);
width = sizeof(arr[0]);
qsort((void *)arr, num, width, compare); /* function name compare */
for (i = 0; i < 5; i++) /* acts as a pointer */
printf("%d ", arr[ i ]);
return 0;
}
A structure is a user-defined data type that groups related variables of different data types.
Structure declaration:
struct course {
int id;
char title[40];
float hours;
};
It includes the keyword struct, a structure tag for referencing and curly braces {} with a list of
variable declarations called members.
The example defines a new data type called course that has three members. They can be of any
data type even other structures. Do not forget the semicolon after the structure declaration as that
is where you may put instances like:
/* class NAME { constituents } instances ; */
class FOO {
int bar;
int baz;
} waldo;
You have to put the semicolon there so the compiler will know whether you declared any instances
or not.
https://stackoverflow.com/questions/1783465/why-must-i-put-a-semicolon-at-the-end-of-class-declaration-in-c
A structure is also called a composite or aggregate data type. Some languages refer to structures
as records.
Declaring variables of a structure:
struct student {
int age;
int grade;
char name[40];
};
You can access the members of a struct variable by using the .(dot operator) between the variable
name and the member name.
s1.age = 19;
You can also assign one structure to another of the same type.
struct student s1 = {19, 9, "Jason"};
struct student s2;
//....
s2 = s1;
Demonstrating using a structure:
#include <stdio.h>
#include <string.h>
struct course {
int id;
char title[40];
float hours;
};
int main() {
struct course cs1 = {341279, "Intro to C++", 12.5};
struct course cs2;
/* initialize cs2 */
cs2.id = 341281;
strcpy(cs2.title, "Advanced C++");
cs2.hours = 14.25;
return 0;
}
String assignment requires strcpy() from the string.h library.
Format specifiers %4.2f include width and precision options.
The typedef keyword creates a type definition that simplifies code and makes programs easier to
read. It is commonly used with structures because it eliminates the need to use the keyword struct
when declaring variables.
typedef struct {
int id;
char title[40];
float hours;
} course;
course cs1;
course cs2;
typedef struct {
float radius;
point center; /* point is a struct */
} circle;
Nested curly braces are used to initialize members that are structs. The dot operator is used to
access members of members.
circle c = {4.5, {1, 3}};
printf("%3.1f %d,%d", c.radius, c.center.x, c.center.y);
/* 4.5 1,3 */
A struct definition must appear before it can be used inside another struct.
Pointers to structures can also be defined
struct myStruct *struct_ptr;
/* defines a pointer to the myStruct structure. */
struct_ptr = &struct_var;
/* stores the address of the structure variable struct_var in the
pointer struct_ptr. */
A function can have structure parameters that accept arguments by value when a copy of the
structure variable is all that is needed. For a function to change the actual values in a struct
variable, pointer parameters are required.
#include <stdio.h>
#include <string.h>
typedef struct {
int id;
char title[40];
float hours;
} course;
int main() {
course cs2;
update_course(&cs2);
display_course(cs2);
return 0;
}
You can have an array of structure. Each element is accessible with the index number, the dot
operator is then used to access members of the element:
#include <stdio.h>
typedef struct {
int h;
int w;
int l;
} box;
int main() {
box boxes[3] = {{2, 6, 8}, {4, 6, 6}, {2, 6, 9}};
int k, volume;
A union allows to store different data types in the same memory location.
It is like a structure because it has members. A union variable uses the same memory location for
all its member’s and only one member at a time can occupy the memory location.
Union members can be of any data type:
union val {
int int_num;
float fl_num;
char str[20];
};
note end semicolon
After declaring a union you can declare its variables. You can also assign one union to another of
the same type:
union val u1;
union val u2;
u2 = u1;
Unions are used for memory management. The largest data type is used to determine the size of
the memory to share and all members use this one location. This process also helps limit memory
fragmentation.
You can access union members using the dot operator. When assignment is performed, the union
memory location will be used for that member until another member assignment is performed.
Trying to access a member that isn’t occupying the memory location gives unexpected results
union val {
int int_num;
float fl_num;
char str[20];
};
printf("%d\n", test.int_num);
printf("%f\n", test.fl_num);
printf("%s\n", test.str);
The last assignment overrides previous assignments, so str stores a value and accessing int_num
and fl_num is meaningless.
Unions are often used within structures because a structure can have a member to keep track of
which union member stores a value.
In this program a vehicle struct can use either a vehicle identification number (VIN) or an assigned
id, but not both:
typedef struct {
char make[20];
int model_year;
int id_type; /* 0 for id_num, 1 for VIN */
union {
int id_num;
char VIN[20];
} id;
} vehicle;
vehicle car1;
strcpy(car1.make, "Ford");
car1.model_year = 2017;
car1.id_type = 0;
car1.id.id_num = 123098;
When a union is declared inside a structure, a union name is required at the end of the
declaration. The dot operator is used twice to access union members of struct members.
/* display vehicle data */
printf("Make: %s\n", car1.make);
printf("Model Year: %d\n", car1.model_year);
if (car1.id_type == 0)
printf("ID: %d\n", car1.id.id_num);
else
printf("ID: %s\n", car1.id.VIN);
A union may also contain a structure.
A pointer to a union points to the memory location allocated to the union. Declared using keyword
union and the union tag along with * and the pointer name.
union val {
int int_num;
float fl_num;
char str[20];
};
An array is a data structure that stores collection values that are all the same type. Arrays of
unions allow storing values of different types.
union type {
int i_val;
float f_val;
char ch_val;
};
union type arr[3];
arr[0].i_val = 42;
arr[1].f_val = 3.14;
arr[2].ch_val = 'x';
Memory Management
When you declare a variable using a basic data type, C automatically allocates space for the
variable in an area of memory called the stack. We can find out the number of bytes used to store
each type of variable with the sizeof operator:
int x;
printf("%d", sizeof(x)); /* output: 4 */
An array is allocated contiguous blocks of memory with each block the size for one element:
int arr[10];
printf("%d", sizeof(arr)); /* output: 40 */
Dynamic memory allocation is the process of allocating and freeing memory as needed. You could
for instance prompt at runtime for the number of array elements and then create an array with that
many elements. Dynamic memory is managed with pointers that point to newly allocated blocks of
memory in an area called the heap.
There is also statically managed data in main memory for variables that persist for the lifetime of
the program.
The stdlib.h library includes memory management functions. #include <stdlib.h> gives you access
to the following:
malloc(bytes) Returns a pointer to a contiguous block of memory that is of size bytes
calloc(num_items, item_size) Returns a pointer to a contiguous block of memory that has
num_items items, each of a certan size. Typically used for arrays, structures, and other derived
types. The memory is initialized to 0.
realloc(ptr, bytes) Resizes the memory pointed to by ptr to size bytes. The newly allocated
memory is not initialized.
free(ptr) Releases the block of memory, useful when you no longer need a block of allocated
memory.
int *ptr;
/* a block of 10 ints */
ptr = malloc(10 * sizeof(*ptr));
if (ptr != NULL) {
*(ptr + 2) = 50; /* assign 50 to third int */
}
malloc returns a pointer to the allocated memory. sizeof was applied to *ptr instead of int, making
the code more robust should the *ptr declaration be changed to a different data type later.
The allocated memory is contiguous and can be treated as an array. Pointer arithmetic is used to
traverse the array. You are advised to use + to refer to array elements. Using ++ or += changes
the address stored by the pointer. If the allocation is unsuccessful, NULL is returned. You should
always check for a NULL pointer.
A simple two-dimensional array requires (rows*columns)*sizeof(datatype) bytes of memory.
free(ptr);
calloc() function allocates memory based on the size of a specific item, such as a strcture.
typedef struct {
int num;
char *info;
} record;
record *recs;
int num_recs = 2;
int k;
char str[ ] = "This is information";
When allocating memory for a string pointer, you may want to use string length rather than the
sizeof operator for calculating bytes.
char str20[20];
char *str = NULL;
strcpy(str20, "12345");
str = malloc(strlen(str20) + 1);
strcpy(str, str20);
printf("%s", str);
This approach is better memory management because you aren’t allocating more space than is
needed to a pointer. When using strlen to determine the number of bytes needed for a string, be
sure to include one extra byte for the NULL character ‘\0’. A char is always one byte.
A dynamic array can grow as needed. As elements are not allocated all at once, dynamic arrays
typically use a structure to keep track of current array size, current capacity and a pointer to the
elements, as in:
typedef struct {
int *elements;
int size;
int cap;
} dyn_array;
dyn_array arr;
/* initialize array */
arr.size = 0;
arr.elements = calloc(1, sizeof(*arr.elements) );
arr.cap = 1; /* room for 1 element */
To expand by more elements:
arr.elements = realloc(arr.elements, (5 + arr.cap) *
sizeof(*arr.elements));
if (arr.elements != NULL)
arr.cap += 5; /* increase capacity */
Adding an element to the array increases its size:
if (arr.size < arr.cap) {
arr.elements[arr.size] = 50;
arr.size++;
} else {
printf("Need to expand the array.");
}
To properly implement a dynamic array, sub-tasks should be broken down into functions such as
init_array(), increase_array(), add_element(), and display_array()+error checking.
C includes the FILE type for defining a file stream. The file stream keeps track of where reading
and writing last occurred. The stdio.h library includes file handling functions: FILE Typedef for
defining a file pointer.
fopen(filename, mode) returns a FILE pointer to file filename which is opened using mode. If a file
cannot be opened, NULL is returned. Mode options are: r open for reading, w open for writing (file
need not exist), a open for append (file need not exist), r+ open for reading and writing from
beginning, w+ open for reading and writing, overwriting file, a+ open for reading and writing,
appending to file.
fclose(fp) Closes file opened with FILE fp, returning 0 if close was successful. EOF (end of file) is
returned if there is an error in closing.
Program opens a file for writing and then closes it:
#include <stdio.h>
int main() {
FILE *fptr; // declare a pointer named fptr of type FILE
A file can be read one character at a time or an entire string can be read into a character buffer,
which is typically a char array used for temporary storage.
fgetc(fp) returns the next character from the file pointed to by fp. If the end of the file has been
reached, then EOF is returned.
fgets(buff, n, fp) Reads n-1 characters from the file pointed to by fp and stores the string in buff. A
NULL character ‘\0’ is appened as the last character in buff. If fgets encounters a newline
character or the end of file before n-1 characters is reached, then only the characters up to that
point are stored in buffer.
fscanf(fp, conversion_specifiers, vars) Reads characters from the file pointed to by fp and assigns
input to a list of variable pointers vars using conversion_specifiers. As with scanf, fscanf stops
reading a string when a space or newline is encountered
int main() {
FILE *fptr; // FILE pointer
int c, stock; // initialise variables
char buffer[200], item[10];
float price;
/* myfile.txt: Inventory\n100 Widget 0.29\nEnd of List */
fclose(fptr);
return 0;
}
gets() reads up until newline. fscanf() reads data according to conversion specifiers.
The while loop reads one character at a time until end of file.
When writing to a file, newline characters ‘\n’ must be explicitly added.
fputc(char, fp) Writes char to file pointed to by fp
fputs(str, fp) Writes string str to the file pointed to by fp
fprintf(fp, str, vars) Prints string str to the file pointed to by fp. str can optionally include format
specifiers and a list of variables vars.
FILE *fptr;
char filename[50];
printf("Enter the filename of the file to create: ");
gets(filename);
fptr = fopen(filename, "w");
/* write to file */
fprintf(fptr, "Inventory\n");
fprintf(fptr, "%d %s %f\n", 100, "Widget", 0.29);
fputs("End of List", fptr);
To write blocks of memory to a file, we can use the following binary functions:
Binary file mode options for the fopen() function are:
rb open for reading (file must exist)
wb open for writing (file need not exist)
ab open for append (file need not exist)
rb+ open for reading and writing from beginning
wb+ open for reading and writing, overwriting file
ab+ open for reading and writing, appending to file.
fwrite(ptr, item_size, num_items, fp) Writes num_items items of item_size size from pointer ptr to
the file pointed to by file pointer fp.
fread(ptr, item_size, num_items, fp) Reads num_items items of item_size size from the file pointed
to by file pointer fp into memory pointed to by ptr.
fclose(fp) Closes file opened with file fp, returning 0 if close was successful. EOF is returned if
there is an error in closing.
feof(fp) returns 0 when the end of the file stream has been reached.
FILE *fptr;
int arr[10];
int x[10];
int k;
/* print array */
for (k = 0; k < 10; k++)
printf("%d", x[k]);
File extensions alone do not determine the format of data in a file, but they are useful for indicating
the type of data to expect. .txt for file, .bin for binary, .csv comma separated values, and .dat for
data.
ftell(fp) returns a long int value corresponding to the fp file pointer position in a number of bytes
from the start of the file.
fseek(fp, num_bytes, from_pos) moves the fp file pointer position by num_bytes bytes relative to
position from_pos, which can be one of the following constraints:
SEEK_SET start of file
SEEK_CUR current position
SEEK_END end of file.
typedef struct {
int id;
char name[20];
} item;
int main() {
FILE *fptr;
item first, second, secondf;
/* create records */
first.id = 10276;
strcpy(first.name, "Widget");
second.id = 11786;
strcpy(second.name, "Gadget");
An exception is any situation that causes your program to stop normal execution.
C does not explicitly support exception handling, but there are ways to manage errors.
Write code to prevent errors In the first place, validate input and make sure division by 0 wont
occur. Use the exit statement to gracefully end program execution.
Use errno, perror() and strerror() to identify errors through code.
The exit command immediately stops execution of a program and sends an exit code back to the
calling process. Using exit to avoid a program crash is a good practice because it closes any open
file connections and processes. You can return any value through an exit statement, but 0 for
success and -1 for failure are typical. The predefined stdlib.h macros EXIT_SUCCESS and
EXIT_FAILURE are also commonly used.
int x = 10;
int y = 0;
if (y != 0)
printf("x / y = %d", x/y);
else {
printf("Divisor is 0. Program exiting.");
exit(EXIT_FAILURE);
}
Some library functions, such as fopen(), set an error code when they do not execute as expected.
The error code is set in a global variable named errno, which is defined in the errno.h header file.
When using errno you should set it to 0 before calling a library function.
To output the error code stored in errno, use fprintf to print to the strerr file stream, the standard
error output to the screen. Using strerr is a matter of convention and a good programming
practice. You can output the errno through other means, but it will be easier to keep track of your
exception handling if you only use strerr for error messages.
To use errno, you need to declare it with the statement extern int errno; at the top of your program,
or can include the errno.h header file.
#include <stdio.h>
#include <stdlib.h>
// #include <errno.h>
int main() {
FILE *fptr;
int c;
errno = 0;
fptr = fopen("c:\\nonexistantfile.txt", "r");
if (fptr == NULL) {
fprintf(stderr, "Error opening file. Error code: %d\n", errno);
exit(EXIT_FAILURE);
}
fclose(fptr);
return 0;
}
When a library function sets errno, a cryptic error number is assigned. For a more descriptive
message about the error, you can use perror(). You can also obtain the message using strerror()
in the string.h header file, which returns a pointer to the message text.
perror() must include a string that will precede the actual error message. Typically there is no need
for both perror() and strerror() for the same error.
FILE *fptr;
errno = 0;
fptr = fopen("c:\\nonexistantfile.txt", "r");
if (fptr == NULL) {
perror("Error");
fprintf(stderr, "%s\n", strerror(errno));
exit(EXIT_FAILURE);
}
If there are more than a hundred error codes. Use these statements to list them:
for (int x = 0; x < 135; x++)
fprintf(stderr, "%d: %s\n", x, strerror(x));
Some mathematical functions in the math.h library set errno to the defined marcro value EDOM
when a domain is out of range. Similarly the ERANGE macro value is used when there is a range
error.
float k = -5;
float num = 1000;
float result;
errno = 0;
result = sqrt(k);
if (errno == 0)
printf("%f ", result);
else if (errno == EDOM)
fprintf(stderr, "%s\n", strerror(errno));
errno = 0;
result = exp(num);
if (errno == 0)
printf("%f ", result);
else if (errno == ERANGE)
fprintf(stderr, "%s\n", strerror(errno));
The feof() and ferror() functions can be used for determining file I/O errors:
feof(fp) Returns a nonzero value if the end of the stream has been reached, 0 otherwise. feof also
sets EOF.
ferror(fp) Returns a nonzero value if there is an error, 0 for no error.
FILE *fptr;
int c;
errno = 0;
if (ferror(fptr)) {
printf("I/O error reading file.");
exit(EXIT_FAILURE);
}
else if (feof(fptr)) {
printf("End of file reached.");
}
The Pre-processor
The C pre-processor uses the # directives to make substitutions in program source code before
compilation. For example, the line #include <stdio.h> is replaced by the contents of the stdio.h
header file before a program is compiled.
Preprocessor directives and their uses:
#include including header files.
#define, #undef Defining and undefining macros.
#ifdef, #ifndef, #if, #else, #elif, #endif Conditional compilation.
#pragma Implementation and compiler specific.
#error, #warning Output an error or warning message An error halts compilation.
#include directive is for including header files in a program. A header file declares a collection of
functions and macros for a library, a term that comes from the way the collection of code can be
reused.
Useful C libraries include:
stdio input/output functions, including printf and file operations.
stdlib memory management and other utilities
string functions for handling strings
errno errno global variable and error code macros
math common mathematical funcitons
time time/date utilities
Corresponding header files for the libraries end with .h by convention. The #include directive
expects brackets around the header filename if the file should be searched for in the compiler
include paths.
A user-defined header file is also given the .h extension, but is referred to with quotation marks, as
in “myutil.h”. When quotation marks are used, the file is searched for in the source code directory.
#include <stdio.h>
#include “myutil.h”
Some devs use .hpp extension for header files.
The #define directive is used to create object-like macros for constants based on values or
expressions. #define can also be used to create function-like macros with arguments that will be
replaced by the pre-processor.
Be cautious with function-like definitions. Keep in mind that the pre-processor does a direct
replacement without any calculations, which can lead to unexpected results, as demonstrated:
#include <stdio.h>
#define PI 3.14
#define AREA(r) (PI*r*r)
int main() {
float radius = 2;
printf("%3.2f\n", PI);
printf("Area is %5.2f\n", AREA(radius)); // PI * r * r
printf("Area with radius + 1: %5.2f\n", AREA(radius+1));
return 0; //PI*r+1*r+1
}
The solution is to enclose each parameter in parentheses to obtain the correct order of operations
like:
#define AREA(r) (PI*(r)*(r))
When using pre-processor directives, the # must be the first character on a line. But there can be
any amount of white space before # and between the # and the directive.
If a # directive is lengthy, you can use the \ continuation character to extend the definition over
more than one line:
#define VERY_LONG_CONSTANT \
23.678901
strcpy(curr_time, __TIME__);
strcpy(curr_date, __DATE__);
printf("%s %s\n", curr_time, curr_date);
printf("This is line %d\n", __LINE__);
std_c = __STDC__;
printf("STDC is %d", std_c);
The #itdef, #ifndef, and #undef directives operate on macros created with #define.
For example, there will be compilation problems if the same macro is defined twice, so you can
check of this with an #itdef directive. Or if you may want to redefine a macro, you first use #undef.
#include <stdio.h>
return 0;
}
Only the #ifdef clause will be compiled as RATE is defined at the top. The optional #else branch
compiles when #itdef RATE is false during preprocessing. An #endif is required to close the block
of code. An #elif directive is like an else if and can be used to provide additional alternatives after
#else.
Conditional compilation of segments of code is controlled by a set of directives: #if, #else, #elif,
and #endif.
#define LEVEL 4
int main() {
#if LEVEL > 6
/* do something */
#elif LEVEL > 5
/* else if branch */
#elif LEVEL > 4
/* another else if */
#else
/* last option here */
#endif
return 0;
}
This type of code should be used sparingly.
The defined() pre-processor operator can be used with #if, as in:
#if !defined(LEVEL)
/* statements */
#endif // the #if and if statements are not interchangeable
The C pre-processor provides the following operators
The # macro operator is called the stringification or stringizing operator and tells the pre-
processor to convert a parameter to a string constant.
White space on either side of the argument are ignored and escape sequences are recognized.
#define TO_STR(x) #x