Introduction
Unary operators in C are operators that work on a single operand. They perform various operations like incrementing, decrementing, finding size, getting address, dereferencing pointers, and logical/bitwise negation. Understanding unary operators is essential for pointer manipulation, memory management, and efficient coding.
Key Concepts
Unary Operator: Operates on single operand Prefix Operator: Operator appears before operand Postfix Operator: Operator appears after operand Side Effect: Operation that modifies the operand Precedence: Unary operators have high precedence Associativity: Right-to-left for most unary operators
Types of Unary Operators
1. Increment Operator (++)
#include <stdio.h>
int main() {
int a = 5, b = 5;
printf("Increment Operator Demo:\n");
printf("Initial values: a = %d, b = %d\n", a, b);
// Pre-increment (++variable)
printf("Pre-increment ++a: %d\n", ++a); // a becomes 6, then used
printf("Value of a after pre-increment: %d\n", a);
// Post-increment (variable++)
printf("Post-increment b++: %d\n", b++); // b used as 5, then becomes 6
printf("Value of b after post-increment: %d\n", b);
// In expressions
int x = 10;
int result1 = 2 * ++x; // x incremented to 11, then multiplied
int result2 = 2 * x++; // x used as 11, then incremented to 12
printf("2 * ++x (x was 10): result = %d, x = %d\n", result1, x);
x = 10; // Reset for next example
printf("2 * x++ (x was 10): result = %d, x = %d\n", result2, x);
return 0;
}
2. Decrement Operator (—)
#include <stdio.h>
int main() {
int a = 10, b = 10;
printf("Decrement Operator Demo:\n");
printf("Initial values: a = %d, b = %d\n", a, b);
// Pre-decrement (--variable)
printf("Pre-decrement --a: %d\n", --a); // a becomes 9, then used
printf("Value of a after pre-decrement: %d\n", a);
// Post-decrement (variable--)
printf("Post-decrement b--: %d\n", b--); // b used as 10, then becomes 9
printf("Value of b after post-decrement: %d\n", b);
// Loop example
printf("Countdown using post-decrement:\n");
int count = 5;
while (count > 0) {
printf("%d ", count--);
}
printf("\nFinal count: %d\n", count);
return 0;
}
3. Unary Plus and Minus
#include <stdio.h>
int main() {
int a = 10, b = -15;
printf("Unary Plus and Minus Demo:\n");
// Unary plus (+)
printf("Original a: %d\n", a);
printf("Unary plus +a: %d\n", +a); // Usually has no effect
// Unary minus (-)
printf("Original b: %d\n", b);
printf("Unary minus -b: %d\n", -b); // Changes sign
printf("Double negation -(-b): %d\n", -(-b));
// With expressions
int x = 5, y = -3;
int result = -x + -y; // -5 + 3 = -2
printf("-x + -y where x=%d, y=%d: %d\n", x, y, result);
// Type promotion with unary plus
char c = 100;
printf("char c = %d\n", c);
printf("Promoted +c = %d\n", +c); // Promotes char to int
return 0;
}
4. Logical NOT Operator (!)
#include <stdio.h>
int main() {
int a = 5, b = 0, c = -10;
printf("Logical NOT Operator Demo:\n");
// Basic logical NOT
printf("a = %d, !a = %d\n", a, !a); // Non-zero becomes 0
printf("b = %d, !b = %d\n", b, !b); // Zero becomes 1
printf("c = %d, !c = %d\n", c, !c); // Non-zero becomes 0
// In conditional statements
if (!b) {
printf("b is zero (false)\n");
}
if (!a) {
printf("This won't print\n");
} else {
printf("a is non-zero (true)\n");
}
// Double negation
printf("!!a = %d (converts to boolean)\n", !!a);
printf("!!b = %d (converts to boolean)\n", !!b);
// With expressions
int x = 10, y = 0;
if (!(x > 15) && !y) {
printf("x is not greater than 15 AND y is zero\n");
}
return 0;
}
5. Bitwise NOT Operator (~)
#include <stdio.h>
void printBinary(unsigned int n) {
for (int i = 7; i >= 0; i--) {
printf("%d", (n >> i) & 1);
}
}
int main() {
unsigned int a = 12; // 00001100 in binary
printf("Bitwise NOT Operator Demo:\n");
printf("a = %u (binary: ", a);
printBinary(a);
printf(")\n");
unsigned int result = ~a;
printf("~a = %u (binary: ", result);
printBinary(result);
printf(")\n");
// Practical usage - bit masking
unsigned int mask = 0xFF; // 11111111
printf("mask = %u (binary: ", mask);
printBinary(mask);
printf(")\n");
unsigned int inverted_mask = ~mask;
printf("~mask = %u\n", inverted_mask);
// Toggle bits
unsigned int x = 5; // 00000101
printf("Original x: %u (binary: ", x);
printBinary(x);
printf(")\n");
x = ~x;
printf("After ~x: %u (binary: ", x);
printBinary(x);
printf(")\n");
return 0;
}
6. Address-of Operator (&)
#include <stdio.h>
int main() {
int a = 42;
float b = 3.14;
char c = 'A';
printf("Address-of Operator Demo:\n");
// Getting addresses
printf("Variable a: value = %d, address = %p\n", a, (void*)&a);
printf("Variable b: value = %.2f, address = %p\n", b, (void*)&b);
printf("Variable c: value = %c, address = %p\n", c, (void*)&c);
// Pointer assignment using address-of
int *ptr_a = &a;
float *ptr_b = &b;
char *ptr_c = &c;
printf("\nPointer values:\n");
printf("ptr_a points to address: %p\n", (void*)ptr_a);
printf("ptr_b points to address: %p\n", (void*)ptr_b);
printf("ptr_c points to address: %p\n", (void*)ptr_c);
// Array elements
int arr[3] = {10, 20, 30};
printf("\nArray element addresses:\n");
for (int i = 0; i < 3; i++) {
printf("arr[%d]: value = %d, address = %p\n",
i, arr[i], (void*)&arr[i]);
}
return 0;
}
7. Indirection Operator (*)
#include <stdio.h>
int main() {
int a = 100;
int *ptr = &a;
printf("Indirection Operator Demo:\n");
// Basic dereferencing
printf("Variable a: %d\n", a);
printf("Pointer ptr: %p\n", (void*)ptr);
printf("Value at ptr (*ptr): %d\n", *ptr);
// Modifying through pointer
*ptr = 200;
printf("After *ptr = 200:\n");
printf("Variable a: %d\n", a);
printf("Value at ptr (*ptr): %d\n", *ptr);
// Multiple levels of indirection
int **double_ptr = &ptr;
printf("\nDouble pointer:\n");
printf("double_ptr: %p\n", (void*)double_ptr);
printf("*double_ptr: %p\n", (void*)*double_ptr);
printf("**double_ptr: %d\n", **double_ptr);
// Modifying through double pointer
**double_ptr = 300;
printf("After **double_ptr = 300, a = %d\n", a);
return 0;
}
8. Sizeof Operator
#include <stdio.h>
int main() {
printf("Sizeof Operator Demo:\n");
// Basic data types
printf("sizeof(char): %zu bytes\n", sizeof(char));
printf("sizeof(short): %zu bytes\n", sizeof(short));
printf("sizeof(int): %zu bytes\n", sizeof(int));
printf("sizeof(long): %zu bytes\n", sizeof(long));
printf("sizeof(float): %zu bytes\n", sizeof(float));
printf("sizeof(double): %zu bytes\n", sizeof(double));
// Variables
int x = 10;
double y = 3.14;
char z = 'A';
printf("\nVariable sizes:\n");
printf("sizeof(x): %zu bytes\n", sizeof(x));
printf("sizeof(y): %zu bytes\n", sizeof(y));
printf("sizeof(z): %zu bytes\n", sizeof(z));
// Arrays
int arr[10];
char str[] = "Hello";
printf("\nArray sizes:\n");
printf("sizeof(arr): %zu bytes\n", sizeof(arr));
printf("sizeof(str): %zu bytes\n", sizeof(str));
printf("Elements in arr: %zu\n", sizeof(arr) / sizeof(arr[0]));
// Expressions
printf("\nExpression sizes:\n");
printf("sizeof(x + y): %zu bytes\n", sizeof(x + y));
printf("sizeof(3.14): %zu bytes\n", sizeof(3.14));
return 0;
}
Unary Operators in Complex Expressions
Precedence and Associativity
#include <stdio.h>
int main() {
int a = 5, b = 10;
int *ptr = &a;
printf("Complex Unary Expressions:\n");
// Multiple unary operators
printf("Original: a = %d\n", a);
printf("++a = %d, a is now %d\n", ++a, a);
printf("--a = %d, a is now %d\n", --a, a);
// Combining with dereferencing
printf("\nPointer operations:\n");
printf("*ptr = %d\n", *ptr);
printf("++(*ptr) = %d\n", ++(*ptr)); // Increment value pointed to
printf("a is now %d\n", a);
// Address and dereference
int x = 20;
printf("*(&x) = %d (same as x)\n", *(&x));
// Sizeof with unary operators
printf("sizeof(++a) = %zu (a unchanged: %d)\n", sizeof(++a), a);
return 0;
}
Practical Applications
#include <stdio.h>
// Function using various unary operators
void demonstratePracticalUsage() {
printf("Practical Unary Operator Usage:\n");
// Counter implementation
static int counter = 0;
printf("Function called %d times\n", ++counter);
// Boolean flag toggle
static int flag = 0;
flag = !flag;
printf("Flag toggled to: %d\n", flag);
// Bit manipulation for flags
unsigned int status = 0x05; // 00000101
printf("Original status: 0x%02X\n", status);
status = ~status & 0xFF; // Invert and mask
printf("Inverted status: 0x%02X\n", status);
}
int main() {
// Call function multiple times to see counter
for (int i = 0; i < 3; i++) {
demonstratePracticalUsage();
}
// Array traversal with pointers
int numbers[] = {10, 20, 30, 40, 50};
int *ptr = numbers;
printf("\nArray traversal using pointers:\n");
for (int i = 0; i < 5; i++) {
printf("numbers[%d] = %d (address: %p)\n",
i, *ptr, (void*)ptr);
ptr++; // Move to next element
}
return 0;
}
Common Use Cases
Loop Control
#include <stdio.h>
int main() {
printf("Unary Operators in Loops:\n");
// Pre-increment in for loop
printf("Pre-increment loop:\n");
for (int i = 0; i < 5; ++i) {
printf("%d ", i);
}
printf("\n");
// Post-decrement in while loop
printf("Post-decrement while loop:\n");
int count = 5;
while (count--) {
printf("%d ", count);
}
printf("\n");
// Using logical NOT for termination
printf("Using logical NOT:\n");
int flag = 1;
while (!(!flag)) { // While flag is true
printf("Flag is %d\n", flag);
flag = 0; // Set to false to exit
}
return 0;
}
Memory Operations
#include <stdio.h>
#include <stdlib.h>
int main() {
printf("Unary Operators in Memory Operations:\n");
// Dynamic memory allocation
int *ptr = malloc(sizeof(int));
if (!ptr) { // Using logical NOT to check for NULL
printf("Memory allocation failed\n");
return 1;
}
*ptr = 42;
printf("Allocated memory contains: %d\n", *ptr);
printf("Memory address: %p\n", (void*)ptr);
printf("Size of allocated block: %zu bytes\n", sizeof(*ptr));
free(ptr);
// Array of pointers
int a = 10, b = 20, c = 30;
int *ptrs[] = {&a, &b, &c};
printf("\nArray of pointers:\n");
for (int i = 0; i < 3; i++) {
printf("ptrs[%d] points to value: %d\n", i, *ptrs[i]);
}
return 0;
}
Best Practices
- Use parentheses to clarify precedence in complex expressions
- Avoid multiple unary operators on same variable in one statement
- Be careful with side effects in function arguments
- Use prefix increment/decrement when return value isn’t needed
- Check pointer validity before dereferencing
- Use sizeof for portable code instead of hardcoded sizes
- Be consistent with operator spacing for readability
Common Pitfalls
Side Effects in Expressions
#include <stdio.h>
int main() {
printf("Common Pitfalls with Unary Operators:\n");
// Undefined behavior - multiple modifications
int x = 5;
// int y = x++ + ++x; // Undefined behavior!
// Correct approach
int y = x++;
y += ++x;
printf("Safe approach: y = %d, x = %d\n", y, x);
// Function argument side effects
x = 5;
printf("Function call with side effects:\n");
printf("Values: %d, %d\n", x++, ++x); // Order undefined!
return 0;
}
Summary
Unary operators in C work on single operands and include increment (++), decrement (—), unary plus (+), unary minus (-), logical NOT (!), bitwise NOT (~), address-of (&), indirection (*), and sizeof. These operators have high precedence and right-to-left associativity. They are essential for pointer operations, memory management, loop control, and various computational tasks. Understanding their behavior and potential side effects is crucial for writing correct C programs.
Part of BCA Programming with C Course (UGCOA22J201)