Fundamentals of Programming (C/C++)

Chapter 3
C/C++ Program Structure and Data Representation

Bachelors Program in Computer Applications
Fundamentals of Programming (C/C++)   •   Bachelor

C/C++ Program Structure and Data Representation

Introduction

A computer program is a precisely organized set of instructions and data. Before a program can perform useful computation, a compiler must recognize its symbols, determine their meanings, check their types, and translate the source code into executable machine instructions. C and C++ provide a small but powerful set of language elements for expressing these instructions.

This chapter develops the foundations required to create, compile, and execute C and C++ programs. It examines program structure, tokens, identifiers, literals, variables, constants, primitive data types, type conversion, namespaces, console input/output, operators, expressions, precedence, and formatted output. These concepts are not isolated rules: together they form a model of how source text becomes a running program.

The examples use standard C++ syntax. Where C and C++ differ significantly, the distinction is identified.

Learning Objectives

After studying this chapter, you should be able to:

  • describe the basic structure and translation process of a C/C++ program;
  • distinguish keywords, identifiers, literals, operators, and punctuators;
  • declare, initialize, and use variables and constants;
  • select suitable primitive data types and explain their limitations;
  • perform and analyze implicit and explicit type conversions;
  • use namespaces and standard console streams safely;
  • construct arithmetic, relational, logical, assignment, and conditional expressions;
  • apply operator precedence and associativity correctly;
  • produce formatted output and interpret input failures;
  • compile, execute, test, and improve small console applications.

1. From Source Code to Execution

A C or C++ program passes through several stages:

  1. Editing: The programmer writes source code in a file such as main.cpp.
  2. Preprocessing: Preprocessor directives are expanded and conditional compilation is performed.
  3. Compilation: The compiler analyzes syntax and semantics and translates source code into an object file.
  4. Linking: Object files and libraries are combined into an executable program.
  5. Loading and execution: The operating system loads the executable, and the program begins at its entry point.

The compiler detects many errors before execution, including undeclared names, invalid syntax, and incompatible operations. Other errors, such as division by zero or an incorrect algorithm, may occur only while the program runs or may produce an incorrect result without any diagnostic.

A minimal C++ program is:

#include <iostream>

int main() {
    std::cout << "Hello, world!\n";
    return 0;
}

#include <iostream> makes declarations for standard input and output available. main is the program entry function. Braces delimit its compound statement. std::cout represents standard output, and << inserts data into the output stream. return 0 conventionally reports successful completion to the operating system. In C++, reaching the end of main also implies a return value of zero, but writing it can improve clarity.

A comparable C program is:

#include <stdio.h>

int main(void) {
    printf("Hello, world!\n");
    return 0;
}

C uses library functions such as printf and scanf; C++ commonly uses stream objects such as std::cout and std::cin.

2. Tokens and Lexical Structure

A token is a meaningful elementary unit recognized by the compiler. Whitespace and comments usually separate tokens but do not themselves perform an operation.

The principal token categories are:

CategoryExamplesPurpose
Keywordsint, if, return, constReserved language words
Identifierstotal, calculateAreaProgrammer-defined names
Literals42, 3.14, 'A', "C++"Values written directly in source code
Operators+, =, &&, <<Perform or describe operations
Punctuators;, ,, (), {}Structure declarations and statements

Comments are ignored by the compiler. A single-line comment begins with //; a multi-line comment begins with / and ends with /.

// Calculate the total cost.
int quantity = 4; /* number of items */

Comments should explain intent, assumptions, or non-obvious decisions. They should not merely repeat the syntax.

Identifiers

An identifier may contain letters, digits, and underscores, but it cannot begin with a digit. C and C++ are case-sensitive, so value, Value, and VALUE are different identifiers. Keywords cannot be used as identifiers.

Valid examples include studentCount, maximum_value, and x2. Invalid examples include 2ndValue, total-cost, and class.

Good identifiers communicate purpose. averageMarks is more maintainable than am when both are legal. Common conventions include lower camel case (totalAmount) or snake case (total_amount); consistency is more important than choosing one universal style.

3. Literals, Variables, and Constants

A literal is a value written directly in a program. Common C++ literals include:

42          // integer literal
3.14159     // floating-point literal
't'         // character literal
"text"      // string literal
true        // Boolean literal

A character literal represents one character and uses single quotation marks. A string literal represents a sequence of characters and uses double quotation marks. The string "A" and the character 'A' are different entities.

Integer literals can be written in decimal, hexadecimal, octal, or binary notation. For example, 26, 0x1A, 032, and 0b11010 represent the same mathematical value in C++ (subject to language-version support for binary notation). Suffixes such as u, l, and ll can influence the literal's type.

