Building Directories and Contents - Reading and Writing Files - C++ All-in-One For Dummies (2009)

C++ All-in-One For Dummies (2009)

Book V

Reading and Writing Files

Chapter 4: Building Directories and Contents

In This Chapter

Creating and deleting directories

Getting the contents of a directory

Copying and moving, and why they are related

Moving and renaming files and why they are similar

We’re about to say something you might not believe: C++ provides no functions for creating directories and getting the contents of a directory.

Really! We know it’s hard to believe, but we can say two points about this:

♦ There really is a (more-or-less) good reason for this lack: C++ is a general-purpose language; issues that deal with directories are specific to individual operating systems. Thus, it doesn’t make sense to include such features in C++. (Supposedly. So they say. We guess. Whatever.)

♦ Some brave rebels have added some functions — and these functions exist in most C++ implementations. Whew! That’s a good thing — otherwise you’d have to call into the operating system to create or modify a directory.

C++ has a holdover from the C programming language in the header file stdio.h that includes functions for renaming and removing files and directories. (Interesting.) Oh yes, and there’s another one in there for creating a temporary file. (Even more interesting.)

image In this chapter we present you with ways to manipulate directories and files. (We have tested these routines only for the GNU gcc compiler that comes with the CodeBlocks product. If you’re working with a different compiler or operating system, try them out. They probably will work.)

image For the examples in this chapter, you need to add both #include <stdio.h> and #include <io.h> to the beginning of the source-code file. (Please do not confuse this file with ios.h. That’s another header file but not the right one to mess with just now.) Now again, if you’re working with a compiler other than CodeBlocks, we cannot guarantee that you’ll find io.h in your include directory. However, there’s a good chance you will. Look for it!

Manipulating Directories

There are a couple functions you can use for creating and deleting directories. These functions are in the io.h header file.

Creating a directory

If you want to create a directory, you can call the mkdir function. If the function can create the directory for you, it returns a 0. Otherwise it returns a nonzero value. (When we ran it we got a -1, but your best bet — always — is to test it against 0.)

Here’s some sample code that uses this function:

#include <iostream>

#include <stdio.h>

#include <io.h>

using namespace std;

int main()

{

if (mkdir(“c:/abc”) != 0)

{

cout << “I’m so sorry. I was not” << endl;

cout << “able to create your directory” << endl;

cout << “as you asked of me. I do hope” << endl;

cout << “you are still able to achieve” << endl;

cout << “your goals in life. Now go away.” << endl;

}

return 0;

}

imageNotice (as usual) that we used a forward slash (/) in the call to mkdir. In Windows, you can use either a forward slash or a backslash. But if you use a backslash, you have to use two of them (as you normally would to get a backslash into a C++ string). For the sake of portability, we recommend always using a forward slash.

Clearing up a directory of confusion

We want to make sure we’re all clear about a few terms. Back in 1994 when Microsoft created Windows 94 — oops, we mean Windows 95 (it shipped late and had to be renamed) — the kind folks at Microsoft started telling us that we had to use the term folder instead of directory. Blah, blah, blah. We say they’re called directories, and in our little private universe that includes only us and revolves around us, we use that term. In fact, that’s what most programmers call them, so in this chapter we’re calling them directories.

image It would be nice to create an entire directory-tree structure in one fell swoop — doing a call such as mkdir(“/abc/def/ghi/jkl”) without having any of the abc, def, or ghi directories already existing. But, alas, you can’t. The function won’t create a jkl directory unless the /abc/def/ghi directory exists. That means you have to break this call into multiple calls: First create /abc. Then create /abc/def, and so on.

imageIf you do want to make all the directories at once, you can use the system function, as we describe in “Using the quick-and-dirty method” sidebar, later in this chapter. If you execute system(“mkdir \\abc\\def\\ghi\\jkl”);, you will be able to make the directory in one fell swoop.

Deleting a directory

