C++拾遗(五)——类

  类是 C++ 中最重要的特征。C++ 语言的早期版本被命名为“带类的 C(Cwith Classes)”,以强调类机制的中心作用。随着语言的演变,创建类的配套支持也在不断增加。语言设计的主要目标也变成提供这样一些特性:允许程序定义自己的类型,它们用起来与内置类型一样容易和直观。

类的定义和声明

  • 类背后蕴涵的基本思想是数据抽象封装
  • 数据抽象是一种依赖于接口和实现分离的编程(和设计)技术。类设计者必须关心类是如何实现的,但使用该类的程序员不必了解这些细节。相反,使用一个类型的程序员仅需了解类型的接口,他们可以抽象地考虑该类型做什么,而不必具体地考虑该类型如何工作。
  • 封装是一项低层次的元素组合起来的形成新的、高层次实体珠技术。函数是封装的一种形式:函数所执行的细节行为被封装在函数本身这个更大的实体中。被封装的元素隐藏了它们的实现细节——可以调用一个函数但不能访问它所执行的语句。同样地,类也是一个封装的实体:它代表若干成员的聚焦,大多数(良好设计的)类类型隐藏了实现该类型的成员。
  • 标准库类型 vector 同时具备数据抽象和封装的特性。在使用方面它是抽象的,只需考虑它的接口,即它能执行的操作。它又是封装的,因为我们既无法了解该类型如何表示的细节,也无法访问其任意的实现制品。另一方面,数组在概念上类似于 vector,但既不是抽象的,也不是封装的。可以通过访问存放数组的内存来直接操纵数组。
  • 并非所有类型都必须是抽象的。标准库中的 pair 类就是一个实用的、设计良好的具体类而不是抽象类。具体类会暴露而非隐藏其实现细节。一些类,例如 pair,确实没有抽象接口。pair 类型只是将两个数据成员捆绑成单个对象。在这种情况下,隐藏数据成员没有必要也没有明显的好处。在像 pair 这样的类中隐藏数据成员只会造成类型使用的复杂化。
  • 数据抽象和封装提供了两个重要优点:1.避免类内部出现无意的、可能破坏对象状态的用户级错误。2.随时间推移可以根据需求改变或缺陷(bug)报告来完美类实现,而无须改变用户级代码。

隐含的this指针

  • 成员函数具有一个附加的隐含形参,即指向该类对象的一个指针。这个隐含形参命名为 this,与调用成员函数的对象绑定在一起。成员函数不能定义 this 形参,而是由编译器隐含地定义。成员函数的函数体可以显式使用 this 指针,但不是必须这么做。如果对类成员的引用没有限定,编译器会将这种引用处理成通过 this 指针的引用。
  • 尽管在成员函数内部显式引用 this 通常是不必要的,但有一种情况下必须这样做:当我们需要将一个对象作为整体引用而不是引用对象的一个成员时。最常见的情况是在这样的函数中使用 this:该函数返回对调用该函数的对象的引用。
  • 在普通的非 const 成员函数中,this 的类型是一个指向类类型的 const 指针。可以改变 this 所指向的值,但不能改变 this 所保存的地址。在 const 成员函数中,this 的类型是一个指向 const 类类型对象的const 指针。既不能改变 this 所指向的对象,也不能改变 this 所保存的地址。注意不能从 const 成员函数返回指向类对象的普通引用。const 成员函数只能返回 *this 作为一个 const 引用。.
  • 有时,我们希望类的数据成员(甚至在 const 成员函数内)可以修改。这可以通过将它们声明为 mutable 来实现。可变数据成员(mutable data member)永远都不能为 const,甚至当它是const 对象的成员时也如此。因此,const 成员函数可以改变 mutable 成员。要将数据成员声明为可变的,必须将关键字 mutable 放在成员声明之前。