A variable is a named storage location whose value can change during execution. Declaration introduces its type and name; initialization gives it an initial value.

int attempts;          // declaration without initialization
int score = 85;        // copy initialization
int level{3};          // list initialization

Uninitialized local fundamental variables may contain indeterminate values. Reading such a value is a serious programming error. Prefer initialization at the point of declaration.

A constant is an object whose value cannot be modified after initialization.

const double TAX_RATE{0.18};
constexpr int DAYS_PER_WEEK{7};

const expresses read-only status. constexpr indicates that the value is intended to be usable in a constant expression and must be evaluable at compile time when required. Named constants improve readability and reduce accidental changes.

4. Primitive Data Types and Representation

A data type specifies the set of values an object can represent and the operations permitted on those values. The exact size and range of some fundamental types are implementation-defined, so portable programs should not assume that int always has a particular number of bytes.

TypeTypical useImportant observation
boolLogical statetrue or false
charCharacter or small integerCharacter encoding is implementation-dependent
short, int, long, long longWhole numbersMinimum ranges are specified; sizes vary
floatSingle-precision approximationLimited precision
doubleGeneral floating-point computationUsually more precise than float
long doubleExtended floating-point computationPrecision varies by implementation
voidAbsence of a valueUsed for functions and generic pointers

Signed integer types represent positive and negative values. Unsigned types represent nonnegative values and have a larger maximum value for a given storage width, but arithmetic involving unsigned values can produce surprising results because it wraps modulo one more than the maximum representable value.

For an unsigned type with n value bits, the representable range is generally:

0 ≤ x ≤ 2ⁿ − 1

Here, x is the stored integer and n is the number of value bits. This relationship applies to a conventional binary unsigned representation; the exact object representation and padding are governed by the language implementation.

For a signed two's-complement type with n value bits, a common range is:

−2ⁿ⁻¹ ≤ x ≤ 2ⁿ⁻¹ − 1

This is a useful model for modern implementations, but portable code should use facilities such as std::numeric_limits<T> when exact limits matter.

The sizeof operator reports the size of an object or type in units of char, not necessarily bytes as defined by every external system.

#include <iostream>
#include <limits>

int main() {
    std::cout << sizeof(int) << '\n';
    std::cout << std::numeric_limits<int>::max() << '\n';
}

Floating-point limitations

Floating-point types use finite binary representations to approximate many real numbers. Values such as 0.1 often cannot be represented exactly in binary. Consequently, direct equality comparisons may be unreliable after arithmetic. For approximate comparison, use a tolerance appropriate to the scale and error requirements of the application.

Money should generally not be represented by unrestricted binary floating-point values when exact decimal cents are required. An integer count of the smallest currency unit, or a carefully designed decimal type, is often safer.

5. Type Conversion and Type Safety

A conversion changes a value from one type to another. Conversions may be implicit, inserted automatically by the language, or explicit, requested by the programmer.

int count = 7;
double average = count;       // widening conversion: int to double
int whole = static_cast<int>(3.9); // explicit conversion; result is 3

Converting an integer to a floating-point type usually preserves its value if the floating-point type has sufficient precision. Converting a floating-point value to an integer discards the fractional part; it does not round to the nearest integer.

A narrowing conversion may lose information, such as fractional digits, range, or precision. Braced initialization helps expose some narrowing conversions:

int a{3.9};       // ill-formed in standard C++
int b = 3.9;      // permitted, but fractional part is discarded

Arithmetic expressions undergo conversions according to the language's promotion and conversion rules. Consider:

int total = 5;
int people = 2;
double result = total / people;

The division occurs as integer division because both operands are integers, so result receives 2.0. To obtain a fractional result, convert an operand before division:

double result = static_cast<double>(total) / people;

The result is 2.5.

Boolean conversion treats zero as false and a nonzero value as true. In the reverse direction, false converts to zero and true generally converts to one. These rules are useful but should not conceal important logic.

Prefer static_cast<T>(expression) for ordinary explicit conversions in C++. It states the intended target type and is easier to search for than C-style casts. Avoid casts that suppress warnings without understanding the possible loss of information.

6. Scope, Lifetime, and Namespaces

Scope is the region of source code in which a name can be used. A local variable declared inside a function or block is normally accessible only there. A global name has broader scope but can create maintenance and naming problems. Lifetime is the period during execution for which an object exists; scope and lifetime are related but not identical.

int globalValue = 10;

int main() {
    int localValue = 5;
    {
        int innerValue = 2;
        // all three names can be used here
    }
    // innerValue is no longer in scope
}

