C++ Tutorials: C++ Language: Classes: Classes (II)

C++官方参考链接:Classes (II) - C++ Tutorials (cplusplus.com)

类(II)
重载操作符
类本质上定义了C++代码中要使用的新类型。C++中的类型不仅通过构造和赋值的方式与代码交互。它们还通过操作符进行交互。例如,对基本类型执行以下操作:
int a, b, c;
a = b + c;
在这里,基本类型(int)的不同变量分别应用加法操作符和赋值操作符。对于基本算术类型,此类操作的含义通常是明显和明确的,但对于某些类类型则可能不是这样。例如:
struct myclass {
  string product;
  float price;
} a, b, c;
a = b + c;
在这里,对b和c的+操作的结果并不明显。事实上,这段代码本身就会导致编译错误,因为类型myclass没有为+定义的行为。然而,C++允许大多数操作符被重载,因此几乎可以为任何类型(包括类)定义它们的行为。下面是所有可以重载的操作符的列表:

Overloadable operators(可重载的操作符)
+    -    *    /    =    <    >    +=   -=   *=   /=   <<   >>
<<=  >>=  ==   !=   <=   >=   ++   --   %    &    ^    !    |
~    &=   ^=   |=   &&   ||   %=   []   ()   ,    ->*  ->   new 
delete    new[]     delete[]

操作符通过operator函数重载,这些函数是具有特殊名称的普通函数:它们的名称以operator关键字开头,后面跟着重载的操作符符号。它的语法是:
type operator sign (parameters) { /*... body ...*/ }
例如,笛卡儿向量是两个坐标的集合:x和y。两个笛卡儿向量的加法操作被定义为两个x坐标相加,两个y坐标相加。例如,将笛卡尔向量(3,1)和(1,2)相加得到(3+1,1+2)=(4,3)。这可以在C++中通过以下代码实现: 
// 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;
}

如果对CVector的这么多出现感到困惑,请考虑其中一些引用了类名(即类型)CVector,而其他一些是具有该名称的函数(即构造函数,必须与类具有相同的名称)。例如:
CVector (int, int) : x(a), y(b) {}  // function name CVector (constructor)
CVector operator+ (const CVector&); // function that returns a CVector  
CVector类的函数operator+重载该类型的加法操作符(+)。一旦声明了,这个函数可以使用操作符隐式调用,也可以使用函数名显式调用:
c = a + b;
c = a.operator+ (b);

两个表达式是等价的。
操作符重载只是可以有任何行为的普通函数;虽然强烈建议这样做,但实际上并不要求该重载执行的操作必须与操作符的数学或通常含义相关。例如,一个类重载operator+来进行实际减法,或者重载operator==来用0填充对象,这是完全有效的,尽管使用这样的类可能具有挑战性。 
operator+等操作的成员函数重载期望的形参自然是操作符右侧的操作数。这是所有二元操作符(二元操作符左有一个操作数,右有一个操作数)的共同特征。但操作符可以有多种形式。这里有一个表,其中总结了可以重载的不同操作符所需的形参(请在每种情况下将@替换为操作符): 

Expression(表达式)Operator(操作符)Member function(成员函数)Non-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()-

其中a是类A的对象,b是类B的对象,c是类C的对象。TYPE是任意类型(操作符重载到类型TYPE的转换)。
注意,一些操作符可能以两种形式重载:作为成员函数或作为非成员函数:第一种情况已在上面的例子中用于operator+。但有些操作符也可以作为非成员函数重载;在本例中,操作符函数接受适当类的对象作为第一个实参。
例如:
// 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;
  //result = operator+(foo, bar);
  cout << result.x << ',' << result.y << '\n';
  return 0;
}

关键字this
关键字this表示指向正在执行其成员函数的对象的指针。它在类的成员函数中用于引用对象本身。
它的用途之一是检查传递给成员函数的形参是否是对象本身。
例如:
// example on this
#include <iostream>
using namespace std;

class Dummy {
  public:
    bool isitme (Dummy& param);
};