构造函数

  • 构造函数是特殊的成员函数,只要创建类类型的新对象,都要执行构造函数。构造函数的工作是保证每个对象的数据成员具有合适的初始值。构造函数的名字与类的名字相同,并且不能指定返回类型。像其他任何函数一样,它们可以没有形参,也可以定义多个形参。构造函数可以被重载,实参决定使用哪个构造函数。构造函数自动执行。
  • 构造函数不能声明为 const,创建类类型的 const 对象时,运行一个普通构造函数来初始化该 const 对象。构造函数的工作是初始化对象。不管对象是否为 const,都用一个构造函数来初始化化该对象。
  • 构造函数初始化列表以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个数据成员后面跟一个放在圆括号中的初始化式。与任意的成员函数一样,构造函数可以定义在类的内部或外部。构造函数初始化只在构造函数的定义中而不是声明中指定。
  • 如果没有为类成员提供初始化式,则编译器会隐式地使用成员类型的默认构造函数。如果那个类没有默认构造函数,则编译器尝试使用默认构造函数将会失败。在这种情况下,为了初始化数据成员,必须提供初始化式。有些成员必须在构造函数初始化列表中进行初始化。对于这样的成员,在构造函数函数体中对它们赋值不起作用。没有默认构造函数的类类型的成员,以及 const 或引用类型的成员,不管是哪种类型,都必须在构造函数初始化列表中进行初始化。
  • 只要定义一个对象时没有提供初始化式,就使用默认构造函数。为所有形参提供默认实参的构造函数也定义了默认构造函数。一个类哪怕只定义了一个构造函数,编译器也不会再生成默认构造函数。这条规则的根据是,如果一个类在某种情况下需要控制对象初始化,则该类很可能在所有情况下都需要控制。只有当一个类没有定义构造函数时,编译器才会自动生成一个默认构造函数。内置和复合类型的成员,如指针和数组,只对定义在全局作用域中的对象才初始化。当对象定义在局部作用域中时,内置或复合类型的成员不进行初始化。如果类包含内置或复合类型的成员,则该类不应该依赖于合成的默认构造函数。它应该定义自己的构造函数来初始化这些成员。

static类成员

  • 不像普通的数据成员,static 数据成员独立于该类的任意对象而存在;每个 static 数据成员是与类关联的对象,并不与该类的对象相关联。正如类可以定义共享的 static 数据成员一样,类也可以定义 static 成员函数。static 成员函数没有 this 形参,它可以直接访问所属类的 static 成员,但不能直接使用非 static 成员。
  • 使用static 成员而不是全局对象有三个优点。1. static 成员的名字是在类的作用域中,因此可以避免与其他类的成员或全局对象名字冲突。2. 可以实施封装。static 成员可以是私有成员,而全局对象不可以。3. 通过阅读程序容易看出 static 成员是与特定类关联的。这种可见性可清晰地显示程序员的意图。

一个实例

  为了增进读者对上述文字的理解,这里给出一个实例,源自《C++ Primer》习题12.13:扩展Screen类以包含move、set和display操作通过执行如下表达式来测试类:

