Key Takeaways
- Operators in C are symbols that instruct the computer to perform mathematical or logical manipulations on data. The data items that operators act upon are called operands.
- There are many types of operators supported by C, including postfix, unary, cast, multiplicative, additive, bitwise shift, relational, equality, bitwise AND, bitwise Exclusive OR, bitwise Inclusive OR, logical AND, logical OR, conditional, assignment, and comma operators.
- Postfix operators in C are those that follow the operand. They can operate on either one or two operators. Examples of postfix operators include array subscripts, function call, structure and union members, postfix increment and decrement operators, and compound literals.
- Function call in C is a postfix expression followed by parentheses containing comma-separated arguments or no arguments at all. The return type of a function may be a function, a pointer, or any of the datatypes discussed earlier. If the return type of a function is a pointer, then the function returns an address.
Postfix Operators
An operator is called a Postfix Operator when the operator follows the operand. Postfix Operators may operate on either one or two operators. There are five postfix operators in C:- 1. Array subscripts 2. Function Call 3. Structure and Union Members 4. Postfix Increment and Decrement Operators 5. Compound Literals1. Array Subscripts
We have already defined arrays in the previous article as an identifier that refers to a collection of data items that have the same name and the same data type. Each data item is represented by their array element. The individual array elements are distinguished from one another by their subscripts. A postfix expression (operand followed by the operator) followed by an expression in square brackets [ ] is a subscripted designation of the array element .The definition of the array subscript operator [ ] is that if a is an array and i is an integer then a[i] =*(a+i)
.
If the array subscripting operator [ ] is applied to an n dimensional array x with dimensions n>=2 then x
is converted to a pointer to an (n-1) dimensional array. Individual array elements are accessed by applying the indirection operator *.
To understand the above definition , the concept of pointers and the relation between arrays and pointers needs to be understood.
The name of the array always stores the address of the first element of the array. For example, if x[10]
is an integer array then x
stores the address of the first element x[0]
. As x
is an integer type array each element of the array will occupy 2 bytes. Therefore every element of an array can be accessed in terms of the offset added to the address of the first element. Here the offset is the subscript * memory occupied by the data type of the array. Therfore the second element x[1]
will be located at an offset of 2 bytes from the first element, the third element x[2]
at an offset of 4 bytes from the first element.
A pointer is a variable that stores the address of another variable. In these terms the array is referred to as a self-referential pointer
as it stores its own address, i.e the address of the first element. Every element of an array can therefore be represented in terms of a pointer (the array name) and an offset (the subscript). So x[1]
can be referred to as x+1
, x[2]
which can be referred to as x+2
and so on. The value of the subscript determines the number of times the memory requirements must be added to the initial address to get the location of the subscripted element. This means that for the integer array x
, one needs to find the address of the third element x[2]
and add the memory requirements (2 bytes) twice to the initial address. Similarly the address of x[3]
will be found by adding 2 bytes thrice to the initial address.
To find the actual value of an array element one needs to use the indirection operator *. Therefore if x+2
denotes the memory address of the third element, *(x+2)
will denote the value of the second element i.e the actual value of the second element stored in the array.
If a one-dimensional array can be represented in terms of a pointer(array name ) and an offset(subscript), multi-dimensional arrays can also be represented in similar terms. Therefore a two-dimensional integer array x[2][3]
is actually a collection of two one-dimensional arrays each containing three data items. This two-dimensional array can be defined as a pointer to a group of one-dimensional arrays, where *x
stores the address of (ie it points to) the first array of 3 elements, *(x+1)
stores the address of the second array of three elements and so on. As each row is a one-dimensional array, *(x)
stores the address of the first element of the first row, and *(x+1)
stores the address of the first element of the second row.
In case of a multi-dimensional arrays with more than two dimensions *(x)
or *(x+1)
would not be storing the address of the first element of each row. Rather they would each be storing the initial address of multi-dimensional arrays. More about this when we discuss arrays and pointers in greater detail in a later article.
In an array x[i][j]
, x[i]
is equal to *(x+i)
, x is first converted to a pointer storing the inital address of the first array of j
elements, *(x+1)
stores the initial address of the second array, and so on. Now, the value of i
defines the offset which by definition is the memory requirements’ * subscript value. The memory requirements here are the size of the array of j
elements. Considering our example of x[2][3]
, the memory requirements are the size of an individual array containing three integers each. If *x
points to the initial address of the first array of three integers, *(x+1)
will store the initial address of the second array of three integers which will be located at 3*2=6 bytes from the initial address of the first array. Thus it follows that multi- dimensional arrays are stored in a row major order.
For example: let x[2][3]= {{5,6,8}, {2,4,7}}
then * x
stores the initial address of the first row, ie of {5,6,8}
*(x+1)
stores the initial address of the second row {2,4,7}
Now each row will occupy 6 bytes, so if the initial address of *x
(ie the address of the first row) is 1182
then the address of the second row is 1182+6=1188; ie *(x+1) = 1188
Now to access the individual elements apply the indirection operator, for example: the second element of the first row will be located at (*(x+0)+1)
=1182+1*2 bytes = 1184
*(x+0)
stores the address of the first element of first row then *(x+0)[0]= x[0][0]=5
as the subscript gives the offset, then *(x+0)[0]= *(*(x+0)+0)=x[0][0]=5
Therefore, *(*(x+0)+0)
will give the first the first element of the first row.
Similarly x[0][1] =*(*(x+0)+1*2bytes))=6
An array and a pointer are equivalent in many ways. Here, we’re looking at the basic concepts of arrays and pointers , so as to understand the array subscript operator. The array subscript operator will become completely clear once we discuss arrays and then pointers in detail.
2. Function Calls
In my earlier post Introduction to C, I gave an example of a function and introduced the concept of function prototype, function heading, return type and arguments. It would be helpful to recollect these concepts to understand the function call operator ( ). A postfix expression followed by parentheses containing comma-separated arguments or no arguments at all is called a function call. The postfix expression denotes the called function. The return type of a function may be a function, a pointer, or any of the datatypes discussed earlier. If the return type of a function is a pointer, then the function returns an address. A function argument is an expression that is used within the parentheses of a function call. A function parameter is an object declared within the parentheses of a function declaration or definition. When you call a function, the arguments are evaluated, and each parameter is initialized with the value of the corresponding argument, in the order in which they are present in the function call. Type Conversions of Arguments Arguments that are arrays or functions are converted to pointers before being passed as function arguments. Arguments passed to non prototyped C functions undergo conversions: typechar
parameters are converted to int
, and float
parameters to double
. These are called default argument promotions.
The compiler compares the datatypes provided by the calling function with the datatypes that the called function expects and performs necessary type conversions, so if the called function does not include a prototype the arguments of the function are implicitly converted to the types of the function parameters.
If the function does not have a prototype, no other arguments types are implicitly converted apart from those mentioned above.The number and type of arguments are not compared with the parameters of the called function if it does not have a function prototype declarator.
If the function is defined with a return type that is not compatible with the actual value being returned, you may get an error or undefined program behavior. For example, char
and int
are compatible, but char
and float
are not compatible. If the return type of a function is declared to be char
but it returns a float, the program will give an error.
To understand the above points consider the following example:
/*program to calculate the area of a circle using a user-defined function*/ #include <stdio.h> #define PI 3.14159 int main() { float area,radius; printf("n Enter the radius:"); scanf("%f", &radius); area= process(radius); //process(radius) is a function call printf("Area =%f", area); return 0; } float process( float r)// function expects a float argument { float a; /*local variable declaration*/ a= PI*r*r; return(a); //function returns value. }If in the above program the function prototype was missing , and if the function expected a value of type
double
and the value of radius passed was of type float
, the value of radius being passed would implicitly have been converted to type double
.
Also if the function was declared to be of type void
and you would be returning a value through the return statement, there would have been a type compatibility error.
There is much more to understanding the working of functions, but we’ll discuss them in a future article on functions. Watch out for an article to be published shortly which will continue with investigating operators in C.
Frequently Asked Questions about Operators in C
What is the difference between unary and binary operators in C?
Unary operators in C are those that require only one operand to perform their operation. Examples include increment (++), decrement (–), and the logical NOT (!) operator. On the other hand, binary operators require two operands. Examples include arithmetic operators like addition (+), subtraction (-), multiplication (*), and division (/), and relational operators like less than (<), greater than (>), etc. The main difference between the two lies in the number of operands they require to perform their operation.
How does the modulus operator work in C?
The modulus operator (%) in C is a mathematical operator that returns the remainder of a division operation. For example, if you have an expression like ’10 % 3′, the result would be 1, because when 10 is divided by 3, the remainder is 1. This operator is particularly useful when you need to determine if a number is even or odd, as even numbers will have a modulus of 0 when divided by 2.
What is the purpose of the assignment operator in C?
The assignment operator (=) in C is used to assign the value of the right-hand operand to the left-hand operand. For example, in the statement ‘int a = 10;’, the assignment operator is used to assign the value 10 to the variable ‘a’. It’s important to note that the assignment operator is different from the equality operator (==), which is used to compare two values.
How do logical operators work in C?
Logical operators in C are used to perform logical operations, such as AND, OR, and NOT. The AND operator (&&) returns true if both operands are true. The OR operator (||) returns true if either or both operands are true. The NOT operator (!) returns true if the operand is false, and vice versa. These operators are often used in conditional statements to combine or invert conditions.
What is the precedence of operators in C?
Operator precedence in C determines the order in which operations are performed in an expression. Operators with higher precedence are performed first. For example, multiplication and division operators have higher precedence than addition and subtraction operators. If an expression contains operators with the same precedence, they are performed from left to right. Parentheses can be used to change the order of operations in an expression.
What are bitwise operators in C?
Bitwise operators in C are used to perform operations on individual bits of a number. They include bitwise AND (&), OR (|), XOR (^), NOT (~), left shift (<<), and right shift (>>). These operators are often used in low-level programming, such as writing device drivers or embedded systems.
How does the ternary operator work in C?
The ternary operator in C is a shorthand way of writing an if-else statement. It’s represented as ‘? :’. The syntax is ‘condition ? expression_if_true : expression_if_false’. If the condition is true, the expression after the ‘?’ is executed. If the condition is false, the expression after the ‘:’ is executed.
What is the difference between the equality operator and the assignment operator in C?
The equality operator (==) in C is used to compare two values and returns true if they are equal and false if they are not. On the other hand, the assignment operator (=) is used to assign the value of the right-hand operand to the left-hand operand. It’s important not to confuse the two, as using the assignment operator when you mean to use the equality operator is a common mistake.
How do increment and decrement operators work in C?
The increment operator (++) and decrement operator (–) in C are used to increase or decrease the value of a variable by 1, respectively. They can be used in both postfix (i++) and prefix (++i) forms. In the postfix form, the value is first used in the expression and then incremented or decremented. In the prefix form, the value is first incremented or decremented and then used in the expression.
What are relational operators in C?
Relational operators in C are used to compare two values. They include less than (<), greater than (>), less than or equal to (<=), greater than or equal to (>=), equal to (==), and not equal to (!=). These operators return true if the comparison is true and false if it is not. They are often used in conditional statements to make decisions based on the relationship between two values.
My name is Surabhi Saxena, I am a graduate in computer applications. I have been a college topper, and have been awarded during college by the education minister for excellence in academics. I love programming and Linux. I have a blog on Linux at www.myblogonunix.wordpress.com.