Unary Operators

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

  1. Use parentheses to clarify precedence in complex expressions
  2. Avoid multiple unary operators on same variable in one statement
  3. Be careful with side effects in function arguments
  4. Use prefix increment/decrement when return value isn’t needed
  5. Check pointer validity before dereferencing
  6. Use sizeof for portable code instead of hardcoded sizes
  7. 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)