Skip to content

Programming by Design

If you're not prepared to be wrong, you'll never come up with anything original. – Sir Ken Robinson

  • About
  • Java-PbD
  • C-PbD
  • ASM-PbD
  • Algorithms
  • Other

Chapter 2 – Elements of the C Language

Posted on August 11, 2019January 20, 2025 By William Jojo
C book

(Updated November 21, 2024)

Table of contents

    Overview
    Components of the Language
    Data Types
    Using void and bool
    Arithmetic Operators and Precedence
    Constants, Literals, Variables, and const
    Strings
    Basic Input/Output
    Escape
    Increment, Decrement and Comments
    The main Function
    Quiz
    Exercises

Overview

This is an extensive chapter. Many components of the C programming language are presented throughout. Although there is a big quiz at the end to measure comprehension and retention, the best approach is to consume this chapter in small portions.

Take your time and remember that this is a 10,000-foot view of the language. Refer to this chapter often when determining the use of certain data types and operators. This chapter will always be here, so do not worry if it is not all sticking at once.

Remember that, as with anything new, we have to start somewhere. So, we begin with the fundamental components of the language.


Components of the Language

The C language is relatively small when you look at the parts that make up the core aspects. First, we will discuss the rules and the meaning of the language. Like any foreign language, some rules govern how the language may be written, and understanding or purpose is derived from how the words are arranged. For example, when we say, “The car belongs to Bill.” We know that the subject, car, and object, Bill, are arranged such that we can derive the meaning of Bill owning a vehicle. We can derive further meaning if there are surrounding sentences of a paragraph in which this original sentence also belongs. Perhaps we would learn that Bill’s car is large or small if it is gas, diesel, or even the color.

The point is every language has syntax rules that govern how the language may be written. The semantic rules help us to determine the meaning. C is broken down into three basic parts, or tokens, for writing the language. These tokens are symbols, reserved words and identifiers.

Objects are a region of memory containing a specific value viewed as having a particular type. The use of the term object is directly from the ISO/IEC 9899:2018 standard (loosely interpreted here) and is not intended to imply object-oriented programming.

Symbols are the characters we use in our writing regardless of the subject. So punctuation, arithmetic, and relational symbols are used throughout the program. Table 1 lists some categories of symbols. Note that regardless of the number of characters used to make the symbol, it is considered one.

Puctuation . , ; “ !
Arithmetic + – * / %
Relational <= != > < ==

Table 1: Some symbols used in C.

Keywords, also called reserved words, are a standard set of words that are set aside expressly for the C language. You may not reuse these words as identifiers. If you do, the compiler will inform you that using the keyword is inappropriate by signaling a syntax error when you compile your program. When using an IDE, you will also know that the word is reserved when the word you are using has a different color from those that are identifiers. Keywords are listed in Table 2.

auto break case char
const continue default do
double else enum extern
float for goto if
inline (since C99) int long register
restrict (since C99) return short signed
sizeof static struct switch
typedef union unsigned void
volatile while _Alignas (since C11) _Alignof (since C11)
_Atomic (since C11) _Bool (since C99) _Complex (since C99) _Generic (since C11)
_Imaginary (since C99) _Noreturn (since C11) _Static_assert (since C11) _Thread_local (since C11)

Table 2: C keywords.

Identifiers are used to label things. These things are often variables, functions, or constants. Like the rest of the C language, identifiers are case-sensitive. The identifiers you create may contain letters, digits, and the underscore character (_).

Your identifiers must not begin with a digit. They can start with any of the other characters. It is conventional to limit the use of the underscore for special-purpose identifiers or to use the underscore where spaces would ordinarily appear in everyday language.

The C standard defines an upper bound on the length of an identifier as 63 significant initial characters. As such, you should endeavor to keep the length of your identifiers within this range.

A few acceptable identifiers are shown in Table 3.

sum totalWords Sum
test1 Word_Count myLittleVariable

Table 3: Examples of unique and valid identifiers.


Data Types

There are several basic data types in C. There are integer types, real (floating point), and character types. These are loose classifications. This is because several modifiers may change the default behavior of a given data type.

The rudimentary set of types are int, float, double and char.

The set of modifiers are short, long, signed and unsigned.

