Previous | Next
Preparing for our second useful program
It's been a while since we did our last useful program, so before y'all doze off, let's create a little database for our CD collection. I guess you can already see the basic structure our program will have: We can do lists by faking an array with malloc(). But right now, our lists are very limited, as an array can only hold one type of data. So we'd have to create several arrays, one for each type of data, and we'd have to resize each of these arrays to add a new CD to our database. Lots of work. Didn't I claim programmers were lazy?
They are, and that's why they invented data structures. A data structure is a type you define yourself that is made up of existing types. Sound complicated? It really isn't. Think of what each entry for a CD will look in our little database:
- Artist
- Composer
- Name of Album
- Number of Tracks
- Is a sampler?
So, how would you translate this into C?
struct CDDatabaseEntry
{
char artist[40];
char composer[40];
char albumName[40];
int trackCount;
bool isSampler;
};
Not hard to read, is it? A data structure is defined using the keyword
struct. What follows is a name you give it (which may not contain spaces or other odd characters, just like a variable name). And then you list all the variables this one variable is supposed to group together between curly brackets. You end it with a semicolon.
That's how you make up your own data type in C. To use it, write something like:
#include <stdio.h>
#include <stdbool.h>
struct CDDatabaseEntry
{
char artist[40];
char composer[40];
char albumName[40];
int trackCount;
bool isSampler;
};
int main()
{
struct CDDatabaseEntry myEntries[10];
myEntries[0].isSampler = false;
return 0;
}
You simply write
struct structName instead of another data type like
int. You can even have arrays of them. And to change the
sub-variables, or
structure fields as they are called, you use the "."-operator. So, the line
myEntries[0].isSampler = false;
above means:
- Take element 0 of the array myEntries
- Take the isSampler field of this first entry
- assign the value false to it.
Similary, you can work with most other data types.
So, let's see what we need to do: Like every program, we'll need a main event loop, i.e. a while loop that keeps asking the user what to do. And in addition, we need to be able to create a new entry for the database and show us the entries in our database. Let's start with our main event loop first:
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
struct CDDatabaseEntry
{
char artist[40];
char composer[40];
char albumName[40];
int trackCount;
bool isSampler;
};
int main()
{
bool keepRunning = true;
char userInput[11];
while( keepRunning == true )
{
printf( "Type NEW , LIST, or QUIT:\n> " );
scanf( "%10s", userInput );
fpurge( stdin );
if( strcmp( userInput, "NEW" ) == 0 )
printf( "I'll write the code for NEW later...\n\n" );
else if( strcmp( userInput, "LIST" ) == 0 )
printf( "I'll write the code for LIST later...\n\n" );
else if( strcmp( userInput, "QUIT" ) == 0 )
keepRunning = false; // We're finished.
else
printf( "ERROR: Unknown command \"%s\"!\n\n", userInput );
}
return 0;
}
Several things I'll have to point out here:
userInput is an array to hold some text characters. Programmers typically call this a string. When you write text in double quotes in C, it also gives you a string. A string is simply an array of characters. After the last character C always puts the Zero-Character (i.e. the character whose ASCII number is 0 -- 'A' for example has the ASCII number 65). That way C knows when the string ends, even if, like in our case, the variable in which scanf places the text is 10 characters in length. So, "NEW" is almost the same as writing:
char myNewString[4];
myNewString[0] = 'N';
myNewString[1] = 'E';
myNewString[2] = 'W';
myNewString[3] = 0;
Just with the exception that "NEW" is a constant, that is, you are not allowed to change it by writing stuff like:
char* foo = "NEW";
foo[1] = 'O'; // BANG! Not allowed to change a constant!
The %10s-part in our call to scanf is the same as %s. However, the number 10 between the % and the s limits the number of characters it will write to userInput. Since userInput is 11 characters large and (as mentioned above) there has to be a 0-character at the end of the text string, we only let it give us 10 characters (10 + 1 zero-char = 11) so our call to scanf doesn't run off the end of userInput. And again, we don't specify an &-operator before userInput on the scanf-line because userInput is an array. And as you know, an array is simply a pointer to a whole chunk of bytes. So, it already is a pointer.
Since both a constant string (e.g. "NEW") and a string variable (i.e. userInput) are pointers, It doesn't make much sense to compare them using the == operator. After all, the ==-operator would simply compare the two pointer addresses, and userInput has a completely different memory address than the string constant "NEW", even if they basically contain the same characters.
You also can't dereference two strings using the *-operator to compare their characters, because a string is simply a pointer to data of type char, and C wouldn't know how many chars that pointer points to. In addition, there is a limit on the == operator that essentially means you can't use it to compare anything but the "short" atomic data types like char, int and bool (as well as pointers, because they're basically the same as an int). So, you can't compare two structs, for example.
So, to compare two strings, we'd have to use a while loop to go over each character in the two arrays of characters and compare them, until we either encounter the 0-character in both arrays simultaneously (the strings are equal) or encounter two characters that aren't the same (not equal). Luckily, the file string.h contains a handy function:
int strcmp( char* strA, char* strB );
which compares the two strings
strA and
strB and returns
0 if they are the same,
1 if
strA would be sorted before
strB, and
-1 if
strB would be sorted before
strA. So we'll just use that. Note that this function compares case-sensitively, i.e. our program will not accept "New" or "new" as equivalent to "NEW".
Compile this program and play with it a little. In the next chapter, we'll put in the actual functionality.
Previous | Next
Richard Howell writes: #include <stdio.h>
#include <stdlib.h>
#include <string.h>
char userEntry[10];
char secUserEntry[10];
int main (int argc, const char * argv[])
{
printf("First of all, enter a name for me to store as a string > ");
scanf("%11s", userEntry);
fpurge(stdin);
printf("User entered : %s", userEntry);
printf("Now, enter a second name for me to compare with the first > ");
scanf("%11s",secUserEntry);
fpurge(stdin);
if (strcmp(char * userEntry, char * secUserEntry) == 0)
{
printf("\nThe two are identical, %11s = %11s", userEntry, secUserEntry);
}
else
{
printf("\nThe two are not idntical, %11s != %11s", userEntry, secUserEntry);
}
return 0;
}
|
Richard Howell writes: I typed in the above code, and found out that I dont quite know the syntax to make it work..can you help?
|
cMacRun writes: Richard,
I typed in your code for practice, but I got a "syntax error before 'char'" error message. What did you change from the above code to make it work?
|
Amoo writes: So I am confused. If any array is a pointer, what is the difference between:
char myArray[20];
and
char* myArray[20]
?
aren't they the same thing?
|
Uli Kusterer replies: ★ Amoo, the first is an array of actual characters (for example, a "string", a piece of text). The second one is an array of POINTERS TO characters, or an array of arrays of characters. So, the first one is to hold, for example, one user's name, while the second would be for keeping a list of several users' names.
Does that clarify things?
Also keep in mind that writing char myArray[20] will actually give you not just a variable to hold an address (a pointer), but also the actual memory block the pointer points to. If you just write char* myArray; it would be the same, but would NOT actually create the memory pointed to. It is just a place to store the address of a memory block.
That's not a problem with parameters to functions, as those would get the value from whoever called them anyway. But for a local variable, this distinction is important.
|
Odikia Irodnar writes: I'm using Xcode 3.0 on an iMac with Snow Leopard (10.6.4), and every time I try to compile the code you've placed above I get the following message:
"The active architecture ppc is not supported by the debugger for cpu architecture i386/x86_64"
I'm assuming this has something to do with the number of bytes that can be assigned for a given CPU (in my case I think 64 bit/8 bytes). How should the code change?
Thanks for the great tutorial by the way! It's serving as my premier to K&R
|
Uli Kusterer replies: ★ It's nothing in the code that's causing the problem. One of your project settings seems to be wrong. The compiler is telling you that you're building on an Intel Mac (i386/x86_64 are the two kinds of Intel Macs, 32-bit and 64 bits), but trying to run the debugger in PowerPC mode.
Look in the "Project" menu. It has menu items for "Set active architecture" and "Set active executable" and the likes. Try to make sure they all have the same kind of Intel checked that your Mac needs.
Also, make sure you have the newest Xcode. You can't usually use older Xcode versions on newer MacOS X versions. 3.0 is way too old, you should have 3.2.x
|
Uli Kusterer replies: ★ Shadow Luster, if this makes no sense for you, re-read the previous chapters, read other web sites' descriptions of how structs and arrays work in C, and/or ask more questions here or on mailing lists or in forums for people learning C.
I *can* help, but only if you ask questions. "It doesn't make sense to me" is too vague for me. What individual part do you not understand of this chapter, what do you think it says, and why do you think that doesn't make any sense?
|
Jack writes: I tried to play around with this by creating a program where I took two passwords from the user and compared them. That worked fine, but I tried to add in a feature so if the passwords didn't match the while loop would restart. This is the code for what I tried to do, and I was wondering why it doesn't work.
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
char userEntry[10];
char userEntry2[10];
int main()
{
bool vRunning;
bool vRestart;
vRunning = true;
vRestart = false;
while( vRunning == true)
{
printf("Please Enter in your desired password\n>");
scanf( "%9s", userEntry);
fpurge( stdin );
printf("Please Enter your desired password again\n>");
scanf( "%9s", userEntry2);
fpurge( stdin );
if( strcmp( userEntry, userEntry2 ) == 0 )
{
printf("The passwords match, your password is now set as %s.", userEntry);
vRestart = false;
vRunning = false;
}
else if( strcmp( userEntry, userEntry2 ) != 0 )
{
printf("These passwords do not match, please re-enter them.");
vRestart = true;
vRunning = false;
}
}
if( vRestart == true)
{
vRunning = true;
}
else
{
printf("\nProgram Quit.");
}
vRunning = false;
printf("\nProgram Quit.");
return 0;
}
|
Kevin Hopcraft writes: Thank you for your 'books'. They are very helpful.
I never understood any other books on programming, they just didn't explain it well. You actually know how to talk to beginners :D.
I will recommend this site to anyone I know!
|
Pedro Pedrosa writes: Hey, gerat tutorial! Gratz on making it easy for us to understand how to program on C.
My question is simple. Since C is case sensitive, i wanted to add "new""list"and "quit"on the command lines.
i tryed this:
if( strcmp( userInput, "NEW" or "new") == 0 )
printf( "I'll write the code for NEW later...nn" );
but didn t get it right. This second command line worked properly, but isn t there an easier way to put more options without having to create multiple else ifs?
if( strcmp( userInput, "NEW") == 0 )
printf( "I'll write the code for NEW later...nn" );
else if( strcmp( userInput, "new") == 0 )
printf( "I'll write the code for NEW later...nn" );
Thanks for ur attention. Keep up the good work. I gave up reading the books, but now that i found ur website, i can understand what they are sayin way easier.
Pedro
|
Zouzou writes: Hello everybody,
the tutorial is really good!
i copy below the code, it compiles , however, when it requests to enter NEW, LIST, QUIT. at the console i need to write twice the word NEW or LIST and when i print QUIT, it does not exit the program...
any help?
further to that after run and build i get the below msg:
stop executable
cdDatabase
project:"cdDatabase.xcodeproj"Target:"cdDatabase"
any ideas, what this msg means?
thnxs
Z
thnxs :)
//*************************************
#include <stdio.h>
# include <stdbool.h>
//********************
//strut
struct CDDatabaseEntry
{
char artist[40];
char composer[40];
char albumName[40];
int trackCount;
bool isSampler;
};
//********************
int main ()
{
bool keepRunning = true;
char userInput[11];
while(keepRunning = true)
{
printf("type new, list, or quit : n ");
scanf(" %10s ", userInput);
fpurge(stdin);
if(strcmp(userInput, "NEW" ) ==0 )
printf("we will enter the elements of a new struct here");
else if(strcmp(userInput, "LIST" ) == 0)
printf("we will enter the elements of a new list here");
else if(strcmp(userInput, "QUIT" ) == 0)
keepRunning = false;
else
printf("error: unknown commnd");
}
return 0;
}
|
ZOUZOU writes: I found why it didn't exit the program when i was printing the QUIT word.
I had this while(keepRunning = true) intstead of this: while(keepRunning == true)
However that, i still need to print twice the word (NEW OR LIST OR QUIT) in order for the program to respond, any reasons ?
|
mshund writes: ZOUZOU:
I'm muddling along with you, so I'm not sure if this fixes your problem, but you're missing the line:
#include <string.h>
unless it got chopped off in your copy/paste. Does that help?
|
Plaidmelon writes: Thanks Uli for the tutorial.
I tweaked this to ignore case using strcasecmp, or a shorter command ("n, l or q"). Thought I'd share.
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
struct CDDatabaseEntry
{
char artist[40];
char composer[40];
char albumName[255];
int trackCount;
bool isSampler;
};
int main()
{
bool keepRunning = true;
char userInput[11];
while (keepRunning == true)
{
printf("Type New[N], List[L] or Quit[Q]: ");
scanf("%10s", userInput);
fpurge(stdin);
// Compare userInput to determine command, case insensitive
if ((strcasecmp(userInput, "new") == 0) || (strcasecmp(userInput, "n") == 0))
{
printf( "I'll write the code for NEW later...nn" );
}
else if ((strcasecmp(userInput, "list") == 0) || (strcasecmp(userInput, "l") == 0))
{
printf( "I'll write the code for LIST later...nn" );
}
else if ((strcasecmp(userInput, "quit") == 0) || (strcasecmp(userInput, "q") == 0))
{
keepRunning = false; //exit program
}
else
{
// Error if userInput doesn't match anything
printf("ERROR, unknown command "%s"!nn", userInput);
}
}
return 0;
} |