Added Ex. 38-43 for pointers, updated README

Added topics beyond the language basics from ziglearn.org
to the README. That's a lot of exercises. I'd like to keep
it under 100, though!
This commit is contained in:
Dave Gauer 2021-02-08 20:35:28 -05:00
parent adf5ddb27d
commit cf0920de31
8 changed files with 252 additions and 12 deletions

View file

@ -48,6 +48,4 @@ pub fn main() void {
std.debug.print("Character {} - G:{} H:{} XP:{}\n",
.{num+1, c.gold, c.health, c.experience});
}
std.debug.print("\n", .{});
}

36
39_pointers.zig Normal file
View file

@ -0,0 +1,36 @@
//
// Check this out:
//
// var foo: u8 = 5; // foo is 5
// var bar: *u8 = &foo; // bar is a pointer
//
// What is a pointer? It's a reference to a value. In this example
// bar is a reference to the memory space that current contains the
// value 5.
//
// A cheatsheet given the above declarations:
//
// u8 the type of a u8 value
// foo the value 5
// *u8 the type of a pointer to a u8 value
// &foo a reference to foo
// bar a pointer to the value at foo
// bar.* the value 5 (the dereferenced value "at" bar)
//
// We'll see why pointers are useful in a moment. For now, see if you
// can make this example work!
//
const std = @import("std");
pub fn main() void {
var num1: u8 = 5;
var num1_pointer: *u8 = &num1;
var num2: u8 = undefined;
// Please make num2 equal 5 using num1_pointer!
// (See the "cheatsheet" above for ideas.)
num2 = ???;
std.debug.print("num1: {}, num2: {}\n", .{num1, num2});
}

27
40_pointers2.zig Normal file
View file

@ -0,0 +1,27 @@
//
// It's important to note that variable pointers and constant pointers
// are different types.
//
// Given:
//
// var foo: u8 = 5;
// const bar: u8 = 5;
//
// Then:
//
// &foo is of type "*u8"
// &bar is of type "*const u8"
//
// You can always make a constant pointer to a variable, but you cannot
// make a variable pointer to a constant. This sounds like a logic puzzle,
// but it just means that once data is declared immutable, you can't
// coerce it to a mutable type. It's a safety thing (to prevent mistakes).
//
const std = @import("std");
pub fn main() void {
const a: u8 = 12;
const b: *u8 = &a; // fix this!
std.debug.print("a: {}, b: {}\n", .{a, b.*});
}

41
41_pointers3.zig Normal file
View file

@ -0,0 +1,41 @@
//
// The tricky part is that the pointer's mutability (var vs const) refers
// to the ability to change what the pointer POINTS TO, not the ability
// to change the VALUE at that location!
//
// const locked: u8 = 5;
// var unlocked: u8 = 10;
//
// const p1: *const u8 = &locked;
// var p2: *const u8 = &locked;
//
// Both p1 and p2 point to constant values which cannot change. However,
// p2 can be changed to point to something else and p1 cannot!
//
// const p3: *u8 = &unlocked;
// var p4: *u8 = &unlocked;
// const p5: *const u8 = &unlocked;
// var p6: *const u8 = &unlocked;
//
// Here p3 and p4 can both be used to change the value they point to but
// p3 cannot point at anything else.
// What's interesting is that p5 and p6 act like p1 and p2, but point to
// the value at "unlocked". This is what we mean when we say that we can
// make a constant reference to any value!
//
const std = @import("std");
pub fn main() void {
var foo: u8 = 5;
var bar: u8 = 10;
// Please define pointer "p" so that it can point to EITHER foo or
// bar AND change the value it points to!
??? p: ??? = undefined;
p = &foo;
p.* += 1;
p = &bar;
p.* += 1;
std.debug.print("foo={}, bar={}\n", .{foo, bar});
}

33
42_pointers4.zig Normal file
View file

@ -0,0 +1,33 @@
//
// Now let's use pointers to do something we haven't been
// able to do before: pass a value by reference to a function!
//
const std = @import("std");
pub fn main() void {
var num: u8 = 1;
var more_nums = [_]u8{ 1, 1, 1, 1 };
// Let's pass a reference to num to our function and print it:
makeFive(&num);
std.debug.print("num: {}, ", .{num});
// Now something interesting. Let's pass a reference to a
// specific array value:
makeFive(&more_nums[2]);
// And print the array:
std.debug.print("more_nums: ", .{});
for (more_nums) |n| {
std.debug.print("{} ", .{n});
}
std.debug.print("\n", .{});
}
// This function should take a reference to a u8 value and set it
// to 5.
fn makeFive(x: *u8) void {
??? = 5; // fix me!
}

