Language Basics: Types, Variables, Scopes, Operators, Statements, and Functions
Programming Workshop in C/C++ (67315) -- Lecture 1
History of C and C++
- C (1970s): Developed alongside UNIX by Ritchie & Kernighan at AT&T Bell Labs. Simple to convert to machine code, fast and efficient, low-level coding.
- C++ (1980s): Object-oriented extension of C by Stroustrup at Bell Labs. Designed for large, efficient codebases.
- Python (1990s): General-purpose interpreted language. High-level but slower.
Hierarchy of Programming Languages
From low-level to high-level: Assembly -> C -> C++ -> Python
C is lean & mean -- it has only 32 keywords. Memorize them!
What is C Used For?
C is used for systems programming:
- Operating systems (e.g., Linux)
- Microcontrollers: cars & planes
- Embedded processors: phones, portable electronics
- Medical devices
- Drivers
- Anywhere with tight limits on memory and CPU time
C has no run-time checks (array boundary overruns, illegal memory access) and no automated memory management -- the programmer must manage memory manually.
C++ -- Object Oriented Extension of C
- Classes & methods: OO design of classes
- (More) Generic programming: Templates allow code reuse
- Stricter type system
- Some run-time checks & memory control
Coding Style
- Coding style is personal (though not always in a course setting)
- CamelCase:
aRatherLongSymbolName - snake_case:
a_rather_long_symbol_name - Big projects require coding style standards
- Good coding style makes code much more readable
Hello World
First Program in C
#include <stdio.h>
int main()
{
printf("Hello class!\n");
return EXIT_SUCCESS;
}
EXIT_SUCCESS (defined in <stdlib.h>) is clearer than returning 0.
Write Clean Self-Documenting Code
- Clear, consistent, and concise naming
- Short blocks of code
- Communicate the goal of each block (ideally one goal per block)
- Short comments to explain why, especially for non-obvious decisions
- Document your interface
Compiling & Running
Default Compilation
gcc hello.c # generates executable a.out
./a.out # run the program
Compilation with Options
gcc -std=c99 -Wall hello.c -o hello
./hello
| Flag | Purpose |
|---|---|
-std=c99 | Use the C99 standard |
-Wall | Enable most compiler warnings |
-o hello | Set program output file name |
-g | Enable debugging |
Types & Variables
Statically vs. Dynamically Typed
# Python (dynamically typed)
x = 4
x = "I am a string!" # OK in Python
// C (statically typed)
int x = 0;
x = "I am a string!"; // Error!
Variable Declaration
C is statically typed -- each variable has a type that must be declared at compile time:
<type> <name>;
int x; // declaration
int x, y; // multiple declarations
int x = 0; // declaration with initialization (recommended!)
Uninitialized local variables contain garbage values -- always initialize your variables.
Variable Types in C
Simple types:
- Arithmetic types (int, char, float, etc.)
- Pointers
- Enumeration types
Aggregate types:
- Arrays
- Structs
Users can define new types with struct and create aliases with typedef.
Arithmetic Data Types
Integer Types
char c = 'A';
short s = 0;
int x = 1;
long y = 9;
// Unsigned variants
unsigned char c = 'A';
unsigned short s = 0;
unsigned int x = 1;
unsigned long y = 9;
Additional initialization forms:
char a = 'A', b = 'B';
char c = 65; // char is also a number!
unsigned char d = 0x3A; // hexadecimal number
int x = (y = 1); // (y = 1) returns the value assigned to y
int x = y = 1; // equivalent -- assignment evaluated right to left
Floating-Point Types
float x = 0.0;
double y = 1.0; // approximately double precision
y = y * 1.2342f; // multiply by float literal
Pointers (Preview)
int y = 10;
int *x = &y; // x is a pointer to int, pointing to y's memory address
Enum -- Integer-like User-Defined Type
enum { SUNDAY = 1, MONDAY, TUESDAY /* ... */ };
enum Color { BLACK, RED, GREEN, YELLOW, BLUE, WHITE = 7, GRAY };
typedef enum Seasons_tag
{
E_WINTER, // = 0 by default
E_SPRING, // = E_WINTER + 1
E_SUMMER, // = E_WINTER + 2
E_AUTUMN // = E_WINTER + 3
} Season_type;
int day = MONDAY; // 2
enum Color ca = BLUE; // 4
enum Color cb = GRAY; // 8
Season_type s = E_SUMMER; // 2
Use enum to eliminate magic numbers. Magic numbers make code harder to read and maintain.
Type Casting and Truncation
int x = 75;
int y = 100;
float z = x / y; // = 0.0 (integer division!)
float z = (float) x / y; // = 0.75 (cast x to float first)
int z = (float) x / y; // = 0 (truncation towards zero)
Typedef -- Create Custom Types
typedef unsigned char byte;
byte x = 'A';
x++; // x will equal 'B'
x = 0xFA; // hexadecimal number
unsigned char y;
y = x; // works -- byte is just unsigned char
Const Qualifier
const int x = 3; // initialization is OK
x = 5; // ERROR -- cannot assign a new value to a const type
A variable with a const-qualified type cannot appear on the left side of an assignment operator.
Variable Scope
Variables can be declared:
- Inside a block -- visible only within that block
- Outside all blocks (global) -- visible everywhere
int x = 0; // global
int main()
{
int x = 1; // local, hides global
{
int x = 2; // local, hides outer scope
// x is 2
}
// x is 1 again
}
Scope Rules
- Code blocks are defined with
{and} - Nesting is possible
- Only declarations in the current or outer scopes are visible
- Inner scope declarations hide outer scope declarations
- The outermost scope (global) has no brackets
- A function body is also a scope
Conditional Statements
if / else if / else
if (conditional_expression)
{
// ...
}
else if (conditional_expression)
{
// ...
}
else
{
// ...
}
Ternary Operator ? :
y = (x > 0) ? 10 : 100;
// Equivalent to:
if (x > 0)
{
y = 10;
}
else
{
y = 100;
}
Switch & Enum
Switch Statement
switch (n) // integer types only (int, char, short, _Bool, etc.)
{
case 1:
// code executed if n == 1
break;
case 2:
// code executed if n == 2
break;
default:
// code executed if n doesn't match any case
}
Never forget break! Without break, execution falls through to the next case. All statements following a matching case execute until a break is reached.
Case Expression Rules
The case expression must be an integral type whose value can be evaluated at compile time:
int a = 1, b = 2;
const int c = 3;
case (1 + 2): // OK (can be evaluated in advance)
case (a + b): // INVALID (known only at run time)
case (c): // INVALID (const means read-only, not a real constant!)
case (3.14159): // INVALID (not integral type)
case 'A': // OK (a char is an integral type!)
Enum and Switch Together
int main()
{
enum Color { RED, GREEN };
enum Color my_color;
scanf("%d", &my_color);
switch (my_color)
{
case RED:
printf("Your favorite color is red\n");
break;
case GREEN:
printf("green\n");
break;
default:
printf("Unknown color\n");
}
return 0;
}
int x = 2;
switch (x)
{
case 1: printf("Choice is 1\n");
case 2: printf("Choice is 2\n");
case 3: printf("Choice is 3\n");
default: printf("Choice other than 1, 2 and 3\n");
}
Output (no breaks!):
Choice is 2
Choice is 3
Choice other than 1, 2 and 3
Loop Statements
for Loop
for (initialization; test_condition; update)
{
// loop body
}
for (int i = 0; i < 5; i++)
{
printf("%d\n", i);
}
// Output: 0 1 2 3 4
for Loop with Comma Operator
int i, j;
for (i = 0, j = 0; i < 10 && j < 5; i++, j += 2)
{
printf("%d %d\n", i, j);
}
// Output: 0 0 / 1 2 / 2 4
while / do-while Loops
while (condition)
{
// ...
}
do
{
// ...
} while (condition);
Operators
Operator Types in C
| Category | Operators |
|---|---|
| Unary | ++ -- |
| Arithmetic | + - * / % |
| Relational | < <= > >= == != |
| Logical | && || ! |
| Bitwise | & | ^ << >> ~ |
| Assignment | = += -= *= /= %= <<= >>= &= ^= |= |
| Ternary | ? : |
| Size | sizeof() |
| Pointer | * (dereference) |
| Address-of | & |
Arithmetic Operators
Precedence follows standard algebra: brackets first, then * and /, then + and -, evaluated left to right.
float x = 3 / 2; // = 1 (integer division!)
float y = 3.0 / 2; // = 1.5
int z = 3.0 / 2; // = 1 (truncated)
int r = 3 % 2; // = 1 (remainder)
Increment and Decrement
x++; // equivalent to x = x + 1
y = x++; // y = x; then x = x + 1 (post-increment)
y = ++x; // x = x + 1; then y = x (pre-increment)
Assignment vs. Initialization
int x = 5; // Initialization (during declaration)
x = 3; // Assignment (after declaration)
x *= 5; // Compound assignment
const int y = 5; // OK -- initialization
y = 10; // ERROR -- const cannot be assigned
All assignment operators return a value: int x = (y = 3); sets both y and x to 3.
Functions
Function Declaration (Prototype)
int power(int a, int b); // return type, name, parameters
Function Definition
int power(int a, int b)
{
int p = 1;
for (int i = 0; i < b; ++i)
{
p *= a;
}
return p;
}
If a function was not declared earlier, the definition also serves as a declaration.
Procedures (void Functions)
void do_something(int a, int b)
{
// ...
return; // optional -- return without a value
}
Function Declaration Order
In C99, a function is only aware of earlier function declarations (those above it in the source file).
// ERROR: func_c not declared yet
void func_b()
{
func_c(); // Error!
}
void func_c()
{
func_b(); // OK -- func_b was declared above
}
Forward Declaration
Use forward declarations to solve ordering issues:
void func_c(int param); // Forward declaration
void func_b()
{
func_c(7); // OK now
}
void func_c(int param)
{
// definition
}
Declaration ";" vs. Definition "{}"
- Declaration: Provides the compiler the function name, parameter types, and return type.
void func_c(int param); - Definition: Allocates "code memory" for the function body.
void func_c(int param) { // body }
Header Files and Source Files
// power.h -- interface
#ifndef _POWER_H
#define _POWER_H
/**
* Computes integer power
* @return base to the power of n
*/
int power(int base, int n);
#endif // _POWER_H
// power.c -- implementation
#include "power.h"
int power(int base, int n)
{
int p = 1;
for (int i = 0; i < n; ++i)
{
p *= base;
}
return p;
}
// main.c -- using the power interface
#include <stdio.h>
#include "power.h"
int main()
{
for (int i = 0; i < 10; i++)
{
printf("2 ^ %d = %d\n", i, power(2, i));
}
return EXIT_SUCCESS;
}
Functions with No Arguments
void foo(); // In a declaration: undetermined number of arguments (obsolescent, don't use)
void foo(void); // In a declaration: explicitly no arguments
// In a definition, both mean "no arguments":
void foo() { }
void foo(void) { }
Boolean Types
Booleans in C are Just Integers
_Bool is an unsigned integer that can store 0 and 1. Any other integer type can be converted to _Bool:
- zero = false
- non-zero = true
while (1) { } // infinite loop (1 is true)
if (-1974) { } // executes (non-zero is true)
int i = (3 == 4); // i = 0 (false)
stdbool.h
Defines bool, false, and true as _Bool, 0, and 1:
#include <stdbool.h>
bool loves_me = false;
while (true)
{
loves_me = !loves_me;
printf(loves_me ? "loves me\n" : "loves me not\n");
}
int loves_me = 5;
if (loves_me == true) // Won't work! 5 != 1
if (loves_me) // Works! Any non-zero is true
When assigned to a bool/_Bool, any non-zero value is converted to 1. But a plain int keeps its value.
assert() -- Debugging with Boolean Expressions
#include <assert.h>
int main()
{
const int b = 6;
assert(b != 3); // makes sure we're not dividing by zero
float c = 1.0f / (b - 3);
return 0;
}
assert() is used for sanity checks during development and is typically disabled in production mode using NDEBUG.
#define Directive (Macros)
Object-like Macros
A simple identifier replaced by a code fragment during preprocessing:
#define PI 3.141256f
double circumference(double R)
{
return 2 * PI * R; // preprocessor substitutes PI for 3.141256f
}
Function-like Macros
#define SQUARE(X) ((X) * (X)) // parentheses keep you safe!
double square_difference(double x, double y)
{
return SQUARE(x - y); // expands to ((x-y) * (x-y))
}
Without parentheses, macro expansion can produce wrong results:
#define SQUARE(X) X*X
SQUARE(x - y) // expands to x-y*x-y (wrong!)
Always wrap macro parameters and the entire expression in parentheses.
Multi-line Macros
Use \ for line continuation:
#define x (5 + \
5)
// x == 10
Alternatives to Macros
- Constants:
enum { FOO = 1 };orconst int FOO = 1; - Inline functions (C99, C++): discussed later in the course