For Loops, Arrays, and Strings
COP-3223H
Table of Contents
New construct: increment (++)
i++;
means
i = i + 1;
Syntactic sugar for reassigning a variable to the value plus one.
Similarly, i-- means decrement, i = i - 1
The i++ operator is postfix, i.e., the operator appears after the variable.
Prefix variations
++i--i
Prefix vs. postfix affects when the increment/decrement happens:
- Prefix, e.g.,
++i, increment the value before using the variable's value. - Postfix, e.g.,
i++, increment the value after using the variable's value.
int i = 1;
printf("%d\n", i++); // prints 1 (increment after using)
printf("%d\n", i); // prints 2
int j = 1;
printf("%d\n", ++j); // prints 2 (increment before using)
printf("%d\n", j); // prints 2
For loops
for (INITIALIZE; CONDITION; ITERATOR) {
instructions;
}
- Run INITIALIZE
- Check CONDITION
- If it's true, run instructions
- Then run ITERATOR
- Then repeat from #2
- If it's not true, done, continue after the for loop
Example: printing digits
What is the output of this program?
for (int i = 0; i < n; i++) {
printf("%d\n", i);
}
Notice that i++ makes this slightly more concise to write, instead of
for (int i = 0; i < n; i = i + 1) {
printf("%d\n", i);
}
Demonstrating for-loop semantics, boundary conditions, etc.
#include <stdio.h>
int main() {
int n = 10;
for (int i = 0; i < n; i++) {
printf("%d\n", i);
}
printf("done\n");
for (int i = 0; i < n; i = i + 1) {
printf("%d\n", i);
}
printf("done\n");
for (int i = 0; i < n; i = i + 2) {
printf("%d\n", i);
// once i is eight
}
printf("done\n");
for (int i = 0; i < n; i = i + 2) {
printf("%d\n", i);
// once i is eight
}
printf("done\n");
for (int i = 9; i < n; i++) {
printf("%d\n", i);
}
// i = 9
// i < n => 0 < 10 true
// print 9
// i++ => 9++ => 10
// i < n => 10 < 10
// for loop is done
printf("done\n");
int i;
for (i = 10; i < n; i++) {
printf("%d\n", i);
}
// i = 10
// i < n => 10 < 10 false
// continue after the for loop
printf("done\n");
printf("%d\n", i);
}
Example: equivalent while loop
for (int i = 0; i < n; i++) {
printf("%d\n", i);
}
int i = 0;
while (i < n) {
instructions;
i++;
}
Aside on C scoping rules
#include <stdio.h>
int main() { // new "scope" for main
int i = 1;
printf("%d\n", i);
if (i > 0) { // new "scope" for a set of variables for if statement
i = 2;
printf("%d\n", i);
if (i > 0) {
i = 3;
}
printf("%d\n", i);
} // scope is thrown away along with any variable declared here
// still inside main's scope
printf("%d\n", i);
}
#include <stdio.h>
int global;
int main(int argc, char **argv) {
int n;
n = 100;
int x;
x = 11; // main.x
int mainsvar = 8; // main.mainsvar
// cs.ucf.edu.
// cs.nyu.edu.
// cs.ucf.com.
int i = 10; // main.i
for (int i = 0; i < n; i++) { // // main.forloop.i
int x; // main.forloop.x
x = 10;
mainsvar = 7;
printf("%d\n", i);
printf("inside loop mainsvar: %d\n", mainsvar);
printf("inside loop x: %d\n", x);
}
printf("%d\n", i);
{
int i = 2;
}
printf("%d\n", i);
printf("mainsvar: %d\n", mainsvar);
printf("x: %d\n", x);
}
General while loop implementation
for (INITIALIZE; CONDITION; ITERATOR) {
instructions;
}
INITIALIZE;
while (CONDITION) {
instructions;
ITERATOR;
}
Technically, the equivalence would be
{
INITIALIZE;
while (CONDITION) {
instructions;
ITERATOR;
}
}
This ensures that any declarations in INITIALIZE are only scoped to the while loop, and do not interfere with declarations above the while loop.
Example: sum of 1..N
#include <stdio.h>
int main() {
int n;
int sum;
scanf("%d", &n);
// sum the numbers from 1 to n
// assume 1 to N is inclusive
sum = 0;
for (int i = 1; i < n + 1; i++) {
sum = sum + i;
}
// what values of i will we have (inside the loop body)?
// n large enough i = { 1, 2, 3, ..., N }
// n = 0? i = { }
// n = 1? i = { 1 }
// sum = { 1, 3, 6, ... }
printf("%d\n", sum);
}
Example: exponent
#include <stdio.h>
int main() {
int x;
int e;
int result;
scanf("%d", &x);
scanf("%d", &e);
// x^e = x * x * x * ... e times
result = 1;
for (int i = 1; i <= e; i++) {
result = result * x;
}
printf("%d\n", result);
}
Example: factorial
Example: counting down
for (int i = 9; i >= 0; i--) {
}
Example: sum of powers of two
int sum = 0;
for (int i = 0; i < N; i++) {
int pow = 1;
for (int j = 0; j < i; j++) {
pow = 2 * pow;
}
sum = sum + pow;
}
More efficient way to do this?
Arrays
- Variables store one value
- Arrays store one or more values
- Uses a single variable name
Defining a fixed-length array
int a[] = { 1, 2, 3, 4, 5 };
All values have the same datatype, e.g., int in this case.
Operations
Looks like variables, but with a "subscript" in brackets
a[0];
Base-0
- Arrays indices start at zero
int a[] = { 1, 2, 3, 4, 5 };
| Index | Value |
|---|---|
| 0 | 1 |
| 1 | 2 |
| 2 | 3 |
| 3 | 4 |
| 4 | 5 |
Note that there is no index 5. The array has five elements, but starting the count as 0 means there are only indices 0 (inclusive) through 4 (inclusive). If you take the length of the array, that is the upper bound, exclusive, i.e,.
[0, 5)
0 <= index < 5
Assigning an array at an index
a[0] = 2;
Reading an array at an index
printf("%d\n", a[0]);
Memory diagram
Diagram
- Variable names one memory box
- Array names several memory boxes
- Index starts at 0
- We can think of this as an offset from 0
Example: reading and writing a fixed array
#include <stdio.h>
int main(int argc, char **argv) {
int a[] = { 1, 2, 3, 4, 5 };
}
Uninitialized arrays
int a[5];
You don't need to initialize an array on declaration. Instead you can declare the variable, and all memory slots will be uninitialized as with scalar variables. It may be difficult to reason about which indicies have been initialized, so it is good practice to carefully initialize arrays before usage, e.g., with a for loop. Be careful about crafting the loop, as mistakes like off-by-one errors can lead to uninitialized indicies or out-of-bounds array accesses.
Initializer loop
int a[5];
for (int i = 0; i < 5; i++) {
a[i] = i + 1;
}
Make the for loop match the array, i.e., start at 0 and end at the array length (exclusive).
C gotcha: out-of-bounds array accesses
Compilers can catch simple-to-reason-about instances. In general, though, it is difficult or impossible to do so. For simplicity of implementation, most C compilers will produce machine code that performs the out of bounds memory access.
Example: what does this array contain?
int a[10];
for (int i = 0; i < 10; i++) {
a[i] = i * i;
}
Example: what does this array contain?
int a[10];
for (int i = 0; i < 10; i++) {
a[i] = 1;
}
for (int i = 1; i < 10; i++) {
a[i] = a[i - 1] * 2;
}
Example: what does this array contain?
int a[10];
for (int i = 0; i < 1; i++) {
a[i] = 0;
}
for (int i = 1; i < 3; i++) {
a[i] = 1;
}
for (int i = 3; i < 10; i++) {
a[i] = a[i - 1] + a[i - 2];
}
Notice how the for-loop boundaries "link" together when using the inclusive/exclusive pattern, i.e., the first assigment is [0, 1), and the first loop is from [1, 3), while the second loop is from [3, 10). It's easy to see that this array is completely initialized by joining [0, 1), [1, 3) and [3, 10) to be [0, 10), the complete set of array indices.
Example: dot product
Tic-tac-toe
- 3x3 board
- 2 players take turns placing X and O
bool type
#include <stdbool.h>
int main(int argc, char **argv) {
bool b;
b = true;
if (b) {
} else {
}
b = false;
if (b) {
} else {
}
int x = 1;
b = x > 0;
if (b) {
} else {
}
}
Game code
#include <stdio.h>
#include <assert.h>
#include <stdbool.h>
#include "term.c"
int main(int argc, char **argv) {
set_input_mode ();
char board[] = { '1', '2', '3', '4', '5', '6', '7', '8', '9' };
int boardlen = 9;
enum { xturn, oturn, xwin, owin, tie };
int state = xturn;
// turn 0 is x, turn 1 is o.
while (1) {
printf("\x1b[2J");
printf("\x1b[H");
// draw board
for (int i = 0; i < boardlen; i++) {
switch (board[i]) {
case 'O':
printf("\033[31m");
break;
case 'X':
printf("\033[32m");
break;
default:
printf("\033[30m");
break;
}
putchar(board[i]);
putchar(' ');
if ((i % 3) == 2) {
putchar('\n');
}
}
// draw state and quit if done
switch (state) {
case xturn:
printf("\033[32m");
printf("X's turn\n");
break;
case oturn:
printf("\033[31m");
printf("O's turn\n");
break;
case xwin:
printf("\033[32m");
printf("X wins\n");
exit(0);
break;
case owin:
printf("\033[31m");
printf("O wins\n");
exit(0);
break;
case tie:
printf("\033[30m");
printf("tie\n");
exit(0);
break;
default:
// should never reach here
assert(0);
break;
}
assert(xturn == state || oturn == state);
// user input
char c;
scanf("%c", &c);
// search for board index
int index = -1;
for (int i = 0; i < boardlen; i++) {
if (c == board[i]) {
index = i;
}
}
// check for valid move
if ( 0 <= index && index < 9
&& !('X' == board[index] || 'O' == board[index])) {
// place the X or O
switch (state) {
case xturn:
board[index] = 'X';
state = oturn;
break;
case oturn:
board[index] = 'O';
state = xturn;
break;
default:
assert(0);
break;
}
}
// check for win
bool is_xwin = false;
bool is_owin = false;
// horizontal and vertical
for (int i = 0; i < 3; i++) {
is_xwin = is_xwin || ( 'X' == board[i * 3 + 0]
&& 'X' == board[i * 3 + 1]
&& 'X' == board[i * 3 + 2])
|| ( 'X' == board[0 + i]
&& 'X' == board[3 + i]
&& 'X' == board[6 + i]);
is_owin = is_owin || ( 'O' == board[i * 3 + 0]
&& 'O' == board[i * 3 + 1]
&& 'O' == board[i * 3 + 2])
|| ( 'O' == board[0 + i]
&& 'O' == board[3 + i]
&& 'O' == board[6 + i]);
}
//diagonal
is_xwin = is_xwin
|| ( 'X' == board[0] && 'X' == board[4] && 'X' == board[8] )
|| ( 'X' == board[2] && 'X' == board[4] && 'X' == board[6] );
is_owin = is_owin
|| ( 'O' == board[0] && 'O' == board[4] && 'O' == board[8] )
|| ( 'O' == board[2] && 'O' == board[4] && 'O' == board[6] );
assert(! (is_xwin && is_owin));
if (is_xwin) {
state = xwin;
} else if (is_owin) {
state = owin;
} else { // no one has won (yet)
// check for filled board
int turns = 0;
for (int i = 0; i < boardlen; i++) {
if ('O' == board[i] || 'X' == board[i]) {
turns++;
}
}
if (boardlen == turns) { // check for tie
state = tie;
} else {
// no win or tie, keep playing
}
}
}
}
wget https://www.cs.ucf.edu/~gazzillo/teaching/cop3223hspring26/files/term.c
State machine
| State |
|---|
| x's turn |
| o's turn |
| x wins |
| o wins |
| tie |
Transitions
- x's turn
- valid move -> o's turn
- winning move -> x wins
- tying move -> tie
- invalid move -> x's turn
- o's turn
- valid move -> x's turn
- winning move -> o wins
- tying move -> tie
- invalid move -> o's turn
enum
- Give names to numbers.
enum { xturn, oturn, xwin, owin, tie };
int game_state = xturn;
Compiler assigns integers under the hood.
enum values in switch statements
- Makes it more intuitive to track state.
switch(game_state) {
case xturn:
// ...
break;
case oturn:
// ...
break;
case xwin:
// ...
break;
case owin:
// ...
break;
case tie:
// ...
break;
default:
// ...
break;
}
Multidimensional arrays
#include <stdio.h>
int main(int argc, char **argv) {
int a[3][3];
a[0][1] = 10;
}
Example: tic-tac-toe
Example: matrix multiplication
Strings
Recall the "hello, world!\n" program:
printf("hello, world!\n");
Strings are character arrays
char str[] = "hello, world!\n";
printf("%s", str); // %s is for strings (character arrays)
#include <stdio.h>
int main(int argc, char **argv) {
/* printf("hello, world!\n"); */
char str[] = "hello, world!\n";
char nullchar = '\0';
/* printf("%s", str); */
int len = 0;
printf("print using a for loop\n");
/* for (int i = 0; i < 14; i++) { */
/* putchar(str[i]); */
/* } */
char c;
int i = 0;
while ( (c = str[i++]) != '\0') {
putchar(c);
}
}
#include <stdio.h>
int main(int argc, char **argv) {
printf("hello, world!\n");
char str[] = "hello, world!\n";
printf("%s", str);
str[7] = 'p';
str[8] = 'a';
str[9] = 'u';
str[10] = 'l';
str[11] = '!';
printf("%s", str);
}
Recall: the character type
- Characters represent single digits, letters, punctuation, etc.
- Wrapped with single quotes:
'
Recall that there are also non-printable characters like '\n';
Strings are null-delimited
Diagram
- Memory slots of string character array
- Null character ends the string
'\0'.
By convention, strings do not have fixed sizes, but end with a special character, null '\0'
Why Pascal Is Not My Favorite Programming Language : Brian W. Kernighan : Fre…
Character arrays
char chararray[] = { 'h', 'e', 'l', 'l', 'o', '!', '\n', '\0' };
printf("%s", chararray);
Just like integer arrays, we can make a character array.
char chararraydelim[] = { 'h', 'e', 'l', 'l', 'o', '!', '\n', '\0', 'X', '\n' };
printf("%s", chararraydelim);
char chararraynodelim[] = { 'h', 'e', 'l', 'l', 'o', '!', '\n', 'X', '\n' };
printf("%s", chararraynodelim);
#include <stdio.h>
int main(int argc, char **argv) {
/* char str[] = "hello, world!\n"; */
/* printf("%s\n", str); */
/* str[7] = 'p'; */
/* str[8] = 'a'; */
/* str[9] = 'u'; */
/* str[10] = 'l'; */
/* str[11] = '!'; */
/* printf("%s", str); */
char chararray[] = { 'h', 'e', 'l', 'l', 'o', '!', '\n', '\0' };
printf("FIRST: %s", chararray);
char chararraydelim[] = { 'e', 'e', 'l', 'l', 'o', '!', '\n', '\0', 'X', '\n' };
printf("SECOND: %s", chararraydelim);
char chararraynodelim[] = { 'h', 'e', 'l', 'l', 'o', '!', '\n', 'X', '\n' };
printf("THIRD: %s", chararraynodelim);
}
String literals
- Like integer (
1,43, etc.), float, or characters - Wrap with double quotes:
"
char chararray = "hello!\n";
This array has the same contents as the previous array
Example: counting characters in a string
Write a program that, given a string str, counts how often a character c appears in string.
String operations
#include <string.h>
Getting length
With a loop
int i = 0;
while ('\0' != s[i]) {
i++;
}
int i = 0;
while ('\0' != s[i++]);
Function
strlen
Compare strings
#include <stdio.h>
int main(int argc, char **argv) {
char str1[] = "hello!\n";
char str2[8] = { 'h', 'e', 'l', 'l', 'o', '!', '\n', '\0' };
printf("%s", str1);
printf("%s", str2);
printf("str1 == str2?\n");
if (str1 == str2) {
printf("yes!\n");
} else {
printf("no!\n");
}
printf("str1[0] == str2[0]?\n");
if (str1[0] == str2[0]) {
printf("yes!\n");
} else {
printf("no!\n");
}
}
str1 and str2 are the addresses of the beginning of the string, not the actual characters.
str1 and str2 are pointing to different memory slots, so these are not the same.
Example: compare strings by hand
Safe(r) usage
| Operation | Function |
|---|---|
strncmp |
Compare two strings |
fgets |
Read a string |
strlcpy |
Copy a string |
Comparing strings
strncmp
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv) {
char str1[] = "hello!\n";
char str2[8] = { 'h', 'e', 'l', 'l', 'o', '!', '\n', '\0' };
printf("%s", str1);
printf("%s", str2);
printf("str1 == str2?\n");
if (str1 == str2) {
printf("yes!\n");
} else {
printf("no!\n");
}
printf("str1[0] == str2[0]?\n");
if (str1[0] == str2[0]) {
printf("yes!\n");
} else {
printf("no!\n");
}
printf("%d\n", strncmp(str1, str2, 8));
if (strncmp(str1, str2, 8) == 0) {
printf("yes!\n");
} else {
printf("no!\n");
}
}
The strcmp() function compares the two strings s1 and s2. The locale is not taken into account (for a locale-aware comparison, see strcoll(3)). The comparison is done using unsigned characters. strcmp() returns an integer indicating the result of the comparison, as follows: • 0, if the s1 and s2 are equal; • a negative value if s1 is less than s2; • a positive value if s1 is greater than s2. The strncmp() function is similar, except it compares only the first (at most) n bytes of s1 and s2.
Reading strings
fgets with size less than some given value
#include <stdio.h>
int main(int argc, char **argv) {
int destlen = 20;
char dest[destlen];
// try less than, equal to, and greater than destlen (null terminator?)
fgets(dest, destlen, stdin);
printf("%s\n", dest);
}
gets, never use this function
gets(3) Library Functions Manual gets(3)
NAME
gets - get a string from standard input (DEPRECATED)
LIBRARY
Standard C library (libc, -lc)
SYNOPSIS
#include <stdio.h>
[[deprecated]] char *gets(char *s);
DESCRIPTION
Never use this function.
fgets() reads in at most one less than size characters from stream and stores them into the buffer pointed to by s.
Reading stops after an EOF or a newline. If a newline is read, it is stored into the buffer. A terminating null
byte ('\0') is stored after the last character in the buffer.
Copying strings
strlcpy
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv) {
char str[] = "hello!";
int destlen = 3;
/* int destlen = 20; */
char dest[destlen];
strlcpy(dest, str, destlen);
printf("%s\n", dest);
}
If we just try to copy using assignment, we will overwrite the original string.
strlcpy and strlcat - consistent, safe, string copy and concatenation.