It’s fun to go on a cleaning spree and just toss everything out. And so it makes sense that deleting a directory is easy. To do it, you just call the rmdir function, passing the name of the directory. If you want to find out whether it worked, test its results against 0. Here’s some sample code:

#include <iostream>

#include <stdio.h>

#include <io.h>

using namespace std;

int main()

{

if (rmdir(“c:/abc”) != 0)

{

cout << “Life is difficult sometimes, and” << endl;

cout << “sometimes you just don’t get what” << endl;

cout << “you asked for. And this is one” << endl;

cout << “such case. I just couldn’t remove” << endl;

cout << “the directory for you. Better” << endl;

cout << “luck next time, my dear friend.” << endl;

}

return 0;

}

image This approach works only if the directory is not empty. If the directory has at least one file in it, the function can’t remove the directory — and returns a nonzero result. Then you get to see the nice, friendly message that we’re particularly proud of.

Getting the Contents of a Directory

If you want to read the contents of a directory, you’re really going against what’s available in the standard C++ language. However, the Kind Souls of the Great Libraries of C++ (that is, the people who wrote most of the available C++ libraries) usually built in some handy functions for getting the contents of a directory.

A directory usually contains multiple files as well as other directories. Getting a list of contents is involved. You don’t just call a function and get something back — we’re not sure what that something would be, other than a pretty basic list.

Of course, if the Standard C++ Library included a function for getting information, it would likely be a template class that contains the directory contents. Alas, the library doesn’t support it. (Delphi, supports it, but don’t get us going.) Instead, you have to climb through some functions. Here’s how it works.

1. Call _findfirst, passing it a pathname and a pattern for the files whose names you want to find.

For example, pass *.* to get all files in the directory, or *.txt to get all files ending in .txt. Also pass it a pointer to a _finddata_t structure.

2. Check the results of _findfirst.

If _findfirst returned -1, it didn’t find any files (which means you’re finished). Otherwise it fills the _finddata_t structure with the first file it found, and it will return a number that you use in subsequent calls to the various find functions.

3. Look at the _finddata_t structure to determine the name of the file, and other information such as create date, last access date, and size.

4. Call _findnext and pass it the following values: the number returned from _findfirst and the address of a _finddata_t structure

If _findnext returns -1, it found no more files; you can go to Step 5. Otherwise look at the _finddata_t structure to get the information for the next file found. Then repeat Step 4.

5. Call _findclose and pass it the number returned from _findfirst.

You’re all finished.

Youch! That’s kind of bizarre, but it’s the way things used to be done in the old days of programming, before the mainstream languages developed such civilized features as classes and objects. We just had structures; we had to pass a bunch of information around by hand (and walk to school, uphill both ways, in the snow).

Listing 4-1 shows how we implemented this elegant, old-fashioned process.

Listing 4-1: Using Code to Read the Contents of a Directory

#include <iostream>

#include <io.h>

#include <time.h>

#include <string>

using namespace std;

string Chop(string &str)

{

string res = str;

int len = str.length();

if (str[len - 1] == ‘\r’)

{

res.replace(len - 1, 1, “”);

}

len = str.length();

if (str[len - 1] == ‘\n’)

{

res.replace(len - 1, 1, “”);

}

return res;

}

void DumpEntry(_finddata_t &data)

{

string createtime(ctime(&data.time_create));

cout << Chop(createtime) << “\t”;

cout << data.size << “\t”;

if (data.attrib & _A_SUBDIR == _A_SUBDIR)

{

cout << “[“ << data.name << “]” << endl;

}

else

{

cout << data.name << endl;

}

}

int main()

