Section Contents
Topics should be read in order presented.
After clicking on a topic below, press "BACK" on your browser to return to Section Contents. Or, instead of jumping to topics and returning back to Section Contents, begin, rather, with the topic cin and cout as it appears immediately following Section Contents, and then advance sequentially through the remainder of the topics.
cin and cout:
cin and cout are features of C++ that provide new options for program input and output. In order to better understand how cin and cout work, let us return to the standard C functions used for input and output: scanf( ) and printf( ). For the programmer to be able to use scanf( ) and printf( ) in a program file, the programmer must first specify the <stdio.h> header file at the beginning of the file that is to make use of these functions, by means of the statement #include <stdio.h>. scanf( ) and printf( ) operate by means of what is called a format string. This 'format string', contained within double quotes, is passed to scanf( ) and printf( ) and is used to inform scanf( ) and printf( ) as to what variable types to expect (so that the variables being inputted or outputted will be manipulated correctly).
scanf( ) and printf( ) are informed as to what variable types to expect by the programmer placing a % sign within the format string, and by the programmer following the sign with a simplified abbreviation of the data type of the variable that is to be inputted or outputted. If output is being performed using printf( ), the format string may or may not contain text. After the format string, the names of the variables to be inputted or outputted are listed (in the order in which they appear in the format string), and the variables, separated by commas, are passed to scanf( ) and printf( ) along with the format string. There always exists the possibility, take note, that but a single variable be specified within the format string being passed to scanf( ) and printf( ) (hence meaning that only one variable name need be listed following the format string).
cin (pronounced see in) and cout (pronounced see out) are superior to scanf( ) and printf( ) in many ways. Let us first, then, familiarize ourselves with cin and cout. In order to be able to use cin and cout in a program file, the programmer must first specify the header file <iostream.h> by placing the statement #include <iostream.h> at the beginning of the program file in which cin and cout are to be made use of. A "header file" surrounded by angle brackets, quite clearly, as in <iostream.h>, is a file that grants the programmer access to a given collection of the C / C++ library of prewritten functions. Header files surrounded by angle brackets are not limited to providing access to prewritten functions, however: such files can contain other types of information written for the purpose of serving the programmer.
Among other header files available to the programmer dedicated to C++ program input and output, specifying the header file <iostream.h> at the beginning of a program file by means of the statement #include <iostream.h> makes possible the use of standard stream input and output within a program. What is meant by the term "stream"? A "stream" is simply a sequence of bytes. In the case of cin, a stream takes the form of a sequence of bytes that is in the process of flowing from a device (such as the keyboard) into memory, which is a form of input. In the case of cout, a stream takes the form of a sequence of bytes that is in the process of flowing from memory into a device (such as the monitor), which is a form of output. Input and output by means of a "stream" is not limited to the keyboard and the monitor, however. Other devices exist in which sequences of bytes can flow into and from memory. Such devices include a printer, a disk drive, and an Internet connection device. cin, as things go, is called the standard input stream. cout, in turn, is called the standard output stream.
What is meant above by the term "sequence of bytes"? A byte is a unit of information. In terms of the inner workings of how computers operate, a byte could be considered to be the largest chunk of data that a computer can process at any given time. A byte can assume one of 256 states, and could be considered to be a piece of memory large enough to hold a single text character. Each "state", along these lines, represents one of those text characters. The fact that bytes can be used to represent text characters is of importance here in that "streams" themselves are made up of text characters. In order to understand what determines the "state" of a byte, we must be made familiar with yet another term: the bit. Every byte is made up of 8 "bits". A "bit" can take on one of two forms: a one or a zero. As it would happen, a bit in the form of a "one" is thought of as the bit being on, and a bit in the form of a "zero" is thought of as the bit being off.
Given that every byte consists of 8 bits, and given that each bit in a byte can be "on" or "off", it does not take much effort to realize that all kinds of different "patterns" among the 8 bits are possible. In fact, if we were to manually calculate all possible combinations of "on" and "off" bits within a byte, we would come to a conclusion that agrees with what was stated above: the total number of combinations of "on" and "off" bits within a byte would result in 256 possible "states" concerning the byte. Therefore, given that a byte can assume one of 256 states, it should not be hard to come to the conclusion that a byte can refer to a maximum of 256 text characters (although some text character systems use only 7 of the 8 bits in a byte, resulting in a maximum of 128 text characters). Bytes are not used soley for representing text characters, however. Bytes occur sequentially in memory; variables of many data types other than char (the data type used to manage text characters) can be of a size consisting of multiple bytes. Having been introduced to the concept of the "bit", finally, it should not be hard to come to the realization that computers "think" in terms of ones and zeros.
Let us continue. There exists certain operators designed for use with cin and cout by which cin and cout perform input and output. cin operates by means of what is called the stream extraction operator (>>). cout, in turn, operates by means of what is called the stream insertion operator (<<). Displayed further below are examples of how these operators can be used along with cin and cout. The primary advantage of cin and cout over scanf( ) and printf( ) is that the programmer does not have to inform cin and cout as to what variable types to expect when inputting or outputting variables (unlike scanf( ) and printf( ) in which a format string must be relied upon to determine the correct variable types). As it would happen, cin and cout simply analyze the type of the variable that they are acting upon, and the variable is treated as it should be. In addition, using cout to output variables in the presence of text is much easier than with printf( ).
With printf( ), the '%' symbols placed within the format string that are used to designate what variable types printf( ) is to expect are "interwoven" with text, and the names of the variables to be outputted must be listed, separated by commas, following the format string. When outputting variables in the presence of text using cout, on the other hand, the programmer need only refer to a variable once: the programmer would implant the name of the variable directly within the text by using the stream insertion operator (<<) to set the variable apart from the text within which the variable appears - there is no awkward "format string" to deal with. Without doubt, and in summary, input and output using cin and cout is simpler and much more straightforward than input and output using scanf( ) and printf( ). Consider, finally, the following demonstration of the usage of cin and cout:
cout << "The number is " << num << '.' << endl; produces the same result as printf("The number is %d.\n", num); ___________________________________ cin >> num; produces the same result as scanf("%d", &num);
The stream manipulator endl, shown above, is the C++ equivalent of the '\n' escape sequence of C (also shown above). In C and C++, an "escape sequence" is a backslash ( \ ) followed by a character. When a program is outputting a collection of text enclosed in double quotes and encounters a backslash ( \ ) (possible examples of such a collection of text including a format string being passed to printf( ) or text being outputted by a cout statement), the presence of the backslash within the text tells the program that the character following the backslash has special meaning. This brings us back to endl: endl, like the '\n' escape sequence of C, designates the end of the current line of text, and tells the program to move down to the beginning of the next line.
Notice, if you will, that the period ( . ) near the end of the cout statement shown above is enclosed in single - ' - not double - " - quotes. In C and C++, individual text characters, as things go, are placed within single quotes. Collections of text characters (or "strings"), on the other hand, are placed within double quotes. Although it is true that strings most usually consist of multiple text characters, it is not impossible, at times, for a string to consist of but a single text character (hence meaning that the single text character be enclosed in double quotes).
Understanding how scanf( ) works requires of us to put forth emphasis upon an operator that has its origins in C: the ampersand operator (&). As you may already know from past experience with C, all variables in a program exist at their own particular location in memory (or "address"). In C and C++, when a program encounters the ampersand operator in front of a variable (as in &num), the program assumes the address in memory where the variable is stored. As you can see by examining the demonstration of the use of scanf( ) shown above, the address of the variable to be inputted must be passed to scanf( ). As you may recall from C, when the address of a variable is passed to a function, all modification to the variable within the function also applies to the version of the variable involved in the function call. Take further note that when the programmer is writing code that inputs a variable using cin, the placement of the ampersand operator (&) in front of the variable to be inputted (necessary when performing input using scanf( ) and perhaps a hassle) is not required.
In conclusion, on the topic of cin and cout, it should be stated that cin and cout are not the only input and output stream possibilities available in C++. Several more possibilities exist within <iostream.h>, as well as a vast, rich collection of alternate header files dedicated to C++ input and output.
Note: as you may recall from reading the introduction to this C++ tutorial, this tutorial is written based upon the standard set down by Microsoft Visual C++ 1.52. As was stated, this version of C++ could be considered to be very old. It was further stated that the reason for basing this tutorial upon such an "outdated" version of C++ was to make the material easier to understand: many of the aspects and features of newer, more up to date versions of C++ are advanced. This also applies to cin, cout, and endl: in more recent versions of C++, cin, cout, and endl are in some ways treated differently than with Microsoft Visual C++ 1.52.
Furthermore, and finally: in addition to how cin, cout, and endl are referred to in program code, there are several more examples in C++ in which there are "old" and "new" ways of doing things. It is quite possible that those reading this tutorial who are familiar with a "new" way of accomplishing something in C++ (as can be observed in more recent versions of C++) may recognize the fact that this tutorial, at times, uses an "old" style of programming in C++ (such as the standard set down by Microsoft Visual C++ 1.52). How are we to react, then, in response to this situation? Rather than letting the differences between the "old" and "new" ways of programming in C++ get in the way of our understanding of this tutorial, we should allow these differences to teach us that C++ is in fact an ever-evolving language.
new and delete:
What purposes do new and delete serve? Through the use of new and delete in a program, a programming practice known as dynamic memory allocation can be performed. What is dynamic memory allocation? Firstly, in order to make dynamic memory allocation possible, it is necessary that a pointer variable be provided: the presence of a pointer variable is what dynamic memory allocation requires. The purpose of the pointer variable, quite clearly, is to store the location in memory of the dynamically allocated memory so that the memory can be accessed later on in the program. The idea behind the use of dynamic memory allocation is simple: dynamic memory allocation, firstly, involves creating a variable (or array) by setting aside a portion of memory for that variable or array. The pointer variable is made use of in performing this feat. When the program is done using the memory, the pointer variable is made use of once again - this time to "give back" the dynamically allocated memory to the program, so that this "released memory", so to speak, can contribute to further dynamically allocated variables or arrays. new and delete are the means by which these tasks are performed.
Pointer variables, as you may recall from C, hold a location in memory. All variables, in turn, are, as stated earlier, stored at a specific location in memory (or "address"). This brings us to the concept of something that you may further recall from C: that of the asterisk operator ( * ). The program displayed below that we have yet to encounter makes use of this operator. The "asterisk operator", so to speak, fulfills its purpose when it is used along with a pointer variable. What sets a pointer variable (such as iptr) apart from a regular numeric variable (such as var)? Just as numeric variables hold a numeric value (an example of which could be 5), pointer variables, along the lines of what we are discussing right here and now, hold the address in memory where a numeric value is stored. This concept should not be considered out of the ordinary, given the fact that numeric variables themselves are located at a specific address in memory. We will learn throughout this tutorial what the use of pointer variables can accomplish.Given that numeric variables and pointer variables both involve a numeric value in one way or another, what are the differences between the two kinds of variables in terms of how that numeric value is accessed? To access the numeric value held by a regular numeric variable, the programmer need only refer to the name of the numeric variable in program code, as in cout << var. To access the numeric value stored at the address held by a pointer variable, on the other hand, the programmer must apply the asterisk operator to the pointer variable, as in cout << *iptr. There are other ways in which the asterisk operator can be used along with a pointer variable; we will be made familiar with such usages of the asterisk operator althroughout this tutorial.
In review: new is used to "set aside" ("allocate") a portion of memory large enough to hold the variable or array being created. delete, in turn, is used to "give back" ("deallocate") the set-aside memory to the program when the variable or array is done being used. This brings us back to dynamic memory allocation. The underlying idea behind using dynamic memory allocation, you see, is to set aside memory for a variable or array for only as long as the variable or array need be used. If the programmer always makes sure to "release" the memory "taken" by a variable or array when the memory is done being used, the program will never run out of space with which further variables or arrays can be created (by means of new). Memory is used only as needed. In other words, unlimited memory is possible. Consider the following program:
void main (void) { int *iptr; // int pointer. int index; // array index. iptr = new int; // memory allocation, *iptr = 5; // value assignment, cout << "*iptr = " << *iptr // output. << '.' << endl; delete iptr; // memory deallocation. iptr = new int[5]; // memory allocation, for (index = 0; index < 5; index ++) // value assignment. iptr[index] = index; for (index = 0; index < 5; index ++) // output. cout << "iptr[" << index << "] = " // << iptr[index] << '.' << endl; delete [] iptr; // memory deallocation. }
OUTPUT:
*iptr = 5.An int pointer variable ("int pointer") named iptr is declared at the beginning of main( ). main( ), as you probably already know, is the fundamental block of program code, part of every C and C++ program, that specifies the instructions that a program is to carry out. As you can see and as you probably already know, opening and closing braces are used to designate the beginning and ending of main( ). Worth mentioning is that these "braces", so to speak, are often referred to as "curly braces", which is no surprise, given the shape of the braces. When these "curly braces", so to speak, appear within program code, this tutorial refers to them simply as "braces" in order to make things simpler.
Let us continue. The 'i' in "iptr" within main( ) quite clearly, stands for int; naming this pointer variable "iptr" therefore designates that the pointer is of the int data type. Next, new is used to allocate memory for one int variable, and the location in memory of the int variable is stored within iptr. The asterisk operator ( * ) is then applied to iptr to assign the int variable to the numeric value 5, and then the asterisk operator is applied to iptr a second time, for the purpose of outputting, using cout, the value held by the int variable. Finally, delete is used upon iptr to deallocate the int variable (iptr being what holds the address of the int variable).
Afterward, new is used once again, this time to allocate memory for an int array possessing 5 elements, and the location in memory of the array is, as before, stored within iptr. As you can see by observing the remainder of the above program, iptr is the means by which this array will be accessed. Next, a for( ) loop is used to assign each element of the array a value, and then cout and another for( ) loop are used to output the values of the array elements. Finally, delete is used upon iptr to deallocate the array. Take note, if you will, that when delete is being used to deallocate an array allocated by means of new, empty square brackets ([]) are always placed in between delete and the name of the pointer that is being used to access the array. Having been made familiar with the basics of how dynamic memory allocation works, let us learn a bit more on the topic.
While a program is compiling (and before it executes), the compiler at those moments allocates memory for the bits and pieces crucial to the execution of the program. "Compiling", if you didn't already know, is the process by which a computer converts C++ source code into machine language (information in the form of ones and zeros that a computer can understand). This memory, allocated during the compile process, remains "taken" while a compiled program is running. However, it doesn't stop there. After the compile process is complete (and all memory for the information crucial to the execution of the program has been allocated), "open" memory remains that is free to be used by a running program. This "free" memory can be used for the application of dynamic memory allocation. Given that this memory is managed properly (using new and delete), space in memory will, as stated, always be available for the creation of new variables and arrays (by means of new) during the time of a program's execution. For every usage of new in a program, finally, there should be a matching usage of delete.
Notice, if you will, the presence of the double-slash symbol (//) to the right of the written code in several places in the above program. What is the purpose of this symbol? The use of the // symbol as shown above is the C++ style of commenting code. As you may recall from C, a "comment" in program code is a message, written by the programmer, whose purpose is to make the written code of a program easier to understand by those reading the program. Everything to the right of the // symbol - and all of the way to the end of the line the symbol is found on - is ignored by the compiler. As it would happen, there is a C style and a C++ style of commenting code. Each style has its own strengths and weaknesses; we will discuss each style of commenting code later on in this section.
operator overloading:
Just what is "operator overloading"? To understand this question, it first must be realized that C and C++ possess what are called "built-in data types". These are data types that need not be defined by the programmer, simply because they are already available for use as a part of the C / C++ language. Examples of such data types include int, long, float, double, and char. Secondly, it must be realized that C and C++ indeed provide a rich collection of operators with which to act upon and manipulate these built-in data types. Examples of such operators are +, *, =, &, and %. These operators, like the built-in data types, are also "built into" C and C++. To better understand operator overloading, let us study an example of how these built-in operators can interact with the built-in data types.
Before doing so, however, it is necessary that we return to a term to which we were introduced earlier in this section: that of the byte. A byte, in addition to being a piece of memory large enough to hold a single text character as we've talked about, is also the largest chunk of data that a computer can process at any given time (based on the understanding that a byte, as we've learned, consists of 8 bits). Bytes occur sequentially in memory; collections of information in memory can consist of multiple bytes. An example of such a "collection of information", so to speak, could be, as we will discuss next, a variable of one of the built-in data types of C and C++.
A commonly-used operator is the + operator: the operator used to perform addition. The + operator, first of all, is sometimes used to perform pointer arithmetic. To understand this, take note, if you will, that variables of all data types in C and C++ take up a certain amount of bytes in memory. For example, int may be 2, long may be 4, float may be 4, double may be 8, and char may be 1. When a pointer variable (which in this case holds the address in memory of a variable of one of the built-in data types of C and C++) is incremented by a certain number (we will assume 1) as in ptr + 1, the program analyzes the data types of the variables / numbers that surround the + operator. If the program finds an int pointer to the left of the + operator (with the numeric value 1 to the right of the + operator), the program will increment the pointer variable by 2 bytes. If the program finds a float pointer to the left of the + operator with the numeric value 1 to the right of the + operator, the program will increment the pointer variable by 4 bytes. If the program, finally, finds a char pointer to the left of the + operator with the numeric value 1 to the right of the + operator, the program will increment the pointer variable by 1 byte. The computer, quite simply, in incrementing the pointer variable to the left of the + operator by 1, adds onto the pointer variable the number of bytes suitable to the data type of the pointer variable.
The purpose of operator overloading is to use an operator that acts upon the already-existing built-in C++ data types in a new way by explaining how these operators act upon new, user-defined data types. Although not all of these built-in operators can be "overloaded", most can, and can serve purposes limited only by the imagination of the programmer. A "user-defined" data type, if you didn't already know, is a data type created by a programmer. Built-in data types such as char and float, make note, are not user-defined data types. A good example of a user-defined data type, then, would be a struct. To "overload" an operator, in summary, means to give new meaning to that operator. As a good programming practice, however, it is advisable that the new meaning given to such an operator be similar in nature to the original meaning the operator had when used with the built-in data types of C++. Specifying exactly how an operator is "overloaded" is done by means of what is called an operator function. The first step in putting an operator function into action is, of course, to specify the new, user-defined data type that an operator is to manipulate. This is done below in the form of the struct definition of the Frac data type (a fraction data type). Notice, if you will, the semicolon ( ; ) placed immediately after the struct definition: this is required for all struct definitions.
struct Frac // a fraction data type. { int numertr; // fraction numerator. int denom; // fraction denominator. };
The next step is to determine what the function prototype of the operator function will be. If you didn't already know, the purpose of the function prototype of a function is to prepare the function for use by specifying the number, order, and types of the parameters that the function will receive, as well as the return type of the function. After writing the function prototype of a function, the programmer then goes about writing the function body. The purpose of the body of a function, quite clearly, is to specify the actual program code contained within the function: the task that the function is to perform. Once the body of a function has been established, the programmer then goes about placing in program code what is called the function call. The purpose of a function call is to access a function for use. Although calls to functions appear in written program code, take note, it is not until a program has been compiled and is running that the actual function call takes place. When a program is running and it encounters a function call, the function call fulfills its designated purpose: the function call brings the program to the function specified by the call.
Let us continue. In order to write the function prototype of the operator function we will be working with, we must determine what the operator function receives and returns. Let us, then, examine this function prototype, displayed below. As you can see, this operator function overloads the * operator: this is specified by naming the function "operator", and by following "operator" immediately with the operator that the function is overloading (in this case the * operator). In C and C++, the * operator, as you probably already know, is used to perform multiplication. In the current present example of operator overloading, we will be using the * operator to multiply 2 Frac structs (fractions) together. As you can see by examining the function prototype displayed below, the operator function we will be working with receives 2 Frac structs and returns a Frac struct. As with variables, structs can be passed to and returned from functions. Inside of the operator function, the inner workings (which we have yet to discuss) of multiplying two Frac structs (fractions) together is performed, and the result is returned in the form of a Frac struct.
Frac operator*(Frac, Frac); // function receives 2 Frac structs // and returns a Frac struct.
Note: above it was stated the fact that the * operator is used to perform multiplication in C and C++. This is true. However, there exists what could be considered a possible source of confusion concerning the * operator. The reasoning behind this source of confusion lies in the fact that in C and C++, the * operator serves a dual purpose. One purpose of the * operator we have already discussed: the purpose of performing multiplication. Understanding the alternate purpose of the * operator does not require of us to go far beyond a concept that we have already been made familiar with earlier in this section: the concept of what we called the "asterisk operator". The * operator serving a dual purpose is an excellent example of operator overloading: giving multiple meanings to a single operator.
The "asterisk operator", as you may recall, fulfills its purpose when used along with a pointer variable. As you may further recall, pointer variables, such as iptr, hold a location in memory. To access what is stored at that location in memory, the programmer would apply the asterisk operator to the pointer variable, as in *iptr. Although it is true that the operator used to perform multiplication and the operator used to access what is stored at the location in memory held by a pointer variable are both asterisks, we will use the term "asterisk operator" to refer soley to the use of the * operator upon a pointer variable (in order to set that usage of the * operator apart from the usage of the * operator to perform multiplication).
The Frac data type consists of 2 members: a member that represents the "numerator" of a fraction, and a member that represents the "denominator" of a fraction. If you didn't already know, a numerator is the part of a fraction that is located on the top of a fraction. A denominator, on the other hand, is the part of a fraction that is located on the bottom of a fraction. How would one multiply 2 fractions together? It is easier than you may first think. To calculate the numerator of the fraction that is the result of 2 fractions being multiplied together, simply multiply both numerators together. To calculate the denominator of the fraction that is the result of 2 fractions being multiplied together, simply multiply both denominators together. That is all that is required.
Important: in C, once a struct has been defined, it is required that the keyword struct be placed to the left of the 'structure tag' of that struct (the structure tag in this case being Frac) when specifying that data type within struct variable declarations, function prototypes, and when the struct is a received parameter or return type within the body of a function. Not so in C++. In C++, after a struct is defined, one need only use its structure tag in all future references to it. Given a struct named struct Frac, the 'structure tag' in this particular situation would be, as stated, Frac. To declare a struct Frac variable in C++, therefore, the statement struct Frac frac is not necessary; all that is required to declare such a struct variable is the statement Frac frac. However: although the presence of the keyword struct is not needed in C++ within struct variable declarations, function prototypes, and when the struct is a received parameter or return type within the body of a function, C++ still requires the keyword struct for all struct definitions.
Furthermore: notice, if you will, in the previous paragraph, the usage of the term "struct variable". Take note here that this term is not speaking of a variable contained within a struct. Rather, the term is referring to the "bringing into being" of the struct itself. Given a struct named struct StructVar (which gets its name from the term "struct variable"), the statement StructVar var would be a good example of the "bringing into being" of a struct variable. Most usually, a variable contained within a struct is referred to as being a "member" of that struct.
If you are familiar with C (or C++), you know that all statements in program code always end with a semicolon ( ; ). However, you may have noticed in the written text of an above paragraph that the program code statement Frac frac (or StructVar var) does not end with a semicolon. Why not? In this tutorial, when a statement of program code statement appears in written text, most usually I choose not to end the statement with a semicolon, simply because it is common knowledge that all statements in C and C++ end with a semicolon. Quite clearly, because it is "common knowledge" that program code statements end with a semicolon, I find it unnecessary repetition to end such statements with a semicolon every time they appear in written text. When the reader encounters a statement of program code in written text that does not end with a semicolon, he is to base that encounter upon the understanding that the lack of a semicolon after the statement is simply a decision made in order to make the written text of this tutorial easier to read.
Sometimes, however, in written text, I make the decision to include a semicolon after a program code statement. This decision is made, quite clearly, when the presence of the semicolon has special meaning. A good example of a semicolon having "special meaning" is when it is part of a function prototype, such as void Func (void);. Function prototypes, as you may already know, always end with a semicolon. Without the presence of a semicolon at the end of a function prototype, you see (as in void Func (void) ), the reader might incorrectly assume that the function prototype is the parameter list of a function body, which it is not. There are also other situations in which the presence of a semicolon following a program statement within written text is necessary. Let us continue. Although program code statements in written text may or may not end with a semicolon, in the code examples that are set apart from written text, program code statements always end with a semicolon: this lets the reader know that the program code he is viewing is complete in every way and is not missing anything that would make it incomplete.
What of the received parameters in the function prototype Frac operator*(Frac, Frac); shown above? Their order of occurrence within the parameter list is of importance: the Frac struct on the left side of the parameter list stands for the Frac struct variable found to the left of the * operator (when the program finds 2 Frac struct variables being multiplied together in program code), and the Frac struct on the right side of the parameter list stands for the Frac struct variable found to the right of the * operator when the program finds 2 Frac struct variables being multiplied together in program code. It's that simple. Now that we've produced the proper function prototype for our operator function, it is now time to study the function body of the operator function, displayed below.
The code is really self-explanatory: first, a new Frac struct variable is declared to hold the result of the numerators and denominators of the two received Frac structs being multiplied together. Next, the numerators of both received Frac structs are multiplied together, and the result is stored within the numerator of the declared Frac struct variable; afterward, the denominators of both received Frac structs are multiplied together, and the result is stored within the denominator of the declared Frac struct variable. The results of the numerators and denominators being multiplied together having been stored within the declared Frac struct variable, the operator function finally returns the contents of the struct variable.
Frac operator*(Frac frac1, Frac frac2) { Frac frac; // hold product of fractions. // numerators multiplied. frac.numertr = frac1.numertr * frac2.numertr; // denominators multiplied. frac.denom = frac1.denom * frac2.denom; return frac; }
Before we discuss the above operator function, focus your attention, if you will, upon the struct variable declaration Frac frac inside of the function. How is it possible, one may reason, given this statement, to declare a struct variable, when the struct data type in the declaration and the struct variable being declared both possess the same name? Understanding how this is possible requires of us to be introduced to a new concept: that of a program being case sensitive. C and C++ are both case sensitive languages. What exactly does that mean? To say that a programming language is "case sensitive" means that a given letter in its uppercase form, and that given letter in its lowercase form, are seen by a program as having 2 separate meanings. For example: in a "case sensitive" programming language, a program would perceive the uppercase letter "A" and the lowercase letter "a" as being 2 different representations of the same letter. Not all programming languages are case sensitive, however: in such a language, a program does not distinguish between a given letter in its uppercase form and that given letter in its lowercase form. Because C++ distinguishes between uppercase and lowercase letters, in summary, Frac and frac could each be considered to be 2 unique expressions.
In order to understand just how the above operator function works, observe, if you will, the program displayed below. At the beginning of main( ), the 2 Frac struct variables - frac1 and frac2 - that will be used to overload the * operator are declared, and their members are initialized. Then, the Frac struct variable - frac - is declared, whose purpose is to receive the result of the two Frac struct variables being multiplied together. The next statement - frac = frac1 * frac2 - is of highest importance. When the program finds the Frac struct variables on both sides of the * operator, it knows the operator function appropriate the situation and generates the function call operator*(frac1, frac2). Inside of this operator function, the inner workings (as described earlier) of multiplying the two fractions together is performed, and the results of the calculation are returned from the operator function in the form of a Frac struct variable. In main( ), frac is assigned to the return value of the operator function. main( ) having received that return value, cout is used to display a message to the user as to the fraction obtained from multiplying the two Frac struct variables together, which can be observed in the OUTPUT of the program.
void main (void) { Frac frac1 = {2, 3}; // the fraction 2/3. Frac frac2 = {3, 5}; // the fraction 3/5. Frac frac; // hold product of fractions. frac = frac1 * frac2; // operator overloading. // result is 6/15. cout << frac1.numertr << '/' << frac1.denom << " * " << frac2.numertr << '/' << frac2.denom << " = " << frac.numertr << '/' << frac.denom << endl; }
OUTPUT:
2/3 * 3/5 = 6/15What, then, is the advantage to using operator overloading (which takes the form of the statement frac = frac1 * frac2 in the above program)? Using the operators that were originally designed for the built-in data types of C++ on new, user-defined types (such as a struct) allows the programmer to assign meaningful processes among those user-defined types, without having to know the inner workings of how the operator does what it does. For example, the programmer need not "worry" about what a 'numerator' or a 'denominator' are in order to multiply 2 Frac struct variables together: the programmer simply tells the program to multiply 2 Frac struct variables together and the program handles the details. These "details", so to speak, are taken care of by the operator function that uses the Frac data type to overload the * operator. The use of operator overloading upon new, user-defined data types (which takes the form of the Frac data type in the above program) enables the programmer to give meaning to processes not directly evident in the struct variables themselves.
Displayed below is another example of operator overloading. As before, the * operator (the operator used to perform multiplication) is being overloaded. However, rather than using a fraction data type - Frac - to overload the * operator, the * operator is overloaded using a struct data type that involves decimal-point numbers: the data type Dec. Displayed below is the struct definition of the Dec data type:
struct Dec // a decimal-point data type. { float decimal_part; // part to right of decimal point. int whole_part; // part to left of decimal point. };
Having defined the data type we will use to overload the * operator (the Dec data type), the next step is to write the function prototype for the operator function that will overload the * operator. Having already been made familiar with the operator function that uses the Frac data type to overload the * operator, nothing here should be out of the ordinary:
Dec operator*(Dec, Dec); // function receives 2 Dec structs // and returns a Dec struct.
Next comes the body of the operator function:
Dec operator*(Dec dec1, Dec dec2) { Dec dec; // hold product in struct Dec form. float dec_num1, dec_num2; // variables used to combine // whole and decimal parts. float product; // product of combined #'s. // whole and decimal parts combined. dec_num1 = dec1.whole_part + dec1.decimal_part; dec_num2 = dec2.whole_part + dec2.decimal_part; // combined numbers multiplied. product = dec_num1 * dec_num2; // removes decimal part of #. dec.whole_part = (int)product; // subtraction gets back decimal part. dec.decimal_part = product - dec.whole_part; return dec; }
As before, a new Dec struct variable is declared at the beginning of the operator function, to hold the result of the two received Dec structs being multiplied together. As part of the process of multiplying the two Dec structs together, the two members of the declared Dec struct variable - decimal_part and whole_part - are assigned values. The members of the declared struct variable having been given values, the contents of the struct variable is ultimately returned from the function.
What goes on inside of this operator function? In order to answer this question we must study the Dec data type itself in greater detail. As we already know, C and C++ have built-in data types for dealing with decimal-point numbers: float and double. By means of but a single variable of one of these built-in data types, a complete decimal-point number can be stored. An example of the declaration and initialization of such a variable could be, therefore, double val = 5.7. As you can see, only one variable, as stated, is needed in order to store the number. What the Dec data type attempts to do, you see, is to divide a decimal-point number into two separate entities (in the form of the members of a struct): the part of the number to the right of the decimal point, and the part of the number to the left of the decimal point. Why would one desire to represent a decimal-point number in this manner?
Representing a decimal-point number in the form of a struct, you see, introduces into the situation an opportunity to portray an example of operator overloading in a program. A struct, you see, is a user-defined data type; user-defined data types, furthermore, are what make operator overloading possible. The feat that the above operator function attempts to perform, quite clearly, is to treat a decimal-point number as if it were a user-defined data type. This brings us back to an important point stated earlier: the use of operator overloading enables the programmer to give meaning to processes not directly evident in the struct variables themselves. How does this apply to the Dec data type? In a nutshell, the programmer need not "worry" about what goes to the right (or to the left) of the decimal point of a number when working with a Dec struct variable: the programmer need only multiply two Dec struct variables together in program code, and the program handles the details. These "details", so to speak, are taken care of by the operator function that uses the Dec data type to overload the * operator.
Let us discuss the body of the above operator function in greater detail. The contents of the function is really self-explanatory, with comments added to document the code. Firstly, the whole_part and decimal_part of each of the received Dec struct variables are added together, and each sum is stored within a float variable. Next, the two float variables are multiplied together and the result is stored within yet another float variable (a variable that we will call product). Next comes the "details" of the operator function. After product receives its value, the "whole" part of product is determined by "slicing off" the portion of product that lies to the right of the decimal point of the number that product holds; the result is stored within the whole_part member of the Dec struct variable that had been declared at the beginning of the operator function. Finally, the value of the decimal_part member of the declared Dec struct variable is calculated by subtracting the whole_part member of the Dec struct variable from the original value held by product. At the end of the operator function, the contents of the Dec struct variable declared at the beginning of the operator function is returned from the function.
void main (void) { Dec dec1 = {.5, 4}; // the decimal-point number 4.5. Dec dec2 = {.2, 3}; // the decimal-point number 3.2. Dec dec; // hold product of decimal-point #'s. dec = dec1 * dec2; // operator overloading. // result is 14.4. cout << dec1.whole_part + dec1.decimal_part << " * " << dec2.whole_part + dec2.decimal_part << " = " << dec.whole_part + dec.decimal_part << endl; }
OUTPUT:
4.5 * 3.2 = 14.4Displayed above is main( ). At the beginning of main( ), the 2 Dec struct variables (dec1 and dec2) that will be used to overload the * operator are declared, and their members are initialized. Declared next is the Dec struct variable (dec) that will be used to receive the result of these two struct variables being multiplied together. The next statement - dec = dec1 * dec2 - is of highest importance. Upon finding a Dec struct variable on both sides of the * operator, the program knows the operator function appropriate to the situation and generates the function call operator*(dec1, dec2). Inside of the operator function, the program handles the inner workings (as just described) of multiplying the two Dec struct variables together, and the results of the calculation are returned from the function in the form of a Dec struct variable. In main( ), the Dec struct variable dec receives this return value, and then cout is used to display a message to the user as to the value obtained from multiplying the two Dec struct variables together, which can be observed in the OUTPUT of the program.
If the Frac data type and the Dec data type happened to be used in the same program (both of which overload the * operator) and the program finds a struct variable on both sides of the * operator, the program would examine the types of the struct variables that surround that usage of the * operator. If the program finds 2 Frac struct variables (or 2 Dec struct variables) surrounding the * operator, the appropriate version of the * operator function would be called, based upon the types of the struct variables encountered. That is, if the program finds a statement in which 2 Frac struct variables surround the * operator, the program uses this knowledge to call the Frac version of the * operator function. If the program encounters a statement in which 2 Dec struct variables surround the * operator, the program uses this knowledge to call the Dec version of the * operator function. It's that simple - much like the example of pointer arithmetic described earlier. Take note that not all operator functions return a value: to specify this, one would simply designate the return value of such a function as void. Equally so, it is entirely possible that an operator function can receive only one parameter (as is the case for operators that involve only one argument).
It is possible to overload operators that perform logical operations. Such an operator includes the equality operator (= =). One important point worth mentioning here is that the equality operator (= =) is not to be confused with the assignment operator (=). An example of the assignment operator being put to use could be a = b. In a nutshell, this expression fulfills its purpose by taking the value held by b and placing that value within a.
A "logical expression", if you didn't already know, is a statement in program code that can be either true or false. A possible example of a logical expression could be the statement if (a = = b). The purpose of the equality operator (= =) within this statement, as you may recall from C, is to determine if the variable a and the variable b both contain the same value. This brings us back to past discussion earlier in this section on bytes and bits. A "byte" consists of 8 "bits". A "bit", furthermore, can assume one of two states: on or off. As things go, a bit that is "on" is considered to be a 1, and a bit that is "off" is considered to be a 0.
What do the two states of a bit have to do with logical operations? When a program encounters a statement such as if (a = = b), the expression (a = = b) will generate one of two values: a 1 or a 0. If the expression is true, quite clearly, the expression will generate a 1. If the expression is false, on the other hand, the expression will generate a 0. This concept should not be too difficult to comprehend, given the fact that, as we've learned, computers "think" in terms of ones and zeros. In order to better understand things here, let us study a couple of pieces of code that demonstrate what we've been talking about. For example, if we were to write a statement such as cout << (5 = = 5) in a program, the cout statement would output 1, because of the clear fact that the numeric value 5 is equal to itself. If we were to write a statement such as cout << (3 = = 5), on the other hand, the cout statement would output 0, simply because the numeric value 3 and the numeric value 5 are not equal.
This brings us back to the idea of overloading the equality operator (= =). As the first step towards learning how we would perform such a feat, we will create a data type (the Date data type) that will be used to overload the equality operator. Displayed below is this new data type:
struct Date { int day; int month; int year; };
Having defined the data type we will use to overload the equality operator, the next step is to write the function prototype for the operator function that will overload the equality operator:
int operator==(Date, Date); // function receives 2 Date structs // and returns an int variable.
As you may recall from past discussion, the struct on the left side of the received parameter list of this operator function represents the Date struct variable to the left of the = = operator when the program finds a Date struct variable on both sides of that operator; the struct on the right side of the received parameter list represents the Date struct variable to the right of the = = operator. Next comes the body of the operator function:
int operator==(Date date1, Date date2) { if ( (date1.day == date2.day) && // individual members (date1.month == date2.month) && // of Date structs (date1.year == date2.year) ) // compared. return 1; // all members are equal. else return 0; // all members not equal. }
Displayed below, finally, is main( ):
void main (void) { Date date1 = {25, 12, 2005}; // Date structs declared Date date2 = {25, 12, 2005}; // and initialized. if (date1 == date2) // operator overloading. cout << "dates are equal."; else cout << "dates are not equal."; }
OUTPUT:
dates are equal.As we've learned, using user-defined data types (in this case a struct variable) to overload C++ operators enables the programmer to give meaning to processes not directly evident in the struct variables themselves. With this in mind, observe, if you will, the statement if (date1 = = date2) inside of main( ). This if( ) statement asks the program to determine if the 2 Date struct variables declared at the beginning of main( ) are equal. As you can see by observing the declaration of both Date struct variables in which their members are initialized, both dates are indeed equivalent. Upon the program finding Date struct variables on both sides of the equality operator (= =), the program knows the operator function applicable to the situation and generates the function call operator= =(date1, date2). What does the operator function operator= =( ) accomplish here? As with the practice of overloading C++ operators in general, overloading the equality operator in this situation makes things much simpler for the programmer. In what way? In a nutshell, the programmer does not have to "worry" about what a day, month, or year are in order to find out if 2 dates are equivalent: the programmer simply tells the program to compare 2 dates and the program handles the details. These "details", so to speak, are taken care of by the operator function that uses the Date data type to overload the = = operator.
This brings us to the body of the operator function operator= =( ). The purpose of this operator function is to manage the inner workings of determining whether or not 2 Date struct variables are equal. The function performs this feat by means of an if( ) statement. The if( ) statement determines the equality of the 2 Date struct variables by comparing the individual members of each struct variable to each other: both days are compared, both months are compared, and both years are compared. If all three members of both struct variables are equal, the operator function returns 1 (which tells the program that the expression (date1 = = date2) inside of main( ) is true).
However, if any of the members of both struct variables are not equal, the program moves on to the else statement inside of the operator function and the operator function then returns 0 (which tells the program that the expression (date1 = = date2) is false). Upon the operator function having returned a 1 or a 0, that return value is then considered applicable to the logical expression (date1 = = date2) inside of main( ). Because the individual members of the Date struct variables declared at the beginning of main( ) are all equivalent (as can be seen by examining the initializations of the members of both struct variables), the operator function returns 1 (telling the program that the expression (date1 = = date2) inside of main( ) is true), and the if( ) statement inside of main( ), by means of a cout statement, expresses to the user that both dates are in fact equal (which can be observed in the OUTPUT of the above program). If the operator function returns 0, on the other hand, the if( ) statement inside of main( ) would move on to the else statement of main( ), where cout is used to inform the user that the two dates are not equal.
Let us discuss the return type of the operator function operator= =( ): int. As you may recall from C, int variables hold whole (non-decimal) numbers. This includes negative numbers. This brings us back to the idea of 1 representing true and 0 representing false. That programs in C (and C++) will generate a 1 or a 0 after analyzing a logical expression is indeed correct. However, things don't end there. Sometimes, a program performs a logical operation based not upon a logical expression, but rather upon the contents of a variable. The bottom line: when a logical operation is performed based upon the contents of a variable, 0, as is the case with a logical expression, also represents false. What must be emphasized and clearly understood here is that when a logical operation based upon the contents of a variable is performed in C and C++, any nonzero value is considered to represent true. As a means of better understanding this idea, consider the piece of code shown below:
int var = 5; if (var) cout << "true"; else cout << "false";
OUTPUT:
trueThis piece of code is quite self-explanatory. First, the variable var is declared and initialized to the numeric value 5. Next is an if( ) statement that takes the variable var as an argument. Because var holds a nonzero value (the numeric value 5), the expression var within the if( ) statement is considered to be true. Therefore, as can be seen in the OUTPUT of the program, a cout statement is used to inform the user as to the fact that the argument var in the if( ) statement is true (hence meaning that var holds, as stated, a nonzero value). If var happened to hold the numeric value 0 upon the program encountering the if( ) statement, on the other hand, the program would move on to the else statement, where a cout statement would inform the user that the expression var within the if( ) statement is in fact considered to be false.
Let us review what we've learned concerning performing logical operations. When a program encounters a logical expression, such as (a = = b), the expression will generate a 1 if the expression is true, or a 0 if the expression is false. We then learned that when dealing with the contents of a variable (in this case the contents of an int variable) within a logical operation, 0 is considered to be false; any nonzero value is considered to be true (as is the case with C and C++). This brings us back to the return type of the operator function operator= =( ): int. By examining the body of this operator function, you will see that the function will return one of two values: a 1 (to represent true) or a 0 (to represent false). This is no surprise, given the fact that, as stated, computers "think" in terms of ones and zeros.
Consider, if you will, the following: that the operator function operator= =( ) will return either a 1 or a 0 is given. There is no reason, it would appear, for this function to return a nonzero value other than 1 in order to specify that the expression in the if( ) statement of the function is true. For the operator function operator= =( ) to return a nonzero value other than 1 to represent true would, quite clearly, introduce unnecessary complication into the situation. Yet int variables can hold nonzero values other than 1. What we need, as you will agree, is an entirely new data type dedicated to handling logical operations. We have yet, in this tutorial, to do just that: within the next section on classes, we will create a data type specifically designed for managing true / false conditions within a program.
As we've learned, a "logical expression" is a statement in program code that can be either true or false. We then learned of the equality operator (= =). As you may recall from C, a statement in which 2 arguments surround the equality operator is true if and only if both arguments contain equal values.
Focus your attention, if you will, upon the body of the operator function displayed earlier which compares the members of the Date struct. Upon doing so, you will find the use of what is known as the logical AND operator of C and C++ (&&). An example of a statement in which the logical AND operator is put to use could be if ( (3 = = 3) && (5 = = 5) ). As you may recall from C, the logical AND operator generates a 1 (is considered true) if and only if BOTH logical expressions that surround the && operator are true.
The logical AND operator is not the only logical operator in C and C++. Next we will discuss the logical OR operator ( | | ) of C and C++. As you can see, this operator is portrayed by means of 2 vertical bars. An example of a statment in which the logical OR operator is put to use could be if ( (3 = = 3) | | (5 = = 5) ). As you may further recall from C, the logical OR operator generates a 1 (is considered true) if at least one of the expressions that surround the | | is true.
This brings us to the third and final logical operator of C and C++: the logical NOT operator ( ! ). As you can see, this operator is portrayed by means of an exclamation point. Unlike the logical AND and logical OR operators, however, the use of the logical NOT operator ( ! ) involves only a single logical expression. A possible example of the use of the logical NOT operator could be if (! (5 = = 5) ). How does the logical NOT operator work? The logical NOT operator is always placed to the left of the logical expression that it is evaluating. When the logical NOT operator is applied to a logical expression, the logical NOT operator will generate a value that is the opposite of the value that the logical expression generates. Therefore, if the logical expression generates a 1, the logical NOT operator will generate a 0; if the logical expression generates a 0, the logical NOT operator will generate a 1.
This brings us to what is known as the inequality operator. Consider the following logical expression: if (! (3 = = 3) ). Based upon what we've learned, we can conclude that the expression (3 = = 3) generates a 1 because it is true, and because the logical NOT operator generates the opposite of that result, and the final outcome is therefore false. As it would happen, there is a "shortcut" to the expression if (! (3 = = 3) ): the inequality operator. This expression can be written as follows: if (3 != 3). In this expression, a 0 is generated, because the expression (3 != 3) is false. This is because 3 is equal to itself: to say that 3 is not equal to itself, as the expression implies, is a false statement.
One point worth mentioning is the presence of parentheses, shown earlier in the text, around the separate logical expressions that surround the && and | | operators, as in if ( (3 = = 3) && (5 = = 5) ) and if ( (3 = = 3) | | (5 = = 5) ). What must be empasized here is that the presence of parentheses in these situations is not actually required. For example, if we were to rewrite these two statements as if (3 = = 3 && 5 = = 5) and if (3 = = 3 | | 5 = = 5) in a program, the rewritten statements would achieve identical results. The reason that I choose to include these extra parentheses, quite clearly, is because they make the program code more readable: the parentheses "group", so to speak, the contents of the two expressions that surround the logical operators, making things clearer to the reader. One final point worth mentioning, in conclusion, is that when applying the logical NOT operator in a program, as in if (! (5 = = 5) ), parentheses are not optional: parentheses are required in order for the logical NOT operator ( ! ) to behave as it should.
As with most operators in C++, it is possible to overload the stream insertion operator (<<). As you may recall from earlier in this section, the stream insertion operator is used along with cout to perform program output. As was the case when we overloaded the = = operator, we will use the Date data type to overload the << operator. Displayed, once again below, is the struct definition of the Date data type:
struct Date { int day; int month; int year; };
As the first means of understanding the idea of overloading the stream insertion operator using the Date data type, consider the following rather brief program:
void main (void) { Date date = {25, 12, 2005}; // Date struct declared // and initialized. cout << "The date is " // output. << date << '.'; // }
OUTPUT:
The date is 12/25/2005.This program is really quite self-explanatory. The first statement is the declaration of a Date struct variable in which the day, month, and year members of the struct variable are initialized. The second and final statement of the program consists of a cout statement. What is there about this cout statement that we need to know? The presence of the Date struct variable date within this cout statement is an example of the stream insertion operator (<<) being overloaded. As with all overloaded operators, the stream insertion operator in the above program requires an operator function to tell the program how to manage the inner workings of that operator. Although we will not show the function prototype and function body of this operator function here (in that the programming necessary to do so is a bit advanced), the operator function that overloads the stream insertion operator (<<) in the above program informs the program as to how the stream insertion operator is to behave in the presence of a Date struct variable.
As you can see in the above program, cout and the stream insertion operator are used to output a Date struct variable. This brings us back to an important fact concerning the use of operator overloading in a program: the use of operator overloading in a program enables the programmer to give meaning to processes not directly evident in the struct variables themselves. This is precisely what we see in the above program. If the programmer wanted to output the date (in the form of the Date struct variable date), the programmer need not "worry" about what a day, month, or year are (or worry about applying forward slashes to the date for that matter) in order to perform that output. Rather, the programmer need only write a cout statement that uses the stream insertion operator (<<) in the presence of a Date struct variable, and the program then handles the details. These "details", so to speak, are taken care of by the operator function that uses the Date data type to overload the << operator. The results of outputting the Date struct variable can be observed in the OUTPUT of the above program.
void parameter list:
As you may recall from C, and from past discussion earlier in this section, every function in C can be thought of as being reducible to 3 main components: the function prototype, the function body, and the function call. The function prototype always comes first. It prepares the function for use by specifying the number, order, and types of the parameters that the function will receive, as well as the return type of the function. The function body contains the code of the function (the task that the function is to perform): the code may tell a program how to manipulate the variables passed to the function (if any such variables were passed to the function). The programmer may choose to declare variables inside of the function, and the function may return a value. Finally, the function call is how a function, whose body has been provided, is accessed by a program for use. As was discussed earlier, although calls to functions appear in written program code, it is not until a program has been compiled and is in the process of running that the actual function call takes place.
In C we learned that some functions receive no parameters - they have a parameter list of type void. If a function does in fact receive no parameters as just mentioned, then the C style of writing its function prototype - as well as the C style of writing the function body - would require the keyword void to be used as the parameter list of the function: the programmer would follow the name of the function with the keyword void enclosed in parentheses: (void). (Take note, however, that this is not true for the function call in C.) In the C++ style of designating a void parameter list, however, we are offered a new option: in the function prototype and function body of a function that receives no parameters, the keyword void enclosed in parentheses is not required following the name of the function - one need only use empty parentheses to portray a void parameter list: ( ). This 'shortcut' of C++ can make things easier for those who find it helpful.
Displayed below is a summary of what we've just discussed portraying the differences between the C and C++ styles of designating a void parameter list. In this summary, we will make use of a function that we will call Func( ):
void parameter list | ||
C style | C++ style | |
function prototype | void Func (void); | void Func(); |
function body | void Func (void) { // function code } |
void Func() { // function code } |
function call | Func(); | Func(); |
From now on within this tutorial, we will use the C++ style of designating a void parameter list.
new commenting style:
In C we were introduced to a means of commenting code - that is, the practice of including messages within program code for the purpose of making it easier to understand by people other than the programmer. Using comments, furthermore, without doubt, can also help aid the programmer in understanding the meaning of the code that he himself has written. The C style of commenting consists of an opening symbol (/*), and a closing symbol (*/). Everything contained within the opening and closing symbol is considered, by the compiler, to be "commented out" (that is, ignored by the compiler). Whatever is placed in between the opening and closing symbols is entirely up to the programmer.
The C style of commenting can be used in one of two ways: as a comment limited to a single line, or as a comment spanning several lines. Both methods are displayed below:
/* This is a comment limited to a single line. */ /* This is a comment spanning several lines. */
The commenting technique used, of the two shown above (single-line and several-line), is ultimately up to the needs of the programmer. As it would happen, several-line comments are a 'strength' of the C commenting style. For example, an entire paragraph of text spanning several lines can be commented out through the use of just one opening symbol (/*) and one closing symbol (*/) (much like the several-line comment shown above). A potential "side-effect" can occur, however, when the C style of commenting is used to comment out a single line of text (which at times may appear to the right of a line of program code): if no closing symbol (*/) is placed at the end of a single-line comment, all program code below the opening symbol will be unintentionally considered part of the comment. The programmer, therefore, when using the C style of commenting, should always be sure to include a closing symbol at the end of a single-line comment. In this sense, the need to place a closing symbol at the end of a single-line comment when using the C style of commenting makes writing single-line comments awkward and is a 'weakness' of the C style of commenting. Having discussed the C style of commenting, what, then, is the C++ style of commenting?
All C++ comments are confined to a single line. Unlike the C style of commenting which consists of an "opening" (/*) and a "closing" (*/) symbol, only one symbol (//) is involved in C++ commenting. When the compiler comes across the C++ commenting symbol (//) (which at times may appear to the right of a line of program code), everything to the right of the symbol - and all of the way to the end of the line the symbol is found on - is commented out. Is this superior to the C style of commenting using /* and */? Well, first of all, because the C++ style of commenting has no 'closing' symbol, there is no symbol that could be left out at the end of a single-line comment that would accidentally comment out any program code below the // symbol. The fact that the C++ style of writing single-line comments requires no closing symbol makes single-line comments easier and is a 'strength' of the C++ style of commenting.
Another 'strength' of the C++ style of commenting is that a comment consisting of several lines of text can be placed to the right of a collection of program code by simply following the // symbol to the right of a line of program code with the line of text that is part of the several-line comment. To place a several-line comment to the right of a collection of program code using a single opening (/*) and closing (*/) symbol would be impossible. To place a several-line comment to the right of a collection of program code using the C style of commenting, then, the programmer would not use a single /* and */. Rather, each line of a several-line comment to the right of a collection of program code would have to be a single-line comment that begins and ends with an opening and a closing symbol. As stated, the need to place a closing symbol at the end of a single-line comment when using the C style of commenting (in this case a single-line comment that is part of a several-line comment to the right of a collection of program code) makes things awkward and could be seen as being a 'weakness' of the C style of commenting.
This brings us to a 'weakness' of the C++ style of commenting. As stated, comments in C++ are confined to a single line. Let us assume, then, that the programmer wanted to comment out a large paragraph of text, spanning several lines, at the beginning of a program, describing the purpose of the program. Using the C++ style of commenting, each and every line of text would have to begin with a //. Using the C style of commenting, on the other hand, the entire paragraph of text could be commented out using but a single /* and */. In this particular situation, the C style of commenting could be seen as preferable, and superior, to the C++ style of commenting.
The two methods of using the C++ style of commenting (single-line and several-line) are displayed below:
// This is a comment limited to a single line. // This is a comment // spanning several // lines.
to previous section |
to table of contents |
to next section |
Comments, questions, feedback: jsfsite@yahoo.com |