Given these types and modifiers, we can derive a stunning set of various sizes and styles of the same root data type. This can also lead to some confusion. If signed is omitted and unsigned is also omitted, and the type is not char, then the data type is assumed to be signed.

Further, there is also accepted shorthand. So short, short int, and signed short int are all the same.

The types and properties list are displayed in Table 4.

Data Type Range of values Size in bytes (bits)
char -127 to 127 or 0 to 255 1 (8)
signed char -127 to 127 1 (8)
unsigned char 0 to 255 1 (8)
short
short int
signed short
signed short int
-32767 to 32767 2 (16)
unsigned short
unsigned short int
0 to 65535 2 (16)
int
signed
signed int
-32767 to 32767 2 (16)
unsigned
unsigned int
0 to 65535 2 (16)
long
long int
signed long
signed long int
-2147483647 to 2147483647 4 (32)
unsigned long
unsigned long int
0 to 4294967295 4 (32)
long long
long long int
signed long long
signed long long int
-9223372036854775807
to 9223372036854775807
8 (64)
unsigned long long
unsigned long long int
0 to 18446744073709551615 8 (64)

Table 4: Values and sizes of integer data types.

The only integral data type with special written properties is char. The values representing this type use single quotes to denote that value as a char literal. A few examples of these literals are ‘+’, ‘A’, ‘(‘ and ‘n’. Without the single quotes, the plus symbol may be mistaken for an arithmetic operator, the open parenthesis as part of the C language, or the letters mistaken for identifiers.

Characters are assumed to be in the 127-bit set of ASCII characters. Characters are numeric quantities, and the characters we use with single quotes are human consumable forms, so we do not need to memorize the entire character set. The table of values to characters is standardized and represents a collating sequence for several character ranges.

Important Note!
There are two classic collating sequences. ASCII, pronounced as’-kee (American Standard Code for Information Interchange) is used on traditional PCs, minicomputers, and such. The other is EBCDIC, pronounced eb’-sid-ick (Extended Binary Coded Decimal Interchange Code), which is used in mainframes.

Given the use of the ASCII collating sequence, it is important to recognize the significance of the groupings of characters therein. Three starting points need to be mentioned. The digit ‘0’ (48), capital ‘A’ (65) and lower case ‘a’ (97). Following ‘0’ is the digit ‘1’, then ‘2’ and so on. The same goes for the two starting points for the alphabet. If ‘A’ is 65, then ‘B’ is 66 and ‘C’ is 67.

The ASCII collating sequence is shown in Table 5.


Table 5: The ASCII Collating sequence.

There are a few real data types, and their properties are displayed in Table 6.

Data type Range of values Max Significant Digits Size in bytes (bits)
float 1.17549435E-38F to 3.40282347E+38F 7 4 (32)
double 2.2250738585072014E-308 to 1.7976931348623157E+308 15 8 (64)
long double 3.362103e-4932 to 1.189731e+4932 19 10 (80)

Table 6: Properties of real data types.

Should you be interested in the ranges of values of your given platform, you can use the code in Example 1 to display the minimums and maximums of the current data types supported in the C17 standard.

dataTypes.c
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <float.h>

int main(void) {

  printf("CHARs\n");
  printf("CHAR_BIT    :   %d\n", CHAR_BIT);
  printf("CHAR_MAX    :   %d\n", CHAR_MAX);
  printf("CHAR_MIN    :   %d\n", CHAR_MIN);
  printf("SCHAR_MAX   :   %d\n", SCHAR_MAX);
  printf("SCHAR_MIN   :   %d\n", SCHAR_MIN);
  printf("UCHAR_MAX   :   %d\n", UCHAR_MAX);
  
  printf("\nINTs\n");
  printf("SHRT_MAX    :   %d\n", SHRT_MAX);
  printf("SHRT_MIN    :   %d\n", SHRT_MIN);
  printf("USHRT_MAX   :   %d\n", (unsigned short) USHRT_MAX);
  printf("INT_MAX     :   %d\n", INT_MAX);
  printf("INT_MIN     :   %d\n", INT_MIN);
  printf("UINT_MAX    :   %u\n", (unsigned int) UINT_MAX);
  printf("LONG_MAX    :   %ld\n", (long) LONG_MAX);
  printf("LONG_MIN    :   %ld\n", (long) LONG_MIN);
  printf("ULONG_MAX   :   %lu\n", (unsigned long) ULONG_MAX);
  printf("LLONG_MAX   :   %lld\n", (long long) LLONG_MAX);
  printf("LLONG_MIN   :   %lld\n", (long long) LLONG_MIN);
  printf("ULLONG_MAX  :   %llu\n", (unsigned long long) ULLONG_MAX);
  
  printf("\nFLOATs\n");
  printf("FLT_MAX     :   %e\n", FLT_MAX);
  printf("FLT_MIN     :   %e\n", FLT_MIN);
  printf("DBL_MAX     :   %e\n", DBL_MAX);
  printf("DBL_MIN     :   %e\n", DBL_MIN);
  printf("LDBL_MAX    :   %Le\n", LDBL_MAX);
  printf("LDBL_MIN    :   %Le\n", LDBL_MIN);
  
  return 0;
}

Example 1: Data type minimums and maximums.

Do not be alarmed that the printf‘s look strange and that you may not know where all of those names came from. If you copy the code to your editor or IDE and compile and run it, you will see your particular platform’s values for the different data types.

The output generated by a Macbook Pro running Apple clang version 12.0.0 (clang-1200.0.32.29) is shown below:

CHARs
CHAR_BIT    :   8
CHAR_MAX    :   127
CHAR_MIN    :   -128
SCHAR_MAX   :   127
SCHAR_MIN   :   -128
UCHAR_MAX   :   255

INTs
SHRT_MAX    :   32767
SHRT_MIN    :   -32768
USHRT_MAX   :   65535
INT_MAX     :   2147483647
INT_MIN     :   -2147483648
UINT_MAX    :   4294967295
LONG_MAX    :   9223372036854775807
LONG_MIN    :   -9223372036854775808
ULONG_MAX   :   18446744073709551615
LLONG_MAX   :   9223372036854775807
LLONG_MIN   :   -9223372036854775808
ULLONG_MAX  :   18446744073709551615

FLOATs
FLT_MAX     :   3.402823e+38
FLT_MIN     :   1.175494e-38
DBL_MAX     :   1.797693e+308
DBL_MIN     :   2.225074e-308
LDBL_MAX    :   1.189731e+4932
LDBL_MIN    :   3.362103e-4932

Using void and bool

The void data type represents an absence of any value. It indicates that no value is expected or should be provided wherever it is used. Any attempt to place a value where void has been defined at the type will result in an error.

The official type for Boolean representation in C is _Bool. That type and the bool macro was introduced in C99. It is, however, a macro. To use it, you must include the header file stdbool.h.

The bool macro is converted to _Bool, which is a reserved word. It has only two possible values. They are true and false, which are also macros that represent 1 and 0, respectively.

It is important to note that this header file defines macros that implement bool, true, and false. These are not keywords and could be redefined.

It is not required to use bool to represent a Boolean value. As we will see later, the use of conditional statements uses integer values to represent true and false.


Arithmetic Operators and Precedence

There are five arithmetic operators, as shown in Table 7. These operators are used to construct expressions. An expression is something simple like 3+2 or 8/5. So our expressions are made up of operators and operands. Operands are the values the operators work with. The previous examples are known as integer expressions. They are integer expressions because they have two integers as their operands, and the result will be an integer. Note that operands can be literals, identifiers, and other expressions.

Operator Purpose
+ addition
– subtraction
* multiplication
/ division
% modulus (remainder)

Table 7: Arithmetic operators.

The operators in Table 7 are also known as binary operators. Not because they work with binary numbers but because they require two operands. You are probably familiar with the unary operators – and +. These can be seen when talking about the value ‐3. See how the minus sign has only one operand? It says that the value of 3 is negative as opposed to positive.

Why is it necessary to talk about unary and binary operators? Consider the expression

x = 4 - -3

Some would look at this and say, “Syntax error!” Quite the contrary. It says four take away a negative three or four minus negative 3, which is the equivalent of 4 plus 3. The result is 7.

When multiple operators are mixed within the same expression, operator precedence is considered. Operator precedence allows us to know the order in which the operators will be applied to the values. Of course, we can alter the default precedence rules by simply using parentheses. The higher precedence operators, the ones nearest the top, are done first. When there are many arithmetic operators of the same precedence, the operators will be evaluated based on associativity.

Table 8 shows the complete C precedence order for operators. Precedence is higher toward the top. All operators are evaluated left to right except unary, ternary, and assignments which are evaluated right to left.

Operator Precedence (highest to lowest)              Associativity
postfix           () [] -> . ++ --                   left to right
unary             + - ! ~ ++ -- (type)* & sizeof     right to left
multiplicative    * / %                              left to right
additive          + -                                left to right
shift             << >>                              left to right
relational        < > <= >=                          left to right
equality          == !=                              left to right
bitwise AND       &                                  left to right
bitwise XOR       ^                                  left to right
bitwise OR        |                                  left to right
logical AND       &&                                 left to right
logical OR        ||                                 left to right
ternary 	  ? :                                right to left
assignment 	  = += -= *= /= %= &= ^= |= <<= >>=  right to left
comma             ,                                  left to right

Table 8: C precedence list.

Expressions With Mixed Types, Type Conversion, Widening and Narrowing of Primitive Types

When writing your programs, you will have values of varying types from varying sources. When this happens, we have to keep some simple rules in mind for how things will be evaluated and what the resultant type will be.

First, C is not a strongly typed language. To be strongly typed, each object must have a type, AND types must match on some level when applying operators. C is a statically typed language. This means that the types are known at compile-time, but the language and the compilers allow you to mix data types at will with no warnings about incompatible types or possible loss of precision. This is also known as implicit type conversion.

Important Note!
The terms widening and narrowing are not explicitly used in the ISO/IEC 9899:2018 standard except for Annex I. However, these terms are used within the programming world to indicate the nature of implicit type conversions.

Implicit type conversion is handled through widening and narrowing, which is described below.

Widening

Consider the fact that values can not only be copied between variables but also between types. C provides a mechanism for automatically narrowing or widening values copied between data types or when used in mixed expressions.

The widening of data types is best described using the chart below.

char → int → long → float → double → long double

Given any starting type, follow the arrows until you reach your destination type. Your smaller type will be converted to the more significant type through sign extension or zero extension for signed and unsigned values, respectively. When moving from a fixed point (integral) to a floating point, internal conversion is made, and the fractional portion is simply zero.

Widening is the default behavior when mixing data of differing types in a given expression.

Narrowing

The narrowing of the types involves shrinking the current type to fit into the new type. You must remain keenly aware that in doing so, there is a chance that you will lose data if the receiver type is sufficiently smaller than the source value.

The narrowing of data types is shown in the chart below.

long double → double → float → long → int → char

In the narrowing process, the more significant type will be converted to the smaller type by way of preserving low-order bits (i.e., long to short), preserving bit patterns (converting between signed and unsigned), or truncating at the decimal point (converting floating-point to integral). Converting a floating-point number to a sufficiently small integral type may undergo two narrowing steps: truncating at the decimal point and preserving low-order bits.

Cast

If we need to change a type temporarily for a specific and intentional purpose, we can use a cast or explicit type conversion. The cast acts as a temporary type of conversion. First, the simplest expression to the right of the cast is evaluated then the cast is applied. The following few examples show how to perform a cast.

C Code Example Result
(int)7.9 yields 7 since the double is converted to an int. (The value 7.9 is considered to be a double literal.)
(float)25/6 yields 4.1666665 since int 25 is converted to float and divided by the int 6 (see rule 2, above).
(float)(25/6) yields 4.0 since the two ints are divided, which yields no precision, and the 4 is converted to 4.0 (see rule 1, above).

Table 9: Examples of casting.

Now that we have discussed narrowing, widening, and casting at length, it is essential to mention the default types for constants. Since there are so many forms of basic types, you will find that Table 10 identifies what sort of values are allowed in different integer data types.

Suffix Example C Types Allowed (first fit)
No Suffix 35 int
long int
unsigned long int (before C99)
long long int (since C99)
U or u 427U unsigned int
unsigned long int
unsigned long long int (since C99)
L or l 519L long int
unsigned long int (before C99)
long long int (since C99)
l/L and u/U 519ul
4057LU
unsigned long int
unsigned long long int (since C99)
LL or ll 397534ll
287934LL
long long int (since C99)
ll/LL and u/U 397534ull
287934LLU
unsigned long long int (since C99)
f or F 6.022f float
No suffix 6.022 double
l or L 6.022L long double

Table 10: Examples of various basic types and suffixes.

Negative constants are treated as the unsigned value with the unary minus operator applied. This means that the compiler determines the best fit by using the unsigned representation of the constant value, then applies the sign.


Constants, Literals, Variables, and const

The term constant is being used as defined by the ISO/IEC 9899:2018 standard and means representing exact meaning whereby the intent is clear. When we say

x = 7;

We know the variable x contains the value 7 when the statement is complete. Or

c = 'A';

There is no doubt that capital A has been assigned to the variable c. We use the term constant for any value not expressed by acquisition from another source. Consider the following:

x = y;

In this example, the variable y already contains some value to be assigned to x. Some examples of constant values are shown below (see also Table 10, above):

Variables must be declared before you can use them, and they must be given a type. Here are a bunch of declared variables with a variety of constants being assigned to them:

int i = 35;
long l = 734L;    // The use of L or l denotes a long type.

int o = 013;      // octal for 11.
int x = 0x80;     // hexadecimal for 128.

float f = 3.14f;         // the f is used to denote a float type and avoid narrowing.
const double PI = 3.14;  // the value is a non-modifiable double.

char c = '#';       // single quotes for chars
Important Note!
There is a qualifier that was added to the variable PI. While the const type qualifier can have a complex definition, we are only concerned with the fact that any type that is qualified by const is not modifiable. You can initialize the variable, but no further modification can be made. The use of this qualifier will enlist the help of the compiler to make sure we do not accidentally change the variable in the future.

When it comes to strings, however, we use double quotes to show the literal value. This is called a string literal. The example below shows how to safely copy a string literal into the variable n.

char n[10];
snprintf(n, sizeof n, "%s", "Bill");  // double quotes for string literals
Important Note!
We are using snprintf() here because functions like strcpy() are considered unsafe and should not be used.

It is important to note that variables are not pre-initialized. The default value of a declared variable is undefined and not simply zero. Hence, we must endeavor to initialize variables that will be used in subsequent calculations (if they will not be filled by other means).

sum.c
#include <stdio.h>

int main(int argc, char **argv) {

  int sum, n;

  sum = 0;

  // Read a value from user into n...

  sum = sum + n;
}

Example 2: Initializing a variable with an integer constant for later use.

In Example 2, we have a somewhat incomplete program – we are not actually reading anything from the user. Details to note:

  • Line 5: The values of sum and n are undefined.
  • Line 7: We initialize sum to zero. This variable now has a reliable value.
  • Line 9: This is a comment to indicate something was read from the user, but that code is omitted for simplicity.
  • Line 11: We can use the value of sum and n in the expression because they both have valid values – it does not matter if the value is placed directly (Line 9) or indirectly through user input.

Failure to initialize variables before using them will result in an error from the compiler.

Consider the program in Example 3.

average.c
#include <stdio.h>
    
int main (int argc, char **argv) {
        
  int test1, test2, test3, sum;
  double average;

  test1 = 90;
  test2 = 85;
  test3 = 87;

  sum = test1 + test2 + test3;

  average = sum / 3.0;

  printf("The average is %f.\n", average);

}

Example 3: Program to calculate an average using variables, constants, and literals.

The output from this program is:

The average is 87.333333

There are several things to note about this program.

  • Line 5: Several variables of type int are declared on the same line by using commas to separate the names.
  • Line 14: The value contained in sum is divided by the double value 3.0, which causes a widening of the result to the double type which is then stored in average.
  • Line 16: The value contained in average is used to replace the %f in the printf statement (see printf in Chapter 3)
  • Line 16: The \n is used to move the cursor to the next line since printf does not advance to the next line by default.
  • The variable sum could be eliminated. Consider for a moment how you would rewrite the average calculation if you eliminated sum.

We will discuss the use of printf at length in the next chapter. However, we should mention the structure of printf since several previous examples have used it.

The first thing to note is that printf always has a string as the first argument. After that, there may be a comma-separated list of additional arguments of practically any type.

The printf from Example 3 is shown below:

  printf("The average is %f.\n", average);

We know that the \n provides the newline and moves the cursor to the next line. What is noteworthy is the %f. This is called a conversion. The conversions make printf so powerful. By using the %f, we are telling printf to save a spot where a floating point value will be placed later. By later, we mean at runtime – when the program gets executed by the user.

When the program executes, the value of average at that time will be substituted for the %f. It is that easy, and we will see more powerful tools in the next chapter.


Strings

The string type will be as valuable as the basic data types mentioned earlier. Strings represent data that a single char is incapable of expressing, such as a person’s name or street address of the name of a country.

Strings are exceedingly easy to represent. They use double quotes to denote the string literal, much like single quotes are used to indicate a char literal value. A few string literals are listed below.

"Bill"
"Route 2"
"United States"

The empty string or null string is represented as empty double quotes ("") and has a length of zero.

You will often need the library of string functions to help you manage strings. To use string functions successfully, you must first import <string.h>. String handling is quite different than in other languages and requires care.

In the following example, we will use the strlen function while we manage some strings.

printName.c
#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[]) {
  
  char name[30] = "William";
  
  printf("The size of name is %lu\n", sizeof name);
  printf("The value of name is %s\n", name);
  printf("The length of name is %lu.\n", strlen(name));

}