// 将光标移至指定位置,设置字符并显示屏幕内容 myScreen.move(4,0).set(‘#‘).display(cout); 

  答案如下:

 1 #include <iostream>
 2 #include <string>
 3
 4 using namespace std;
 5
 6 class Screen {
 7 public:
 8     typedef string::size_type index;
 9     char get() const { return contents[cursor]; }
10     inline char get(index ht, index wd) const;
11     index get_cursor() const;
12     Screen(index hght, index wdth, const string &cntnts);
13
14     // 增加三个成员函数
15     Screen& move(index r, index c);
16     Screen& set(char);
17     Screen& display(ostream &os);
18
19 private:
20     std::string contents;
21     index cursor;
22     index height, width;
23 };
24
25 Screen::Screen(index hght, index wdth, const string &cntnts) :
26         contents(cntnts), cursor(0), height(hght), width(wdth) { }
27
28 char Screen::get(index r, index c) const
29 {
30     index row = r * width;
31     return contents[row + c];
32 }
33
34 inline Screen::index Screen::get_cursor() const
35 {
36     return cursor;
37 }
38
39 // 增加的三个成员函数的定义
40 Screen& Screen::set(char c)
41 {
42     contents[cursor] = c;
43     return *this;
44 }
45
46 Screen& Screen::move(index r, index c)
47 {
48     index row = r * width;
49     cursor = row + c;
50     return *this;
51 }
52
53 Screen& Screen::display(ostream &os)
54 {
55     os << contents;
56     return *this;
57 }
58
59 int main()
60 {
61     // 根据屏幕的高度、宽度和内容的值来创建
62     Screen Screen myScreen(5, 6, "aaaaa\naaaaa\naaaaa\naaaaa\naaaaa\n");
63
64     // 将光标移至指定位置,设置字符并显示屏幕内容
65     myScreen.move(4, 0).set(‘#‘).display(cout);
66
67     return 0;
68 }

  这个解决方法已满足了题目提出的要求,但存在一些缺陷:

  (1) 创建Screen对象时必须给出表示整个屏幕内容的字符串,即使有些位置上没有内容。

  (2) 显示的屏幕内容没有恰当地分行,而是连续显示,因此(4,0)位置上的‘#‘,在实际显示时 不一定正好在屏幕的(4,0)位置,显示效果较差。

  (3) 如果创建的Screen对象是一个const对象,则不能使用display函数进行显示(因为const对 象只能使用const成员)。

  (4) 如果move操作的目的位置超出了屏幕的边界,会出现运行时错误。

  要解决第一个缺陷,可以如下修改构造函数:

1 Screen::Screen(index hght, index wdth, const string &cntnts = " "): cursor(0), height(hght), width(wdth)
2  {
3     // 将整个屏幕内容置为空格
4     contents.assign(hght*wdth, ‘ ‘);
5      // 用形参string对象的内容设置屏幕的相应字符
6     if (cntnts.size() != 0)
7       contents.replace(0, cntnts.size(), cntnts);
8 }    

  要解决第二个缺陷,可以如下修改display函数:

 1 Screen& Screen::display(ostream &os)
 2 {
 3     string::size_type index = 0;
 4     while (index != contents.size())
 5     {
 6         os << contents[index];
 7          if ((index+1) % width == 0)
 8         {
 9             os << ‘\n‘;
10         }
11         ++index;
12     }
13     return *this;
14 }        

  要解决第三个缺陷,可以在Screen类定义体中增加如下函数声明: const Screen& display(ostream &os) const; 声明display函数的一个重载版本,供 const对象使用。
  要解决第四个缺陷,可以如下修改move函数:

 1 Screen& Screen::move(index r, index c)
 2 {
 3     // 行、列号均从0开始
 4     if (r >= height c >= width)
 5     {
 6         cerr << "invalid row or column" << endl;
 7         throw EXIT_FAILURE;
 8     }
 9
10     index row = r * width;
11     cursor = row + c;
12     return *this;
13 }         

  经过如上述几处修改,整个程序的健壮性,鲁棒性都得到了改善。全部代码如下:

  1 #include <iostream>
  2 #include <string>
  3
  4 using namespace std;
  5
  6 class Screen {
  7 public:
  8     typedef string::size_type index;
  9     char get() const { return contents[cursor]; }
 10     inline char get(index ht, index wd) const;
 11     index get_cursor() const;
 12
 13     Screen(index hght, index wdth, const string &cntnts);
 14
 15     Screen& move(index r, index c);
 16     Screen& set(char);
 17     Screen& display(ostream &os);
 18     const Screen& display(ostream &os) const;
 19
 20 private:
 21     std::string contents;
 22     index cursor;
 23     index height, width;
 24 };
 25
 26 Screen::Screen(index hght, index wdth, const string &cntnts = "1"):
 27         cursor(0), height(hght), width(wdth)
 28 {
 29     contents.assign(hght*wdth, ‘1‘);
 30
 31     if (cntnts.size() != 0)
 32         contents.replace(0, cntnts.size(), cntnts);
 33 }
 34
 35
 36 char Screen::get(index r, index c) const
 37 {
 38     index row = r * width;
 39     return contents[row + c];
 40 }
 41
 42 inline Screen::index Screen::get_cursor() const
 43 {
 44     return cursor;
 45 }
 46
 47 Screen& Screen::set(char c)
 48 {
 49     contents[cursor] = c;
 50     return *this;
 51 }
 52
 53 Screen& Screen::move(index r, index c)
 54 {
 55     if (r >= height || c >= width)
 56     {
 57         cerr << "invalid row or column" << endl;
 58         throw EXIT_FAILURE;
 59     }
 60
 61     index row = r * width;
 62     cursor = row + c;
 63
 64     return *this;
 65 }
 66
 67 Screen& Screen::display(ostream &os)
 68 {
 69     string::size_type index = 0;
 70
 71     while (index != contents.size())
 72     {
 73         os << contents[index];
 74         if ((index + 1) % width == 0)
 75         {
 76             os << ‘\n‘;
 77         }
 78         ++index;
 79     }
 80     return *this;
 81 }
 82
 83 const Screen& Screen::display(ostream &os) const
 84 {
 85     string::size_type index = 0;
 86
 87     while (index != contents.size())
 88     {
 89         os << contents[index];
 90         if ((index + 1) % width == 0)
 91         {
 92             os << ‘\n‘;
 93         }
 94         ++index;
 95     }
 96     return *this;
 97 }
 98
 99 int main()
100 {
101     Screen myScreen(10,30);
102     //Screen myScreen(5, 6, "aaaaa\naaaaa\naaaaa\naaaaa\naaaaa\n");
103     myScreen.move(4, 0).set(‘#‘).display(cout);
104
105     system("pause");
106
107     return 0;
108 } 

时间: 2024-10-12 03:49:21

C++拾遗(五)——类的相关文章

最常用的五类CSS选择器

一些新手朋友对选择器一知半解,不知道在什么情况下运用什么样的选择器,这是一个比较头疼的问题,针对新手朋友,对CSS选择器作一些简单的说明,希望能对大家的学习工作有一定的帮助,更多的CSS知识请参考Webjx.com的其他文章. 准确而简洁的运用CSS选择器会达到非常好的效果.我们不必通篇给每一个元素定义类(class)或ID,通过合适的组织,可以用最简单的方法实现同样的效果.在实际工作中,最常用的选择器有以下五类: 一.标签选择器: 顾名思议,标签选择器是直接将HTML标签作为选择器,可以是p.

千兆网络中——使用五类线、超五类线和六类线的区别

去年入手了一台Seagate的Central.一直没时间好好研究,直接插上就用了.内网速度一直维持在10M-15M左右.今年618换了款千兆路由器,速度瞬间提升到了50M-70M.线缆用的都是五类,心中就产生疑问:如果换成超五类线或者六类线,性能是不是能再提高点??于是网上搜到一个哥们十分专业的回答,忍不住粘下来跟大家共享: 原帖地址:http://we.poppur.com/thread-1098485-1-1.html 5类线的传输频率标准是100MHz  超5一般是125MHz  6类标准

五类常见算法小记 (递归与分治,动态规划,贪心,回溯,分支界限法)

近日复习了一些算法知识,小记于此 递归与分治法 直接或间接地调用自身的算法称为递归算法. 递归是算法设计与分析中常用的一种技术,描述简单且易于理解. 分治法的设计思想是将一个规模为n难以解决的问题分解为k个规模较小的子问题,这些子问题相互独立且与原问题相同. 递归地解这些子问题,然后将各子问题的解合并得到原问题的解. 典型例子:Fibonacci数列,阶乘,Hanoi塔:二分法搜索.快速排序.合并排序. 动态规划法 动态规划过程是:根据当前(阶段)状态,采取相应的决策,引起状态的转移.如下图,一

怎么区分五类线、超五类线和六类线

五类线的标识是“CAT5”,带宽100M ,适用于百兆以下的网:超五类线的标识是“CAT5E”,带宽155M,是目前的主流产品:六类线的标识是“ CAT6”,带宽250M,用于架设千兆网,是未来发展的趋势. 5类线: “五类”非屏蔽双绞线采用4个绕对,但没有抗拉丝,价格同超五类相差不大 超5类线: “超五类”指的是超五类非屏蔽双绞线(UTP—Unshielded Twisted Pair).超五类非屏蔽双绞线是在对现有五类屏蔽双绞线的部分性能加以改善后出现的电缆,不少性能参数,如近端串扰.衰减串

五类线、超五类线、六类线,有什么区别?

五类线带宽100兆赫兹,超五类155兆赫兹,六类线250兆赫兹.五类和超五类里面单股铜芯都是24AWG,六类一般是23AWG.五类和超五类线里面没有隔离带,六类线里面有一字或十字隔离带.五类线皮上标有CAT5字样,超五类上是CAT5E或5E,六类是CAT6.五类和超五类支持百兆的应用,六类支持千兆的应用,超五类在特定条件,特定距离下也支持千兆的应用. 五类在商用市场已经淘汰了,超五类和六类都用于高速网络的架设,都可以用于商业建筑布线,六类可用在传输像医用CT扫描数据上和机房,以及其他需要海量数据

A、B、C、D、E五类IP地址

转:[A.B.C.D和E类IP地址] IP地址分为A,B,C,D,E五类. 网络号:用于识别主机所在的网络:主机号:用于识别该网络中的主机. 其中A类分配给政府机关使用,B类地址给大中型企业使用,C类地址给个人使用.这三种是主要的. IP地址分为五类,A类保留给政府机构,B类分配给中等规模的公司,C类分配给任何需要的人,D类用于组播,E类用于实验,各类可容纳的地址数目不同. 其中A类.B类.和C类这三类地址用于TCP/IP节点,其它两类D类和E类被用于特殊用途.A.B.C三类IP地址的特征:当将

IP地址的分类及范围详解:A、B、C、D、E五类是如何划分的?

IP地址的分类及范围详解:A.B.C.D.E五类是如何划分的? 最近在考证,有一项内容是网络IP地址划分的题目,一直从事IT的猛然发现回答这个问题时产生了怀疑,于是习惯百度了一下,发现网上广泛流传的有关IP地址划分的文章居然是有点错误,误导人,现重新整理补充一下. 大家都知道现在的互联网使用的是32位地址,IPv6虽然也说了好些年,但大家都习惯接受IPv4的用法说法.IP以点分十进制表示,如172.16.0.0.地址格式为:IP地址=网络地址+主机地址 或 IP地址=主机地址+子网地址+主机地址

常见的五类排序算法图解和实现(多关键字排序:基数排序以及各个排序算法的总结)

基数排序思想 完全不同于以前的排序算法,可以说,基数排序也叫做多关键字排序,基数排序是一种借助“多关键字排序”的思想来实现“单关键字排序”的内部排序算法. 两种方式: 1.最高位优先,先按照最高位排成若干子序列,再对子序列按照次高位排序 2.最低位优先:不必分子序列,每次排序全体元素都参与,不比较,而是通过分配+收集的方式. 多关键字排序 例:将下表所示的学生成绩单按数学成绩的等级由高到低排序,数学成绩相同的学生再按英语成绩的高低等级排序.        第一个关键字是数学成绩,第二个关键字是英

领导最不愿意培养的五类下属,你必须知道!

一.道德品质存在缺陷之人不能用 小人不能用 当今社会,我们在评论一个人的好坏时,首先看到的是他的道德素养.这方面界定这个人的为人本质.一个道德本质不行.差的人,我们统称为小人. 二.不懂得感恩之人不能培养 "白眼狼"类之人不能用 要"阅"清楚此类人,只要看他在几次关键的事件中的面对态度,就能充分体现出他"白眼狼"的本质.不懂得感恩之人,自然不会感恩自己的平台,也不会去感恩曾经对他支持和帮助的任何人.自然而然他也不会成"器".

实验五 类和对象-3 zxt

实验一:运行ex1.cpp 实验二:ex2.cpp   实验三:补全ex3.cpp并运行 我以为是自己喜欢和不喜欢的呢...... 实验四:习题6-17 实验五:习题6-18 实验六:matrix类 我智障的没看见matrix.h,自己写的matrix,我都要写傻了,还运行不出来......求解决 实验七:期中考试第一题 写完突然意识到,忘了释放,要不然会造成内存泄漏,会有很严重的后果,尴尬 我真是老年人的脑袋,啥都忘...... 修改以后如下: 实验八:期中考试第二题 当密码输入错误时 实验九