Overloading operators

 

Classes, essentially, define new types to be used in C++ code. And types in C++ not only interact with code by means of constructions and assignments. They also interact by means of operators. For example, take the following operation on fundamental types:

int a, b, c;

a = b + c;

 

Here, different variables of a fundamental type (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:

structmyclass {

  string product;

  floatprice;

} a, b, c;

a = b + c;

 

Here, it is not obvious what the result of the addition operation on 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[]

 

Operators are overloaded by means of 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 ...*/ } 

For example, cartesian vectors are sets of two coordinates: 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>

usingnamespace std;

 

classCVector {

  public:

    intx,y;

    CVector () {};

    CVector (int a,int b) : x(a), y(b) {}  // function name CVector (constructor)

    CVector operator + (constCVector&);  // function that returns a CVector

};

 

CVector CVector::operator+ (constCVector& 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;

}

 

you should get:

 

4.3

 

If confused about so many appearances of 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:

 

CVector (int, int) : x(a), y(b) {}  // function name CVector (constructor)

CVector operator+ (const CVector&); // function that returns a CVector  

 

The function 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:

c = a + b;

c = a.operator+ (b); // 这个就是上文中用到的

 

Both expressions are equivalent.

 

In an earlier chapter, the copy assignment function was introduced as one of the special member functions that are implicitly defined, even when not explicitly declared in the class. The behavior of this function by default is to copy the whole content of the data members of the object passed as argument (the one at the right side of the sign) to the one at the left side:

CVector d (2,3);

CVector e;

e = d;           // copy assignment operator, copy the          hole data members of the object passed as argument.

 

The copy assignment member is the only operator implicitly defined for all classes. Of course, it can be redefined to any other functionality, such as, for example, to copy only certain members or perform additional initialization operations.

 

The operator overload functions 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):

 

ExpressionOperatorMember functionNon-member function

@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) aTYPEA::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>

usingnamespace std;

 

classCVector {

  public:

    intx,y;

    CVector () {}

    CVector (int a, int b) : x(a), y(b) {}

};

 

 

CVector operator+ (constCVector& lhs, constCVector& 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;

}

 

you should get

 

4,3

 

 

The keyword this

 

The keyword 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>

usingnamespace std;

 

classDummy {

  public:

    bool isitme (Dummy& param);

};

 

bool Dummy::isitme (Dummy& param)

{

  if (&param == this) returntrue;

  elsereturnfalse;

}

 

int main () {

  Dummy a;

  Dummy* b = &a;

  if ( b->isitme(a) )

    cout << "yes, &a is b\n";

  return 0;

}

 

you should get:

 

yes, &a is b

 

It is also frequently used in 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:

 

CVector& CVector::operator= (const CVector& param)

{

    x=param.x;

    y=param.y;

    return *this;

}

 

 

In fact, this function is very similar to the code that the compiler generates implicitly for this class for operator=.

 

Static members

 

A class can contain static members, either data or functions.

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>

usingnamespace std;

 

classDummy {

  public:

    staticintn;

    Dummy () { ++n; };

    ~Dummy () { --n; };

};

 

intDummy::n=0;

 

int main () {

  Dummy a;

  Dummy b[5];

  Dummy * c = newDummy;

  cout << a.n << '\n';

  delete c;

  cout << Dummy::n << '\n';

  return 0;

}

 

you should get

 

7

6

 

In fact, static members have the same properties as non-member variables but they enjoy class scope. For that reason, and to avoid them to be declared several times, they cannot be initialized directly in the class, but need to be initialized somewhere outside it. As in the previous example:

int Dummy::n=0;

 

Because it is a common variable value for all the objects of the same class, it can be referred to as a member of any object of that class or even directly by the class name (of course this is only valid for static members):

cout << a.n;

cout << Dummy::n;

 

 

These two calls above are referring to the same variable: the static variable 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 member functions

 

When an object of a class is qualified as a const object:

 

 

const MyClass myobject;

The access to its data members from outside the class is restricted to read-only, as if all its data members were 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:

// constructor on const object

#include<iostream>

usingnamespace std;

 