Example 4: Using a simple string.

The code in Example 4 shows a declared and initialized string called name.

Note
printf is using more conversions. In this example, it is %s (string) and %lu for long, unsigned value.

It is essential to know that in C, we always deal with different-sized values. Simply using %d instead of %lu has the potential to lose precision due to misrepresentation of the data. Fortunately, the compiler will provide warnings to inform you when this might occur.

Finally, we use the strlen function to count the number of characters in the string – which is also the index of the null character.

The output of Example 4 is shown below.

The size of name is 30
The value of name is William
The length of name is 7.

Strings and how to safely use them are covered in much greater detail in Chapter 3.


Basic Input/Output

We have seen a few examples of printf and the introduction of scanf. The truth is, there are many ways to display output. There are also many ways to get input from the user, and only a few are secure enough to be trusted today.

It is essential to review the chronology of the program in Example 3.

  1. The variables are declared.
  2. Values are assigned for the three test grades.
  3. The sum is calculated.
  4. The average is calculated and displayed.

It would be silly to put the output statement first; we have nothing to display. Remember the steps necessary to solve a problem when working through any solution. This example is rudimentary, and you can expect your programming projects to be more complex than this. Still, the premise is the same: determine what must happen first, then what must occur after that, and so on.

Important Tip!
When you begin to write your program, or even a bit before, try to sketch out the steps needed. Identify pieces of information you need, where they come from, and when you realize you have them. This will go a long way in shaping your programs by identifying what you have, what you need, and what you need to do with that knowledge once you realize it.

