Lesson 04: Operators
In this lesson, we will explore the various operators available
in C programming. Operators are special symbols or keywords that
perform operations on operands. Understanding how to use these
operators effectively is crucial for writing efficient and concise
code.
Objectives
By the end of this lesson, you should be able to:
- Understand and use arithmetic operators in C.
- Apply increment and decrement operators.
- Use assignment operators for value assignments.
- Compare values using relational operators.
- Implement logical operations with logical operators.
- Manipulate bits using bitwise operators.
- Utilize other special operators like the comma operator and the
sizeof operator.
Arithmetic Operators
Arithmetic operators are used to perform basic mathematical
operations.
Operator |
Description |
Example |
+ |
Addition |
a + b |
- |
Subtraction |
a - b |
* |
Multiplication |
a * b |
/ |
Division |
a / b |
% |
Modulus (Remainder) |
a % b |
#include <stdio.h>
int main() {
int a = 10;
int b = 3;
printf("Addition: %d + %d = %d\n", a, b, a + b);
printf("Subtraction: %d - %d = %d\n", a, b, a - b);
printf("Multiplication: %d * %d = %d\n", a, b, a * b);
printf("Division: %d / %d = %d\n", a, b, a / b);
printf("Modulus: %d %% %d = %d\n", a, b, a % b);
return 0;
}
- Addition: Adds two operands.
10 + 3 = 13
- Subtraction: Subtracts the second operand from the
first.
10 - 3 = 7
- Multiplication: Multiplies two operands.
10 * 3 = 30
- Division: Divides the first operand by the second. Note
that for integers, this operation results in an integer (truncated
division).
10 / 3 = 3
- Modulus: Returns the remainder of the division of the
first operand by the second.
10 % 3 = 1
Increment and Decrement
Operators
Increment and decrement operators are unary operators that
increase or decrease the value of a variable by one,
respectively.
Operator |
Description |
Example |
++ |
Increment |
++a or a++ |
-- |
Decrement |
--a or a-- |
#include <stdio.h>
int main() {
int a = 5;
printf("Initial value: %d\n", a); /* prints: 5 */
printf("Pre-increment: %d\n", ++a); /* prints: 6 */
printf("Post-increment: %d\n", a++); /* prints: 6 */
printf("After post-increment: %d\n", a); /* prints: 7 */
printf("Pre-decrement: %d\n", --a); /* prints: 6 */
printf("Post-decrement: %d\n", a--); /* prints: 6 */
printf("After post-decrement: %d\n", a); /* prints: 5 */
return 0;
}
- Pre-increment (++a): Increments the value of a before
using it in an expression.
- Post-increment (a++): Uses the current value of a in an
expression and then increments it.
- Pre-decrement (--a): Decrements the value of a before
using it in an expression.
- Decrements the value of a before using it in an
expression.: Uses the current value of a in an expression and
then decrements it.
Bitwise
Operators
Bitwise operators are used to perform operations on individual
bits of integer data types. These operations are crucial in
low-level programming, where direct manipulation of bits is
required. The common bitwise operators in C include AND
& , OR | , XOR ^ , NOT
~ , left shift << , and right shift
>> .
Operator |
Description |
Example |
& |
Bitwise AND |
a & b |
| |
Bitwise OR |
a | b |
^ |
Bitwise XOR (excessive OR) |
a ^ b |
~ |
Bitwise NOT |
~a |
<< |
Left shift |
a << b |
>> |
Right shift |
a >> b |
#include <stdio.h>
int main() {
unsigned int a = 5; /* 0101 in binary */
unsigned int b = 3; /* 0011 in binary */
printf("a & b: %u\n", a & b); /* 0001 in binary, which is 1 */
printf("a | b: %u\n", a | b); /* 0111 in binary, which is 7 */
printf("a ^ b: %u\n", a ^ b); /* 0110 in binary, which is 6 */
printf("~a: %u\n", ~a); /* 1111...1010 in binary, which
is -6 in 2's complement form
or 4294967290 unsigned */
printf("a << 1: %u\n", a << 1); /* 1010 in binary, which is 10 */
printf("a >> 1: %u\n", a >> 1); /* 0010 in binary, which is 2 */
return 0;
}
& Bitwise AND compares each bit of the first
operand to the corresponding bit of the second operand. If both
bits are 1, the resulting bit is set to 1.
| Bitwise OR compares each bit of the first
operand to the corresponding bit of the second operand. If either
bit is 1, the resulting bit is set to 1.
^ Bitwise XOR compares each bit of the first
operand to the corresponding bit of the second operand. If the bits
are different, the resulting bit is set to 1.
~ Bitwise NOT inverts all the bits of the operand.
Note: in the example above ~5
was equal to 4294967290. This is becuase an integer is 32 bits long
and not just 4 bits, so 1010 should actually be
11111111111111111111111111111010 .
<< Left shift shifts the bits of the first
operand to the left by the number of positions specified by the
second operand.
>> Right shift shifts the bits of the first
operand to the right by the number of positions specified by the
second operand.
Note: In actuality a new zero is not added
from the left as an integer is 32 bits long. The first bit (the 1)
is removed though.
XOR and Simple Encryption
The XOR operator is a bit special and can be used for simple
encryption and decryption, known as the XOR cipher. This method
involves XORing the plaintext with a key to produce the ciphertext.
To decrypt, the ciphertext is XORed with the same key to retrieve
the original plaintext.
The XOR cipher works because of the properties of the XOR
operation. Specifically, the XOR operation is its own inverse. This
means that if you apply the XOR operation twice with the same key,
you get back the original value. Here’s a detailed explanation:
- Initial XOR Operation (Encryption): When you XOR the
plaintext with the key, you get the ciphertext. For example, if
P is the plaintext and K is the key, then
C = P ^ K where C is the ciphertext.
- Decrypting XOR Operation: When you XOR the ciphertext
with the same key, you retrieve the original plaintext. Using the
same example,
P = C ^ K can be expanded as P =
(P ^ K) ^ K .
- Property of XOR: The operation
(P ^ K) ^ K
simplifies to P because of the associative property of
XOR.
P ^ K ^ K results in P ^ 0 (since
K ^ K equals 0 for any K ).
P ^ 0 equals P (since XORing any
value with 0 leaves it unchanged).
This property ensures that the XOR operation can securely
encrypt and decrypt data with the same key, making it a simple yet
effective method for encryption in certain scenarios. However, it
is important to note that the XOR cipher is not suitable for secure
encryption in most practical applications due to its simplicity and
vulnerability to attacks if the key is reused or known.
By using a seed to generate the key, you can create a more
secure implementation of the XOR cipher. This method means that the
security of the encryption relies on the secrecy of the seed and
the PRNG algorithm.
Using a Seed to
Generate the Key for XOR Cipher
- Seed Initialization: A seed value is used to initialize
the PRNG. This seed must be kept secret to ensure the security of
the encryption.
- Key Generation: The PRNG generates a pseudorandom
sequence of bytes that will be used as the key for encryption and
decryption.
- Encryption: The plaintext is XORed with the key to
produce the ciphertext.
- Decryption: The same seed is used to reinitialize the
PRNG, generating the same sequence of bytes for the key. The
ciphertext is then XORed with this key to recover the
plaintext.
Here’s an example of how you can implement this in C:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
void xor_encrypt_decrypt(char *input, char *output, int seed) {
srand(seed); /* Initialize the PRNG with the seed */
for (int i = 0; i < strlen(input); i++) {
output[i] = input[i] ^ (rand() % 256); /* XOR with pseudorandom byte */
}
output[strlen(input)] = '\0';
}
int main() {
char plaintext[] = "HELLO";
int seed = 12345; /* Secret seed value, could be
generated from as password */
char ciphertext[6]; /* +1 for null-terminator */
char decryptedtext[6];
xor_encrypt_decrypt(plaintext, ciphertext, seed);
printf("Ciphertext: ");
for (int i = 0; i < strlen(ciphertext); i++) {
printf("%02X ", (unsigned char)ciphertext[i]);
}
printf("\n");
xor_encrypt_decrypt(ciphertext, decryptedtext, seed);
printf("Decrypted text: %s\n", decryptedtext);
return 0;
}
Note: It is never recommended to implement
your own encryption algorithms for use in real-life applications.
Always use cryptographically proven and tested algorithms that have
been thoroughly vetted by security experts. While experimenting
with encryption can be educational and fun, remember that security
is a critical concern and should always rely on widely tested
practices.
Assignment Operators
Assignment operators are used to assign values to variables.
Operator |
Description |
Example |
Equivalent to |
= |
Assign |
a = b |
|
+= |
Add and assign |
a += b |
a = a + b |
-= |
Subtract and assign |
a -= b |
a = a - b |
*= |
Multiply and assign |
a *= b |
a = a * b |
/= |
Divide and assign |
a =/ b |
a = a / b |
%= |
Modulus and assign |
a %= b |
a = a % b |
&= |
Bitwise AND and assign |
a &= b |
a = a & b |
|= |
Bitwise OR and assign |
a |= b |
a = a | b |
^= |
Bitwise XOR and assign |
a ^= b |
a = a ^ b |
<<= |
Left shift and assign |
a <<= b |
a = a << b |
>>= |
Right shift and assign |
a >>= b |
a = a >> b |
#include <stdio.h>
int main() {
int a;
int b = 3;
a = 5;
a += b;
printf("a += b: %d\n", a);
a = 5;
a -= b;
printf("a -= b: %d\n", a);
a = 5;
a *= b;
printf("a *= b: %d\n", a);
a = 5;
a /= b;
printf("a /= b: %d\n", a);
a = 5;
a %= b;
printf("a %%= b: %d\n", a);
a = 5; /* 101 */
a &= b; /* 101 &= 011 -> 001*/
printf("a &= b: %d\n", a);
a = 5; /* 101 */
a |= b; /* 101 |= 011 -> 111 */
printf("a |= b: %d\n", a);
a = 5; /* 101 */
a ^= b; /* 101 ^= 011 -> 110 */
printf("a ^= b: %d\n", a);
a = 5; /* 101 */
a <<= b; /* 101 <<= 3 -> 101000 */
printf("a <<= b: %d\n", a);
a = 256; /* 100000000 */
a >>= b; /* 100000000 >>= 3 -> 100000 */
printf("a >>= b: %d\n", a);
return 0;
}
= Assigns the right-hand value to the left-hand
variable.
+= Adds the right-hand value to the left-hand
variable and assigns the result to the left-hand variable.
-= Subtracts the right-hand value from the
left-hand variable and assigns the result to the left-hand
variable.
*= Multiplies the left-hand variable by the
right-hand value and assigns the result to the left-hand
variable.
/= Divides the left-hand variable by the
right-hand value and assigns the result to the left-hand
variable.
%= Computes the modulus of the left-hand variable
by the right-hand value and assigns the result to the left-hand
variable.
&= Performs a bitwise AND between the
left-hand variable and the right-hand value and assigns the result
to the left-hand variable.
|= Performs a bitwise OR between the left-hand
variable and the right-hand value and assigns the result to the
left-hand variable.
^= Performs a bitwise XOR between the left-hand
variable and the right-hand value and assigns the result to the
left-hand variable.
<<= Performs a left bitwise shift on the
left-hand variable by the number of positions specified by the
right-hand value and assigns the result to the left-hand
variable.
>>= Performs a right bitwise shift on the
left-hand variable by the number of positions specified by the
right-hand value and assigns the result to the left-hand
variable.
Relational Operators
Relational operators are used to compare two values. They return
either true (1) or false (0).
Operator |
Description |
Example |
True example |
False example |
== |
Equal to |
a == b |
1 == 1 |
1 == 2 |
!= |
Not equal to |
a != b |
1 != 2 |
1 != 1 |
> |
Greater than |
a > b |
2 > 1 |
1 > 2 |
< |
Less than |
a < b |
1 < 2 |
2 < 1 |
>= |
Greater than or equal to |
a >= b |
2 >= 2 |
1 >= 2 |
<= |
Less than or equal to |
a <= b |
1 <= 2 |
2 <= 1 |
#include <stdio.h>
int main() {
int a = 5;
int b = 3;
printf("a == b: %d\n", a == b);
printf("a != b: %d\n", a != b);
printf("a > b: %d\n", a > b);
printf("a < b: %d\n", a < b);
printf("a >= b: %d\n", a >= b);
printf("a <= b: %d\n", a <= b);
return 0;
}
== : Checks if two operands are equal.
!= : Checks if two operands are not equal.
> : Checks if the left operand is greater than
the right operand.
< : Checks if the left operand is less than the
right operand.
>= : Checks if the left operand is greater than
or equal to the right operand.
<= : Checks if the left operand is less than or
equal to the right operand.
Logical
Operators
Operator |
Description |
Example |
&& |
Logical AND |
a && b |
|| |
Logical OR |
a || b |
! |
Logical NOT |
!a |
#include <stdio.h>
int main() {
int a = 5;
int b = 3;
int c = 0;
printf("a && b: %d\n", a && b);
printf("a && c: %d\n", a && c);
printf("a || c: %d\n", a || c);
printf("!a: %d\n", !a);
printf("!c: %d\n", !c);
return 0;
}
&& : Logical AND returns true if both
operands are true.
|| : Logical OR returns true if at least one of the
operands is true.
! : Logical NOT returns true if the operand is
false and vice versa.
Other
Operators
Operator |
Description |
Example |
, |
Comma operator |
a, b |
sizeof |
sizeof Operator |
sizeof a or sizeof(a) |
?: |
Ternary operator |
a = condition ? if_true : if_false |
[] |
Array subscript operator |
a[b] |
& |
Address operator or reference operator |
&a |
* |
Star operator or dereference operator |
*b |
. |
Member access operator |
a.b |
-> |
Member access through pointer operator |
a->b |
Comma
Operator
The comma operator (,) allows two expressions to be evaluated in
a single statement, with the value of the entire statement being
the value of the second expression.
#include <stdio.h>
int main() {
int a, b; /* both a and b are declared on one line */
a = (b = 5, b + 2); /* b is assigned 5, and then a is assigned b + 2 */
printf("a: %d, b: %d\n", a, b); /* a is 7, b is 5 */
return 0;
}
sizeof Operator
The sizeof operator returns the size (in bytes) allocated in
memory of its operand.
#include <stdio.h>
int main() {
int a;
double b;
printf("Size of int: %zu bytes\n", sizeof(a));
printf("Size of double: %zu bytes\n", sizeof b);
return 0;
}
Ternary
Operator
The ternary operator (?:) is a shorthand for an if-else
statement.
#include <stdio.h>
int main() {
int a = 10, b = 20;
int max;
max = (a > b) ? a : b; /* If a > b, max is a; otherwise, max is b */
printf("Max value: %d\n", max);
return 0;
}
Array
Subscript Operator
The array subscript operator [] is used to access
elements within an array. This operator allows you to reference a
specific element in an array by specifying its index, which
represents the element’s position within the array. Array indices
in C start from 0, meaning the first element of an array is
accessed with index 0, the second element with index 1, and so
on.
#include <stdio.h>
int main() {
int numbers[5] = {10, 20, 30, 40, 50};
printf("First element: %d\n", numbers[0]); /* Output: 10 */
printf("Second element: %d\n", numbers[1]); /* Output: 20 */
printf("Third element: %d\n", numbers[2]); /* Output: 30 */
printf("Fourth element: %d\n", numbers[3]); /* Output: 40 */
printf("Fifth element: %d\n", numbers[4]); /* Output: 50 */
numbers[2] = 60; /* Modify an element of the array */
printf("Modified third element: %d\n", numbers[2]); /* Output: 60 */
return 0;
}
Reference Operator &
The reference operator is used to obtain the memory address of a
variable. When you apply the operator to a variable, it returns the
address where the variable is stored in memory. This is
particularly useful in the context of pointers, where you need to
store the address of a variable.
#include <stdio.h>
int main() {
int a = 10;
int *ptr;
ptr = &a; /* Using the reference operator to get the address of 'a' */
printf("The address of variable 'a' is: %p\n", ptr);
return 0;
}
Dereference Operator *
The dereference operator is used to access the value stored at a
particular memory address. When you apply the operator to a
pointer, it returns the value located at the memory address the
pointer holds. This allows you to work with the actual value stored
in the address, rather than the address itself.
#include <stdio.h>
int main() {
int a = 10;
int *ptr;
ptr = &a;
printf("The value at the address stored in 'ptr' is: %d\n", *ptr);
return 0;
}
Member
Access Operator .
The member access operator is used to access members (attributes
or methods) of a structure or union directly through a variable of
that structure or union type. This operator is essential for
working with the individual fields of a structure.
#include <stdio.h>
struct Point {
int x;
int y;
};
int main() {
struct Point p1;
p1.x = 10;
p1.y = 20;
printf("Point p1: (%d, %d)\n", p1.x, p1.y);
return 0;
}
Member Access Through Pointer
Operator ->
The member access through pointer operator is used to access
members of a structure or union through a pointer to that structure
or union. This operator is especially useful when you have a
pointer to a structure and you want to access its members.
#include <stdio.h>
struct Point {
int x;
int y;
};
int main() {
struct Point p1;
struct Point *ptr;
ptr = &p1;
ptr->x = 10;
ptr->y = 20;
printf("Point p1: (%d, %d)\n", ptr->x, ptr->y);
return 0;
}
The reference and dereference operators are fundamental for
working with pointers, allowing you to retrieve memory addresses
and access values stored at those addresses, respectively. The
member operators are essential for accessing members of structures
or unions, whether directly through variables or through pointers.
These operators will be covered in more detail in the lessons on
structures and pointers.
Exercises
- Arithmetic Operators: Write a program that takes two
integers from the user and demonstrates the use of each arithmetic
operator on these values.
- Logical Operators: Write a program to evaluate logical
expressions and print the results. Take different combinations of
boolean variables as inputs.
- Bitwise Operations: Write a program to perform various
bitwise operations (AND, OR, XOR, NOT, left shift, right shift) on
two given integers and print the results.
- Bitwise XOR Cipher: Implement a program that uses the
XOR cipher to encrypt and decrypt a string using a given key. The
key should be used to create a hash that serves as a seed for
generating the XOR-key. The program should be able to handle
strings of any length (or at least 1024) via standard input.
- Read Input String and Key
- Prompt the user to enter a string to encrypt or decrypt.
- Prompt the user to enter a key for the encryption/decryption
process.
- Generate Hash from Key
- Create a simple hash from the provided key to use as a seed for
the random number generator. This hash ensures that the key
generates a unique sequence of random numbers. Look up algorithms
online, for example: djb2.
- Encrypt/Decrypt the String
- Use the generated seed to initialize the random number
generator.
- XOR each character of the input string with a random number
generated from the seed to encrypt or decrypt the string.
- Output the Result
- Print the encrypted or decrypted string.
Solutions
Example solution to ’Bitwise XOR Cipher’:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
unsigned long generate_hash(const char *key) {
unsigned long hash = 5381;
int c;
while ((c = *key++)) {
hash = ((hash << 5) + hash) + c;
}
return hash;
}
void xor_cipher(char *data, unsigned long seed, size_t length) {
srand(seed);
for (size_t i = 0; i < length; ++i) {
data[i] ^= rand();
}
}
int main() {
char input[1024];
char key[256];
printf("Enter the string to encrypt/decrypt: ");
fgets(input, sizeof(input), stdin);
input[strcspn(input, "\n")] = '\0'; /* Remove the newline character */
printf("Enter the key: ");
fgets(key, sizeof(key), stdin);
key[strcspn(key, "\n")] = '\0'; /* Remove the newline character */
unsigned long seed = generate_hash(key);
xor_cipher(input, seed, strlen(input));
printf("Output encrypted string: %s\n", input);
xor_cipher(input, seed, strlen(input));
printf("Output decrypted string: %s\n", input);
return 0;
}
If you haven’t figured it out yet, this program can be further
optimized by using a loop to read input of any length without
specifying a fixed buffer size (e.g., 1024). In two lessons, you
will learn how to implement this enhancement.
Next Lesson
In the next lesson, we will delve into the fundamental control
structures in C programming, starting with the "if...else"
statements. These conditional statements allow you to control the
flow of your program based on specific conditions, enabling more
dynamic and responsive code.
Next Lesson
|