C++ Programming - Chapter 3
Object Oriented Programming
[edit | edit source]Structures
[edit | edit source]A structure is a compound data type that contains different members of different types. The members are accessed by their names. A value of a structure-object is a tuple of values of each member of the object. A structure can also be seen as a simple implementation of the object paradigm from (OOP). A struct is like a class except for the default access (class has default access of private, struct has default access of public). C++ also guarantees that a struct that only contains C types is equivalent to the same C struct thus allowing access to legacy C functions, it can (but may not) also have constructors (and must have them, if a templated class is used inside a struct), as with Classes the compiler implicitly-declares a destructor if the struct doesn’t have a user-declared destructor. Structures will also allow Operator Overloading.
A struct is defined by:
struct myStructType /*: inheritances */ {
public:
// declare public members here
protected:
// declare protected members here
private:
// declare private members here
};
The optional keywords public:, protected:, private: declare the protection status of the following members. They can be put in any order, and more than one of each may occur. If no protection status is given, then the members are public. (Private or protected members can be accessed only by methods or friends of the structure; explained in a later chapter).
Because it is not supported in C, it is uncommon to have structs in C++ using inheritances even though they are supported just like in classes. The more distinctive aspect is that structs can have two identities one is in reference to the type and another to the specific object. The public access label can sometimes be ignored since the default state of struct for member functions and fields is public.
Objects of type myStructType are declared using:
/* struct */ myStructType obj1 /* , obj2, ... */;
Repeating the keyword struct at the beginning is optional.
It is possible to define objects directly in the struct definition instead of using a name for the struct-type:
struct { /*members*/ } obj1 /*, obj2, .. */ ;
- Why should you Use Structs, Not Classes?
Older programmer languages used a similar type called Record (i.e.: COBOL, FORTRAN) this was implemented in C as the struct keyword. And so C++ uses structs to comply with this C's heritage (the code and the programmers). Structs are simpler to be managed by the programmer and the compiler. One should use a struct for POD (PlainOldData) types that have no methods and whose data members are all public. struct may be used more efficiently in situations that default to public inheritance (which is the most common kind) and where public access (which is what you want if you list the public interface first) is the intended effect. Using a class, you typically have to insert the keyword public in two places, for no real advantage. In the end it's just a matter of convention, which programmers should be able to get used to.
- Point objects
As a simple example of a compound structure, consider the concept of a mathematical point. At one level, a point is two numbers (coordinates) that we treat collectively as a single object. In mathematical notation, points are often written in parentheses, with a comma separating the coordinates. For example, (0, 0) indicates the origin, and (x, y) indicates the point x units to the right and y units up from the origin.
The natural way to represent a point is using two doubles. The structure or struct is one of the solutions to group these two values into a compound object.
// A struct definition:
struct Point { double x, y; };
This definition indicates that this structure contains two members, named x and y. These members are also called instance variables, for reasons I will explain a little later.
It is a common error to leave off the semi-colon at the end of a structure definition. It might seem odd to put a semi-colon after a squiggly-brace, but you'll get used to it. This syntax is in place to allow the programmer the facility to create an instance[s] of the struct when it is defined.
Once you have defined the new structure, you can create variables with that type:
struct Point blank;
blank.x = 3.0;
blank.y = 4.0;
The first line is a conventional variable declaration: blank has type Point. The next two lines initialize the instance variables of the structure. The "dot notation" used here is similar to the syntax for invoking a function on an object, as in fruit.length(). Of course, one difference is that function names are always followed by an argument list, even if it is empty.
As usual, the name of the variable blank appears outside the box and its value appears inside the box. In this case, that value is a compound object with two named instance variables.
- Accessing instance variables
You can read the values of an instance variable using the same syntax we used to write them:
double x = blank.x;
The expression blank.x means "go to the object named blank and get the value of the member named x." In this case we assign that value to a local variable named x. Notice that there is no conflict between the local variable named x and the instance variable named x. The purpose of dot notation is to identify which variable you are referring to unambiguously.
You can use dot notation as part of any expression, so the following are legal.
cout << blank.x << ", " << blank.y << endl;
double distance = sqrt(blank.x * blank.x + blank.y * blank.y);
The first line outputs 3, 4; the second line calculates the value 5.
- Operations on structures
Most of the operators we have been using on other types, like mathematical operators ( +, %, etc.) and comparison operators (==, >, etc.), do not work on structures. Actually, it is possible to define the meaning of these operators for the new type, but we won't do that in this book.
On the other hand, the assignment operator does work for structures. It can be used in two ways: to initialize the instance variables of a structure or to copy the instance variables from one structure to another. An initialization looks like this:
Point blank = { 3.0, 4.0 };
The values in curly brackets get assigned to the instance variables of the structure one by one, in order. So in this case, x gets the first value and y gets the second.
Unfortunately, this syntax can be used only in an initialization, not in an assignment statement. Therefore, the following is illegal.
Point blank;
blank = { 3.0, 4.0 }; // WRONG !!
You might wonder why this perfectly reasonable statement should be illegal, and there is no good answer. (Note, however, that a similar syntax is legal in C since 1999, and is under consideration for possible inclusion in C++ in the future.)
On the other hand, it is legal to assign one structure to another. For example:
Point p1 = { 3.0, 4.0 };
Point p2 = p1;
cout << p2.x << ", " << p2.y << endl;
The output of this program is 3, 4
.
- Structures as function arguments and return types
You can write functions that take or return structures. For example, findCenter takes a Rectangle as an argument and returns a Point that contains the coordinates of the center of the Rectangle:
struct Rectangle {
Point corner;
double width, height;
};
Point findCenter (const Rectangle& box)
{
double x = box.corner.x + box.width/2;
double y = box.corner.y + box.height/2;
Point result = {x, y};
return result;
}
To call this function, we have to pass a Rectangle as an argument, and assign the return value to a Point variable:
Rectangle mybox = { {10.0, 0.0}, 100, 200 };
Point center = findCenter (mybox);
printPoint (center);
The output of this program is (60, 100).
Notice that the Rectangle is being passed to function findCenter by a reference (explained in chapter Functions), because this is more efficient than copying the whole structure what would be done on passing by value. The reference is declared constant, meaning that function findCenter will not modify the argument box, especially that mybox of the caller will remain unchanged.
- Pointers and structures
Structures can also be pointed by pointers and store pointers. The rules are the same as for any fundamental data type. The pointer must be declared as a pointer to the structure.
Nesting structures
[edit | edit source]Structures can also be nested so that a valid element of a structure can also be another structure.
//of course you have to define the Point struct first!
struct Rectangle {
Point upper_left;
Point upper_right;
Point lower_left;
Point lower_right;
};
this
[edit | edit source]The this keyword is an implicitly created pointer that is only accessible within nonstatic member functions of a struct (or a union or class) and points to the object for which the member function is called. This pointer is not available in static member functions. This will be restated again on when introducing unions a more in depth analysis is provided in the Section about classes.
union
[edit | edit source]The union keyword is used to define a union type.
- Syntax
union union-name
{
public-members-list;
private:
private-members-list;
} object-list;
Union is similar to struct
(more than class
), unions differ in the aspect that the fields of a union
share the same position in memory and are by default public
rather than private
. The size of the union
is the size of its largest field (or larger if alignment so requires, for example on a SPARC machine a union
contains a double
and a char [17]
so its size is likely to be 24 because it needs 64-bit alignment). Unions cannot have a destructor
.
What is the point of this? Unions provide multiple ways of viewing the same memory location, allowing for more efficient use of memory. Most of the uses of unions are covered by object-oriented features of C++, so it is more common in C. However, sometimes it is convenient to avoid the formalities of object-oriented programming when performance is important or when one knows that the item in question will not be extended.
union Data {
int i;
char c;
};
Writing to Different Bytes
[edit | edit source]Unions are very useful for low-level programming tasks that involve writing to the same memory area but at different portions of the allocated memory space, for instance:
union item
{
// The item is 16-bits
short theItem;
// In little-endian lo accesses the low 8-bits -
// hi, the upper 8-bits
struct { char lo; char hi; } portions;
};
item tItem;
tItem.theItem = 0xBEAD;
tItem.portions.lo = 0xEF; // The item now equals 0xBEEF
Using this union we can modify the low-order or high-order bytes of theItem without disturbing any other bytes.
Example in Practice: SDL Events
[edit | edit source]One real-life example of unions is the event system of SDL, a graphics library in C. In graphical programming, an event is an action triggered by the user, such as a mouse move or keyboard press. One of the SDL's responsibilities is to handle events and provide a mechanism for the programmer to listen for and react to them.
// primary event structure in SDL
typedef union
{
Uint8 type;
SDL_ActiveEvent active;
SDL_KeyboardEvent key;
SDL_MouseMotionEvent motion;
SDL_MouseButtonEvent button;
SDL_JoyAxisEvent jaxis;
SDL_JoyBallEvent jball;
SDL_JoyHatEvent jhat;
SDL_JoyButtonEvent jbutton;
SDL_ResizeEvent resize;
SDL_ExposeEvent expose;
SDL_QuitEvent quit;
SDL_UserEvent user;
SDL_SysWMEvent syswm;
} SDL_Event;
Each of the types other than Uint8 (an 8-bit unsigned
integer) is a struct with details for that particular event.
// SDL_MouseButtonEvent
typedef struct
{
Uint8 type;
Uint8 button;
Uint8 state;
Uint16 x, y;
} SDL_MouseButtonEvent;
When the programmer receives an event from SDL, he first checks the type value. This tells him what kind of an event it is. Based on this value, he either ignores the event or gets more information by getting the appropriate part of the union.
For example, if the programmer received an event in SDL_Event ev, he could react to mouse clicks with the following code.
if (ev.type == SDL_MOUSEBUTTONUP && ev.button.button == SDL_BUTTON_RIGHT)
{
cout << "You have right-clicked at coordinates (" << ev.button.x << ", "
<< ev.button.y << ")." << endl;
}
While identical functionality can be provided with a struct rather than a union, the union is far more space efficient; the struct would use memory for each of the different event types, whereas the union only uses memory for one. As only one entry has meaning per instance, it is reasonable to use a union in this case.
This scheme could also be constructed with polymorphism and inheritance features of object-oriented C++, however the setup would be involved and less efficient than this one. Use of unions loses type safety, however it gains in performance.
this
[edit | edit source]The this keyword is a implicitly created pointer that is only accessible within nonstatic member functions of a union (or a struct or class ) and points to the object for which the member function is called. The this pointer is not available in static member functions. This will be restated again on when introducing unions a more in depth analysis is provided in the Section about classes.
Classes
[edit | edit source]Classes are used to create user defined types. An instance of a class is called an object and programs can contain any number of classes. As with other types, object types are case-sensitive.
Classes provide encapsulation as defined in the Object Oriented Programming (OOP) paradigm. A class can have both data members and functions members associated with it. Unlike the built-in types, the class can contain several variables and functions, those are called members.
Classes also provide flexibility in the "divide and conquer" scheme in program writing. In other words, one programmer can write a class and guarantee an interface. Another programmer can write the main program with that expected interface. The two pieces are put together and compiled for usage.
Declaration
[edit | edit source]A class is defined by:
class MyClass
{
/* public, protected and private
variables, constants, and functions */
};
An object of type MyClass (case-sensitive) is declared using:
MyClass object;
- by default, all class members are initially private.
- keywords public and protected allow access to class members.
- classes contain not only data members, but also functions to manipulate that data.
- a class is used as the basic building block of OOP (this is a distinction of convention, not of language-enforced semantics).
- A class can be created
- before main() is called.
- when a function is called in which the object is declared.
- when the "new" operator is used.
- Class Names
- Name the class after what it is. If you can't determine a name, then you have not designed the system well enough.
- Compound names of over three words are a clue your design may be confusing various entities in your system. Revisit your design. Try a CRC card session to see if your objects have more responsibilities than they should.
- Avoid the temptation of naming a class something similar to the class it is derived from. A class should stand on its own. Declaring an object with a class type doesn't depend on where that class is derived from.
- Suffixes or prefixes are sometimes helpful. For example, if your system uses agents then naming something DownloadAgent conveys real information.
- Data Abstraction
A fundamental concept of Object Oriented (OO) recommends an object should not expose any of its implementation details. This way, you can change the implementation without changing the code that uses the object. The class, by design, allows its programmer to hide (and also prevents changes as to) how the class is implemented. This powerful tool allows the programmer to build in a 'preventive' measure. Variables within the class often have a very significant role in what the class does, therefore variables can be secured within the private section of the class.
Access labels
[edit | edit source]The access labels Public, Protected and Private are used within classes to set access permissions for the members in that section of the class. All class members are initially private by default. The labels can be in any order. These labels can be used multiple times in a class declaration for cases where it is logical to have multiple groups of these types. An access label will remain active until another access label is used to change the permissions.
We have already mentioned that a class can have member functions "inside" it; we will see more about them later. Those member functions can access and modify all the data and member function that are inside the class. Therefore, permission labels are to restrict access to member function that reside outside the class and for other classes.
For example, a class "Bottle" could have a private variable fill, indicating a liquid level 0-3 dl. fill cannot be modified directly (compiler error), but instead Bottle provides the member function sip() to reduce the liquid level by 1. Mywaterbottle could be an instance of that class, an object.
/* Bottle - Class and Object Example */
#include <iostream>
#include <iomanip>
using namespace std;
class Bottle
{
private: // variables are modified by member functions of class
int iFill; // dl of liquid
public:
Bottle() // Default Constructor
: iFill(3) // They start with 3 dl of liquid
{
// More constructor code would go here if needed.
}
bool sip() // return true if liquid was available
{
if (iFill > 0)
{
--iFill;
return true;
}
else
{
return false;
}
}
int level() const // return level of liquid dl
{
return iFill;
}
}; // Class declaration has a trailing semicolon
int main()
{
// terosbottle object is an instance of class Bottle
Bottle terosbottle;
cout << "In the beginning, mybottle has "
<< terosbottle.level()
<< " dl of liquid"
<< endl;
while (terosbottle.sip())
{
cout << "Mybottle has "
<< terosbottle.level()
<< " dl of liquid"
<< endl;
}
return 0;
}
These keywords, private, public, and protected, affect the permissions of the members—whether functions or variables.
public
[edit | edit source]This label indicates any members within the 'public' section can be accessed freely anywhere a declared object is in scope.
private
[edit | edit source]Members defined as private are only accessible within the class defining them, or friend classes. Usually the domain of member variables and helper functions. It's often useful to begin putting functions here and then moving them to the higher access levels as needed so to reduce complexity.
(This is an example where the default copy constructor will do the same thing.)
class Foo
{
public:
Foo(const Foo &f)
{
m_iValue = f.m_iValue; // perfectly legal
}
private:
int m_iValue;
};
protected
[edit | edit source]The protected label has a special meaning to inheritance, protected members are accessible in the class that defines them and in classes that inherit from that base class, or friends of it. In the section on inheritance we will see more about it.
Inheritance (Derivation)
[edit | edit source]As seen early when introducing the programming paradigms, inheritance is a property that describes a relationship between two (or more) types or classes, of objects. It is a characteristic of OOP, and in C++, classes share this property.
Derivation is the action of creating a new class using the inheritance property. It is possible to derive one class from another or even several (Multiple inheritance), like a tree we can call base class to the root and child class to any leaf; in any other case the parent/child relation will exist for each class derived from another.
- Base Class
A base class is a class that is created with the intention of deriving other classes from it.
- Child Class
A child class is a class that was derived from another, that will now be the parent class to it.
- Parent Class
A parent class is the closest class that we derived from to create the one we are referencing as the child class.
As an example, suppose you are creating a game, something using different cars, and you need specific type of car for the policemen and another type for the player(s). Both car types share similar properties. The major difference (on this example case) would be that the policemen type would have sirens on top of their cars and the players' cars will not.
One way of getting the cars for the policemen and the player ready is to create separate classes for policemen's car and for the player's car like this:
class PlayerCar {
private:
int color;
public:
void driveAtFullSpeed(int mph){
// code for moving the car ahead
}
};
class PoliceCar {
private:
int color;
bool sirenOn; // identifies whether the siren is on or not
bool inAction; // identifies whether the police is in action (following the player) or not
public:
bool isInAction(){
return this->inAction;
}
void driveAtFullSpeed(int mph){
// code for moving the car ahead
}
};
and then creating separate objects for the two cars like this:
PlayerCar player1;
PoliceCar policemen1;
So, except for one thing that you can easily notice: there are certain parts of code that are very similar (if not exactly the same) in the above two classes. In essence, you have to type in the same code at two different locations! And when you update your code to include methods (functions) for handBrake()
and pressHorn()
, you'll have to do that in both the classes above.
Therefore, to escape this frustrating (and confusing) task of writing the same code at multiple locations in a single project, you use Inheritance.
Now that you know what kind of problems Inheritance solves in C++, let us examine how to implement Inheritance in our programs. As its name suggests, Inheritance lets us create new classes which automatically have all the code from existing classes. It means that if there is a class called MyClass
, a new class with the name MyNewClass
can be created which will have all the code present inside the MyClass
class. The following code segment shows it all:
class MyClass {
protected:
int age;
public:
void sayAge(){
this->age = 20;
cout << age;
}
};
class MyNewClass : public MyClass {
};
int main() {
MyNewClass *a = new MyNewClass();
a->sayAge();
return 0;
}
As you can see, using the colon ':' we can inherit a new class out of an existing one. It’s that simple! All the code inside the MyClass
class is now available to the MyNewClass
class. And if you are intelligent enough, you can already see the advantages it provides. If you are like me (i.e. not too intelligent), you can see the following code segment to know what I mean:
class Car {
protected:
int color;
int currentSpeed;
int maxSpeed;
public:
void applyHandBrake(){
this->currentSpeed = 0;
}
void pressHorn(){
cout << "Teeeeeeeeeeeeent"; // funny noise for a horn
}
void driveAtFullSpeed(int mph){
// code for moving the car ahead;
}
};
class PlayerCar : public Car {
};
class PoliceCar : public Car {
private:
bool sirenOn; // identifies whether the siren is on or not
bool inAction; // identifies whether the police is in action (following the player) or not
public:
bool isInAction(){
return this->inAction;
}
};
In the code above, the two newly created classes PlayerCar and PoliceCar have been inherited from the Car class. Therefore, all the methods and properties (variables) from the Car class are available to the newly created classes for the player's car and the policemen's car. Technically speaking, in C++, the Car class in this case is our "Base Class" since this is the class which the other two classes are based on (or inherit from).
Just one more thing to note here is the keyword protected instead of the usual private keyword. That’s no big deal: We use protected when we want to make sure that the variables we define in our base class should be available in the classes that inherit from that base class. If you use private in the class definition of the Car class, you will not be able to inherit those variables inside your inherited classes.
There are three types of class inheritance: public, private and protected. We use the keyword public to implement public inheritance. The classes who inherit with the keyword public from a base class, inherit all the public members as public members, the protected data is inherited as protected data and the private data is inherited but it cannot be accessed directly by the class.
The following example shows the class Circle that inherits "publicly" from the base class Form:
class Form {
private:
double area;
public:
int color;
double getArea(){
return this->area;
}
void setArea(double area){
this->area = area;
}
};
class Circle : public Form {
public:
double getRatio() {
double a;
a = getArea();
return sqrt(a / 2 * 3.14);
}
void setRatio(double diameter) {
setArea( pow(diameter * 0.5, 2) * 3.14 );
}
bool isDark() {
return (color > 10);
}
};
The new class Circle inherits the attribute area from the base class Form (the attribute area is implicitly an attribute of the class Circle), but it cannot access it directly. It does so through the functions getArea and setArea (that are public in the base class and remain public in the derived class). The color attribute, however, is inherited as a public attribute, and the class can access it directly.
The following table indicates how the attributes are inherited in the three different types of inheritance:
private | protected | public | |
private inheritance | The member is inaccessible. | The member is private. | The member is private. |
protected inheritance | The member is inaccessible. | The member is protected. | The member is protected. |
public inheritance | The member is inaccessible. | The member is protected. | The member is public. |
As the table above shows, protected members are inherited as protected methods in public inheritance. Therefore, we should use the protected label whenever we want to declare a method inaccessible outside the class and not to lose access to it in derived classes. However, losing accessibility can be useful sometimes, because we are encapsulating details in the base class.
Let us imagine that we have a class with a very complex method "m" that invokes many auxiliary methods declared as private in the class. If we derive a class from it, we should not bother about those methods because they are inaccessible in the derived class. If a different programmer is in charge of the design of the derived class, allowing access to those methods could be the cause of errors and confusion. So, it is a good idea to avoid the protected label whenever we can have a design with the same result with the private label.
Now one more additional "syntax trick". If the base / parent class has a constructor which requires parameters, we are in trouble, you may think. Of course calling constructors directly is forbidden, but we have a special syntax for this purpose. The way, is just so that when you define the constructor of the delivered class, you call the parent constructor like this:
ChildClass::ChildClass(int a, int b) : ParentClass(a, b)
{
//Child constructor here
}
Multiple inheritance
[edit | edit source]Multiple inheritance allows the construction of classes that inherit from more than one type or class. This contrasts with single inheritance, where a class will only inherit from one type or class.
Multiple inheritance can cause some confusing situations, and is much more complex than single inheritance, so there is some debate over whether or not its benefits outweigh its risks. Multiple inheritance has been a touchy issue for many years, with opponents pointing to its increased complexity and ambiguity in situations such as the "diamond problem". Most modern OOP languages do not allow multiple inheritance.
The declared order of derivation is relevant for determining the order of default initialization by constructors and destructors cleanup.
class One
{
// class internals
};
class Two
{
// class internals
};
class MultipleInheritance : public One, public Two
{
// class internals
};
Data members
[edit | edit source]Data members are declared in the same way as a global or function variable, but as part of the class definition. Their purpose is to store information for that class and may include members of any type, even other user-defined types. They are usually hidden from outside use, depending on the coding style adopted, external use is normally done through special member functions.
this pointer
[edit | edit source]The this keyword acts as a pointer to the class being referenced. The this pointer acts like any other pointer, although you can't change the pointer itself. Read the section concerning pointers and references to understand more about general pointers.
The this pointer is only accessible within nonstatic member functions of a class, union or struct, and is not available in static member functions. It is not necessary to write code for the this pointer as the compiler does this implicitly. When using a debugger, you can see the this pointer in some variable list when the program steps into nonstatic class functions.
In the following example, the compiler inserts an implicit parameter this in the nonstatic member function int getData(). Additionally, the code initiating the call passes an implicit parameter (provided by the compiler).
class Foo
{
private:
int iX;
public:
Foo(){ iX = 5; };
int getData()
{
return this->iX; // this is provided by the compiler at compile time
}
};
int main()
{
Foo Example;
int iTemp;
iTemp = Example.getData(&Example); // compiler adds the &Example reference at compile time
return 0;
}
There are certain times when a programmer should know about and use the this pointer. The this pointer should be used when overloading the assignment operator to prevent a catastrophe. For example, add in an assignment operator to the code above.
class Foo
{
private:
int iX;
public:
Foo() { iX = 5; };
int getData()
{
return iX;
}
Foo& operator=(const Foo &RHS);
};
Foo& Foo::operator=(const Foo &RHS)
{
if(this != &RHS)
{ // the if this test prevents an object from copying to itself (ie. RHS = RHS;)
this->iX = RHS.iX; // this is suitable for this class, but can be more complex when
// copying an object in a different much larger class
}
return (*this); // returning an object allows chaining, like a = b = c; statements
}
However little you may know about this, it is important in implementing any class.
static data member
[edit | edit source]The use of the static
specifier in a data member, will cause that member to be shared by all instances of the owner class and derived classes. To use static data members you must declare the data member as static and initialize it outside of the class declaration, at file scope.
When used in a class data member, all instantiations of that class share one copy of the variable.
class Foo {
public:
Foo() {
++iNumFoos;
cout << "We have now created " << iNumFoos << " instances of the Foo class\n";
}
private:
static int iNumFoos;
};
int Foo::iNumFoos = 0; // allocate memory for numFoos, and initialize it
int main() {
Foo f1;
Foo f2;
Foo f3;
}
In the example above, the static class variable numFoos is shared between all three instances of the Foo class (f1, f2 and f3) and keeps a count of the number of times that the Foo class has been instantiated.
Member Functions
[edit | edit source]Member functions can (and should) be used to interact with data contained within user defined types. User defined types provide flexibility in the "divide and conquer" scheme in program writing. In other words, one programmer can write a user defined type and guarantee an interface. Another programmer can write the main program with that expected interface. The two pieces are put together and compiled for usage. User defined types provide encapsulation defined in the Object Oriented Programming (OOP) paradigm.
Within classes, to protect the data members, the programmer can define functions to perform the operations on those data members. Member functions and functions are names used interchangeably in reference to classes. Function prototypes are declared within the class definition. These prototypes can take the form of non-class functions as well as class suitable prototypes. Functions can be declared and defined within the class definition. However, most functions can have very large definitions and make the class very unreadable. Therefore it is possible to define the function outside of the class definition using the scope resolution operator "::". This scope resolution operator allows a programmer to define the functions somewhere else. This can allow the programmer to provide a header file .h defining the class and a .obj file built from the compiled .cpp file which contains the function definitions. This can hide the implementation and prevent tampering. The user would have to define every function again to change the implementation. Functions within classes can access and modify (unless the function is constant) data members without declaring them, because the data members are already declared in the class.
Simple example:
file: Foo.h
// the header file named the same as the class helps locate classes within a project
// one class per header file makes it easier to keep the
// header file readable (some classes can become large)
// each programmer should determine what style works for them or what programming standards their
// teacher/professor/employer has
#ifndef FOO_H
#define FOO_H
class Foo{
public:
Foo(); // function called the default constructor
Foo( int a, int b ); // function called the overloaded constructor
int Manipulate( int g, int h );
private:
int x;
int y;
};
#endif
file: Foo.cpp
#include "Foo.h"
/* these constructors should really show use of initialization lists
Foo::Foo() : x(5), y(10)
{
}
Foo::Foo(int a, int b) : x(a), y(b)
{
}
*/
Foo::Foo(){
x = 5;
y = 10;
}
Foo::Foo( int a, int b ){
x = a;
y = b;
}
int Foo::Manipulate( int g, int h ){
x = h + g*x;
y = g + h*y;
}
Overloading
[edit | edit source]Member functions can be overloaded. This means that multiple member functions can exist with the same name on the same scope, but must have different signatures. A member function's signature is comprised of the member function's name and the type and order of the member function's parameters.
Due to name hiding, if a member in the derived class shares the same name with members of the base class, they will be hidden to the compiler. To make those members visible, one can use declarations to introduce them from base class scopes.
Constructors and other class member functions, except the Destructor, can be overloaded.
Constructors
[edit | edit source]A constructor is a special member function that is called whenever a new instance of a class is created. The compiler calls the constructor after the new object has been allocated in memory, and converts that "raw" memory into a proper, typed object. The constructor is declared much like a normal member function but it will share the name of the class and it has no return value.
Constructors are responsible for almost all of the run-time setup necessary for the class operation. Its main purpose becomes in general defining the data members upon object instantiation (when an object is declared), they can also have arguments, if the programmer so chooses. If a constructor has arguments, then they should also be added to the declaration of any other object of that class when using the new operator. Constructors can also be overloaded.
Foo myTest; // essentially what happens is: Foo myTest = Foo();
Foo myTest( 3, 54 ); // accessing the overloaded constructor
Foo myTest = Foo( 20, 45 ); // although a new object is created, there are some extra function calls involved
// with more complex classes, an assignment operator should
// be defined to ensure a proper copy (includes ''deep copy'')
// myTest would be constructed with the default constructor, and then the
// assignment operator copies the unnamed Foo( 20, 45 ) object to myTest
using new with a constructor
Foo* myTest = new Foo(); // this defines a pointer to a dynamically allocated object
Foo* myTest = new Foo( 40, 34 ); // constructed with Foo( 40, 34 )
// be sure to use delete to avoid memory leaks
A constructor can delegate to another (introduced in C++ 11). It is also considered desirable to reduce the use of default arguments, if a maintainer has to write and maintain multiple constructors it can result in code duplication, which reduces maintainability because of the potential for introducing inconsistencies and even lead to code bloat.
- Default Constructors
A default constructor is one which can be called with no arguments. Most commonly, a default constructor is declared without any parameters, but it is also possible for a constructor with parameters to be a default constructor if all of those parameters are given default values.
In order to create an array of objects of a class type, the class must have an accessible default constructor; C++ has no syntax to specify constructor arguments for array elements.
Overloaded Constructors
[edit | edit source]When an object of a class is instantiated, the class writer can provide various constructors each with a different purpose. A large class would have many data members, some of which may or may not be defined when an object is instantiated. Anyway, each project will vary, so a programmer should investigate various possibilities when providing constructors.
These are all constructors for a class myFoo.
myFoo(); // default constructor, the user has no control over initial values
// overloaded constructors
myFoo( int a, int b=0 ); // allows construction with a certain 'a' value, but accepts 'b' as 0
// or allows the user to provide both 'a' and 'b' values
// or
myFoo( int a, int b ); // overloaded constructor, the user must specify both values
class myFoo {
private:
int Useful1;
int Useful2;
public:
myFoo(){ // default constructor
Useful1 = 5;
Useful2 = 10;
};
myFoo( int a, int b = 0 ) { // two possible cases when invoked
Useful1 = a;
Useful2 = b;
};
};
myFoo Find; // default constructor, private member values Useful1 = 5, Useful2 = 10
myFoo Find( 8 ); // overloaded constructor case 1, private member values Useful1 = 8, Useful2 = 0
myFoo Find( 8, 256 ); // overloaded constructor case 2, private member values Useful1 = 8, Useful2 = 256
Constructor initialization lists
[edit | edit source]Constructor initialization lists (or member initialization list) are the only way to initialize data members and base classes with a non-default constructor. Constructors for the members are included between the argument list and the body of the constructor (separated from the argument list by a colon). Using the initialization lists is not only better in terms of efficiency but also the simplest way to guarantee that all initialization of data members are done before entering the body of constructors.
// Using the initialization list for myComplexMember_
MyClass::MyClass(int mySimpleMember, MyComplexClass myComplexMember)
: myComplexMember_(myComplexMember) // only 1 call, to the copy constructor
{
mySimpleMember_=mySimpleMember; // uses 2 calls, one for the constructor of the mySimpleMember class
// and a second for the assignment operator of the MyComplexClass class
}
This is more efficient than assigning value to the complex data member inside the body of the constructor because in that case the variable is initialized with its corresponding constructor.
Note that the arguments provided to the constructors of the members do not need to be arguments to the constructor of the class; they can also be constants. Therefore you can create a default constructor for a class containing a member with no default constructor.
Example:
MyClass::MyClass() : myComplexMember_(0) { }
It is useful to initialize your members in the constructor using this initialization lists. This makes it obvious for the reader that the constructor does not execute logic. The order the initialization is done should be the same as you defined your base-classes and members. Otherwise you can get warnings at compile-time. Once you start initializing your members make sure to keep all in the constructor(s) to avoid confusion and possible 0xbaadfood.
It is safe to use constructor parameters that are named like members.
Example:
class MyClass : public MyBaseClassA, public MyBaseClassB {
private:
int c;
void *pointerMember;
public:
MyClass(int,int,int);
};
/*...*/
MyClass::MyClass(int a, int b, int c):
MyBaseClassA(a)
,MyBaseClassB(b)
,c(c)
,pointerMember(NULL)
,referenceMember()
{
//logic
}
Note that this technique was also possible for normal functions but it is now obsoleted and is classified as an error in such case.
Destructors
[edit | edit source]Destructors like the Constructors are declared as any normal member functions but will share the same name as the Class, what distinguishes them is that the Destructor's name is preceded with a "~", it can not have arguments and can't be overloaded.
Destructors are called whenever an Object of the Class is destroyed. Destructors are crucial in avoiding resource leaks (by deallocating memory), and in implementing the RAII idiom. Resources which are allocated in a Constructor of a Class are usually released in the Destructor of that Class as to return the system to some known or stable state after the Class ceases to exist.
The Destructor is invoked when Objects are destroyed, after the function they were declared in returns, when the delete operator is used or when the program is over. If an object of a derived type is destructed, first the Destructor of the most derived object is executed. Then member objects and base class subjects are destructed recursively, in the reverse order their corresponding Constructors completed. As with structs the compiler implicitly declares a Destructor as an inline public member of its class if the class doesn’t have a user-declared Destructor.
The dynamic type of the object will change from the most derived type as Destructors run, symmetrically to how it changes as Constructors execute. This affects the functions called by virtual calls during construction and destruction, and leads to the common (and reasonable) advice to avoid calling virtual functions of an object either directly or indirectly from its Constructors or Destructors.
Sharing most of the concepts we have seen before on the introduction to inline functions, when dealing with member function those concepts are extended, with a few additional considerations.
If the member functions definition is included inside the declaration of the class, that function is by default made implicitly inline. Compiler options may override this behavior.
Calls to virtual functions cannot be inlined if the object's type is not known at compile-time, because we don't know which function to inline.
The static keyword can be used in four different ways:
- to create permanent storage for local variables in a function.
- to specify internal linkage.
- to declare member functions that act like non-member functions.
- to create a single copy of a data member.
static member function
[edit | edit source]Member functions or variables declared static are shared between all instances of an object type. Meaning that only one copy of the member function or variable does exists for any object type.
- member functions callable without an object
When used in a class function member, the function does not take an instantiation as an implicit this
parameter, instead behaving like a free function. This means that static class functions can be called without creating instances of the class:
class Foo {
public:
Foo() {
++numFoos;
cout << "We have now created " << numFoos << " instances of the Foo class\n";
}
static int getNumFoos() {
return numFoos;
}
private:
static int numFoos;
};
int Foo::numFoos = 0; // allocate memory for numFoos, and initialize it
int main() {
Foo f1;
Foo f2;
Foo f3;
cout << "So far, we've made " << Foo::getNumFoos() << " instances of the Foo class\n";
}
Named constructors
[edit | edit source]Named constructors are a good example of using static member functions. Named constructors is the name given to functions used to create an object of a class without (directly) using its constructors. This might be used for the following:
- To circumvent the restriction that constructors can be overloaded only if their signatures differ.
- Making the class non-inheritable by making the constructors private.
- Preventing stack allocation by making constructors private
Declare a static member function that uses a private constructor to create the object and return it. (It could also return a pointer or a reference but this complication seems useless, and turns this into the factory pattern rather than a conventional named constructor.)
Here's an example for a class that stores a temperature that can be specified in any of the different temperature scales.
class Temperature
{
public:
static Temperature Fahrenheit (double f);
static Temperature Celsius (double c);
static Temperature Kelvin (double k);
private:
Temperature (double temp);
double _temp;
};
Temperature::Temperature (double temp):_temp (temp) {}
Temperature Temperature::Fahrenheit (double f)
{
return Temperature ((f + 459.67) / 1.8);
}
Temperature Temperature::Celsius (double c)
{
return Temperature (c + 273.15);
}
Temperature Temperature::Kelvin (double k)
{
return Temperature (k);
}
const
[edit | edit source]This type of member function cannot modify the member variables of a class. It's a hint both to the programmer and the compiler that a given member function doesn't change the internal state of a class; however, any variables declared as mutable can still be modified.
Take for example:
class Foo
{
public:
int value() const
{
return m_value;
}
void setValue( int i )
{
m_value = i;
}
private:
int m_value;
};
Here value() clearly does not change m_value and as such can and should be const. However setValue() does modify m_value and as such cannot be const.
Another subtlety often missed is a const member function cannot call a non-const member function (and the compiler will complain if you try). The const member function cannot change member variables and a non-const member functions can change member variables. Since we assume non-const member functions do change member variables, const member functions are assumed to never change member variables and can't call functions that do change member variables.
The following code example explains what const can do depending on where it is placed.
class Foo
{
public:
/*
* Modifies m_widget and the user
* may modify the returned widget.
*/
Widget *widget();
/*
* Does not modify m_widget but the
* user may modify the returned widget.
*/
Widget *widget() const;
/*
* Modifies m_widget, but the user
* may not modify the returned widget.
*/
const Widget *cWidget();
/*
* Does not modify m_widget and the user
* may not modify the returned widget.
*/
const Widget *cWidget() const;
private:
Widget *m_widget;
};
Accessors and Modifiers (Setter/Getter)
[edit | edit source]- What is an accessor?
- An accessor is a member function that does not modify the state of an object. The accessor functions should be declared as const.
- Getter is another common definition of an accessor due to the naming ( GetSize() ) of that type of member functions.
- What is a modifier?
- A modifier, also called a modifying function, is a member function that changes the value of at least one data member. In other words, an operation that modifies the state of an object. Modifiers are also known as ‘mutators’.
- Setter is another common definition of a modifier due to the naming ( SetSize( int a_Size ) ) of that type of member functions.
Dynamic polymorphism (Overrides)
[edit | edit source]So far, we have learned that we can add new data and functions to a class through inheritance. But what about if we want our derived class to inherit a method from the base class, but to have a different implementation for it? That is when we are talking about polymorphism, a fundamental concept in OOP programming.
As seen previously in the Programming Paradigms Section, Polymorphism is subdivided in two concepts static polymorphism and dynamic polymorphism. This section concentrates on dynamic polymorphism, which applies in C++ when a derived class overrides a function declared in a base class.
We implement this concept redefining the method in the derived class. However, we need to have some considerations when we do this, so now we must introduce the concepts of dynamic binding, static binding and virtual methods.
Suppose that we have two classes, A
and B
. B
derives from A
and redefines the implementation of a method c()
that resides in class A
. Now suppose that we have an object b
of class B
. How should the instruction b.c()
be interpreted?
If b
is declared in the stack (not declared as a pointer or a reference) the compiler applies static binding, this means it interprets (at compile time) that we refer to the implementation of c()
that resides in B
.
However, if we declare b
as a pointer or a reference of class A
, the compiler could not know which method to call at compile time, because b
can be of type A
or B
. If this is resolved at run time, the method that resides in B
will be called. This is called dynamic binding. If this is resolved at compile time, the method that resides in A
will be called. This is again, static binding.
Virtual member functions
[edit | edit source]The virtual member functions is relatively simple, but often misunderstood. The concept is an essential part of designing a class hierarchy in regards to sub-classing classes as it determines the behavior of overridden methods in certain contexts.
Virtual member functions are class member functions, that can be overridden in any class derived from the one where they were declared. The member function body is then replaced with a new set of implementation in the derived class.
By placing the keyword virtual before a method declaration we are indicating that when the compiler has to decide between applying static binding or dynamic binding it will apply dynamic binding. Otherwise, static binding will be applied.
Again, this should be clearer with an example:
class Foo
{
public:
void f()
{
std::cout << "Foo::f()" << std::endl;
}
virtual void g()
{
std::cout << "Foo::g()" << std::endl;
}
};
class Bar : public Foo
{
public:
void f()
{
std::cout << "Bar::f()" << std::endl;
}
virtual void g()
{
std::cout << "Bar::g()" << std::endl;
}
};
int main()
{
Foo foo;
Bar bar;
Foo *baz = &bar;
Bar *quux = &bar;
foo.f(); // "Foo::f()"
foo.g(); // "Foo::g()"
bar.f(); // "Bar::f()"
bar.g(); // "Bar::g()"
// So far everything we would expect...
baz->f(); // "Foo::f()"
baz->g(); // "Bar::g()"
quux->f(); // "Bar::f()"
quux->g(); // "Bar::g()"
return 0;
}
Our first calls to f() and g() on the two objects are straightforward. However things get interesting with our baz pointer which is a pointer to the Foo type.
f() is not virtual and as such a call to f() will always invoke the implementation associated with the pointer type—in this case the implementation from Foo.
Virtual function calls are computationally more expensive than regular function calls. Virtual functions use pointer indirection, invocation and will require a few extra instructions than normal member functions. They also require that the constructor of any class/structure containing virtual functions to initialize a table of pointers to its virtual member functions.
All this characteristics will signify a trade-off between performance and design. One should avoid preemptively declaring functions virtual without an existing structural need. Keep in mind that virtual functions that are only resolved at run-time cannot be inlined.
Pure virtual member function
[edit | edit source]There is one additional interesting possibility. Sometimes we don't want to provide an implementation of our function at all, but want to require people sub-classing our class to provide an implementation on their own. This is the case for pure virtuals.
To indicate a pure virtual function instead of an implementation we simply add an "= 0" after the function declaration.
Again—an example:
class Widget
{
public:
virtual void paint() = 0;
};
class Button : public Widget
{
public:
void paint() // is virtual because it is an override
{
// do some stuff to draw a button
}
};
Because paint() is a pure virtual function in the Widget class we are required to provide an implementation in all concrete subclasses. If we don't the compiler will give us an error at build time.
This is helpful for providing interfaces—things that we expect from all of the objects based on a certain hierarchy, but when we want to ignore the implementation details.
- So why is this useful?
Let's take our example from above where we had a pure virtual for painting. There are a lot of cases where we want to be able to do things with widgets without worrying about what kind of widget it is. Painting is an easy example.
Imagine that we have something in our application that repaints widgets when they become active. It would just work with pointers to widgets—i.e. Widget *activeWidget() const might be a possible function signature. So we might do something like:
Widget *w = window->activeWidget();
w->paint();
We want to actually call the appropriate paint member function for the "real" widget type—not Widget::paint() (which is a "pure" virtual and will cause the program to crash if called using virtual dispatch). By using a virtual function we insure that the member function implementation for our subclass -- Button::paint() in this case—will be called.
Covariant return types
[edit | edit source]Covariant return types is the ability for a virtual function in a derived class to return a pointer or reference to an instance of itself if the version of the method in the base class does so. e.g.
class base
{
public:
virtual base* create() const;
};
class derived : public base
{
public:
virtual derived* create() const;
};
This allows casting to be avoided.
virtual Constructors
[edit | edit source]There is a hierarchy of classes with base class Foo. Given an object bar belonging in the hierarchy, it is desired to be able to do the following:
- Create an object baz of the same class as bar (say, class Bar) initialized using the default constructor of the class. The syntax normally used is:
- Bar* baz = bar.create();
- Create an object baz of the same class as bar which is a copy of bar. The syntax normally used is:
- Bar* baz = bar.clone();
In the class Foo, the methods Foo::create() and Foo::clone() are declared as follows:
class Foo
{
// ...
public:
// Virtual default constructor
virtual Foo* create() const;
// Virtual copy constructor
virtual Foo* clone() const;
};
If Foo is to be used as an abstract class, the functions may be made pure virtual:
class Foo
{
// ...
public:
virtual Foo* create() const = 0;
virtual Foo* clone() const = 0;
};
In order to support the creation of a default-initialized object, and the creation of a copy object, each class Bar in the hierarchy must have public default and copy constructors. The virtual constructors of Bar are defined as follows:
class Bar : ... // Bar is a descendant of Foo
{
// ...
public:
// Non-virtual default constructor
Bar ();
// Non-virtual copy constructor
Bar (const Bar&);
// Virtual default constructor, inline implementation
Bar* create() const { return new Foo (); }
// Virtual copy constructor, inline implementation
Bar* clone() const { return new Foo (*this); }
};
The above code uses covariant return types. If your compiler doesn't support Bar* Bar::create(), use Foo* Bar::create() instead, and similarly for clone().
While using these virtual constructors, you must manually deallocate the object created by calling delete baz;. This hassle could be avoided if a smart pointer (e.g. std::unique_ptr<Foo>) is used in the return type instead of the plain old Foo*.
Remember that whether or not Foo uses dynamically allocated memory, you must define the destructor virtual ~Foo () and make it virtual to take care of deallocation of objects using pointers to an ancestral type.
virtual Destructor
[edit | edit source]It is of special importance to remember to define a virtual destructor even if empty in any base class, since failing to do so will create problems with the default compiler generated destructor that will not be virtual.
A virtual destructor is not overridden when redefined in a derived class, the definitions to each destructor are cumulative and they start from the last derivate class toward the first base class.
Pure virtual Destructor
[edit | edit source]Every abstract class should contain the declaration of a pure virtual destructor.
Pure virtual destructors are a special case of pure virtual functions (meant to be overridden in a derived class). They must always be defined and that definition should always be empty.
class Interface {
public:
virtual ~Interface() = 0; //declaration of a pure virtual destructor
};
Interface::~Interface(){} //pure virtual destructor definition (should always be empty)
Law of three
[edit | edit source]The "law of three" is not really a law, but rather a guideline: if a class needs an explicitly declared copy constructor, copy assignment operator, or destructor, then it usually needs all three.
There are exceptions to this rule (or, to look at it another way, refinements). For example, sometimes a destructor is explicitly declared just in order to make it virtual
; in that case there's not necessarily a need to declare or implement the copy constructor and copy assignment operator.
Most classes should not declare any of the "big three" operations; classes that manage resources generally need all three.
Subsumption property
[edit | edit source]Subsumption is a property that all objects that reside in a class hierarchy must fulfill: an object of the base class can be substituted by an object that derives from it (directly or indirectly). All mammals are animals (they derive from them), and all cats are mammals. Therefore, because of the subsumption property we can "treat" any mammal as an animal and any cat as a mammal. This implies abstraction, because when we are "treating" a mammal as an animal, the only information we should know about it is that it lives, it grows, etc, but nothing related to mammals.
This property is applied in C++, whenever we are using pointers or references to objects that reside in a class hierarchy. In other words, a pointer of class animal can point to an object of class animal, mammal or cat.
Let's continue with our example:
//needs to be corrected
enum AnimalType {
Herbivore,
Carnivore,
Omnivore,
};
class Animal {
public:
AnimalType Type;
bool bIsAlive;
int iNumberOfChildren;
};
class Mammal : public Animal{
public:
int iNumberOfTeats;
};
class Cat : public Mammal{
public:
bool bLikesFish; // probably true
};
int main() {
Animal* pA1 = new Animal;
Animal* pA2 = new Mammal;
Animal* pA3 = new Cat;
Mammal* pM = new Cat;
pA2->bIsAlive = true; // Correct
pA2->Type = Herbivore; // Correct
pM->iNumberOfTeats = 2; // Correct
pA2->iNumberOfTeats = 6; // Incorrect
pA3->bLikesFish = true; // Incorrect
Cat* pC = (Cat*)pA3; // Downcast, correct (but very poor practice, see later)
pC->bLikesFish = false; // Correct (although it is a very awkward cat)
}
In the last lines of the example there is cast of a pointer to Animal, to a pointer to Cat. This is called "Downcast". Downcasts are useful and should be used, but first we must ensure that the object we are casting is really of the type we are casting to it. Downcasting a base class to an unrelated class is an error. To resolve this issue, the casting operators dynamic_cast
<>, or static_cast
<> should be used.
These correctly cast an object from one class to another, and will throw an exception if the class types are not related. eg. If you try:
Cat* pC = new Cat;
motorbike* pM = dynamic_cast<motorbike*>(pC);
Then, the app will throw an exception, as a cat is not a motorbike. Static_cast is very similar, only it will perform the type checking at compile time. If you have an object where you are not sure of its type then you should use dynamic_cast
, and be prepared to handle errors when casting. If you are downcasting objects where you know the types, then you should use static_cast
. Do not use old-style C casts as these will simply give you an access violation if the types cast are unrelated.
Local classes
[edit | edit source]A local class is any class that is defined inside a specific statement block, in a local scope, for instance inside a function. This is done like defining any other class, but local classes can not however access non-static local variables or be used to define static data members. These type of classes are useful especially in template functions, as we will see later.
void MyFunction()
{
class LocalClass
{
// ... members definitions ...
};
// ... any code that needs the class ...
}
User defined automatic type conversion
[edit | edit source]We already covered automatic type conversions (implicit conversion) and mentioned that some can be user-defined.
A user-defined conversion from a class to another class can be done by providing a constructor in the target class that takes the source class as an argument, Target(const Source& a_Class)
or by providing the target class with a conversion operator, as operator Source()
.
Ensuring objects of a class are never copied
[edit | edit source]This is required e.g. to prevent memory-related problems that would result in case the default copy-constructor or the default assignment operator is unintentionally applied to a class C which uses dynamically allocated memory, where a copy-constructor and an assignment operator are probably an overkill as they won't be used frequently.
Some style guidelines suggest making all classes non-copyable by default, and only enabling copying if it makes sense. Other (bad) guidelines say that you should always explicitly write the copy constructor and copy assignment operators; that's actually a bad idea, as it adds to the maintenance effort, adds to the work to read a class, is more likely to introduce errors than using the implicitly declared ones, and doesn't make sense for most object types. A sensible guideline is to think about whether copying makes sense for a type; if it does, then first prefer to arrange that the compiler-generated copy operations will do the right thing (e.g., by holding all resources via resource management classes rather than via raw pointers or handles), and if that's not reasonable then obey the law of three. If copying doesn't make sense, you can disallow it in either of two idiomatic ways as shown below.
Just declare the copy-constructor and assignment operator, and make them private. Do not define them. As they are not protected or public, they are inaccessible outside the class. Using them within the class would give a linker error since they are not defined.
class C
{
...
private:
// Not defined anywhere
C (const C&);
C& operator= (const C&);
};
Remember that if the class uses dynamically allocated memory for data members, you must define the memory release procedures in destructor ~C () to release the allocated memory.
A class which only declares these two functions can be used as a private base class, so that all classes which privately inherits such a class will disallow copying.
Container class
[edit | edit source]A class that is used to hold objects in memory or external storage is often called a container class. A container class acts as a generic holder and has a predefined behavior and a well-known interface. It is also a supporting class whose purpose is to hide the topology used for maintaining the list of objects in memory. When it contains a group of mixed objects, the container is called a heterogeneous container; when the container is holding a group of objects that are all the same, the container is called a homogeneous container.
Interface class
[edit | edit source]
Singleton class
[edit | edit source]A Singleton class is a class that can only be instantiated once (similar to the use of static variables or functions). It is one of the possible implementations of a creational pattern, which is fully covered in the Design Patterns Section of the book.
Abstract Classes
[edit | edit source]An abstract class is, conceptually, a class that cannot be instantiated and is usually implemented as a class that has one or more pure virtual (abstract) functions.
A pure virtual function is one which must be overridden by any concrete (i.e., non-abstract) derived class. This is indicated in the declaration with the syntax " = 0" in the member function's declaration.
- Example
class AbstractClass {
public:
virtual void AbstractMemberFunction() = 0; // Pure virtual function makes
// this class Abstract class.
virtual void NonAbstractMemberFunction1(); // Virtual function.
void NonAbstractMemberFunction2();
};
In general an abstract class is used to define an implementation and is intended to be inherited from by concrete classes. It's a way of forcing a contract between the class designer and the users of that class. If we wish to create a concrete class (a class that can be instantiated) from an abstract class we must declare and define a matching member function for each abstract member function of the base class. Otherwise, if any member function of the base class is left undefined, we will create a new abstract class (this could be useful sometimes).
Sometimes we use the phrase "pure abstract class," meaning a class that exclusively has pure virtual functions (and no data). The concept of interface is mapped to pure abstract classes in C++, as there is no "interface" construct in C++ the same way that there is in Java.
- Example
class Vehicle {
public:
explicit
Vehicle( int topSpeed )
: m_topSpeed( topSpeed )
{}
int TopSpeed() const {
return m_topSpeed;
}
virtual void Save( std::ostream& ) const = 0;
private:
int m_topSpeed;
};
class WheeledLandVehicle : public Vehicle {
public:
WheeledLandVehicle( int topSpeed, int numberOfWheels )
: Vehicle( topSpeed ), m_numberOfWheels( numberOfWheels )
{}
int NumberOfWheels() const {
return m_numberOfWheels;
}
void Save( std::ostream& ) const; // is implicitly virtual
private:
int m_numberOfWheels;
};
class TrackedLandVehicle : public Vehicle {
public:
TrackedLandVehicle ( int topSpeed, int numberOfTracks )
: Vehicle( topSpeed ), m_numberOfTracks ( numberOfTracks )
{}
int NumberOfTracks() const {
return m_numberOfTracks;
}
void Save( std::ostream& ) const; // is implicitly virtual
private:
int m_numberOfTracks;
};
In this example the Vehicle is an abstract base class as it has an abstract member function.The class WheeledLandVehicle is derived from the base class. It also holds data which is common to all wheeled land vehicles, namely the number of wheels. The class TrackedLandVehicle is another variation of the Vehicle class.
This is something of a contrived example but it does show how that you can share implementation details among a hierarchy of classes. Each class further refines a concept. This is not always the best way to implement an interface but in some cases it works very well. As a guideline, for ease of maintenance and understanding you should try to limit the inheritance to no more than 3 levels. Often the best set of classes to use is a pure virtual abstract base class to define a common interface. Then use an abstract class to further refine an implementation for a set of concrete classes and lastly define the set of concrete classes.
An abstract class is a class that is designed to be specifically used as a base class. An abstract class contains at least one pure virtual function. You declare a pure virtual function by using a pure specifier (= 0) in the declaration of a virtual member function in the class declaration.
The following is an example of an abstract class:
class AB {
public:
virtual void f() = 0;
};
Function AB::f is a pure virtual function. A function declaration cannot have both a pure specifier and a definition.
Abstract class cannot be used as a parameter type, a function return type, or the type of an explicit conversion, and not to declare an object of an abstract class. It can be used to declare pointers and references to an abstract class.
Pure Abstract Classes
[edit | edit source]An abstract class is one in which there is a declaration but no definition for a member function. The way this concept is expressed in C++ is to have the member function declaration assigned to zero.
- Example
class PureAbstractClass
{
public:
virtual void AbstractMemberFunction() = 0;
};
A pure Abstract class has only abstract member functions and no data or concrete member functions. In general, a pure abstract class is used to define an interface and is intended to be inherited by concrete classes. It's a way of forcing a contract between the class designer and the users of that class. The users of this class must declare a matching member function for the class to compile.
- Example of usage for a pure Abstract Class
class DrawableObject
{
public:
virtual void Draw(GraphicalDrawingBoard&) const = 0; //draw to GraphicalDrawingBoard
};
class Triangle : public DrawableObject
{
public:
void Draw(GraphicalDrawingBoard&) const; //draw a triangle
};
class Rectangle : public DrawableObject
{
public:
void Draw(GraphicalDrawingBoard&) const; //draw a rectangle
};
class Circle : public DrawableObject
{
public:
void Draw(GraphicalDrawingBoard&) const; //draw a circle
};
typedef std::list<DrawableObject*> DrawableList;
DrawableList drawableList;
GraphicalDrawingBoard drawingBoard;
drawableList.pushback(new Triangle());
drawableList.pushback(new Rectangle());
drawableList.pushback(new Circle());
for(DrawableList::const_iterator iter = drawableList.begin(),
endIter = drawableList.end();
iter != endIter;
++iter)
{
DrawableObject *object = *iter;
object->Draw(drawingBoard);
}
Note that this is a bit of a contrived example and that the drawable objects are not fully defined (no constructors or data) but it should give you the general idea of the power of defining an interface. Once the objects are constructed, the code that calls the interface does not know any of the implementation details of the called objects, only that of the interface. The object GraphicalDrawingBoard is a placeholder meant to represent the thing onto which the object will be drawn, i.e. the video memory, drawing buffer, printer.
Note that there is a great temptation to add concrete member functions and data to pure abstract base classes. This must be resisted, in general it is a sign that the interface is not well factored. Data and concrete member functions tend to imply a particular implementation and as such can inherit from the interface but should not be that interface. Instead if there is some commonality between concrete classes, creation of abstract class which inherits its interface from the pure abstract class and defines the common data and member functions of the concrete classes works well. Some care should be taken to decide whether inheritance or aggregation should be used. Too many layers of inheritance can make the maintenance and usage of a class difficult. Generally, the maximum accepted layers of inheritance is about 3, above that and refactoring of the classes is generally called for. A general test is the "is a" vs "has a", as in a Square is a Rectangle, but a Square has a set of sides.
What is a "nice" (container safe) class?
[edit | edit source]A "nice" class takes into consideration the use of the following functions:
1. The copy constructor.
2. The assignment operator.
3. The equality operator.
4. The inequality operator.
Class Declaration
[edit | edit source]class Nice
{
public:
Nice(const Nice &Copy);
Nice &operator= (const Nice &Copy);
bool operator== (const Nice ¶m) const;
bool operator!= (const Nice ¶m) const;
};
Description
[edit | edit source]A "nice" class could also be called a container safe class. Many containers such as those in the Standard Template Library (STL), that we'll see later, use copy construction and the assignment operator when interacting with the objects of your class. The assignment operator and copy constructor only need to be declared and defined if the default behavior, which is a member-wise (not binary) copy, is undesirable or insufficient to properly copy/construct your object.
A general rule of thumb is that if the default, member-wise copy operations do not work for your objects then you should define a suitable copy constructor and assignment operator. They are both needed if either is defined.
Copy Constructor
[edit | edit source]The purpose of the copy constructor is to allow the programmer to perform the same instructions as the assignment operator with the special case of knowing that the caller is initializing/constructing rather than an copying.
It is also good practice to use the explicit keyword when using a copy constructor to prevent unintended implicit type conversion.
Example
class Nice
{
public:
explicit Nice(int _a) : a(_a)
{
return;
}
private:
int a;
};
class NotNice
{
public:
NotNice(int _a) : a(_a)
{
return;
}
private:
int a;
};
int main()
{
Nice proper = Nice(10); //this is ok
Nice notproper = 10; //this will result in an error
NotNice eg = 10; //this WILL compile, you may not have intended this conversion
return 0;
}
Equality Operator
[edit | edit source]The equality operator says, "Is this object equal to that object?". What constitutes equal is up to the programmer. This is a requirement if you ever want to use the equality operator with objects of your class.
However, in most applications (e.g. mathematics), it is usually the case that coding the inequality is easier than coding the equality. In which case the following code can be written for the equality.
inline bool Nice::operator== (const Nice& param) const
{
return !(*this != param);
}
Inequality Operator
[edit | edit source]The inequality operator says, "Is this object not equal to that object?". What constitutes not equal is up to the programmer. This is a requirement if you ever want to use the inequality operator with objects of your class.
However, in some applications, coding the equality is easier than coding the inequality. In which case the following code can be written for the inequality.
inline bool Nice::operator!= (const Nice& param) const
{
return !(*this == param);
}
If the statement about the (in)equality operators having different efficiency (whatever kind) seems complete nonsense to you, consider that typically, all object attributes must match for two objects to be considered equal.
Typically, only one object attribute must differ for two objects to be considered unequal. For equality and inequality operators, that doesn't mean one is faster than the other.
Note, however, that using both the above equality and inequality functions as defined will result in an infinite recursive loop and care must be taken to use only one or the other. Also, there are some situations where neither applies and therefore neither of the above can be used.
Given two objects A and B (with class attributes x and y), an equality operator could be written as
if (A.x != B.x) return false;
if (A.y != B.y) return false;
return true;
while an inequality operator could be written as
if (A.x != B.x) return true;
if (A.y != B.y) return true;
return false;
So yes, the equality operator can certainly be written ...!(a!=b)..., but it isn't any faster. In fact, there's the additional overhead of a method call and a negation operation.
So the question becomes, is a little execution overhead worth the smaller code and improved maintainability? There is no simple answer to this it all depend on how the programmer is using them. If your class is composed of, say, an array of 1 billion elements, the overhead is negligible.
Operator overloading
[edit | edit source]Operator overloading (less commonly known as ad-hoc polymorphism) is a specific case of polymorphism (part of the OO nature of the language) in which some or all operators like +, = or == are treated as polymorphic functions and as such have different behaviors depending on the types of its arguments. Operator overloading is usually only syntactic sugar. It can easily be emulated using function calls.
Consider this operation:
add (a, multiply (b,c))
Using operator overloading permits a more concise way of writing it, like this:
a + b * c
(Assuming the * operator has higher precedence than +.)
Operator overloading can provide more than an aesthetic benefit, since the language allows operators to be invoked implicitly in some circumstances. Problems, and critics, to the use of operator overloading arise because it allows programmers to give operators completely free functionality, without an imposition of coherency that permits to consistently satisfy user/reader expectations. Usage of the <<
operator is an example of this problem.
// The expression
a << 1;
Will return twice the value of a if a is an integer variable, but if a is an output stream instead this will write "1" to it. Because operator overloading allows the programmer to change the usual semantics of an operator, it is usually considered good practice to use operator overloading with care.
To overload an operator is to provide it with a new meaning for user-defined types. This is done in the same fashion as defining a function. The basic syntax follows (where @ represents a valid operator):
return_type operator@(parameter_list)
{
// ... definition
}
Not all operators may be overloaded, new operators cannot be created, and the precedence, associativity or arity of operators cannot be changed (for example ! cannot be overloaded as a binary operator). Most operators may be overloaded as either a member function or non-member function, some, however, must be defined as member functions. Operators should only be overloaded where their use would be natural and unambiguous, and they should perform as expected. For example, overloading + to add two complex numbers is a good use, whereas overloading * to push an object onto a vector would not be considered good style.
- A simple Message Header
// sample of Operator Overloading
#include <string>
class PlMessageHeader
{
std::string m_ThreadSender;
std::string m_ThreadReceiver;
//return true if the messages are equal, false otherwise
inline bool operator == (const PlMessageHeader &b) const
{
return ( (b.m_ThreadSender==m_ThreadSender) &&
(b.m_ThreadReceiver==m_ThreadReceiver) );
}
//return true if the message is for name
inline bool isFor (const std::string &name) const
{
return (m_ThreadReceiver==name);
}
//return true if the message is for name
inline bool isFor (const char *name) const
{
return (m_ThreadReceiver==name);// since name type is std::string, it becomes unsafe if name == NULL
}
};
Operators as member functions
[edit | edit source]Aside from the operators which must be members, operators may be overloaded as member or non-member functions. The choice of whether or not to overload as a member is up to the programmer. Operators are generally overloaded as members when they:
- change the left-hand operand, or
- require direct access to the non-public parts of an object.
When an operator is defined as a member, the number of explicit parameters is reduced by one, as the calling object is implicitly supplied as an operand. Thus, binary operators take one explicit parameter and unary operators none. In the case of binary operators, the left hand operand is the calling object, and no type coercion will be done upon it. This is in contrast to non-member operators, where the left hand operand may be coerced.
// binary operator as member function
//Vector2D Vector2D::operator+(const Vector2D& right)const [...]
// binary operator as non-member function
//Vector2D operator+(const Vector2D& left, const Vector2D& right)[...]
// binary operator as non-member function with 2 arguments
//friend Vector2D operator+(const Vector2D& left, const Vector2D& right) [...]
// unary operator as member function
//Vector2D Vector2D::operator-()const {...}
// unary operator as non-member function[...]
//Vector2D operator-(const Vector2D& vec) [...]
Overloadable operators
[edit | edit source]Arithmetic operators
[edit | edit source]- + (addition)
- - (subtraction)
- * (multiplication)
- / (division)
- % (modulus)
As binary operators, these involve two arguments which do not have to be the same type. These operators may be defined as member or non-member functions. An example illustrating overloading for the addition of a 2D mathematical vector type follows.
Vector2D Vector2D::operator+(const Vector2D& right)
{
Vector2D result;
result.set_x(x() + right.x());
result.set_y(y() + right.y());
return result;
}
It is good style to only overload these operators to perform their customary arithmetic operation. Because operator has been overloaded as member function, it can access private fields.
Bitwise operators
[edit | edit source]- ^ (XOR)
- | (OR)
- & (AND)
- ~ (complement)
- << (shift left, insertion to stream)
- >> (shift right, extraction from stream)
All of the bitwise operators are binary, except complement, which is unary. It should be noted that these operators have a lower precedence than the arithmetic operators, so if ^ were to be overloaded for exponentiation, x ^ y + z may not work as intended. Of special mention are the shift operators, << and >>. These have been overloaded in the standard library for interaction with streams. When overloading these operators to work with streams the rules below should be followed:
- overload << and >> as friends (so that it can access the private variables with the stream be passed in by references)
- (input/output modifies the stream, and copying is not allowed)
- the operator should return a reference to the stream it receives (to allow chaining, cout << 3 << 4 << 5)
- An example using a 2D vector
friend ostream& operator<<(ostream& out, const Vector2D& vec) // output
{
out << "(" << vec.x() << ", " << vec.y() << ")";
return out;
}
friend istream& operator>>(istream& in, Vector2D& vec) // input
{
double x, y;
// skip opening paranthesis
in.ignore(1);
// read x
in >> x;
vec.set_x(x);
// skip delimiter
in.ignore(2);
// read y
in >> y;
vec.set_y(y);
// skip closing paranthesis
in.ignore(1);
return in;
}
Assignment operator
[edit | edit source]The assignment operator, =, must be a member function, and is given default behavior for user-defined classes by the compiler, performing an assignment of every member using its assignment operator. This behavior is generally acceptable for simple classes which only contain variables. However, where a class contains references or pointers to outside resources, the assignment operator should be overloaded (as general rule, whenever a destructor and copy constructor are needed so is the assignment operator), otherwise, for example, two strings would share the same buffer and changing one would change the other.
In this case, an assignment operator should perform two duties:
- clean up the old contents of the object
- copy the resources of the other object
For classes which contain raw pointers, before doing the assignment, the assignment operator should check for self-assignment, which generally will not work (as when the old contents of the object are erased, they cannot be copied to refill the object). Self assignment is generally a sign of a coding error, and thus for classes without raw pointers, this check is often omitted, as while the action is wasteful of cpu cycles, it has no other effect on the code.
- Example
class BuggyRawPointer { // example of super-common mistake
T *m_ptr;
public:
BuggyRawPointer(T *ptr) : m_ptr(ptr) {}
BuggyRawPointer& operator=(BuggyRawPointer const &rhs) {
delete m_ptr; // free resource; // Problem here!
m_ptr = 0;
m_ptr = rhs.m_ptr;
return *this;
};
};
BuggyRawPointer x(new T);
x = x; // We might expect this to keep x the same. This sets x.m_ptr == 0. Oops!
// The above problem can be fixed like so:
class WithRawPointer2 {
T *m_ptr;
public:
WithRawPointer2(T *ptr) : m_ptr(ptr) {}
WithRawPointer2& operator=(WithRawPointer2 const &rhs) {
if (this != &rhs) {
delete m_ptr; // free resource;
m_ptr = 0;
m_ptr = rhs.m_ptr;
}
return *this;
};
};
WithRawPointer2 x2(new T);
x2 = x2; // x2.m_ptr unchanged.
Another common use of overloading the assignment operator is to declare the overload in the private part of the class and not define it. Thus any code which attempts to do an assignment will fail on two accounts, first by referencing a private member function and second fail to link by not having a valid definition. This is done for classes where copying is to be prevented, and generally done with the addition of a privately declared copy constructor
- Example
class DoNotCopyOrAssign {
public:
DoNotCopyOrAssign() {};
private:
DoNotCopyOrAssign(DoNotCopyOrAssign const&);
DoNotCopyOrAssign &operator=(DoNotCopyOrAssign const &);
};
class MyClass : public DoNotCopyOrAssign {
public:
MyClass();
};
MyClass x, y;
x = y; // Fails to compile due to private assignment operator;
MyClass z(x); // Fails to compile due to private copy constructor.
Relational operators
[edit | edit source]- == (equality)
- != (inequality)
- > (greater-than)
- < (less-than)
- >= (greater-than-or-equal-to)
- <= (less-than-or-equal-to)
All relational operators are binary, and should return either true or false. Generally, all six operators can be based off a comparison function, or each other, although this is never done automatically (e.g. overloading > will not automatically overload < to give the opposite). There are, however, some templates defined in the header <utility>; if this header is included, then it suffices to just overload operator== and operator<, and the other operators will be provided by the STL.
Logical operators
[edit | edit source]- ! (NOT)
- && (AND)
- || (OR)
The logical operators AND are used when evaluating two expressions to obtain a single relational result.The operator corresponds to the boolean logical operation AND,which yields true if operands are true,and false otherwise.The following panel shows the result of operator evaluating the expression.
The ! operator is unary, && and || are binary. It should be noted that in normal use, && and || have "short-circuit" behavior, where the right operand may not be evaluated, depending on the left operand. When overloaded, these operators get function call precedence, and this short circuit behavior is lost. It is best to leave these operators alone.
- Example
bool Function1();
bool Function2();
Function1() && Function2();
If the result of Function1() is false, then Function2() is not called.
MyBool Function3();
MyBool Function4();
bool operator&&(MyBool const &, MyBool const &);
Function3() && Function4()
Both Function3() and Function4() will be called no matter what the result of the call is to Function3() This is a waste of CPU processing, and worse, it could have surprising unintended consequences compared to the expected "short-circuit" behavior of the default operators. Consider:
extern MyObject * ObjectPointer;
bool Function1() { return ObjectPointer != null; }
bool Function2() { return ObjectPointer->MyMethod(); }
MyBool Function3() { return ObjectPointer != null; }
MyBool Function4() { return ObjectPointer->MyMethod(); }
bool operator&&(MyBool const &, MyBool const &);
Function1() && Function2(); // Does not execute Function2() when pointer is null
Function3() && Function4(); // Executes Function4() when pointer is null
Compound assignment operators
[edit | edit source]- += (addition-assignment)
- -= (subtraction-assignment)
- *= (multiplication-assignment)
- /= (division-assignment)
- %= (modulus-assignment)
- &= (AND-assignment)
- |= (OR-assignment)
- ^= (XOR-assignment)
- <<= (shift-left-assignment)
- >>= (shift-right-assignment)
Compound assignment operators should be overloaded as member functions, as they change the left-hand operand. Like all other operators (except basic assignment), compound assignment operators must be explicitly defined, they will not be automatically (e.g. overloading = and + will not automatically overload +=). A compound assignment operator should work as expected: A @= B should be equivalent to A = A @ B. An example of += for a two-dimensional mathematical vector type follows.
Vector2D& Vector2D::operator+=(const Vector2D& right)
{
this->x += right.x;
this->y += right.y;
return *this;
}
Increment and decrement operators
[edit | edit source]- ++ (increment)
- -- (decrement)
Increment and decrement have two forms, prefix (++i) and postfix (i++). To differentiate, the postfix version takes a dummy integer. Increment and decrement operators are most often member functions, as they generally need access to the private member data in the class. The prefix version in general should return a reference to the changed object. The postfix version should just return a copy of the original value. In a perfect world, A += 1, A = A + 1, A++, ++A should all leave A with the same value.
- Example
SomeValue& SomeValue::operator++() // prefix
{
++data;
return *this;
}
SomeValue SomeValue::operator++(int unused) // postfix
{
SomeValue result = *this;
++data;
return result;
}
Often one operator is defined in terms of the other for ease in maintenance, especially if the function call is complex.
SomeValue SomeValue::operator++(int unused) // postfix
{
SomeValue result = *this;
++(*this); // call SomeValue::operator++()
return result;
}
Subscript operator
[edit | edit source]The subscript operator, [ ], is an operator which can take in any number of arguments (much like a function call) but usually one. It also must be a member function (hence it make take only one explicit parameter, the index). The subscript operator is not limited to taking an integral index. For instance, the index for the subscript operator for the std::map template is the same as the type of the key, so it may be a string etc. The subscript operator is generally overloaded twice; as a non-constant function (for when elements are altered), and as a constant function (for when elements are only accessed).
Function call operator
[edit | edit source]The function call operator, ( ), is generally overloaded to create objects which behave like functions, or for classes that have a primary operation. The function call operator must be a member function, but has no other restrictions - it may be overloaded with any number of parameters of any type, and may return any type. A class may also have several definitions for the function call operator.
Address of, Reference, and Pointer operators
[edit | edit source]These three operators, operator&(), operator*() and operator->() can be overloaded. In general these operators are only overloaded for smart pointers, or classes which attempt to mimic the behavior of a raw pointer. The pointer operator, operator->() has the additional requirement that the result of the call to that operator, must return a pointer, or a class with an overloaded operator->(). In general A == *&A should be true.
Note that overloading operator& invokes undefined behavior:
- ISO/IEC 14882:2003, Section 5.3.1
- The address of an object of incomplete type can be taken, but if the complete type of that object is a class type that declares operator&() as a member function, then the behavior is undefined (and no diagnostic is required).
- Example
class T {
public:
const memberFunction() const;
};
// forward declaration
class DullSmartReference;
class DullSmartPointer {
private:
T *m_ptr;
public:
DullSmartPointer(T *rhs) : m_ptr(rhs) {};
DullSmartReference operator*() const {
return DullSmartReference(*m_ptr);
}
T *operator->() const {
return m_ptr;
}
};
class DullSmartReference {
private:
T *m_ptr;
public:
DullSmartReference (T &rhs) : m_ptr(&rhs) {}
DullSmartPointer operator&() const {
return DullSmartPointer(m_ptr);
}
// conversion operator
operator T() { return *m_ptr; }
};
DullSmartPointer dsp(new T);
dsp->memberFunction(); // calls T::memberFunction
T t;
DullSmartReference dsr(t);
dsp = &dsr;
t = dsr; // calls the conversion operator
These are extremely simplified examples designed to show how the operators can be overloaded and not the full details of a SmartPointer or SmartReference class. In general you won't want to overload all three of these operators in the same class.
Comma operator
[edit | edit source]The comma operator,() , can be overloaded. The language comma operator has left to right precedence, the operator,() has function call precedence, so be aware that overloading the comma operator has many pitfalls.
- Example
MyClass operator,(MyClass const &, MyClass const &);
MyClass Function1();
MyClass Function2();
MyClass x = Function1(), Function2();
For non overloaded comma operator, the order of execution will be Function1(), Function2(); With the overloaded comma operator, the compiler can call either Function1(), or Function2() first.
Member Reference operators
[edit | edit source]The two member access operators, operator->() and operator->*() can be overloaded. The most common use of overloading these operators is with defining expression template classes, which is not a common programming technique. Clearly by overloading these operators you can create some very unmaintainable code so overload these operators only with great care.
When the -> operator is applied to a pointer value of type (T *), the language dereferences the pointer and applies the . member access operator (so x->m is equivalent to (*x).m). However, when the -> operator is applied to a class instance, it is called as a unary postfix operator; it is expected to return a value to which the -> operator can again be applied. Typically, this will be a value of type (T *), as in the example under Address of, Reference, and Pointer operators above, but can also be a class instance with operator->() defined; the language will call operator->() as many times as necessary until it arrives at a value of type (T *).
Memory management operators
[edit | edit source]- new (allocate memory for object)
- new[ ] (allocate memory for array)
- delete (deallocate memory for object)
- delete[ ] (deallocate memory for array)
The memory management operators can be overloaded to customize allocation and deallocation (e.g. to insert pertinent memory headers). They should behave as expected, new should return a pointer to a newly allocated object on the heap, delete should deallocate memory, ignoring a NULL argument. To overload new, several rules must be followed:
- new must be a member function
- the return type must be void*
- the first explicit parameter must be a size_t value
To overload delete there are also conditions:
- delete must be a member function (and cannot be virtual)
- the return type must be void
- there are only two forms available for the parameter list, and only one of the forms may appear in a class:
- void*
- void*, size_t
Conversion operators
[edit | edit source]Conversion operators enable objects of a class to be either implicitly (coercion) or explicitly (casting) converted to another type. Conversion operators must be member functions, and should not change the object which is being converted, so should be flagged as constant functions. The basic syntax of a conversion operator declaration, and declaration for an int-conversion operator follows.
operator ''type''() const; // const is not necessary, but is good style
operator int() const;
Notice that the function is declared without a return-type, which can easily be inferred from the type of conversion. Including the return type in the function header for a conversion operator is a syntax error.
double operator double() const; // error - return type included
Operators which cannot be overloaded
[edit | edit source]- ?: (conditional)
- . (member selection)
- .* (member selection with pointer-to-member)
- :: (scope resolution)
sizeof
(object size information)- typeid (object type information)
- static_cast (casting operator)
- const_cast (casting operator)
- reinterpret_cast (casting operator)
- dynamic_cast (casting operator)
To understand the reasons why the language doesn't permit these operators to be overloaded, read "Why can't I overload dot, ::, sizeof
, etc.?" at the Bjarne Stroustrup's C++ Style and Technique FAQ ( http://www.stroustrup.com/bs_faq2.html#overload-dot ).
I/O
[edit | edit source]Also commonly referenced as the C++ I/O of the C++ Standard Library, since the library also includes the C Standard library and its I/O implementation, as seen before in the Standard C I/O Section.
Input and output are essential for any computer software, as these are the only means by which the program can communicate with the user. The simplest form of input/output is pure textual, i.e. the application displays in console form, using simple ASCII characters to prompt the user for inputs, which are supplied using the keyboard.
There are many ways for a program to gain input and output, including
- File i/o, that is, reading and writing to files
- Console i/o, reading and writing to a console window, such as a terminal in UNIX-based operating systems or a DOS prompt in Windows.
- Network i/o, reading and writing from a network device
- String i/o, reading and writing treating a string as if it were the input or output device
While these may seem unrelated, they work very similarly. In fact, operating systems that follow the POSIX specification deal with files, devices, network sockets, consoles, and many other things all with one type of handle, a file descriptor. However, low-level interfaces provided by the operating system tend to be difficult to use, so C++, like other languages, provide an abstraction to make programming easier. This abstraction is the stream.
Character encoding
[edit | edit source]
American Standard Code for Information Interchange (ASCII) 95 chart
[edit | edit source]ASCII is a character-encoding scheme based on the ordering of the English alphabet. The 95 ASCII graphic characters numbered from 0x20 to 0x7E (32 to 126 decimal), also known as the printable characters, represent letters, digits, punctuation marks, and a few miscellaneous symbols. The first 32 ASCII characters, from 0x00 to 0x20, are known as control characters. The space character, that denotes the space between words, as produced by the space-bar of a keyboard, represented by code 0x20 (hexadecimal), is considered a non-printing graphic (or an invisible graphic) rather than a control character.
Binary | Oct | Dec | Hex | Glyph |
---|---|---|---|---|
010 0000 | 040 | 32 | 20 | space |
010 0001 | 041 | 33 | 21 | ! |
010 0010 | 042 | 34 | 22 | " |
010 0011 | 043 | 35 | 23 | # |
010 0100 | 044 | 36 | 24 | $ |
010 0101 | 045 | 37 | 25 | % |
010 0110 | 046 | 38 | 26 | & |
010 0111 | 047 | 39 | 27 | ' |
010 1000 | 050 | 40 | 28 | ( |
010 1001 | 051 | 41 | 29 | ) |
010 1010 | 052 | 42 | 2A | * |
010 1011 | 053 | 43 | 2B | + |
010 1100 | 054 | 44 | 2C | , |
010 1101 | 055 | 45 | 2D | - |
010 1110 | 056 | 46 | 2E | . |
010 1111 | 057 | 47 | 2F | / |
011 0000 | 060 | 48 | 30 | 0 |
011 0001 | 061 | 49 | 31 | 1 |
011 0010 | 062 | 50 | 32 | 2 |
011 0011 | 063 | 51 | 33 | 3 |
011 0100 | 064 | 52 | 34 | 4 |
011 0101 | 065 | 53 | 35 | 5 |
011 0110 | 066 | 54 | 36 | 6 |
011 0111 | 067 | 55 | 37 | 7 |
011 1000 | 070 | 56 | 38 | 8 |
011 1001 | 071 | 57 | 39 | 9 |
011 1010 | 072 | 58 | 3A | : |
011 1011 | 073 | 59 | 3B | ; |
011 1100 | 074 | 60 | 3C | < |
011 1101 | 075 | 61 | 3D | = |
011 1110 | 076 | 62 | 3E | > |
011 1111 | 077 | 63 | 3F | ? |
Binary | Oct | Dec | Hex | Glyph |
---|---|---|---|---|
100 0000 | 100 | 64 | 40 | @ |
100 0001 | 101 | 65 | 41 | A |
100 0010 | 102 | 66 | 42 | B |
100 0011 | 103 | 67 | 43 | C |
100 0100 | 104 | 68 | 44 | D |
100 0101 | 105 | 69 | 45 | E |
100 0110 | 106 | 70 | 46 | F |
100 0111 | 107 | 71 | 47 | G |
100 1000 | 110 | 72 | 48 | H |
100 1001 | 111 | 73 | 49 | I |
100 1010 | 112 | 74 | 4A | J |
100 1011 | 113 | 75 | 4B | K |
100 1100 | 114 | 76 | 4C | L |
100 1101 | 115 | 77 | 4D | M |
100 1110 | 116 | 78 | 4E | N |
100 1111 | 117 | 79 | 4F | O |
101 0000 | 120 | 80 | 50 | P |
101 0001 | 121 | 81 | 51 | Q |
101 0010 | 122 | 82 | 52 | R |
101 0011 | 123 | 83 | 53 | S |
101 0100 | 124 | 84 | 54 | T |
101 0101 | 125 | 85 | 55 | U |
101 0110 | 126 | 86 | 56 | V |
101 0111 | 127 | 87 | 57 | W |
101 1000 | 130 | 88 | 58 | X |
101 1001 | 131 | 89 | 59 | Y |
101 1010 | 132 | 90 | 5A | Z |
101 1011 | 133 | 91 | 5B | [ |
101 1100 | 134 | 92 | 5C | \ |
101 1101 | 135 | 93 | 5D | ] |
101 1110 | 136 | 94 | 5E | ^ |
101 1111 | 137 | 95 | 5F | _ |
Binary | Oct | Dec | Hex | Glyph |
---|---|---|---|---|
110 0000 | 140 | 96 | 60 | ` |
110 0001 | 141 | 97 | 61 | a |
110 0010 | 142 | 98 | 62 | b |
110 0011 | 143 | 99 | 63 | c |
110 0100 | 144 | 100 | 64 | d |
110 0101 | 145 | 101 | 65 | e |
110 0110 | 146 | 102 | 66 | f |
110 0111 | 147 | 103 | 67 | g |
110 1000 | 150 | 104 | 68 | h |
110 1001 | 151 | 105 | 69 | i |
110 1010 | 152 | 106 | 6A | j |
110 1011 | 153 | 107 | 6B | k |
110 1100 | 154 | 108 | 6C | l |
110 1101 | 155 | 109 | 6D | m |
110 1110 | 156 | 110 | 6E | n |
110 1111 | 157 | 111 | 6F | o |
111 0000 | 160 | 112 | 70 | p |
111 0001 | 161 | 113 | 71 | q |
111 0010 | 162 | 114 | 72 | r |
111 0011 | 163 | 115 | 73 | s |
111 0100 | 164 | 116 | 74 | t |
111 0101 | 165 | 117 | 75 | u |
111 0110 | 166 | 118 | 76 | v |
111 0111 | 167 | 119 | 77 | w |
111 1000 | 170 | 120 | 78 | x |
111 1001 | 171 | 121 | 79 | y |
111 1010 | 172 | 122 | 7A | z |
111 1011 | 173 | 123 | 7B | { |
111 1100 | 174 | 124 | 7C | | |
111 1101 | 175 | 125 | 7D | } |
111 1110 | 176 | 126 | 7E | ~ |
Streams
[edit | edit source]A stream is a type of object from which we can take values, or to which we can pass values. This is done transparently in terms of the underlying code that demonstrates the use of the std::cout stream, known as the standard output stream.
// 'Hello World!' program
#include <iostream>
int main()
{
std::cout << "Hello World!" << std::endl;
return 0;
}
Almost all input and output one ever does can be modeled very effectively as a stream. Having one common model means that one only has to learn it once. If you understand streams, you know the basics of how to output to files, the screen, sockets, pipes, and anything else that may come up.
A stream is an object that allows one to push data in or out of a medium, in order. Usually a stream can only output or can only input. It is possible to have a stream that does both, but this is rare. One can think of a stream as a car driving along a one-way street of information. An output stream can insert data and move on. It (usually) cannot go back and adjust something it has already written. Similarly, an input stream can read the next bit of data and then wait for the one that comes after it. It does not skip data or rewind and see what it had read five minutes ago.
The semantics of what a stream's read and write operations do depend on the type of stream. In the case of a file, an input file stream reads the file's contents in order without rewinding, and an output file stream writes to the file in order. For a console stream, output means displaying text, and input means getting input from the user via the console. If the user has not inputted anything, then the program blocks, or waits, for the user to enter in something.
iostream
[edit | edit source]iostream
is a header file used for input/output. It is part of the C++ standard library. The name stands for Input/Output Stream. In C++ there is no special syntax for streaming data input or output. Instead, these are combined as a library of functions. Like we have seen with the C Standard Library use of <cstdio>
header, iostream
provides basic OOP services for I/O.
The iostream
automatically defines and uses a few standard objects:
cin
, an object of the istream class that reads data from the standard input device.cout
, an object of the ostream class, which displays data to the standard output device.cerr
, another object of the ostream class that writes unbuffered output to the standard error device.clog
, like cerr, but uses buffered output.
for sending data to and from the standard streams input, output, error (unbuffered), and error (buffered) respectively. As part of the C++ standard library, these objects are a part of the std namespace.
- Standard input, output, and error
The most common streams one uses are cout, cin, and cerr (pronounced "c out", "c in", and "c err(or)", respectively). They are defined in the header <iostream>. Usually, these streams read and write from a console or terminal. In UNIX-based operating systems, such as Linux and Mac OS X, the user can redirect them to other files, or even other programs, for logging or other purposes. They are analogous to stdout, stdin, and stderr found in C. cout is used for generic output, cin is used for input, and cerr is used for printing errors. (cerr typically goes to the same place as cout, unless one or both is redirected, but it is not buffered and allows the user to fine-tune which parts of the program's output is redirected where.)
Output
[edit | edit source]The standard syntax for outputting to a stream, in this case, cout, is
cout << some_data << some_more_data;
Example
#include <iostream>
using namespace std;
int main()
{
int iA = 1;
cout << "Hello, World! " << iA << '\n';
return 0;
}
Result of Execution
Hello, World! 1
To add a line break, send a newline character, \n.
Using std::endl
also outputs a newline character, but it also calls os.flush().
Example
#include <iostream>
#include <ostream>
using namespace std;
int main()
{
int iA = 1;
char ch = 13;
cout << "Hello, World!" << "\n" << iA << "\n" << ch << endl;
return 0;
}
Execution
Hello, World! 1
It is always a good idea to end your output with a blank line, so as to not mess up with user's terminals.
As seen in the "Hello, World!" program, we direct the output to std::cout
. This means that it is a member of the standard library. For now, don't worry about what this means; we will cover the library and namespaces in later chapters.
What you do need to remember is that, in order to use the output stream, you must include a reference to the standard IO library, as shown here:
#include <iostream>
This opens up a number of streams, functions and other programming devices that we can now use.
For this section, we are interested in two of these; std::cout
and std::endl
.
Once we have referenced the standard IO library, we can use the output stream very simply. To use a stream, give its name, then pipe something in or out of it, as shown:
std::cout << "Hello, World!";
The << operator feeds everything to the right of it into the stream. We have essentially fed a text object into the stream. That's as far as our work goes; the stream now decides what to do with that object. In the case of the output stream, it's printed on-screen.
We're not limited to only sending a single object type to the stream, nor indeed are we limited to one object a time. Consider the examples below:
std::cout << "Hello, " << "Joe"<< std::endl;
std::cout << "The answer to life, the universe and everything is " << 42 << std::endl;
As can be seen, we feed in various values, separated by a pipe character. The result comes out something like:
Hello, Joe The answer to life, the universe and everything is 42
You will have noticed the use of std::endl throughout some of the examples so far. This is a special manipulator, which not only outputs a newline character but also calls os.flush().
Input
[edit | edit source]What would be the use of an application that only ever outputted information, but didn't care about what its users wanted? Minimal to none. Fortunately, inputting is as easy as outputting when you're using the stream.
The standard input stream is called std::cin and is used very similarly to the output stream. Once again, we instantiate the standard IO library:
#include <iostream>
This gives us access to std::cin (and the rest of that class). Now, we give the name of the stream as usual, and pipe output from it into a variable. A number of things have to happen here, demonstrated in the example below:
#include <iostream>
int main(int iArgc, char a_chArgv[]) {
int iA;
std::cout << "Hello! How old are you? ";
std::cin >> iA;
std::cout << "You're really " << iA << " years old?" << std::endl;
return 0;
}
We instantiate the standard IO library as usual, and call our main function in the normal way. Now we need to consider where the user's input goes. This calls for a variable (discussed in a later chapter) that we declare as being called a.
Next, we send some output, asking the user for their age. The real input happens now; everything the user types until they hit Enter is going to be stored in the input stream. We pull this out of the input stream and save it in our variable.
Finally, we output the user's age, piping the contents of our variable into the output stream.
Note: You will notice that, if anything other than a whole number is entered, the program will crash. This is due to the way in which we set up our variable. Don't worry about this for now; we will cover variables later on.
A Program Using User Input
[edit | edit source]The following program inputs two numbers from the user and prints their sum:
#include <iostream>
int main()
{
int iNumber1, iNumber2;
std::cout << "Enter number 1: ";
std::cin >> iNumber1;
std::cout << "Enter number 2: ";
std::cin >> iNumber2;
std::cout << "The sum of " << iNumber1 << " and " << iNumber2 << " is "
<< iNumber1 + iNumber2 << ".\n";
return 0;
}
Just like std::cout that represents the standard output stream, the C++ library provides (and the iostream header declares) the object std::cin representing standard input, which usually gets input from the keyboard. The statement:
std::cin >> iNumber1;
uses the extraction operator (>>) to get an integer input from the user. When used to input integers, any leading whitespace is skipped, a sequence of valid digits optionally preceded by a + or - sign is read and the value stored in the variable. Any remaining characters in the user input are not consumed. These would be considered next time some input operation is performed.
If you want the program to use a function from a specific namespace, normally you must specify which namespace the function is in. The above example calls to cout, which is a member of the std namespace (hence std::cout). If you want a program to specifically use the std namespace for an identifier, which essentially removes the need for all future scope resolution (e.g. std::), you could write the above program like this:
#include <iostream>
using namespace std;
int main()
{
int iNumber1, iNumber2;
cout << "Enter number 1: ";
cin >> iNumber1;
cout << "Enter number 2: ";
cin >> iNumber2;
cout << "The sum of " << iNumber1 << " and " << iNumber2 << " is "
<< iNumber1 + iNumber2 << ".\n";
return 0;
}
Please note that 'std' namespace is the namespace defined by standard C++ library.
Manipulators
[edit | edit source]A manipulator is a function that can be passed as an argument to a stream in different circumstances. For example, the manipulator 'hex' will cause the stream object to format subsequent integer input to the stream in hexadecimal instead of decimal. Likewise, 'oct' results in integers displaying in octal, and 'dec' reverts back to decimal.
Example
#include <iostream>
using namespace std;
int main()
{
cout << dec << 16 << ' ' << 10 << "\n";
cout << oct << 16 << ' ' << 10 << "\n";
cout << hex << 16 << ' ' << 10 << endl;
return 0;
}
Execution
16 10 20 12 10 a
There are many manipulators that can be used in conjunction with streams to simplify the formatting of input. For example, 'setw()' sets the field width of the data item next displayed. Used in conjunction with 'left' and 'right'(that set the justification of the data), 'setw' can easily be used to create columns of data.
Example
#include <iostream>
#include <iomanip>
using namespace std;
int main()
{
cout << setw(10) << right << 90 << setw(8) << "Help!\n";
cout << setw(10) << left << 45 << setw(8) << "Hi!" << endl;
return 0;
}
Execution
90 Help! 45 Hi!
The data in the top row display at the right of the columns created by 'setw', while in the next row, the data is left justified in the column. Please note the inclusion of a new library 'iomanip'. Most formatting manipulators require this library.
Here are some other manipulators and their uses:
Manipulator | Function |
---|---|
boolalpha | displays boolean values as 'true' and 'false' instead of as integers. |
noboolalpha | forces bools to display as integer values |
showuppercase | converts strings to uppercase before displaying them |
noshowuppercase | displays strings as they are received, instead of in uppercase |
fixed | forces floating point numbers to display with a fixed number of decimal places |
scientific | displays floating point numbers in scientific notation |
Buffers
[edit | edit source]Most stream objects, including 'cout' and 'cin', have an area in memory where the information they are transferring sits until it is asked for. This is called a 'buffer'. Understanding the function of buffers is essential to mastering streams and their use.
Example
#include <iostream>
using namespace std;
int main()
{
int iNumber1, iNumber2;
cin >> iNumber1;
cin >> iNumber2;
cout << "Number1: " << iNumber1 << "\n"
<< "Number2: " << iNumber2 << endl;
return 0;
}
Execution 1
>74 >27 Number1: 74 Number2: 27
The inputs are given separately, with a hard return between them. '>' denotes user input.
Execution 2
>74 27 Number1: 74 Number2: 27
The inputs are entered on the same line. They both go into the 'cin' stream buffer, where they are stored until needed. As 'cin' statements are executed, the contents of the buffer are read into the appropriate variables.
Execution 3
>74 27 56 Number1: 74 Number2: 27
In this example, 'cin' received more input than it asked for. The third number it read in, 56, was never inserted into a variable. It would have stayed in the buffer until 'cin' was called again. The use of buffers can explain many strange behaviors that streams can exhibit.
Example
#include <iostream>
using namespace std;
int main()
{
int iNumber1, iNumber2, iNumber3;
cin >> iNumber1 >> iNumber2;
cout << "Number1: " << iNumber1 << "\n"
<< "Number2: " << iNumber2 << endl;
cin >> iNumber3;
cout << "Number3: " << iNumber3 << endl;
return 0;
}
Execution
>45 89 37 Number1: 45 Number2: 89 Number3: 37
Notice how all three numbers were entered at the same time in one line, but the stream only pulled them out of the buffer when they were asked for. This can cause unexpected output, since the user might accidentally put an extra space into his input. A well written program will test for this type of unexpected input and handle it gracefully.
ios
[edit | edit source]ios is a header file in the C++ standard library that defines several types and functions basic to the operation of iostreams. This header is typically included automatically by other iostream headers. Programmers rarely include it directly.
Typedefs
[edit | edit source]Name | description |
---|---|
ios |
Supports the ios class from the old iostream library.
|
streamoff |
Supports internal operations. |
streampos |
Holds the current position of the buffer pointer or file pointer. |
streamsize |
Specifies the size of the stream. |
wios |
Supports the wios class from the old iostream library.
|
wstreampos |
Holds the current position of the buffer pointer or file pointer. |
Manipulators
[edit | edit source]Name | description |
---|---|
boolalpha |
Specifies that variables of type bool appear as true or false in the stream.
|
dec |
Specifies that integer variables appear in base 10 notation. |
fixed |
Specifies that a floating-point number is displayed in fixed-decimal notation. |
hex |
Specifies that integer variables appear in base 16 notation. |
internal |
Causes a number's sign to be left justified and the number to be right justified. |
left |
Causes text that is not as wide as the output width to appear in the stream flush with the left margin. |
noboolalpha |
Specifies that variables of type bool appear as 1 or 0 in the stream.
|
noshowbase |
Turns off indicating the notational base in which a number is displayed. |
noshowpoint |
Displays only the whole-number part of floating-point numbers whose fractional part is zero. |
noshowpos |
Causes positive numbers to not be explicitly signed. |
noskipws |
Cause spaces to be read by the input stream. |
nounitbuf |
Causes output to be buffered and processed when the buffer is full. |
nouppercase |
Specifies that hexadecimal digits and the exponent in scientific notation appear in lowercase. |
oct |
Specifies that integer variables appear in base 8 notation. |
right |
Causes text that is not as wide as the output width to appear in the stream flush with the right margin. |
scientific |
Causes floating point numbers to be displayed using scientific notation. |
showbase |
Indicates the notational base in which a number is displayed. |
showpoint |
Displays the whole-number part of a floating-point number and digits to the right of the decimal point even when the fractional part is zero. |
showpos |
Causes positive numbers to be explicitly signed. |
skipws |
Cause spaces to not be read by the input stream. |
unitbuf |
Causes output to be processed when the buffer is not empty. |
uppercase |
Specifies that hexadecimal digits and the exponent in scientific notation appear in uppercase. |
Classes
[edit | edit source]Name | description |
---|---|
basic_ios |
The template class describes the storage and member functions common to both input streams (of template class basic_istream) and output streams (of template class basic_ostream) that depend on the template parameters. |
fpos |
The template class describes an object that can store all the information needed to restore an arbitrary file-position indicator within any stream. |
ios_base |
The class describes the storage and member functions common to both input and output streams that do not depend on the template parameters. |
fstream
[edit | edit source]With cout and cin, we can do basic communication with the user. For more complex io, we would like to read from and write to files. This is done with file stream classes, defined in the header <fstream>. ofstream is an output file stream, and ifstream is an input file stream.
- Files
To open a file, one can either call open on the file stream or, more commonly, use the constructor. One can also supply an open mode to further control the file stream. Open modes include
- ios::app Leaves the file's original contents and appends new data to the end.
- ios::out Outputs new data in the file, removing the old contents. (default for ofstream)
- ios::in Reads data from the file. (default for ifstream)
Example
// open a file called Test.txt and write "HELLO, HOW ARE YOU?" to it
#include <fstream>
using namespace std;
int main()
{
ofstream file1;
file1.open("file1.txt", ios::app);
file1 << "This data will be appended to the file file1.txt\n";
file1.close();
ofstream file2("file2.txt");
file2 << "This data will replace the contents of file2.txt\n";
return 0;
}
The call to close()
can be omitted, if you do not care about the return value (whether it succeeded); the destructors will call close when the object goes out of scope.
If an operation (e.g. opening a file) was unsuccessful, a flag is set in the stream object. You can check the flags' status using the bad()
or fail()
member functions, which return a boolean value. The stream object doesn't throw any exceptions in such a situation; hence manual status check is required. See reference for details on bad()
and fail()
.
Text input until EOF/error/invalid input
[edit | edit source]Input from the stream infile to a variable data until one of the following:
- EOF reached on infile.
- An error occurs while reading from infile (e.g., connection closed while reading from a remote file).
- The input item is invalid, e.g. non-numeric characters, when data is of type int.
#include <iostream>
// …
while (infile >> data)
{
// manipulate data here
}
Note that the following is not correct:
#include <iostream>
// …
while (!infile.eof())
{
infile >> data; // wrong!
// manipulate data here
}
This will cause the last item in the input file to be processed twice, because eof() does not return true until input fails due to EOF.
ostream
[edit | edit source]- Classes and output streams
It is often useful to have your own classes' instances compatible with the stream framework. For instance, if you defined the class Foo like this:
class Foo
{
public:
Foo() : m_iX(1), m_iY(2)
{
}
int m_iX, m_iY;
};
You will not be able to pass its instance to cout directly using the '<<' operator, because it is not defined for these two objects (Foo and ostream). What needs to be done is to define this operator and thus bind the user-defined class with the stream class.
ostream& operator<<(ostream& output, Foo& arg)
{
output << arg.m_iX << "," << arg.m_iY;
return output;
}
Now this is possible:
Foo myObject;
cout << "my_object's values are: " << myObject << endl;
The operator function needs to have 'ostream&' as its return type, so chaining output works as usual between the stream and objects of type Foo:
Foo my1, my2, my3;
cout << my1 << my2 << my3;
This is because (cout << my1) is of type ostream&, so the next argument (my2) can be appended to it in the same expression, which again gives an ostream& so my3 can be appended and so on.
If you decided to restrict access to the member variables m_iX
and m_iY
(that is probably a good idea) within the class Foo, i.e.:
class Foo
{
public:
Foo() : m_iX(1), m_iY(2)
{
}
private:
int m_iX, m_iY;
};
you will have trouble, because operator
<< function doesn't have access to the private variables of its second argument. There are two possible solutions to this problem:
1. Within the class Foo, declare the operator
<< function as the classes' friend that grants it access to private members, i.e. add the following line to the class declaration:
friend ostream& operator<<(ostream& output, Foo& arg);
Then define the operator<< function as you normally would (note that the declared function is not a member of Foo, just its friend, so don't try defining it as Foo::operator<<).
2. Add public-available functions for accessing the member variables and make the operator
<< function use these instead:
class Foo
{
public:
Foo() : m_iX(1), m_iY(2)
{
}
int getX()
{
return m_iX;
}
int getY()
{
return m_iY;
}
private:
int m_iX, m_iY;
};
ostream& operator<<(ostream& output, Foo& arg)
{
output << iArg.getX() << "," << iArg.getY();
return output;
}
Rounding number example
[edit | edit source]This is a small example that rounds a number to a string, a function called RoundToString
. Figures may have trailing zeros, those that would be expected to disappear using a number format.
The constant class contains repeating constants that should exist only once in the code so that to avoid inadvertent changes. (If the one constant is changed inadvertently, it is most likely to be seen, as it is used at several locations.)
The code was first cross-compiled by the JAVA TO C++ CONVERTER of Tangible Software Solutions. Parts may have a copyright notice requirement that prevents inclusion in this work you can get yourself the code for the StringConverter
at that location.
Here is the relevant code and its call. You are invited to write a shorter version that gives the same result.
Common.cpp
:
#include "StdAfx.h"
namespace common
{
double const Common::NEARLY_ZERO = 1E-4;
double const Common::VERY_LARGE = 1E10;
string const Common::strZERO = *new string(1, Common::ZERO);
bool Common::IsTrimmable(char const chCHARACTER,
char const chTRIM,
bool const bIS_NUMERIC)
{
return ((chCHARACTER == chTRIM)
|| (bIS_NUMERIC && (chCHARACTER == Common::ZERO)));
}
string const& Common::Trim(string const& strVALUE, char const chTRIM)
{
return TrimLeft(TrimRight(strVALUE, chTRIM), chTRIM);
}
string const& Common::TrimLeft(string const& strVALUE,
char const chTRIM)
{
if (strVALUE.length() == 0) return strVALUE;
else
{
ushort usPosition = 0;
for (; usPosition < strVALUE.length(); usPosition++)
{
if (!IsTrimmable(strVALUE[usPosition], chTRIM)) break;
}
return *new string(strVALUE.substr(usPosition));
}
}
string const& Common::TrimRight(string const& strVALUE,
char const chTRIM)
{
if (strVALUE.length() == 0) return strVALUE;
else
{
ushort usPosition = strVALUE.length() - 1;
for (; usPosition < strVALUE.length(); usPosition--)
{
if (!IsTrimmable(strVALUE[usPosition], chTRIM))
{
if (strVALUE[usPosition] != Common::PERIOD) ++usPosition;
break;
}
}
return *new string(strVALUE.substr(0, usPosition));
}
}
}
Common.h
:
#pragma once
#include <string>
namespace common
{
/// <summary>
/// Class that comprises of constant values and recurring algorithms.
///
/// @author Saban
///
///</summary>
class Common
{
/// <summary>
/// Determines, if the character is trimmable or not.
/// </summary>
/// <param name="chCHARACTER">Character to be checked</param>
/// <param name="chTRIM">
/// Trim character that defaults to a space
/// </param>
/// <param name="bIS_NUMERIC">
/// If numeric, zeros are also considered as trimmable characters
/// </param>
/// <returns>Whether the character is trimmable or not</returns>
static bool IsTrimmable(char const chCHARACTER,
char const chTRIM = SPACE, bool const bIS_NUMERIC = true);
public:
/// <summary>Carriage return constant</summary>
//static char const CARRIAGE_RETURN = '\r';
/// <summary>Constant of comma or decimal point in German</summary>
static char const COMMA = ',';
/// <summary>Dash or minus constant</summary>
static char const DASH = '-';
/// <summary>
/// The exponent sign in a scientific number, or the letter e.
/// </summary>
static char const EXPONENT = 'e';
/// <summary>The full stop or period</summary>
static char const PERIOD = '.';
/// <summary>Space constant</summary>
static char const SPACE = ' ';
/// <summary>Space constant</summary>
static char const ZERO = '0';
/// <summary>
//// Value under which the double should switch to fixed-point.
/// </summary>
static double const VERY_LARGE;
/// <summary>
//// Value above which the double should switch to fixed-point.
/// </summary>
static double const NEARLY_ZERO;
/// <summary>
/// The zero string constant used at several places
/// </summary>
static string const strZERO;
/// <summary>
/// Trims the trim character from left and right of the value.
/// </summary>
/// <param name="strVALUE">Value to be trimmed</param>
/// <param name="chTRIM">
/// Trim character that defaults to a space
/// </param>
/// <returns>Trimmed string</returns>
static string const& Trim(string const& strVALUE,
char const chTRIM = Common::SPACE);
/// <summary>
/// Trims the trim character from left the value.
/// </summary>
/// <param name="strVALUE">Value to be trimmed</param>
/// <param name="chTRIM">
/// Trim character that defaults to a space
/// </param>
/// <returns>Trimmed string</returns>
static string const& TrimLeft(string const& strVALUE,
char const chTRIM = Common::SPACE);
/// <summary>
/// Trims the trim character from right of the value.
/// </summary>
/// <param name="strVALUE">Value to be trimmed</param>
/// <param name="chTRIM">
/// Trim character that defaults to a space
/// </param>
/// <returns>Trimmed string</returns>
static string const& TrimRight(string const& strVALUE,
char const chTRIM = Common::SPACE);
}; // class Common
}
The Math
class is an enhancement to the <math.h>
library and contains the rounding calculations.
Math.cpp
:
#include "StdAfx.h"
#include <string>
namespace common
{
string const Maths::strZEROS = "000000000000000000000000000000000";
byte Maths::CalculateMissingSignificantZeros(
byte const ySIGNIFICANTS_AFTER,
char const chSEPARATOR,
double const dVALUE,
string const strMANTISSA)
{
// Existing significants after decimal separator are
byte const yAFTER = FindSignificantsAfterDecimal(chSEPARATOR, strMANTISSA);
// Number of digits to add are
byte const yZEROS = ySIGNIFICANTS_AFTER - ((yAFTER == 0) ? 1 : yAFTER);
return ((yZEROS >= 0) ? yZEROS : 0);
}
byte Maths::FindDecimalSeparatorPosition(string const& strVALUE)
{
byte const ySEPARATOR_AT = (byte)strVALUE.find(Common::PERIOD);
return (ySEPARATOR_AT > -1)
? ySEPARATOR_AT : (byte)strVALUE.find(Common::COMMA);
}
byte Maths::FindFirstNonZeroDigit(double const dVALUE)
{
return FindFirstNonZeroDigit(StringConverter::ToString
<double, StringConverter::DIGITS>(dVALUE));
}
byte Maths::FindFirstNonZeroDigit(string const& strVALUE)
{
// Find the position of the first non-zero digit:
byte yNonZeroAt = 0;
for (; (yNonZeroAt < (byte)strVALUE.length())
&& ((strVALUE[yNonZeroAt] == Common::DASH)
|| (strVALUE[yNonZeroAt] == Common::PERIOD)
|| (strVALUE[yNonZeroAt] == Common::ZERO)); yNonZeroAt++) ;
return yNonZeroAt;
}
byte Maths::FindSignificantDigits(byte const ySIGNIFICANTS_AFTER,
char const chSEPARATOR,
double const dVALUE)
{
if (dVALUE == 0) return 0;
else
{
//JAVA TO C++ CONVERTER TODO TASK: No native C++ equivalent to 'ToString':
string strMantissa = FindMantissa(TestCommons::SIGNIFICANTS,
chSEPARATOR, StringConverter::ToString<double,
StringConverter::DIGITS>(dVALUE));
if (dVALUE == static_cast<long>(dVALUE))
{
strMantissa =
strMantissa.substr(0, strMantissa.find(Common::COMMA));
}
strMantissa = RetrieveDigits(chSEPARATOR, strMantissa);
return strMantissa.substr(
FindFirstNonZeroDigit(strMantissa)).length();
}
}
byte Maths::FindSignificantsAfterDecimal(byte const ySIGNIFICANTS_BEFORE,
byte const ySIGNIFICANT_DIGITS)
{
byte const yAFTER_DECIMAL = ySIGNIFICANT_DIGITS - ySIGNIFICANTS_BEFORE;
return (yAFTER_DECIMAL > 0) ? yAFTER_DECIMAL : 0;
}
byte Maths::FindSignificantsAfterDecimal(char const chSEPARATOR,
double const dVALUE)
{
if (dVALUE == 0) return 1;
else
{
//JAVA TO C++ CONVERTER TODO TASK: No native C++ equivalent to 'ToString':
string strValue = StringConverter::ToString<double,
StringConverter::DIGITS>(dVALUE);
byte const ySEPARATOR_AT = (byte)strValue.find(chSEPARATOR);
if (ySEPARATOR_AT > -1)
{
strValue = strValue.substr(ySEPARATOR_AT + 1);
}
short const sE_AT = strValue.find(Common::EXPONENT);
if (sE_AT > 0) strValue = strValue.substr(0, sE_AT);
long lValue = StringConverter::FromString
<long, StringConverter::DIGITS>(strValue);
if (abs(dVALUE) < 1)
{
return (byte)StringConverter::ToString<long,
StringConverter::DIGITS>(lValue).length();
}
else if (lValue == 0) return 0;
else
{
strValue = "0." + strValue;
return (byte)(strValue.length() - 2);
}
}
}
byte Maths::FindSignificantsBeforeDecimal(char const chSEPARATOR,
double const dVALUE)
{
string const strVALUE = StringConverter::ToString<double,
StringConverter::DIGITS>(dVALUE);
// Return immediately, if result is clear: Special handling at
// crossroads of floating point and exponential numbers:
if ((dVALUE == 0)
|| (abs(dVALUE) >= Common::NEARLY_ZERO) && (abs(dVALUE) < 1))
{
return 0;
}
else if ((abs(dVALUE) > 0) && (abs(dVALUE) < Common::NEARLY_ZERO))
{
return 1;
}
else
{
byte significants = 0;
// Significant digits to the right of decimal separator:
for (byte s = 0; s < (byte)strVALUE.length(); s++)
{
if ((strVALUE[s] == chSEPARATOR)
|| (strVALUE[s] == Common::EXPONENT))
{
break;
}
else if (strVALUE[s] != Common::DASH) significants++;
}
return significants;
}
}
byte Maths::FindSignificantsAfterDecimal(char const chSEPARATOR,
string const strVALUE)
{
size_t const COMMA_AT = strVALUE.find(chSEPARATOR);
size_t const LENGTH = strVALUE.length();
// Existing digits after decimal separator are
byte yAfter = 0;
// Existing significants after decimal separator may start at the first
// non-zero digit:
if (StringConverter::FromString<double, 5>
(strVALUE.substr(0, COMMA_AT)) == 0)
{
string strRightOf = Common::TrimLeft(strVALUE.substr(COMMA_AT + 1));
yAfter = strRightOf.length();
}
else yAfter = (COMMA_AT < string::npos) ? LENGTH - 1 - COMMA_AT : 0;
return yAfter;
}
double Maths::Power(short const sBASIS, short const sEXPONENT)
{
if (sBASIS == 0) return (sEXPONENT != 0) ? 1 : 0;
else
{
if (sEXPONENT == 0) return 1;
else
{
// The Math method power does change the least significant
// digits after the decimal separator and is therefore useless.
double result = 1;
if (sEXPONENT > 0)
{
for (short s = 0; s < sEXPONENT; s++) result *= sBASIS;
}
else if (sEXPONENT < 0)
{
for (short s = sEXPONENT; s < 0; s++) result /= sBASIS;
}
return result;
}
}
}
double Maths::Round(byte const yDIGITS,
char const chSEPARATOR,
double const dVALUE)
{
if (dVALUE == 0) return 0;
else
{
bool bIsScientific = false;
double const dCONSTANT = Power(10, yDIGITS);
if ((abs(dVALUE) < Common::NEARLY_ZERO)
|| (abs(dVALUE) >= Common::VERY_LARGE))
{
bIsScientific = true;
}
short const sEXPONENT =
FindExponent(dVALUE, chSEPARATOR, bIsScientific);
short sExponent = sEXPONENT;
// Determine the correcting power:
short sPower = sExponent;
if (sEXPONENT == 0)
{
sPower = FindExponent(dVALUE, chSEPARATOR, HasDecimals(dVALUE));
}
double dValue1 = dVALUE*dCONSTANT*pow(10., -sPower);
string const strE_SIGN = (sExponent < 0)
? StringConverter::ToString<char,
StringConverter::DIGITS>(Common::DASH) : "";
if (sExponent != 0)
{
sExponent = static_cast<short>(abs(sExponent));
dValue1 = Round(dValue1);
}
else dValue1 = Round(dValue1)/dCONSTANT/pow(10., -sPower);
// Power method cannot be used, as the exponentiated number may
// exceed the maximal long value.
sExponent -= Signum(sEXPONENT)*(FindSignificantDigits(
yDIGITS, chSEPARATOR, dValue1) - 1);
if (sEXPONENT != 0)
{
//JAVA TO C++ CONVERTER TODO TASK: No native C++ equivalent to 'ToString':
string strValue = StringConverter::ToString<double,
StringConverter::DIGITS>(dValue1);
//JAVA TO C++ CONVERTER TODO TASK: No native C++ equivalent to 'ToString':
strValue =
strValue.substr(0, FindDecimalSeparatorPosition(strValue))
+ Common::EXPONENT + strE_SIGN
+ StringConverter::ToString<short,
StringConverter::DIGITS>(sExponent);
dValue1 = StringConverter::FromString<double, 5>(strValue);
}
return dValue1;
}
}
double Maths::Round(double const dValue)
{
return (double)(long long)(dValue + .5);
}
short Maths::FindExponent(double const dVALUE,
char const chSEPARATOR,
bool const bSCIENTIFIC)
{
//JAVA TO C++ CONVERTER TODO TASK: No native C++ equivalent to 'ToString':
return (short)StringConverter::FromString<short,
StringConverter::DIGITS>(FindExponent(StringConverter::ToString
<double, StringConverter::DIGITS>(dVALUE), chSEPARATOR,
bSCIENTIFIC));
}
string Maths::FindExponent(string const& strVALUE,
char const chSEPARATOR,
bool const bSCIENTIFIC)
{
if (StringConverter::FromString
<double, StringConverter::DIGITS>(strVALUE) == 0)
{
return Common::strZERO;
}
short const sE_AT = strVALUE.find(Common::EXPONENT);
short sExponent = 0;
if (sE_AT < 0)
{
// If all numbers are to be considered scientific, such as
// 1 = 1.0000e0…
if (bSCIENTIFIC)
{
// Find the exponent by counting leading zeros:
byte const ySEPARATOR_AT = strVALUE.find(chSEPARATOR);
if (ySEPARATOR_AT > -1)
{
string const strAFTER = strVALUE.substr(ySEPARATOR_AT + 1);
sExponent = 0;
for (; sExponent < (short)strAFTER.length(); sExponent++)
{
if ((strAFTER[sExponent] >= '1')
&& (strAFTER[sExponent] <= '9'))
{
sExponent *= -1;
break;
}
}
}
}
else return Common::strZERO;
}
else
{
sExponent = (short)StringConverter::FromString
<double, StringConverter::DIGITS>(strVALUE.substr(sE_AT + 1));
}
return StringConverter::ToString<short,
StringConverter::DIGITS>(sExponent);
}
string Maths::FindMantissa(byte const yDIGITS,
char const chSEPARATOR,
const string& strVALUE)
{
byte yDigits = yDIGITS;
short const sE_AT = strVALUE.find(Common::EXPONENT);
string strValue = strVALUE;
// Remove lagging insignificant zeros, if any:
if (sE_AT == -1)
{
if (StringConverter::FromString<short,
3>(strValue.substr(0, 2)) == 0)
{
byte yPosition = 2;
for (; (yPosition < (byte)strValue.length())
&& ((strValue[yPosition] == Common::PERIOD)
|| (strValue[yPosition] == Common::ZERO)); yPosition++);
yDigits += yPosition;
strValue = strValue.substr(0, yDigits);
}
}
else strValue = Common::TrimRight(strValue.substr(0, sE_AT));
if (StringConverter::FromString<double, StringConverter::DIGITS>(strValue) == 0)
{
strValue = RemoveInsignificants(yDIGITS, strValue);
}
else strValue = RemoveInsignificants(yDigits, strValue);
if (FindDecimalSeparatorPosition(strValue) == -1)
{
return strValue + ".0";
}
else return strValue;
}
string Maths::RemoveInsignificants(byte const yDIGITS,
string const& strVALUE)
{
byte const yCHARACTERS = yDIGITS
+ ((FindDecimalSeparatorPosition(strVALUE) < string::npos) ? 1 : 0)
+ ((strVALUE[0] == Common::DASH)
? ((strVALUE[1] == Common::ZERO) ? 2 : 1)
: ((strVALUE[0] == Common::ZERO) ? 1 : 0));
return strVALUE.substr(0, yCHARACTERS);
}
string Maths::RetrieveDigits(char const chSEPARATOR, const string& strNUMBER)
{
string strNumber = strNUMBER;
short const sE_AT = strNumber.find(Common::EXPONENT);
// Strip off exponent part, if it exists:
if (sE_AT > -1) strNumber = strNumber.substr(0, sE_AT);
//JAVA TO C++ CONVERTER TODO TASK: No native C++ equivalent to 'ToString':
//JAVA TO C++ CONVERTER TODO TASK: No native C++ equivalent to 'replace':
return StringConverter::Replace(StringConverter::Replace(strNumber,
StringConverter::ToString<char, 1>(Common::DASH), ""),
StringConverter::ToString<char, 1>(chSEPARATOR), "");
}
string Maths::RoundToString(byte const ySIGNIFICANT_DIGITS,
char const chSEPARATOR,
double dValue)
{
// Number of significants that *are* before the decimal separator:
//JAVA TO C++ CONVERTER TODO TASK: No native C++ equivalent to 'ToString':
byte const ySIGNIFICANTS_BEFORE =
FindSignificantsBeforeDecimal(chSEPARATOR, dValue);
// Number of decimals that *should* be after the decimal separator:
byte const ySIGNIFICANTS_AFTER = FindSignificantsAfterDecimal(
ySIGNIFICANTS_BEFORE, ySIGNIFICANT_DIGITS);
byte const yDIGITS = (dValue != 0)
? ySIGNIFICANTS_BEFORE + ySIGNIFICANTS_AFTER : 3 /* = 0.0 */;
// Round to the specified number of digits after decimal separator:
double const dROUNDED =
Maths::Round(ySIGNIFICANTS_AFTER, chSEPARATOR, dValue);
//JAVA TO C++ CONVERTER TODO TASK: No native C++ equivalent to 'ToString':
string const strEXPONENT = FindExponent(StringConverter::ToString
<double, StringConverter::DIGITS>(dROUNDED));
//JAVA TO C++ CONVERTER TODO TASK: No native C++ equivalent to 'ToString':
string const strMANTISSA = FindMantissa(TestCommons::SIGNIFICANTS,
chSEPARATOR, StringConverter::ToString<double,
StringConverter::DIGITS>(dROUNDED));
double const dMANTISSA = StringConverter::FromString
<double, TestCommons::SIGNIFICANTS>(strMANTISSA);
StringBuilder* pRESULT = new StringBuilder(strMANTISSA);
// Determine the significant digits in this number:
byte const ySIGNIFICANTS = FindSignificantDigits(ySIGNIFICANTS_AFTER,
chSEPARATOR, dMANTISSA);
// Add lagging zeros, if necessary:
if (ySIGNIFICANTS <= ySIGNIFICANT_DIGITS)
{
if (ySIGNIFICANTS_AFTER != 0)
{
if (dValue != 0)
{
pRESULT->Append(strZEROS.substr(0,
CalculateMissingSignificantZeros(ySIGNIFICANTS_AFTER,
chSEPARATOR, dMANTISSA, strMANTISSA)));
}
}
else
{
// Cut off the decimal separator & after decimal digits:
//JAVA TO C++ CONVERTER TODO TASK: No native C++ equivalent to 'ToString':
byte const yDECIMAL =
pRESULT->Find(StringConverter::ToString<char,
StringConverter::DIGITS>(chSEPARATOR));
if (yDECIMAL > -1) pRESULT->SetLength(yDECIMAL);
}
}
else if (ySIGNIFICANTS_BEFORE > ySIGNIFICANT_DIGITS)
{
dValue /= Power(10, ySIGNIFICANTS_BEFORE - ySIGNIFICANT_DIGITS);
dValue = Round(dValue);
byte const yDIGITS = ySIGNIFICANT_DIGITS + ((dValue < 0) ? 1 : 0);
//JAVA TO C++ CONVERTER TODO TASK: No native C++ equivalent to 'ToString':
string const strVALUE = StringConverter::ToString<double,
StringConverter::DIGITS>(dValue).substr(0, yDIGITS);
pRESULT->SetLength(0);
pRESULT->Append(strVALUE + strZEROS.substr(0,
ySIGNIFICANTS_BEFORE - ySIGNIFICANT_DIGITS));
}
if (StringConverter::FromString<double,
StringConverter::DIGITS>(strEXPONENT) != 0)
{
pRESULT->Append(Common::EXPONENT + strEXPONENT);
}
//JAVA TO C++ CONVERTER TODO TASK: No native C++ equivalent to 'ToString':
return pRESULT->ToString();
} // public static String RoundToString(…)
}
Math.h
:
#pragma once
#include <string>
#include <cmath>
///
namespace common
{
/// <summary>
/// Class for special mathematical calculations.
/// ATTENTION: Should not depend on any other class except Java libraries!
/// @author Saban
///</summary>
class Maths
{
private:
/// <summary>The string of zeros</summary>
static string const strZEROS;
/// <summary>
/// Determines how many zeros are to be appended after the decimal
/// digits.
/// </summary>
/// <param name="ySIGNIFICANTS_AFTER">
/// Requested significant digits after decimal
/// </param>
/// <param name="chSEPARATOR">
/// Language-specific decimal separator
/// </param>
/// </param>
/// <param name="dVALUE">Rounded number</param>
/// <param name="strMANTISSA">
/// Current string where missing digits are to be determined
/// </param>
/// <returns>Requested value</returns>
static byte CalculateMissingSignificantZeros(
byte const ySIGNIFICANTS_AFTER, char const chSEPARATOR,
double const dVALUE,
string const strMANTISSA = "");
/// <summary>
/// Finds the decimal position language-independently.
/// </summary>
/// <param name="strVALUE">
/// Value to be searched for the decimal separator
/// </param>
/// <returns>
/// The position of the decimal separator or string::npos,
/// if no decimal separator has been found.
/// </returns>
static byte FindDecimalSeparatorPosition(string const& strVALUE);
/// <summary>
/// Finds the first non-zero decimal position.
/// </summary>
/// <param name="dVALUE">
/// Value to be searched for the decimal position
/// </param>
/// <returns>The first non-zero decimal position</returns>
static byte FindFirstNonZeroDigit(double const dVALUE);
/// <summary>
/// Finds the first non-zero decimal position.
/// </summary>
/// <param name="strVALUE">
/// Value to be searched for the decimal position
/// </param>
/// <returns>The first non-zero decimal position</returns>
static byte FindFirstNonZeroDigit(string const& strVALUE);
/// <summary>
/// Calculates the number of all significant digits (without the sign
/// and the decimal separator).
/// </summary>
/// <param name="ySIGNIFICANTS_AFTER">
/// Number of decimal places after the separator</param>
/// <param name="chSEPARATOR">
/// Language-specific decimal separator
/// </param>
/// <param name="dVALUE">
/// Value where the digits are to be counted
/// </param>
/// <returns>Number of significant digits</returns>
static byte FindSignificantDigits(byte const ySIGNIFICANTS_AFTER,
char const chSEPARATOR, double const dVALUE);
/// <summary>
/// Determines the number of significant digits after the decimal
/// separator knowing the total number of significant digits and the
/// number before the decimal separator.
/// </summary>
/// <param name="ySIGNIFICANTS_BEFORE">
/// Number of significant digits before separator
/// </param>
/// <param name="ySIGNIFICANT_DIGITS">
/// Number of all significant digits
/// </param>
/// Number of significant decimals after the separator
/// </returns>
static byte FindSignificantsAfterDecimal(
byte const ySIGNIFICANTS_BEFORE, byte const ySIGNIFICANT_DIGITS);
/// <summary>
/// Finds the significant digits after the decimal separator of a
/// mantissa.
/// </summary>
/// <param name="chSEPARATOR">
/// Language-specific decimal separator
/// </param>
/// <param name="dVALUE">Value to be scrutinised</param>
/// <returns>
/// Number of insignificant zeros after decimal separator.
/// </returns>
static byte FindSignificantsAfterDecimal(char const chSEPARATOR,
double const dVALUE);
/// <summary>
/// Finds the significant digits after the decimal separator of a
/// mantissa.
/// </summary>
/// <param name="chSEPARATOR">
/// Language-specific decimal separator
/// </param>
/// <param name="strVALUE">Value to be scrutinised</param>
/// <returns>
/// Number of insignificant zeros after decimal separator.
/// </returns>
static byte FindSignificantsAfterDecimal(char const chSEPARATOR,
string const strVALUE);
/// <summary>
/// Determines the number of digits before the decimal point.</summary>
/// <param name="chSEPARATOR">
/// Language-specific decimal separator
/// </param>
/// <param name="dVALUE">Value to be scrutinised</param>
/// <returns>Number of digits before the decimal separator</returns>
static byte FindSignificantsBeforeDecimal(char const chSEPARATOR,
double const dVALUE);
/// <summary>
/// Returns the exponent part of the double number.</summary>
/// <param name="dVALUE">
/// Value of which the exponent is of interest
/// </param>
/// <param name="chSEPARATOR">Decimal separator</param>
/// <param name="bSCIENTIFIC">
/// If true, the number is considered in scientific notation of the form
/// 9.999e999 (like 1 = 1.0e0 or 0.124 = 1.24e-1).
/// </param>
/// <returns>Exponent of the number or zero.</returns>
static short FindExponent(double const dVALUE,
char const chSEPARATOR = Common::PERIOD,
bool const bSCIENTIFIC = false);
/// <summary>
/// Finds the exponent of a number.</summary>
/// <param name="strVALUE">
/// Value where an exponent is to be searched
/// </param>
/// <param name="chSEPARATOR">Decimal separator</param>
/// <param name="bSCIENTIFIC">
/// If true, the number is considered in scientific notation of the form
/// 1 = 1.0e0 or 0.124 = 1.24e-1.
/// </param>
/// <returns>Exponent, if it exists, or "0"</returns>
static string FindExponent(string const& strVALUE,
char const chSEPARATOR = Common::PERIOD,
bool const bSCIENTIFIC = false);
/// <summary>
/// Finds the mantissa of a number.</summary>
/// <param name="yDIGITS">
/// Number of all digits
/// </param>
/// <param name="chSEPARATOR">
/// Language-specific decimal separator
/// </param>
/// <param name="strVALUE">
/// Value where the mantissa is to be found
/// </param>
/// <returns>Mantissa of the number</returns>
static string FindMantissa(byte const yDIGITS,
char const chSEPARATOR,
string const& strVALUE);
/// <summary>
/// Removes all insignificant digits.</summary>
/// <param name="yDIGITS">
/// Number of significant digits
/// </param>
/// <returns>Number with the requested number of digits</returns>
static string RemoveInsignificants(byte const yDigits,
string const& strVALUE);
/// <summary>
/// Retrieves the digits of the value without decimal separator or
/// sign.
/// </summary>
/// <param name="chSEPARATOR"></param>
/// <param name="strNUMBER">Mantissa to be scrutinised</param>
/// <returns>The digits only</returns>
static string RetrieveDigits(char const chSEPARATOR,
string const& strNUMBER);
public:
/// <summary>
/// Determines whether the number has decimal places after
/// the separator or not.
/// </summary>
/// <param name="VALUE"></param>
/// <returns>true, if it has decimals and false otherwise.</returns>
template<class T>
static bool HasDecimals(const T VALUE)
{
return ((VALUE - (long long)VALUE) != 0);
}
/// <summary>
/// Calculates the power of the base to the exponent without changing
/// the least-significant digits of a number.
/// </summary>
/// <param name="BASIS"></param>
/// <param name="EXPONENT"></param>
/// <returns>BASIS to power of EXPONENT</returns>
static double Power(short const sBASIS, short const sEXPONENT);
/// <summary>
/// Rounds a number to the decimal places.</summary>
/// <param name="yDIGITS">Number of all decimal places</param>
/// <param name="chSEPARATOR">
/// Language-specific decimal separator
/// </param>
/// <param name="dVALUE">Number to be rounded</param>
/// <returns>Rounded number to the requested decimal places</returns>
static double Round(byte const yDIGITS,
char const chSEPARATOR, double const dVALUE);
/// <summary>
/// Replacement for Math.round(double) of Java.</summary>
/// <param name="dValue">Number to be rounded</param>
/// <returns>Rounded number to the requested decimal places</returns>
static double Round(double const dVALUE);
/// <summary>Signum function</summary>
/// <param name="number">Value to be scrutinised</param>
/// <returns>Sign of the number</returns>
template<class T>
static byte Signum(T number)
{
return (number < T(0)) ? T(-1) : (number > T(0));
}
/// <summary>
/// Rounds to a fixed number of significant digits.</summary>
/// <param name="ySIGNIFICANT_DIGITS">
/// Requested number of significant digits
/// </param>
/// <param name="chSEPARATOR">
/// Language-specific decimal separator
/// </param>
/// <param name="dValue">Number to be rounded</param>
/// <returns>Rounded number</returns>
static string RoundToString(byte const ySIGNIFICANT_DIGITS,
char const chSEPARATOR, double dValue);
}; // class Maths
}
Using precompiled headers requires the StdAfx files:
StdAfx.cpp
:
// StdAfx.cpp : Quelldatei, die nur die Standard-Includes einbindet.
// Commons.pch ist der vorkompilierte Header.
// StdAfx.obj enthält die vorkompilierten Typinformationen.
#include "StdAfx.h"
StdAfx.h
:
// StdAfx.h : Includedatei für Standardsystem-Includedateien
// oder häufig verwendete projektspezifische Includedateien,
// die nur in unregelmäßigen Abständen geändert werden.
//
#pragma once
using namespace std;
typedef signed char byte;
typedef unsigned short ushort;
// Used headers:
#include "Common.h"
#include "Maths.h"
#include "StringBuilder.h"
#include "TestCommons.h"
#include "StringConverter.h"
#include <iostream>
The StringBuilder
class was added by the JAVA TO C++ CONVERTER during the crosscompilation:
StringBuilder.cpp
:
#include "StdAfx.h"
namespace common
{
StringBuilder::StringBuilder()
{
strMain = "";
}
StringBuilder::StringBuilder(string const& strVALUE)
{
Append(strVALUE);
}
size_t const StringBuilder::Find(string const& strSEARCH) const
{
return strMain.find(strSEARCH);
}
string const& StringBuilder::ToString() const
{
return strMain;
}
void StringBuilder::Append(string const& strVALUE)
{
strMain.append(strVALUE);
}
void StringBuilder::SetLength(const string::size_type SIZE)
{
strMain.resize(SIZE);
}
}
StringBuilder.h
:
#pragma once
namespace common
{
class StringBuilder
{
private:
string strMain;
public:
/// <summary>
/// Standard constructor
/// </summary>
StringBuilder();
/// <summary>
/// Constructor
/// </summary>
/// <param name="strSUBJECT">Value to be used</param>
StringBuilder(string const& strSUBJECT);
/// <summary>
/// Finds the search string inside itself.
/// </summary>
/// <param name="strSEARCH">Value to be used</param>
/// <returns>
/// The position of the searched text or -1, if the search string
/// has not been found.
/// </returns>
size_t const Find(string const& strSEARCH) const;
/// <summary>
/// Converts the contents of itself to a string.
/// </summary>
/// <returns>String content</returns>
string const& ToString() const;
/// <summary>
/// Appends a text to this object
/// </summary>
/// <param name="strVALUE">Value to be appended</param>
void Append(string const& strVALUE);
/// <summary>
/// Sets the length of the content of this object.
/// </summary>
/// <param name="SIZE">New reduced size</param>
void SetLength(const string::size_type SIZE);
};
}
Extensive testing of a software is crucial for qualitative code. To say that the code is tested does not give much information. The question is what is tested. Not in this case, but often it is also important to know where (in which environment) it was tested, and how - i.e. the test succession. Here is the code used to test the Maths
class.
TestCommons.cpp
:
#include "StdAfx.h"
namespace common
{
void TestCommons::Test()
{
// Test rounding
vector<double>* pa_dValues = new vector<double>();
vector<double>& a_dValues = *pa_dValues;
a_dValues.push_back(0.0);
AddValue(1.4012984643248202e-45, a_dValues);
AddValue(1.999999757e-5, a_dValues);
AddValue(1.999999757e-4, a_dValues);
AddValue(0.000640589, a_dValues);
AddValue(1.999999757e-3, a_dValues);
AddValue(0.3396899998188019, a_dValues);
AddValue(0.34, a_dValues);
AddValue(7.07, a_dValues);
AddValue(118.188, a_dValues);
AddValue(118.2, a_dValues);
AddValue(123.405009, a_dValues);
AddValue(30.76994323730469, a_dValues);
AddValue(130.76994323730469, a_dValues);
AddValue(540, a_dValues);
AddValue(12345, a_dValues);
AddValue(123456, a_dValues);
AddValue(540911, a_dValues);
AddValue(9.223372036854776e56, a_dValues);
byte const ySIGNIFICANTS = 5;
for (vector<double>::const_iterator element = a_dValues.begin();
element != a_dValues.end(); ++element)
{
cout << "Maths::RoundToString(" << (short)ySIGNIFICANTS << ", '"
<< Common::PERIOD << "', " << StringConverter::ToString
<double, StringConverter::DIGITS>(*element) << ") = ";
cout << Maths::RoundToString(ySIGNIFICANTS, Common::PERIOD,
*element) << endl;
}
pa_dValues->clear();
byte y;
cin >> y;
} // void Test()
void TestCommons::AddValue(double const dVALUE, vector<double>& a_dValues)
{
a_dValues.push_back(-dVALUE);
a_dValues.push_back(dVALUE);
}
}
TestCommons.h
:
#pragma once
#include <string>
#include <vector>
namespace common
{
/// <summary>
/// Test class for the common functionality
/// @author Saban
///</summary>
class TestCommons
{
private:
/// <summary>
/// Method that adds a negative and a positive value to values.</summary>
/// <param name="dVALUE"></param>
/// <param name="a_dValues"></param>
static void AddValue(double const dVALUE, vector<double>& a_dValues);
public:
/// <summary>Number of significant digits</summary>
static short const SIGNIFICANTS = 5;
/// <summary>
/// Test for the common functionality</summary>
/// <param name="args"></param>
static void Test();
}; // class TestCommons
}
The results of your better code should comply with the result I got:
Maths::RoundToString(5, '.', 0.00000000000000000) = 0.00000 Maths::RoundToString(5, '.', -1.40129846432482020e-045) = -1.4012e-45 Maths::RoundToString(5, '.', 1.40129846432482020e-045) = 1.4013e-45 Maths::RoundToString(5, '.', -1.99999975700000000e-005) = -1.9998e-5 Maths::RoundToString(5, '.', 1.99999975700000000e-005) = 2.0000e-5 Maths::RoundToString(5, '.', -0.00019999997570000) = -0.00019999 Maths::RoundToString(5, '.', 0.00019999997570000) = 0.00020000 Maths::RoundToString(5, '.', -0.00064058900000000) = -0.00064058 Maths::RoundToString(5, '.', 0.00064058900000000) = 0.00064059 Maths::RoundToString(5, '.', -0.00199999975700000) = -0.0019999 Maths::RoundToString(5, '.', 0.00199999975700000) = 0.0020000 Maths::RoundToString(5, '.', -0.33968999981880188) = -0.33967 Maths::RoundToString(5, '.', 0.33968999981880188) = 0.33968 Maths::RoundToString(5, '.', -0.34000000000000002) = -0.33999 Maths::RoundToString(5, '.', 0.34000000000000002) = 0.34000 Maths::RoundToString(5, '.', -7.07000000000000030) = -7.0699 Maths::RoundToString(5, '.', 7.07000000000000030) = 7.0700 Maths::RoundToString(5, '.', -118.18800000000000000) = -118.18 Maths::RoundToString(5, '.', 118.18800000000000000) = 118.19 Maths::RoundToString(5, '.', -118.20000000000000000) = -118.19 Maths::RoundToString(5, '.', 118.20000000000000000) = 118.20 Maths::RoundToString(5, '.', -123.40500900000001000) = -123.40 Maths::RoundToString(5, '.', 123.40500900000001000) = 123.41 Maths::RoundToString(5, '.', -30.76994323730469100) = -30.768 Maths::RoundToString(5, '.', 30.76994323730469100) = 30.770 Maths::RoundToString(5, '.', -130.76994323730469000) = -130.75 Maths::RoundToString(5, '.', 130.76994323730469000) = 130.77 Maths::RoundToString(5, '.', -540.00000000000000000) = -539.99 Maths::RoundToString(5, '.', 540.00000000000000000) = 540.00 Maths::RoundToString(5, '.', -12345.00000000000000000) = -12344 Maths::RoundToString(5, '.', 12345.00000000000000000) = 12345 Maths::RoundToString(5, '.', -123456.00000000000000000) = -123450 Maths::RoundToString(5, '.', 123456.00000000000000000) = 123460 Maths::RoundToString(5, '.', -540911.00000000000000000) = -540900 Maths::RoundToString(5, '.', 540911.00000000000000000) = 540910 Maths::RoundToString(5, '.', -9.22337203685477560e+056) = -9.2232e56 Maths::RoundToString(5, '.', 9.22337203685477560e+056) = 9.2234e56
If you are interested in a comparison of C++ with C#, take a look at C# programming rounding number example. If you want to compare C++ with Java, compare it to the rounding code at Java Programming rounding number example.
The string class
[edit | edit source]The string class is a part of the C++ standard library, used for convenient manipulation of sequences of characters, to replace the static, unsafe C method of handling strings. To use the string class in a program, the <string> header must be included. The standard library string class can be accessed through the std namespace.
The basic template class is basic_string<>
and its standard specializations are string
and wstring
.
Basic usage
[edit | edit source]Declaring a std string is done by using one of these two methods:
using namespace std;
string std_string;
or
std::string std_string;
Text I/O
[edit | edit source]This section will deal only with keyboard and text input. There are many other inputs that can be read (mouse movements and button clicks, etc), but these will not be covered in this section, even reading the special keys of the keyboard will be excluded.
Perhaps the most basic use of the string class is for reading text from the user and writing it to the screen. In the header file iostream, C++ defines an object named cin that handles input in much the same way that cout handles output.
// snipped designed to get an integer value from the user
int x;
std::cin >> x;
The >> operator will cause the execution to stop and will wait for the user to type something. If the user types a valid integer, it will be converted into an integer value and stored in x.
If the user types something other than an integer, the compiler will not report an error. Instead, it leaves the old content (a "random" meaningless value) in x and continues.
This can then be extended into the following program:
#include <iostream>
#include <string>
int main(){
std::string name;
std::cout << "Please enter your first name: ";
std::cin >> name;
std::cout << "Welcome " << name << "!" << std::endl;
return 0;
}
Although a string may hold a sequence containing any character—including spaces and nulls—when reading into a string using cin and the extraction operator (>>) only the characters before the first space will be stored. Alternatively, if an entire line of text is desired, the getline function may be used:
std::getline(std::cin, name);
Getting user input
[edit | edit source]Fortunately, there is a way to check and see if an input statement succeeds. We can invoke the good function on cin to check what is called the stream state. good returns a bool: if true, then the last input statement succeeded. If not, we know that some previous operation failed, and also that the next operation will fail.
Thus, getting input from the user might look like this:
#include <iostream>
using namespace std;
int main ()
{
int x;
// prompt the user for input
cout << "Enter an integer: ";
// get input
cin >> x;
// check and see if the input statement succeeded
if (cin.good() == false) {
cout << "That was not an integer." << endl;
return -1;
}
// print the value we got from the user
cout << x << endl;
return 0;
}
cin can also be used to input a string:
string name;
cout << "What is your name? ";
cin >> name;
cout << name << endl;
As with the scanf() function from the Standard C Library, this statement only takes the first word of input, and leaves the rest for the next input statement. So, if you run this program and type your full name, it will only output your first name.
You may also notice the >> operator doesn't handle errors as expected (for example, if you accidentally typed your name in a prompt for a number.) Because of these issues, it may be more suitable to read a line of text, and using the line for input — this is performed using the function called getline.
string name;
cout << "What is your name? ";
getline (cin, name);
cout << name << endl;
The first argument to getline is cin, which is where the input is coming from. The second argument is the name of the string variable where you want the result to be stored.
getline reads the entire line until the user hits Return or Enter. This is useful for inputting strings that contain spaces.
In fact, getline is generally useful for getting input of any kind. For example, if you wanted the user to type an integer, you could input a string and then check to see if it is a valid integer. If so, you can convert it to an integer value. If not, you can print an error message and ask the user to try again.
To convert a string to an integer you can use the strtol function defined in the header file cstdlib. (Note that the older function atoi is less safe than strtol, as well as being less capable.)
If you still need the features of the >> operator, you will need to create a string stream as available from <sstream>. The use of this stream will be discussed in a later chapter.
More advanced string manipulation
[edit | edit source]
We will be using this dummy string for some of our examples.
string str("Hello World!");
This invokes the default constructor with a const char*
argument. Default constructor creates a string which contains nothing, i.e. no characters, not even a '\0'
(however std::string is not null terminated).
string str2(str);
Will trigger the copy constructor. std::string
knows enough to make a deep copy of the characters it
stores.
string str2 = str;
This will copy strings using assignment operator. Effect of this code is same as using copy constructor in example above.
Size
[edit | edit source]string::size_type string::size() const;
string::size_type string::length() const;
So for example one might do:
string::size_type strSize = str.size();
string::size_type strSize2 = str2.length();
The methods size()
and length()
both return the size of the string object. There is no apparent difference. Remember that the last character in the string is size() - 1
and not size()
. Like in C-style strings, and arrays in general, std::string
starts counting from 0.
I/O
[edit | edit source]ostream& operator<<(ostream &out, string &str);
istream& operator>>(istream &in, string &str);
The shift operators (>>
and <<
) have been overloaded so you can perform I/O operations on istream
and ostream
objects, most notably cout
, cin
, and filestreams. Thus you could just do console I/O like this:
std::cout << str << endl;
std::cin >> str;
istream& getline (istream& in, string& str, char delim = '\n');
Alternatively, if you want to read entire lines at a time, use getline()
. Note that this is not a member function. getline()
will retrieve characters from input stream in
and assign them to str
until EOF
is reached or delim
is encountered. getline
will reset the input string before appending data to it. delim
can be set to any char
value and acts as a general delimiter. Here is some example usage:
#include <fstream>
//open a file
std::ifstream file("somefile.cpp");
std::string data, temp;
while( getline(file, temp, '#')) //while data left in file
{
//append data
data += temp;
}
std::cout << data;
Because of the way getline
works (i.e. it returns the input stream), you can nest multiple getline()
calls to get multiple strings; however this may significantly reduce readability.
Operators
[edit | edit source]char& string::operator[](string::size_type pos);
Chars
in string
s can be accessed directly using the overloaded subscript ([]
) operator, like in char
arrays:
std::cout << str[0] << str[2];
prints "Hl".
std::string
supports casting from the older C string type const char*
. You can also assign or append a simple char
to a string. Assigning a char*
to a string
is as simple as
str = "Hello World!";
If you want to do it character by character, you can also use
str = 'H';
Not surprisingly, operator+
and operator+=
are also defined! You can append another string
, a const char*
or a char
to any string.
The comparison operators >, <, ==, >=, <=, !=
all perform comparison operations on strings, similar to the C strcmp() function. These return a true/false value.
if(str == "Hello World!")
{
std::cout << "Strings are equal!";
}
Searching strings
[edit | edit source]string::size_type string::find(string needle, string::size_type pos = 0) const;
You can use the find()
member function to find the first occurrence of a string inside another. find()
will look for needle
inside this
starting from position pos
and return the position of the first occurrence of the needle
. For example:
std::string haystack = "Hello World!";
std::string needle = "o";
std::cout << haystack.find(needle);
Will simply print "4" which is the index of the first occurrence of "o" in str
. If we want the "o" in "World", we need to modify pos
to point past the first occurrence. str.find(find, 4)
would return 4, while str.find(find, 5)
would give 7. If the substring isn't found, find()
returns std::string::npos
.This simple code searches a string for all occurrences of "wiki" and prints their positions:
std::string wikistr = "wikipedia is full of wikis (wiki-wiki means fast)";
for(string::size_type i = 0, tfind; (tfind = wikistr.find("wiki", i)) != string::npos; i = tfind + 1)
{
std::cout << "Found occurrence of 'wiki' at position " << tfind << std::endl;
}
string::size_type string::rfind(string needle, string::size_type pos = string::npos) const;
The function rfind()
works similarly, except it returns the last occurrence of the passed string.
Inserting/erasing
[edit | edit source]string& string::insert(size_type pos, const string& str);
You can use the insert()
member function to insert another string into a string.
For example:
string newstr = " Human";
str.insert (5,newstr);
Would return Hello Human World!
string& string::erase(size_type pos, size_type n);
You can use erase()
to remove a substring from a string. For example:
str.erase (5,6);
Would return Hello!
string& string::substr(size_type pos, size_type n);
You can use substr()
to extract a substring from a string. For example:
string str = "Hello World!";
string part = str.substr(6,5);
Would return World.
Backwards compatibility
[edit | edit source]const char* string::c_str() const;
const char* string::data() const;
For backwards compatibility with C/C++ functions which only accept char*
parameters, you can use the member functions string::c_str()
and string::data()
to return a temporary const char*
string you can pass to a function. The difference between these two functions is that c_str()
returns a null-terminated string while data()
does not necessarily return a null-terminated string. So, if your legacy function requires a null-terminated string, use c_str()
, otherwise use data()
(and presumably pass the length of the string in as well).
String Concatenation
[edit | edit source]Strings can be concatenated(appended) together by simply using the + operator
string firstString = "Hello";
string secondString = " World!";
string finalString = firstString + secondString;
cout << finalString << endl;
Output here will be "Hello World"
Appending Strings
[edit | edit source]Another thing to note is that instead of the + operator or concatenation, the .append(str2)
class member function can be used to concatenate one string to another. The str2
object is permitted to be a string object or a C-string. This will add the string in the parenthesis to the string which is calling append
.
It should also be noted that the append function can be used to append a string at a specific character location in the string. If a programmer puts str.append(str2, p, n)
, n
number of characters from position p
in string str2
will be appended to the end of str
. For example, in the following code, there are two strings. 5 characters from the second string, starting with position 8 of str2
will be appended to the end of the first string, str
.
string str("Watch out for ");
string str2("Llamas, Bears, and Telemarketers!");
str.append(str2, 8, 5);
cout << str << endl;
The code above will append the word Bears
to the end of the first string, and then print Watch out for Bears
on the screen.
String Conversion to signed integer
[edit | edit source]Sometimes we want to convert strings into numbers. To do so we can use stoi() function which takes a string as an argument and returns the value.
string exString1 = "12023";
string exString2 = "1.23249";
string exString3 = "1232 test";
To convert these strings to a number we save the stoi of the string variable in an integer variable.
int exInt1 = stoi(exString1);
int exInt2 = stoi(exString2);
int exInt3 = stoi(exString3);
cout << "Before stoi string:" << exString1 << " and after stoi int:" << exInt1 << endl;
cout << "Before stoi string:" << exString2 << " and after stoi int: " << exInt2 << endl;
cout << "Before stoi string:" << exString3 << " and after stoi int:" << exInt3 << endl;
Output will be: Before stoi string :12023 and after stoi int: 12023 Before stoi string :1 and after stoi int: 1 Before stoi string :1232 and after stoi int: 1232
Integer Conversion to String
[edit | edit source]If we instead want to do the opposite and convert an integer to a string, we can use the to_string() function, which takes an integer as an argument and returns the integer as a string.
int exInt = 12023;
To convert this integer to a string, we call the to_string() function. The integer variable is passed into the function as an argument. The function will then return that integer as a string, which can then be assigned to a string variable.
string exString = to_string(exInt);
cout << "Before to_string int:" << exInt << " and after to_string string:" << exString << endl;
Output will be: Before to_string int:12023 and after to_string string:12023
String Formatting
[edit | edit source]Strings can only be appended to other strings, but not to numbers or other datatypes, so something like std::string("Foo") + 5
would not result in a string with the content "Foo5"
. To convert other datatypes into string there exist the class std::ostringstream
, found in the include file <sstream>
. std::ostringstream
acts exactly like std::cout
, the only difference is that the output doesn't go to the current standard output as provided by the operating system, but into an internal buffer, that buffer can be converted into a std::string
via the std::ostringstream::str()
method.
Example
[edit | edit source]#include <iostream>
#include <sstream>
int main()
{
std::ostringstream buffer;
// Use the std::ostringstream just like std::cout or other iostreams
buffer << "You have: " << 5 << " Helloworlds in your inbox";
// Convert the std::ostringstream to a normal string
std::string text = buffer.str();
std::cout << text << std::endl;
return 0;
}
Advanced use
[edit | edit source]Chapter Summary
[edit | edit source]- Structures
- Unions
- Classes (Inheritance, Member Functions, Polymorphism and this pointer)
- Operator overloading
- Standard Input/Output streams Library
Object Oriented Programming
[edit | edit source]Structures
[edit | edit source]A structure is a compound data type that contains different members of different types. The members are accessed by their names. A value of a structure-object is a tuple of values of each member of the object. A structure can also be seen as a simple implementation of the object paradigm from (OOP). A struct is like a class except for the default access (class has default access of private, struct has default access of public). C++ also guarantees that a struct that only contains C types is equivalent to the same C struct thus allowing access to legacy C functions, it can (but may not) also have constructors (and must have them, if a templated class is used inside a struct), as with Classes the compiler implicitly-declares a destructor if the struct doesn’t have a user-declared destructor. Structures will also allow Operator Overloading.
A struct is defined by:
struct myStructType /*: inheritances */ {
public:
// declare public members here
protected:
// declare protected members here
private:
// declare private members here
};
The optional keywords public:, protected:, private: declare the protection status of the following members. They can be put in any order, and more than one of each may occur. If no protection status is given, then the members are public. (Private or protected members can be accessed only by methods or friends of the structure; explained in a later chapter).
Because it is not supported in C, it is uncommon to have structs in C++ using inheritances even though they are supported just like in classes. The more distinctive aspect is that structs can have two identities one is in reference to the type and another to the specific object. The public access label can sometimes be ignored since the default state of struct for member functions and fields is public.
Objects of type myStructType are declared using:
/* struct */ myStructType obj1 /* , obj2, ... */;
Repeating the keyword struct at the beginning is optional.
It is possible to define objects directly in the struct definition instead of using a name for the struct-type:
struct { /*members*/ } obj1 /*, obj2, .. */ ;
- Why should you Use Structs, Not Classes?
Older programmer languages used a similar type called Record (i.e.: COBOL, FORTRAN) this was implemented in C as the struct keyword. And so C++ uses structs to comply with this C's heritage (the code and the programmers). Structs are simpler to be managed by the programmer and the compiler. One should use a struct for POD (PlainOldData) types that have no methods and whose data members are all public. struct may be used more efficiently in situations that default to public inheritance (which is the most common kind) and where public access (which is what you want if you list the public interface first) is the intended effect. Using a class, you typically have to insert the keyword public in two places, for no real advantage. In the end it's just a matter of convention, which programmers should be able to get used to.
- Point objects
As a simple example of a compound structure, consider the concept of a mathematical point. At one level, a point is two numbers (coordinates) that we treat collectively as a single object. In mathematical notation, points are often written in parentheses, with a comma separating the coordinates. For example, (0, 0) indicates the origin, and (x, y) indicates the point x units to the right and y units up from the origin.
The natural way to represent a point is using two doubles. The structure or struct is one of the solutions to group these two values into a compound object.
// A struct definition:
struct Point { double x, y; };
This definition indicates that this structure contains two members, named x and y. These members are also called instance variables, for reasons I will explain a little later.
It is a common error to leave off the semi-colon at the end of a structure definition. It might seem odd to put a semi-colon after a squiggly-brace, but you'll get used to it. This syntax is in place to allow the programmer the facility to create an instance[s] of the struct when it is defined.
Once you have defined the new structure, you can create variables with that type:
struct Point blank;
blank.x = 3.0;
blank.y = 4.0;
The first line is a conventional variable declaration: blank has type Point. The next two lines initialize the instance variables of the structure. The "dot notation" used here is similar to the syntax for invoking a function on an object, as in fruit.length(). Of course, one difference is that function names are always followed by an argument list, even if it is empty.
As usual, the name of the variable blank appears outside the box and its value appears inside the box. In this case, that value is a compound object with two named instance variables.
- Accessing instance variables
You can read the values of an instance variable using the same syntax we used to write them:
double x = blank.x;
The expression blank.x means "go to the object named blank and get the value of the member named x." In this case we assign that value to a local variable named x. Notice that there is no conflict between the local variable named x and the instance variable named x. The purpose of dot notation is to identify which variable you are referring to unambiguously.
You can use dot notation as part of any expression, so the following are legal.
cout << blank.x << ", " << blank.y << endl;
double distance = sqrt(blank.x * blank.x + blank.y * blank.y);
The first line outputs 3, 4; the second line calculates the value 5.
- Operations on structures
Most of the operators we have been using on other types, like mathematical operators ( +, %, etc.) and comparison operators (==, >, etc.), do not work on structures. Actually, it is possible to define the meaning of these operators for the new type, but we won't do that in this book.
On the other hand, the assignment operator does work for structures. It can be used in two ways: to initialize the instance variables of a structure or to copy the instance variables from one structure to another. An initialization looks like this:
Point blank = { 3.0, 4.0 };
The values in curly brackets get assigned to the instance variables of the structure one by one, in order. So in this case, x gets the first value and y gets the second.
Unfortunately, this syntax can be used only in an initialization, not in an assignment statement. Therefore, the following is illegal.
Point blank;
blank = { 3.0, 4.0 }; // WRONG !!
You might wonder why this perfectly reasonable statement should be illegal, and there is no good answer. (Note, however, that a similar syntax is legal in C since 1999, and is under consideration for possible inclusion in C++ in the future.)
On the other hand, it is legal to assign one structure to another. For example:
Point p1 = { 3.0, 4.0 };
Point p2 = p1;
cout << p2.x << ", " << p2.y << endl;
The output of this program is 3, 4
.
- Structures as function arguments and return types
You can write functions that take or return structures. For example, findCenter takes a Rectangle as an argument and returns a Point that contains the coordinates of the center of the Rectangle:
struct Rectangle {
Point corner;
double width, height;
};
Point findCenter (const Rectangle& box)
{
double x = box.corner.x + box.width/2;
double y = box.corner.y + box.height/2;
Point result = {x, y};
return result;
}
To call this function, we have to pass a Rectangle as an argument, and assign the return value to a Point variable:
Rectangle mybox = { {10.0, 0.0}, 100, 200 };
Point center = findCenter (mybox);
printPoint (center);
The output of this program is (60, 100).
Notice that the Rectangle is being passed to function findCenter by a reference (explained in chapter Functions), because this is more efficient than copying the whole structure what would be done on passing by value. The reference is declared constant, meaning that function findCenter will not modify the argument box, especially that mybox of the caller will remain unchanged.
- Pointers and structures
Structures can also be pointed by pointers and store pointers. The rules are the same as for any fundamental data type. The pointer must be declared as a pointer to the structure.
Nesting structures
[edit | edit source]Structures can also be nested so that a valid element of a structure can also be another structure.
//of course you have to define the Point struct first!
struct Rectangle {
Point upper_left;
Point upper_right;
Point lower_left;
Point lower_right;
};
this
[edit | edit source]The this keyword is an implicitly created pointer that is only accessible within nonstatic member functions of a struct (or a union or class) and points to the object for which the member function is called. This pointer is not available in static member functions. This will be restated again on when introducing unions a more in depth analysis is provided in the Section about classes.
union
[edit | edit source]The union keyword is used to define a union type.
- Syntax
union union-name
{
public-members-list;
private:
private-members-list;
} object-list;
Union is similar to struct
(more than class
), unions differ in the aspect that the fields of a union
share the same position in memory and are by default public
rather than private
. The size of the union
is the size of its largest field (or larger if alignment so requires, for example on a SPARC machine a union
contains a double
and a char [17]
so its size is likely to be 24 because it needs 64-bit alignment). Unions cannot have a destructor
.
What is the point of this? Unions provide multiple ways of viewing the same memory location, allowing for more efficient use of memory. Most of the uses of unions are covered by object-oriented features of C++, so it is more common in C. However, sometimes it is convenient to avoid the formalities of object-oriented programming when performance is important or when one knows that the item in question will not be extended.
union Data {
int i;
char c;
};
Writing to Different Bytes
[edit | edit source]Unions are very useful for low-level programming tasks that involve writing to the same memory area but at different portions of the allocated memory space, for instance:
union item
{
// The item is 16-bits
short theItem;
// In little-endian lo accesses the low 8-bits -
// hi, the upper 8-bits
struct { char lo; char hi; } portions;
};
item tItem;
tItem.theItem = 0xBEAD;
tItem.portions.lo = 0xEF; // The item now equals 0xBEEF
Using this union we can modify the low-order or high-order bytes of theItem without disturbing any other bytes.
Example in Practice: SDL Events
[edit | edit source]One real-life example of unions is the event system of SDL, a graphics library in C. In graphical programming, an event is an action triggered by the user, such as a mouse move or keyboard press. One of the SDL's responsibilities is to handle events and provide a mechanism for the programmer to listen for and react to them.
// primary event structure in SDL
typedef union
{
Uint8 type;
SDL_ActiveEvent active;
SDL_KeyboardEvent key;
SDL_MouseMotionEvent motion;
SDL_MouseButtonEvent button;
SDL_JoyAxisEvent jaxis;
SDL_JoyBallEvent jball;
SDL_JoyHatEvent jhat;
SDL_JoyButtonEvent jbutton;
SDL_ResizeEvent resize;
SDL_ExposeEvent expose;
SDL_QuitEvent quit;
SDL_UserEvent user;
SDL_SysWMEvent syswm;
} SDL_Event;
Each of the types other than Uint8 (an 8-bit unsigned
integer) is a struct with details for that particular event.
// SDL_MouseButtonEvent
typedef struct
{
Uint8 type;
Uint8 button;
Uint8 state;
Uint16 x, y;
} SDL_MouseButtonEvent;
When the programmer receives an event from SDL, he first checks the type value. This tells him what kind of an event it is. Based on this value, he either ignores the event or gets more information by getting the appropriate part of the union.
For example, if the programmer received an event in SDL_Event ev, he could react to mouse clicks with the following code.
if (ev.type == SDL_MOUSEBUTTONUP && ev.button.button == SDL_BUTTON_RIGHT)
{
cout << "You have right-clicked at coordinates (" << ev.button.x << ", "
<< ev.button.y << ")." << endl;
}
While identical functionality can be provided with a struct rather than a union, the union is far more space efficient; the struct would use memory for each of the different event types, whereas the union only uses memory for one. As only one entry has meaning per instance, it is reasonable to use a union in this case.
This scheme could also be constructed with polymorphism and inheritance features of object-oriented C++, however the setup would be involved and less efficient than this one. Use of unions loses type safety, however it gains in performance.
this
[edit | edit source]The this keyword is a implicitly created pointer that is only accessible within nonstatic member functions of a union (or a struct or class ) and points to the object for which the member function is called. The this pointer is not available in static member functions. This will be restated again on when introducing unions a more in depth analysis is provided in the Section about classes.
Classes
[edit | edit source]Classes are used to create user defined types. An instance of a class is called an object and programs can contain any number of classes. As with other types, object types are case-sensitive.
Classes provide encapsulation as defined in the Object Oriented Programming (OOP) paradigm. A class can have both data members and functions members associated with it. Unlike the built-in types, the class can contain several variables and functions, those are called members.
Classes also provide flexibility in the "divide and conquer" scheme in program writing. In other words, one programmer can write a class and guarantee an interface. Another programmer can write the main program with that expected interface. The two pieces are put together and compiled for usage.
Declaration
[edit | edit source]A class is defined by:
class MyClass
{
/* public, protected and private
variables, constants, and functions */
};
An object of type MyClass (case-sensitive) is declared using:
MyClass object;
- by default, all class members are initially private.
- keywords public and protected allow access to class members.
- classes contain not only data members, but also functions to manipulate that data.
- a class is used as the basic building block of OOP (this is a distinction of convention, not of language-enforced semantics).
- A class can be created
- before main() is called.
- when a function is called in which the object is declared.
- when the "new" operator is used.
- Class Names
- Name the class after what it is. If you can't determine a name, then you have not designed the system well enough.
- Compound names of over three words are a clue your design may be confusing various entities in your system. Revisit your design. Try a CRC card session to see if your objects have more responsibilities than they should.
- Avoid the temptation of naming a class something similar to the class it is derived from. A class should stand on its own. Declaring an object with a class type doesn't depend on where that class is derived from.
- Suffixes or prefixes are sometimes helpful. For example, if your system uses agents then naming something DownloadAgent conveys real information.
- Data Abstraction
A fundamental concept of Object Oriented (OO) recommends an object should not expose any of its implementation details. This way, you can change the implementation without changing the code that uses the object. The class, by design, allows its programmer to hide (and also prevents changes as to) how the class is implemented. This powerful tool allows the programmer to build in a 'preventive' measure. Variables within the class often have a very significant role in what the class does, therefore variables can be secured within the private section of the class.
Access labels
[edit | edit source]The access labels Public, Protected and Private are used within classes to set access permissions for the members in that section of the class. All class members are initially private by default. The labels can be in any order. These labels can be used multiple times in a class declaration for cases where it is logical to have multiple groups of these types. An access label will remain active until another access label is used to change the permissions.
We have already mentioned that a class can have member functions "inside" it; we will see more about them later. Those member functions can access and modify all the data and member function that are inside the class. Therefore, permission labels are to restrict access to member function that reside outside the class and for other classes.
For example, a class "Bottle" could have a private variable fill, indicating a liquid level 0-3 dl. fill cannot be modified directly (compiler error), but instead Bottle provides the member function sip() to reduce the liquid level by 1. Mywaterbottle could be an instance of that class, an object.
/* Bottle - Class and Object Example */
#include <iostream>
#include <iomanip>
using namespace std;
class Bottle
{
private: // variables are modified by member functions of class
int iFill; // dl of liquid
public:
Bottle() // Default Constructor
: iFill(3) // They start with 3 dl of liquid
{
// More constructor code would go here if needed.
}
bool sip() // return true if liquid was available
{
if (iFill > 0)
{
--iFill;
return true;
}
else
{
return false;
}
}
int level() const // return level of liquid dl
{
return iFill;
}
}; // Class declaration has a trailing semicolon
int main()
{
// terosbottle object is an instance of class Bottle
Bottle terosbottle;
cout << "In the beginning, mybottle has "
<< terosbottle.level()
<< " dl of liquid"
<< endl;
while (terosbottle.sip())
{
cout << "Mybottle has "
<< terosbottle.level()
<< " dl of liquid"
<< endl;
}
return 0;
}
These keywords, private, public, and protected, affect the permissions of the members—whether functions or variables.
public
[edit | edit source]This label indicates any members within the 'public' section can be accessed freely anywhere a declared object is in scope.
private
[edit | edit source]Members defined as private are only accessible within the class defining them, or friend classes. Usually the domain of member variables and helper functions. It's often useful to begin putting functions here and then moving them to the higher access levels as needed so to reduce complexity.
(This is an example where the default copy constructor will do the same thing.)
class Foo
{
public:
Foo(const Foo &f)
{
m_iValue = f.m_iValue; // perfectly legal
}
private:
int m_iValue;
};
protected
[edit | edit source]The protected label has a special meaning to inheritance, protected members are accessible in the class that defines them and in classes that inherit from that base class, or friends of it. In the section on inheritance we will see more about it.
Inheritance (Derivation)
[edit | edit source]As seen early when introducing the programming paradigms, inheritance is a property that describes a relationship between two (or more) types or classes, of objects. It is a characteristic of OOP, and in C++, classes share this property.
Derivation is the action of creating a new class using the inheritance property. It is possible to derive one class from another or even several (Multiple inheritance), like a tree we can call base class to the root and child class to any leaf; in any other case the parent/child relation will exist for each class derived from another.
- Base Class
A base class is a class that is created with the intention of deriving other classes from it.
- Child Class
A child class is a class that was derived from another, that will now be the parent class to it.
- Parent Class
A parent class is the closest class that we derived from to create the one we are referencing as the child class.
As an example, suppose you are creating a game, something using different cars, and you need specific type of car for the policemen and another type for the player(s). Both car types share similar properties. The major difference (on this example case) would be that the policemen type would have sirens on top of their cars and the players' cars will not.
One way of getting the cars for the policemen and the player ready is to create separate classes for policemen's car and for the player's car like this:
class PlayerCar {
private:
int color;
public:
void driveAtFullSpeed(int mph){
// code for moving the car ahead
}
};
class PoliceCar {
private:
int color;
bool sirenOn; // identifies whether the siren is on or not
bool inAction; // identifies whether the police is in action (following the player) or not
public:
bool isInAction(){
return this->inAction;
}
void driveAtFullSpeed(int mph){
// code for moving the car ahead
}
};
and then creating separate objects for the two cars like this:
PlayerCar player1;
PoliceCar policemen1;
So, except for one thing that you can easily notice: there are certain parts of code that are very similar (if not exactly the same) in the above two classes. In essence, you have to type in the same code at two different locations! And when you update your code to include methods (functions) for handBrake()
and pressHorn()
, you'll have to do that in both the classes above.
Therefore, to escape this frustrating (and confusing) task of writing the same code at multiple locations in a single project, you use Inheritance.
Now that you know what kind of problems Inheritance solves in C++, let us examine how to implement Inheritance in our programs. As its name suggests, Inheritance lets us create new classes which automatically have all the code from existing classes. It means that if there is a class called MyClass
, a new class with the name MyNewClass
can be created which will have all the code present inside the MyClass
class. The following code segment shows it all:
class MyClass {
protected:
int age;
public:
void sayAge(){
this->age = 20;
cout << age;
}
};
class MyNewClass : public MyClass {
};
int main() {
MyNewClass *a = new MyNewClass();
a->sayAge();
return 0;
}
As you can see, using the colon ':' we can inherit a new class out of an existing one. It’s that simple! All the code inside the MyClass
class is now available to the MyNewClass
class. And if you are intelligent enough, you can already see the advantages it provides. If you are like me (i.e. not too intelligent), you can see the following code segment to know what I mean:
class Car {
protected:
int color;
int currentSpeed;
int maxSpeed;
public:
void applyHandBrake(){
this->currentSpeed = 0;
}
void pressHorn(){
cout << "Teeeeeeeeeeeeent"; // funny noise for a horn
}
void driveAtFullSpeed(int mph){
// code for moving the car ahead;
}
};
class PlayerCar : public Car {
};
class PoliceCar : public Car {
private:
bool sirenOn; // identifies whether the siren is on or not
bool inAction; // identifies whether the police is in action (following the player) or not
public:
bool isInAction(){
return this->inAction;
}
};
In the code above, the two newly created classes PlayerCar and PoliceCar have been inherited from the Car class. Therefore, all the methods and properties (variables) from the Car class are available to the newly created classes for the player's car and the policemen's car. Technically speaking, in C++, the Car class in this case is our "Base Class" since this is the class which the other two classes are based on (or inherit from).
Just one more thing to note here is the keyword protected instead of the usual private keyword. That’s no big deal: We use protected when we want to make sure that the variables we define in our base class should be available in the classes that inherit from that base class. If you use private in the class definition of the Car class, you will not be able to inherit those variables inside your inherited classes.
There are three types of class inheritance: public, private and protected. We use the keyword public to implement public inheritance. The classes who inherit with the keyword public from a base class, inherit all the public members as public members, the protected data is inherited as protected data and the private data is inherited but it cannot be accessed directly by the class.
The following example shows the class Circle that inherits "publicly" from the base class Form:
class Form {
private:
double area;
public:
int color;
double getArea(){
return this->area;
}
void setArea(double area){
this->area = area;
}
};
class Circle : public Form {
public:
double getRatio() {
double a;
a = getArea();
return sqrt(a / 2 * 3.14);
}
void setRatio(double diameter) {
setArea( pow(diameter * 0.5, 2) * 3.14 );
}
bool isDark() {
return (color > 10);
}
};
The new class Circle inherits the attribute area from the base class Form (the attribute area is implicitly an attribute of the class Circle), but it cannot access it directly. It does so through the functions getArea and setArea (that are public in the base class and remain public in the derived class). The color attribute, however, is inherited as a public attribute, and the class can access it directly.
The following table indicates how the attributes are inherited in the three different types of inheritance:
private | protected | public | |
private inheritance | The member is inaccessible. | The member is private. | The member is private. |
protected inheritance | The member is inaccessible. | The member is protected. | The member is protected. |
public inheritance | The member is inaccessible. | The member is protected. | The member is public. |
As the table above shows, protected members are inherited as protected methods in public inheritance. Therefore, we should use the protected label whenever we want to declare a method inaccessible outside the class and not to lose access to it in derived classes. However, losing accessibility can be useful sometimes, because we are encapsulating details in the base class.
Let us imagine that we have a class with a very complex method "m" that invokes many auxiliary methods declared as private in the class. If we derive a class from it, we should not bother about those methods because they are inaccessible in the derived class. If a different programmer is in charge of the design of the derived class, allowing access to those methods could be the cause of errors and confusion. So, it is a good idea to avoid the protected label whenever we can have a design with the same result with the private label.
Now one more additional "syntax trick". If the base / parent class has a constructor which requires parameters, we are in trouble, you may think. Of course calling constructors directly is forbidden, but we have a special syntax for this purpose. The way, is just so that when you define the constructor of the delivered class, you call the parent constructor like this:
ChildClass::ChildClass(int a, int b) : ParentClass(a, b)
{
//Child constructor here
}
Multiple inheritance
[edit | edit source]Multiple inheritance allows the construction of classes that inherit from more than one type or class. This contrasts with single inheritance, where a class will only inherit from one type or class.
Multiple inheritance can cause some confusing situations, and is much more complex than single inheritance, so there is some debate over whether or not its benefits outweigh its risks. Multiple inheritance has been a touchy issue for many years, with opponents pointing to its increased complexity and ambiguity in situations such as the "diamond problem". Most modern OOP languages do not allow multiple inheritance.
The declared order of derivation is relevant for determining the order of default initialization by constructors and destructors cleanup.
class One
{
// class internals
};
class Two
{
// class internals
};
class MultipleInheritance : public One, public Two
{
// class internals
};
Data members
[edit | edit source]Data members are declared in the same way as a global or function variable, but as part of the class definition. Their purpose is to store information for that class and may include members of any type, even other user-defined types. They are usually hidden from outside use, depending on the coding style adopted, external use is normally done through special member functions.
this pointer
[edit | edit source]The this keyword acts as a pointer to the class being referenced. The this pointer acts like any other pointer, although you can't change the pointer itself. Read the section concerning pointers and references to understand more about general pointers.
The this pointer is only accessible within nonstatic member functions of a class, union or struct, and is not available in static member functions. It is not necessary to write code for the this pointer as the compiler does this implicitly. When using a debugger, you can see the this pointer in some variable list when the program steps into nonstatic class functions.
In the following example, the compiler inserts an implicit parameter this in the nonstatic member function int getData(). Additionally, the code initiating the call passes an implicit parameter (provided by the compiler).
class Foo
{
private:
int iX;
public:
Foo(){ iX = 5; };
int getData()
{
return this->iX; // this is provided by the compiler at compile time
}
};
int main()
{
Foo Example;
int iTemp;
iTemp = Example.getData(&Example); // compiler adds the &Example reference at compile time
return 0;
}
There are certain times when a programmer should know about and use the this pointer. The this pointer should be used when overloading the assignment operator to prevent a catastrophe. For example, add in an assignment operator to the code above.
class Foo
{
private:
int iX;
public:
Foo() { iX = 5; };
int getData()
{
return iX;
}
Foo& operator=(const Foo &RHS);
};
Foo& Foo::operator=(const Foo &RHS)
{
if(this != &RHS)
{ // the if this test prevents an object from copying to itself (ie. RHS = RHS;)
this->iX = RHS.iX; // this is suitable for this class, but can be more complex when
// copying an object in a different much larger class
}
return (*this); // returning an object allows chaining, like a = b = c; statements
}
However little you may know about this, it is important in implementing any class.
static data member
[edit | edit source]The use of the static
specifier in a data member, will cause that member to be shared by all instances of the owner class and derived classes. To use static data members you must declare the data member as static and initialize it outside of the class declaration, at file scope.
When used in a class data member, all instantiations of that class share one copy of the variable.
class Foo {
public:
Foo() {
++iNumFoos;
cout << "We have now created " << iNumFoos << " instances of the Foo class\n";
}
private:
static int iNumFoos;
};
int Foo::iNumFoos = 0; // allocate memory for numFoos, and initialize it
int main() {
Foo f1;
Foo f2;
Foo f3;
}
In the example above, the static class variable numFoos is shared between all three instances of the Foo class (f1, f2 and f3) and keeps a count of the number of times that the Foo class has been instantiated.
Member Functions
[edit | edit source]Member functions can (and should) be used to interact with data contained within user defined types. User defined types provide flexibility in the "divide and conquer" scheme in program writing. In other words, one programmer can write a user defined type and guarantee an interface. Another programmer can write the main program with that expected interface. The two pieces are put together and compiled for usage. User defined types provide encapsulation defined in the Object Oriented Programming (OOP) paradigm.
Within classes, to protect the data members, the programmer can define functions to perform the operations on those data members. Member functions and functions are names used interchangeably in reference to classes. Function prototypes are declared within the class definition. These prototypes can take the form of non-class functions as well as class suitable prototypes. Functions can be declared and defined within the class definition. However, most functions can have very large definitions and make the class very unreadable. Therefore it is possible to define the function outside of the class definition using the scope resolution operator "::". This scope resolution operator allows a programmer to define the functions somewhere else. This can allow the programmer to provide a header file .h defining the class and a .obj file built from the compiled .cpp file which contains the function definitions. This can hide the implementation and prevent tampering. The user would have to define every function again to change the implementation. Functions within classes can access and modify (unless the function is constant) data members without declaring them, because the data members are already declared in the class.
Simple example:
file: Foo.h
// the header file named the same as the class helps locate classes within a project
// one class per header file makes it easier to keep the
// header file readable (some classes can become large)
// each programmer should determine what style works for them or what programming standards their
// teacher/professor/employer has
#ifndef FOO_H
#define FOO_H
class Foo{
public:
Foo(); // function called the default constructor
Foo( int a, int b ); // function called the overloaded constructor
int Manipulate( int g, int h );
private:
int x;
int y;
};
#endif
file: Foo.cpp
#include "Foo.h"
/* these constructors should really show use of initialization lists
Foo::Foo() : x(5), y(10)
{
}
Foo::Foo(int a, int b) : x(a), y(b)
{
}
*/
Foo::Foo(){
x = 5;
y = 10;
}
Foo::Foo( int a, int b ){
x = a;
y = b;
}
int Foo::Manipulate( int g, int h ){
x = h + g*x;
y = g + h*y;
}
Overloading
[edit | edit source]Member functions can be overloaded. This means that multiple member functions can exist with the same name on the same scope, but must have different signatures. A member function's signature is comprised of the member function's name and the type and order of the member function's parameters.
Due to name hiding, if a member in the derived class shares the same name with members of the base class, they will be hidden to the compiler. To make those members visible, one can use declarations to introduce them from base class scopes.
Constructors and other class member functions, except the Destructor, can be overloaded.
Constructors
[edit | edit source]A constructor is a special member function that is called whenever a new instance of a class is created. The compiler calls the constructor after the new object has been allocated in memory, and converts that "raw" memory into a proper, typed object. The constructor is declared much like a normal member function but it will share the name of the class and it has no return value.
Constructors are responsible for almost all of the run-time setup necessary for the class operation. Its main purpose becomes in general defining the data members upon object instantiation (when an object is declared), they can also have arguments, if the programmer so chooses. If a constructor has arguments, then they should also be added to the declaration of any other object of that class when using the new operator. Constructors can also be overloaded.
Foo myTest; // essentially what happens is: Foo myTest = Foo();
Foo myTest( 3, 54 ); // accessing the overloaded constructor
Foo myTest = Foo( 20, 45 ); // although a new object is created, there are some extra function calls involved
// with more complex classes, an assignment operator should
// be defined to ensure a proper copy (includes ''deep copy'')
// myTest would be constructed with the default constructor, and then the
// assignment operator copies the unnamed Foo( 20, 45 ) object to myTest
using new with a constructor
Foo* myTest = new Foo(); // this defines a pointer to a dynamically allocated object
Foo* myTest = new Foo( 40, 34 ); // constructed with Foo( 40, 34 )
// be sure to use delete to avoid memory leaks
A constructor can delegate to another (introduced in C++ 11). It is also considered desirable to reduce the use of default arguments, if a maintainer has to write and maintain multiple constructors it can result in code duplication, which reduces maintainability because of the potential for introducing inconsistencies and even lead to code bloat.
- Default Constructors
A default constructor is one which can be called with no arguments. Most commonly, a default constructor is declared without any parameters, but it is also possible for a constructor with parameters to be a default constructor if all of those parameters are given default values.
In order to create an array of objects of a class type, the class must have an accessible default constructor; C++ has no syntax to specify constructor arguments for array elements.
Overloaded Constructors
[edit | edit source]When an object of a class is instantiated, the class writer can provide various constructors each with a different purpose. A large class would have many data members, some of which may or may not be defined when an object is instantiated. Anyway, each project will vary, so a programmer should investigate various possibilities when providing constructors.
These are all constructors for a class myFoo.
myFoo(); // default constructor, the user has no control over initial values
// overloaded constructors
myFoo( int a, int b=0 ); // allows construction with a certain 'a' value, but accepts 'b' as 0
// or allows the user to provide both 'a' and 'b' values
// or
myFoo( int a, int b ); // overloaded constructor, the user must specify both values
class myFoo {
private:
int Useful1;
int Useful2;
public:
myFoo(){ // default constructor
Useful1 = 5;
Useful2 = 10;
};
myFoo( int a, int b = 0 ) { // two possible cases when invoked
Useful1 = a;
Useful2 = b;
};
};
myFoo Find; // default constructor, private member values Useful1 = 5, Useful2 = 10
myFoo Find( 8 ); // overloaded constructor case 1, private member values Useful1 = 8, Useful2 = 0
myFoo Find( 8, 256 ); // overloaded constructor case 2, private member values Useful1 = 8, Useful2 = 256
Constructor initialization lists
[edit | edit source]Constructor initialization lists (or member initialization list) are the only way to initialize data members and base classes with a non-default constructor. Constructors for the members are included between the argument list and the body of the constructor (separated from the argument list by a colon). Using the initialization lists is not only better in terms of efficiency but also the simplest way to guarantee that all initialization of data members are done before entering the body of constructors.
// Using the initialization list for myComplexMember_
MyClass::MyClass(int mySimpleMember, MyComplexClass myComplexMember)
: myComplexMember_(myComplexMember) // only 1 call, to the copy constructor
{
mySimpleMember_=mySimpleMember; // uses 2 calls, one for the constructor of the mySimpleMember class
// and a second for the assignment operator of the MyComplexClass class
}
This is more efficient than assigning value to the complex data member inside the body of the constructor because in that case the variable is initialized with its corresponding constructor.
Note that the arguments provided to the constructors of the members do not need to be arguments to the constructor of the class; they can also be constants. Therefore you can create a default constructor for a class containing a member with no default constructor.
Example:
MyClass::MyClass() : myComplexMember_(0) { }
It is useful to initialize your members in the constructor using this initialization lists. This makes it obvious for the reader that the constructor does not execute logic. The order the initialization is done should be the same as you defined your base-classes and members. Otherwise you can get warnings at compile-time. Once you start initializing your members make sure to keep all in the constructor(s) to avoid confusion and possible 0xbaadfood.
It is safe to use constructor parameters that are named like members.
Example:
class MyClass : public MyBaseClassA, public MyBaseClassB {
private:
int c;
void *pointerMember;
public:
MyClass(int,int,int);
};
/*...*/
MyClass::MyClass(int a, int b, int c):
MyBaseClassA(a)
,MyBaseClassB(b)
,c(c)
,pointerMember(NULL)
,referenceMember()
{
//logic
}
Note that this technique was also possible for normal functions but it is now obsoleted and is classified as an error in such case.
Destructors
[edit | edit source]Destructors like the Constructors are declared as any normal member functions but will share the same name as the Class, what distinguishes them is that the Destructor's name is preceded with a "~", it can not have arguments and can't be overloaded.
Destructors are called whenever an Object of the Class is destroyed. Destructors are crucial in avoiding resource leaks (by deallocating memory), and in implementing the RAII idiom. Resources which are allocated in a Constructor of a Class are usually released in the Destructor of that Class as to return the system to some known or stable state after the Class ceases to exist.
The Destructor is invoked when Objects are destroyed, after the function they were declared in returns, when the delete operator is used or when the program is over. If an object of a derived type is destructed, first the Destructor of the most derived object is executed. Then member objects and base class subjects are destructed recursively, in the reverse order their corresponding Constructors completed. As with structs the compiler implicitly declares a Destructor as an inline public member of its class if the class doesn’t have a user-declared Destructor.
The dynamic type of the object will change from the most derived type as Destructors run, symmetrically to how it changes as Constructors execute. This affects the functions called by virtual calls during construction and destruction, and leads to the common (and reasonable) advice to avoid calling virtual functions of an object either directly or indirectly from its Constructors or Destructors.
Sharing most of the concepts we have seen before on the introduction to inline functions, when dealing with member function those concepts are extended, with a few additional considerations.
If the member functions definition is included inside the declaration of the class, that function is by default made implicitly inline. Compiler options may override this behavior.
Calls to virtual functions cannot be inlined if the object's type is not known at compile-time, because we don't know which function to inline.
The static keyword can be used in four different ways:
- to create permanent storage for local variables in a function.
- to specify internal linkage.
- to declare member functions that act like non-member functions.
- to create a single copy of a data member.
static member function
[edit | edit source]Member functions or variables declared static are shared between all instances of an object type. Meaning that only one copy of the member function or variable does exists for any object type.
- member functions callable without an object
When used in a class function member, the function does not take an instantiation as an implicit this
parameter, instead behaving like a free function. This means that static class functions can be called without creating instances of the class:
class Foo {
public:
Foo() {
++numFoos;
cout << "We have now created " << numFoos << " instances of the Foo class\n";
}
static int getNumFoos() {
return numFoos;
}
private:
static int numFoos;
};
int Foo::numFoos = 0; // allocate memory for numFoos, and initialize it
int main() {
Foo f1;
Foo f2;
Foo f3;
cout << "So far, we've made " << Foo::getNumFoos() << " instances of the Foo class\n";
}
Named constructors
[edit | edit source]Named constructors are a good example of using static member functions. Named constructors is the name given to functions used to create an object of a class without (directly) using its constructors. This might be used for the following:
- To circumvent the restriction that constructors can be overloaded only if their signatures differ.
- Making the class non-inheritable by making the constructors private.
- Preventing stack allocation by making constructors private
Declare a static member function that uses a private constructor to create the object and return it. (It could also return a pointer or a reference but this complication seems useless, and turns this into the factory pattern rather than a conventional named constructor.)
Here's an example for a class that stores a temperature that can be specified in any of the different temperature scales.
class Temperature
{
public:
static Temperature Fahrenheit (double f);
static Temperature Celsius (double c);
static Temperature Kelvin (double k);
private:
Temperature (double temp);
double _temp;
};
Temperature::Temperature (double temp):_temp (temp) {}
Temperature Temperature::Fahrenheit (double f)
{
return Temperature ((f + 459.67) / 1.8);
}
Temperature Temperature::Celsius (double c)
{
return Temperature (c + 273.15);
}
Temperature Temperature::Kelvin (double k)
{
return Temperature (k);
}
const
[edit | edit source]This type of member function cannot modify the member variables of a class. It's a hint both to the programmer and the compiler that a given member function doesn't change the internal state of a class; however, any variables declared as mutable can still be modified.
Take for example:
class Foo
{
public:
int value() const
{
return m_value;
}
void setValue( int i )
{
m_value = i;
}
private:
int m_value;
};
Here value() clearly does not change m_value and as such can and should be const. However setValue() does modify m_value and as such cannot be const.
Another subtlety often missed is a const member function cannot call a non-const member function (and the compiler will complain if you try). The const member function cannot change member variables and a non-const member functions can change member variables. Since we assume non-const member functions do change member variables, const member functions are assumed to never change member variables and can't call functions that do change member variables.
The following code example explains what const can do depending on where it is placed.
class Foo
{
public:
/*
* Modifies m_widget and the user
* may modify the returned widget.
*/
Widget *widget();
/*
* Does not modify m_widget but the
* user may modify the returned widget.
*/
Widget *widget() const;
/*
* Modifies m_widget, but the user
* may not modify the returned widget.
*/
const Widget *cWidget();
/*
* Does not modify m_widget and the user
* may not modify the returned widget.
*/
const Widget *cWidget() const;
private:
Widget *m_widget;
};
Accessors and Modifiers (Setter/Getter)
[edit | edit source]- What is an accessor?
- An accessor is a member function that does not modify the state of an object. The accessor functions should be declared as const.
- Getter is another common definition of an accessor due to the naming ( GetSize() ) of that type of member functions.
- What is a modifier?
- A modifier, also called a modifying function, is a member function that changes the value of at least one data member. In other words, an operation that modifies the state of an object. Modifiers are also known as ‘mutators’.
- Setter is another common definition of a modifier due to the naming ( SetSize( int a_Size ) ) of that type of member functions.
Dynamic polymorphism (Overrides)
[edit | edit source]So far, we have learned that we can add new data and functions to a class through inheritance. But what about if we want our derived class to inherit a method from the base class, but to have a different implementation for it? That is when we are talking about polymorphism, a fundamental concept in OOP programming.
As seen previously in the Programming Paradigms Section, Polymorphism is subdivided in two concepts static polymorphism and dynamic polymorphism. This section concentrates on dynamic polymorphism, which applies in C++ when a derived class overrides a function declared in a base class.
We implement this concept redefining the method in the derived class. However, we need to have some considerations when we do this, so now we must introduce the concepts of dynamic binding, static binding and virtual methods.
Suppose that we have two classes, A
and B
. B
derives from A
and redefines the implementation of a method c()
that resides in class A
. Now suppose that we have an object b
of class B
. How should the instruction b.c()
be interpreted?
If b
is declared in the stack (not declared as a pointer or a reference) the compiler applies static binding, this means it interprets (at compile time) that we refer to the implementation of c()
that resides in B
.
However, if we declare b
as a pointer or a reference of class A
, the compiler could not know which method to call at compile time, because b
can be of type A
or B
. If this is resolved at run time, the method that resides in B
will be called. This is called dynamic binding. If this is resolved at compile time, the method that resides in A
will be called. This is again, static binding.
Virtual member functions
[edit | edit source]The virtual member functions is relatively simple, but often misunderstood. The concept is an essential part of designing a class hierarchy in regards to sub-classing classes as it determines the behavior of overridden methods in certain contexts.
Virtual member functions are class member functions, that can be overridden in any class derived from the one where they were declared. The member function body is then replaced with a new set of implementation in the derived class.
By placing the keyword virtual before a method declaration we are indicating that when the compiler has to decide between applying static binding or dynamic binding it will apply dynamic binding. Otherwise, static binding will be applied.
Again, this should be clearer with an example:
class Foo
{
public:
void f()
{
std::cout << "Foo::f()" << std::endl;
}
virtual void g()
{
std::cout << "Foo::g()" << std::endl;
}
};
class Bar : public Foo
{
public:
void f()
{
std::cout << "Bar::f()" << std::endl;
}
virtual void g()
{
std::cout << "Bar::g()" << std::endl;
}
};
int main()
{
Foo foo;
Bar bar;
Foo *baz = &bar;
Bar *quux = &bar;
foo.f(); // "Foo::f()"
foo.g(); // "Foo::g()"
bar.f(); // "Bar::f()"
bar.g(); // "Bar::g()"
// So far everything we would expect...
baz->f(); // "Foo::f()"
baz->g(); // "Bar::g()"
quux->f(); // "Bar::f()"
quux->g(); // "Bar::g()"
return 0;
}
Our first calls to f() and g() on the two objects are straightforward. However things get interesting with our baz pointer which is a pointer to the Foo type.
f() is not virtual and as such a call to f() will always invoke the implementation associated with the pointer type—in this case the implementation from Foo.
Virtual function calls are computationally more expensive than regular function calls. Virtual functions use pointer indirection, invocation and will require a few extra instructions than normal member functions. They also require that the constructor of any class/structure containing virtual functions to initialize a table of pointers to its virtual member functions.
All this characteristics will signify a trade-off between performance and design. One should avoid preemptively declaring functions virtual without an existing structural need. Keep in mind that virtual functions that are only resolved at run-time cannot be inlined.
Pure virtual member function
[edit | edit source]There is one additional interesting possibility. Sometimes we don't want to provide an implementation of our function at all, but want to require people sub-classing our class to provide an implementation on their own. This is the case for pure virtuals.
To indicate a pure virtual function instead of an implementation we simply add an "= 0" after the function declaration.
Again—an example:
class Widget
{
public:
virtual void paint() = 0;
};
class Button : public Widget
{
public:
void paint() // is virtual because it is an override
{
// do some stuff to draw a button
}
};
Because paint() is a pure virtual function in the Widget class we are required to provide an implementation in all concrete subclasses. If we don't the compiler will give us an error at build time.
This is helpful for providing interfaces—things that we expect from all of the objects based on a certain hierarchy, but when we want to ignore the implementation details.
- So why is this useful?
Let's take our example from above where we had a pure virtual for painting. There are a lot of cases where we want to be able to do things with widgets without worrying about what kind of widget it is. Painting is an easy example.
Imagine that we have something in our application that repaints widgets when they become active. It would just work with pointers to widgets—i.e. Widget *activeWidget() const might be a possible function signature. So we might do something like:
Widget *w = window->activeWidget();
w->paint();
We want to actually call the appropriate paint member function for the "real" widget type—not Widget::paint() (which is a "pure" virtual and will cause the program to crash if called using virtual dispatch). By using a virtual function we insure that the member function implementation for our subclass -- Button::paint() in this case—will be called.
Covariant return types
[edit | edit source]Covariant return types is the ability for a virtual function in a derived class to return a pointer or reference to an instance of itself if the version of the method in the base class does so. e.g.
class base
{
public:
virtual base* create() const;
};
class derived : public base
{
public:
virtual derived* create() const;
};
This allows casting to be avoided.
virtual Constructors
[edit | edit source]There is a hierarchy of classes with base class Foo. Given an object bar belonging in the hierarchy, it is desired to be able to do the following:
- Create an object baz of the same class as bar (say, class Bar) initialized using the default constructor of the class. The syntax normally used is:
- Bar* baz = bar.create();
- Create an object baz of the same class as bar which is a copy of bar. The syntax normally used is:
- Bar* baz = bar.clone();
In the class Foo, the methods Foo::create() and Foo::clone() are declared as follows:
class Foo
{
// ...
public:
// Virtual default constructor
virtual Foo* create() const;
// Virtual copy constructor
virtual Foo* clone() const;
};
If Foo is to be used as an abstract class, the functions may be made pure virtual:
class Foo
{
// ...
public:
virtual Foo* create() const = 0;
virtual Foo* clone() const = 0;
};
In order to support the creation of a default-initialized object, and the creation of a copy object, each class Bar in the hierarchy must have public default and copy constructors. The virtual constructors of Bar are defined as follows:
class Bar : ... // Bar is a descendant of Foo
{
// ...
public:
// Non-virtual default constructor
Bar ();
// Non-virtual copy constructor
Bar (const Bar&);
// Virtual default constructor, inline implementation
Bar* create() const { return new Foo (); }
// Virtual copy constructor, inline implementation
Bar* clone() const { return new Foo (*this); }
};
The above code uses covariant return types. If your compiler doesn't support Bar* Bar::create(), use Foo* Bar::create() instead, and similarly for clone().
While using these virtual constructors, you must manually deallocate the object created by calling delete baz;. This hassle could be avoided if a smart pointer (e.g. std::unique_ptr<Foo>) is used in the return type instead of the plain old Foo*.
Remember that whether or not Foo uses dynamically allocated memory, you must define the destructor virtual ~Foo () and make it virtual to take care of deallocation of objects using pointers to an ancestral type.
virtual Destructor
[edit | edit source]It is of special importance to remember to define a virtual destructor even if empty in any base class, since failing to do so will create problems with the default compiler generated destructor that will not be virtual.
A virtual destructor is not overridden when redefined in a derived class, the definitions to each destructor are cumulative and they start from the last derivate class toward the first base class.
Pure virtual Destructor
[edit | edit source]Every abstract class should contain the declaration of a pure virtual destructor.
Pure virtual destructors are a special case of pure virtual functions (meant to be overridden in a derived class). They must always be defined and that definition should always be empty.
class Interface {
public:
virtual ~Interface() = 0; //declaration of a pure virtual destructor
};
Interface::~Interface(){} //pure virtual destructor definition (should always be empty)
Law of three
[edit | edit source]The "law of three" is not really a law, but rather a guideline: if a class needs an explicitly declared copy constructor, copy assignment operator, or destructor, then it usually needs all three.
There are exceptions to this rule (or, to look at it another way, refinements). For example, sometimes a destructor is explicitly declared just in order to make it virtual
; in that case there's not necessarily a need to declare or implement the copy constructor and copy assignment operator.
Most classes should not declare any of the "big three" operations; classes that manage resources generally need all three.
Subsumption property
[edit | edit source]Subsumption is a property that all objects that reside in a class hierarchy must fulfill: an object of the base class can be substituted by an object that derives from it (directly or indirectly). All mammals are animals (they derive from them), and all cats are mammals. Therefore, because of the subsumption property we can "treat" any mammal as an animal and any cat as a mammal. This implies abstraction, because when we are "treating" a mammal as an animal, the only information we should know about it is that it lives, it grows, etc, but nothing related to mammals.
This property is applied in C++, whenever we are using pointers or references to objects that reside in a class hierarchy. In other words, a pointer of class animal can point to an object of class animal, mammal or cat.
Let's continue with our example:
//needs to be corrected
enum AnimalType {
Herbivore,
Carnivore,
Omnivore,
};
class Animal {
public:
AnimalType Type;
bool bIsAlive;
int iNumberOfChildren;
};
class Mammal : public Animal{
public:
int iNumberOfTeats;
};
class Cat : public Mammal{
public:
bool bLikesFish; // probably true
};
int main() {
Animal* pA1 = new Animal;
Animal* pA2 = new Mammal;
Animal* pA3 = new Cat;
Mammal* pM = new Cat;
pA2->bIsAlive = true; // Correct
pA2->Type = Herbivore; // Correct
pM->iNumberOfTeats = 2; // Correct
pA2->iNumberOfTeats = 6; // Incorrect
pA3->bLikesFish = true; // Incorrect
Cat* pC = (Cat*)pA3; // Downcast, correct (but very poor practice, see later)
pC->bLikesFish = false; // Correct (although it is a very awkward cat)
}
In the last lines of the example there is cast of a pointer to Animal, to a pointer to Cat. This is called "Downcast". Downcasts are useful and should be used, but first we must ensure that the object we are casting is really of the type we are casting to it. Downcasting a base class to an unrelated class is an error. To resolve this issue, the casting operators dynamic_cast
<>, or static_cast
<> should be used.
These correctly cast an object from one class to another, and will throw an exception if the class types are not related. eg. If you try:
Cat* pC = new Cat;
motorbike* pM = dynamic_cast<motorbike*>(pC);
Then, the app will throw an exception, as a cat is not a motorbike. Static_cast is very similar, only it will perform the type checking at compile time. If you have an object where you are not sure of its type then you should use dynamic_cast
, and be prepared to handle errors when casting. If you are downcasting objects where you know the types, then you should use static_cast
. Do not use old-style C casts as these will simply give you an access violation if the types cast are unrelated.
Local classes
[edit | edit source]A local class is any class that is defined inside a specific statement block, in a local scope, for instance inside a function. This is done like defining any other class, but local classes can not however access non-static local variables or be used to define static data members. These type of classes are useful especially in template functions, as we will see later.
void MyFunction()
{
class LocalClass
{
// ... members definitions ...
};
// ... any code that needs the class ...
}
User defined automatic type conversion
[edit | edit source]We already covered automatic type conversions (implicit conversion) and mentioned that some can be user-defined.
A user-defined conversion from a class to another class can be done by providing a constructor in the target class that takes the source class as an argument, Target(const Source& a_Class)
or by providing the target class with a conversion operator, as operator Source()
.
Ensuring objects of a class are never copied
[edit | edit source]This is required e.g. to prevent memory-related problems that would result in case the default copy-constructor or the default assignment operator is unintentionally applied to a class C which uses dynamically allocated memory, where a copy-constructor and an assignment operator are probably an overkill as they won't be used frequently.
Some style guidelines suggest making all classes non-copyable by default, and only enabling copying if it makes sense. Other (bad) guidelines say that you should always explicitly write the copy constructor and copy assignment operators; that's actually a bad idea, as it adds to the maintenance effort, adds to the work to read a class, is more likely to introduce errors than using the implicitly declared ones, and doesn't make sense for most object types. A sensible guideline is to think about whether copying makes sense for a type; if it does, then first prefer to arrange that the compiler-generated copy operations will do the right thing (e.g., by holding all resources via resource management classes rather than via raw pointers or handles), and if that's not reasonable then obey the law of three. If copying doesn't make sense, you can disallow it in either of two idiomatic ways as shown below.
Just declare the copy-constructor and assignment operator, and make them private. Do not define them. As they are not protected or public, they are inaccessible outside the class. Using them within the class would give a linker error since they are not defined.
class C
{
...
private:
// Not defined anywhere
C (const C&);
C& operator= (const C&);
};
Remember that if the class uses dynamically allocated memory for data members, you must define the memory release procedures in destructor ~C () to release the allocated memory.
A class which only declares these two functions can be used as a private base class, so that all classes which privately inherits such a class will disallow copying.
Container class
[edit | edit source]A class that is used to hold objects in memory or external storage is often called a container class. A container class acts as a generic holder and has a predefined behavior and a well-known interface. It is also a supporting class whose purpose is to hide the topology used for maintaining the list of objects in memory. When it contains a group of mixed objects, the container is called a heterogeneous container; when the container is holding a group of objects that are all the same, the container is called a homogeneous container.
Interface class
[edit | edit source]
Singleton class
[edit | edit source]A Singleton class is a class that can only be instantiated once (similar to the use of static variables or functions). It is one of the possible implementations of a creational pattern, which is fully covered in the Design Patterns Section of the book.
Abstract Classes
[edit | edit source]An abstract class is, conceptually, a class that cannot be instantiated and is usually implemented as a class that has one or more pure virtual (abstract) functions.
A pure virtual function is one which must be overridden by any concrete (i.e., non-abstract) derived class. This is indicated in the declaration with the syntax " = 0" in the member function's declaration.
- Example
class AbstractClass {
public:
virtual void AbstractMemberFunction() = 0; // Pure virtual function makes
// this class Abstract class.
virtual void NonAbstractMemberFunction1(); // Virtual function.
void NonAbstractMemberFunction2();
};
In general an abstract class is used to define an implementation and is intended to be inherited from by concrete classes. It's a way of forcing a contract between the class designer and the users of that class. If we wish to create a concrete class (a class that can be instantiated) from an abstract class we must declare and define a matching member function for each abstract member function of the base class. Otherwise, if any member function of the base class is left undefined, we will create a new abstract class (this could be useful sometimes).
Sometimes we use the phrase "pure abstract class," meaning a class that exclusively has pure virtual functions (and no data). The concept of interface is mapped to pure abstract classes in C++, as there is no "interface" construct in C++ the same way that there is in Java.
- Example
class Vehicle {
public:
explicit
Vehicle( int topSpeed )
: m_topSpeed( topSpeed )
{}
int TopSpeed() const {
return m_topSpeed;
}
virtual void Save( std::ostream& ) const = 0;
private:
int m_topSpeed;
};
class WheeledLandVehicle : public Vehicle {
public:
WheeledLandVehicle( int topSpeed, int numberOfWheels )
: Vehicle( topSpeed ), m_numberOfWheels( numberOfWheels )
{}
int NumberOfWheels() const {
return m_numberOfWheels;
}
void Save( std::ostream& ) const; // is implicitly virtual
private:
int m_numberOfWheels;
};
class TrackedLandVehicle : public Vehicle {
public:
TrackedLandVehicle ( int topSpeed, int numberOfTracks )
: Vehicle( topSpeed ), m_numberOfTracks ( numberOfTracks )
{}
int NumberOfTracks() const {
return m_numberOfTracks;
}
void Save( std::ostream& ) const; // is implicitly virtual
private:
int m_numberOfTracks;
};
In this example the Vehicle is an abstract base class as it has an abstract member function.The class WheeledLandVehicle is derived from the base class. It also holds data which is common to all wheeled land vehicles, namely the number of wheels. The class TrackedLandVehicle is another variation of the Vehicle class.
This is something of a contrived example but it does show how that you can share implementation details among a hierarchy of classes. Each class further refines a concept. This is not always the best way to implement an interface but in some cases it works very well. As a guideline, for ease of maintenance and understanding you should try to limit the inheritance to no more than 3 levels. Often the best set of classes to use is a pure virtual abstract base class to define a common interface. Then use an abstract class to further refine an implementation for a set of concrete classes and lastly define the set of concrete classes.
An abstract class is a class that is designed to be specifically used as a base class. An abstract class contains at least one pure virtual function. You declare a pure virtual function by using a pure specifier (= 0) in the declaration of a virtual member function in the class declaration.
The following is an example of an abstract class:
class AB {
public:
virtual void f() = 0;
};
Function AB::f is a pure virtual function. A function declaration cannot have both a pure specifier and a definition.
Abstract class cannot be used as a parameter type, a function return type, or the type of an explicit conversion, and not to declare an object of an abstract class. It can be used to declare pointers and references to an abstract class.
Pure Abstract Classes
[edit | edit source]An abstract class is one in which there is a declaration but no definition for a member function. The way this concept is expressed in C++ is to have the member function declaration assigned to zero.
- Example
class PureAbstractClass
{
public:
virtual void AbstractMemberFunction() = 0;
};
A pure Abstract class has only abstract member functions and no data or concrete member functions. In general, a pure abstract class is used to define an interface and is intended to be inherited by concrete classes. It's a way of forcing a contract between the class designer and the users of that class. The users of this class must declare a matching member function for the class to compile.
- Example of usage for a pure Abstract Class
class DrawableObject
{
public:
virtual void Draw(GraphicalDrawingBoard&) const = 0; //draw to GraphicalDrawingBoard
};
class Triangle : public DrawableObject
{
public:
void Draw(GraphicalDrawingBoard&) const; //draw a triangle
};
class Rectangle : public DrawableObject
{
public:
void Draw(GraphicalDrawingBoard&) const; //draw a rectangle
};
class Circle : public DrawableObject
{
public:
void Draw(GraphicalDrawingBoard&) const; //draw a circle
};
typedef std::list<DrawableObject*> DrawableList;
DrawableList drawableList;
GraphicalDrawingBoard drawingBoard;
drawableList.pushback(new Triangle());
drawableList.pushback(new Rectangle());
drawableList.pushback(new Circle());
for(DrawableList::const_iterator iter = drawableList.begin(),
endIter = drawableList.end();
iter != endIter;
++iter)
{
DrawableObject *object = *iter;
object->Draw(drawingBoard);
}
Note that this is a bit of a contrived example and that the drawable objects are not fully defined (no constructors or data) but it should give you the general idea of the power of defining an interface. Once the objects are constructed, the code that calls the interface does not know any of the implementation details of the called objects, only that of the interface. The object GraphicalDrawingBoard is a placeholder meant to represent the thing onto which the object will be drawn, i.e. the video memory, drawing buffer, printer.
Note that there is a great temptation to add concrete member functions and data to pure abstract base classes. This must be resisted, in general it is a sign that the interface is not well factored. Data and concrete member functions tend to imply a particular implementation and as such can inherit from the interface but should not be that interface. Instead if there is some commonality between concrete classes, creation of abstract class which inherits its interface from the pure abstract class and defines the common data and member functions of the concrete classes works well. Some care should be taken to decide whether inheritance or aggregation should be used. Too many layers of inheritance can make the maintenance and usage of a class difficult. Generally, the maximum accepted layers of inheritance is about 3, above that and refactoring of the classes is generally called for. A general test is the "is a" vs "has a", as in a Square is a Rectangle, but a Square has a set of sides.
What is a "nice" (container safe) class?
[edit | edit source]A "nice" class takes into consideration the use of the following functions:
1. The copy constructor.
2. The assignment operator.
3. The equality operator.
4. The inequality operator.
Class Declaration
[edit | edit source]class Nice
{
public:
Nice(const Nice &Copy);
Nice &operator= (const Nice &Copy);
bool operator== (const Nice ¶m) const;
bool operator!= (const Nice ¶m) const;
};
Description
[edit | edit source]A "nice" class could also be called a container safe class. Many containers such as those in the Standard Template Library (STL), that we'll see later, use copy construction and the assignment operator when interacting with the objects of your class. The assignment operator and copy constructor only need to be declared and defined if the default behavior, which is a member-wise (not binary) copy, is undesirable or insufficient to properly copy/construct your object.
A general rule of thumb is that if the default, member-wise copy operations do not work for your objects then you should define a suitable copy constructor and assignment operator. They are both needed if either is defined.
Copy Constructor
[edit | edit source]The purpose of the copy constructor is to allow the programmer to perform the same instructions as the assignment operator with the special case of knowing that the caller is initializing/constructing rather than an copying.
It is also good practice to use the explicit keyword when using a copy constructor to prevent unintended implicit type conversion.
Example
class Nice
{
public:
explicit Nice(int _a) : a(_a)
{
return;
}
private:
int a;
};
class NotNice
{
public:
NotNice(int _a) : a(_a)
{
return;
}
private:
int a;
};
int main()
{
Nice proper = Nice(10); //this is ok
Nice notproper = 10; //this will result in an error
NotNice eg = 10; //this WILL compile, you may not have intended this conversion
return 0;
}
Equality Operator
[edit | edit source]The equality operator says, "Is this object equal to that object?". What constitutes equal is up to the programmer. This is a requirement if you ever want to use the equality operator with objects of your class.
However, in most applications (e.g. mathematics), it is usually the case that coding the inequality is easier than coding the equality. In which case the following code can be written for the equality.
inline bool Nice::operator== (const Nice& param) const
{
return !(*this != param);
}
Inequality Operator
[edit | edit source]The inequality operator says, "Is this object not equal to that object?". What constitutes not equal is up to the programmer. This is a requirement if you ever want to use the inequality operator with objects of your class.
However, in some applications, coding the equality is easier than coding the inequality. In which case the following code can be written for the inequality.
inline bool Nice::operator!= (const Nice& param) const
{
return !(*this == param);
}
If the statement about the (in)equality operators having different efficiency (whatever kind) seems complete nonsense to you, consider that typically, all object attributes must match for two objects to be considered equal.
Typically, only one object attribute must differ for two objects to be considered unequal. For equality and inequality operators, that doesn't mean one is faster than the other.
Note, however, that using both the above equality and inequality functions as defined will result in an infinite recursive loop and care must be taken to use only one or the other. Also, there are some situations where neither applies and therefore neither of the above can be used.
Given two objects A and B (with class attributes x and y), an equality operator could be written as
if (A.x != B.x) return false;
if (A.y != B.y) return false;
return true;
while an inequality operator could be written as
if (A.x != B.x) return true;
if (A.y != B.y) return true;
return false;
So yes, the equality operator can certainly be written ...!(a!=b)..., but it isn't any faster. In fact, there's the additional overhead of a method call and a negation operation.
So the question becomes, is a little execution overhead worth the smaller code and improved maintainability? There is no simple answer to this it all depend on how the programmer is using them. If your class is composed of, say, an array of 1 billion elements, the overhead is negligible.
Operator overloading
[edit | edit source]Operator overloading (less commonly known as ad-hoc polymorphism) is a specific case of polymorphism (part of the OO nature of the language) in which some or all operators like +, = or == are treated as polymorphic functions and as such have different behaviors depending on the types of its arguments. Operator overloading is usually only syntactic sugar. It can easily be emulated using function calls.
Consider this operation:
add (a, multiply (b,c))
Using operator overloading permits a more concise way of writing it, like this:
a + b * c
(Assuming the * operator has higher precedence than +.)
Operator overloading can provide more than an aesthetic benefit, since the language allows operators to be invoked implicitly in some circumstances. Problems, and critics, to the use of operator overloading arise because it allows programmers to give operators completely free functionality, without an imposition of coherency that permits to consistently satisfy user/reader expectations. Usage of the <<
operator is an example of this problem.
// The expression
a << 1;
Will return twice the value of a if a is an integer variable, but if a is an output stream instead this will write "1" to it. Because operator overloading allows the programmer to change the usual semantics of an operator, it is usually considered good practice to use operator overloading with care.
To overload an operator is to provide it with a new meaning for user-defined types. This is done in the same fashion as defining a function. The basic syntax follows (where @ represents a valid operator):
return_type operator@(parameter_list)
{
// ... definition
}
Not all operators may be overloaded, new operators cannot be created, and the precedence, associativity or arity of operators cannot be changed (for example ! cannot be overloaded as a binary operator). Most operators may be overloaded as either a member function or non-member function, some, however, must be defined as member functions. Operators should only be overloaded where their use would be natural and unambiguous, and they should perform as expected. For example, overloading + to add two complex numbers is a good use, whereas overloading * to push an object onto a vector would not be considered good style.
- A simple Message Header
// sample of Operator Overloading
#include <string>
class PlMessageHeader
{
std::string m_ThreadSender;
std::string m_ThreadReceiver;
//return true if the messages are equal, false otherwise
inline bool operator == (const PlMessageHeader &b) const
{
return ( (b.m_ThreadSender==m_ThreadSender) &&
(b.m_ThreadReceiver==m_ThreadReceiver) );
}
//return true if the message is for name
inline bool isFor (const std::string &name) const
{
return (m_ThreadReceiver==name);
}
//return true if the message is for name
inline bool isFor (const char *name) const
{
return (m_ThreadReceiver==name);// since name type is std::string, it becomes unsafe if name == NULL
}
};
Operators as member functions
[edit | edit source]Aside from the operators which must be members, operators may be overloaded as member or non-member functions. The choice of whether or not to overload as a member is up to the programmer. Operators are generally overloaded as members when they:
- change the left-hand operand, or
- require direct access to the non-public parts of an object.
When an operator is defined as a member, the number of explicit parameters is reduced by one, as the calling object is implicitly supplied as an operand. Thus, binary operators take one explicit parameter and unary operators none. In the case of binary operators, the left hand operand is the calling object, and no type coercion will be done upon it. This is in contrast to non-member operators, where the left hand operand may be coerced.
// binary operator as member function
//Vector2D Vector2D::operator+(const Vector2D& right)const [...]
// binary operator as non-member function
//Vector2D operator+(const Vector2D& left, const Vector2D& right)[...]
// binary operator as non-member function with 2 arguments
//friend Vector2D operator+(const Vector2D& left, const Vector2D& right) [...]
// unary operator as member function
//Vector2D Vector2D::operator-()const {...}
// unary operator as non-member function[...]
//Vector2D operator-(const Vector2D& vec) [...]
Overloadable operators
[edit | edit source]Arithmetic operators
[edit | edit source]- + (addition)
- - (subtraction)
- * (multiplication)
- / (division)
- % (modulus)
As binary operators, these involve two arguments which do not have to be the same type. These operators may be defined as member or non-member functions. An example illustrating overloading for the addition of a 2D mathematical vector type follows.
Vector2D Vector2D::operator+(const Vector2D& right)
{
Vector2D result;
result.set_x(x() + right.x());
result.set_y(y() + right.y());
return result;
}
It is good style to only overload these operators to perform their customary arithmetic operation. Because operator has been overloaded as member function, it can access private fields.
Bitwise operators
[edit | edit source]- ^ (XOR)
- | (OR)
- & (AND)
- ~ (complement)
- << (shift left, insertion to stream)
- >> (shift right, extraction from stream)
All of the bitwise operators are binary, except complement, which is unary. It should be noted that these operators have a lower precedence than the arithmetic operators, so if ^ were to be overloaded for exponentiation, x ^ y + z may not work as intended. Of special mention are the shift operators, << and >>. These have been overloaded in the standard library for interaction with streams. When overloading these operators to work with streams the rules below should be followed:
- overload << and >> as friends (so that it can access the private variables with the stream be passed in by references)
- (input/output modifies the stream, and copying is not allowed)
- the operator should return a reference to the stream it receives (to allow chaining, cout << 3 << 4 << 5)
- An example using a 2D vector
friend ostream& operator<<(ostream& out, const Vector2D& vec) // output
{
out << "(" << vec.x() << ", " << vec.y() << ")";
return out;
}
friend istream& operator>>(istream& in, Vector2D& vec) // input
{
double x, y;
// skip opening paranthesis
in.ignore(1);
// read x
in >> x;
vec.set_x(x);
// skip delimiter
in.ignore(2);
// read y
in >> y;
vec.set_y(y);
// skip closing paranthesis
in.ignore(1);
return in;
}
Assignment operator
[edit | edit source]The assignment operator, =, must be a member function, and is given default behavior for user-defined classes by the compiler, performing an assignment of every member using its assignment operator. This behavior is generally acceptable for simple classes which only contain variables. However, where a class contains references or pointers to outside resources, the assignment operator should be overloaded (as general rule, whenever a destructor and copy constructor are needed so is the assignment operator), otherwise, for example, two strings would share the same buffer and changing one would change the other.
In this case, an assignment operator should perform two duties:
- clean up the old contents of the object
- copy the resources of the other object
For classes which contain raw pointers, before doing the assignment, the assignment operator should check for self-assignment, which generally will not work (as when the old contents of the object are erased, they cannot be copied to refill the object). Self assignment is generally a sign of a coding error, and thus for classes without raw pointers, this check is often omitted, as while the action is wasteful of cpu cycles, it has no other effect on the code.
- Example
class BuggyRawPointer { // example of super-common mistake
T *m_ptr;
public:
BuggyRawPointer(T *ptr) : m_ptr(ptr) {}
BuggyRawPointer& operator=(BuggyRawPointer const &rhs) {
delete m_ptr; // free resource; // Problem here!
m_ptr = 0;
m_ptr = rhs.m_ptr;
return *this;
};
};
BuggyRawPointer x(new T);
x = x; // We might expect this to keep x the same. This sets x.m_ptr == 0. Oops!
// The above problem can be fixed like so:
class WithRawPointer2 {
T *m_ptr;
public:
WithRawPointer2(T *ptr) : m_ptr(ptr) {}
WithRawPointer2& operator=(WithRawPointer2 const &rhs) {
if (this != &rhs) {
delete m_ptr; // free resource;
m_ptr = 0;
m_ptr = rhs.m_ptr;
}
return *this;
};
};
WithRawPointer2 x2(new T);
x2 = x2; // x2.m_ptr unchanged.
Another common use of overloading the assignment operator is to declare the overload in the private part of the class and not define it. Thus any code which attempts to do an assignment will fail on two accounts, first by referencing a private member function and second fail to link by not having a valid definition. This is done for classes where copying is to be prevented, and generally done with the addition of a privately declared copy constructor
- Example
class DoNotCopyOrAssign {
public:
DoNotCopyOrAssign() {};
private:
DoNotCopyOrAssign(DoNotCopyOrAssign const&);
DoNotCopyOrAssign &operator=(DoNotCopyOrAssign const &);
};
class MyClass : public DoNotCopyOrAssign {
public:
MyClass();
};
MyClass x, y;
x = y; // Fails to compile due to private assignment operator;
MyClass z(x); // Fails to compile due to private copy constructor.
Relational operators
[edit | edit source]- == (equality)
- != (inequality)
- > (greater-than)
- < (less-than)
- >= (greater-than-or-equal-to)
- <= (less-than-or-equal-to)
All relational operators are binary, and should return either true or false. Generally, all six operators can be based off a comparison function, or each other, although this is never done automatically (e.g. overloading > will not automatically overload < to give the opposite). There are, however, some templates defined in the header <utility>; if this header is included, then it suffices to just overload operator== and operator<, and the other operators will be provided by the STL.
Logical operators
[edit | edit source]- ! (NOT)
- && (AND)
- || (OR)
The logical operators AND are used when evaluating two expressions to obtain a single relational result.The operator corresponds to the boolean logical operation AND,which yields true if operands are true,and false otherwise.The following panel shows the result of operator evaluating the expression.
The ! operator is unary, && and || are binary. It should be noted that in normal use, && and || have "short-circuit" behavior, where the right operand may not be evaluated, depending on the left operand. When overloaded, these operators get function call precedence, and this short circuit behavior is lost. It is best to leave these operators alone.
- Example
bool Function1();
bool Function2();
Function1() && Function2();
If the result of Function1() is false, then Function2() is not called.
MyBool Function3();
MyBool Function4();
bool operator&&(MyBool const &, MyBool const &);
Function3() && Function4()
Both Function3() and Function4() will be called no matter what the result of the call is to Function3() This is a waste of CPU processing, and worse, it could have surprising unintended consequences compared to the expected "short-circuit" behavior of the default operators. Consider:
extern MyObject * ObjectPointer;
bool Function1() { return ObjectPointer != null; }
bool Function2() { return ObjectPointer->MyMethod(); }
MyBool Function3() { return ObjectPointer != null; }
MyBool Function4() { return ObjectPointer->MyMethod(); }
bool operator&&(MyBool const &, MyBool const &);
Function1() && Function2(); // Does not execute Function2() when pointer is null
Function3() && Function4(); // Executes Function4() when pointer is null
Compound assignment operators
[edit | edit source]- += (addition-assignment)
- -= (subtraction-assignment)
- *= (multiplication-assignment)
- /= (division-assignment)
- %= (modulus-assignment)
- &= (AND-assignment)
- |= (OR-assignment)
- ^= (XOR-assignment)
- <<= (shift-left-assignment)
- >>= (shift-right-assignment)
Compound assignment operators should be overloaded as member functions, as they change the left-hand operand. Like all other operators (except basic assignment), compound assignment operators must be explicitly defined, they will not be automatically (e.g. overloading = and + will not automatically overload +=). A compound assignment operator should work as expected: A @= B should be equivalent to A = A @ B. An example of += for a two-dimensional mathematical vector type follows.
Vector2D& Vector2D::operator+=(const Vector2D& right)
{
this->x += right.x;
this->y += right.y;
return *this;
}
Increment and decrement operators
[edit | edit source]- ++ (increment)
- -- (decrement)
Increment and decrement have two forms, prefix (++i) and postfix (i++). To differentiate, the postfix version takes a dummy integer. Increment and decrement operators are most often member functions, as they generally need access to the private member data in the class. The prefix version in general should return a reference to the changed object. The postfix version should just return a copy of the original value. In a perfect world, A += 1, A = A + 1, A++, ++A should all leave A with the same value.
- Example
SomeValue& SomeValue::operator++() // prefix
{
++data;
return *this;
}
SomeValue SomeValue::operator++(int unused) // postfix
{
SomeValue result = *this;
++data;
return result;
}
Often one operator is defined in terms of the other for ease in maintenance, especially if the function call is complex.
SomeValue SomeValue::operator++(int unused) // postfix
{
SomeValue result = *this;
++(*this); // call SomeValue::operator++()
return result;
}
Subscript operator
[edit | edit source]The subscript operator, [ ], is an operator which can take in any number of arguments (much like a function call) but usually one. It also must be a member function (hence it make take only one explicit parameter, the index). The subscript operator is not limited to taking an integral index. For instance, the index for the subscript operator for the std::map template is the same as the type of the key, so it may be a string etc. The subscript operator is generally overloaded twice; as a non-constant function (for when elements are altered), and as a constant function (for when elements are only accessed).
Function call operator
[edit | edit source]The function call operator, ( ), is generally overloaded to create objects which behave like functions, or for classes that have a primary operation. The function call operator must be a member function, but has no other restrictions - it may be overloaded with any number of parameters of any type, and may return any type. A class may also have several definitions for the function call operator.
Address of, Reference, and Pointer operators
[edit | edit source]These three operators, operator&(), operator*() and operator->() can be overloaded. In general these operators are only overloaded for smart pointers, or classes which attempt to mimic the behavior of a raw pointer. The pointer operator, operator->() has the additional requirement that the result of the call to that operator, must return a pointer, or a class with an overloaded operator->(). In general A == *&A should be true.
Note that overloading operator& invokes undefined behavior:
- ISO/IEC 14882:2003, Section 5.3.1
- The address of an object of incomplete type can be taken, but if the complete type of that object is a class type that declares operator&() as a member function, then the behavior is undefined (and no diagnostic is required).
- Example
class T {
public:
const memberFunction() const;
};
// forward declaration
class DullSmartReference;
class DullSmartPointer {
private:
T *m_ptr;
public:
DullSmartPointer(T *rhs) : m_ptr(rhs) {};
DullSmartReference operator*() const {
return DullSmartReference(*m_ptr);
}
T *operator->() const {
return m_ptr;
}
};
class DullSmartReference {
private:
T *m_ptr;
public:
DullSmartReference (T &rhs) : m_ptr(&rhs) {}
DullSmartPointer operator&() const {
return DullSmartPointer(m_ptr);
}
// conversion operator
operator T() { return *m_ptr; }
};
DullSmartPointer dsp(new T);
dsp->memberFunction(); // calls T::memberFunction
T t;
DullSmartReference dsr(t);
dsp = &dsr;
t = dsr; // calls the conversion operator
These are extremely simplified examples designed to show how the operators can be overloaded and not the full details of a SmartPointer or SmartReference class. In general you won't want to overload all three of these operators in the same class.
Comma operator
[edit | edit source]The comma operator,() , can be overloaded. The language comma operator has left to right precedence, the operator,() has function call precedence, so be aware that overloading the comma operator has many pitfalls.
- Example
MyClass operator,(MyClass const &, MyClass const &);
MyClass Function1();
MyClass Function2();
MyClass x = Function1(), Function2();
For non overloaded comma operator, the order of execution will be Function1(), Function2(); With the overloaded comma operator, the compiler can call either Function1(), or Function2() first.
Member Reference operators
[edit | edit source]The two member access operators, operator->() and operator->*() can be overloaded. The most common use of overloading these operators is with defining expression template classes, which is not a common programming technique. Clearly by overloading these operators you can create some very unmaintainable code so overload these operators only with great care.
When the -> operator is applied to a pointer value of type (T *), the language dereferences the pointer and applies the . member access operator (so x->m is equivalent to (*x).m). However, when the -> operator is applied to a class instance, it is called as a unary postfix operator; it is expected to return a value to which the -> operator can again be applied. Typically, this will be a value of type (T *), as in the example under Address of, Reference, and Pointer operators above, but can also be a class instance with operator->() defined; the language will call operator->() as many times as necessary until it arrives at a value of type (T *).
Memory management operators
[edit | edit source]- new (allocate memory for object)
- new[ ] (allocate memory for array)
- delete (deallocate memory for object)
- delete[ ] (deallocate memory for array)
The memory management operators can be overloaded to customize allocation and deallocation (e.g. to insert pertinent memory headers). They should behave as expected, new should return a pointer to a newly allocated object on the heap, delete should deallocate memory, ignoring a NULL argument. To overload new, several rules must be followed:
- new must be a member function
- the return type must be void*
- the first explicit parameter must be a size_t value
To overload delete there are also conditions:
- delete must be a member function (and cannot be virtual)
- the return type must be void
- there are only two forms available for the parameter list, and only one of the forms may appear in a class:
- void*
- void*, size_t
Conversion operators
[edit | edit source]Conversion operators enable objects of a class to be either implicitly (coercion) or explicitly (casting) converted to another type. Conversion operators must be member functions, and should not change the object which is being converted, so should be flagged as constant functions. The basic syntax of a conversion operator declaration, and declaration for an int-conversion operator follows.
operator ''type''() const; // const is not necessary, but is good style
operator int() const;
Notice that the function is declared without a return-type, which can easily be inferred from the type of conversion. Including the return type in the function header for a conversion operator is a syntax error.
double operator double() const; // error - return type included
Operators which cannot be overloaded
[edit | edit source]- ?: (conditional)
- . (member selection)
- .* (member selection with pointer-to-member)
- :: (scope resolution)
sizeof
(object size information)- typeid (object type information)
- static_cast (casting operator)
- const_cast (casting operator)
- reinterpret_cast (casting operator)
- dynamic_cast (casting operator)
To understand the reasons why the language doesn't permit these operators to be overloaded, read "Why can't I overload dot, ::, sizeof
, etc.?" at the Bjarne Stroustrup's C++ Style and Technique FAQ ( http://www.stroustrup.com/bs_faq2.html#overload-dot ).
I/O
[edit | edit source]Also commonly referenced as the C++ I/O of the C++ Standard Library, since the library also includes the C Standard library and its I/O implementation, as seen before in the Standard C I/O Section.
Input and output are essential for any computer software, as these are the only means by which the program can communicate with the user. The simplest form of input/output is pure textual, i.e. the application displays in console form, using simple ASCII characters to prompt the user for inputs, which are supplied using the keyboard.
There are many ways for a program to gain input and output, including
- File i/o, that is, reading and writing to files
- Console i/o, reading and writing to a console window, such as a terminal in UNIX-based operating systems or a DOS prompt in Windows.
- Network i/o, reading and writing from a network device
- String i/o, reading and writing treating a string as if it were the input or output device
While these may seem unrelated, they work very similarly. In fact, operating systems that follow the POSIX specification deal with files, devices, network sockets, consoles, and many other things all with one type of handle, a file descriptor. However, low-level interfaces provided by the operating system tend to be difficult to use, so C++, like other languages, provide an abstraction to make programming easier. This abstraction is the stream.
Character encoding
[edit | edit source]
American Standard Code for Information Interchange (ASCII) 95 chart
[edit | edit source]ASCII is a character-encoding scheme based on the ordering of the English alphabet. The 95 ASCII graphic characters numbered from 0x20 to 0x7E (32 to 126 decimal), also known as the printable characters, represent letters, digits, punctuation marks, and a few miscellaneous symbols. The first 32 ASCII characters, from 0x00 to 0x20, are known as control characters. The space character, that denotes the space between words, as produced by the space-bar of a keyboard, represented by code 0x20 (hexadecimal), is considered a non-printing graphic (or an invisible graphic) rather than a control character.
Binary | Oct | Dec | Hex | Glyph |
---|---|---|---|---|
010 0000 | 040 | 32 | 20 | space |
010 0001 | 041 | 33 | 21 | ! |
010 0010 | 042 | 34 | 22 | " |
010 0011 | 043 | 35 | 23 | # |
010 0100 | 044 | 36 | 24 | $ |
010 0101 | 045 | 37 | 25 | % |
010 0110 | 046 | 38 | 26 | & |
010 0111 | 047 | 39 | 27 | ' |
010 1000 | 050 | 40 | 28 | ( |
010 1001 | 051 | 41 | 29 | ) |
010 1010 | 052 | 42 | 2A | * |
010 1011 | 053 | 43 | 2B | + |
010 1100 | 054 | 44 | 2C | , |
010 1101 | 055 | 45 | 2D | - |
010 1110 | 056 | 46 | 2E | . |
010 1111 | 057 | 47 | 2F | / |
011 0000 | 060 | 48 | 30 | 0 |
011 0001 | 061 | 49 | 31 | 1 |
011 0010 | 062 | 50 | 32 | 2 |
011 0011 | 063 | 51 | 33 | 3 |
011 0100 | 064 | 52 | 34 | 4 |
011 0101 | 065 | 53 | 35 | 5 |
011 0110 | 066 | 54 | 36 | 6 |
011 0111 | 067 | 55 | 37 | 7 |
011 1000 | 070 | 56 | 38 | 8 |
011 1001 | 071 | 57 | 39 | 9 |
011 1010 | 072 | 58 | 3A | : |
011 1011 | 073 | 59 | 3B | ; |
011 1100 | 074 | 60 | 3C | < |
011 1101 | 075 | 61 | 3D | = |
011 1110 | 076 | 62 | 3E | > |
011 1111 | 077 | 63 | 3F | ? |
Binary | Oct | Dec | Hex | Glyph |
---|---|---|---|---|
100 0000 | 100 | 64 | 40 | @ |
100 0001 | 101 | 65 | 41 | A |
100 0010 | 102 | 66 | 42 | B |
100 0011 | 103 | 67 | 43 | C |
100 0100 | 104 | 68 | 44 | D |
100 0101 | 105 | 69 | 45 | E |
100 0110 | 106 | 70 | 46 | F |
100 0111 | 107 | 71 | 47 | G |
100 1000 | 110 | 72 | 48 | H |
100 1001 | 111 | 73 | 49 | I |
100 1010 | 112 | 74 | 4A | J |
100 1011 | 113 | 75 | 4B | K |
100 1100 | 114 | 76 | 4C | L |
100 1101 | 115 | 77 | 4D | M |
100 1110 | 116 | 78 | 4E | N |
100 1111 | 117 | 79 | 4F | O |
101 0000 | 120 | 80 | 50 | P |
101 0001 | 121 | 81 | 51 | Q |
101 0010 | 122 | 82 | 52 | R |
101 0011 | 123 | 83 | 53 | S |
101 0100 | 124 | 84 | 54 | T |
101 0101 | 125 | 85 | 55 | U |
101 0110 | 126 | 86 | 56 | V |
101 0111 | 127 | 87 | 57 | W |
101 1000 | 130 | 88 | 58 | X |
101 1001 | 131 | 89 | 59 | Y |
101 1010 | 132 | 90 | 5A | Z |
101 1011 | 133 | 91 | 5B | [ |
101 1100 | 134 | 92 | 5C | \ |
101 1101 | 135 | 93 | 5D | ] |
101 1110 | 136 | 94 | 5E | ^ |
101 1111 | 137 | 95 | 5F | _ |
Binary | Oct | Dec | Hex | Glyph |
---|---|---|---|---|
110 0000 | 140 | 96 | 60 | ` |
110 0001 | 141 | 97 | 61 | a |
110 0010 | 142 | 98 | 62 | b |
110 0011 | 143 | 99 | 63 | c |
110 0100 | 144 | 100 | 64 | d |
110 0101 | 145 | 101 | 65 | e |
110 0110 | 146 | 102 | 66 | f |
110 0111 | 147 | 103 | 67 | g |
110 1000 | 150 | 104 | 68 | h |
110 1001 | 151 | 105 | 69 | i |
110 1010 | 152 | 106 | 6A | j |
110 1011 | 153 | 107 | 6B | k |
110 1100 | 154 | 108 | 6C | l |
110 1101 | 155 | 109 | 6D | m |
110 1110 | 156 | 110 | 6E | n |
110 1111 | 157 | 111 | 6F | o |
111 0000 | 160 | 112 | 70 | p |
111 0001 | 161 | 113 | 71 | q |
111 0010 | 162 | 114 | 72 | r |
111 0011 | 163 | 115 | 73 | s |
111 0100 | 164 | 116 | 74 | t |
111 0101 | 165 | 117 | 75 | u |
111 0110 | 166 | 118 | 76 | v |
111 0111 | 167 | 119 | 77 | w |
111 1000 | 170 | 120 | 78 | x |
111 1001 | 171 | 121 | 79 | y |
111 1010 | 172 | 122 | 7A | z |
111 1011 | 173 | 123 | 7B | { |
111 1100 | 174 | 124 | 7C | | |
111 1101 | 175 | 125 | 7D | } |
111 1110 | 176 | 126 | 7E | ~ |
Streams
[edit | edit source]A stream is a type of object from which we can take values, or to which we can pass values. This is done transparently in terms of the underlying code that demonstrates the use of the std::cout stream, known as the standard output stream.
// 'Hello World!' program
#include <iostream>
int main()
{
std::cout << "Hello World!" << std::endl;
return 0;
}
Almost all input and output one ever does can be modeled very effectively as a stream. Having one common model means that one only has to learn it once. If you understand streams, you know the basics of how to output to files, the screen, sockets, pipes, and anything else that may come up.
A stream is an object that allows one to push data in or out of a medium, in order. Usually a stream can only output or can only input. It is possible to have a stream that does both, but this is rare. One can think of a stream as a car driving along a one-way street of information. An output stream can insert data and move on. It (usually) cannot go back and adjust something it has already written. Similarly, an input stream can read the next bit of data and then wait for the one that comes after it. It does not skip data or rewind and see what it had read five minutes ago.
The semantics of what a stream's read and write operations do depend on the type of stream. In the case of a file, an input file stream reads the file's contents in order without rewinding, and an output file stream writes to the file in order. For a console stream, output means displaying text, and input means getting input from the user via the console. If the user has not inputted anything, then the program blocks, or waits, for the user to enter in something.
iostream
[edit | edit source]iostream
is a header file used for input/output. It is part of the C++ standard library. The name stands for Input/Output Stream. In C++ there is no special syntax for streaming data input or output. Instead, these are combined as a library of functions. Like we have seen with the C Standard Library use of <cstdio>
header, iostream
provides basic OOP services for I/O.
The iostream
automatically defines and uses a few standard objects:
cin
, an object of the istream class that reads data from the standard input device.cout
, an object of the ostream class, which displays data to the standard output device.cerr
, another object of the ostream class that writes unbuffered output to the standard error device.clog
, like cerr, but uses buffered output.
for sending data to and from the standard streams input, output, error (unbuffered), and error (buffered) respectively. As part of the C++ standard library, these objects are a part of the std namespace.
- Standard input, output, and error
The most common streams one uses are cout, cin, and cerr (pronounced "c out", "c in", and "c err(or)", respectively). They are defined in the header <iostream>. Usually, these streams read and write from a console or terminal. In UNIX-based operating systems, such as Linux and Mac OS X, the user can redirect them to other files, or even other programs, for logging or other purposes. They are analogous to stdout, stdin, and stderr found in C. cout is used for generic output, cin is used for input, and cerr is used for printing errors. (cerr typically goes to the same place as cout, unless one or both is redirected, but it is not buffered and allows the user to fine-tune which parts of the program's output is redirected where.)
Output
[edit | edit source]The standard syntax for outputting to a stream, in this case, cout, is
cout << some_data << some_more_data;
Example
#include <iostream>
using namespace std;
int main()
{
int iA = 1;
cout << "Hello, World! " << iA << '\n';
return 0;
}
Result of Execution
Hello, World! 1
To add a line break, send a newline character, \n.
Using std::endl
also outputs a newline character, but it also calls os.flush().
Example
#include <iostream>
#include <ostream>
using namespace std;
int main()
{
int iA = 1;
char ch = 13;
cout << "Hello, World!" << "\n" << iA << "\n" << ch << endl;
return 0;
}
Execution
Hello, World! 1
It is always a good idea to end your output with a blank line, so as to not mess up with user's terminals.
As seen in the "Hello, World!" program, we direct the output to std::cout
. This means that it is a member of the standard library. For now, don't worry about what this means; we will cover the library and namespaces in later chapters.
What you do need to remember is that, in order to use the output stream, you must include a reference to the standard IO library, as shown here:
#include <iostream>
This opens up a number of streams, functions and other programming devices that we can now use.
For this section, we are interested in two of these; std::cout
and std::endl
.
Once we have referenced the standard IO library, we can use the output stream very simply. To use a stream, give its name, then pipe something in or out of it, as shown:
std::cout << "Hello, World!";
The << operator feeds everything to the right of it into the stream. We have essentially fed a text object into the stream. That's as far as our work goes; the stream now decides what to do with that object. In the case of the output stream, it's printed on-screen.
We're not limited to only sending a single object type to the stream, nor indeed are we limited to one object a time. Consider the examples below:
std::cout << "Hello, " << "Joe"<< std::endl;
std::cout << "The answer to life, the universe and everything is " << 42 << std::endl;
As can be seen, we feed in various values, separated by a pipe character. The result comes out something like:
Hello, Joe The answer to life, the universe and everything is 42
You will have noticed the use of std::endl throughout some of the examples so far. This is a special manipulator, which not only outputs a newline character but also calls os.flush().
Input
[edit | edit source]What would be the use of an application that only ever outputted information, but didn't care about what its users wanted? Minimal to none. Fortunately, inputting is as easy as outputting when you're using the stream.
The standard input stream is called std::cin and is used very similarly to the output stream. Once again, we instantiate the standard IO library:
#include <iostream>
This gives us access to std::cin (and the rest of that class). Now, we give the name of the stream as usual, and pipe output from it into a variable. A number of things have to happen here, demonstrated in the example below:
#include <iostream>
int main(int iArgc, char a_chArgv[]) {
int iA;
std::cout << "Hello! How old are you? ";
std::cin >> iA;
std::cout << "You're really " << iA << " years old?" << std::endl;
return 0;
}
We instantiate the standard IO library as usual, and call our main function in the normal way. Now we need to consider where the user's input goes. This calls for a variable (discussed in a later chapter) that we declare as being called a.
Next, we send some output, asking the user for their age. The real input happens now; everything the user types until they hit Enter is going to be stored in the input stream. We pull this out of the input stream and save it in our variable.
Finally, we output the user's age, piping the contents of our variable into the output stream.
Note: You will notice that, if anything other than a whole number is entered, the program will crash. This is due to the way in which we set up our variable. Don't worry about this for now; we will cover variables later on.
A Program Using User Input
[edit | edit source]The following program inputs two numbers from the user and prints their sum:
#include <iostream>
int main()
{
int iNumber1, iNumber2;
std::cout << "Enter number 1: ";
std::cin >> iNumber1;
std::cout << "Enter number 2: ";
std::cin >> iNumber2;
std::cout << "The sum of " << iNumber1 << " and " << iNumber2 << " is "
<< iNumber1 + iNumber2 << ".\n";
return 0;
}
Just like std::cout that represents the standard output stream, the C++ library provides (and the iostream header declares) the object std::cin representing standard input, which usually gets input from the keyboard. The statement:
std::cin >> iNumber1;
uses the extraction operator (>>) to get an integer input from the user. When used to input integers, any leading whitespace is skipped, a sequence of valid digits optionally preceded by a + or - sign is read and the value stored in the variable. Any remaining characters in the user input are not consumed. These would be considered next time some input operation is performed.
If you want the program to use a function from a specific namespace, normally you must specify which namespace the function is in. The above example calls to cout, which is a member of the std namespace (hence std::cout). If you want a program to specifically use the std namespace for an identifier, which essentially removes the need for all future scope resolution (e.g. std::), you could write the above program like this:
#include <iostream>
using namespace std;
int main()
{
int iNumber1, iNumber2;
cout << "Enter number 1: ";
cin >> iNumber1;
cout << "Enter number 2: ";
cin >> iNumber2;
cout << "The sum of " << iNumber1 << " and " << iNumber2 << " is "
<< iNumber1 + iNumber2 << ".\n";
return 0;
}
Please note that 'std' namespace is the namespace defined by standard C++ library.
Manipulators
[edit | edit source]A manipulator is a function that can be passed as an argument to a stream in different circumstances. For example, the manipulator 'hex' will cause the stream object to format subsequent integer input to the stream in hexadecimal instead of decimal. Likewise, 'oct' results in integers displaying in octal, and 'dec' reverts back to decimal.
Example
#include <iostream>
using namespace std;
int main()
{
cout << dec << 16 << ' ' << 10 << "\n";
cout << oct << 16 << ' ' << 10 << "\n";
cout << hex << 16 << ' ' << 10 << endl;
return 0;
}
Execution
16 10 20 12 10 a
There are many manipulators that can be used in conjunction with streams to simplify the formatting of input. For example, 'setw()' sets the field width of the data item next displayed. Used in conjunction with 'left' and 'right'(that set the justification of the data), 'setw' can easily be used to create columns of data.
Example
#include <iostream>
#include <iomanip>
using namespace std;
int main()
{
cout << setw(10) << right << 90 << setw(8) << "Help!\n";
cout << setw(10) << left << 45 << setw(8) << "Hi!" << endl;
return 0;
}
Execution
90 Help! 45 Hi!
The data in the top row display at the right of the columns created by 'setw', while in the next row, the data is left justified in the column. Please note the inclusion of a new library 'iomanip'. Most formatting manipulators require this library.
Here are some other manipulators and their uses:
Manipulator | Function |
---|---|
boolalpha | displays boolean values as 'true' and 'false' instead of as integers. |
noboolalpha | forces bools to display as integer values |
showuppercase | converts strings to uppercase before displaying them |
noshowuppercase | displays strings as they are received, instead of in uppercase |
fixed | forces floating point numbers to display with a fixed number of decimal places |
scientific | displays floating point numbers in scientific notation |
Buffers
[edit | edit source]Most stream objects, including 'cout' and 'cin', have an area in memory where the information they are transferring sits until it is asked for. This is called a 'buffer'. Understanding the function of buffers is essential to mastering streams and their use.
Example
#include <iostream>
using namespace std;
int main()
{
int iNumber1, iNumber2;
cin >> iNumber1;
cin >> iNumber2;
cout << "Number1: " << iNumber1 << "\n"
<< "Number2: " << iNumber2 << endl;
return 0;
}
Execution 1
>74 >27 Number1: 74 Number2: 27
The inputs are given separately, with a hard return between them. '>' denotes user input.
Execution 2
>74 27 Number1: 74 Number2: 27
The inputs are entered on the same line. They both go into the 'cin' stream buffer, where they are stored until needed. As 'cin' statements are executed, the contents of the buffer are read into the appropriate variables.
Execution 3
>74 27 56 Number1: 74 Number2: 27
In this example, 'cin' received more input than it asked for. The third number it read in, 56, was never inserted into a variable. It would have stayed in the buffer until 'cin' was called again. The use of buffers can explain many strange behaviors that streams can exhibit.
Example
#include <iostream>
using namespace std;
int main()
{
int iNumber1, iNumber2, iNumber3;
cin >> iNumber1 >> iNumber2;
cout << "Number1: " << iNumber1 << "\n"
<< "Number2: " << iNumber2 << endl;
cin >> iNumber3;
cout << "Number3: " << iNumber3 << endl;
return 0;
}
Execution
>45 89 37 Number1: 45 Number2: 89 Number3: 37
Notice how all three numbers were entered at the same time in one line, but the stream only pulled them out of the buffer when they were asked for. This can cause unexpected output, since the user might accidentally put an extra space into his input. A well written program will test for this type of unexpected input and handle it gracefully.
ios
[edit | edit source]ios is a header file in the C++ standard library that defines several types and functions basic to the operation of iostreams. This header is typically included automatically by other iostream headers. Programmers rarely include it directly.
Typedefs
[edit | edit source]Name | description |
---|---|
ios |
Supports the ios class from the old iostream library.
|
streamoff |
Supports internal operations. |
streampos |
Holds the current position of the buffer pointer or file pointer. |
streamsize |
Specifies the size of the stream. |
wios |
Supports the wios class from the old iostream library.
|
wstreampos |
Holds the current position of the buffer pointer or file pointer. |
Manipulators
[edit | edit source]Name | description |
---|---|
boolalpha |
Specifies that variables of type bool appear as true or false in the stream.
|
dec |
Specifies that integer variables appear in base 10 notation. |
fixed |
Specifies that a floating-point number is displayed in fixed-decimal notation. |
hex |
Specifies that integer variables appear in base 16 notation. |
internal |
Causes a number's sign to be left justified and the number to be right justified. |
left |
Causes text that is not as wide as the output width to appear in the stream flush with the left margin. |
noboolalpha |
Specifies that variables of type bool appear as 1 or 0 in the stream.
|
noshowbase |
Turns off indicating the notational base in which a number is displayed. |
noshowpoint |
Displays only the whole-number part of floating-point numbers whose fractional part is zero. |
noshowpos |
Causes positive numbers to not be explicitly signed. |
noskipws |
Cause spaces to be read by the input stream. |
nounitbuf |
Causes output to be buffered and processed when the buffer is full. |
nouppercase |
Specifies that hexadecimal digits and the exponent in scientific notation appear in lowercase. |
oct |
Specifies that integer variables appear in base 8 notation. |
right |
Causes text that is not as wide as the output width to appear in the stream flush with the right margin. |
scientific |
Causes floating point numbers to be displayed using scientific notation. |
showbase |
Indicates the notational base in which a number is displayed. |
showpoint |
Displays the whole-number part of a floating-point number and digits to the right of the decimal point even when the fractional part is zero. |
showpos |
Causes positive numbers to be explicitly signed. |
skipws |
Cause spaces to not be read by the input stream. |
unitbuf |
Causes output to be processed when the buffer is not empty. |
uppercase |
Specifies that hexadecimal digits and the exponent in scientific notation appear in uppercase. |
Classes
[edit | edit source]Name | description |
---|---|
basic_ios |
The template class describes the storage and member functions common to both input streams (of template class basic_istream) and output streams (of template class basic_ostream) that depend on the template parameters. |
fpos |
The template class describes an object that can store all the information needed to restore an arbitrary file-position indicator within any stream. |
ios_base |
The class describes the storage and member functions common to both input and output streams that do not depend on the template parameters. |
fstream
[edit | edit source]With cout and cin, we can do basic communication with the user. For more complex io, we would like to read from and write to files. This is done with file stream classes, defined in the header <fstream>. ofstream is an output file stream, and ifstream is an input file stream.
- Files
To open a file, one can either call open on the file stream or, more commonly, use the constructor. One can also supply an open mode to further control the file stream. Open modes include
- ios::app Leaves the file's original contents and appends new data to the end.
- ios::out Outputs new data in the file, removing the old contents. (default for ofstream)
- ios::in Reads data from the file. (default for ifstream)
Example
// open a file called Test.txt and write "HELLO, HOW ARE YOU?" to it
#include <fstream>
using namespace std;
int main()
{
ofstream file1;
file1.open("file1.txt", ios::app);
file1 << "This data will be appended to the file file1.txt\n";
file1.close();
ofstream file2("file2.txt");
file2 << "This data will replace the contents of file2.txt\n";
return 0;
}
The call to close()
can be omitted, if you do not care about the return value (whether it succeeded); the destructors will call close when the object goes out of scope.
If an operation (e.g. opening a file) was unsuccessful, a flag is set in the stream object. You can check the flags' status using the bad()
or fail()
member functions, which return a boolean value. The stream object doesn't throw any exceptions in such a situation; hence manual status check is required. See reference for details on bad()
and fail()
.
Text input until EOF/error/invalid input
[edit | edit source]Input from the stream infile to a variable data until one of the following:
- EOF reached on infile.
- An error occurs while reading from infile (e.g., connection closed while reading from a remote file).
- The input item is invalid, e.g. non-numeric characters, when data is of type int.
#include <iostream>
// …
while (infile >> data)
{
// manipulate data here
}
Note that the following is not correct:
#include <iostream>
// …
while (!infile.eof())
{
infile >> data; // wrong!
// manipulate data here
}
This will cause the last item in the input file to be processed twice, because eof() does not return true until input fails due to EOF.
ostream
[edit | edit source]- Classes and output streams
It is often useful to have your own classes' instances compatible with the stream framework. For instance, if you defined the class Foo like this:
class Foo
{
public:
Foo() : m_iX(1), m_iY(2)
{
}
int m_iX, m_iY;
};
You will not be able to pass its instance to cout directly using the '<<' operator, because it is not defined for these two objects (Foo and ostream). What needs to be done is to define this operator and thus bind the user-defined class with the stream class.
ostream& operator<<(ostream& output, Foo& arg)
{
output << arg.m_iX << "," << arg.m_iY;
return output;
}
Now this is possible:
Foo myObject;
cout << "my_object's values are: " << myObject << endl;
The operator function needs to have 'ostream&' as its return type, so chaining output works as usual between the stream and objects of type Foo:
Foo my1, my2, my3;
cout << my1 << my2 << my3;
This is because (cout << my1) is of type ostream&, so the next argument (my2) can be appended to it in the same expression, which again gives an ostream& so my3 can be appended and so on.
If you decided to restrict access to the member variables m_iX
and m_iY
(that is probably a good idea) within the class Foo, i.e.:
class Foo
{
public:
Foo() : m_iX(1), m_iY(2)
{
}
private:
int m_iX, m_iY;
};
you will have trouble, because operator
<< function doesn't have access to the private variables of its second argument. There are two possible solutions to this problem:
1. Within the class Foo, declare the operator
<< function as the classes' friend that grants it access to private members, i.e. add the following line to the class declaration:
friend ostream& operator<<(ostream& output, Foo& arg);
Then define the operator<< function as you normally would (note that the declared function is not a member of Foo, just its friend, so don't try defining it as Foo::operator<<).
2. Add public-available functions for accessing the member variables and make the operator
<< function use these instead:
class Foo
{
public:
Foo() : m_iX(1), m_iY(2)
{
}
int getX()
{
return m_iX;
}
int getY()
{
return m_iY;
}
private:
int m_iX, m_iY;
};
ostream& operator<<(ostream& output, Foo& arg)
{
output << iArg.getX() << "," << iArg.getY();
return output;
}
Rounding number example
[edit | edit source]This is a small example that rounds a number to a string, a function called RoundToString
. Figures may have trailing zeros, those that would be expected to disappear using a number format.
The constant class contains repeating constants that should exist only once in the code so that to avoid inadvertent changes. (If the one constant is changed inadvertently, it is most likely to be seen, as it is used at several locations.)
The code was first cross-compiled by the JAVA TO C++ CONVERTER of Tangible Software Solutions. Parts may have a copyright notice requirement that prevents inclusion in this work you can get yourself the code for the StringConverter
at that location.
Here is the relevant code and its call. You are invited to write a shorter version that gives the same result.
Common.cpp
:
#include "StdAfx.h"
namespace common
{
double const Common::NEARLY_ZERO = 1E-4;
double const Common::VERY_LARGE = 1E10;
string const Common::strZERO = *new string(1, Common::ZERO);
bool Common::IsTrimmable(char const chCHARACTER,
char const chTRIM,
bool const bIS_NUMERIC)
{
return ((chCHARACTER == chTRIM)
|| (bIS_NUMERIC && (chCHARACTER == Common::ZERO)));
}
string const& Common::Trim(string const& strVALUE, char const chTRIM)
{
return TrimLeft(TrimRight(strVALUE, chTRIM), chTRIM);
}
string const& Common::TrimLeft(string const& strVALUE,
char const chTRIM)
{
if (strVALUE.length() == 0) return strVALUE;
else
{
ushort usPosition = 0;
for (; usPosition < strVALUE.length(); usPosition++)
{
if (!IsTrimmable(strVALUE[usPosition], chTRIM)) break;
}
return *new string(strVALUE.substr(usPosition));
}
}
string const& Common::TrimRight(string const& strVALUE,
char const chTRIM)
{
if (strVALUE.length() == 0) return strVALUE;
else
{
ushort usPosition = strVALUE.length() - 1;
for (; usPosition < strVALUE.length(); usPosition--)
{
if (!IsTrimmable(strVALUE[usPosition], chTRIM))
{
if (strVALUE[usPosition] != Common::PERIOD) ++usPosition;
break;
}
}
return *new string(strVALUE.substr(0, usPosition));
}
}
}
Common.h
:
#pragma once
#include <string>
namespace common
{
/// <summary>
/// Class that comprises of constant values and recurring algorithms.
///
/// @author Saban
///
///</summary>
class Common
{
/// <summary>
/// Determines, if the character is trimmable or not.
/// </summary>
/// <param name="chCHARACTER">Character to be checked</param>
/// <param name="chTRIM">
/// Trim character that defaults to a space
/// </param>
/// <param name="bIS_NUMERIC">
/// If numeric, zeros are also considered as trimmable characters
/// </param>
/// <returns>Whether the character is trimmable or not</returns>
static bool IsTrimmable(char const chCHARACTER,
char const chTRIM = SPACE, bool const bIS_NUMERIC = true);
public:
/// <summary>Carriage return constant</summary>
//static char const CARRIAGE_RETURN = '\r';
/// <summary>Constant of comma or decimal point in German</summary>
static char const COMMA = ',';
/// <summary>Dash or minus constant</summary>
static char const DASH = '-';
/// <summary>
/// The exponent sign in a scientific number, or the letter e.
/// </summary>
static char const EXPONENT = 'e';
/// <summary>The full stop or period</summary>
static char const PERIOD = '.';
/// <summary>Space constant</summary>
static char const SPACE = ' ';
/// <summary>Space constant</summary>
static char const ZERO = '0';
/// <summary>
//// Value under which the double should switch to fixed-point.
/// </summary>
static double const VERY_LARGE;
/// <summary>
//// Value above which the double should switch to fixed-point.
/// </summary>
static double const NEARLY_ZERO;
/// <summary>
/// The zero string constant used at several places
/// </summary>
static string const strZERO;
/// <summary>
/// Trims the trim character from left and right of the value.
/// </summary>
/// <param name="strVALUE">Value to be trimmed</param>
/// <param name="chTRIM">
/// Trim character that defaults to a space
/// </param>
/// <returns>Trimmed string</returns>
static string const& Trim(string const& strVALUE,
char const chTRIM = Common::SPACE);
/// <summary>
/// Trims the trim character from left the value.
/// </summary>
/// <param name="strVALUE">Value to be trimmed</param>
/// <param name="chTRIM">
/// Trim character that defaults to a space
/// </param>
/// <returns>Trimmed string</returns>
static string const& TrimLeft(string const& strVALUE,
char const chTRIM = Common::SPACE);
/// <summary>
/// Trims the trim character from right of the value.
/// </summary>
/// <param name="strVALUE">Value to be trimmed</param>
/// <param name="chTRIM">
/// Trim character that defaults to a space
/// </param>
/// <returns>Trimmed string</returns>
static string const& TrimRight(string const& strVALUE,
char const chTRIM = Common::SPACE);
}; // class Common
}
The Math
class is an enhancement to the <math.h>
library and contains the rounding calculations.
Math.cpp
:
#include "StdAfx.h"
#include <string>
namespace common
{
string const Maths::strZEROS = "000000000000000000000000000000000";
byte Maths::CalculateMissingSignificantZeros(
byte const ySIGNIFICANTS_AFTER,
char const chSEPARATOR,
double const dVALUE,
string const strMANTISSA)
{
// Existing significants after decimal separator are
byte const yAFTER = FindSignificantsAfterDecimal(chSEPARATOR, strMANTISSA);
// Number of digits to add are
byte const yZEROS = ySIGNIFICANTS_AFTER - ((yAFTER == 0) ? 1 : yAFTER);
return ((yZEROS >= 0) ? yZEROS : 0);
}
byte Maths::FindDecimalSeparatorPosition(string const& strVALUE)
{
byte const ySEPARATOR_AT = (byte)strVALUE.find(Common::PERIOD);
return (ySEPARATOR_AT > -1)
? ySEPARATOR_AT : (byte)strVALUE.find(Common::COMMA);
}
byte Maths::FindFirstNonZeroDigit(double const dVALUE)
{
return FindFirstNonZeroDigit(StringConverter::ToString
<double, StringConverter::DIGITS>(dVALUE));
}
byte Maths::FindFirstNonZeroDigit(string const& strVALUE)
{
// Find the position of the first non-zero digit:
byte yNonZeroAt = 0;
for (; (yNonZeroAt < (byte)strVALUE.length())
&& ((strVALUE[yNonZeroAt] == Common::DASH)
|| (strVALUE[yNonZeroAt] == Common::PERIOD)
|| (strVALUE[yNonZeroAt] == Common::ZERO)); yNonZeroAt++) ;
return yNonZeroAt;
}
byte Maths::FindSignificantDigits(byte const ySIGNIFICANTS_AFTER,
char const chSEPARATOR,
double const dVALUE)
{
if (dVALUE == 0) return 0;
else
{
//JAVA TO C++ CONVERTER TODO TASK: No native C++ equivalent to 'ToString':
string strMantissa = FindMantissa(TestCommons::SIGNIFICANTS,
chSEPARATOR, StringConverter::ToString<double,
StringConverter::DIGITS>(dVALUE));
if (dVALUE == static_cast<long>(dVALUE))
{
strMantissa =
strMantissa.substr(0, strMantissa.find(Common::COMMA));
}
strMantissa = RetrieveDigits(chSEPARATOR, strMantissa);
return strMantissa.substr(
FindFirstNonZeroDigit(strMantissa)).length();
}
}
byte Maths::FindSignificantsAfterDecimal(byte const ySIGNIFICANTS_BEFORE,
byte const ySIGNIFICANT_DIGITS)
{
byte const yAFTER_DECIMAL = ySIGNIFICANT_DIGITS - ySIGNIFICANTS_BEFORE;
return (yAFTER_DECIMAL > 0) ? yAFTER_DECIMAL : 0;
}
byte Maths::FindSignificantsAfterDecimal(char const chSEPARATOR,
double const dVALUE)
{
if (dVALUE == 0) return 1;
else
{
//JAVA TO C++ CONVERTER TODO TASK: No native C++ equivalent to 'ToString':
string strValue = StringConverter::ToString<double,
StringConverter::DIGITS>(dVALUE);
byte const ySEPARATOR_AT = (byte)strValue.find(chSEPARATOR);
if (ySEPARATOR_AT > -1)
{
strValue = strValue.substr(ySEPARATOR_AT + 1);
}
short const sE_AT = strValue.find(Common::EXPONENT);
if (sE_AT > 0) strValue = strValue.substr(0, sE_AT);
long lValue = StringConverter::FromString
<long, StringConverter::DIGITS>(strValue);
if (abs(dVALUE) < 1)
{
return (byte)StringConverter::ToString<long,
StringConverter::DIGITS>(lValue).length();
}
else if (lValue == 0) return 0;
else
{
strValue = "0." + strValue;
return (byte)(strValue.length() - 2);
}
}
}
byte Maths::FindSignificantsBeforeDecimal(char const chSEPARATOR,
double const dVALUE)
{
string const strVALUE = StringConverter::ToString<double,
StringConverter::DIGITS>(dVALUE);
// Return immediately, if result is clear: Special handling at
// crossroads of floating point and exponential numbers:
if ((dVALUE == 0)
|| (abs(dVALUE) >= Common::NEARLY_ZERO) && (abs(dVALUE) < 1))
{
return 0;
}
else if ((abs(dVALUE) > 0) && (abs(dVALUE) < Common::NEARLY_ZERO))
{
return 1;
}
else
{
byte significants = 0;
// Significant digits to the right of decimal separator:
for (byte s = 0; s < (byte)strVALUE.length(); s++)
{
if ((strVALUE[s] == chSEPARATOR)
|| (strVALUE[s] == Common::EXPONENT))
{
break;
}
else if (strVALUE[s] != Common::DASH) significants++;
}
return significants;
}
}
byte Maths::FindSignificantsAfterDecimal(char const chSEPARATOR,
string const strVALUE)
{
size_t const COMMA_AT = strVALUE.find(chSEPARATOR);
size_t const LENGTH = strVALUE.length();
// Existing digits after decimal separator are
byte yAfter = 0;
// Existing significants after decimal separator may start at the first
// non-zero digit:
if (StringConverter::FromString<double, 5>
(strVALUE.substr(0, COMMA_AT)) == 0)
{
string strRightOf = Common::TrimLeft(strVALUE.substr(COMMA_AT + 1));
yAfter = strRightOf.length();
}
else yAfter = (COMMA_AT < string::npos) ? LENGTH - 1 - COMMA_AT : 0;
return yAfter;
}
double Maths::Power(short const sBASIS, short const sEXPONENT)
{
if (sBASIS == 0) return (sEXPONENT != 0) ? 1 : 0;
else
{
if (sEXPONENT == 0) return 1;
else
{
// The Math method power does change the least significant
// digits after the decimal separator and is therefore useless.
double result = 1;
if (sEXPONENT > 0)
{
for (short s = 0; s < sEXPONENT; s++) result *= sBASIS;
}
else if (sEXPONENT < 0)
{
for (short s = sEXPONENT; s < 0; s++) result /= sBASIS;
}
return result;
}
}
}
double Maths::Round(byte const yDIGITS,
char const chSEPARATOR,
double const dVALUE)
{
if (dVALUE == 0) return 0;
else
{
bool bIsScientific = false;
double const dCONSTANT = Power(10, yDIGITS);
if ((abs(dVALUE) < Common::NEARLY_ZERO)
|| (abs(dVALUE) >= Common::VERY_LARGE))
{
bIsScientific = true;
}
short const sEXPONENT =
FindExponent(dVALUE, chSEPARATOR, bIsScientific);
short sExponent = sEXPONENT;
// Determine the correcting power:
short sPower = sExponent;
if (sEXPONENT == 0)
{
sPower = FindExponent(dVALUE, chSEPARATOR, HasDecimals(dVALUE));
}
double dValue1 = dVALUE*dCONSTANT*pow(10., -sPower);
string const strE_SIGN = (sExponent < 0)
? StringConverter::ToString<char,
StringConverter::DIGITS>(Common::DASH) : "";
if (sExponent != 0)
{
sExponent = static_cast<short>(abs(sExponent));
dValue1 = Round(dValue1);
}
else dValue1 = Round(dValue1)/dCONSTANT/pow(10., -sPower);
// Power method cannot be used, as the exponentiated number may
// exceed the maximal long value.
sExponent -= Signum(sEXPONENT)*(FindSignificantDigits(
yDIGITS, chSEPARATOR, dValue1) - 1);
if (sEXPONENT != 0)
{
//JAVA TO C++ CONVERTER TODO TASK: No native C++ equivalent to 'ToString':
string strValue = StringConverter::ToString<double,
StringConverter::DIGITS>(dValue1);
//JAVA TO C++ CONVERTER TODO TASK: No native C++ equivalent to 'ToString':
strValue =
strValue.substr(0, FindDecimalSeparatorPosition(strValue))
+ Common::EXPONENT + strE_SIGN
+ StringConverter::ToString<short,
StringConverter::DIGITS>(sExponent);
dValue1 = StringConverter::FromString<double, 5>(strValue);
}
return dValue1;
}
}
double Maths::Round(double const dValue)
{
return (double)(long long)(dValue + .5);
}
short Maths::FindExponent(double const dVALUE,
char const chSEPARATOR,
bool const bSCIENTIFIC)
{
//JAVA TO C++ CONVERTER TODO TASK: No native C++ equivalent to 'ToString':
return (short)StringConverter::FromString<short,
StringConverter::DIGITS>(FindExponent(StringConverter::ToString
<double, StringConverter::DIGITS>(dVALUE), chSEPARATOR,
bSCIENTIFIC));
}
string Maths::FindExponent(string const& strVALUE,
char const chSEPARATOR,
bool const bSCIENTIFIC)
{
if (StringConverter::FromString
<double, StringConverter::DIGITS>(strVALUE) == 0)
{
return Common::strZERO;
}
short const sE_AT = strVALUE.find(Common::EXPONENT);
short sExponent = 0;
if (sE_AT < 0)
{
// If all numbers are to be considered scientific, such as
// 1 = 1.0000e0…
if (bSCIENTIFIC)
{
// Find the exponent by counting leading zeros:
byte const ySEPARATOR_AT = strVALUE.find(chSEPARATOR);
if (ySEPARATOR_AT > -1)
{
string const strAFTER = strVALUE.substr(ySEPARATOR_AT + 1);
sExponent = 0;
for (; sExponent < (short)strAFTER.length(); sExponent++)
{
if ((strAFTER[sExponent] >= '1')
&& (strAFTER[sExponent] <= '9'))
{
sExponent *= -1;
break;
}
}
}
}
else return Common::strZERO;
}
else
{
sExponent = (short)StringConverter::FromString
<double, StringConverter::DIGITS>(strVALUE.substr(sE_AT + 1));
}
return StringConverter::ToString<short,
StringConverter::DIGITS>(sExponent);
}
string Maths::FindMantissa(byte const yDIGITS,
char const chSEPARATOR,
const string& strVALUE)
{
byte yDigits = yDIGITS;
short const sE_AT = strVALUE.find(Common::EXPONENT);
string strValue = strVALUE;
// Remove lagging insignificant zeros, if any:
if (sE_AT == -1)
{
if (StringConverter::FromString<short,
3>(strValue.substr(0, 2)) == 0)
{
byte yPosition = 2;
for (; (yPosition < (byte)strValue.length())
&& ((strValue[yPosition] == Common::PERIOD)
|| (strValue[yPosition] == Common::ZERO)); yPosition++);
yDigits += yPosition;
strValue = strValue.substr(0, yDigits);
}
}
else strValue = Common::TrimRight(strValue.substr(0, sE_AT));
if (StringConverter::FromString<double, StringConverter::DIGITS>(strValue) == 0)
{
strValue = RemoveInsignificants(yDIGITS, strValue);
}
else strValue = RemoveInsignificants(yDigits, strValue);
if (FindDecimalSeparatorPosition(strValue) == -1)
{
return strValue + ".0";
}
else return strValue;
}
string Maths::RemoveInsignificants(byte const yDIGITS,
string const& strVALUE)
{
byte const yCHARACTERS = yDIGITS
+ ((FindDecimalSeparatorPosition(strVALUE) < string::npos) ? 1 : 0)
+ ((strVALUE[0] == Common::DASH)
? ((strVALUE[1] == Common::ZERO) ? 2 : 1)
: ((strVALUE[0] == Common::ZERO) ? 1 : 0));
return strVALUE.substr(0, yCHARACTERS);
}
string Maths::RetrieveDigits(char const chSEPARATOR, const string& strNUMBER)
{
string strNumber = strNUMBER;
short const sE_AT = strNumber.find(Common::EXPONENT);
// Strip off exponent part, if it exists:
if (sE_AT > -1) strNumber = strNumber.substr(0, sE_AT);
//JAVA TO C++ CONVERTER TODO TASK: No native C++ equivalent to 'ToString':
//JAVA TO C++ CONVERTER TODO TASK: No native C++ equivalent to 'replace':
return StringConverter::Replace(StringConverter::Replace(strNumber,
StringConverter::ToString<char, 1>(Common::DASH), ""),
StringConverter::ToString<char, 1>(chSEPARATOR), "");
}
string Maths::RoundToString(byte const ySIGNIFICANT_DIGITS,
char const chSEPARATOR,
double dValue)
{
// Number of significants that *are* before the decimal separator:
//JAVA TO C++ CONVERTER TODO TASK: No native C++ equivalent to 'ToString':
byte const ySIGNIFICANTS_BEFORE =
FindSignificantsBeforeDecimal(chSEPARATOR, dValue);
// Number of decimals that *should* be after the decimal separator:
byte const ySIGNIFICANTS_AFTER = FindSignificantsAfterDecimal(
ySIGNIFICANTS_BEFORE, ySIGNIFICANT_DIGITS);
byte const yDIGITS = (dValue != 0)
? ySIGNIFICANTS_BEFORE + ySIGNIFICANTS_AFTER : 3 /* = 0.0 */;
// Round to the specified number of digits after decimal separator:
double const dROUNDED =
Maths::Round(ySIGNIFICANTS_AFTER, chSEPARATOR, dValue);
//JAVA TO C++ CONVERTER TODO TASK: No native C++ equivalent to 'ToString':
string const strEXPONENT = FindExponent(StringConverter::ToString
<double, StringConverter::DIGITS>(dROUNDED));
//JAVA TO C++ CONVERTER TODO TASK: No native C++ equivalent to 'ToString':
string const strMANTISSA = FindMantissa(TestCommons::SIGNIFICANTS,
chSEPARATOR, StringConverter::ToString<double,
StringConverter::DIGITS>(dROUNDED));
double const dMANTISSA = StringConverter::FromString
<double, TestCommons::SIGNIFICANTS>(strMANTISSA);
StringBuilder* pRESULT = new StringBuilder(strMANTISSA);
// Determine the significant digits in this number:
byte const ySIGNIFICANTS = FindSignificantDigits(ySIGNIFICANTS_AFTER,
chSEPARATOR, dMANTISSA);
// Add lagging zeros, if necessary:
if (ySIGNIFICANTS <= ySIGNIFICANT_DIGITS)
{
if (ySIGNIFICANTS_AFTER != 0)
{
if (dValue != 0)
{
pRESULT->Append(strZEROS.substr(0,
CalculateMissingSignificantZeros(ySIGNIFICANTS_AFTER,
chSEPARATOR, dMANTISSA, strMANTISSA)));
}
}
else
{
// Cut off the decimal separator & after decimal digits:
//JAVA TO C++ CONVERTER TODO TASK: No native C++ equivalent to 'ToString':
byte const yDECIMAL =
pRESULT->Find(StringConverter::ToString<char,
StringConverter::DIGITS>(chSEPARATOR));
if (yDECIMAL > -1) pRESULT->SetLength(yDECIMAL);
}
}
else if (ySIGNIFICANTS_BEFORE > ySIGNIFICANT_DIGITS)
{
dValue /= Power(10, ySIGNIFICANTS_BEFORE - ySIGNIFICANT_DIGITS);
dValue = Round(dValue);
byte const yDIGITS = ySIGNIFICANT_DIGITS + ((dValue < 0) ? 1 : 0);
//JAVA TO C++ CONVERTER TODO TASK: No native C++ equivalent to 'ToString':
string const strVALUE = StringConverter::ToString<double,
StringConverter::DIGITS>(dValue).substr(0, yDIGITS);
pRESULT->SetLength(0);
pRESULT->Append(strVALUE + strZEROS.substr(0,
ySIGNIFICANTS_BEFORE - ySIGNIFICANT_DIGITS));
}
if (StringConverter::FromString<double,
StringConverter::DIGITS>(strEXPONENT) != 0)
{
pRESULT->Append(Common::EXPONENT + strEXPONENT);
}
//JAVA TO C++ CONVERTER TODO TASK: No native C++ equivalent to 'ToString':
return pRESULT->ToString();
} // public static String RoundToString(…)
}
Math.h
:
#pragma once
#include <string>
#include <cmath>
///
namespace common
{
/// <summary>
/// Class for special mathematical calculations.
/// ATTENTION: Should not depend on any other class except Java libraries!
/// @author Saban
///</summary>
class Maths
{
private:
/// <summary>The string of zeros</summary>
static string const strZEROS;
/// <summary>
/// Determines how many zeros are to be appended after the decimal
/// digits.
/// </summary>
/// <param name="ySIGNIFICANTS_AFTER">
/// Requested significant digits after decimal
/// </param>
/// <param name="chSEPARATOR">
/// Language-specific decimal separator
/// </param>
/// </param>
/// <param name="dVALUE">Rounded number</param>
/// <param name="strMANTISSA">
/// Current string where missing digits are to be determined
/// </param>
/// <returns>Requested value</returns>
static byte CalculateMissingSignificantZeros(
byte const ySIGNIFICANTS_AFTER, char const chSEPARATOR,
double const dVALUE,
string const strMANTISSA = "");
/// <summary>
/// Finds the decimal position language-independently.
/// </summary>
/// <param name="strVALUE">
/// Value to be searched for the decimal separator
/// </param>
/// <returns>
/// The position of the decimal separator or string::npos,
/// if no decimal separator has been found.
/// </returns>
static byte FindDecimalSeparatorPosition(string const& strVALUE);
/// <summary>
/// Finds the first non-zero decimal position.
/// </summary>
/// <param name="dVALUE">
/// Value to be searched for the decimal position
/// </param>
/// <returns>The first non-zero decimal position</returns>
static byte FindFirstNonZeroDigit(double const dVALUE);
/// <summary>
/// Finds the first non-zero decimal position.
/// </summary>
/// <param name="strVALUE">
/// Value to be searched for the decimal position
/// </param>
/// <returns>The first non-zero decimal position</returns>
static byte FindFirstNonZeroDigit(string const& strVALUE);
/// <summary>
/// Calculates the number of all significant digits (without the sign
/// and the decimal separator).
/// </summary>
/// <param name="ySIGNIFICANTS_AFTER">
/// Number of decimal places after the separator</param>
/// <param name="chSEPARATOR">
/// Language-specific decimal separator
/// </param>
/// <param name="dVALUE">
/// Value where the digits are to be counted
/// </param>
/// <returns>Number of significant digits</returns>
static byte FindSignificantDigits(byte const ySIGNIFICANTS_AFTER,
char const chSEPARATOR, double const dVALUE);
/// <summary>
/// Determines the number of significant digits after the decimal
/// separator knowing the total number of significant digits and the
/// number before the decimal separator.
/// </summary>
/// <param name="ySIGNIFICANTS_BEFORE">
/// Number of significant digits before separator
/// </param>
/// <param name="ySIGNIFICANT_DIGITS">
/// Number of all significant digits
/// </param>
/// Number of significant decimals after the separator
/// </returns>
static byte FindSignificantsAfterDecimal(
byte const ySIGNIFICANTS_BEFORE, byte const ySIGNIFICANT_DIGITS);
/// <summary>
/// Finds the significant digits after the decimal separator of a
/// mantissa.
/// </summary>
/// <param name="chSEPARATOR">
/// Language-specific decimal separator
/// </param>
/// <param name="dVALUE">Value to be scrutinised</param>
/// <returns>
/// Number of insignificant zeros after decimal separator.
/// </returns>
static byte FindSignificantsAfterDecimal(char const chSEPARATOR,
double const dVALUE);
/// <summary>
/// Finds the significant digits after the decimal separator of a
/// mantissa.
/// </summary>
/// <param name="chSEPARATOR">
/// Language-specific decimal separator
/// </param>
/// <param name="strVALUE">Value to be scrutinised</param>
/// <returns>
/// Number of insignificant zeros after decimal separator.
/// </returns>
static byte FindSignificantsAfterDecimal(char const chSEPARATOR,
string const strVALUE);
/// <summary>
/// Determines the number of digits before the decimal point.</summary>
/// <param name="chSEPARATOR">
/// Language-specific decimal separator
/// </param>
/// <param name="dVALUE">Value to be scrutinised</param>
/// <returns>Number of digits before the decimal separator</returns>
static byte FindSignificantsBeforeDecimal(char const chSEPARATOR,
double const dVALUE);
/// <summary>
/// Returns the exponent part of the double number.</summary>
/// <param name="dVALUE">
/// Value of which the exponent is of interest
/// </param>
/// <param name="chSEPARATOR">Decimal separator</param>
/// <param name="bSCIENTIFIC">
/// If true, the number is considered in scientific notation of the form
/// 9.999e999 (like 1 = 1.0e0 or 0.124 = 1.24e-1).
/// </param>
/// <returns>Exponent of the number or zero.</returns>
static short FindExponent(double const dVALUE,
char const chSEPARATOR = Common::PERIOD,
bool const bSCIENTIFIC = false);
/// <summary>
/// Finds the exponent of a number.</summary>
/// <param name="strVALUE">
/// Value where an exponent is to be searched
/// </param>
/// <param name="chSEPARATOR">Decimal separator</param>
/// <param name="bSCIENTIFIC">
/// If true, the number is considered in scientific notation of the form
/// 1 = 1.0e0 or 0.124 = 1.24e-1.
/// </param>
/// <returns>Exponent, if it exists, or "0"</returns>
static string FindExponent(string const& strVALUE,
char const chSEPARATOR = Common::PERIOD,
bool const bSCIENTIFIC = false);
/// <summary>
/// Finds the mantissa of a number.</summary>
/// <param name="yDIGITS">
/// Number of all digits
/// </param>
/// <param name="chSEPARATOR">
/// Language-specific decimal separator
/// </param>
/// <param name="strVALUE">
/// Value where the mantissa is to be found
/// </param>
/// <returns>Mantissa of the number</returns>
static string FindMantissa(byte const yDIGITS,
char const chSEPARATOR,
string const& strVALUE);
/// <summary>
/// Removes all insignificant digits.</summary>
/// <param name="yDIGITS">
/// Number of significant digits
/// </param>
/// <returns>Number with the requested number of digits</returns>
static string RemoveInsignificants(byte const yDigits,
string const& strVALUE);
/// <summary>
/// Retrieves the digits of the value without decimal separator or
/// sign.
/// </summary>
/// <param name="chSEPARATOR"></param>
/// <param name="strNUMBER">Mantissa to be scrutinised</param>
/// <returns>The digits only</returns>
static string RetrieveDigits(char const chSEPARATOR,
string const& strNUMBER);
public:
/// <summary>
/// Determines whether the number has decimal places after
/// the separator or not.
/// </summary>
/// <param name="VALUE"></param>
/// <returns>true, if it has decimals and false otherwise.</returns>
template<class T>
static bool HasDecimals(const T VALUE)
{
return ((VALUE - (long long)VALUE) != 0);
}
/// <summary>
/// Calculates the power of the base to the exponent without changing
/// the least-significant digits of a number.
/// </summary>
/// <param name="BASIS"></param>
/// <param name="EXPONENT"></param>
/// <returns>BASIS to power of EXPONENT</returns>
static double Power(short const sBASIS, short const sEXPONENT);
/// <summary>
/// Rounds a number to the decimal places.</summary>
/// <param name="yDIGITS">Number of all decimal places</param>
/// <param name="chSEPARATOR">
/// Language-specific decimal separator
/// </param>
/// <param name="dVALUE">Number to be rounded</param>
/// <returns>Rounded number to the requested decimal places</returns>
static double Round(byte const yDIGITS,
char const chSEPARATOR, double const dVALUE);
/// <summary>
/// Replacement for Math.round(double) of Java.</summary>
/// <param name="dValue">Number to be rounded</param>
/// <returns>Rounded number to the requested decimal places</returns>
static double Round(double const dVALUE);
/// <summary>Signum function</summary>
/// <param name="number">Value to be scrutinised</param>
/// <returns>Sign of the number</returns>
template<class T>
static byte Signum(T number)
{
return (number < T(0)) ? T(-1) : (number > T(0));
}
/// <summary>
/// Rounds to a fixed number of significant digits.</summary>
/// <param name="ySIGNIFICANT_DIGITS">
/// Requested number of significant digits
/// </param>
/// <param name="chSEPARATOR">
/// Language-specific decimal separator
/// </param>
/// <param name="dValue">Number to be rounded</param>
/// <returns>Rounded number</returns>
static string RoundToString(byte const ySIGNIFICANT_DIGITS,
char const chSEPARATOR, double dValue);
}; // class Maths
}
Using precompiled headers requires the StdAfx files:
StdAfx.cpp
:
// StdAfx.cpp : Quelldatei, die nur die Standard-Includes einbindet.
// Commons.pch ist der vorkompilierte Header.
// StdAfx.obj enthält die vorkompilierten Typinformationen.
#include "StdAfx.h"
StdAfx.h
:
// StdAfx.h : Includedatei für Standardsystem-Includedateien
// oder häufig verwendete projektspezifische Includedateien,
// die nur in unregelmäßigen Abständen geändert werden.
//
#pragma once
using namespace std;
typedef signed char byte;
typedef unsigned short ushort;
// Used headers:
#include "Common.h"
#include "Maths.h"
#include "StringBuilder.h"
#include "TestCommons.h"
#include "StringConverter.h"
#include <iostream>
The StringBuilder
class was added by the JAVA TO C++ CONVERTER during the crosscompilation:
StringBuilder.cpp
:
#include "StdAfx.h"
namespace common
{
StringBuilder::StringBuilder()
{
strMain = "";
}
StringBuilder::StringBuilder(string const& strVALUE)
{
Append(strVALUE);
}
size_t const StringBuilder::Find(string const& strSEARCH) const
{
return strMain.find(strSEARCH);
}
string const& StringBuilder::ToString() const
{
return strMain;
}
void StringBuilder::Append(string const& strVALUE)
{
strMain.append(strVALUE);
}
void StringBuilder::SetLength(const string::size_type SIZE)
{
strMain.resize(SIZE);
}
}
StringBuilder.h
:
#pragma once
namespace common
{
class StringBuilder
{
private:
string strMain;
public:
/// <summary>
/// Standard constructor
/// </summary>
StringBuilder();
/// <summary>
/// Constructor
/// </summary>
/// <param name="strSUBJECT">Value to be used</param>
StringBuilder(string const& strSUBJECT);
/// <summary>
/// Finds the search string inside itself.
/// </summary>
/// <param name="strSEARCH">Value to be used</param>
/// <returns>
/// The position of the searched text or -1, if the search string
/// has not been found.
/// </returns>
size_t const Find(string const& strSEARCH) const;
/// <summary>
/// Converts the contents of itself to a string.
/// </summary>
/// <returns>String content</returns>
string const& ToString() const;
/// <summary>
/// Appends a text to this object
/// </summary>
/// <param name="strVALUE">Value to be appended</param>
void Append(string const& strVALUE);
/// <summary>
/// Sets the length of the content of this object.
/// </summary>
/// <param name="SIZE">New reduced size</param>
void SetLength(const string::size_type SIZE);
};
}
Extensive testing of a software is crucial for qualitative code. To say that the code is tested does not give much information. The question is what is tested. Not in this case, but often it is also important to know where (in which environment) it was tested, and how - i.e. the test succession. Here is the code used to test the Maths
class.
TestCommons.cpp
:
#include "StdAfx.h"
namespace common
{
void TestCommons::Test()
{
// Test rounding
vector<double>* pa_dValues = new vector<double>();
vector<double>& a_dValues = *pa_dValues;
a_dValues.push_back(0.0);
AddValue(1.4012984643248202e-45, a_dValues);
AddValue(1.999999757e-5, a_dValues);
AddValue(1.999999757e-4, a_dValues);
AddValue(0.000640589, a_dValues);
AddValue(1.999999757e-3, a_dValues);
AddValue(0.3396899998188019, a_dValues);
AddValue(0.34, a_dValues);
AddValue(7.07, a_dValues);
AddValue(118.188, a_dValues);
AddValue(118.2, a_dValues);
AddValue(123.405009, a_dValues);
AddValue(30.76994323730469, a_dValues);
AddValue(130.76994323730469, a_dValues);
AddValue(540, a_dValues);
AddValue(12345, a_dValues);
AddValue(123456, a_dValues);
AddValue(540911, a_dValues);
AddValue(9.223372036854776e56, a_dValues);
byte const ySIGNIFICANTS = 5;
for (vector<double>::const_iterator element = a_dValues.begin();
element != a_dValues.end(); ++element)
{
cout << "Maths::RoundToString(" << (short)ySIGNIFICANTS << ", '"
<< Common::PERIOD << "', " << StringConverter::ToString
<double, StringConverter::DIGITS>(*element) << ") = ";
cout << Maths::RoundToString(ySIGNIFICANTS, Common::PERIOD,
*element) << endl;
}
pa_dValues->clear();
byte y;
cin >> y;
} // void Test()
void TestCommons::AddValue(double const dVALUE, vector<double>& a_dValues)
{
a_dValues.push_back(-dVALUE);
a_dValues.push_back(dVALUE);
}
}
TestCommons.h
:
#pragma once
#include <string>
#include <vector>
namespace common
{
/// <summary>
/// Test class for the common functionality
/// @author Saban
///</summary>
class TestCommons
{
private:
/// <summary>
/// Method that adds a negative and a positive value to values.</summary>
/// <param name="dVALUE"></param>
/// <param name="a_dValues"></param>
static void AddValue(double const dVALUE, vector<double>& a_dValues);
public:
/// <summary>Number of significant digits</summary>
static short const SIGNIFICANTS = 5;
/// <summary>
/// Test for the common functionality</summary>
/// <param name="args"></param>
static void Test();
}; // class TestCommons
}
The results of your better code should comply with the result I got:
Maths::RoundToString(5, '.', 0.00000000000000000) = 0.00000 Maths::RoundToString(5, '.', -1.40129846432482020e-045) = -1.4012e-45 Maths::RoundToString(5, '.', 1.40129846432482020e-045) = 1.4013e-45 Maths::RoundToString(5, '.', -1.99999975700000000e-005) = -1.9998e-5 Maths::RoundToString(5, '.', 1.99999975700000000e-005) = 2.0000e-5 Maths::RoundToString(5, '.', -0.00019999997570000) = -0.00019999 Maths::RoundToString(5, '.', 0.00019999997570000) = 0.00020000 Maths::RoundToString(5, '.', -0.00064058900000000) = -0.00064058 Maths::RoundToString(5, '.', 0.00064058900000000) = 0.00064059 Maths::RoundToString(5, '.', -0.00199999975700000) = -0.0019999 Maths::RoundToString(5, '.', 0.00199999975700000) = 0.0020000 Maths::RoundToString(5, '.', -0.33968999981880188) = -0.33967 Maths::RoundToString(5, '.', 0.33968999981880188) = 0.33968 Maths::RoundToString(5, '.', -0.34000000000000002) = -0.33999 Maths::RoundToString(5, '.', 0.34000000000000002) = 0.34000 Maths::RoundToString(5, '.', -7.07000000000000030) = -7.0699 Maths::RoundToString(5, '.', 7.07000000000000030) = 7.0700 Maths::RoundToString(5, '.', -118.18800000000000000) = -118.18 Maths::RoundToString(5, '.', 118.18800000000000000) = 118.19 Maths::RoundToString(5, '.', -118.20000000000000000) = -118.19 Maths::RoundToString(5, '.', 118.20000000000000000) = 118.20 Maths::RoundToString(5, '.', -123.40500900000001000) = -123.40 Maths::RoundToString(5, '.', 123.40500900000001000) = 123.41 Maths::RoundToString(5, '.', -30.76994323730469100) = -30.768 Maths::RoundToString(5, '.', 30.76994323730469100) = 30.770 Maths::RoundToString(5, '.', -130.76994323730469000) = -130.75 Maths::RoundToString(5, '.', 130.76994323730469000) = 130.77 Maths::RoundToString(5, '.', -540.00000000000000000) = -539.99 Maths::RoundToString(5, '.', 540.00000000000000000) = 540.00 Maths::RoundToString(5, '.', -12345.00000000000000000) = -12344 Maths::RoundToString(5, '.', 12345.00000000000000000) = 12345 Maths::RoundToString(5, '.', -123456.00000000000000000) = -123450 Maths::RoundToString(5, '.', 123456.00000000000000000) = 123460 Maths::RoundToString(5, '.', -540911.00000000000000000) = -540900 Maths::RoundToString(5, '.', 540911.00000000000000000) = 540910 Maths::RoundToString(5, '.', -9.22337203685477560e+056) = -9.2232e56 Maths::RoundToString(5, '.', 9.22337203685477560e+056) = 9.2234e56
If you are interested in a comparison of C++ with C#, take a look at C# programming rounding number example. If you want to compare C++ with Java, compare it to the rounding code at Java Programming rounding number example.
The string class
[edit | edit source]The string class is a part of the C++ standard library, used for convenient manipulation of sequences of characters, to replace the static, unsafe C method of handling strings. To use the string class in a program, the <string> header must be included. The standard library string class can be accessed through the std namespace.
The basic template class is basic_string<>
and its standard specializations are string
and wstring
.
Basic usage
[edit | edit source]Declaring a std string is done by using one of these two methods:
using namespace std;
string std_string;
or
std::string std_string;
Text I/O
[edit | edit source]This section will deal only with keyboard and text input. There are many other inputs that can be read (mouse movements and button clicks, etc), but these will not be covered in this section, even reading the special keys of the keyboard will be excluded.
Perhaps the most basic use of the string class is for reading text from the user and writing it to the screen. In the header file iostream, C++ defines an object named cin that handles input in much the same way that cout handles output.
// snipped designed to get an integer value from the user
int x;
std::cin >> x;
The >> operator will cause the execution to stop and will wait for the user to type something. If the user types a valid integer, it will be converted into an integer value and stored in x.
If the user types something other than an integer, the compiler will not report an error. Instead, it leaves the old content (a "random" meaningless value) in x and continues.
This can then be extended into the following program:
#include <iostream>
#include <string>
int main(){
std::string name;
std::cout << "Please enter your first name: ";
std::cin >> name;
std::cout << "Welcome " << name << "!" << std::endl;
return 0;
}
Although a string may hold a sequence containing any character—including spaces and nulls—when reading into a string using cin and the extraction operator (>>) only the characters before the first space will be stored. Alternatively, if an entire line of text is desired, the getline function may be used:
std::getline(std::cin, name);
Getting user input
[edit | edit source]Fortunately, there is a way to check and see if an input statement succeeds. We can invoke the good function on cin to check what is called the stream state. good returns a bool: if true, then the last input statement succeeded. If not, we know that some previous operation failed, and also that the next operation will fail.
Thus, getting input from the user might look like this:
#include <iostream>
using namespace std;
int main ()
{
int x;
// prompt the user for input
cout << "Enter an integer: ";
// get input
cin >> x;
// check and see if the input statement succeeded
if (cin.good() == false) {
cout << "That was not an integer." << endl;
return -1;
}
// print the value we got from the user
cout << x << endl;
return 0;
}
cin can also be used to input a string:
string name;
cout << "What is your name? ";
cin >> name;
cout << name << endl;
As with the scanf() function from the Standard C Library, this statement only takes the first word of input, and leaves the rest for the next input statement. So, if you run this program and type your full name, it will only output your first name.
You may also notice the >> operator doesn't handle errors as expected (for example, if you accidentally typed your name in a prompt for a number.) Because of these issues, it may be more suitable to read a line of text, and using the line for input — this is performed using the function called getline.
string name;
cout << "What is your name? ";
getline (cin, name);
cout << name << endl;
The first argument to getline is cin, which is where the input is coming from. The second argument is the name of the string variable where you want the result to be stored.
getline reads the entire line until the user hits Return or Enter. This is useful for inputting strings that contain spaces.
In fact, getline is generally useful for getting input of any kind. For example, if you wanted the user to type an integer, you could input a string and then check to see if it is a valid integer. If so, you can convert it to an integer value. If not, you can print an error message and ask the user to try again.
To convert a string to an integer you can use the strtol function defined in the header file cstdlib. (Note that the older function atoi is less safe than strtol, as well as being less capable.)
If you still need the features of the >> operator, you will need to create a string stream as available from <sstream>. The use of this stream will be discussed in a later chapter.
More advanced string manipulation
[edit | edit source]
We will be using this dummy string for some of our examples.
string str("Hello World!");
This invokes the default constructor with a const char*
argument. Default constructor creates a string which contains nothing, i.e. no characters, not even a '\0'
(however std::string is not null terminated).
string str2(str);
Will trigger the copy constructor. std::string
knows enough to make a deep copy of the characters it
stores.
string str2 = str;
This will copy strings using assignment operator. Effect of this code is same as using copy constructor in example above.
Size
[edit | edit source]string::size_type string::size() const;
string::size_type string::length() const;
So for example one might do:
string::size_type strSize = str.size();
string::size_type strSize2 = str2.length();
The methods size()
and length()
both return the size of the string object. There is no apparent difference. Remember that the last character in the string is size() - 1
and not size()
. Like in C-style strings, and arrays in general, std::string
starts counting from 0.
I/O
[edit | edit source]ostream& operator<<(ostream &out, string &str);
istream& operator>>(istream &in, string &str);
The shift operators (>>
and <<
) have been overloaded so you can perform I/O operations on istream
and ostream
objects, most notably cout
, cin
, and filestreams. Thus you could just do console I/O like this:
std::cout << str << endl;
std::cin >> str;
istream& getline (istream& in, string& str, char delim = '\n');
Alternatively, if you want to read entire lines at a time, use getline()
. Note that this is not a member function. getline()
will retrieve characters from input stream in
and assign them to str
until EOF
is reached or delim
is encountered. getline
will reset the input string before appending data to it. delim
can be set to any char
value and acts as a general delimiter. Here is some example usage:
#include <fstream>
//open a file
std::ifstream file("somefile.cpp");
std::string data, temp;
while( getline(file, temp, '#')) //while data left in file
{
//append data
data += temp;
}
std::cout << data;
Because of the way getline
works (i.e. it returns the input stream), you can nest multiple getline()
calls to get multiple strings; however this may significantly reduce readability.
Operators
[edit | edit source]char& string::operator[](string::size_type pos);
Chars
in string
s can be accessed directly using the overloaded subscript ([]
) operator, like in char
arrays:
std::cout << str[0] << str[2];
prints "Hl".
std::string
supports casting from the older C string type const char*
. You can also assign or append a simple char
to a string. Assigning a char*
to a string
is as simple as
str = "Hello World!";
If you want to do it character by character, you can also use
str = 'H';
Not surprisingly, operator+
and operator+=
are also defined! You can append another string
, a const char*
or a char
to any string.
The comparison operators >, <, ==, >=, <=, !=
all perform comparison operations on strings, similar to the C strcmp() function. These return a true/false value.
if(str == "Hello World!")
{
std::cout << "Strings are equal!";
}
Searching strings
[edit | edit source]string::size_type string::find(string needle, string::size_type pos = 0) const;
You can use the find()
member function to find the first occurrence of a string inside another. find()
will look for needle
inside this
starting from position pos
and return the position of the first occurrence of the needle
. For example:
std::string haystack = "Hello World!";
std::string needle = "o";
std::cout << haystack.find(needle);
Will simply print "4" which is the index of the first occurrence of "o" in str
. If we want the "o" in "World", we need to modify pos
to point past the first occurrence. str.find(find, 4)
would return 4, while str.find(find, 5)
would give 7. If the substring isn't found, find()
returns std::string::npos
.This simple code searches a string for all occurrences of "wiki" and prints their positions:
std::string wikistr = "wikipedia is full of wikis (wiki-wiki means fast)";
for(string::size_type i = 0, tfind; (tfind = wikistr.find("wiki", i)) != string::npos; i = tfind + 1)
{
std::cout << "Found occurrence of 'wiki' at position " << tfind << std::endl;
}
string::size_type string::rfind(string needle, string::size_type pos = string::npos) const;
The function rfind()
works similarly, except it returns the last occurrence of the passed string.
Inserting/erasing
[edit | edit source]string& string::insert(size_type pos, const string& str);
You can use the insert()
member function to insert another string into a string.
For example:
string newstr = " Human";
str.insert (5,newstr);
Would return Hello Human World!
string& string::erase(size_type pos, size_type n);
You can use erase()
to remove a substring from a string. For example:
str.erase (5,6);
Would return Hello!
string& string::substr(size_type pos, size_type n);
You can use substr()
to extract a substring from a string. For example:
string str = "Hello World!";
string part = str.substr(6,5);
Would return World.
Backwards compatibility
[edit | edit source]const char* string::c_str() const;
const char* string::data() const;
For backwards compatibility with C/C++ functions which only accept char*
parameters, you can use the member functions string::c_str()
and string::data()
to return a temporary const char*
string you can pass to a function. The difference between these two functions is that c_str()
returns a null-terminated string while data()
does not necessarily return a null-terminated string. So, if your legacy function requires a null-terminated string, use c_str()
, otherwise use data()
(and presumably pass the length of the string in as well).
String Concatenation
[edit | edit source]Strings can be concatenated(appended) together by simply using the + operator
string firstString = "Hello";
string secondString = " World!";
string finalString = firstString + secondString;
cout << finalString << endl;
Output here will be "Hello World"
Appending Strings
[edit | edit source]Another thing to note is that instead of the + operator or concatenation, the .append(str2)
class member function can be used to concatenate one string to another. The str2
object is permitted to be a string object or a C-string. This will add the string in the parenthesis to the string which is calling append
.
It should also be noted that the append function can be used to append a string at a specific character location in the string. If a programmer puts str.append(str2, p, n)
, n
number of characters from position p
in string str2
will be appended to the end of str
. For example, in the following code, there are two strings. 5 characters from the second string, starting with position 8 of str2
will be appended to the end of the first string, str
.
string str("Watch out for ");
string str2("Llamas, Bears, and Telemarketers!");
str.append(str2, 8, 5);
cout << str << endl;
The code above will append the word Bears
to the end of the first string, and then print Watch out for Bears
on the screen.
String Conversion to signed integer
[edit | edit source]Sometimes we want to convert strings into numbers. To do so we can use stoi() function which takes a string as an argument and returns the value.
string exString1 = "12023";
string exString2 = "1.23249";
string exString3 = "1232 test";
To convert these strings to a number we save the stoi of the string variable in an integer variable.
int exInt1 = stoi(exString1);
int exInt2 = stoi(exString2);
int exInt3 = stoi(exString3);
cout << "Before stoi string:" << exString1 << " and after stoi int:" << exInt1 << endl;
cout << "Before stoi string:" << exString2 << " and after stoi int: " << exInt2 << endl;
cout << "Before stoi string:" << exString3 << " and after stoi int:" << exInt3 << endl;
Output will be: Before stoi string :12023 and after stoi int: 12023 Before stoi string :1 and after stoi int: 1 Before stoi string :1232 and after stoi int: 1232
Integer Conversion to String
[edit | edit source]If we instead want to do the opposite and convert an integer to a string, we can use the to_string() function, which takes an integer as an argument and returns the integer as a string.
int exInt = 12023;
To convert this integer to a string, we call the to_string() function. The integer variable is passed into the function as an argument. The function will then return that integer as a string, which can then be assigned to a string variable.
string exString = to_string(exInt);
cout << "Before to_string int:" << exInt << " and after to_string string:" << exString << endl;
Output will be: Before to_string int:12023 and after to_string string:12023
String Formatting
[edit | edit source]Strings can only be appended to other strings, but not to numbers or other datatypes, so something like std::string("Foo") + 5
would not result in a string with the content "Foo5"
. To convert other datatypes into string there exist the class std::ostringstream
, found in the include file <sstream>
. std::ostringstream
acts exactly like std::cout
, the only difference is that the output doesn't go to the current standard output as provided by the operating system, but into an internal buffer, that buffer can be converted into a std::string
via the std::ostringstream::str()
method.
Example
[edit | edit source]#include <iostream>
#include <sstream>
int main()
{
std::ostringstream buffer;
// Use the std::ostringstream just like std::cout or other iostreams
buffer << "You have: " << 5 << " Helloworlds in your inbox";
// Convert the std::ostringstream to a normal string
std::string text = buffer.str();
std::cout << text << std::endl;
return 0;
}
Advanced use
[edit | edit source]Chapter Summary
[edit | edit source]- Structures
- Unions
- Classes (Inheritance, Member Functions, Polymorphism and this pointer)
- Operator overloading
- Standard Input/Output streams Library