Consider what you would do if you had to read values from the user instead of assigning them directly using literals. The scanf function is a relatively simple way to read data from some input stream, including the user’s keyboard.

averageInteractive.c
#include <stdio.h>

int main(int argc, char **argv) {

  int test1, test2, test3;
  double average;

  printf("Enter test 1: ");
  scanf("%d", &test1);

  printf("Enter test 2: ");
  scanf("%d", &test2);

  printf("Enter test 3: ");
  scanf("%d", &test3);

  average = (test1 + test2 + test3) / 3.0;

  printf("The average is %f.\n", average);
}

Example 5: Program modified to read values from user.

The program in Example 5 is fundamentally the same as Example 3 with test data read from the user by the scanf function. A sample run with the user-provided values in bold is shown here:

Enter test 1: 90
Enter test 2: 85
Enter test 3: 87
The average is 87.333333.

There are some items to note about this program:

  1. The variable sum was eliminated.
  2. The scanf() function stores the input in the form of an integer in the address of the variable passed as the second argument.
  3. There is an alternation of prompt, read, prompt, read. This is necessary to inform the user what to do next.

A very important note about the program in Example 5 is the use of the address-of operator &. To use the scanf() function, we need to work with destinations that are addresses – memory locations. This is because we want scanf to store the requested information in a particular location in memory. That memory is also a named location, specifically the variable we intend to use later to reference the stored value. So, instead of passing test1, we have to pass the address of test1 or &test1.

