Practice programs

Practice program 1.

In this unit, we wrote our own versions of the strlen() and strncpy() functions, as int string_length(char *string) and int string_copy(char *dest, char *dest, char maxsize). Now write your own version of the strchr() function, as int is_charstring(char *bigstring, char c). Return a true value if the character c exists in the string bigstring, or a false value if not.

To solve this problem, let's first write a function that loops through the string. If we find the character c, then we can immediately return a true value (anything that's not zero is a true value).

int
is_charstring(char *bigstring, char c)
{
   int i = 0;

   /* loop through bigstring to find c */

   while (bigstring[i] != '\0') {
      if (bigstring[i] == c) {
         /* return a true value if c exists in bigstring */
         return 1;
      }

      i = i + 1;
   }

   /* exited the loop without finding the character */

   /* return a false value */

   return 0;
}

Every time you write a new function, you should write a test program to make sure the function works as you intended. So let's write a simple test program with a known string, and look for a character that we know is in the string, and another character we know is not in there.

#include <stdio.h>

int
main()
{
   char string[] = "This is a test";

   puts("Testing 'e' in the string:");
   puts(string);

   /* test the is_charstring() function */

   if (is_charstring(string, 'e')) {
      /* 'e' is in the test string, so this should be true */
      puts("Yes, 'e' is in the test string");
   }
   else {
      /* we shouldn't see this */
      puts("YOU SHOULD NOT SEE THIS ('e')");
   }

   /* test again */

   puts("Testing 'x' in the string:");
   puts(string);

   if (is_charstring(string, 'x')) {
      /* 'x' should not be in the test string */
      puts("YOU SHOULD NOT SEE THIS ('x')");
   }
   else {
      puts("No, 'x' is not in the test string");
   }

   return 0;
}

If you compile and run this sample program, you should see this output:

Testing 'e' in the string:
This is a test
Yes, 'e' is in the test string
Testing 'x' in the string:
This is a test
No, 'x' is not in the test string

I've included a few messages that help to determine if the function is working as intended.

Practice program 2.

Write a function char *ltrim_string(char *string) that "trims off" any whitespace characters (space, tab, or newline) from the beginning of a string. Also write the program to test this function.

An easy way to do this is to create a separate char * pointer, and use that to walk through the string. As soon as we find a character that is not a whitespace character, we can return the pointer. Because the pointer will indicate the start of the string.

To write this function, let's also write a function to help test if a character is a whitespace character. By writing this as a separate function, our code should be easier to read. Otherwise, we would need to "stack up" several tests.

int
is_whitespace(char ch)
{
   return ((ch == ' ') || (ch == '\t') || (ch == '\n'));
}

char *
ltrim_string(char *string)
{
   char *ptr;

   /* find the first non-whitespace character, and return a pointer to
      that location */

   /* an easy way to do this is to create a separate pointer and
      "increment" it as you move along the string */

   ptr = string;

   while ((ptr[0] != '\0') && (is_whitespace(ptr[0]))) {
      ptr = ptr + 1;
   }

   /* after that loop, ptr points to the start of the string (or to the
      null at the end, if the string was empty) so we can just return
      the pointer */

   return ptr;
}

And now let's write a program to test this function. We can use two strings: one has whitespace at thebeginning of the string, and the other does not.

#include <stdio.h>

int
main()
{
   char string1[] = "Hello world";
   char string2[] = "   Hi there";
   char *string;

   /* test the new function */

   printf("Trimming this string: <%s>\n", string1);
   string = ltrim_string(string1);
   printf("After trimming:       <%s>\n", string);

   printf("Trimming this string: <%s>\n", string2);
   string = ltrim_string(string2);
   printf("After trimming:       <%s>\n", string);

   /* done */

   return 0;
}

I put the string in brackets in the printf statement so you can see if the string has whitespace in it.

The sample output should look like this:

Trimming this string: <Hello world>
After trimming:       <Hello world>
Trimming this string: <   Hi there>
After trimming:       <Hi there>

Practice program 3.

Write a function int is_number(char *string) that tests if a string contains only numbers. This is a test for integers.

