Cpp/C++

C++ 类与泛型

2018-10-18  本文已影响17人  Jimmy_L_Wang

关键字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;
}

实际上,this函数与编译器为类隐式生成的operator=重载操作符代码一致。

static members 静态成员

类可以包含静态成员,数据或函数。

类的静态数据成员也称为“类变量”,因为同一类的所有对象只有一个公共变量,共享相同的值:,其值与这个类的一个对象之间没有区别。

例如,它可以用于类中的一个变量,该变量可以包含一个计数器,计数器中包含该类当前分配的对象的数量,如下例所示:

// 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对象不受限制,因此可以同时访问constconst成员函数和非成员函数。

您可能认为无论如何您很少会声明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 () 

模板特殊化

当特定类型作为模板参数传递时,可以为模板定义不同的实现。这称为模板特殊化。

例如,让我们假设我们有一个非常简单的类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> { ... };

第一行是泛型模板,第二行是模板特殊化。

当我们声明模板类的特殊化时,我们还必须定义它的所有成员,甚至是那些与泛型模板类相同的成员,因为没有从泛型模板到模板特殊化成员的“继承”关系。

上一篇下一篇

猜你喜欢

热点阅读