The %d in scanf allows us to specify the type of data we wish to parse. The printf function does the same thing with what we wish to write.

Integers are not the only things we would want to read. What about characters, words, lines of text, and precision values? The next chapter explains the details of how that can be accomplished.

Important Note!
No matter how we intend to get data from the user or other sources, we must be mindful of today’s security landscape and best practices. For now, we are simply introducing the basics and saving the deeper discussion for subsequent chapters.

This and the next chapter are focused on providing essential tools to achieve I/O with the end-user. We acknowledge these will need review and refinement, but for the moment, you can get up and running now, and we will discuss the finer points and best practices later.

It is worth noting that the scanf function gets a bad reputation on nearly every online forum. There are issues with it. They are not wrong in this respect. But this book will help you navigate the correct way to use the tools that are still deemed safe.


Escape

Have you considered how we store the single quote character in a char variable if single quotes are used to define character literals? Or, how do we print a double quote character if double quotes define the boundaries of a string literal?

It is frequently necessary to print characters that are otherwise reserved for special use. This is quickly resolved with a series of escape sequences. Escape sequences are represented by a leading backslash (\) which changes the meaning of the next character.

Consider the following:

char squote;
char fiveBlankLines[] = "\n\n\n\n\n";

squote = '\'';

The character sequence \' escapes the single quote character making it simply a single quote and not part of the delimiting quotes for a character literal. The assignment statement stores a single quote character into the variable squote.