{

_finddata_t data;

// Change this entry to match your Windows directory.

int ff = _findfirst (“C:/Windows/*.*”, &data);

if (ff != -1)

{

int res = 0;

while (res != -1)

{

DumpEntry(data);

res = _findnext(ff, &data);

}

_findclose(ff);

}

return 0;

}

You can see how in main we followed the steps we just outlined. And for each of the data structures, we used our own function called DumpEntry. The DumpEntry function prints out the information about the file. Here are some sample lines from when we ran the program:

Wed Jul 26 10:00:00 2000 16730 FeatherTexture.bmp

Thu Sep 28 12:01:54 2000 21692 FOLDER.HTT

Fri Dec 08 06:05:10 2000 0 [Fonts]

Note how, in the DumpEntry function, we’re testing whether the item is a directory. This is another old (but reliable) way to program: We check for the presence of one particular tiny little bit in the middle of the attrib member of the structure, like this:

if (data.attrib & _A_SUBDIR == _A_SUBDIR)

{

cout << “[“ << data.name << “]” << endl;

}

And finally, you’ll notice a strange function we included called Chop. That’s because when we wrote the program, we discovered that the ctime function — otherwise handy for formatting the time — adds a carriage return (or newline) to the end of the string it creates. So we chop that off. Otherwise the information after the date has to start on the next line of text, which wasn’t what we wanted.

image You might not have a C:/Windows directory on your computer, so you may want to change this code to include some directory you do have, such as C:/WinNT. (And as usual, you can see how we get to use those forward slashes!)

Copying Files

Ah, copying a file — something so simple, it happens all the time. Copy this file there; copy that file here. But what exactly takes place when you copy a file? You actually create a new file, and fill it with the same contents as the original file. And how do you do that? Well, from what we just said it sounds like you have to read each and every byte from the first file, and write it to the second. Big-time yuck.

But to make matters worse, copying a file means you have to make sure that you copy it exactly the same, that you don’t accidentally tack an extra 0 or two at the end of the file, or an extra carriage return or linefeed at the end of the file (which could happen when you copy a text file). When all is done, the two files should be identical — not only contain the same information, but also be the same size. (We can hear the nonconformists now: “Super big-time yuck!”)

And on top of all that, most good copy routines do even more! They give the new file a date that matches the date of the original file, and they will set all the attributes — including, say, read-only if the original is a read-only file. (We’re pointedly ignoring that if the file is read-only, then maybe we shouldn’t be able to copy it in the first place. . . . )

Suddenly copying a file doesn’t sound so easy after all!

Now this is going to go against the norm for every computer book you’ve ever seen, but we’re not going to give you code and tell you to use it for your file-copying operations. The reason for this heresy? Well, as simple as it may sound, we’ve seen too many people write code that’ssupposed to copy files but runs too slowly, or screws up the process, or both!

So we tucked a couple of sidebars into this section to give you the best way to copy a file. Enjoy!

Copying with windows: You’re in luck

If you’re programming in Windows, you’re in luck! As long as you’re not using the ancient Windows 3.1, you get a CopyFile function! To get ready to use it, you include the line #include <windows.h> in your program. Then here’s all you have to do:

CopyFile(“c:/dog.txt”, “c:/dog2.txt”, TRUE);

This copies from c:/dog.txt to c:/dog2.txt. But notice the final parameter: It’s the word TRUE in all capitals. What’s that? That’s a preprocessor macro defined somewhere in the bowels of the header windows header files. You have to use either TRUE or FALSE when calling the Windows functions. That’s because in the old days of C, when the early versions of Windows were invented, no bool type existed. Those resourceful people of the late twentieth century had to define their own TRUE and FALSE as integers (usually either 1 and 0, respectively, or 0 and 1, respectively). And by the way, that final parameter in CopyFile tells the function what to do if the file you’re copying to already exists: TRUE means don’t overwrite the existing file; just abort. FALSE means overwrite it.

Using the quick-and-dirty method

Okay, time for a secret: There’s another way you can copy a file, and you can use this to also move, delete, and rename files. However, this method we’re about to show you is absolutely not portable: in effect, if you do this on Windows, for example, you won’t be able to run the same program on Unix, and vice versa. That is, you have to make a version for the operating system you’re using. Now, if you’re familiar with DOS (remember that?) or the Unix shell, you can execute any DOS or Unix-shell commands by using the system function. If you use Dev-C++, you’ve already seen the system function many times:

system(“PAUSE”);

This runs the DOS pause command, which prints the message

Press any key to continue . . .

and waits for you to press the Any key (or any other key for that matter). Because the system function can run any DOS or shell command, you can use it to call the DOS copy command, like this:

system(“copy c:\\abc.txt c:\\def.txt”);

Note that we had to use the backslash, not a forward slash; DOS really doesn’t like forward slashes. To make the command DOS-friendly, we had to use two backslashes inside the string.

When you use this approach, you can run into some strange situations. For example, if you write a program that calls system and you’re planning to run it under the Cygwin environment in Windows, you can use the Unix-style cp command instead of the DOS copy command. The resulting weird command looks like this:

system(“cp c:\\abc.txt c:\\def.txt”);

But you can only use this command under the Cygwin environment. Otherwise it gives you a huffy error message:

‘cp’ is not recognized as an internal or external command, operable program or batch file.

Moral: You have to make sure that whatever command you call in the system function really exists in the environment from which you issue the call.

Moving and Renaming Files and Directories

Think about this: We have a file called

c:\dog.txt

and we’re going to rename it to

c:\temp\dog.txt

Is that a valid way to rename a file? If you notice, the file started out being called dog.txt, and afterwards it was called dog.txt. Did we rename it or did we just move it? Indeed, we moved it from the root directory on the C: drive to the temp directory on the C: drive. Why did we call this operation a rename? Because that’s also what we did! We’re thinking of the file’s real name as the entire pathname and filename together. Thus, the file’s name at first is c:\dog.txt, not just dog.txt. When we moved the file, it got a new name, c:\temp\dog.txt.

For that reason, you can move and rename by using the same function. If you want to move a file, rename it with a different path. Of course, the path must exist (details, details). If we try to rename c:\dog.txt to c:\temp\dog.txt and there’s no c:\temp directory, the rename fails and we get an error message.

The following example renames a file:

#include <iostream>

#include <stdio.h>

using namespace std;

int main()

{

if (rename(“/mydata/dog.txt”,”/mydata/dog.dat”) != 0)

{

cout << “I quit.” << endl;

}

if (rename(“/mydata/dog.dat”,”/mydata/temp/dog.dat”) != 0)

{

cout << “Same old story. No respect at all.” << endl;

}

return 0;

}

We used the rename function, passing first the old filename and then the new filename. The first call renames the file from /mydata/dog.txt to /mydata/dog.dat. The second call renames it from /mydata/dog.dat to /mydata/temp/dog.dat, which (in effect) moves the file.

imageYou can also give the file a new filename when you move it, as in this code:

rename(“/mydata/dog.dat”,”/mydata/temp/cat.txt”)

image There are conditions under which the rename operation won’t work:

♦ You’re renaming the file to move it to a new directory, but that directory does not exist. In this case, create the directory before you move the file.

♦ You’re renaming a file but some other file already exists under that name. In this case, either delete the other file or (better yet) make your program ask its users what they want it to do: Delete the old file (that is, “overwrite it”)? Abort the operation? Abort! Abort! Abort! (No, wait, that’s only for the self-destruct. Never mind.)

♦ You’re renaming a file to move it to a new directory, but there’s already a file by that name in that directory. In this case, as in the previous example, get your program to ask the users what to do — overwrite or abort?

imageNow for some really exciting news! Renaming also works with directories! You can move directory names around just as if they were files! But there’s a catch: If any program has a file open within that directory, the rename function won’t work. The operating system lets you move or rename a directory only if you’re not accessing any files inside the directory. That’s still true if you’re using a DOS window and staying inside the C: directory like this:

C:\>cd dog

C:\dog>

If you have a DOS window open with this sort of operation in it, you can’t move the dog directory unless you either move out of the dog directory first or close the DOS window before you move the dog directory. (Or you can just make a new directory and move everything out of the old directory.)