C++ 类与泛型

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

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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容

  • 需要多大的能量,才能站在这个舞台上去站住 此刻感悟:自己真的真的很优秀,面对一群人,自己可以在台上挥洒自如。 这需...
    代码数字阅读 118评论 0 0
  • 如题,2016年八月我犹豫了关于考研的决定,这个犹豫要从初中时代说起。说实话从幼儿园一直走到大学毕业,我最留恋的时...
    有故事的晓姑凉阅读 467评论 0 0
  • 心心念念的 是你的一意孤行的执拗 用力塞住耳郭 不愿听半点的亲谏 盈弱的羽翼 又遇见最偏执的刚愎自用! 不忍看 为...
    赋心阅读 281评论 0 1