1
2
int a, b, c;
a = b + c;
int
) are applied the addition operator, and then the assignment operator. For a fundamental arithmetic type, the meaning of such operations is generally obvious and unambiguous, but it may not be so for certain class types. For example:
1
2
3
4
5
struct myclass {
string product;
float price;
} a, b, c;
a = b + c;
b
and c
does. In fact, this code alone would cause a compilation error, since the type myclass
has no defined behavior for additions. However, C++ allows most operators to be overloaded so that their behavior can be defined for just about any type, including classes. Here is a list of all the operators that can be overloaded:
Overloadable operators
+ - * / =
> += -= *= /=
>>
= >>= == !=
= >= ++ -- % & ^ ! |
~ &= ^= |= && || %= [] () , ->* -> new
delete new[] delete[]
operator
functions, which are regular functions with special names: their name begins by the operator
keyword followed by the operator sign that is overloaded. The syntax is:
type operator sign (parameters) { /*... body ...*/ }
x
and y
. The addition operation of two cartesian vectors is defined as the addition both x
coordinates together, and both y
coordinates together. For example, adding the cartesian vectors (3,1)
and (1,2)
together would result in (3+1,1+2) = (4,3)
. This could be implemented in C++ with the following code:
// overloading operators example
#include <iostream>
using namespace std;
class CVector {
public:
int x,y;
CVector () {};
CVector (int a,int b) : x(a), y(b) {}
CVector operator + (const CVector&);
};
CVector CVector::operator+ (const CVector& param) {
CVector temp;
temp.x = x + param.x;
temp.y = y + param.y;
return temp;
}
int main () {
CVector foo (3,1);
CVector bar (1,2);
CVector result;
result = foo + bar;
cout << result.x << ',' << result.y << '\n';
return 0;
}
4,3
CVector
, consider that some of them refer to the class name (i.e., the type) CVector
and some others are functions with that name (i.e., constructors, which must have the same name as the class). For example:
1
2
CVector (int, int) : x(a), y(b) {} // function name CVector (constructor)
CVector operator+ (const CVector&); // function that returns a CVector
operator+
of class CVector
overloads the addition operator (+
) for that type. Once declared, this function can be called either implicitly using the operator, or explicitly using its functional name:
1
2
c = a + b;
c = a.operator+ (b);
The operator overloads are just regular functions which can have any behavior; there is actually no requirement that the operation performed by that overload bears a relation to the mathematical or usual meaning of the operator, although it is strongly recommended. For example, a class that overloads operator+
to actually subtract or that overloads operator==
to fill the object with zeros, is perfectly valid, although using such a class could be challenging.
The parameter expected for a member function overload for operations such as operator+
is naturally the operand to the right hand side of the operator. This is common to all binary operators (those with an operand to its left and one operand to its right). But operators can come in diverse forms. Here you have a table with a summary of the parameters needed for each of the different operators than can be overloaded (please, replace @
by the operator in each case):
@a
+ - * & ! ~ ++ --
A::operator@()
operator@(A)
a@
++ --
A::operator@(int)
operator@(A,int)
a@b
+ - * / % ^ & | < > == != <= >= << >> && || ,
A::operator@(B)
operator@(A,B)
a@b
= += -= *= /= %= ^= &= |= <<= >>= []
A::operator@(B)
- a(b,c...)
()
A::operator()(B,C...)
- a->b
->
A::operator->()
- (TYPE) a
TYPE
A::operator TYPE()
- Where a
is an object of class A
, b
is an object of class B
and c
is an object of class C
. TYPE
is just any type (that operators overloads the conversion to type TYPE
).
Notice that some operators may be overloaded in two forms: either as a member function or as a non-member function: The first case has been used in the example above for operator+
. But some operators can also be overloaded as non-member functions; In this case, the operator function takes an object of the proper class as first argument.
For example:
// non-member operator overloads
#include <iostream>
using namespace std;
class CVector {
public:
int x,y;
CVector () {}
CVector (int a, int b) : x(a), y(b) {}
};
CVector operator+ (const CVector& lhs, const CVector& rhs) {
CVector temp;
temp.x = lhs.x + rhs.x;
temp.y = lhs.y + rhs.y;
return temp;
}
int main () {
CVector foo (3,1);
CVector bar (1,2);
CVector result;
result = foo + bar;
cout << result.x << ',' << result.y << '\n';
return 0;
}
4,3
this
represents a pointer to the object whose member function is being executed. It is used within a class's member function to refer to the object itself.
One of its uses can be to check if a parameter passed to a member function is the object itself. For example:
// example on this
#include <iostream>
using namespace std;
class Dummy {
public:
bool isitme (Dummy& param);
};
bool Dummy::isitme (Dummy& param)
{
if (¶m == this) return true;
else return false;
}
int main () {
Dummy a;
Dummy* b = &a;
if ( b->isitme(a) )
cout << "yes, &a is b\n";
return 0;
}
yes, &a is b
operator=
member functions that return objects by reference. Following with the examples on cartesian vector seen before, its operator=
function could have been defined as:
1
2
3
4
5
6
CVector& CVector::operator= (const CVector& param)
{
x=param.x;
y=param.y;
return *this;
}
operator=
.
A static data member of a class is also known as a "class variable", because there is only one common variable for all the objects of that same class, sharing the same value: i.e., its value is not different from one object of this class to another.
For example, it may be used for a variable within a class that can contain a counter with the number of objects of that class that are currently allocated, as in the following example:
// static members in classes
#include <iostream>
using namespace std;
class Dummy {
public:
static int n;
Dummy () { n++; };
};
int Dummy::n=0;
int main () {
Dummy a;
Dummy b[5];
cout << a.n << '\n';
Dummy * c = new Dummy;
cout << Dummy::n << '\n';
delete c;
return 0;
}
6 7
1
2
cout << a.n;
cout << Dummy::n;
n
within class Dummy
shared by all objects of this class.
Again, it is just like a non-member variable, but with a name that requires to be accessed like a member of a class (or an object).
Classes can also have static member functions. These represent the same: members of a class that are common to all object of that class, acting exactly as non-member functions but being accessed like members of the class. Because they are like non-member functions, they cannot access non-static members of the class (neither member variables nor member functions). They neither can use the keyword this
.
const
object:
1
const MyClass myobject;
const
for those accessing them from outside the class. Note though, that the constructor is still called and is allowed to initialize and modify these data members:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// constructor on const object
#include <iostream>
using namespace std;
class MyClass {
public:
int x;
MyClass(int val) : x(val) {}
int get() {return x;}
};
int main() {
const MyClass foo(10);
// foo.x = 20; // not valid: x cannot be modified
cout << foo.x << '\n'; // ok: data member x can be read
return 0;
}
10
const
object can only be called if they are themselves specified as const
members; in the example above, member get
(which is not specified as const
) cannot be called from foo
. To specify that a member is a const
member, the const
keyword shall follow the function prototype, after the closing parenthesis for its parameters:
1
int get() const {return x;}
const
can be used to qualify the type returned by a member function. This const
is not the same as the one which specifies a member as const
. Both are independent and are located at different places in the function prototype:
1
2
3
int get() const {return x;} // const member function
const int& get() {return x;} // member function returning a const&
const int& get() const {return x;} // const member function returning a const&
const
cannot modify non-static data members nor call other non-const
member functions. In essence, const
members shall not modify the state of an object.
const
objects are limited to access only member functions marked as const
, but non-const
objects are not restricted and thus can access both const
and non-const
member functions alike.
You may think that anyway you are seldom going to declare const
objects, and thus marking all members that don't modify the object as const is not worth the effort, but const objects are actually very common. Most functions taking classes as parameters actually take them by const
reference, and thus, these functions can only access their const
members:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// const objects
#include <iostream>
using namespace std;
class MyClass {
int x;
public:
MyClass(int val) : x(val) {}
const int& get() const {return x;}
};
void print (const MyClass& arg) {
cout << arg.get() << '\n';
}
int main() {
MyClass foo (10);
print(foo);
return 0;
}
10
get
was not specified as a const
member, the call to arg.get()
in the print
function would not be possible, because const
objects only have access to const
member functions.
Member functions can be overloaded on their constness: i.e., a class may have two member functions with identical signatures except that one is const
and the other is not: in this case, the const
version is called only when the object is itself const, and the non-const
version is called when the object is itself non-const
.
// overloading members on constness
#include <iostream>
using namespace std;
class MyClass {
int x;
public:
MyClass(int val) : x(val) {}
const int& get() const {return x;}
int& get() {return x;}
};
int main() {
MyClass foo (10);
const MyClass bar (20);
foo.get() = 15; // ok: get() returns int&
// bar.get() = 25; // not valid: get() returns const int&
cout << foo.get() << '\n';
cout << bar.get() << '\n';
return 0;
}
15 20
1
2
3
4
5
6
7
8
9
template <class T>
class mypair {
T values [2];
public:
mypair (T first, T second)
{
values[0]=first; values[1]=second;
}
};
int
with the values 115 and 36 we would write:
1
mypair<int> myobject (115, 36);
1
mypair<double> myfloats (3.0, 2.18);
template <...>
prefix:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
// class templates
#include <iostream>
using namespace std;
template <class T>
class mypair {
T a, b;
public:
mypair (T first, T second)
{a=first; b=second;}
T getmax ();
};
template <class T>
T mypair<T>::getmax ()
{
T retval;
retval = a>b? a : b;
return retval;
}
int main () {
mypair <int> myobject (100, 75);
cout << myobject.getmax();
return 0;
}
100
getmax
:
1
2
template <class T>
T mypair<T>::getmax ()
T
's? There are three T
's in this declaration: The first one is the template parameter. The second T
refers to the type returned by the function. And the third T
(the one between angle brackets) is also a requirement: It specifies that this function's template parameter is also the class template parameter.
For example, let's suppose that we have a very simple class called mycontainer
that can store one element of any type and that has just one member function called increase
, which increases its value. But we find that when it stores an element of type char
it would be more convenient to have a completely different implementation with a function member uppercase
, so we decide to declare a class template specialization for that type:
// template specialization
#include <iostream>
using namespace std;
// class template:
template <class T>
class mycontainer {
T element;
public:
mycontainer (T arg) {element=arg;}
T increase () {return ++element;}
};
// class template specialization:
template <>
class mycontainer <char> {
char element;
public:
mycontainer (char arg) {element=arg;}
char uppercase ()
{
if ((element>='a')&&(element<='z'))
element+='A'-'a';
return element;
}
};
int main () {
mycontainer<int> myint (7);
mycontainer<char> mychar ('j');
cout << myint.increase() << endl;
cout << mychar.uppercase() << endl;
return 0;
}
8 J
1
template <> class mycontainer <char> { ... };
template<>
, including an empty parameter list. This is because all types are known and no template arguments are required for this specialization, but still, it is the specialization of a class template, and thus it requires to be noted as such.
But more important than this prefix, is the <char>
specialization parameter after the class template name. This specialization parameter itself identifies the type for which the template class is being specialized (char
). Notice the differences between the generic class template and the specialization:
1
2
template <class T> class mycontainer { ... };
template <> class mycontainer <char> { ... };
When we declare specializations for a template class, we must also define all its members, even those identical to the generic template class, because there is no "inheritance" of members from the generic template to the specialization.
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4