classMyClass {

  public:

    intx;

    MyClass(int val) : x(val) {}

    int get() {returnx;}

};

 

int main() {

  constMyClass foo(10);

// foo.x = 20;            // not valid: x cannot be modified

  cout << foo.x << '\n';  // ok: data member x can be read

  return 0;

}

 

you should get

 

10

 

The member functions of a 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:

 

 

int get() const {returnx;}

 

Note that 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:

 

int get() const {return x;}        // const member function

constint& get() {returnx;}       // member function returning a const&

constint& get() const {returnx;} // const member function returning a const& 

 

 

Member functions specified to be 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 members marked as const, but non-const objects are not restricted can access both const members and non-const members 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:

// const objects

#include<iostream>

usingnamespace std;

 

classMyClass {

    intx;

  public:

    MyClass(int val) : x(val) {}

    constint& get() const {returnx;}

};

 

void print (constMyClass& arg) {

  cout << arg.get() << '\n';

}

 

int main() {

  MyClass foo (10);

  print(foo);

 

  return 0;

}

 

you should get

 

10

 

If in this example, 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>

usingnamespace std;

 

classMyClass {

    intx;

  public:

    MyClass(int val) : x(val) {}

    constint& get() const {returnx;}

    int& get() {returnx;}

};

 

int main() {

  MyClass foo (10);

  constMyClass 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;

}

 

you should get

 

15

20

 

Class templates

 

Just like we can create function templates, we can also create class templates, allowing classes to have members that use template parameters as types. For example: 

template <classT>

classmypair {

    Tvalues [2];

  public:

    mypair (T first, T second)

    {

      values[0]=first; values[1]=second;

    }

};

 

 

The class that we have just defined serves to store two elements of any valid type. For example, if we wanted to declare an object of this class to store two integer values of type int with the values 115 and 36 we would write:

 

 

mypair<int> myobject (115, 36);

 

 

This same class could also be used to create an object to store any other type, such as:

 

 

mypair<double> myfloats (3.0, 2.18); 

 

 

The constructor is the only member function in the previous class template and it has been defined inline within the class definition itself. In case that a member function is defined outside the defintion of the class template, it shall be preceded with the template <...> prefix:

// class templates

#include<iostream>

usingnamespace std;

 

template <classT>

classmypair {

    Ta, b;

  public:

    mypair (T first, T second)

      {a=first; b=second;}

    T getmax ();

};

 

template <classT>

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;

}

 

you should get

 

100

 

 

Notice the syntax of the definition of member function getmax:

 

template <class T>

T mypair<T>::getmax () 

 

 

Confused by so many 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.

 

Template specialization

 

It is possible to define a different implementation for a template when a specific type is passed as template argument. This is called a template specialization.

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>

usingnamespace std;

 

// class template:

template <classT>

classmycontainer {

    Telement;

  public:

    mycontainer (T arg) {element=arg;}

    T increase () {return ++element;}

};

 

// class template specialization:

template <>

classmycontainer <char> {

    charelement;

  public:

    mycontainer (char arg) {element=arg;}

    char uppercase ()

    {

      if ((element>='a')&&(element<='z'))

      element+='A'-'a';

      returnelement;

    }

};

 

int main () {

  mycontainer<int> myint (7);

  mycontainer<char> mychar ('j');

  cout << myint.increase() << endl;

  cout << mychar.uppercase() << endl;

  return 0;

}

 

you should get:

 

8

J

 

 

This is the syntax used for the class template specialization:

 

 

template <> class mycontainer <char> { ... };

 

 

First of all, notice that we precede the class name with 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:

 

template <classT> classmycontainer { ... };

template <> classmycontainer <char> { ... };

 

The first line is the generic template, and the second one is the specialization.

 

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

