Bogotobogo
contact@bogotobogo.com
- C++ Home
- String
- Constructor
- Operator Overloading
- Virtual Functions
- Dynamic Cast Operator
- Type Cast Operators
- Class auto_ptr
- References for Built-in Types
- Pass by Value vs. Pass by Reference
- Memory Allocation
- Friend Functions and Friend Classes
- Functors (Function Objects)
- Static Variables and Static Class Members
- Exceptions
- Stack Unwinding
- Pointers
- Pointers II - void pointers & arrays
- Pointers III - pointer to function & multi-dimensional arrays
- Taste of Assembly
- Small Programs
- Linked List Examples
- Binary Tree Example Code
- Templates
- Standard Template Library (STL) I
- Standard Template Library (STL) II - Maps
- Standard Template Library (STL) III - Iterators
- Standard Template Library (STL) IV - Algorithms
- Object Slicing and Virtual Table
- The this Pointer
- Stack Unwinding
- Upcasting and Downcasting
- Object Returning
- Private Inheritance
- Preprocessor - Macro
- C++_Keywords
- fstream: input & output
- Multi-Threaded Programming - Terminology
- Multi-Threaded Programming II - Native Thread for Win32 (A)
- Multi-Threaded Programming II - Native Thread for Win32 (B)
- Multi-Threaded Programming II - Native Thread for Win32 (C)
- Multi-Threaded Programming II - C++ Thread for Win32
- Multi-Threaded Programming III - C++ Class Thread for Pthreads
- Multithread Debugging
- Socket - Server & Client
- Embedded Systems Programming
- Boost
- make
- Debugging Crash & Memory Leak
- Libraries
- C++ API Testing
- Design Patterns in C++
- Algorithms in C++
- Programming Questions and Solutions
- Blackjack with Qt
| Keyword | Description |
|---|---|
| and | alternative to && operator |
| and_eq | alternative to &= operator |
| asm | insert an assembly instruction |
| auto | declare a local variable |
| bitand | alternative to bitwise & operator |
| bitor | alternative to | operator |
| bool | declare a boolean variable |
| break | break out of a loop |
| case | a block of code in a switch statement |
| catch | handles exceptions from throw |
| char | declare a character variable |
| class | declare a class |
| compl | alternative to ~ operator |
| const | declare immutable data or functions that do not change data |
| const_cast | cast from const variables |
| continue | bypass iterations of a loop |
| default | default handler in a case statement |
| delete | make dynamic memory available |
| do | looping construct |
| double | declare a double precision floating-point variable |
| dynamic_cast | perform runtime casts |
| else | alternate case for an if statement |
| enum | create enumeration types |
| explicit | only use constructors when they exactly match |
| export | allows template definitions to be separated from their declarations |
| extern | declares a variable or function and specifies that it has external linkage |
| false | a constant representing the boolean false value |
| float | declare a floating-point variable |
| for | looping construct | friend | grant non-member function access to private data |
| goto | jump to a different part of the program |
| if | execute code based on the result of a test |
| inline | optimize calls to short functions |
| int | declare an integer variable |
| long | declare a long integer variable |
| mutable | override a const variable |
| namespace | partition the global namespace by defining a scope |
| new | allocate dynamic memory for a new variable |
| not | alternative to ! operator |
| not_eq | alternative to != operator |
| operator | create overloaded operator functions |
| or | alternative to || operator |
| or_eq | alternative to |= operator |
| private | declare private members of a class |
| protected | declare protected members of a class |
| public | declare public members of a class |
| register | request that a variable be optimized for speed |
| reinterpret_cast | change the type of a variable |
| short | declare a short integer variable |
| signed | modify variable type declarations |
| sizeof | return the size of a variable or type |
| static | create permanent storage for a variable |
| static_cast | perform a nonpolymorphic cast |
| struct | define a new structure |
| switch | execute code based on different possible values for a variable |
| template | create generic functions |
| this | a pointer to the current object |
| throw | throws an exception |
| true | a constant representing the boolean true value |
| try | execute code that can throw an exception |
| typedef | create a new type name from an existing type |
| typeid | describes an object |
| typename | declare a class or undefined type |
| union | a structure that assigns multiple variables to the same memory location |
| unsigned | declare an unsigned integer variable |
| using | import complete or partial namespaces into the current scope |
| virtual | create a function that can be overridden by a derived class |
| void | declare functions or data with no associated data type |
| volatile | warn the compiler about variables that can be modified unexpectedly |
| void | declare functions or data with no associated data type |
| wchar_t | declare a wide-character variable |
| while | looping construct |
| xor | alternative to ^ operator |
| xor_eq | alternative to ^= operator |
const qualifier allows us to ask compiler to enforce a semantic constraint: a particular object should not be modified. It also allows us to tell other programmers that a value should remain invariant. The general form for creating a constant is:
const type name = value;
Note that we initialize a const in the declaration. So, the following line is an error:
const int cint; cint = 10; // too late
We'll get an error message something like this:
error: 'cint' : const object must be initialized if not extern error: 'cint' : you cannot assign to a variable that is const
If we don't provide a value when we declare the constant, it ends up with an unspecified value that we cannot modify.
Const correctness refers to use of the C++ const keyword to declare a variable or method as immutable. It is a compile-time construct that can be used to maintain the correctness of code that shouldn't modify certain variables. We can define variables as const, to indicate that they should not be modified, and we can also define methods as const, to mean that they should not modify any member variables of the class. Using const correctness is simply good programming practice. It can also provide documentation on the intent of our methods, and hence make them easier to use.
For pointers, we can specify whether the pointer itself is const, the data it points to is const, both, or neither:
char str[] = "constantness"; char *p = str; //non-const pointer to non-const data const char *pc = str; //non-const pointer to const data char * const cp = str; //const pointer to non-const data const char * const cpc = str; //const pointer to const data
When const appears to the left of the *, what's pointed to is constant, and if const appears to the right of the *, the pointer itself is constant. If const appears on both sizes, both are constants.
Using const with pointers has subtle aspects. Let's declare a pointer to a constant:
int year = 2012; const int *ptr = &year; *ptr = 2020; // not ok because ptr points to a const int
How about the following code:
const int year = 2012; int *p = &year; // not ok
C++ doesn't allow the last line for simple reason: if we can assign the address of year to p, then we can cheat and use p to modify the value of year. That doesn't make sense because year is declared as const. C++ prohibits us from assigning the address of a const to a non-const pointer.
Since STL iterators are modeled on pointers, an iterator behaves mush like a T* pointer. So, declaring an iterator as const is like declaring a pointer const. If we want an iterator that points to something that can't be altered (const T*), we want to use a const_iterator:
vector<int> v; vector<int>::const_iterator itc = v.begin(); *itc = 2012; // error: *itc is cost ++itc; // ok, itc is not const
How about T*const iterator:
vector<int> v; const vector<int>::iterator cit = v.begin; *cit = 2012; // ok ++cit; // error: cit is const
Making a function to display the array is simple. We pass the name of the array and the number of elements to the function. However, there are some implications. We need to guarantee that the display doesn't change the original array. In other words, we need to guard it from altering the values of array. That kind of protection comes automatically with ordinary parameters of the function because C++ passes them by value, and the function plays with a copy. But functions that use an array play with the original. To keep a function from accidentally modifying the contents of an array, we can use the keyword const:
void display_array(const int arr[], int sz);
This declaration says that the pointer arr points to constant data, which means that we can't use ar to alter the data.
A class designer indicates which member functions do not modify the class object by declaring them as const member functions. For example:
class Testing
{
public:
void foo() const {}
};
In that way, we can protect members of an object from being modified.
So, in the following example, we'll get an error. For VS, we get "Error: expression (val) must be a modifiable lvalue."
#include <iostream>
using namespace std;
class Testing
{
public:
Testing(int n):val(n){}
int getValue() const { return val; }
void setValue(int n) const { val = n; }
private:
int val;
};
int main()
{
Testing test1(10);
return 0;
}
Because, in the member function setValue() is trying to modify a member variable val though the function is declared as const. So, we should remove the const from the setValue() function.
There is another case which a member function appears against the const declaration:
#include <iostream>
using namespace std;
class Testing
{
public:
Testing(int n):val(n){}
void foo1() const { foo2(); }
void foo2() {}
private:
int val;
};
int main()
{
Testing test1(10);
return 0;
}
In this case, the member function foo2() is not doing anything. However, compiler thinks the foo2() is not safe because it does not have const declaration. In other words, compiler thinks that by calling non-constant function from const function, the code may try to change the value of the class object.
So, as a constant member function can't modify a data member of its class, a constant member function cannot make a call to a non-constant function.
However, there are exceptions to the rule:
- A constant member function can alter a static data member.
- If we qualify a data member with the mutable keyword, then even a constant member function can modify it.
The example below shows that a const member function can be overloaded with a non-const member function that has the same parameter list. In this case, the constness of the class object determines which of the two functions is invoked:
#include <iostream>
using namespace std;
class Testing
{
public:
Testing(int n):val(n){}
int getVal() const {
cout << "getVal() const" << endl;
return val;
}
int getVal() {
cout << "getVal() non-const" << endl;
return val;
}
private:
int val;
};
int main()
{
const Testing ctest(10);
Testing test(20);
ctest.getVal();
test.getVal();
return 0;
}
Output is:
getVal() const getVal() non-const
Other exmaples using const related to returning object, see Object Returning.
An enum is a very simple user-defined type, specifying its set of values as symbolic constants.
#include <iostream>
enum Month {
Jan = 1, Feb, Mar, Apr, May, June,
Jul, Aug, Sep, Oct, Nov, Dec
};
int main()
{
using namespace std;
Month f = Feb;
Month j = Jul;
cout << "f = " << f << endl;
// f = 2; // error: cannot convert from 'int' to 'Month'
int jj = j; // allowed: can get the numeric value of a 'Month'
Month jjj = Month(7); // Convering int to 'Month'
cout << "jj = " << jj << ", jjj = " << jjj << endl;
return 0;
}
Output is:
f = 2 jj = 7, jjj = 7
Let's look at another usage example of enum:
#define NumArrays 10
class ArrayObj
{
private:
int array[NumArrays];
};
int main()
{
ArrayObj a;
return 0;
}
Here, #define does its job. However, we can use const instead. const qualifier lets us specify the type explicitly as well as we can use scoping rules to limit the definition to particular functions or files. In other words, there's no way to create a class-specific constant using a #define, because #define doesn't respect scope.
class ArrayObj
{
private:
static const int NumArrays = 5;
int array[NumArrays];
};
We can also use enum for the array size:
class ArrayObj
{
private:
enum {NumArrays = 5};
int array[NumArrays];
};
Adding explicit is a good practice for any constructor that accepts a single argument. It is used to prevent a specific constructor from being called implicitly when constructing an object. For example, without the explicit keyword, the following is valid C++ code:
Array a = 10;
This will call the Array single-argument constructor with the integer argument of 10:
Array::Array(int size) {}
This type of implicit behavior, however, can be confusing, and in most cases, unintended. As a further example of this kind of undesired implicit conversion, let's consider the following function:
void checkArraySize(const Array &array, int size);
Without declaring the single-argument constructor of Array as explicit, we could call this function as
checkArray(10,10);
As another example, let's look at the following example which has a constructor that takes a single argument. It actually, defines a conversion from its argument type to its class:
class Complex
{
public:
Complex(double) // This defines double-to-complex conversion
Complex(double, double)
};
...
Complex cmplx = 3.14 // OK: convert 3.14 to (3.14,0)
Complex cmplx = Complex(1.0, 2.5);
But this kind of conversion may cause unexpected and undesirable effects as we see in the example below:
class Vector
{
int sz;
double *elem;
public:
Vector(int s): sz(s)
elem (new double[s]) {
for(int i=0; i< s; ++i) elem[i] = 0;
}
...
};
The Vector has a constructor that takes an int, which implies that it defines a conversion from int to Vector:
class Vector
{
...
Vector(int);
...
};
Vector v = 10 // makes a vector of 10 double ?
v = 20; // assignes a new Vector of 20 double to v ?
This weakens the type safety of our code because now the compiler will not enforce the type of the first argument to be an explicit Array/Vector object in the above examples. As a result, there is the potential for the user to forget the correct order of arguments and pass them in the wrong order. This is why we chould always use the explicit keyword for any single argument constructors unless we know that we want to support implicit conversion.
So, for the 2nd example, we put explicit:
class Vector
{
...
explicit Vector(int);
...
};
Vector v = 10 // error: no int-to-Vector,double> conversion
v = 20; // error: no int-to-Vector,double> conversion
Vector v(10) // OK
Though implementing a program as a set of functions is good from a software engineering standpoint, function calls involve execution-time overhead. So, C++ provides inline functions to help reduce function call overhead, especially for small functions. Placing the qualifier inline before a function's return type in the function definition tells the compiler to generate a copy of the function's code in place to avoid a function call.
The trade-off is that multiple copies of the function code are inserted in the program rather than there being a single copy of the function to which control is passed each time the function is called. The compiler can ignore the inline qualifier and typically does so for all but the smallest functions.
The complete definition of function should appear before it is used in the program. This is required so that the compiler knows how to expand a function call into its inlined code. For this reason, reusable inline functions are typically placed in header files, so that their definitions can be included in each source file that uses them.
In general, we provide declaration in our .h files and associated definition in our .cpp files. However, it's also possible to provide a definition for a method at the point where we declare it in a .h file:
class MyClass
{
public:
void MyMethod() { }
};
This implicitly requests the compiler to inline the MyMethod() member function at all places where it is called. In terms of API design, this is a bad practice since it exposes the code for how the method has been implemented and directly inlines the code into our clients' programs.
There are exceptions to this rule to support templates and intentional use of inline.
To allow a class data member to be modified even though it is the data member of a const object, we can declare the data member as mutable. A mutable member is a member that is never const, even when it is the data member of a const object. A mutable member can always be updated, even in a const member function.
struct account
{
char name[50];
mutable int id;
};
const account ac = {"Bush", 0, ....};
strcpy(ac.name, "Obama"} // not allowed
ac.id++; // allowed
The following example has an error because it tries to modify a variable which in a const member function:
#include <iostream>
#include <cstring>
class MyText
{
public:
std::size_t getLength() const;
private:
char * ptrText;
std::size_t txtLen;
};
std::size_t MyText::getLength() const
{
// error: l-value specifies const object
// cannot assign to txtLen because it is in a const member function
txtLen = std::strlen(ptrText);
return txtLen;
}
We can solve the problem. mutable frees non-static data members from the const constraints:
#include <iostream>
#include <cstring>
class MyText
{
public:
std::size_t getLength() const;
private:
char * ptrText;
mutable std::size_t txtLen;
};
std::size_t MyText::getLength() const
{
txtLen = std::strlen(ptrText); // ok
return txtLen;
}
As programming projects grow large, the potential for name conflicts increases. The language mechanism for organizing classes, functions, data, and types into an identifiable and named part of a program without defining a type is a namespace. The C++ standard provides namespace which allows us to have greater control over the scope of names.
The following code uses namespace to create two namespaces, Stock, and Market:
namespace Stock {
double penny;
void order();
int amount;
struct Option { ... };
}
namesapce Market {
double dollar;
void order();
int amount;
struct Purchase { ... };
}
The names in any one namespace don't conflict with names in another namespace. So, the order in Stock is not confused with the order in Market
Namespaces can be located at the global level or inside other namespaces, but they cannot be in a block. Therefore, a name declared in a namespace has external linkage by default.
Namespaces are open. In other words, we can add names to existing namespaces. For instance, we can add another name to the existing list of names in Stock:
namespace Stock {
string getCompanyName();
}
The original Stock namespace provides a prototypes for order() function. We can provide the code for the function later in the file or in another file by using the Stock namespace again:
namespace Stock {
void order() {
...
}
}
How do we access names in a given namespace?
We use the scope-resolution operator (::), to qualify a name with its namespace:
Stock::amount = 200; Market::Purchase p; Market::order();
Just variable name, such as penny is called unqualified name, which a name with the namespace, as in Market::dollar is called qualified name.
As a quick summary:
If we want to reference "j" in "main()", how do we do that?
namespace
{
int j;
}
int main()
{
// ???
return 0;
}
Answer:
int i = ::j;
The register keyword is a hint to the compiler that we want it to provide fast access to the variable, perhaps by using a CPU register instead of the stack to handle a particular variable. The CPU can access a value in one of its registers more quickly than it can access memory in the stack. Some compilers may ignore the hint and use register allocation algorithms to figure out the best candidates to be placed within the available machine registers. Because the compiler is aware of the machine architecture on which the program is run, it is often able to make a more informed decision when selecting the content of machine registers.
Usually, automatic objects used heavily within a function can be declared with the keyword register. If possible, the compiler will load the object into a machine register. If it cannot, the object remains in memory.
To declare a register variable, we preface the type with the keyword register:
register int heavy_use;
Array indexes and pointers occurring within a loop are good candidates for register objects.
for (register int i = 0; i < sz ; i++) ... for (register int *ip = array; p < arraySize ; p++)
If a variable is stored in a register, it doesn't have a memory address. So, we can't apply the address operator to a register variable. Therefore, in the following code, it's okay to take the address of the variable xStack but not of the register variable xRegister:
void f(int *);
int main()
{
int xStack;
int register xRegister;
f(&xStack) // ok
f(&xRegister) // not ok
...
}
The keyword struct introduces a structure declaration, which is a list of declarations enclosed in braces.
struct structure_name {
type1 member1;
type2 member2;
} object_name;
Specific example is like this:
struct data
{
int idata;
float fdata;
} myData;
myData.idata = 10;
myData.fdata = 3.14;
Once used like above, the data can represent for the declaration {...}, and it can be used later in definition of instances of the structure. For example, it can be used like this:
struct node yourData;
It defines a variable yourData which is a structure of type struct data.
We can also define array of structures with initializer:
#include <stdio.h>
struct data
{
int idata;
float fdata;
} myData[] = {
{10, 3.14},
{20, 0.314},
{30, 0.0314}
};
int main()
{
printf("%d,%f\n", myData[0].idata,myData[0].fdata);
printf("%d,%f\n", myData[1].idata,myData[1].fdata);
printf("%d,%f\n", myData[2].idata,myData[2].fdata);
printf("%d\n", sizeof data); // 8
printf("%d\n", sizeof myData); // 24
printf("%d\n", sizeof *myData); // 8
return 0;
}
The number of entries in the array myData[] will be computed if initializers are there and the [] is left empty as in the above example.
We can also calculate the size of myData[] array using:
sizeof myData / sizeof (struct data)
or
sizeof myData / sizeof myData[0]
The 2nd one is better because it does not need to be changed if the type changes. For more on the size of struct, see Size of struct
The struct is very useful when we build linked list:
struct list {
int data;
struct list *next;
};
The recursive declaration looks illegal because it's referring itself. But it's not. It's not containing an instance of itself, but
struct list *next;
declares next to be a pointer to a list, not a list itself.
We can define a new name for an existing type. We use typedef to create an alias:
typedef typeName aliasName;
So, we can make byte_pointer an alias for char *:
typedef char* byte_pointer;
Or we can create shorter names for types with longer names:
typedef unsigned short int ushort;
It defines ushort as another name for the type unsigned short int.
As an another example:
struct node
{
int data;
node *next;
};
typedef node node_t;
But typedef declaration does not create a new type. It just adds a new name for existing type. The primary reason of using typedef is to parameterize a code against portability issues. So, by just changing typedefs, we can minimize the change in our source code.
C++ provides two mechanisms to qualify names:
- using declaration lets us to make particular identifiers available.
using Stock::order; // a using declaration
- using directives makes the entire namespace accessible.
using namespace Stock; // make all the names in Stock available
The volatile keyword indicates that the value in a memory location can be altered even though nothing in the program code modifies the contents. In other words, volatile informs the compiler that the value of the variable can change from the outside, without any update done by the code.
For example, we could have a pointer to a hardware location. The hardware, not the program, may change the value of that address.
The intent of volatile keyword is to improve the optimization of compilers. In that optimization, compilers, can cache a value in a register if it's used several times with the same value, under the assumption the variable doesn't change during those uses. If we don't declare a variable as volatile, then the compiler may make the optimization. If we do declare a variable as volatile, we're telling the compiler not to make the optimization of the code referring to the object.
- C++ Home
- String
- Constructor
- Operator Overloading
- Virtual Functions
- Dynamic Cast Operator
- Type Cast Operators
- Class auto_ptr
- References for Built-in Types
- Pass by Value vs. Pass by Reference
- Memory Allocation
- Friend Functions and Friend Classes
- Functors (Function Objects)
- Static Variables and Static Class Members
- Exceptions
- Stack Unwinding
- Pointers
- Pointers II - void pointers & arrays
- Pointers III - pointer to function & multi-dimensional arrays
- Taste of Assembly
- Small Programs
- Linked List Examples
- Binary Tree Example Code
- Templates
- Standard Template Library (STL) I
- Standard Template Library (STL) II - Maps
- Standard Template Library (STL) III - Iterators
- Standard Template Library (STL) IV - Algorithms
- Object Slicing and Virtual Table
- The this Pointer
- Stack Unwinding
- Upcasting and Downcasting
- Object Returning
- Private Inheritance
- Preprocessor - Macro
- C++_Keywords
- fstream: input & output
- Multi-Threaded Programming - Terminology
- Multi-Threaded Programming II - Native Thread for Win32 (A)
- Multi-Threaded Programming II - Native Thread for Win32 (B)
- Multi-Threaded Programming II - Native Thread for Win32 (C)
- Multi-Threaded Programming II - C++ Thread for Win32
- Multi-Threaded Programming III - C++ Class Thread for Pthreads
- Multithread Debugging
- Socket - Server & Client
- Embedded Systems Programming
- Boost
- make
- Debugging Crash & Memory Leak
- Libraries
- C++ API Testing
- Design Patterns in C++
- Algorithms in C++
- Programming Questions and Solutions
- Blackjack with Qt