A namespace groups names and helps prevent collisions between libraries and application code. Standard library names belong to the std namespace.

std::cout << "Ready\n";
std::string name;

A using-declaration can introduce one name:

using std::cout;
cout << "Ready\n";

A using-directive such as using namespace std; makes many names visible and can create ambiguity, especially in header files or large programs. Qualified names such as std::cout are generally the safest and clearest practice.

7. Console Input and Output

C++ stream input and output are provided by the <iostream> header.

#include <iostream>

int main() {
    int age{};
    std::cout << "Enter age: ";
    std::cin >> age;
    std::cout << "Age = " << age << '\n';
}

std::cin extracts formatted values and automatically skips leading whitespace for many fundamental types. std::cout inserts values into the output stream. The newline character \n moves to the next line; std::endl also flushes the stream, so it should not be used reflexively in performance-sensitive loops.

For a complete line of text, use std::getline:

#include <iostream>
#include <string>

int main() {
    std::string fullName;
    std::cout << "Full name: ";
    std::getline(std::cin, fullName);
    std::cout << "Hello, " << fullName << '\n';
}

A common issue occurs when formatted extraction leaves a newline in the input buffer before getline. One solution is:

std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');

Input must be validated. If the user enters text where an integer is expected, std::cin enters a failure state. A robust program detects and repairs it:

int quantity{};
while (!(std::cin >> quantity) || quantity < 0) {
    std::cout << "Enter a nonnegative integer: ";
    std::cin.clear();
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}

The loop clears the error flags and discards invalid input. Validation rules should reflect the domain, not merely the data type.

8. Operators and Expressions

An expression combines operands and operators to produce a value or effect. Operators can be classified as follows:

CategoryOperatorsTypical purpose
Arithmetic+ - * / %Numeric calculation
Relational< <= > >= == !=Comparison
Logical`&&
Assignment= += -= *= /= %=Storing updated values
Increment/decrement++ --Add or subtract one
Conditional?:Select one of two expressions
Bitwise`&^ ~ << >>`

The remainder operator % is defined for integral operands and yields the remainder after division. For example, 17 % 5 is 2. It is useful for parity and cyclic indexing.

The equality operator is ==; a single = performs assignment. Confusing them is a frequent source of defects.

Logical operators use short-circuit evaluation. In A && B, B is evaluated only if A is true. In A || B, B is evaluated only if A is false. This can safely guard an operation:

if (denominator != 0 && numerator / denominator > 2) {
    // division is evaluated only after the check
}

The conditional operator is useful for a simple selection:

std::string status = score >= 50 ? "Pass" : "Fail";

Do not use complex nested conditional expressions when an if statement would be clearer.

9. Precedence, Associativity, and Evaluation

Precedence determines which operators bind more tightly. Associativity determines grouping when operators have equal precedence. For example:

int value = 2 + 3 * 4;     // 14
int result = (2 + 3) * 4;  // 20

Multiplication has higher precedence than addition. Parentheses explicitly communicate intended grouping and should be used when they improve clarity.

A simplified precedence sequence, from higher to lower, is:

  1. parentheses and function calls;
  2. unary operators such as !, unary -, and prefix ++;
  3. multiplication, division, and remainder;
  4. addition and subtraction;
  5. relational comparisons;
  6. equality comparisons;
  7. logical AND;
  8. logical OR;
  9. conditional ?:;
  10. assignment.

This is not a complete language table. When exact behavior matters, consult the standard or a reliable reference. Precedence does not determine the order in which all operands are evaluated. Avoid expressions that modify and read the same object in an unclear way, such as relying on multiple increments in one statement.

10. Formatted Output

The <iomanip> header provides manipulators for readable output.

#include <iomanip>
#include <iostream>

int main() {
    double price = 19.5;
    std::cout << std::fixed << std::setprecision(2)
              << "Price: $" << price << '\n';
}

std::fixed displays fixed-point notation, and std::setprecision(2) displays two digits after the decimal point in that mode. std::setw controls the width of the next field, while std::left and std::right control alignment.

std::cout << std::left << std::setw(15) << "Item"
          << std::right << std::setw(8) << "Qty" << '\n';

Formatting affects presentation, not the stored value. Rounding displayed output does not necessarily round the underlying floating-point number.

C provides formatted output through printf:

printf("Total: %.2f\n", total);

The format specifier must match the argument type. Incorrect format strings can cause undefined behavior, so C programmers must use them carefully.

11. Worked Application: A Validated Purchase Calculator

Suppose a small campus shop needs a console program that reads a quantity and unit price, calculates a subtotal, applies a discount for sufficiently large purchases, and displays a formatted receipt. This is an illustrative application.

The business rule is:

  • subtotal = quantity × unit price;
  • if quantity is at least 10, discount rate = 0.10; otherwise discount rate = 0;
  • discount = subtotal × discount rate;
  • total = subtotal − discount.

The variables are quantity, a nonnegative integer, and unitPrice, a nonnegative monetary amount. The program must reject invalid input.

#include <iomanip>
#include <iostream>
#include <limits>

int main() {
    int quantity{};
    double unitPrice{};

    std::cout << "Quantity: ";
    while (!(std::cin >> quantity) || quantity < 0) {
        std::cout << "Enter a nonnegative whole number: ";
        std::cin.clear();
        std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
    }

    std::cout << "Unit price: ";
    while (!(std::cin >> unitPrice) || unitPrice < 0) {
        std::cout << "Enter a nonnegative price: ";
        std::cin.clear();
        std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
    }

    const double discountRate = quantity >= 10 ? 0.10 : 0.0;
    const double subtotal = quantity * unitPrice;
    const double discount = subtotal * discountRate;
    const double total = subtotal - discount;

    std::cout << std::fixed << std::setprecision(2)
              << "\nSubtotal: $" << subtotal
              << "\nDiscount: $" << discount
              << "\nTotal:    $" << total << '\n';
}

For quantity 12 and unit price 25.00, the subtotal is 300.00, the discount is 30.00, and the total is 270.00. The calculation is straightforward, but the design illustrates several professional practices: initialization, named constants, input validation, explicit conditional logic, and controlled output formatting.

For systems that must represent currency exactly, replace double with an integer number of cents. If the unit price is supplied as cents, the subtotal in cents is computed using integer arithmetic. The program must also consider overflow and tax rules. Financial requirements should be established before choosing a representation.

12. Compilation, Testing, and Debugging

A typical command using the GNU C++ compiler is:

g++ -std=c++17 -Wall -Wextra -pedantic purchase.cpp -o purchase
./purchase

The standard version should match the features used by the course or project. Warning options are valuable because they identify suspicious conversions, unused variables, and other risks. Treat warnings as issues to investigate rather than noise.

Testing should include normal, boundary, and invalid inputs. For the purchase calculator, useful tests include quantity 0, quantity 9, quantity 10, a decimal price, negative input, and nonnumeric input. A boundary test at quantity 10 verifies the discount transition.

Three broad defect categories are important:

  • Syntax errors: The compiler cannot parse the program, such as a missing semicolon.
  • Semantic or type errors: The program violates language rules or uses incompatible entities.
  • Logic and runtime errors: The program compiles but computes an incorrect result or fails during execution.

A disciplined workflow is to compile frequently, read the first diagnostic carefully, isolate one change at a time, and use a debugger or temporary diagnostic output to inspect values.

13. Challenges, Limitations, and Best Practices

C and C++ give programmers substantial control over representation and performance. That control also creates risks. Implicit conversions may lose data; uninitialized objects may contain indeterminate values; signed overflow is not a safe substitute for arbitrary-precision arithmetic; and unchecked input can invalidate assumptions.

Recommended practices include:

  • initialize objects immediately;
  • use const and constexpr for values that should not change;
  • select types based on domain meaning and required range;
  • use braces for initialization where appropriate;
  • prefer explicit casts and inspect narrowing conversions;
  • qualify standard names with std:: in teaching code and headers;
  • validate input and check stream state;
  • use parentheses to make nontrivial expressions unambiguous;
  • compile with strong warnings;
  • separate calculation from input and presentation when programs grow;
  • avoid using floating-point arithmetic for exact monetary requirements;
  • document assumptions such as permitted ranges and units.

Programming also has ethical implications. A program that accepts invalid data can produce unfair, unsafe, or financially harmful decisions. Developers should validate inputs, protect sensitive output, avoid misleading formatting, and preserve auditability where calculations affect people. Although this chapter concerns small console programs, these principles scale to commercial and public systems.

Chapter Summary

C and C++ programs are translated from source text through preprocessing, compilation, linking, and execution. Source code is composed of tokens, including keywords, identifiers, literals, operators, and punctuators. Identifiers name entities; literals express values; variables store changeable values; and constants express values that should not change.

Fundamental types determine representation and permitted operations. Integer, character, Boolean, and floating-point types have different ranges and precision characteristics, and exact limits can vary by implementation. Implicit and explicit conversions can be useful but may lose information, especially when converting floating-point values to integers or performing integer division.

Namespaces organize names, while std::cin, std::cout, and std::getline support console interaction. Operators form expressions for calculation, comparison, logic, assignment, and selection. Precedence and associativity affect grouping, but parentheses should be used whenever intent may be unclear. Formatted output through <iomanip> improves readability without changing stored values.

A reliable program initializes data, validates input, uses suitable types, handles conversion deliberately, compiles with warnings, and tests boundary cases. These practices provide the foundation for larger, safer, and more maintainable software.

Key Terms

Token: A meaningful lexical unit recognized by the compiler.

Keyword: A reserved word with a predefined language meaning.

Identifier: A programmer-defined name for an entity.

Literal: A value written directly in source code.

Variable: A named storage location whose value may change.

Constant: An object whose value cannot be modified after initialization.

Data type: A specification of values, representation, and permitted operations.

Primitive type: A fundamental built-in type such as int, double, or bool.

Scope: The region of source code where a name is accessible.

Lifetime: The period during execution in which an object exists.

Namespace: A named region used to organize identifiers and prevent collisions.

Expression: A combination of operands and operators that produces a value or effect.

Operator: A symbol or token that performs an operation.

Precedence: The rule determining which operators bind more strongly.

Associativity: The rule for grouping operators with equal precedence.

Implicit conversion: A type conversion performed automatically by the language.

Explicit conversion: A conversion requested by the programmer.

Stream: An abstraction representing a sequence of input or output data.

Narrowing conversion: A conversion that may lose range, precision, or fractional information.

Formatted output: Output controlled by settings such as width, alignment, and precision.

Review Questions

Short Answer Questions

  1. What is the role of the main function in a C++ program?
  2. Distinguish a character literal from a string literal.
  3. Why should local variables normally be initialized when declared?
  4. What is the purpose of the std namespace?
  5. What is the difference between = and ==?

Long Answer Questions

  1. Explain the stages through which a C++ source file passes before execution.
  2. Compare integer, Boolean, character, and floating-point data types, including important limitations.
  3. Explain implicit and explicit type conversion with examples of information loss.
  4. Describe how std::cin, std::cout, and std::getline work, including input failure handling.
  5. Explain precedence and associativity and discuss why parentheses improve maintainability.

Analytical / Application Questions

  1. A program computes int average = total / count;, where both variables are integers. Explain the result when total is 7 and count is 2, and redesign the statement to preserve the fractional result.
  2. Design test cases for a program that grants a discount when a customer buys at least 10 items. Identify normal, boundary, and invalid cases.
  3. A payroll program stores currency in double and repeatedly adds tax and deductions. Analyze the risks of this representation and recommend a more reliable design for exact cents.

Multiple Choice Questions with Answers

Question 1

Which token category includes int and return?

A. Identifiers

B. Keywords

C. Literals

D. Punctuators

Correct Answer: B. Keywords

Question 2

What is the result of 7 / 2 when both operands have type int?

A. 2

B. 2.5

C. 3

D. 1

Correct Answer: A. 2

Question 3

Which declaration creates a compile-time constant intended for constant expressions?

A. int value = 7;

B. static int value = 7;

C. constexpr int value{7};

D. volatile int value = 7;

Correct Answer: C. constexpr int value{7};

Question 4

What does std::setprecision(2) do with std::fixed output?

A. Sets the field width to two characters

B. Displays two digits after the decimal point

C. Rounds every stored value permanently

D. Converts a number to an integer

Correct Answer: B. Displays two digits after the decimal point

Question 5

Which operator performs equality comparison?

A. =

B. !=

C. ==

D. <=

Correct Answer: C. ==

Question 6

What is the value of 2 + 3 * 4?

A. 20

B. 24

C. 14

D. 10

Correct Answer: C. 14

Question 7

Which statement correctly reads an entire line, including spaces?

A. std::cin >> line;

B. std::read(line);

C. std::getline(std::cin, line);

D. std::input(line);

Correct Answer: C. std::getline(std::cin, line);

Question 8

What happens when static_cast<int>(3.9) is evaluated?

A. It produces 4 by rounding

B. It produces 3 by discarding the fractional part

C. It produces 3.9 as an integer

D. It always causes a runtime error

Correct Answer: B. It produces 3 by discarding the fractional part

Question 9

Which practice best reduces accidental name collisions with standard-library identifiers?

A. Use using namespace std; in every header

B. Avoid all namespaces

C. Qualify names such as std::cout

D. Declare all variables globally

Correct Answer: C. Qualify names such as std::cout

Question 10

If quantity = 12 and unitPrice = 25.00, what is the total after a 10% discount?

A. $250.00

B. $270.00

C. $275.00

D. $300.00

Correct Answer: B. $270.00

Fundamentals of Programming (C/C++)
Chapter 3 of 15