A similar concept is applied to the sequence \n, which makes the n special – this n represents the newline character. A list of escape sequences is shown in Table 8.

Escape Sequence Purpose
\n Newline. Moves the cursor to the beginning of the next line.
\t Tab. Moves cursor to next tab stop.
\\ Backslash. Represents a backslash.
\" Double quote. Represents a double quote within a string or character literal.
\' Single quote. Represents a single quote within a string or character literal.
\b Backspace. Move the cursor back one character.
\a Audible bell.
\r Return. Move the cursor to the beginning of the current line.
\0 The null character. Typically used to mark the end of a string.

Table 8: Common escape sequences.


Increment, Decrement and Comments

Some additional aspects of the C language that require mention but not a great deal of detail are listed here.

The increment (++) and decrement (‐‐) operators are unary operators similar to unary plus and unary minus. One crucial difference is ++ and ‐‐ may appear on either side of the expression element. This defines pre- or post- for the operator.

Consider the following:

int x=4, y=6, z, a;

x++;
--y;
z = x++;
a = ++y;

printf("x is %d\ny is %d\nz is %d\na is %d\n", x, y, z, a);

Example code for increment and decrement operators.

Important Note!
The printf noted above looks incredibly complex. Look closer and practice seeing the boundaries within the format string. Note the text to be expressly printed, the placement of the conversions, and the use of newlines to produce the output below.

