 Welcome back to 105. So, did a whole bunch of strings today was, or this week was string week. Anyone, any questions about strings that we've had so far? Other than that, we're ahead of other sections, so we can just go ahead and practice today. So, pop quiz then. What is the last value in the C string? Yeah? The null byte. The null byte, right? All right, good. So, using that, we should be able to carry our adventure. So that is the main thing to take away from C strings. It's just an array of characters, but only difference is that it ends in a null byte. So, we'll do two quick exam questions and then I will show you something cool and possibly useful, but let's take a look at the exam question. So, this says, complete the following function below to alter a string to remove any leading zeros. You are not allowed to change any part of the provided C program. So, they want us to write this print num function. They give us i equals zero while the character, the current character, the string is a zero, increment i. Then we're allowed to write something on this line right before the print f and I guess the print f should just print out the number without any leading zeros. Whoops. All right, my camera is probably gonna get stuck down there. Nope, we're good. All right, so, they give us a little main that they kind of test the program with. So, it just declares a array of characters which is of length 10. So, let's make sure that actually fits. So, one, two, three, four, five, six, seven, eight, nine, and then a null byte, so, fits. So, something weird wouldn't happen. So, that's a good thing to Sandy check even though they don't ask you to. Then it calls print num. So, it says, whenever we complete the program it should print out, well, just get rid of all the zeros and print out eight, nine, eight, seven, six. So, we can see what it does initially. So, initially, well, if we wanna test it, string would be pointing to the first character in this C string. And, we would have a variable whenever we use this function called, let's use purple, I, and it would start with the value zero. So, if we execute this function with that test function, well, while string I equals zero, so right now I is equal to zero. So, we look at the first character and see that it is a zero, so that is true. So, all we'll do is increment I and then go through and try the loop again. Next time through the loop we're looking at the character at index one which is this zero. So, that would be true. We'd go ahead, increment I from one to two. Then, look at that character. Okay, well, that is also a zero. So, we would increment I, go through, move it again, and then finally, go through, look at the next character. Okay, move I again, now it's four. And now, whenever we looked at the character at index four, it would be an eight. So, that would be false, and we would break out of the loop and we would run this code. So, currently, I is equal to four and maybe we can use that to go ahead and get rid of all these zeros because it seems to also match the number of zeros. It just count the number of zeros for us. So, if I wanted to make sure that line printed just the string without any leading zeros, what should I write on the next line? This question I think was worth a bunch of marks and we can solve it in one line. So, right now, if I ran it, it would just print like zero, zero, zero, zero and then the actual number. What if I did something like string equals string plus one? What would it print? Three zeros and then a number. So, if I is the number of zeros, how would I make it just print the number without any zeros? Yeah. Plus I, yeah, so I can solve this in one line. It could just be string equals string plus I. So, that does pointer arithmetic. So, all it's doing is reassigning the pointer string to be equal to, well, the old value of string which would point at the first zero and then plus I, so in this case it's four. So, it would just move it up four characters. So, one, two, three, four and then if we print it as a string, well, still has an all byte at the end. It's still a C string. We're just skipping the first four characters. So, if I wanted to write that even smaller, I could just do string plus equals I and get all my marks, yay. So, questions about that one? And we can do that because it says you can alter the string. Well, it turns out we didn't really have to alter it. Doesn't say you have to keep the original one intact or return a pointer to the original or anything like that. So, we can go ahead and reuse it. We don't have to copy the string. We don't have to do anything like that. Yep. So, if I did, instead, I did string equals to string square bracket I. So, what would that do? So, what is string square bracket I? Yeah, eight, specifically, that's the character eight, right? And since it's that character eight, we have a little bit of a mismatch because the type of the string, it's like a pointer to a character and eight itself is a character, right? So, that would be like the value of that character, which is just eight. So, if we say this, then, well, we would, whoops, then we would take eight and then, well, that's just some special number. It converts to, I forget the exact same in the forties or something like that. And then we would use forties as an address and then we'd probably get some weird bug. So, I've seen this on Piazza people. I've used this with like lab six and stuff like that and they have encountered weird errors because of that. So, just do not assign a pointer to a character. Otherwise, you'll have trouble. So, basically, whatever you use the erase subscript, whatever the type of this is, you just get rid of a star and that's like the resulting type. So, if I access an element of a char star, well, that will just give me a character and be some, just a single character. So, any other questions about this one? Okay, well, let's do the long version and then do something fun. So, this has a lot of text, all right. So, write a function so called last string in string, the prototype of which is provided below. It returns a pointer to the last occurrence of string S1 in the string S2. If the string S1 cannot be found in the string S2, the function returns null. So, we're looking for a string within a string or a substring. So, we might immediately think we should use that stir stir function, but if we skip to the bottom, it says we can use any function except for the one that we probably want to use. So, our first idea is unfortunately out. So, then it says for this example, if we're looking for the string is as S1 in the string, this is a sample string. So, if we just look at all the matches of is, there's an is right here in this, and then there's one right here, a second one which is just the word is. So, if we used stir stir and looked for is, we would essentially just get this one, right, or pointer to that one. But we want the second one. So, it says the pointer should be the second is of the string. So, we should return essentially a pointer right there. So, then it gives another example. If we're looking for the string of the, in the string D with a capital T, apple, the function should return null. This is because T is lowercase in the, and in the actual string we're looking at, it is a capital. So, any ideas how we would solve this, and I'll give you a hint. The solution is very, very wordy and long, and I argue confusing. Yeah. Yeah. Yeah, so one way I could look at it. So, useful function might be something like stir n comp, something like that. And like I try to look for essentially this string in the other string. And like maybe I just start at the first character, see if it matches. And then if I have a match, well I still have to go until I find the last match, and then the last match can stay around. Yeah, I could do that. So I could try and see, starting at the beginning of the search string, like try and match those two characters with the two characters I'm looking for. So, this with is, not a match, this, not a match. Oh, this is a match, not a match, not a match. Oh, that's a match, and then nothing else would be a match, and maybe I return the other one. Could I do it in a way where I don't have to, as soon as I find the match, I can just finish. I can just be done. Why not? Yeah, we're looking for the last one. Is there any reason I can't do the opposite and be like, okay, start here, look at these two characters, no, okay, next, next, next, next, next, next, next, next, next, next, next. Oh yay, I'm done, I found is. So the way at least I thought of this initially is, okay, well, given I'm searching for is in a string, generally, if the size of this is like two, so it's length is two, well then, I start looking at the last two characters, and then I, in general, would look at all groups of two characters, do do do do do do do do like that, until I reach zero, and if I don't find it, then I return null, I haven't found it in the string, I can't move it past zero, otherwise memory errors, if I do this, very much memory errors over here, and then, yeah, I just start at the end. So if I were to write something like that, well, I kind of, I can use stir length, so that's probably a good function to use for the purposes of this, so what's it called? Last string in string, and I have to return a char. So if I had to write that, okay, I'm not gonna write the whole, I'm not sure, last string in string, and then char star S1, which I'm looking for, char star S2. So if I have something like that, I don't know, maybe I need the length of both strings because I need to know when to stop and how big of a thing to search for, so let's just get those just in case, so the string length of S1 in stir length is stir length of S2, so could do something like that, and then I have a for loop, so for int i, so I want to start at the very end, so if I wanted to start at the very end of S2, well, that would be like S2 len minus one, right? Because I wanna make, so it's zero indexed, so I could access the bytes from zero all the way up to S2 len minus one, although I could access S2 len, but it would be a null byte, right? So if I want to make sure that I start at the index such that in this case, I actually am looking at the beginning of two characters, well, here would be my length, sorry, I have my index at my first character, and then, well, I have to move it back, string length essentially plus one, so it turns out they cancel out, and the first character I would look at is S2 length minus S1 length, so in this case, I just, if I access index S2 length, it would be like here, which would be like a null byte, and then I just back up the length of S1 that I'm searching for, so I would back up two characters in this example, so I would start at the end, right? So that's what I would start with, and then my condition, well, I want to keep on going, oops, while I is greater or equal to zero, and then I could do not plus plus I, sorry, woo, minus minus I, so yeah, almost set myself up in some infinite loop, and then in here, then I can do the comparison, right? So I could do stir end comp with S2, or S1, and then S2 plus I, right? So I just want to move that character in this case, I want to move the S2 pointer to point to the N, so that would just be S2 plus I, and then I could give it S1 length, so I only want to look at the number of characters matching S1, and then I could check if it is zero, which means they are the same, so if the value is zero, then I simply just return S2 plus I, and then if I don't return, and I make it to the end of this for loop, then I know I haven't found diddly squat, and I can just return null. So questions about that one? Yeah, so the string comparison is byte-by-byte ASCII comparison in order, and yeah, so the question is just would this work if it was an int array instead, or same idea? Yeah, so everything's a number at the end of the day, but how they're stored in memory might be different in a way that causes it to behave slightly differently. I'd have to look at a specific example and weird things probably happen that you don't expect. Yeah, yeah, S2 is the big one, it's kind of weird. So not while that pointer wouldn't equal the null byte, when you dereference it. Yeah, yeah, yeah, yeah, yep, yep, yep, yeah. So the question is can I use just pointer arithmetic on that, like if I do plus plus A, and A is a pointer, or in this case, even if I do plus plus S2, and S2 is a pointer, that's the same as S2 equals S2 plus one, so that'll just do the pointer arithmetic, move it forward one element. Yeah, another one which I think is what the solution does is it goes forward, and just if there's a match, it stores it, and then at the end, it just returns the last match. So I could just make a variable called match, initialize it to null, go through it forward, and just update it every time I find a match, and then if there's a match, like I just return it when I'm done. And then if nothing's found, it'll still be null, will be good, otherwise it'll find the last one, right, because it'll keep on updating that variable. So both ways are fine. This way you would argue is like faster, so like you might have to match like 1,000 times if this was gigantic, but we don't really care, we just care if you get a solution, not the best solution. All right, other questions about that? All right, well here, question for you then. So if instead of stir and comp, if I just did stir comp and did this, would this work? Yeah, it would only work for like the first one, right, because first one, it would compare like IS, and for this one, it assumes that for stir comp, they both have to be null terminated strings, right? So S2 is just IS, that's the full C string, and then if I start at the beginning, or at the end, first time through would compare IS versus NG, but the second time through, when I move the pointer here, well then that string until the null byte would be ING, because well, there's not a null byte at G, so we would go ahead and just try and match the remaining string until it hits a new byte. So that's why we had to use stir and comp, and we had to limit it. Okay, so the next thing is not testable, but useful, because we will write a program that you might actually get to use, so fun fact, the main you have been writing is only one of two options. So there's actually another prototype for main, again, not covered in this course, but most people write programs like this, so if you ever wondered how your compiler works, if you did by hand, you wrote like GCC and then like a file name and something like that, well, this is how they wrote GCC, so they had to go ahead and take your function, like whatever your file name you typed and actually open a file and all that stuff, so GCC would have a main that looks more like this, and we can actually finally read it now, so looks a bit scary again, but main takes an integer, so argc and then anyone wanna tell me what the hell the type of this is, wait, uh-oh, stop it. Yeah, what is the type of that, or anyone wanna explain this to me in English? Yeah, yeah, well, I guess you can't tell me, so it's an array of character pointers, in this it is technically an array of C strings because they tell you that. So this version of main, it allows you to just access arguments as the user typed them, so argc is the number of C strings typed to run your program, well, they don't have to be C strings, it makes some C strings, so it's the number of strings typed to run your program, so if you ran GCC by hand and like the GCC file name and stuff like that, they would all be individual arguments and then argv is short for argument values and that is the actual array of C strings that you typed into your program and because it gives you the count, you know how many elements are in this array, so it basically tells you how many C strings there are. So the offering system, whatever I type stuff like on your terminal or sometimes it's called a command line, the offering system removes all the white space for you, so assuming I had a program named hex to decimal and if I ran it using like hex to des, 255, well then, oh crap, typo here, we would have argc equal to two and then just the way it works is the first argument or the argument at index zero is gonna be the name the user typed to run your program, so in here it would be hex to des and then the second one would be the next argument and it would just be the string 255. So we can write this program, so we can actually write a program that converts decimal to hex, so let's just write it. So we're going to not use, we saw a to i that tried to convert but I had to question last time of like what if you inputted an a or something like that, turns out the answer is it just returns the value of zero and you will not know if there's an error or not because the user could have actually typed zero, so we're going to have to write our own a to i because we're gonna have the rule where if there's any problems with the input or anything like that, we'll just exit with exit failure, so we wanna actually handle some invalid input here in this scenario, so we would have to think how do we take that string of 255 and convert it to an integer? So any thoughts of how I would do that? So we could say here that, so for example, if we input the string 255, we want to output the int 255. Yeah, 255 and decimal. Yeah, so first off, yeah, let's read character by character so that's a good start. So for int i equals zero, oh well, can't read character by character. I don't know how many things there are, well, I can. If I want, I could write it like this. So start i is zero and then check that this isn't an all byte and then I could increment i. So if I wanna write it in a for loop instead of a while, again, you can write whatever wherever you want. So this would go character by character, right? So how do I actually convert it to the decimal? So the first character would be like a two, the character two. So if I do that, yeah. So first, let's check that there's a valid number. So I'm not going to assume the user is smart at all, which is generally what you should do. So first I'll check that, okay, well, is si in this case, we only want the character zero through nine and they go in that order in terms of values. So I know it's invalid if the current character is less than zero, I don't know what the hell it is. Or if the current character is greater than nine, so that's the last one, I don't know what the hell it is. So I could just exit with exit failure and then we are at least better off than AOTI where we just stop running if the user does something stupid. So now I'm sure that that character is valid. So in this case, if I took the, if it was two, well then I would have the character two minus the ASCII value of zero. So this would also be ASCII. So it's the ASCII two minus the ASCII one and they all differ by one. So like for example, if I just did this, well that would result in zero, this would result in one, this would result in two. So in general, I want this. So this will give me my digit two. So if I just do that and I run this function, well, first time it updates the value to two, then it updates the value to five, then it updates it to five and we lose out. So what should I do here to get 255? So I'm close. This value every time through here is like just the number I got before. Nope, so yeah, one way you could think of is I get the length of the string and then I could go backwards and do multiples of two. Yeah, so the current trick is I could do this. Whoops. So I start off with the value of zero and then the first time through, I just increase that by 10. Looks a bit weird right now, but 10 times zero is still zero and then I would add in this case two. So now the value is two. Then next time through, okay, if I make sure it's a valid character, then essentially I know I have a next digit, so multiplying it by 10 essentially just shifts it up the column, right? If I just had like the tens column, the ones column and everything. So it would take that number, turn into 20 and then the current input is five, so then I'd get 25 at the end and if I just wrote 25, that would be fine, but it would loop again, come through here, multiply it by 10 so I'd get 250 and then I would just add five to it and then I'd get 255. So that's AOTI, but better because you can't write garbage to it. Questions about that one? Yeah, yeah. So the question is, say I didn't want eggs that I want, just like tell the user that, hey, they input a stupid number. So you can't do it, so the programmer that calls it is like the user, so the user in this case would be me. So if I type hex to desk and then I do like A, well, that's just me and this minus one tells me I did something stupid, but you can't continue the program, they just have to run it again. But I could print off like, hey, give me valid input dummy. So I could do that because if I run that, that just means I have to run it again. Okay, I didn't give them valid input, maybe I do that. Yeah, but I'm calling this from main, oh, sorry, I should look at the code. So here's my main, so it checks if there's two arguments, if there's not two arguments, like if the user only types one, exits failure and then it just gives it the first argument, so it's user's input, whatever they type. So they can't retry it, the operating system called main. Yeah, so in this case, the best you can do is just give an error message and then if your program can't continue, it won't, you just can't retry main, they have to run the program again. But like, maybe I could write this like, okay, maybe input a number or something like that, but I just basically have to just give an error message and tell them to try again. Because yeah, in some programs, some programs might just, if it does other things, it might just skip this, give you an error message, but continue on doing other things. And this, this program only has one job, so it makes sense to just quit it. All right, so what about the other part? So this one wants to, given an integer value, produce a hexadecimal string. Turns out we've pretty much done that before though, it's not too bad. So remember, we kind of know, or at least before we went through using an integer and went by and went through like each single digit. So it turns out we can do the same thing here, except our base is 16 and not 10, but works out the same. So how would I do that? Yeah, I can just keep dividing by 16, right? Like before, if it was, if we wanted to count how many digits there were, and it was just a normal string, or sorry, if it was decimal, we would do something like this, right? Like num digits equals zero, and then, well, I would like do well. Actually, let's make another variable, int remaining equals value. I could do something like while remaining is greater than zero, and it was every single decimal digit, what did I do? I got the digit by doing like remaining mod 10, right? And then I did remaining equals, or I could just do it like that, right? Sorry? Nope, not mod equal to. So this would just be like to get the current digit, right? So something like that. So hexadecimal isn't any scary, it's just a different base. So it turns out I don't really need the digit because I'm just counting how many hex digits there are. So let's be clear, num hex digits. So I don't need the digit for now, I'm just going to count. So I would just divide equal by 16 instead, and then increment the number of num hex digits. Because if I need to make it a string, well, one thing I should do is I should probably figure out how big the damn string needs to be first. So if I conclude that I need num hex digits, how big should my string be to hold it? That is a C string. So should it just be, my C string is just num hex digits large? Plus one, right? Because it's a C string and it needs a null byte at the end. So the size of the string that, or the size, how much memory I need or how many characters I need, I need one character for each hex digit, and then one for the null byte. So I can go ahead, malloc it, and then, well, I'll set the first byte just equal, or the last byte equal to the null byte so that I make sure that it is null terminated. And then I could set the i as just the size minus one, because in this example, if this was just the number of digits, we would start off by the last digit at the end, right? So if this was, if the value was 255, and I did this in base 10, I already got the digits like five, five, then two. So it starts at the end, so I should probably start filling in my string at the end. So what I could do is same idea as before. So set the remaining equals to the value, and then each time through I just decrement it, and here I don't need the number of hex digits. And I'm going backwards. Well, first let's get the hex digit. So I could do int, hex digit equals remaining mod 16, like before. And then this is the hex digit that I want to write, essentially starting at the end. So currently I, well, that value was the null byte, so I probably want to back it up by one each time through the loop. So I'm actually writing them backwards. So now all I have to do is take this hex digit and be able to convert it to a character. So this hex digit will be between zero and nine, right? So if the hex digit is a zero or a nine, how do I get the character zero to nine? So if, so the hex digit is less than 10, then I could just do something like si equals the hex digit plus zero, right? So the hex digit zero, well, zero plus the character zero. If it's one, adds one and we get one. So what about if it is greater than 10? We might have forgot hex digits. So remember, past that they just made up numbers. So they started at hex digit 10 equals a, yeah. Yeah, if I type cast a hex digit, which is an int to a char, it would just use that number and then look up whatever ASCII value that is. It wouldn't actually change the value, but we would interpret that as just whatever that number is, which, okay, we can't see it here, but if you go like ASCIItable.com, you can look up and see what they are. There's no relation really between the number, like the integer number and what character it is-ish. So here I could do like hex digit. Well, in this case, hex digit, my first one would be capital A, but if I did that, hex digit right now would start at 10 and I want it to start at zero. So it's just hex digit minus 10, whoops. Uh-oh, didn't mean to do that. All right, saved. All right, so just be hex digit minus 10 plus a and then that would fill in that hex digit. I'd get a all the way up to f and then it turns out I'm done, right? That's all I have to do. And then my useful program should actually work. So if I compile this, let's make sure we don't have any bugs. So if I do des to hex, let's say I start with zero, whoops. What did I do wrong? Did I compile? All right, debugging live, what have I done wrong? Oh yeah, if it's zero, maybe it just doesn't do it. Let's see, one, okay. Ah, yes, educated zero, okay. Didn't test that. So one gives us one, two gives us two, three gives us three, nine gives us nine and then 10 should give us A, right? So if we did it right, we get A. Cool. See, if we go up to 15, we get F. If we go up to 16, we get one zero in hex. If we do something cool, we get 255, that's F, F, 256, one zero zero, cool, right? And we could use this. We don't have to convert decimal to hex by ourselves. We could just use this program if we really wanted to or we could type into Google, but we made something useful ourselves. So question about, A, questions about this as we have written it, why I did anything that I did. Sorry? What we're doing? Yeah, there's no prompt, so it just, so how it works is, oh, okay. Yeah, so here what I did, I was also a good programmer. So I got a string back as output. I outputted it and I malloc'd it, so I made sure to free it. So if I also just, you know, just use Valgrind on this, our favorite tool, two allocations, two freeze, all heap blocks freed, no leaks possible. It didn't yell at me, it didn't do anything. Everything was fantastic, but kind of works. So if you ever wondered, like on a web thing, why some colors were like F, F, F, F, F, F, F, well, 255 is like the highest number it can go to. Turns out that we got that. So questions about that before we get released for Un Weekend. Cool, this is probably more like useful programs you will actually end up writing because, well, you don't have that awkward thing where you're waiting for user input or anything like that. This kind of works like your compiler. So this is how you would actually write how most normal people write C programs. No one really uses scanf. Just ask for, they'll just use the command line arguments and then maybe they might like read a file or something like that, which we might not touch, but if we have extra time and anyone's interested, I can do it since we're ahead of other things, but yeah, we can go enjoy our weekend four minutes early. Joy. All right, so just remember, bone for ya, we're all in this together. Thank you. Thank you.