bool Dummy::isitme (Dummy& param)
{
  if (&param == 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;
}

它也经常用于通过引用返回对象的operator=成员函数中。根据前面关于笛卡尔向量的例子,它的operator=函数可以定义为:
CVector& CVector::operator= (const CVector& param)
{
  x=param.x;
  y=param.y;
  return *this;

实际上,这个函数非常类似于编译器为类operator=隐式生成的代码。

静态成员
类可以包含静态成员,可以是数据或函数。 
类的静态数据成员也被称为“类变量”,因为同一个类的所有对象只有一个公共变量,共享相同的值:也就是说,它的值在该类的一个对象与另一个对象之间没有不同。
例如,它可以用于类中的一个变量,该变量可以包含一个计数器,该计数器表示当前分配给该类的对象的数量,如下例所示:
// 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;

事实上,静态成员具有与非成员变量相同的属性,但它们具有类作用域。由于这个原因,并且为了避免多次声明它们,它们不能直接在类中初始化,而需要在类外部的某个地方初始化。如前面的例子所示:
int Dummy::n=0;
因为它是同一个类的所有对象的公共变量值,所以它可以作为该类的任何对象的成员引用,甚至可以直接通过类名引用(当然这只对静态成员有效):
cout << a.n;
cout << Dummy::n;
上面的两个调用引用同一个变量:类Dummy中的静态变量n,由该类的所有对象共享。 
同样,它就像一个非成员变量,但是它的名称需要像类(或对象)的成员一样被访问。
类也可以有静态成员函数。它们表示的是相同的:类的所有对象都通用的类的成员,它们的作用完全像非成员函数一样,但像类的成员一样被访问。因为它们与非成员函数类似,所以不能访问类的非静态成员(既不是成员变量也不是成员函数)。它们都不能使用关键字this。

const成员函数
当类的对象限定为const对象时:

const MyClass myobject;
从类外部对其数据成员的访问被限制为只读,就好像从类外部访问它的所有数据成员都是const一样。注意,构造函数仍然被调用,并且允许初始化和修改这些数据成员:
// 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;

const对象的成员函数只有在自身被指定为const成员时才能被调用;在上面的例子中,成员get(没有指定为const)不能从foo调用。要指定一个成员为const成员,const关键字必须紧跟在函数原型后面,在其形参的右括号之后:
int get() const {return x;}
注意,可以使用const限定成员函数返回的类型。这个const与将成员指定为const的那个不同。两者都是独立的,并且位于函数原型中的不同位置:
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的成员函数不能修改非静态数据成员,也不能调用其他非const成员函数。本质上,const成员不能修改对象的状态。 
const对象被限制只能访问标记为const的成员函数,而非const对象不受限制,因此可以访问const和非const成员函数。
您可能认为无论如何都很少声明const对象,因此将所有不修改对象的成员标记为const并不值得,但const对象实际上非常常见。大多数接受类作为形参的函数实际上是通过const引用来获取类的,因此,这些函数只能访问它们的const成员:
// 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;
}

如果在本例中,get没有指定为const成员,则在print函数中调用arg.get()是不可能的,因为const对象只能访问const成员函数。 
成员函数可以根据它们的const来重载:例如,一个类可以有两个具有相同签名的成员函数,除了一个是const而另一个不是:在这种情况下,只有当对象本身是const时才调用const版本,而当对象本身非const时调用非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;
}

类模板
就像我们可以创建函数模板一样,我们也可以创建类模板,允许类的成员使用模板形参作为类型。例如:
template <class T>
class mypair {
    T values [2];
  public:
    mypair (T first, T second)
    {
      values[0]=first; values[1]=second;
    }
};
我们刚刚定义的类用于存储任意有效类型的两个元素。例如,如果我们想声明这个类的一个对象来存储两个int类型的整数值,值为115和36,我们可以这样写:
mypair<int> myobject (115, 36);
同样的类也可以用来创建一个对象来存储任何其他类型,例如:
mypair<double> myfloats (3.0, 2.18); 
构造函数是前面的类模板中唯一的成员函数,它是在类定义本身内嵌定义的。如果成员函数定义在类模板的定义之外,它的前面应该加上template <…>前缀:
// 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;

注意成员函数getmax定义的语法:
template <class T>
T mypair<T>::getmax () 
被这么多T搞糊涂了?在这个声明中有三个T:第一个是模板形参。第二个T是指函数返回的类型。第三个T(尖括号之间的那个)也是一个要求:它指定这个函数的模板形参也是类模板形参。 

模板特殊化
当特定类型作为模板形参传递时,可以为模板定义不同的实现。这称为模板特殊化。
例如,假设我们有一个非常简单的类mycontainer,它可以存储任何类型的一个元素,并且只有一个名为increase的成员函数,该函数会增加它的值。但我们发现,当它存储char类型的元素时,使用一个函数成员uppercase完全不同的实现会更方便,因此我们决定为该类型声明一个类模板特殊化:
// 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;
}

这是用于类模板特殊化的语法
template <> class mycontainer <char> { ... };
首先,注意我们在类名前面加上template<>,包括一个空形参列表。这是因为所有类型都是已知的,这个特殊化不需要模板实参,但它仍然是类模板的特殊化,因此需要这样标记。 
但是比这个前缀更重要的是类模板名称后面的<char>特殊化形参。这个特殊化形参本身标识特殊化模板类的类型(char)。注意泛型类模板和特殊化模板之间的区别:
template <class T> class mycontainer { ... };
template <> class mycontainer <char> { ... };
第一行是泛型模板,第二行是特殊化模板。
当我们为模板类声明特殊化时,我们还必须定义它的所有成员,甚至是那些与泛型模板类相同的成员,因为从泛型模板到特殊化的成员没有“继承”。 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

weixin_40186813

你的能量无可限量。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值