This is a good example of a complex-looking printf function call which you will also be writing one day!

If this code snippet were in a complete program, the output would look like this:

x is 6
y is 6
z is 5
a is 6

The variable x starts at 4, and y starts at 6. A post-increment is applied to x, making its value 5. A pre-decrement of y makes its value 5. Since these statements only have single variable expressions, it doesn’t matter if they are pre or post – in this case.

Now look at the assignment statements for z and a. The variable z will get the value of a post-increment of x. This means z will get the value of x before it is incremented. Therefore z will be 5, and x will be 6. The variable a will get the value of y after it is incremented. Therefore a will be 6, and y will be 6.

Comments

Comments are the favorite tool of any good programmer to document their program, especially complex or non-intuitive solutions. Comments come in two forms – the // form (since C99) and the /* */ form.

int x; //comment to end of line describing purpose of x

/*
This comment is allowed to span lines.
It is quite useful when the necessary programming note
requires much more detail than a single line will allow.
*/

x = 10;

Examples of C comments.

As you can see, the // comment is only a comment until the end of the line. and the /*...*/ comment may span multiple lines. Note that there are no spaces between any of the comment characters. Therefore the //, /* and */ must not have spaces.


The main Function

As mentioned earlier, in any programming language, there needs to be an entry point to begin the execution of statements. In C that function is called main. The main function has had a number of variations since its inception in the original K & R standard. At that time, the most common implementation was