84
43_pointers5.zig Normal file
View file

@ -0,0 +1,84 @@
//
// Passing integer pointers around is generally not something you're going
// to do. Integers are cheap to copy.
//
// But you know what IS useful? Pointers to structs:
//
// const Vertex = struct{ x: u32, y: u32, z: u32 };
//
// var v1 = Vertex{ .x=3, .y=2, .z=5 };
//
// var pv: *Vertex = &v1; // <-- a pointer to our struct
//
// Note that you don't need to dereference the "pv" pointer to access
// the struct's fields:
//
// YES: pv.x
// NO: pv.*.x
//
// We can write functions that take pointer arguments:
//
// fn foo(v: *Vertex) void {
// v.x += 2;
// v.y += 3;
// v.z += 7;
// }
//
// And pass references to them:
//
// foo(&v1);
//
//
// Let's revisit our RPG example and make a printCharacter() function
// that takes a Character pointer.
//
const std = @import("std");
const Class = enum{
wizard,
thief,
bard,
warrior,
};
const Character = struct{
class: Class,
gold: u32,
health: u8,
experience: u32,
};
pub fn main() void {
var glorp = Character{
.class = Class.wizard,
.gold = 10,
.health = 100,
.experience = 20,
};
// FIX ME!
// Please pass our Character "glorp" to printCharacter():
printCharacter( ??? );
}
// Note how this function's "c" parameter is a pointer to a Character struct.
fn printCharacter(c: *Character) void {
// Here's something you haven't seen before: when switching an enum, you
// don't have to write the full enum name. Zig understands that ".wizard"
// means "Class.wizard" when we switch on a Class enum value:
const class_name = switch (c.class) {
.wizard => "Wizard",
.thief => "Thief",
.bard => "Bard",
.warrior => "Warrior",
};
std.debug.print("{s} (G:{} H:{} XP:{})", .{
class_name,
c.gold,
c.health,
c.experience,
});
}

View file

@ -84,24 +84,40 @@ Planned exercises:
* [x] Switch
* [x] Unreachable
* [x] Enums
* [ ] Structs
* [ ] Unions
* [ ] Pointers
* [ ] Pointer sized integers
* [x] Structs
* [x] Pointers
* [ ] Multi pointers
* [ ] Slices
* [ ] Integer rules
* [ ] Floats
* [ ] Labelled blocks
* [ ] Labelled loops
* [ ] Unions
* [ ] Numeric types (integers, floats)
* [ ] Labelled blocks and loops
* [ ] Loops as expressions
* [ ] Optionals
* [ ] Comptime
* [ ] Inline loops
* [ ] Inline loops (how to DEMO this?)
* [ ] Anonymous structs
* [ ] Sentinel termination
* [ ] Vectors
* [ ] Imports
* [ ] Allocators
* [ ] Arraylist
* [ ] Filesystem
* [ ] Readers and Writers
* [ ] Formatting
* [ ] JSON
* [ ] Random Numbers
* [ ] Crypto
* [ ] Threads
* [ ] Hash Maps
* [ ] Stacks
* [ ] Sorting
* [ ] Iterators
* [ ] Formatting specifiers
* [ ] Advanced Formatting
* [ ] Suspend / Resume
* [ ] Async / Await
* [ ] Nosuspend
* [ ] Async Frames, Suspend Blocks
The initial topics for these exercises were unabashedly cribbed from
[ziglearn.org](https://ziglearn.org/). I've since moved things around

View file

@ -70,7 +70,7 @@ function check_it {
# I've chosen to explicitly number AND list each exercise rather than rely
# on sorting. Though it does mean manually renaming things to remove/insert,
# it's worked out well so far because its explicit and foolproof.
# it's worked out well so far.
check_it 01_hello.zig "Hello world" "Note the error: the source file has a hint for fixing 'main'."
check_it 02_std.zig "Standard Library"
@ -110,6 +110,11 @@ check_it 35_enums.zig "1 2 3 9 8 7" "This problem seems familiar..."
check_it 36_enums2.zig "#0000ff" "I'm feeling blue about this."
check_it 37_structs.zig "Your wizard has 90 health and 25 gold."
check_it 38_structs2.zig "Character 2 - G:10 H:100 XP:20"
check_it 39_pointers.zig "num1: 5, num2: 5" "Pointers aren't so bad."
check_it 40_pointers2.zig "a: 12, b: 12"
check_it 41_pointers3.zig "foo=6, bar=11"
check_it 42_pointers4.zig "num: 5, more_nums: 1 1 5 1"
check_it 43_pointers5.zig "Wizard (G:10 H:100 XP:20)"
echo
echo " __ __ _ "