From cf0920de31e9b5f3c5ba6de19a1b4c8d0c58b907 Mon Sep 17 00:00:00 2001 From: Dave Gauer Date: Mon, 8 Feb 2021 20:35:28 -0500 Subject: [PATCH] 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! --- 38_structs2.zig | 2 -- 39_pointers.zig | 36 +++++++++++++++++++++ 40_pointers2.zig | 27 ++++++++++++++++ 41_pointers3.zig | 41 +++++++++++++++++++++++ 42_pointers4.zig | 33 +++++++++++++++++++ 43_pointers5.zig | 84 ++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 34 ++++++++++++++------ ziglings | 7 +++- 8 files changed, 252 insertions(+), 12 deletions(-) create mode 100644 39_pointers.zig create mode 100644 40_pointers2.zig create mode 100644 41_pointers3.zig create mode 100644 42_pointers4.zig create mode 100644 43_pointers5.zig diff --git a/38_structs2.zig b/38_structs2.zig index 9a67c25..b6def93 100644 --- a/38_structs2.zig +++ b/38_structs2.zig @@ -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", .{}); } diff --git a/39_pointers.zig b/39_pointers.zig new file mode 100644 index 0000000..25b56c6 --- /dev/null +++ b/39_pointers.zig @@ -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}); +} diff --git a/40_pointers2.zig b/40_pointers2.zig new file mode 100644 index 0000000..b046dc1 --- /dev/null +++ b/40_pointers2.zig @@ -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.*}); +} diff --git a/41_pointers3.zig b/41_pointers3.zig new file mode 100644 index 0000000..21a43bd --- /dev/null +++ b/41_pointers3.zig @@ -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}); +} diff --git a/42_pointers4.zig b/42_pointers4.zig new file mode 100644 index 0000000..e6b8964 --- /dev/null +++ b/42_pointers4.zig @@ -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! +} diff --git a/43_pointers5.zig b/43_pointers5.zig new file mode 100644 index 0000000..adfaea1 --- /dev/null +++ b/43_pointers5.zig @@ -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, + }); +} diff --git a/README.md b/README.md index 61d02fb..880b385 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/ziglings b/ziglings index b328b81..402d867 100755 --- a/ziglings +++ b/ziglings @@ -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 " __ __ _ "