|
10. Stupid CD Database
Previous | Next Doing the workNow that we have the framework for our database program, let's fill it with meaning. Instead of the printf()s, we'll need to actually do some work. Since it would get kind of unreadable if we stuffed all of this into main(), we'll write our own functions, DoNewCommand() and DoListCommand() for doing that. I already wrote about the benefits of extracting code that is used repeatedly into a function of its own, but it's also useful for just making the program easier to read. You'll spend a lot of time reading your source code - definitely more time than writing it - so do what you can to make your code more readable - it'll pay off in the thousands. But now, let's get back to the code: #include <stdio.h> // declares printf(), scanf() and fpurge(). #include <stdbool.h> // declares bool. #include <string.h> // declares strcmp(). #include <stdlib.h> // We'll need that later for malloc() and realloc().
// Data structure: struct CDDatabaseEntry { char artist[40]; char composer[40]; char albumName[40]; int trackCount; bool isSampler; };
// Global variables: int gNumDatabaseEntries = 0; struct CDDatabaseEntry* gDatabase = NULL;
// Main event loop: // Fetches user input and calls our DoXXX functions to do the work. 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 ) DoNewCommand(); else if( strcmp( userInput, "LIST" ) == 0 ) DoListCommand(); else if( strcmp( userInput, "QUIT" ) == 0 ) keepRunning = false; // We're finished. else printf( "ERROR: Unknown command \"%s\"!\n\n", userInput ); } DoCleanUp(); return 0; }
One thing we'll need to be able to write these functions, though, is a place to keep our database. That's what gDatabase above is. It's a pointer-to-CDDatabaseEntry, which we'll be abusing as a dynamic array. You may be surprised that, so far, we only defined variables at the top of our functions. gDatabase is outside our functions. What gives? Well, when you define a variable at the top of a function, it's only visible at the top of that function. When you define a variable outside any functions, it's visible to all code below that. That's what we call a global variable. Global variables are good when you have one piece of data that needs to be accessed from lots of places. When you have a variable inside a function (called a local variable), only this function can mess with it (unless you hand a pointer to that variable to another function, of course). So, why don't we always use global variables? Well, we'll later see additional advantages of local variables, but the main point is that a local variable's name only has to be unique inside the function you declare it in, while a global variable's name has to be unique across the whole program, because the whole program can see and use it! So, if you used a global for every x you use, you'd eventually end up with names like x123466. In addition, if a global can be changed from any function, you'd have to be very careful when calling another function. I'm currently using x27, what if the author of that other function also uses and changes x27 and overwrites my number? Admitted, with a tiny project like this, you can just scroll up two lines and check, but as soon as you do a project that's actually useful, or you work on a project together with someone else, you'll love the peace of mind you get from knowing nobody can screw up your local variables. Note the NULL that we assign to gDatabase. NULL is simply a fancy way of saying "0 as a pointer". When your computer starts up, it usually loads the operating system somewhere right at the start of your memory. So, the address 0 is guaranteed to lie in the operating system's code, and can't be used by your program for anything. So, as a convention, programmers use the memory address 0 to mean "I'm not using that memory yet". Okay, so now we have to write those two functions. DoNewCommand() is supposed to create a new array element in gDatabase and fill it out with info it gets from the user, and DoListCommand() is supposed to use a while() loop to print the info for each array element, and DoCleanUp() is supposed to get rid of the memory we malloced for our array. Now, three hints: - There's no way to find out the size of a pointer created using malloc(), so we'll also have to keep track of the number of items so we know where our array ends.
- Make sure you put the three functions in the source file above main(). The C compiler reads your source file from top to bottom, so you'll get odd error messages if you try to call a function in main() that the compiler hasn't seen yet because it's defined below main().
- If the user starts up our application and immediately types in QUIT, we will never have malloc()ed the memory to go in gDatabase (because you can't malloc() a memory block of size 0, and we never created a database entry). So, be sure that DoCleanUp() can cope with this situation.
Want to give it a try yourself? Below, I'll provide my versions of the two functions we need. scanf() will only read the first word of what you type in. So, you have two options: You can just not write any spaces in the names (e.g. write "Simon_and_Garfunkel"), or you could use the getchar() function in a loop to get the whole line out, until you encounter the '\n' character. I'm leaving that as an exercise to the reader. void DoNewCommand() { char yesOrNo;
// First, create a new array element (or a new array if we don't have one yet): if( gDatabase == NULL ) { gDatabase = malloc( sizeof(struct CDDatabaseEntry) ); // size of 1 element. if( gDatabase == NULL ) // Still NULL? malloc() must have returned NULL due to error. { printf( "ERROR: Couldn't create a new entry!\n" ); return; } } else { struct CDDatabaseEntry* newPtr = NULL; newPtr = realloc( gDatabase, (gNumDatabaseEntries +1) *sizeof(struct CDDatabaseEntry) ); if( newPtr == NULL ) // Error! Out of memory? { // We just keep the old pointer in gDatabase. printf( "ERROR: Couldn't create a new entry!\n" ); return; } // newPtr is our new ptr, gDatabase is no longer valid! gDatabase = newPtr; // Remember newPtr in gDatabase. }
// Make sure we remember we have one more entry: gNumDatabaseEntries += 1;
// Now replace the garbage data in the new, last entry with data the user entered: printf( "Artist Name: " ); scanf( "%39s", gDatabase[ gNumDatabaseEntries -1 ].artist ); fpurge( stdin );
printf( "Composer: " ); scanf( "%39s", gDatabase[ gNumDatabaseEntries -1 ].composer ); fpurge( stdin );
printf( "Album Name: " ); scanf( "%39s", gDatabase[ gNumDatabaseEntries -1 ].albumName ); fpurge( stdin );
printf( "No. of Tracks: " ); scanf( "%d", &gDatabase[ gNumDatabaseEntries -1 ].trackCount ); fpurge( stdin );
printf( "Sampler? (y/n): " ); scanf( "%c", &yesOrNo ); fpurge( stdin );
gDatabase[ gNumDatabaseEntries -1 ].isSampler = (yesOrNo == 'y' || yesOrNo == 'Y'); } Not much special in this function. We're pretty much just applying what we learned in earlier chapters. Only two things to point out, and they're all in the lines that mess with gDatabase: scanf( "%d", &gDatabase[ gNumDatabaseEntries -1 ].trackCount ); The easy one here is that we need to say gNumDatabaseEntries -1 because the number of entries is always 1 bigger than our highest index (our indices start at 0, while a 0 count means no items). And here, we want the number of our newest, last element, which always has the index gNumDatabaseEntries -1. The other thing to watch out for is called precedence. When you use several operators in a row, there's a certain order they are evaluated in. Just like 5 + 6 * 4 is evaluated as 5 + (6 * 4) (because the * and / operators have precedence over the + and - operators), the other operators have an order. In the line above, the critical ones are the &, [] and . operators. The way the compiler will read the above is: &((gDatabase[gNumDatabaseEntries -1]).trackCount) I.e. it will first get our last entry, then it will get the trackCount field from that, and only then will it get the address. This will not get the address of gDatabase and then try to use that as an array, and it will not get the last element's address and try to get a field from that pointer. Obviously, both wouldn't make sense, but C wouldn't know that. If you're in doubt what operator has precedence, you'll want to either get a good C reference book where you can look it up, or use brackets to make sure C uses the right order. Don't worry about "unnecessarily" using brackets. Brackets don't generate any additional code, they simply control the order code is generated in. And they make things more readable, and you know that that's a Good Thing(tm). On to our listing function: void DoListCommand() { int x = 0;
if( gDatabase == NULL ) { printf("There are no CDs in the database.\n"); return; }
while( x < gNumDatabaseEntries ) { printf( "Artist Name: %s\n", gDatabase[ x ].artist ); printf( "Composer: %s\n", gDatabase[ x ].composer ); printf( "Album Name: %s\n", gDatabase[ x ].albumName ); printf( "No. of Tracks: %d\n", gDatabase[ x ].trackCount ); if( gDatabase[ x ].isSampler ) printf( "\tThis CD is a sampler.\n" ); printf( "\n" ); // Add an empty line for space to the next CD.
x += 1; } } This is a pretty common thing, and you'll probably write lots of loops like this. You'll always have some sort of counter variable with an initial value (here x = 0), a termination condition that controls when the loop will end (when x < gNumDatabaseEntries is no longer true), and a statement that adds one to the counter (x += 1, a shorter form of writing x = x +1). Loops like this are actually so common, that C has added a few things to save you some time typing: the for loop and the ++ increment operator. Usually, you'll be using them in cases like: int x;
for( x = 0; x < gNumDatabaseEntries; ++x ) { // actual code goes here. } When I started out, this was unreadable gibberish to me. Not only was it pretty much unlabeled and I had no idea what goes where, no, it's also one command that contains semicolons, so it looked like three commands on one line. And strictly spoken, that's what it is. If you feel more comfortable using while(), feel free to stick to that. I introduced you to while() first because it can do everything you can do with any of C's other loop constructs. Everything else is just syntactic sugar. The advantage of for() is that you write the looping stuff in one go. The start value, the end value, the step. When I originally wrote the while() loop above, I forgot to add the x +=1; line, and when I tested my program it got stuck in an endless loop and I had to abort it. A few more words about the ++ prefix increment operator: If you want to, you can replace ++x above with x = x +1 or with x += 1. That's fine. You can have loops that take bigger steps than 1 that way. You can also use ++x anywhere you use the others, it will work exactly the same. There's just one thing you rarely want to do: Don't write x++ unless you know what you're doing (i.e. put the ++ after the variable instead of before it). You see, every operation in C has a return value. Yes, even = and +=. Usually, it's the same as the result of the operation. So, if you write foo = bar +=1; This will add 1 to bar, and then assign that value to foo. The same will happen if you write: foo = bar = bar +1; foo = ++bar; But when you write foo = bar++; It will first remember bar's current value, then add 1 to bar, then use bar's old value as the result of the operation and assign that to foo. Confused? Let's say bar was 20. The three statements above will result in both foo and bar containing 21. The line above, with the postfix increment operator on the other hand will result in foo containing 20, and bar 21. So, whenever you use the ++ operator, be mindful of this difference. And yes, there's also a -- operator in both prefix and postfix varieties that you can use to subtract 1 from a variable, too. Now, let's quickly cover our clean-up function: void DoCleanUp( void ) { if( gDatabase != NULL ) // We have allocated memory? { free( gDatabase ); gDatabase = NULL; // Not really necessary, but good style. gNumDatabaseEntries = 0; } } Not much happening here. gDatabase starts out being NULL if we never created an item, so to cover the instant-quit situation, we check for that and do nothing in that case (if we don't malloc(), we don't need to free() anything). Otherwise, we free the database and, just to be nice, we set gDatabase back to NULL and gNumDatabaseEntries to 0. In this program that's pretty unnecessary, but if we were in a bigger program, someone could call DoCleanUp() at some other time to empty the array. This way, we make sure that the rest of the code can still work and won't crash trying to talk to a pointer that has already been freed (and maybe reused). Previous | Next stijnschoor writes: Dear Uli
Thanks for the tutorials, they have been a great help to me.
I am also creating a database(not CD's) and your article have helped me alot. Thanks.
Maybe you can help me with a question about C, in my database program I want the user to edit the data. Is there a way of printing the data from a file(In my case the FILE pointer file) and let the user edit it? I was thinking about some sort of scanf function where there's already some text.
Thank you
Stijn
|
gsoli writes: Very cool! This tutorial clarifies a lot of things for me. I especially enjoyed the explanation on the stack and heap in memory. These last few pages have been a little heavy, so I will have to come back again to try to force these concepts into my stubborn brain. It has been fun. Thanks!
|
Uli Kusterer replies: ★ stijnschoor, it is definitely possible to do that: There are variants of the functions you already know that work on files instead of the screen. Google for information on the "C standard library", particularly the functions fprintf(), fscanf(), fopen(), fwrite(), fread(), fseek(), ftell() and fclose().
|
Uli Kusterer replies: ★ Ryan, gsoli, I hope the new animations will make re-reading this tutorial much more pleasant and understandable than the old graphics with their forests of arrows. Let me know what you think!
|
tómppu writes: Thanks for the great tutorials Uli, some parts have been really hard to comprehend but that problem can be solved by trying harder. Learning new things is never easy, but you have managed to simplify this one and most of all made it flow so its interesting and fun!
This chapter however has one obvious flaw in the sample code;
Artist name, composer or album name can't contain any spaces or at least they're not printed out in the LISTing. One would think that it doesn't matter what goes inside "%39s" since it waits for a string and gets one but apparently it does... Any way to correct this?
Thanks!
|
Richard Howell writes: I think I copied the code word for word, and at first it worked fine. But now I get this error, and to be fair, I don't understand it at all.
Is there something wrong with one of the library files?
Internal error occurred while creating dependency graph: _registerUndoObject:: NSUndoManager 0x2016497a0 is in invalid state, must begin a group before registering undo
|
John writes: Hi,
First of all, thanks for writing these tutorials. I've never found other tutorials intuitive to follow, but this definitely helps!
I'm just confused about two points in this chapter:
1. Inside the function DoNewCommand() with this line in particular:
scanf( "%d", &gDatabase[ gNumDatabaseEntries -1 ].trackCount );
is the reason there's a "&" character for trackCount because it's still an integer inside the CDDatabase structure and not a pointer like the character arrays? I just assumed that since I made a pointer to the entire CDDatabase structure that you wouldn't need that "&" character so I guess I'm wrong?
2. My other question is somewhat related to the above, but it deals with the code in the DoListCommand() function and the following lines of code:
printf( "Artist Name: %s\n", gDatabase[ x ].artist );
printf( "Composer: %s\n", gDatabase[ x ].composer );
printf( "Album Name: %s\n", gDatabase[ x ].albumName );
printf( "No. of Tracks: %d\n", gDatabase[ x ].trackCount );
Why isn't there a "*" character in front of the pointers for the artist, compose, and albumName character arrays? I thought it would be written as follows:
printf( "Artist Name: %s\n", *gDatabase[ x ].artist );
printf( "Composer: %s\n", *gDatabase[ x ].composer );
printf( "Album Name: %s\n", *gDatabase[ x ].albumName );
printf( "No. of Tracks: %d\n", gDatabase[ x ].trackCount );
with trackCount not having a "*" in front since it's still a variable within the CDDatabase structure unlike the character arrays inside which are pointers themselves.
|
Martin Baker writes: Missing backslash in DoNewCommand code?
printf( "ERROR: Couldn't create a new entry!n" );
|
Ardeshir Sepahsalar writes: Here is a DoNewCommand and a helper function to check pointers, with some improvements, it lets you create names of artists and albums with spaces in them using getchar() :
int PtrNotOk( void *check ) {
bool status = false;
if (check == NULL) {
printf("Error: Couldn't create entry!\n");
status = true;
return status;
} else {
return status;
}
}
void doNewCommand() {
printf("Ok, lets create a new entry in the database.\n");
char yesOrNo; // this is to check if the entry is Sampler or not.
int c = 0; // array counter, for getchar()
// first create a new array element (or a new array if we don't have one
if (gDB == NULL) {
gDB = malloc( sizeof(struct dbCD) ); // size of 1 database
if ( PtrNotOk(gDB) ) {
return;
}
} else {
struct dbCD* newPtr = NULL;
newPtr = realloc( gDB, (gNumDbEntries+1) * sizeof( struct dbCD ) );
if( PtrNotOk(newPtr) ) {
return;
}
// newPtr is our new ptr, gDB is no longer valid
gDB = newPtr;
}
// make sure we remember we added one more entry
gNumDbEntries += 1;
printf("Enter Artist Name: ");
while((gDB[gNumDbEntries-1].artist[c++] = getchar()) != '\n')
;
fpurge(stdin);
gDB[gNumDbEntries-1].artist[--c] = 0; c=0; // remove the new line
printf("Enter Composer: ");
while((gDB[gNumDbEntries-1].composer[c++] = getchar()) != '\n')
;
fpurge(stdin);
gDB[gNumDbEntries-1].composer[--c] = 0; c=0;
printf("Enter Album Name: ");
while((gDB[gNumDbEntries-1].albumName[c++] = getchar()) != '\n')
;
fpurge(stdin);
gDB[gNumDbEntries-1].albumName[--c] = 0; c=0;
printf("No. of Tracks: ");
scanf("%d", &gDB[ gNumDbEntries-1].tracks ); // get the address of the Int for scanf
fpurge(stdin);
printf("Sampler? (y/n) ");
scanf("%c", &yesOrNo );
fpurge(stdin);
gDB[ gNumDbEntries-1].isSampler = (yesOrNo == 'y' || yesOrNo == 'Y');
}
|
Uli Kusterer replies: ★ Martin, thank you, corrected. Sometimes the server eats backslashes because it's trying to be helpful and keep people from injecting code...
|
Someone writes: Whenever I try to enter any text (ie after "Artist Name:") I get this
[Session started at 2010-03-21 09:04:39 -0700.]
GNU gdb 6.3.50-20050815 (Apple version gdb-967) (Tue Jul 14 02:11:58 UTC 2009)
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-apple-darwin".sharedlibrary apply-load-rules all
Attaching to process 31286.
|
phill writes: I get a error when I try to build the code that says "expected expression before 'void'"
here is my code
#include <stdio.h> // declares printf(), scanf(), and fpurge()
#include <stdbool.h> //declares bool
#include <string.h> // declares strcmp()
#include <stdlib.h> // We'll need that later for malloc() and realloc()
// Data structure
struct CDDatabaseEntry
{
char artist[40];
char composer[40];
char albumName[40];
int trackCount;
bool isSampler;
};
//Global variables:
int gNumDatabaseEntries = 0;
struct CDDatabaseEntry* gDatabase = NULL;
// Main event loop:
//Fetches user input and calls our DoXXX functions to do the work.
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 )
void DoNewCommand()
{
char yesOrNo;
// First, create a new array element (or a new array if we don't have one yet):
if( gDatabase == NULL )
{
gDatabase = malloc( sizeof(struct CDDatabaseEntry) ); // size of 1 element.
if( gDatabase == NULL ) // Still NULL? malloc() must have returned NULL due to error.
{
printf( "ERROR: Couldn't create a new entry!\n" );
return;
}
}
else
{
struct CDDatabaseEntry* newPtr = NULL;
newPtr = realloc( gDatabase, (gNumDatabaseEntries +1) *sizeof(struct CDDatabaseEntry) );
if( newPtr == NULL ) // Error! Out of memory?
{
// We just keep the old pointer in gDatabase.
printf( "ERROR: Couldn't create a new entry!\n" );
return;
}
// newPtr is our new ptr, gDatabase is no longer valid!
gDatabase = newPtr; // Remember newPtr in gDatabase.
}
// Make sure we remember we have one more entry:
gNumDatabaseEntries += 1;
// Now replace the garbage data in the new, last entry with data the user entered:
printf( "Artist Name: " );
scanf( "%39s", gDatabase[ gNumDatabaseEntries -1 ].artist );
fpurge( stdin );
printf( "Composer: " );
scanf( "%39s", gDatabase[ gNumDatabaseEntries -1 ].composer );
fpurge( stdin );
printf( "Album Name: " );
scanf( "%39s", gDatabase[ gNumDatabaseEntries -1 ].albumName );
fpurge( stdin );
printf( "No. of Tracks: " );
scanf( "%d", &gDatabase[ gNumDatabaseEntries -1 ].trackCount );
fpurge( stdin );
printf( "Sampler? (y/n): " );
scanf( "%c", &yesOrNo );
fpurge( stdin );
gDatabase[ gNumDatabaseEntries -1 ].isSampler = (yesOrNo == 'y' || yesOrNo == 'Y');
}
else if( strcmp( userInput, "LIST" ) == 0)
void DoListCommand()
{
int x = 0;
if( gDatabase == NULL )
{
printf("There are no CDs in the database.\n");
return;
}
while( x < gNumDatabaseEntries )
{
printf( "Artist Name: %s\n", gDatabase[ x ].artist );
printf( "Composer: %s\n", gDatabase[ x ].composer );
printf( "Album Name: %s\n", gDatabase[ x ].albumName );
printf( "No. of Tracks: %d\n", gDatabase[ x ].trackCount );
if( gDatabase[ x ].isSampler )
printf( "\tThis CD is a sampler.\n" );
printf( "\n" ); // Add an empty line for space to the next CD.
x += 1;
}
}
else if( strcmp( userInput, "QUIT" ) ==0)
keepRunning = false; // we're finished
else
printf("ERROR: Unknown command \"%s\"!\n\n", userInput );
}
void DoCleanUp( void )
{
if( gDatabase != NULL ) // We have allocated memory?
{
free( gDatabase );
gDatabase = NULL; // Not really necessary, but good style.
gNumDatabaseEntries = 0;
}
}
|
phill writes: I get a error when I try to build the code that says "expected expression before 'void'"
here is my code
#include <stdio.h> // declares printf(), scanf(), and fpurge()
#include <stdbool.h> //declares bool
#include <string.h> // declares strcmp()
#include <stdlib.h> // We'll need that later for malloc() and realloc()
// Data structure
struct CDDatabaseEntry
{
char artist[40];
char composer[40];
char albumName[40];
int trackCount;
bool isSampler;
};
//Global variables:
int gNumDatabaseEntries = 0;
struct CDDatabaseEntry* gDatabase = NULL;
// Main event loop:
//Fetches user input and calls our DoXXX functions to do the work.
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 )
void DoNewCommand()
{
char yesOrNo;
// First, create a new array element (or a new array if we don't have one yet):
if( gDatabase == NULL )
{
gDatabase = malloc( sizeof(struct CDDatabaseEntry) ); // size of 1 element.
if( gDatabase == NULL ) // Still NULL? malloc() must have returned NULL due to error.
{
printf( "ERROR: Couldn't create a new entry!\n" );
return;
}
}
else
{
struct CDDatabaseEntry* newPtr = NULL;
newPtr = realloc( gDatabase, (gNumDatabaseEntries +1) *sizeof(struct CDDatabaseEntry) );
if( newPtr == NULL ) // Error! Out of memory?
{
// We just keep the old pointer in gDatabase.
printf( "ERROR: Couldn't create a new entry!\n" );
return;
}
// newPtr is our new ptr, gDatabase is no longer valid!
gDatabase = newPtr; // Remember newPtr in gDatabase.
}
// Make sure we remember we have one more entry:
gNumDatabaseEntries += 1;
// Now replace the garbage data in the new, last entry with data the user entered:
printf( "Artist Name: " );
scanf( "%39s", gDatabase[ gNumDatabaseEntries -1 ].artist );
fpurge( stdin );
printf( "Composer: " );
scanf( "%39s", gDatabase[ gNumDatabaseEntries -1 ].composer );
fpurge( stdin );
printf( "Album Name: " );
scanf( "%39s", gDatabase[ gNumDatabaseEntries -1 ].albumName );
fpurge( stdin );
printf( "No. of Tracks: " );
scanf( "%d", &gDatabase[ gNumDatabaseEntries -1 ].trackCount );
fpurge( stdin );
printf( "Sampler? (y/n): " );
scanf( "%c", &yesOrNo );
fpurge( stdin );
gDatabase[ gNumDatabaseEntries -1 ].isSampler = (yesOrNo == 'y' || yesOrNo == 'Y');
}
else if( strcmp( userInput, "LIST" ) == 0)
void DoListCommand()
{
int x = 0;
if( gDatabase == NULL )
{
printf("There are no CDs in the database.\n");
return;
}
while( x < gNumDatabaseEntries )
{
printf( "Artist Name: %s\n", gDatabase[ x ].artist );
printf( "Composer: %s\n", gDatabase[ x ].composer );
printf( "Album Name: %s\n", gDatabase[ x ].albumName );
printf( "No. of Tracks: %d\n", gDatabase[ x ].trackCount );
if( gDatabase[ x ].isSampler )
printf( "\tThis CD is a sampler.\n" );
printf( "\n" ); // Add an empty line for space to the next CD.
x += 1;
}
}
else if( strcmp( userInput, "QUIT" ) ==0)
keepRunning = false; // we're finished
else
printf("ERROR: Unknown command \"%s\"!\n\n", userInput );
}
void DoCleanUp( void )
{
if( gDatabase != NULL ) // We have allocated memory?
{
free( gDatabase );
gDatabase = NULL; // Not really necessary, but good style.
gNumDatabaseEntries = 0;
}
}
|
José writes: Finally I had my aha moment.
Thanks so much to the writer of this tutorial.
|
Isaac writes: @ phill, you've constructed your "DoNewCommand()" and other definitions inside the main() function. They should be outside and before the main() function for them to be recognized when called.
I've been taking a crack at extending the code and creating an "EDIT" function to edit the existing elements inside one data structure, to challenge my new knowledge. But I noticed that to "identify" one new structure, you simply add 1 to the existing "gNumDatabaseEntries" to offset the count when listing. That makes it difficult to search for individual data structures by name since they don't each have a unique identity.
My question is (since I'm still new to all this), am I seeing this correctly? Is there a simpler alternative with the existing code, or do I have to rewrite it so that it assigns each new data structure with it's own identity, thus having to rewrite the "LIST" function as a byproduct?
|
Alex writes: Hey, wondering if I could get some help with this..
I've modified this project into an employee database kind of thing, where the user is prompted for their name, email, gender, and whether they are employed or not. If they are employed, they are asked where and at what position. It works well, with the exception that I can't seem to figure out the syntax for displaying the gender in the List command. Here's what I've got:
struct CDDatabaseEntry
{
char firstName[40];
char lastName[40];
char email[40];
char employer[40];
char position[40];
int gender[2];
bool employed;
};
//All the usual things in betwee, works fine.
//Within the Main command:
printf("Gender (1/2): ");
scanf("%d", gDatabase[ gNumDatabaseEntries -1 ].gender);
fpurge( stdin );
//And here's my List command:
void DoListCommand()
{
int x = 0;
if( gDatabase == NULL )
{
printf("There are no employees in the database.n");
return;
}
while( x < gNumDatabaseEntries )
{
printf( "First Name: %sn", gDatabase[ x ].firstName );
printf( "Last Name: %sn", gDatabase[ x ].lastName );
printf( "Email Address: %sn", gDatabase[ x ].email );
if( gDatabase[ x ].gender == 1)
printf( "tThis person is a man.n" );
else {
printf("tThis person is a woman.n");
}
printf("Gender: %dn.", gDatabase[ x ].gender);
printf( "n" ); // Add an empty line for space to the next CD.
x += 1;
}}
The problem is in my syntax for the IF command of gender; I'm getting a "comparison between pointer and integer" warning, and upon running the program, the gender integer seems to be in the 10,000s, which obviously isn't right.
Any pointers? Thanks.
|
Uli Kusterer replies: ★ Alex,
you're declaring the gender as an array of 2 ints. You want 1 int. Also, the scanf statement takes a pointer, so you'll have to write &gDatabase[ gNumDatabaseEntries -1 ].gender there (since an array of 2 ints is a pointer, it works for you for now, but when you printf it, you print the address of the array. To get the right value with the array, you'd have to write gDatabase[ x ].gender[0] in your printf. But really, you want one int, not an array of two ints.
|
Jacob Hallman writes: Hey Uli,
I'm loving the tutorials, kind of makes me wish I'd majored in CS (graduating with a Philosophy degree in May). That said, I really like the Database program and was wondering how I would go about getting the data to persist from one session to the next?
I suspect it has something to do with writing and calling a file of some sort, and thus incurs a whole new level of complexity. Could you just point me in the right direction?
Thanks again for the help.
|
Andre writes: Hey Uli,
thanks a lot for the tutorial. I am refreshing my C and especially memory management is important to me. I wanted to say that and suggest you to introduce flattr to your tutorial.* I'd be glad to flattr you and I am sure that there are more that would.
Thanks again!
*Disclaimer: I am not affiliated with flattr, but I use it and I like the idea a lot.
|
Denis writes: #include <stdio.h>
Uli,
Great tutorial. Have three error messages I cannot reconcile, they are included as comments in the code below. I'm using xCode 4.1 on a brand new Mac mini with OS Lion.
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
// Data structure:
struct CDDatabaseEntry
{
char artist[40];
char composer[40];
char albumName[40];
int trackCount;
bool isSampler;
};
//Global variables:
int gNumDatabaseEntries = 0;
struct CDDatabaseEntry* gDatabase = NULL;
//Main event loop:
//Fetches user input and calls our DoXXX functions to do the work.
void DoNewCommand() //GOT ERROR HERE "no previous prototype for function DoNewCommand..."
{
char yesOrNo;
//First, create a new array element (or a new array if we don't have one yet):
if ( gDatabase == NULL )
{
gDatabase = malloc( sizeof(struct CDDatabaseEntry) ); //size of one element.
if( gDatabase == NULL ) // Still NULL? malloc() must have returned NULL due to error.
{
printf( "ERROR: Couldn't create a new entry!n");
return;
}
}
else
{
struct CDDatabaseEntry* newPtr = NULL;
newPtr = realloc( gDatabase, (gNumDatabaseEntries +1) *sizeof(struct CDDatabaseEntry));
if( newPtr == NULL ) //Error! Out of memory?
{
//We just keep the old pointer in gDatabase.
gDatabase = newPtr; //Remember newPtr in gDatabase.
}
//Make sure we remember we have one more entry:
gNumDatabaseEntries += 1;
//Now replace the garbage data in the new, last entry with data the user entered.
printf( "Artist Name: ");
scanf( "%39s", gDatabase[ gNumDatabaseEntries -1 ].artist );
fpurge(stdin);
printf( "Composer: ");
scanf( "%39s", gDatabase[ gNumDatabaseEntries -1 ].composer );
fpurge(stdin);
printf( "Album Name: ");
scanf( "%39s", gDatabase[ gNumDatabaseEntries -1 ].albumName );
fpurge(stdin);
printf( "No. of Tracks: ");
scanf( "%d", gDatabase[ gNumDatabaseEntries -1 ].trackCount );
fpurge(stdin);
printf( "Sampler? (y/n): ");
scanf( "%c", &yesOrNo );
fpurge(stdin);
gDatabase[ gNumDatabaseEntries -1 ].isSampler = (yesOrNo == 'y' || yesOrNo == 'Y');
}
void DoListCommand() //ERROR HERE, wants a semicolon at end of this line?
{
int x = 0;
if(gDatabase == NULL )
{
printf("There are no CDs in the database.n");
return;
}
while ( x < gNumDatabaseEntries )
{
printf( "Artist Name: %sn", gDatabase[ x].artist);
printf( "Composer: %sn", gDatabase[ x].composer);
printf( "Album Name: %sn", gDatabase[ x].albumName);
printf( "No. of Tracks: %dn", gDatabase[ x].trackCount);
if( gDatabase[ x ].isSampler )
printf("tThis CD is a sampler.n");
printf( "n" ); //Add an empty line for spae to the next CD.
x += 1;
}
}
void DoCleanUp( void );
{
if(gDatabase != NULL ) //We have allocated memory?
{
free( gDatabase );
gDatabase = NULL; //Not really necessary, but good style.
gNumDatabaseEntries = 0;
}
}
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...nn" );
else if( strcmp( userInput, "LIST" ) == 0)
printf( "I'll write the code for LIST later...nn" );
else if( strcmp( userInput, "QUIT" ) == 0 )
keepRunning = false; //we're finished.
else
printf( "ERROR: Unknown command "%s"!nn", userInput );
}
DoCleanUp(); //ERROR "implicit declaration of function DoCleanUp is invalid in C99"
}
return 0; //Void function DoNewCommand should not return a value.
}
|
Uli Kusterer replies: ★ Denis, you wrote all other functions inside the DoNewCommand() function's brackets for one. That won't work.
The "No Previous Prototype" warning shouldn't happen, but it may be that the defaults for Xcode 4.1 are different from those for Xcode 3. If it still occurs after you've moved the other functions out of DoNewCommand(), skip ahead to chapter 11 "Organizing your Code" and add declarations for all your functions.
It isn't strictly necessary, but these days Xcode likes to enforce good style.
|
Withan writes: It runs fine all the way, except when I quit. It stops on the DoCleanUp() line, without going to the subroutine, it seems. I've tried defining the subroutine "void DoCleanUp()" and "void DoCleanUp(void)"
I also tried moving the DoCLeanUp subroutine to the very top (after the global variables. My Xcode 4.2 compiler is giving warnings at the initiation of each subroutine: "No previous prototype of function 'DoCleanUp'" "No previous prototype of function 'DoListCommand'" "No previous prototype of function 'DoNewCommand'"
|
Withan writes: It runs fine all the way, except when I quit. It stops on the DoCleanUp() line, without going to the subroutine, it seems. I've tried defining the subroutine "void DoCleanUp()" and "void DoCleanUp(void)"
I also tried moving the DoCLeanUp subroutine to the very top (after the global variables. My Xcode 4.2 compiler is giving warnings at the initiation of each subroutine: "No previous prototype of function 'DoCleanUp'" "No previous prototype of function 'DoListCommand'" "No previous prototype of function 'DoNewCommand'"
|
Withan writes: Full text of code from previous comment
//
// main.c
// MyFirstProgram
//
// Created by Withan Lemmon on 11/17/11.
// Copyright (c) 2011 Advanta-STAR Automotive Research. All rights reserved.
//
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
// Data Structure:
struct CDDatabaseEntry
{
char artist[40];
char composer[40];
char albumName[40];
int trackCount;
bool isSampler;
};
//Global Variables
int gNumDatabaseEntries = 0;
struct CDDatabaseEntry* gDatabase = NULL;
//New Subroutines
void DoNewCommand()
{
char yesOrNo;
// First, create a new array element (or a new array if we don't have one, yet.
if( gDatabase == NULL)
{
gDatabase = malloc(sizeof(struct CDDatabaseEntry) ); //size of 1 element
if( gDatabase == NULL)
{
printf("Error: Counldn't create a new entry!n" );
return;
}
}
else
{
struct CDDatabaseEntry* newPtr = NULL;
newPtr = realloc( gDatabase,(gNumDatabaseEntries + 1) *sizeof(struct CDDatabaseEntry) );
if(newPtr == NULL) //Oh uh!
{
//We just keep the old pointer
printf( "ERROR: Couldn't create a new entry!n" );
return;
}
//newPtr is our new ptr, gDatabase is no longer valid
gDatabase = newPtr; //Remember newPtr in gDatabase.
}
//Remember we have one more entry
gNumDatabaseEntries += 1;
//now replace garbage data in latest entry with user input
printf("Artist Name: ");
scanf("%39s",gDatabase[gNumDatabaseEntries - 1 ].artist);
fpurge(stdin);
printf("Composer: ");
scanf("%39s",gDatabase[gNumDatabaseEntries - 1 ].composer);
fpurge(stdin);
printf("Album Name: ");
scanf("%39s",gDatabase[gNumDatabaseEntries - 1 ].albumName);
fpurge(stdin);
printf("Number of Tracks: ");
scanf("%d",&gDatabase[gNumDatabaseEntries - 1 ].trackCount);
fpurge(stdin);
printf("Sampler? (y/n): ");
scanf("%c",&yesOrNo);
fpurge(stdin);
gDatabase[gNumDatabaseEntries - 1 ].isSampler = (yesOrNo == 'y' || yesOrNo == 'Y');
}
void DoListCommand()
{
int x = 0;
if(gDatabase == NULL)
{
printf("There are no CDs in the database.n");
return;
}
while (x < gNumDatabaseEntries )
{
printf("Artist Name: %39sn",gDatabase[x].artist);
printf("Composer: %39sn",gDatabase[x].composer);
printf("Album Name: %39sn",gDatabase[x].albumName);
printf("Number of Tracks: %dn",gDatabase[x].trackCount);
if( gDatabase[x].isSampler)
{
printf("This CD is a sampler.n");
}
printf("n");
x += 1;
}
}
void DoCleanUp( void )
{
if(gDatabase != NULL) //We have allocated memory, i.e. added at least 1 record
{
free (gDatabase);
gDatabase = NULL;
gNumDatabaseEntries = 0;
}
}
//Main event loop, fetches user input and calls our DoXXX functions to do the work
int main ()
{
bool keepRunning = true;
char userInput[11];
while (keepRunning == true )
{
printf("Type NEW, LIST, or QUIT:n> " );
scanf( "%10s", userInput);
fpurge(stdin);
if ( (strcasecmp(userInput,"NEW") == 0) || (strcasecmp(userInput,"N") == 0) )
DoNewCommand();
else if( ( strcasecmp( userInput, "LIST" ) == 0 ) || ( strcasecmp( userInput, "L" ) == 0 ))
DoListCommand();
else if(( strcasecmp( userInput, "QUIT" ) == 0 ) ||( strcasecmp( userInput, "Q" ) == 0 ))
{
printf( "We're done." );
keepRunning = false; // We're finished.
}
else
printf( "ERROR: Unknown command "%s"!nn", userInput );
}
DoCleanUp();
return 0;
}
|
Uli Kusterer replies: ★ Withan, I just tried to run this program, and it works just fine for me. And reading over the code, it looks just fine as well. The warnings aren't important, they're just about style and don't influence what our programs do (the next version of this tutorial will get rid of them, but it requires a bit of restructuring of the whole tutorial).
You say it stops at the DoCleanUp() line. Is there any error message at the right of the line? Is there a little icon at the left of the line (like a little arrow, in addition to the little yellow warning icon from the prototype error)? You may have accidentally set a breakpoint (see Chapter 13). What happens if you click the little "continue program execution" right-facing arrow at the bottom of the window? (Not the "Run" play button at the top).
|
Withan writes: You're right. How do I even get rid of it? Never mind, I right clicked it (little blue arrow thingy) and it allowed me to disable or delete it. Nice functionality in the Xcode environment.
Does anyone use any other editors to write C for the Apple platforms? |
|
Home | Admin | Edit
Last Change: 2012-05-17 @319, © 2003-2012 by M. Uli Kusterer, all rights reserved. |
|
|