void main(void) {
  // ...
}

The old K&R main function.

This is no longer a valid form. In later releases, the return type (the type specified to the left of main) was required to be int. Today, the most widely accepted forms are as follows.

int main(void) {
  // ...
  return 0;
}

A main function with no parameters.

or

int main(int argc, char *argv[]) {
  // ...
  return 0;
}

A more conventional use of the main function.

or

int main(int argc, char **argv) {
  // ...
  return 0;
}

The last two are the same. (We will learn later that a * on the left can be moved to the right and represented as [].)

The first version says the main function will accept no arguments from the hosting system’s runtime environment. This is okay and is often the desired implementation. The later versions indicate that information can be passed to the main function as strings. This can be very useful if additional instructions are required for the program’s success. This information can be anything from file names to command-line options; anything goes as long as a string can represent it.

Important Note!
The default value returned by the main function is zero, even if there is no return statement or the return has no specified value. The value returned from main is typically used to indicate success or failure to the host environment.

It is, however, considered good form to provide a return statement.

It is worth mentioning that some implementations allow for another variation shown below.

int main(int argc, char **argv, char **envp)

This is not a part of any C standard. Using char **envp is entirely vendor and/or platform-dependent.


Quiz

Which are true when comparing variables to named constants?

 
 
 
 

Which if the following declares and initializes a string variable to the value "Potato" and is a complete C statement.

 
 
 
 

What is the default data type associated with the following literal value:

128

 
 
 
 

What value will the following code produce?

int i=1, j=6, k;

k = 2 * --i + j++;
printf("%d\n", k);
 
 
 
 

Which statement is true about the function printf()?

 
 
 
 

What value will the following code produce?

int i=1, j=6, k;

k = 2 * j + i;
printf("%d\n", j);
 
 
 
 

The act of shrinking one type to fit another type is known as

 
 
 
 

What is the default data type associated with the following literal value:

'A'

 
 
 
 

What value will the following code produce?

char name[]="Jacob";
int k;

k = strlen(name) / 2;
printf("%d\n", k);
 
 
 
 

The meaning behind a written statement in any language is called:

 
 
 
 

Which of the following is not a valid identifier:

 
 
 
 

Which is the correct way to embed double quotes into a String?

 
 
 
 

The use of bool has some caveates. These include:

 
 
 
 

The rules that govern how the language is written are called:

 
 
 
 

The act of enlarging one type to fit another type is known as

 
 
 
 

To the best of your knowledge, which of the following is not a reserved word:

 
 
 
 

The C language contains a small set of words that are used to describe the language. These are called:

 
 
 
 

An explicit coercion or cast is done by surrounding a type with a set of:

 
 
 
 

Given:

a = x + y;

If a is an int and x and y are both of type double,

 
 
 
 

What is the default data type associated with the following literal value:

3.14

 
 
 
 

Give the following expression:

(x - j) * (k - i)

Which of the following is true?

 
 
 
 

Which pair of data types are considered to be the same size when stored in memory?

 
 
 
 

Given:

a = x + y;

If a is a double and x and y are both of type int,

 
 
 
 

Which data type is considered to be an unsigned value (representing only positive values)?

 
 
 
 
 

Loading ... Loading …

Question 1 of 24

Loading


Exercises

[Note: All of this is done using the basic template for main, above.]

  1. (Beginner) Declare a string called name capable of holding 15 characters.
  2. (Beginner) Declare an int called i, a double called d and boolean called b.
  3. (Beginner) Assign appropriate values to each variable and use printf() to display the contents of each, separately.
  4. (Intermediate) Intentionally create errors in some statements to see how the compiler conveys its knowledge of the issue. These should include the following:
    • Missing punctuation.
    • Assigning inappropriate values to variables.
    • Leaving quotes or parenthesis unbalanced.
  5. (Advanced) Try your hand at scanf and use it to read in values to populate the variables noted above. [You may have to read ahead a bit!]

Post navigation

❮ Previous Post: Java Exercise Answers
Next Post: Chapter 3 – Input and Output ❯

Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.

Copyright © 2018 – 2025 Programming by Design.