C Basics - C Programming Tutorial
C Basics - C Programming Tutorial
| HOME
Basics 2.3
2.4
2.3
2.4
Statements and Blocks
White Spaces and Formatting So
2.5 2.5 Preprocessor Directives
3. 3. Variables and Types
3.1 3.1 Variables
This chapter explains the features, technical details and syntaxes of the C programming language. I assume that you
3.2 3.2 Identifiers
could write some simple programs. Otherwise, read "Introduction to Programming in C for Novices and First-time
3.3 3.3 Variable Declaration
Programmers".
3.4 3.4 Constants (const)
3.5 3.5 Expressions
3.6 3.6 Assignment (=)
1. 1. Introduction to C
3.7 3.7 Fundamental Types
3.8 3.8 Output via printf() Function
C Standards
3.9 3.9 Input via scanf() Function
C is standardized as ISO/IEC 9899. 3.10 3.10 Literals for Fundamental Type
1. K&R C: Pre-standardized C, based on Brian Kernighan and Dennis Ritchie (K&R) "The C Programming Language" 4. 4. Operations
1978 book. 4.1 4.1 Arithmetic Operators
2. C90 (ISO/IEC 9899:1990 "Programming Languages. C"). Also known as ANSI C 89. 4.2 4.2 Arithmetic Expressions
4.3 4.3 Mixed-Type Operations
3. C99 (ISO/IEC 9899:1999 "Programming Languages. C")
4.4 4.4 Overflow/UnderFlow
4. C11 (ISO/IEC 9899:2011 "Programming Languages. C") 4.5 4.5 Compound Assignment Operator
4.6 4.6 Increment/Decrement Operators
C Features 4.7 4.7 Implicit Type-Conversion vs. Exp
[TODO] 4.8 4.8 Relational and Logical Operators
5. 5. Flow Control
C Strength and Pitfall 5.1 5.1 Sequential Flow Control
5.2 5.2 Conditional (Decision) Flow Cont
[TODO]
5.3 5.3 Loop Flow Control
5.4 5.4 Interrupting Loop Flow - "
1 /*
2 * Sum the odd and even numbers, respectively, from 1 to a given upperbound.
3 * Also compute the absolute difference.
4 * (SumOddEven.c)
5 */
6 #include <stdio.h> // Needed to use IO functions
7
8 int main() {
9 int sumOdd = 0; // For accumulating odd numbers, init to 0
10 int sumEven = 0; // For accumulating even numbers, init to 0
11 int upperbound; // Sum from 1 to this upperbound
12 int absDiff; // The absolute difference between the two sums
13
14 // Prompt user for an upperbound
15 printf("Enter the upperbound: ");
16 scanf("%d", &upperbound); // Use %d to read an int
17
18 // Use a while-loop to repeatedly add 1, 2, 3,..., to the upperbound
19 int number = 1;
20 while (number <= upperbound) {
21 if (number % 2 == 0) { // Even number
22 sumEven += number; // Add number into sumEven
23 } else { // Odd number
24 sumOdd += number; // Add number into sumOdd
25 }
26 ++number; // increment number by 1
27 }
28
29 // Compute the absolute difference between the two sums
30 if (sumOdd > sumEven) {
31 absDiff = sumOdd - sumEven;
32 } else {
33 absDiff = sumEven - sumOdd;
34 }
35
36 // Print the results
37 printf("The sum of odd numbers is %d.\n", sumOdd);
38 printf("The sum of even numbers is %d.\n", sumEven);
39 printf("The absolute difference is %d.\n", absDiff);
40
41 return 0;
42 }
2. End-of-line Comment: begins with // and lasts till the end of the current line.
You should use comments liberally to explain and document your codes. During program development, instead of deleting a chunk of statements permanently, you
could comment-out these statements so that you could get them back later, if needed.
For examples,
// Each of the following lines is a programming statement, which ends with a semi-colon (;)
int number1 = 10;
int number2, number3 = 99;
int product;
product = number1 * number2 * number3;
printf("Hello\n");
Block : A block (or a compound statement) is a group of statements surrounded by braces { }. All the statements inside the block is treated as one unit. Blocks
are used as the body in constructs like function, if-else and loop, which may contain multiple statements but are treated as one unit. There is no need to put a
semi-colon after the closing brace to end a complex statement. Empty block (without any statement) is permitted. For examples,
// Each of the followings is a "complex" statement comprising one or more blocks of statements.
// No terminating semi-colon needed after the closing brace to end the "complex" statement.
// Take note that a "complex" statement is usually written over a few lines for readability.
if (mark >= 50) {
printf("PASS\n");
printf("Well Done!\n");
printf("Keep it Up!\n");
}
if (number == 88) {
printf("Got it\n");
} else {
printf("Try Again\n");
}
i = 1;
while (i < 8) {
printf("%d\n", i);
++i;
}
int main() {
...statements...
}
You need to use a white space to separate two keywords or tokens, e.g.,
Additional white spaces and extra lines are, however, ignored, e.g.,
// same as above
int sum
= 0 ;
double average ;
average = sum / 100.0;
Formatting Source Codes : As mentioned, extra white spaces are ignored and have no computational significance. However, proper indentation (with tabs
and blanks) and extra empty lines greatly improves the readability of the program, which is extremely important for others (and yourself three days later) to
understand your programs. For example, the following hello-world works, but can you understand the program?
#include <stdio.h>
int main(){printf("Hello, world!\n");return 0;}
Braces : Place the beginning brace at the end of the line, and align the ending brace with the start of the statement.
Indentation : Indent the body of a block by an extra 3 (or 4 spaces), according to its level.
For example,
/*
* Recommended Programming style.
*/
#include <stdio.h>
// blank line to separate sections of codes
int main() { // Place the beginning brace at the end of the current line
// Indent the body by an extra 3 or 4 spaces for each level
return 0;
} // ending brace aligned with the start of the statement
Most IDEs (such as CodeBlocks, Eclipse and NetBeans) have a command to re-format your source code automatically.
Note: Traditional C-style formatting places the beginning and ending braces on the same column. For example,
/*
* Traditional C-style.
*/
#include <stdio.h>
int main()
{
int mark = 70;
if (mark >= 50) // in level-1 block, indent once
{
printf("You Pass!\n"); // in level-2 block, indent twice
}
else
{
printf("You Fail!\n");
}
return 0;
}
A preprocessor directive, which begins with a # sign (such as #include, #define), tells the preprocessor to perform a certain action (such as including a header
file, or performing text replacement), before compiling the source code into object code. Preprocessor directives are not programming statements, and therefore
should NOT be terminated with a semi-colon. For example,
In almost all of the C programs, we use #include <stdio.h> to include the input/output stream library header into our program, so as to use the IO library
function to carry out input/output operations (such as printf() and scanf()).
More precisely, a variable is a named storage location, that stores a value of a particular data type. In other words, a variable has a name, a type and stores a
value.
A variable has a name (or identifier), e.g., radius, area, age, height. The name is needed to uniquely identify each variable, so as to assign a value to the
variable (e.g., radius=1.2), and retrieve the value stored (e.g., area = radius*radius*3.1416).
double: for floating-point or real numbers such as 3.1416, -55.66, having a decimal point and fractional part.
A variable can store a value of that particular type. It is important to take note that a variable in most programming languages is associated with a type, and
can only store value of the particular type. For example, a int variable can store an integer value such as 123, but NOT real number such as 12.34, nor texts
such as "Hello".
The concept of type was introduced into the early programming languages to simplify interpretation of data made up of 0s and 1s. The type determines the
size and layout of the data, the range of its values, and the set of operations that can be applied.
The following diagram illustrates two types of variables: int and double. An int variable stores an integer (whole number). A double variable stores a real
number.
3.2 3.2 Identifiers
An identifier is needed to name a variable (or any other entity such as a function or a class). C imposes the following rules on identifiers:
An identifier is a sequence of characters, of up to a certain length (compiler-dependent, typically 255 characters), comprising uppercase and lowercase letters
(a-z, A-Z), digits (0-9), and underscore "_".
White space (blank, tab, new-line) and other special characters (such as +, -, *, /, @, &, commas, etc.) are not allowed.
An identifier must begin with a letter or underscore. It cannot begin with a digit. Identifiers beginning with an underscore are typically reserved for system use.
An identifier cannot be a reserved keyword or a reserved literal (e.g.,int, double, if, else, for).
Caution : Programmers don't use blank character in names. It is either not supported, or will pose you more challenges.
Recommendations
1. It is important to choose a name that is self-descriptive and closely reflects the meaning of the variable, e.g., numberOfStudents or numStudents.
3. Avoid single-alphabet names, which is easier to type but often meaningless, unless they are common names like x, y, z for coordinates, i for index.
4. It is perfectly okay to use long names of says 30 characters to make sure that the name accurately reflects its meaning!
5. Use singular and plural nouns prudently to differentiate between singular and plural variables. For example, you may use the variable row to refer to a single
row number and the variable rows to refer to many rows (such as an array of rows - to be discussed later).
Syntax Example
Example,
1 #include <stdio.h>
2
3 int main() {
4 int number; // Declared but not initialized
5 printf("%d\n", number); // Used before initialized
6 // No warning/error, BUT unexpected result
7 return 0;
8 }
Constant Naming Convention: Use uppercase words, joined with underscore. For example, MIN_VALUE, MAX_SIZE.
1 + 2 * 3 // give int 7
The RHS shall be a value; and the LHS shall be a variable (or memory address).
Syntax Example
// Assign the literal value (of the RHS) to the variable (of the LHS)
variable = literal-value; number = 88;
// Evaluate the expression (RHS) and assign the result to the variable (LHS)
variable = expression; sum = sum + number;
The assignment statement should be interpreted this way: The expression on the right-hand-side (RHS) is first evaluated to produce a resultant value (called rvalue
or right-value). The rvalue is then assigned to the variable on the left-hand-side (LHS) (or lvalue, which is a location that can hold a rvalue). Take note that you
have to first evaluate the RHS, before assigning the resultant value to the LHS. For examples,
The symbol "=" is known as the assignment operator. The meaning of "=" in programming is different from Mathematics. It denotes assignment instead of equality.
The RHS is a literal value; or an expression that evaluates to a value; while the LHS must be a variable. Note that x = x + 1 is valid (and often used) in
programming. It evaluates x + 1 and assign the resultant value to the variable x. x = x + 1 illegal in Mathematics. While x + y = 1 is allowed in
Mathematics, it is invalid in programming (because the LHS of an assignment statement must be a variable). Some programming languages use symbol ":=", "←",
"->", or "→" as the assignment operator to avoid confusion with equality.
Floating-point Numbers: There are 3 floating point types: float, double and long double, for single, double and long double precision floating point
numbers. float and double are represented as specified by IEEE 754 standard. A float can represent a number between ±1.40239846×10^-45 and
±3.40282347×10^38, approximated. A double can represented a number between ±4.94065645841246544×10^-324 and
±1.79769313486231570×10^308, approximated. Take note that not all real numbers can be represented by float and double, because there are infinite real
numbers. Most of the values are approximated.
The table below shows the typical size, minimum, maximum for the primitive types. Again, take note that the sizes are implementation dependent.
In addition, many C library functions use a type called size_t, which is equivalent (typedef) to a unsigned int, meant for counting, size or length, with 0
and positive integers.
C provides an unary sizeof operator to get the size of the operand (in bytes). The following program uses sizeof operator to print the size of the fundamental
types.
1 /*
2 * Print Size of Fundamental Types (SizeofTypes.cpp).
3 */
4 #include <stdio.h>
5
6 int main() {
7 printf("sizeof(char) is %d bytes.\n", sizeof(char));
8 printf("sizeof(short) is %d bytes.\n", sizeof(short));
9 printf("sizeof(int) is %d bytes.\n", sizeof(int));
10 printf("sizeof(long) is %d bytes.\n", sizeof(long));
11 printf("sizeof(long long) is %d bytes.\n", sizeof(long long));
12 printf("sizeof(float) is %d bytes.\n", sizeof(float));
13 printf("sizeof(double) is %d bytes.\n", sizeof(double));
14 printf("sizeof(long double) is %d bytes.\n", sizeof(long double));
15 return 0;
16 }
sizeof(char) is 1 bytes.
sizeof(short) is 2 bytes.
sizeof(int) is 4 bytes.
sizeof(long) is 4 bytes.
sizeof(long long) is 8 bytes.
sizeof(float) is 4 bytes.
sizeof(double) is 8 bytes.
sizeof(long double) is 12 bytes.
*Header <limits.h>
The limits.h header contains information about limits of integer type. For example,
The minimum of unsigned integer is always 0. The other constants are SHRT_MAX, SHRT_MIN, USHRT_MAX, LONG_MIN, LONG_MAX, ULONG_MAX. Try inspecting
this header (search for limits.h under your compiler).
*Header <float.h>
Similarly, the float.h header contain information on limits for floating point numbers, such as minimum number of significant digits (FLT_DIG, DBL_DIG,
LDBL_DIG for float, double and long double), number of bits for mantissa (FLT_MANT_DIG, DBL_MANT_DIG, LDBL_MANT_DIG), maximum and minimum
exponent values, etc. Try inspecting this header (search for cfloat under your compiler).
Choosing Types
As a programmer, you need to choose variables and decide on the type of the variables to be used in your programs. Most of the times, the decision is intuitive. For
example, use an integer type for counting and whole number; a floating-point type for number with fractional part, char for a single character, and boolean for
binary outcome.
Rule of Thumb
Use int for integer and double for floating point numbers. Use byte, short, long and float only if you have a good reason to choose that specific
precision.
Use int (or unsigned int) for counting and indexing, NOT floating-point type (float or double). This is because integer type are precise and more
efficient in operations.
Use an integer type if possible. Use a floating-point type only if the number contains a fractional part.
Read my article on "Data Representation" if you wish to understand how the numbers and characters are represented inside the computer memory. In brief, It is
important to take note that char '1' is different from int 1, short 1, float 1.0, double 1.0, and String "1". They are represented differently in the
computer memory, with different precision and interpretation. For example, short 1 is "00000000 00000001", int 1 is "00000000 00000000 00000000
00000001", long long 1 is "00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001", float 1.0 is "0 01111111
0000000 00000000 00000000", double 1.0 is "0 01111111111 0000 00000000 00000000 00000000 00000000 00000000 00000000", char
'1' is "00110001".
Furthermore, you MUST know the type of a value before you can interpret a value. For example, this value "00000000 00000000 00000000 00000001"
cannot be interpreted unless you know the type.
Typing "unsigned int" many time can get annoying. The typedef statement can be used to create a new name for an existing type. For example, you can
create a new type called "uint" for "unsigned int" as follow. You should place the typedef immediately after #include. Use typedef with care because it
makes the program hard to read and understand.
Many C compilers define a type called size_t, which is a typedef of unsigned int.
To print a string literal such as "Hello, world", simply place it inside the parentheses, as follow:
printf(aStringLiteral);
For example,
printf("Hello, world\n");
Hello, world
_
The \n represents the newline character. Printing a newline advances the cursor (denoted by _ in the above example) to the beginning of next line. printf(), by
default, places the cursor after the printed string, and does not advance the cursor to the next line. For example,
printf("Hello");
printf(", ");
printf("world!");
printf("\n");
printf("Hello\nworld\nagain\n");
Hello, world!
Hello
world
again
_
The "f" in printf() stands for "formatted" printing. To do formatted printing, you need to use the following syntax:
The formattingString is a string composing of normal texts and conversion specifiers. Normal texts will be printed as they are. A conversion specifier begins with a
percent sign (%), followed by a code to specify the type of variable and format of the output (such as the field width and number of decimal places). For example,
%d denotes an int; %3d for an int with field-width of 3. The conversion specifiers are used as placeholders, which will be substituted by the variables given after
the formatting string in a sequential manner. For example,
1 /*
2 * Test formatted printing for int (TestPrintfInt.c)
3 */
4 #include <stdio.h>
5
6 int main() {
7 int number1 = 12345, number2 = 678;
8 printf("Hello, number1 is %d.\n", number1); // 1 format specifier
9 printf("number1=%d, number2=%d.\n", number1, number2); // 2 format specifiers
10 printf("number1=%8d, number2=%5d.\n", number1, number2); // Set field-widths
11 printf("number1=%08d, number2=%05d.\n", number1, number2); // Pad with zero
12 printf("number1=%-8d, number2=%-5d.\n", number1, number2); // Left-align
13 return 0;
14 }
%u unsigned int
%o int in octal
%f, %lf (printf), %lf (scanf) double: Use %f or %lf in printf(), but %lf in scanf().
Character %c char
String %s string
Notes:
For double, you must use %lf (for long float) in scanf() (or %le, %lE, %lg, %lG), but you can use either %f or %lf in printf() (or %e, %E, %g, %G, %le,
%lE, %lg, %lG).
For example,
Field Width
You can optionally specify a field-width before the type conversion code, e.g., %3d, %6f, %20s. If the value to be formatted is shorter than the field width, it will be
padded with spaces (by default). Otherwise, the field-width will be ignored. For example,
Alignment
The output are right-aligned by default. You could include a "-" flag (before the field width) to ask for left-aligned. For example,
Others
+ (plus sign): display plus or minus sign preceding the number.
C11's printf_s()/scanf_s()
C11 introduces more secure version of printf()/scanf() called printf_s()/scanf_s() to deal with mismatched conversion specifiers. Microsoft Visual C
implemented its own versions of printf_s()/scanf_s() before C11, and issues a deprecated warning for using printf()/scanf().
1 /*
2 * TestScanf.c
3 */
4 #include <stdio.h>
5
6 int main() {
7 int anInt;
8 float aFloat;
9 double aDouble;
10
11 printf("Enter an int: "); // Prompting message
12 scanf("%d", &anInt); // Read an int from keyboard and assign to variable anInt.
13 printf("The value entered is %d.\n", anInt);
14
15 printf("Enter a floating-point number: "); // Prompting message
16 scanf("%f", &aFloat); // Read a double from keyboard and assign to variable aFloat.
17 printf("The value entered is %f.\n", aFloat);
18
19 printf("Enter a floating-point number: "); // Prompting message
20 scanf("%lf", &aDouble); // Read a double from keyboard and assign to variable aDouble.
21 printf("The value entered is %lf.\n", aDouble);
22
23 return 0;
24 }
Notes:
To place the input into a variable in scanf(), you need to prefix the variable name by an ampersand sign (&). The ampersand (&) is called address-of
operator, which will be explained later. However, it is important to stress that missing ampersand (&) is a common error.
For double, you must use type conversion code %lf for scanf(). You could use %f or %lf for printf().
The scanf() returns 1 if user enters an integer which is read into the variable number. It returns 0 if user enters a non-integer (such as "hello"), and variable
number is not assigned.
The scanf() returns 2 if user enters two integers that are read into number1 and number2. It returns 1 if user enters an integer followed by a non-integer, and
number2 will not be affected. It returns 0 if user enters a non-integer, and both number1 and number2 will not be affected.
Integer Literals
A whole number, such as 123 and -456, is treated as an int, by default. For example,
An int literal may precede with a plus (+) or minus (-) sign, followed by digits. No commas or special symbols (e.g., $ or space) is allowed (e.g., 1,234 and
$123 are invalid). No preceding 0 is allowed too (e.g., 007 is invalid).
Besides the default base 10 integers, you can use a prefix '0' (zero) to denote a value in octal, prefix '0x' for a value in hexadecimal, and prefix '0b' for binary
value (in some compilers), e.g.,
A long literal is identified by a suffix 'L' or 'l' (avoid lowercase, which can be confused with the number one). A long long int is identified by a suffix
'LL'. You can also use suffix 'U' for unsigned int, 'UL' for unsigned long, and 'ULL' for unsigned long long int. For example,
No suffix is needed for short literals. But you can only use integer values in the permitted range. For example,
short smallNumber = 1234567890; // ERROR: this value is outside the range of short.
short midSizeNumber = -12345;
Floating-point Literals
A number with a decimal point, such as 55.66 and -33.44, is treated as a double, by default. You can also express them in scientific notation, e.g., 1.2e3,
-5.5E-6, where e or E denotes the exponent in power of 10. You could precede the fractional part or exponent with a plus (+) or minus (-) sign. Exponent shall
be an integer. There should be no space or other characters (e.g., space) in the number.
You MUST use a suffix of 'f' or 'F' for float literals, e.g., -1.2345F. For example,
float average = 55.66; // Error! RHS is a double. Need suffix 'f' for float.
float average = 55.66f;
For example,
Non-printable and control characters can be represented by so-called escape sequences, which begins with a back-slash (\) followed by a code. The commonly-
used escape sequences are:
Escape Hex
Description
Sequence (Decimal)
\n New-line (or Line-feed) 0AH (10D)
\r Carriage-return 0DH (13D)
\t Tab 09H (9D)
\" Double-quote (needed to include " in double- 22H (34D)
quoted string)
\' Single-quote 27H (39D)
\\ Back-slash (to resolve ambiguity) 5CH (92D)
Notes:
New-line (0AH) and carriage return (0dH), represented by \n, and \r respectively, are used as line delimiter (or end-of-line, or EOL). However, take note that
UNIX/Linux/Mac use \n as EOL, Windows use \r\n.
To resolve ambiguity, characters back-slash (\), single-quote (') and double-quote (") are represented using escape sequences \\, \' and \", respectively.
This is because a single back-slash begins an escape sequence, while single-quotes and double-quotes are used to enclose character and string.
Other less commonly-used escape sequences are: \? or ?, \a for alert or bell, \b for backspace, \f for form-feed, \v for vertical tab. These may not be
supported in some consoles.
The ctype.h header provides functions such as isalpha(), isdigit(), isspace(), ispunct(), isalnum(), isupper(), islower() to determine the
type of character; and toupper(), tolower() for case conversion.
String Literals
A String literal is composed of zero of more characters surrounded by a pair of double quotes, e.g., "Hello, world!", "The sum is ", "".
String literals may contains escape sequences. Inside a String, you need to use \" for double-quote to distinguish it from the ending double-quote, e.g.
"\"quoted\"". Single quote inside a String does not require escape sequence. For example,
TRY: Write a program to print the following picture. Take note that you need to use escape sequences to print special characters.
'__'
(oo)
+========\/
/ || %%% ||
* ||-----||
"" ""
Example (Literals)
1 /* Testing Primitive Types (TestLiteral.c) */
2 #include <stdio.h>
3
4 int main() {
5 char gender = 'm'; // char is single-quoted
6 unsigned short numChildren = 8; // [0, 255]
7 short yearOfBirth = 1945; // [-32767, 32768]
8 unsigned int salary = 88000; // [0, 4294967295]
9 double weight = 88.88; // With fractional part
10 float gpa = 3.88f; // Need suffix 'f' for float
11
12 printf("Gender is %c.\n", gender);
13 printf("Number of children is %u.\n", numChildren);
14 printf("Year of birth is %d.\n", yearOfBirth);
15 printf("Salary is %u.\n", salary);
16 printf("Weight is %.2lf.\n", weight);
17 printf("GPA is %.2f.\n", gpa);
18 return 0;
19 }
Gender is m.
Number of children is 8.
Year of birth is 1945.
Salary is 88000.
Weight is 88.88.
GPA is 3.88.
4. 4. Operations
All the above operators are binary operators, i.e., they take two operands. The multiplication, division and remainder take precedence over addition and
subtraction. Within the same precedence level (e.g., addition and subtraction), the expression is evaluated from left to right. For example, 1+2+3-4 is evaluated as
((1+2)+3)-4.
It is important to take note that int/int produces an int, with the result truncated, e.g., 1/2 → 0 (instead of 0.5).
Take note that C does not have an exponent (power) operator ('^' is exclusive-or, not exponent).
must be written as (1+2*a)/3 + (4*(b+c)*(5-d-e))/f - 6*(7/g+h). You cannot omit the multiplication symbol '*' (as in Mathematics).
Like Mathematics, the multiplication '*' and division '/' take precedence over addition '+' and subtraction '-'. Parentheses () have higher precedence. The
operators '+', '-', '*', and '/' are left-associative. That is, 1 + 2 + 3 + 4 is treated as (((1+2) + 3) + 4).
However, if the two operands belong to different types, the compiler promotes the value of the smaller type to the larger type (known as implicit type-casting). The
operation is then carried out in the larger type. For example, int/double → double/double → double. Hence, 1/2 → 0, 1.0/2.0 → 0.5, 1.0/2 →
0.5, 1/2.0 → 0.5.
For example,
Example
1 /* Testing mix-type arithmetic operations (TestMixTypeOp.c) */
2 #include <stdio.h>
3
4 int main() {
5 int i1 = 2, i2 = 4;
6 double d1 = 2.5, d2 = 5.2;
7
8 printf("%d + %d = %d\n", i1, i2, i1+i2); // 2 + 4 = 6
9 printf("%.1lf + %.1lf = %.1lf\n", d1, d2, d1+d2); // 2.5 + 5.2 = 7.7
10 printf("%d + %.1lf = %.1lf\n", i1, d2, i1+d2); // 2 + 5.2 = 7.2 <== mix type
11
12 printf("%d / %d = %d\n", i1, i2, i1/i2); // 2 / 4 = 0 <== NOTE: truncate
13 printf("%.1lf / %.1lf = %.2lf\n", d1, d2, d1/d2); // 2.5 / 5.2 = 0.48
14 printf("%d / %.1lf = %.2lf\n", i1, d2, i1/d2); // 2 / 5.2 = 0.38 <== mix type
15 return 0;
16 }
4.4 4.4 Overflow/UnderFlow
Study the output of the following program:
In arithmetic operations, the resultant value wraps around if it exceeds its range (i.e., overflow or underflow). C runtime does not issue an error/warning message
but produces incorrect result.
It is important to take note that checking of overflow/underflow is the programmer's responsibility, i.e., your job!
This feature is an legacy design, where processors were slow. Checking for overflow/underflow consumes computation power and reduces performance.
To check for arithmetic overflow (known as secure coding) is tedious. Google for "INT32-C. Ensure that operations on signed integers do not result in overflow" @
www.securecoding.cert.org.
Example
1 /* Test on increment (++) and decrement (--) Operator (TestIncDec.cpp) */
2 #include <stdio.h>
3
4 int main() {
5 int mark = 76; // declare & assign
6 printf("%d\n", mark); // 76
7
8 mark++; // increase by 1 (post-increment)
9 printf("%d\n", mark); // 77
10
11 ++mark; // increase by 1 (pre-increment)
12 printf("%d\n", mark); // 78
13
14 mark = mark + 1; // also increase by 1 (or mark += 1)
15 printf("%d\n", mark); // 79
16
17 mark--; // decrease by 1 (post-decrement)
18 printf("%d\n", mark); // 78
19
20 --mark; // decrease by 1 (pre-decrement)
21 printf("%d\n", mark); // 77
22
23 mark = mark - 1; // also decrease by 1 (or mark -= 1)
24 printf("%d\n", mark); // 76
25 return 0;
26 }
The increment/decrement unary operator can be placed before the operand (prefix operator), or after the operands (postfix operator). They take on different
meaning in operations.
If '++' or '--' involves another operation, then pre- or post-order is important to specify the order of the two operations. For examples,
x = 5;
printf("%d\n", x++); // Save x (5); Increment x (=6); Print old x (5).
x = 5;
printf("%d\n", ++x); // Increment x (=6); Print x (6).
// This is confusing! Try to avoid! What is i=++i? What is i=i++?
Prefix operator (e.g, ++i) could be more efficient than postfix operator (e.g., i++) in some situations.
if you assign a double value of to an int variable, the compiler automatically casts the double value to an int value (e.g., from 1.2 to 1) and assigns it to
the int variable. The fractional part would be truncated and lost. Some compilers issue a warning/error "possible loss in precision"; others do not.
1 /*
2 * Test implicit type casting (TestImplicitTypeCast.c)
3 */
4 #include <stdio.h>
5
6 int main() {
7 int i;
8 double d;
9
10 i = 3;
11 d = i; // Assign an int value to double
12 printf("d = %lf\n", d); // d = 3.0
13
14 d = 5.5;
15 i = d; // Assign a double value to int
16 printf("i = %d\n", i); // i = 5 (truncated, no warning!)
17
18 i = 6.6; // Assign a double literal to int
19 printf("i = %d\n", i); // i = 6 (truncated, no warning!)
20 }
C will not perform automatic type conversion, if the two types are not compatible.
Explicit Type-Casting
You can explicitly perform type-casting via the so-called unary type-casting operator in the form of (new-type)operand. The type-casting operator takes one
operand in the particular type, and returns an equivalent value in the new type. Take note that it is an operation that yields a resultant value, similar to an addition
operation although addition involves two operands. For example,
Example: Suppose that you want to find the average (in double) of the integers between 1 and 100. Study the following codes:
1 /*
2 * Testing Explicit Type Cast (Average1to100.c).
3 */
4 #include <stdio.h>
5
6 int main() {
7 int sum = 0;
8 double average;
9 int number = 1;
10 while (number <= 100) {
11 sum += number; // Final sum is int 5050
12 ++number;
13 }
14 average = sum / 100; // Won't work (average = 50.0 instead of 50.5)
15 printf("Average is %lf\n", average); // Average is 50.0
16 return 0;
17 }
You don't get the fractional part although the average is a double. This is because both the sum and 100 are int. The result of division is an int, which is then
implicitly casted to double and assign to the double variable average. To get the correct answer, you can do either:
average = (double)sum / 100; // Cast sum from int to double before division
average = sum / (double)100; // Cast 100 from int to double before division
average = sum / 100.0;
average = (double)(sum / 100); // Won't work. why?
Example:
1 /*
2 * Converting between Celsius and Fahrenheit (ConvertTemperature.c)
3 * Celsius = (5/9)(FahrenheitɃ32)
4 * Fahrenheit = (9/5)Celsius+32
5 */
6 #include <stdio.h>
7
8 int main() {
9 double celsius, fahrenheit;
10
11 printf("Enter the temperature in celsius: ");
12 scanf("%lf", &celsius);
13 fahrenheit = celsius * 9 / 5 + 32;
14 // 9/5*celsius + 32 gives wrong answer! Why?
15 printf("%.2lf degree C is %.2lf degree F\n", celsius, fahrenheit);
16
17 printf("Enter the temperature in fahrenheit: ");
18 scanf("%lf", &fahrenheit);
19 celsius = (fahrenheit - 32) * 5 / 9;
20 // 5/9*(fahrenheit - 32) gives wrong answer! Why?
21 printf("%.2lf degree F is %.2lf degree C\n", fahrenheit, celsius);
22 return 0;
23 }
Each comparison operation involves two operands, e.g., x <= 100. It is invalid to write 1 < x < 100 in programming. Instead, you need to break out the two
comparison operations x > 1, x < 100, and join with with a logical AND operator, i.e., (x > 1) && (x < 100), where && denotes AND operator.
false true
Example:
Exercise: Given the year, month (1-12), and day (1-31), write a boolean expression which returns true for dates before October 15, 1582 (Gregorian calendar
cut over date).
Ans: (year < 1582) || (year == 1582 && month < 10) || (year == 1582 && month == 10 && day < 15)
5. 5. Flow Control
There are three basic flow control constructs - sequential, conditional (or decision), and loop (or iteration), as illustrated below.
"switch-case" is an alternative to the "nested-if". In a switch-case statement, a break statement is needed for each of the cases. If break is missing, execution
will flow through the following case. You can use either an int or char variable as the case-selector.
Conditional Operator: A conditional operator is a ternary (3-operand) operator, in the form of booleanExpr ? trueExpr : falseExpr. Depending on
the booleanExpr, it evaluates and returns the value of trueExpr or falseExpr.
Syntax Example
Braces: You could omit the braces { }, if there is only one statement inside the block. For example,
However, I recommend that you keep the braces, even though there is only one statement in the block, to improve the readability of your program.
Exercises
[TODO]
The difference between while-do and do-while lies in the order of the body and condition. In while-do, the condition is tested first. The body will be executed if the
condition is true and the process repeats. In do-while, the body is executed and then the condition is tested. Take note that the body of do-while will be executed
at least once (vs. possibly zero for while-do).
Suppose that your program prompts user for a number between 1 to 10, and checks for valid input, do-while with a boolean flag could be more appropriate.
// Game loop
bool gameOver = false;
while (!gameOver) {
// play the game
......
// Update the game state
// Set gameOver to true if appropriate to exit the game loop
......
}
Example (Counter-Controlled Loop): Prompt user for an upperbound. Sum the integers from 1 to a given upperbound and compute its average.
1 /*
2 * Sum from 1 to a given upperbound and compute their average (SumNumbers.c)
3 */
4 #include <stdio.h>
5
6 int main() {
7 int sum = 0; // Store the accumulated sum
8 int upperbound;
9
10 printf("Enter the upperbound: ");
11 scanf("%d", &upperbound);
12
13 // Sum from 1 to the upperbound
14 int number;
15 for (number = 1; number <= upperbound; ++number) {
16 sum += number;
17 }
18 printf("Sum is %d\n", sum);
19 printf("Average is %.2lf\n", (double)sum / upperbound);
20
21 // Sum only the odd numbers
22 int count = 0; // counts of odd numbers
23 sum = 0; // reset sum
24 for (number = 1; number <= upperbound; number = number + 2) {
25 ++count;
26 sum += number;
27 }
28 printf("Sum of odd numbers is %d\n", sum);
29 printf("Average is %.2lf\n", (double)sum / count);
30 }
Example (Sentinel-Controlled Loop): Prompt user for positive integers, and display the count, maximum, minimum and average. Terminate when user
enters -1.
1 /* Prompt user for positive integers and display the count, maximum,
2 minimum and average. Terminate the input with -1 (StatNumbers.c) */
3 #include <stdio.h>
4 #include <limits.h> // for INT_MAX
5
6 int main() {
7 int numberIn = 0; // input number (positive integer)
8 int count = 0; // count of inputs, init to 0
9 int sum = 0; // sum of inputs, init to 0
10 int max = 0; // max of inputs, init to minimum
11 int min = INT_MAX; // min of inputs, init to maximum (need <climits>)
12 int sentinel = -1; // Input terminating value
13
14 // Read Inputs until sentinel encountered
15 printf("Enter a positive integer or %d to exit: ", sentinel);
16 scanf("%d", &numberIn);
17 while (numberIn != sentinel) {
18 // Check input for positive integer
19 if (numberIn > 0) {
20 ++count;
21 sum += numberIn;
22 if (max < numberIn) max = numberIn;
23 if (min > numberIn) min = numberIn;
24 } else {
25 printf("error: input must be positive! try again...\n");
26 }
27 printf("Enter a positive integer or %d to exit: ", sentinel);
28 scanf("%d", &numberIn);
29 }
30
31 // Print result
32 printf("\n");
33 printf("Count is %d\n", count);
34 if (count > 0) {
35 printf("Maximum is %d\n", max);
36 printf("Minimum is %d\n", min);
37 printf("Average is %.2lf\n", (double)sum / count);
38 }
39 }
Program Notes
In computing, a sentinel value is a special value that indicates the end of data (e.g., a negative value to end a sequence of positive value, end-of-file, null
character in the null-terminated string). In this example, we use -1 as the sentinel value to indicate the end of inputs, which is a sequence of positive integers.
Instead of hardcoding the value of -1, we use a variable called sentinel for flexibility and ease-of-maintenance.
Take note of the while-loop pattern in reading the inputs. In this pattern, you need to repeat the prompting and input statement.
Exercises
[TODO]
The continue statement aborts the current iteration and continue to the next iteration of the current (innermost) loop.
break and continue are poor structures as they are hard to read and hard to follow. Use them only if absolutely necessary. You can always write the same
program without using break and continue.
Example (break): The following program lists the non-prime numbers between 2 and an upperbound.
1 /*
2 * List non-prime from 1 to an upperbound (NonPrimeList.c).
3 */
4 #include <stdio.h>
5 #include <math.h>
6
7 int main() {
8 int upperbound, number, maxFactor, factor;
9 printf("Enter the upperbound: ");
10 scanf("%d", &upperbound);
11 for (number = 2; number <= upperbound; ++number) {
12 // Not a prime, if there is a factor between 2 and sqrt(number)
13 maxFactor = (int)sqrt(number);
14 for (factor = 2; factor <= maxFactor; ++factor) {
15 if (number % factor == 0) { // Factor?
16 printf("%d ", number);
17 break; // A factor found, no need to search for more factors
18 }
19 }
20 }
21 printf("\n");
22 return 0;
23 }
Let's rewrite the above program without using break statement. A while loop is used (which is controlled by the boolean flag) instead of for loop with break.
1 /*
2 * List primes from 1 to an upperbound (PrimeList.c).
3 */
4 #include <stdio.h>
5 #include <math.h>
6
7 int main() {
8 int upperbound, number, maxFactor, isPrime, factor;
9 printf("Enter the upperbound: ");
10 scanf("%d", &upperbound);
11
12 for (number = 2; number <= upperbound; ++number) {
13 // Not prime, if there is a factor between 2 and sqrt of number
14 maxFactor = (int)sqrt(number);
15 isPrime = 1;
16 factor = 2;
17 while (isPrime && factor <= maxFactor) {
18 if (number % factor == 0) { // Factor of number?
19 isPrime = 0;
20 }
21 ++factor;
22 }
23 if (isPrime) printf("%d ", number);
24 }
25 printf("\n");
26 return 0;
27 }
Example (continue):
exit(): You could invoke the function exit(int exitCode), in <stdlib.h>, to terminate the program and return the control to the Operating System. By
convention, return code of zero indicates normal termination; while a non-zero exitCode (-1) indicates abnormal termination. For example,
abort(): The header <stdlib.h> also provide a function called abort(), which can be used to terminate the program abnormally.
The "return" Statement: You could also use a "return returnValue" statement in the main() function to terminate the program and return control
back to the Operating System. For example,
int main() {
...
if (errorCount > 10) {
printf("too many errors\n");
return -1; // Terminate and return control to OS from main()
}
...
}
Try out the following program, which prints a 8-by-8 checker box pattern using nested loops, as follows:
# # # # # # # #
# # # # # # # #
# # # # # # # #
# # # # # # # #
# # # # # # # #
# # # # # # # #
# # # # # # # #
# # # # # # # #
1 /*
2 * Print square pattern (PrintSquarePattern.c).
3 */
4 #include <stdio.h>
5
6 int main() {
7 int size = 8, row, col;
8 for (row = 1; row <= size; ++row) { // Outer loop to print all the rows
9 for (col = 1; col <= size; ++col) { // Inner loop to print all the columns of each row
10 printf("# ");
11 }
12 printf("\n"); // A row ended, bring the cursor to the next line
13 }
14
15 return 0;
16 }
This program contains two nested for-loops. The inner loop is used to print a row of eight "# ", which is followed by printing a newline. The outer loop repeats the
inner loop to print all the rows.
Suppose that you want to print this pattern instead (in program called PrintCheckerPattern.cpp):
# # # # # # # #
# # # # # # # #
# # # # # # # #
# # # # # # # #
# # # # # # # #
# # # # # # # #
# # # # # # # #
# # # # # # # #
You need to print an additional space for even-number rows. You could do so by adding the following statement before Line 8.
Exercises
1. Print these patterns using nested loop (in a program called PrintPattern1x). Use a variable called size for the size of the pattern and try out various
sizes. You should use as few printf() statements as possible.
# * # * # * # * # # # # # # # # # # # # # # # # 1 1
# * # * # * # * # # # # # # # # # # # # # # 2 1 1 2
# * # * # * # * # # # # # # # # # # # # 3 2 1 1 2 3
# * # * # * # * # # # # # # # # # # 4 3 2 1 1 2 3 4
# * # * # * # * # # # # # # # # 5 4 3 2 1 1 2 3 4 5
# * # * # * # * # # # # # # 6 5 4 3 2 1 1 2 3 4 5 6
# * # * # * # * # # # # 7 6 5 4 3 2 1 1 2 3 4 5 6 7
# * # * # * # * # # 8 7 6 5 4 3 2 1 1 2 3 4 5 6 7 8
(a) (b) (c) (d) (e)
Hints:
The equations for major and opposite diagonals are row = col and row + col = size + 1. Decide on what to print above and below the diagonal.
2. Print the timetable of 1 to 9, as follows, using nested loop. (Hints: you need to use an if-else statement to check whether the product is single-digit or
double-digit, and print an additional space if needed.)
1 2 3 4 5 6 7 8 9
2 4 6 8 10 12 14 16 18
......
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# # # # # # # # # #
# # # # # # # # # #
# # # # # # # #
# # # # # # # # # #
# # # # # # # # # #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
(a) (b) (c) (d) (e)
if (i == 0)
if (j == 0)
printf("i and j are zero\n");
else printf("i is not zero\n"); // intend for the outer-if
The else clause in the above codes is syntactically applicable to both the outer-if and the inner-if. The C compiler always associate the else clause with the
innermost if (i.e., the nearest if). Dangling else can be resolved by applying explicit parentheses. The above codes are logically incorrect and require explicit
parentheses as shown below.
if ( i == 0) {
if (j == 0) printf("i and j are zero\n");
} else {
printf("i is not zero\n"); // non-ambiguous for outer-if
}
is commonly used. It seems to be an endless loop (or infinite loop), but it is usually terminated via a break or return statement inside the loop body. This kind
of code is hard to read - avoid if possible by re-writing the condition.
Provide comments to explain the important as well as salient concepts. Comment your codes liberally.
Write your program documentation while writing your programs.
Avoid un-structured constructs, such as break and continue, which are hard to follow.
Use "mono-space" fonts (such as Consola, Courier New, Courier) for writing/displaying your program.
Programming Errors
There are generally three classes of programming errors:
1. Compilation Error (or Syntax Error): can be fixed easily.
2. Runtime Error: program halts pre-maturely without producing the results - can also be fixed easily.
3. Logical Error: program completes but produces incorrect results. It is easy to detect if the program always produces wrong result. It is extremely hard to fix if
the program produces the correct result most of the times, but incorrect result sometimes. For example,
This kind of errors is very serious if it is not caught before production. Writing good programs helps in minimizing and detecting these errors. A good testing
strategy is needed to ascertain the correctness of the program. Software testing is an advanced topics which is beyond our current scope.
Debugging Programs
Here are the common debugging techniques:
1. Stare at the screen! Unfortunately, errors usually won't pop-up even if you stare at it extremely hard.
2. Study the error messages! Do not close the console when error occurs and pretending that everything is fine. This helps most of the times.
3. Insert print statements at appropriate locations to display the intermediate results. It works for simple toy program, but it is neither effective nor efficient for
complex program.
4. Use a graphic debugger. This is the most effective means. Trace program execution step-by-step and watch the value of variables and outputs.
5. Advanced tools such as profiler (needed for checking memory leak and function usage).
6. Proper program testing to wipe out the logical errors.
7. 7. Arrays
An array is a list of elements of the same type, identified by a pair of square brackets [ ]. To use an array, you need to declare the array with 3 things: a name, a
type and a dimension (or size, or length). The syntax is:
type arrayName[arraylength];
I recommend using a plural name for array, e.g., marks, rows, numbers. For example,
int size;
printf("Enter the length of the array: ");
scanf("%d", size);
float values[size];
Take note that, in C, the value of the elements are undefined after declaration.
You can also initialize the array during declaration with a comma-separated list of values, as follows:
You can refer to an element of an array via an index (or subscript) enclosed within the square bracket [ ]. C's array index begins with zero. For example, suppose
that marks is an int array of 5 elements, then the 5 elements are: marks[0], marks[1], marks[2], marks[3], and marks[4].
You can find the array length using expression sizeof(arrayName)/sizeof(arrayName[0]), where sizeof(arrayName) returns the total bytes of the
array and sizeof(arrayName[0]) returns the bytes of first element.
C does not perform array index-bound check. In other words, if the index is beyond the array's bounds, it does not issue a warning/error. For example,
This is another pitfall of C. Checking the index bound consumes computation power and depicts the performance. However, it is better to be safe than fast. Newer
programming languages such as Java/C# performs array index bound check.
1 /*
2 * Find the mean and standard deviation of numbers kept in an array (MeanStdArray.c).
3 */
4 #include <stdio.h>
5 #include <math.h>
6 #define SIZE 7
7
8 int main() {
9 int marks[] = {74, 43, 58, 60, 90, 64, 70};
10 int sum = 0;
11 int sumSq = 0;
12 double mean, stdDev;
13 int i;
14 for (i = 0; i < SIZE; ++i) {
15 sum += marks[i];
16 sumSq += marks[i] * marks[i];
17 }
18 mean = (double)sum/SIZE;
19 printf("Mean is %.2lf\n", mean);
20
21 stdDev = sqrt((double)sumSq/SIZE - mean*mean);
22 printf("Std dev is %.2lf\n", stdDev);
23
24 return 0;
25 }
Exercises
[TODO]
Example
1 /* Test Multi-dimensional Array (Test2DArray.c) */
2 #include <stdio.h>
3 void printArray(const int[][3], int);
4
5 int main() {
6 int myArray[][3] = {{8, 2, 4}, {7, 5, 2}}; // 2x3 initialized
7 // Only the first index can be omitted and implied
8 printArray(myArray, 2);
9 return 0;
10 }
11
12 // Print the contents of rows-by-3 array (columns is fixed)
13 void printArray(const int array[][3], int rows) {
14 int i, j;
15 for (i = 0; i < rows; ++i) {
16 for (j = 0; j < 3; ++j) {
17 printf("%d ", array[i][j]);
18 }
19 printf("\n");
20 }
21 }
8. 8. Functions
Two parties are involved in using a function: a caller who calls the function, and the function called. The caller passes argument(s) to the function. The function
receives these argument(s), performs the programmed operations within the function's body, and returns a piece of result back to the caller.
area 1 is 3.63
area 2 is 14.52
area 3 is 32.67
In the above example, a reusable function called getArea() is defined, which receives a parameter (in double) from the caller, performs the calculation, and
return a piece of result (in double) to the caller. In the main(), we invoke getArea() functions thrice, each time with a different parameter.
In C, you need to declare a function prototype (before the function is used), and provide a function definition, with a body containing the programmed operations.
Function Definition
The syntax for function definition is as follows:
The parameterList consists of comma-separated parameter-type and parameter-name, i.e., param-1-type param-1-name, param-2-type param-2-
name,...
The returnValueType specifies the type of the return value, such as int or double. An special return type called void can be used to denote that the function
returns no value. In C, a function is allowed to return one value or no value (void). It cannot return multiple values. [C does not allow you to return an array!]
Inside the function's body, you could use a return statement to return a value (of the returnValueType declared in the function's header) and pass the control
back to the caller. The syntax is:
Take note that invoking a function (by the caller) transfers the control to the function. The return statement in the function transfers the control back to the
caller.
Function Prototype
In C, a function must be declared before it can be called. It can be achieved by either placing the function definition before it is being used, or declare a so-called
function prototype.
A function prototype tells the compiler the function's interface, i.e., the return-type, function name, and the parameter type list (the number and type of
parameters). The function can now be defined anywhere in the file. For example,
You could optionally include the parameter names in the function prototype. The names will be ignored by the compiler, but serve as documentation. For example,
// Function Prototype
double getArea(double radius); // parameter names are ignored, but serve as documentation
int max(int number1, int number2);
Function prototypes are usually grouped together and placed in a so-called header file. The header file can be included in many programs. We will discuss header
file later.
Another Example
We have a function called max(int, int), which takes two int and return their maximum. We invoke the max() function from the main().
1 /* Testing max function (TestMaxFunction.c) */
2 #include <stdio.h>
3
4 int maximum(int, int); // Function prototype (declaration)
5
6 int main() {
7 printf("%d\n", maximum(5, 8)); // Call maximum() with literals
8
9 int a = 6, b = 9, c;
10 c = maximum(a, b); // Call maximum() with variables
11 printf("%d\n", c);
12
13 printf("%d\n", maximum(c, 99)); // Call maximum()
14 }
15
16 // Function definition
17 // A function that returns the maximum of two given int
18 int maximum(int num1, int num2) {
19 return (num1 > num2) ? num1 : num2;
20 }
Suppose that you need a function to perform certain actions (e.g., printing) without a need to return a value to the caller, you can declare its return-value type as
void. In the function's body, you could use a "return;" statement without a return value to return control to the caller. In this case, the return statement is
optional. If there is no return statement, the entire body will be executed, and control returns to the caller at the end of the body.
In the above example, the variable (double radius) declared in the signature of getArea(double radius) is known as formal parameter. Its scope is
within the function's body. When the function is invoked by a caller, the caller must supply so-called actual parameters (or arguments), whose value is then used
for the actual computation. For example, when the function is invoked via "area1 = getArea(radius1)", radius1 is the actual parameter, with a value of
1.1.
Boolean Functions
A boolean function returns a int value of either 0 or not 0 to the caller.
Suppose that we wish to write a function called isOdd() to check if a given number is odd.
1 /*
2 * Test Boolean function (BooleanfunctionTest.c).
3 */
4 #include <stdio.h>
5
6 // Function Prototype
7 int isOdd(int);
8
9 int main() {
10 printf("%d\n", isOdd(5)); // 1 (true)
11 printf("%d\n", isOdd(6)); // 0 (false)
12 printf("%d\n", isOdd(-5)); // 0 (false)
13 }
14
15 int isOdd(int number) {
16 if (number % 2 == 1) {
17 return 1;
18 } else {
19 return 0;
20 }
21 }
This seemingly correct codes produces false for -5, because -5%2 is -1 instead of 1. You may rewrite the condition:
The above code produces the correct answer, but is poor. For boolean function, you should simply return the resultant value of the comparison, instead of using a
conditional statement, as follow:
int isEven(int number) {
return (number % 2 == 0);
}
int main() {
int number = -9;
if (isEven(number)) { // Don't write (isEven(number) != 0)
printf("Even\n");
}
if (isOdd(number)) { // Don't write (isOdd(number) != 0)
printf("Odd\n");
}
}
For example,
Pass-by-Value
In pass-by-value, a "copy" of argument is created and passed into the function. The invoked function works on the "clone", and cannot modify the original copy. In
C, fundamental types (such as int and double) are passed by value. That is, you cannot modify caller's value inside the function - there is no side effect.
Pass-by-Reference
On the other hand, in pass-by-reference, a reference of the caller's variable is passed into the function. In other words, the invoked function works on the same
data. If the invoked function modifies the parameter, the same caller's copy will be modified as well.
In C, arrays are passed by reference. That is, you can modify the contents of the caller's array inside the invoked function - there could be side effect in passing
arrays into function.
C does not allow functions to return an array. Hence, if you wish to write a function that modifies the contents of an array (e.g., sorting the elements of an array),
you need to rely on pass-by-reference to work on the same copy inside and outside the function. Recall that in pass-by-value, the invoked function works on a
clone copy and has no way to modify the original copy.
Array is passed into function by reference. That is, the invoked function works on the same copy of the array as the caller. Hence, changes of array inside the
function is reflected outside the function (i.e., side effect).
1 /* Search an array for the given key using Linear Search (LinearSearch.c) */
2 #include <stdio.h>
3
4 int linearSearch(const int a[], int size, int key);
5
6 int main() {
7 const int SIZE = 8;
8 int a1[] = {8, 4, 5, 3, 2, 9, 4, 1};
9
10 printf("%d\n", linearSearch(a1, SIZE, 8)); // 0
11 printf("%d\n", linearSearch(a1, SIZE, 4)); // 1
12 printf("%d\n", linearSearch(a1, SIZE, 99)); // 8 (not found)
13 }
14
15 // Search the array for the given key
16 // If found, return array index [0, size-1]; otherwise, return size
17 int linearSearch(const int a[], int size, int key) {
18 int i;
19 for (i = 0; i < size; ++i) {
20 if (a[i] == key) return i;
21 }
22 return size;
23 }
Program Notes:
[TODO]
{8,4,5,3,2,9,4,1}
PASS 1 ...
{8,4,5,3,2,9,4,1} => {4,8,5,3,2,9,4,1}
{4,8,5,3,2,9,4,1} => {4,5,8,3,2,9,4,1}
{4,5,8,3,2,9,4,1} => {4,5,3,8,2,9,4,1}
{4,5,3,8,2,9,4,1} => {4,5,3,2,8,9,4,1}
{4,5,3,2,8,9,4,1} => {4,5,3,2,8,4,9,1}
{4,5,3,2,8,4,9,1} => {4,5,3,2,8,4,1,9}
PASS 2 ...
{4,5,3,2,8,4,1,9} => {4,3,5,2,8,4,1,9}
{4,3,5,2,8,4,1,9} => {4,3,2,5,8,4,1,9}
{4,3,2,5,8,4,1,9} => {4,3,2,5,4,8,1,9}
{4,3,2,5,4,8,1,9} => {4,3,2,5,4,1,8,9}
PASS 3 ...
{4,3,2,5,4,1,8,9} => {3,4,2,5,4,1,8,9}
{3,4,2,5,4,1,8,9} => {3,2,4,5,4,1,8,9}
{3,2,4,5,4,1,8,9} => {3,2,4,4,5,1,8,9}
{3,2,4,4,5,1,8,9} => {3,2,4,4,1,5,8,9}
PASS 4 ...
{3,2,4,4,1,5,8,9} => {2,3,4,4,1,5,8,9}
{2,3,4,4,1,5,8,9} => {2,3,4,1,4,5,8,9}
PASS 5 ...
{2,3,4,1,4,5,8,9} => {2,3,1,4,4,5,8,9}
PASS 6 ...
{2,3,1,4,4,5,8,9} => {2,1,3,4,4,5,8,9}
PASS 7 ...
{2,1,3,4,4,5,8,9} => {1,2,3,4,4,5,8,9}
PASS 8 ...
{1,2,3,4,4,5,8,9}
Program Notes:
[TODO]
{8,4,5,3,2,9,4,1}
{8} {4,5,3,2,9,4,1}
{4,8} {5,3,2,9,4,1}
{4,5,8} {3,2,9,4,1}
{3,4,5,8} {2,9,4,1}
{2,3,4,5,8} {9,4,1}
{2,3,4,5,8,9} {4,1}
{2,3,4,4,5,8,9} {1}
{1,2,3,4,4,5,8,9}
Program Notes:
[TODO]
{8,4,5,3,2,9,4,1}
{} {8,4,5,3,2,9,4,1} => {} {1,4,5,3,2,9,4,8}
{1} {4,5,3,2,9,4,8} => {1} {2,5,3,4,9,4,8}
{1,2} {5,3,4,9,4,8} => {1,2} {3,5,4,9,4,8}
{1,2,3} {5,4,9,4,8} => {1,2,3} {4,5,9,4,8}
{1,2,3,4} {5,9,4,8} => {1,2,3,4} {4,9,5,8}
{1,2,3,4,4} {9,5,8} => {1,2,3,4,4} {5,9,8}
{1,2,3,4,4,5} {9,8} => {1,2,3,4,4,5} {8,9}
{1,2,3,4,4,5,8,9}
Program Notes:
[TODO]
You could also use const for fundamental-type function parameters (such as int, double) to prevent the parameters from being modified inside the function.
However, as fundamental-type parameters are passed by value (with a cloned copy), there will never be side effect on the caller. We typically do not use the const
keyword for fundamental types. In other words, const is used to indicate that there shall NOT be side-effect.
atan2(y, x):
Return arc-tan of y/x. Better than atan(x) for handling 90 degree.
ceil(x), floor(x):
returns the ceiling and floor integer of floating point number.
rand() generates the same squence of pseudo-random numbers on different invocations. The stblib.h also provides a srand() function to seed or initialize
the random number generator. We typically seed it with the current time obtained via time(0) function (in <time.h> header), which returns the number of
seconds since January 1st, 1970.
We shall test the rand()'s distribution by repeatedly throwing a 6-sided die and count the occurrences.
1: 333109 (16.66%)
2: 333113 (16.66%)
3: 333181 (16.66%)
4: 333562 (16.68%)
5: 333601 (16.68%)
6: 333434 (16.67%)
As seen from the output, rand() is fairly uniformly-distributed over [0, RAND_MAX].
Clearly, the length of array is the length of string plus 1, to account for the terminating null character '\0'.
You can use scanf() to input a string, and printf() to print a string, with %s conversion specifier. For example,
1 #include <stdio.h>
2 #include <string.h>
3
4 int main() {
5 char message[256];
6 // The length of char array shall be sufficient to hold the string
7 // plus the terminating null character '\0'
8 printf("Enter a message: ");
9 scanf("%s", message);
10 // Do not place an & before the array variable
11 printf("The message is: %s\n", message);
12 // Print up to but not including the terminating null character '\0'
13
14 // print each characters
15 int i;
16 for (i = 0; message[i] != '\0'; ++i) {
17 printf("'%c' ", message[i]);
18 }
19 printf("\n");
20
21 int len = strlen(message);
22 // Length of string does not include terminating null character '\0'
23 printf("The length of string is %d\n", len);
24 }
Take note that you need to allocate a char array that is big enough to hold the input string including the terminating null character '\0'.
int isupper(int c); [A-Z] Check if uppercase/lowercase and return true (non-zero) or false (0)
int islower(int c); [a-z]
int toupper(int c); To Uppercase Return the uppercase/lowercase character, if c is a lowercase/uppercase character; otherwise, return c.
int tolower(int c); To Lowercase
Example: [TODO]
Function Description
int atoi(const char * str); String to int Convert the str to
double atof(const char * str); String to double int/double/long/long long.
long atol(const char * str); String to long
long long atoll(const char * str); String to long long
double strtod(const char* str, char** endptr); String to double Convert the str to double/float.
float strtof(const char* str, char** endptr); String to float If endptr is not a null pointer,
it will be set to point to the first character after the
number.
long strtol(const char* str, char** endptr, int base); String to long Convert the str to long/unsigned long.
unsigned long strtoul(const char* str, char** endptr, int String to unsigned
base); long
Example: [TODO]
char* strcat(char* dest, const char* src); String concatenation Concatenate src into dest.
char* strncat(char* dest, const char* src, size_t String concatenation at most n-char Return dest.
n);
int strcmp(const char* s1, const char* s2); String compare Comparing s1 and s2.
int strncmp(const char* s1, const char* s2, size_t String compare at most n-char Return 0, less than 0, more than 0
n); if s1 is the same, less than, more than
s2.
int strlen(const char* str); String Length Return the length of str
(excluding terminating null char)
char* strchr(const char* str, int c); Search string for char Return a pointer to the first/last
char* strrchr(const char* str, int c); Search string for char reverse occurrence
of c in str
if present. Otherwise, return NULL.
char* strpbrk(const char* str, const char* Search string for char in pattern Locate the first occurrence in str
pattern); of any character in pattern.
char* strstr(const char* str, const char* substr); Search string for sub-string Return a pointer to the first occurrence
of substr in str
if present. Otherwise, return NULL.
char* strspn(const char* str, const char* substr); Search string for span of substr
char* strcspn(const char* str, const char* substr); Search string for complement span of
substr
char* strtok(char* str, char *delimit); Split string into tokens
void* memcpy(void *dest, const void *src, size_t Memory block copy
n); Memory block move
void* memmove(void *dest, const void *src, size_t Memory block compare
n); Search memory block for char
Memory block set (fill)
int memcmp(const void *p1, const void *p2, size_t
n);
void* memchr(void *ptr, int value, size_t n);
void* memset(void *ptr, int value, size_t n);
Example: [TODO]
int getc(FILE *stream); Get character (from FILE stream) Input/Output a character
int putc(int c, FILE *stream); Put character (to FILE stream) from FILE stream.
int ungetc(int c, FILE *stream); Un-get character (to FILE stream)
int sprintf(char *str, const char *format, ...); Formatted print (to string) Formatted string input/output.
int sscanf(char *str, const char *format, ....); Formatted scan (from string) Similar to printf() and scanf(),
except that the output/input comes from the str.
Example: [TODO]
int fprintf(FILE *stream, const char *format, ...); Formatted print to Formatted file input/output.
int fscanf(FILE *stream, const char *format, ...); file Similar to printf()/scanf(),
Formatted scan from except that the input/output comes from file
file
int fgetc(FILE *stream) Get character from Unformatted character/string input/output
int fputc(int c, FILE *stream); file from file
char* fgets(char *str, size_t n, FILE *stream); Put character to file
int fputs(const char *str, FILE *stream); Get string from file
Put string to file
size_t fread(void *ptr size_t size, size_t count, FILE *stream) File read Direct Access
size_t fwrite(const void *ptr, size_t size, size_t count, FILE File write
*stream); Get file position
int fgetpos(FILE *stream, fpos_t *pos); Set file position
File seek
int fsetpos(FILE *stream, const fpos_t *pos);
Tell file
int fseek(FILE *stream, long offset, int origin);
long ftell(FILE *stream);
void rewind(FILE *stream); Rewind file Set the file position to the beginning
Open/Close File
To open a file, use fopen(filename, mode). The modes are:
Mode Description
"r" Read Open file for reading. The file shall exist.
"w" Write Open file for writing. If the file does not exist, create a new file; otherwise, discard existing contents.
"a" Append Open file for writing. If the file does not exist, create a new file; otherwise, append to the existing file.
"r+" Read/Write Open file for reading/writing. The file shall exist.
"w+" Read/Write Open file for reading/writing. If the file does not exist, create a new file; otherwise, discard existing contents.
"a+" Read/Append Open file for reading/writing. If the file does not exist, create a new file; otherwise, append to the existing file.
File Stream
You can use stdin, stdout, stderr to denote standard input stream (keyboard), standard output stream (console) and standard error stream (console).
Example 2
1 #include <stdio.h>
2 #define SIZE 80 // size of string buffer
3
4 int main() {
5 FILE * pFile;
6 char buffer[SIZE];
7
8 pFile = fopen("test.txt" , "r");
9 if (pFile == NULL) {
10 perror("Error opening file");
11 } else {
12 while (!feof(pFile)) {
13 if (fgets(buffer, SIZE, pFile) == NULL) break;
14 fputs (buffer , stdout);
15 }
16 fclose(pFile);
17 }
18 return 0;
19 }
We can declare and initialize a string via char pointer; and operate the string via char pointer.
1 #include <stdio.h>
2
3 int main() {
4 char *msg = "hello"; // Append a terminating null character '\0'
5 char *p;
6
7 for (p = msg; *p != '\0'; ++p) {
8 printf("'%c' ", *p);
9 }
10 printf("\n");
11 }
[TODO]
<math.h>: contains function prototypes for mathematical functions (e.g., pow(), sqrt()).
<ctype.h>: (character type) contains function prototypes for testing character properties (isupper(), isalpha(), isspace()) and case conversion
(toupper(), tolower()).
<string.h>: contains function prototypes for string processing functions (e.g., strcpy(), strcat(), strcmp()).
<time.h>: contains function prototypes for date and time functions (e.g., time()).
<locale.h>:
<setjmp.h>:
Control: if, else, switch, case, break, default, for, do, while, continue, goto (11).
Feedback, comments, corrections, and errata can be sent to Chua Hock-Chuan (ehchua@ntu.edu.sg) | HOME