(转) class II的更多相关文章

  1. Leetcode 笔记 113 - Path Sum II

    题目链接:Path Sum II | LeetCode OJ Given a binary tree and a sum, find all root-to-leaf paths where each ...

  2. Leetcode 笔记 117 - Populating Next Right Pointers in Each Node II

    题目链接:Populating Next Right Pointers in Each Node II | LeetCode OJ Follow up for problem "Popula ...

  3. 函数式Android编程(II):Kotlin语言的集合操作

    原文标题:Functional Android (II): Collection operations in Kotlin 原文链接:http://antonioleiva.com/collectio ...

  4. 统计分析中Type I Error与Type II Error的区别

    统计分析中Type I Error与Type II Error的区别 在统计分析中,经常提到Type I Error和Type II Error.他们的基本概念是什么?有什么区别? 下面的表格显示 b ...

  5. hdu1032 Train Problem II (卡特兰数)

    题意: 给你一个数n,表示有n辆火车,编号从1到n,入站,问你有多少种出站的可能.    (题于文末) 知识点: ps:百度百科的卡特兰数讲的不错,注意看其参考的博客. 卡特兰数(Catalan):前 ...

  6. [LeetCode] Guess Number Higher or Lower II 猜数字大小之二

    We are playing the Guess Game. The game is as follows: I pick a number from 1 to n. You have to gues ...

  7. [LeetCode] Number of Islands II 岛屿的数量之二

    A 2d grid map of m rows and n columns is initially filled with water. We may perform an addLand oper ...

  8. [LeetCode] Palindrome Permutation II 回文全排列之二

    Given a string s, return all the palindromic permutations (without duplicates) of it. Return an empt ...

  9. [LeetCode] Permutations II 全排列之二

    Given a collection of numbers that might contain duplicates, return all possible unique permutations ...

  10. History lives on in this distinguished Polish city II 2017/1/5

    原文 Some fresh air After your time underground,you can return to ground level or maybe even a little ...

随机推荐

  1. 有关SetTimer函数的用法

    1 )用WM_TIMER来设置定时器 先请看SetTimer这个API函数的原型 UINT_PTR SetTimer( HWND hWnd, // 窗口句柄 UINT_PTR nIDEvent, // ...

  2. Qt将表格table保存为excel(odbc方式)

    首先是保存excel的方法,可参照: http://dzmlmszp.blog.163.com/blog/static/179271962014819111812531/ ok,进入正题. 现在我有一 ...

  3. 让footer在底部(测试它人方法)

    要求:网页布局中,页脚在底部.内容不够一页时,在底部.内容超过一页时,出现卷动条,页脚也在被挤到底部 1.测试的这个文章介绍的办法   链接: http://www.cnblogs.com/cheny ...

  4. cocos2dx ——屏幕适配

    本文出自 “夏天的风” 博客,请务必保留此出处 http://shahdza.blog.51cto.com/2410787/1550089 手机的屏幕大小千差万别,如现在流行的安卓手机屏幕大部分长宽比 ...

  5. Spark添加/更改集群节点需要修改的配置文件

    笔记:在配置好了spark后,如果需要添加/删除一个结点需要修改如下配置文件 cd $HADOOP/etc/hadoop 进入hadoop配置文件夹下 修改 slaves,将对应的节点添加/删除 修改 ...

  6. WORD-如何解除WORD文档的锁定

    Word文档保护破解 般来说WORD文档有两种密码打开密码和文档保护密码下面介绍几种破解文档保护密码方法 方法1:插入文件法 启动WORD新建空白文档执行插入→文件打开插入文件对框定位需要解除保护文档 ...

  7. 25045操作标准子程序集41.C

    /* ;程 序 最 后 修 改 时 间 0-4-3 23:43 ;软 件 标 题:25045操作标准子程序集41 ;软 件 说 明:25045 I2C 串行EEPROM 驱动 ;___________ ...

  8. 【剑指offer】面试题32:从1到n整数中1出现的次数

    题目: 求出1~13的整数中1出现的次数,并算出100~1300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1.10.11.12.13因此共出现6次,但是对于后面问题他就没辙了.A ...

  9. Walls and Gates 解答

    Question You are given a m x n 2D grid initialized with these three possible values. -1 - A wall or ...

  10. 再造轮子之网易彩票-第二季(IOS 篇 by sixleaves)

    02-彩票项目第二季 2.封装SWPTabBar方式一 接着我们思考如何进行封装.前面已经将过了为什么要封装, 和封装达到的效果.这里我们主要有两种封装方式,分别是站在不同的角度上看待问题.虽然角度不 ...