You could write this function in several different ways, but the method basically comes down to this: Examine each character in the string. If you find a character that is not a number, then you can return a false value.

This function is much easier to write if we use the is_charstring() function that we created earlier.

int
is_charstring(char *bigstring, char c)
{
⋮
}

int
is_number(char *string)
{
   int i = 0;
   char nums[] = "1234567890";

   /* there are several ways you could do this, but this function will
      loop through each character in string, and see if that character
      is in the bigstring of numbers */

   /* uses the is_charstring() function we wrote earlier */

   while (string[i] != '\0') {
      if (!is_charstring(nums, string[i])) {
         /* found something that is not a number, so return false */

         return 0;
      }

      i = i + 1;
   }

   /* we ended the loop without returning, so the string must only be
      numbers. return a true value */

   return 1;
}

And now we should write a test program to make sure the function works the way we expect it to. We can test the new function with a string that only contains letters (not a number) and another string that only contains numbers.

#include <stdio.h>

int
main()
{
   char letters[] = "abcd";
   char numbers[] = "1234";

   /* test the strings */

   printf("Testing the string <%s> for numbers only\n", letters);

   if (is_number(letters)) {
      /* "abcd" is not a number, so we should not see this */
      puts("YOU SHOULD NOT SEE THIS (abcd)");
   }
   else {
      puts("No, that is not a number");
   }

   printf("Testing the string <%s> for numbers only\n", numbers);

   if (is_number(numbers)) {
      puts("Yes, that is a number");
   }
   else {
      /* "abcd" is not a number, so we should not see this */
      puts("YOU SHOULD NOT SEE THIS (1234)");
   }

   /* done */

   return 0;
}

I've included a few messages that help to determine if the function is working as intended.

And the sample output for this program:

Testing the string <abcd> for numbers only
No, that is not a number
Testing the string <1234> for numbers only
Yes, that is a number

Practice program 4.

Write a function char *uppercase(char *string) that converts any letters in a string to uppercase letters. (It is helpful to remember that C tracks characters as their value from the character encoding. Virtually all systems today are based on ASCII, which conveniently puts letters together, in order. So 'a' through 'z' and 'A' through 'Z' are always in sequence.)

This practice program demonstrates an important difference between regular variables and arrays: When you pass a regular variable to a function, you pass the value in that variable. But when you pass an array to a function, you actually pass the address. And that means the function can modify the array you give it. In this case, the array is a string, because a string is just an array of chars.

(If you were curious about the & that we used in the scanf function, earlier in the propgramming series, the ampersand gives the address of the variable. That's why scanf was able to give a new value to the variable that you gave it.)

Another assumption we need to make for this function is that the letters a through z and A through Z are each contiguous ranges. This is true on computer systems that use ASCII character encoding. But this is not true on systems that use EBCDIC. Note that IBM mainframes typically use EBCDIC; pretty much every other computer system in the world uses ASCII.

With that assumption, we can do arithmetic and subtraction on char values, as though they were ints. That's because C stores a number from the ASCII range for each character. In the ASCII character set, 'A' is the value 65 and 'a' is the value 97.

char *
uppercase(char *string)
{
   int i = 0;
   int diff;

   while (string[i] != '\0') {

      if ((string[i] >= 'a') && (string[i] <= 'z')) {
         diff = string[i] - 'a';

         /* Yes, you can do addition and subtraction with chars, as long
            as you are on an ASCII system (should be a safe assumption
            today; but note that this assumption breaks on EBCDIC) */

         string[i] = 'A' + diff;
      }

      i = i + 1;
   }

   /* done, return a pointer to the string */

   return string;
}

And to test this function, we should write a test program that uses a string of mixed uppercase and lowercase numbers.

#include <stdio.h>

int
main()
{
   char string[] = "Hello world 1234 !@#$";

   puts("Convert this string to uppercase:");
   puts(string);

   puts(uppercase(string));

   /* done */

   return 0;
}

In my example, I also include a few other non-letter characters, because we shouldn't change those as we convert letters to uppercase.

The sample output for this program should look like this:

Convert this string to uppercase:
Hello world 1234 !@#$
HELLO WORLD 1234 !@#$