Learn C With Me #07 — Strings | Live

In Lesson 7 of Learn C With Me, we finally wrap up our journey through arrays in C and move on to our next major topic: strings.

Using Beej’s Guide to C as our study material, we finish the remaining section on passing multidimensional arrays to functions and then discover something very C-like: strings aren’t really a separate built-in type at all. Instead, they’re closely connected to arrays, pointers, and memory.

We cover:

  • Passing multidimensional arrays to functions
  • Why C needs to know all array dimensions except the first
  • How multidimensional array indexes translate into memory offsets
  • A final look at the relationship between arrays and pointers
  • String literals in C
  • The difference between "double quotes" and 'single quotes'
  • Escaping characters inside strings
  • String variables
  • Representing strings using char *
  • Representing strings as arrays of char
  • Accessing individual characters using array notation
  • The difference between sizeof on an array and sizeof on a pointer
  • Using strlen() and <string.h>
  • Why strlen() returns size_t
  • String initializers
  • The difference between a pointer to a string literal and a mutable character array
  • Why attempting to modify a string literal results in undefined behavior
  • Null-terminated strings
  • The special '\0' character
  • How C knows where a string ends
  • Writing our own simple version of strlen()
  • Copying strings
  • Why assigning one string pointer to another doesn’t actually copy the string
  • How two pointers can refer to the same memory

One of the biggest takeaways from this lesson is that C doesn’t keep track of a string’s length for us. A C string is essentially a sequence of characters in memory followed by a special '\0' terminator. We even look at how a simple strlen()-style function can find the length by walking through those characters until it reaches the terminator.

We also run into an important distinction between char *s = "Hello"; and char s[] = "Hello";. They might initially look almost interchangeable, but their behavior becomes very different when we try to modify the data. This gives us another practical look at the relationship between pointers, arrays, strings, and memory.

Finally, we experiment with copying strings and see why simply assigning one pointer to another doesn’t create an independent copy. Both pointers can end up referring to the same underlying data, so modifying it through one pointer affects what we see through the other.

As usual, we experiment, make mistakes, follow a few programming-related side quests, get help from the live chat, and try to understand why C behaves the way it does rather than simply memorizing syntax.

For homework try testing your arrays skill on making a tic-tac-toe game. Or at least attempting to make one and display it here, as code, so we can discuss.

might have missunderstood the asingment but did make it so 2 players played tictactoe how ever i did not understand how to alternate player one and two moves. nor did i want to do all the turn order logic or terminal move inputs with wait functions. but found it bit fun to do i used 2 types of arrays for example use of arrays.

Spoiler code
#include <stdio.h>

int main(void) {

char tictac\[3\]\[3\]={

    {'\_','\_','\_'},

    {'\_','\_','\_'},

    {'\_','\_','\_'}

};

printf("empty board\\n");

for (int i=0; i<3; i++) {

    for (int j=0; j<3; j++) {

        printf("%c ", tictac\[i\]\[j\]);

        if (j == 2) {

            printf("\\n");

        };

    }

}

char p1\[3\]\[3\]={

    {'X','\_','\_'},

    {'\_','X','\_'},

    {'\_','\_','X'}

};

char p2\[\]\[3\]={

    {'\_','\_','O'},

    {'\_','\_','\_'},

    {'O','O','\_'}

};



for (int i=0; i<3; i++) {

    for (int j=0; j<3; j++) {

        if (p1\[i\]\[j\] == 'X') {

            tictac\[i\]\[j\]= 'X';

            printf("Player one placed X at Row:%d Column:%d\\n", i, j);

        };

        if (p2\[i\]\[j\] == 'O') {

            tictac\[i\]\[j\]= 'O';

            printf("Player two placed O at Row:%d Column:%d\\n", i, j);

        };

    }

}

printf("Players one wins\\n");

for (int i=0; i<3; i++) {

    for (int j=0; j<3; j++) {

        printf("%c ", tictac\[i\]\[j\]);

        if (j == 2) {

            printf("\\n");

        };

    }

}

};

Thanks for spoiler tag. I will do mine first, then look at yours.

I started with pluses and minuses to draw, and realized it does not look good so I searched for UTF-8 characters that would better draw the grid but they did not count as regular characters so this was an interesting exercise because my odd rows are 16 long while my even rows are 34 long.

It took me an hour to come up with just this: row1[i] != '\0'

#include <stdio.h>

int main(void){
    char row1[] = " 1 │ 2 │ 3 ";
    char row2[] = "───┼───┼───";
    char row3[] = " 4 │ 5 │ 6 ";
    char row4[] = "───┼───┼───";
    char row5[] = " 7 │ 8 │ 9 ";

    for (int i = 0; row1[i] != '\0'; i++)
        printf("%c", row1[i]);
    printf("\n");
    for (int i = 0; row2[i] != '\0'; i++)
        printf("%c", row2[i]);
    printf("\n");
    for (int i = 0; row3[i] != '\0'; i++)
        printf("%c", row3[i]);
    printf("\n");
    for (int i = 0; row4[i] != '\0'; i++)
        printf("%c", row4[i]);
    printf("\n");
    for (int i = 0; row5[i] != '\0'; i++)
        printf("%c", row5[i]);
    printf("\n");
}

The reasoning while I have number in those fields where X and O will eventually come is because I think this could be a good way to actually finish this game later on, with character replacement.

I will look at what Iceblade has done, now. I did not see it yet.

Holy shit Iceblade beat the crap out of me. I thought I only need to use strings to draw the table, not the entire functional game.

one think a notesed in your code is that most of it could have been done in one loop and that you did make structs but home work asks for more then 1 way sens you only did row[] but could have been row[numbers] like the lesson was about.

Now I am looking at my code and wondering why didn’t compiler warn me about having two rows inside the for loop without curly braces. How come my code even works. I see how I can make one loop, but I think curly braces are needed.

EDIT: Actually no. You confused me. The first line in the for loop is a part of for loop and the newline one is outside of the loop, preparing terrain for the next loop.

I did one too:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>

const char logos[3][4] = {" ", {0xef, 0x8c, 0x83, 0x00}, {0xef, 0x8c, 0x86, 0x00}};

int has_won(int board[3][3], int player) {
  int n;
  player += 1;

  // horizontal
  for (int row = 0; row < 3; ++row) {
    n = 0;
    for (int col = 0; col < 3; ++col)
      n += board[row][col] == player;
    if (n == 3) return 1;
  }

  // vertical
  for (int col = 0; col < 3; ++col) {
    n = 0;
    for (int row = 0; row < 3; ++row)
      n += board[row][col] == player;
    if (n == 3) return 1;
  }

  // diagonal right & down
  n = 0;
  for (int i = 0; i < 3; ++i)
    n += board[i][i] == player;
  if (n == 3) return 1;

  // diagonal right & up
  n = 0;
  for (int i = 0; i < 3; ++i)
    n += board[2-i][i] == player;
  if (n == 3) return 1;

  return 0;
}

void draw_board(int board[3][3]) {
  const char template[] =
    "    1   2   3\n"
    "  +---+---+---+\n"
    "A | ? | ? | ? |\n"
    "  +---+---+---+\n"
    "B | ? | ? | ? |\n"
    "  +---+---+---+\n"
    "C | ? | ? | ? |\n"
    "  +---+---+---+\n";
  
  int i = 0;
  for (const char *t = template; *t; ++t) {
    if (*t == '?') {
      int player = board[i/3][i%3];
      fputs(logos[player], stdout);
      ++i;
    } else {
      putchar(*t);
    }
  }
}

enum input_result {
  QUIT,
  INVALID,
  POSITION,
};

enum input_result get_input(int *row, int *col) {
  char readbuf[4];
  if (!fgets(readbuf, sizeof(readbuf), stdin)) return QUIT;
  if (readbuf[0] == 'q') return QUIT;
  if (strlen(readbuf) < 2) return INVALID;

  const char *s = "acAC";
  for (; *s; s += 2) {
    if (readbuf[0] >= s[0] && readbuf[0] <= s[1]) {
      *row = readbuf[0] - s[0];
      break;
    }
  }
  if (!*s) return INVALID;

  if (readbuf[1] >= '1' && readbuf[1] <= '3') {
    *col = readbuf[1] - '1';
  } else return INVALID;

  return POSITION;
}


int main(int argc, char const *argv[]) {
  int board[3][3] = {0};
  int quit = 0;
  int won = 0;
  int player = 0;

  draw_board(board);
  puts("To make a move, enter <row><column> e.g. a2\n"
       "Enter q or ctrl-d to quit\n" ); 

  while (!(quit || won)) {
    printf("%s's turn: ", logos[player+1]);

    int row, col;
    switch (get_input(&row, &col)) {
      case QUIT:
        quit = true;
        break;
      case INVALID:
        putchar('\a');
        break;
      case POSITION:
        if (board[row][col] != 0) {
          putchar('\a');
        } else {
          board[row][col] = player + 1;
          if (has_won(board, player)) {
            won = true;
          } else {
            player = (player + 1) % 2;
          }
        }
        break;
    }
    if (!quit || won) {
      draw_board(board);
    }
  }

  if (won) {
    printf("%s wins!\n", logos[player+1]);
  } else {
    puts("Bye!");
  }
}

2 Likes

well that was a read i saw you used alot of things the lessons have not coverd yet but was fun to see what it would look like wen someone who knows what there doing and your enum was uniqe to the point i fought is was magic olmost XD

I’m still learning :slight_smile: My input function got some problems I can see. Maybe we’ll get to some of that stuff later.

Code patreon provided an extra homework:

Write a function which accepts 3 parameters:

  1. Source string.
  2. Number representing the index of a word.
  3. Destination string.

It should copy an N-th word from source string to destination. Words are sequences of non-space characters delimited by space (assume it’s a single space).
If it can’t find the N-th word then function should return false, otherwise it should return true.