C++篇:基础入门(三)

C++篇:基础入门(三)

类和数据抽象
类定义了数据成员和函数成员:数据成员用于存储与该类类型的对象相关联的状态,而函数成员则负责执行赋予数据意义的操作。通过类我们能够将实现和接口分离,用接口指定类所支持的操作,而实现的细节只需类的实现者了解或关心。这种分离可以减少使编程冗长乏味和容易出错的那些繁琐工作。

从第一章开始,程序中就已经使用了类。已经用过的标准库类型,比如vector,istream 和 string,都是类类型。还定义了一些简单的类,如Sales_item 和 TextQuery 类。为了扼要秣,再来看年 Sales_item 类:

class Sales_item {
public:
// operations on Sales_item objects
double avg_price() const;
bool same_isbn(const Sales_item &rhs) const
{ return isbn == rhs.isbn; }
// default constructor needed to initialize members of built-in type
Sales_item(): units_sold(0), revenue(0.0) { }
private:
std::string isbn;
unsigned units_sold;
double revenue;
};
double Sales_item::avg_price() const
{
if (units_sold)
return revenue/units_sold;
else
return 0;
}

类成员
每个类可以没有成员,也可以定义多个成员,成员可以是数据、函数或类型别名。一个类可以包含若干公有的、私有的和受保护的部分。我们已经使用过public 和 private 访问标号:在 public 部分定义的成员可被使用该类型的所有代码访问;在 private 部分定义的成员可被其他类成员访问。在第十五章讨论继承时将进一步探讨 protected。所有成员必须在类的内部声明,一旦类定义完成后,就没有任何方式可以增加成员了。
构造函数
创建一个类类型的对象时,编译器会自动使用一个构造函数(第 2.3.3 节)来初始化该对象。构造函数是一个特殊的、与类同名的成员函数,用于给每个数据成员设置适当的初始值。
构造函数一般就使用一个构造函数初始化列表(第 7.7.3 节),来初始化对象的数据成员:
// default constructor needed to initialize members of built-in type
Sales_item(): units_sold(0), revenue(0.0) { }
成员函数
在类内部,声明成员函数是必需的,而定义成员函数则是可选的。在类内部定义的函数默认为 inline(第 7.6 节)。在类外部定义的成员函数必须指明它们是在类的作用域中。Sales_item::avg_price 的定义使用作用域操作符(第 1.2.2 节)来指明这是Sales_item 类中 avg_price 函数的定义。
将关键字 const 加在形参表之后,就可以将成员函数声明为常量:double avg_price() const;
const 成员不能改变其所操作的对象的数据成员。const 必须同时出现在声明和定义中,若只出现在其中一处,就会出现一个编译时错误。
数据抽象和封装
类背后蕴涵的基本思想是数据抽象和封装,数据抽象是一种依赖于接口和实现分离的编程(和设计)技术。类设计者必须关心类是如何实现的,但使用该类的程序员不必了解这些细节。相反,使用一个类型的程序员仅需了解类型的接口,他们可以抽象地考虑该类型做什么,而不必具体地考虑该类型如何工作。
封装是一项低层次的元素组合起来的形成新的、高层次实体珠技术。函数是封装的一种形式:函数所执行的细节行为被封装在函数本身这个更大的实体中。被封装的元素隐藏了它们的实现细节——可以调用一个函数但不能访问它所执行的语句。同样地,类也是一个封装的实体:它代表若干成员的聚焦,大多数(良好设计的)类类型隐藏了实现该类型的成员。标准库类型 vector 同时具备数据抽象和封装的特性。在使用方面它是抽象的,只需考虑它的接口,即它能执行的操作。它又是封装的,因为我们既无法了解该类型如何表示的细节,也无法访问其任意的实现制品。另一方面,数组在概念上类似于 vector,但既不是抽象的,也不是封装的。可以通过访问存放数组的内存来直接操纵数组。
访问标号实施抽象和封装
在 C++ 中,使用访问标号(第 2.8 节)来定义类的抽象接口和实施封装。一个类可以没有访问标号,也可以包含多个访问标号:
•程序的所有部分都可以访问带有 public 标号的成员。类型的数据抽象视图由其 public 成员定义。
•使用类的代码不可以访问带有 private 标号的成员。private 封装了类型的实现细节。

一个访问标号可以出现的次数通常是没有限制的。每个访问标号指定了随后的成员定义的访问级别。这个指定的访问级别持续有效,直到遇到下一个访问标号或看到类定义体的右花括号为止。
可以在任意的访问标号出现之前定义类成员。在类的左花括号之后、第一个访问标号之前定义成员的访问级别,其值依赖于类是如何定义的。如果类是用struct 关键字定义的,则在第一个访问标号之前的成员是公有的;如果类是用class 关键字是定义的,则这些成员是私有的。
同一类型的多个数据成员
正如我们所见,类的数据成员的声明类似于普通变量的声明。如果一个类具有多个同一类型的数据成员,则这些成员可以在一个成员声明中指定,这种情况下,成员声明和普通变量声明是相同的。
例如,可以定义一个名为 Screen 的类型表示计算机上的窗口。每个 Screen可以有一个保存窗口内容的 string 成员,以及三个 string::size_type 成员:一个指定光标当前停留的字符,另外两个指定窗口的高度和宽度。可以用如下方式这个类的成员:

class Screen {
public:
// interface member functions
private:
std::string contents;
std::string::size_type cursor;
std::string::size_type height, width;
};

使用类型别名来简化类
除了定义数据和函数成员之外,类还可以定义自己的局部类型名字。如果为
std::string::size_type 提供一个类型别名,那么 Screen 类将是一个更好的
抽象:

class Screen {
public:
// interface member functions
typedef std::string::size_type index;
private:
std::string contents;
index cursor;
index height, width;
};

类所定义的类型名遵循任何其他成员的标准访问控制。将 index 的定义放
在类的 public 部分,是因为希望用户使用这个名字。Screen 类的使用者不必
了解用 string 实现的底层细节。定义 index 来隐藏 Screen 的实现细节。将
这个类型设为 public,就允许用户使用这个名字。

成员函数可被重载
这些类之所以简单,另一个方面也是因为它们只定义了几个成员函数。特别地,这些类都不需要定义其任意成员函数的重载版本。然而,像非成员函数一样,成员函数也可以被重载(第 7.8 节)。重载操作符(第 14.9.5 节)有特殊规则,是个例外,成员函数只能重载本类的其他成员函数。类的成员函数与普通的非成员函数以及在其他类中声明的函数不相关,也不能重载它们。重载的成员函数和普通函数应用相同的规则:两个重载成员的形参数量和类型不能完全相同。调用非成员重载函数所用到的函数匹配(第 7.8.2 节)过程也应用于重载成员函数的调用。
定义重载成员函数
为了举例说明重载,可以给出 Screen 类的两个重载成员,用于从窗口返回一个特定字符。两个重载成员中,一个版本返回由当前光标指示的字符,另一个返回指定行列处的字符:

class Screen {
public:
typedef std::string::size_type index;
// return character at the cursor or at a given position
char get() const { return contents[cursor]; }
char get(index ht, index wd) const;
// remaining members
private:
std::string contents;
index cursor;
index height, width;
};

与任意的重载函数一样,给指定的函数调用提供适当数目和/或类型的实参
来选择运行哪个版本:

Screen myscreen;
char ch = myscreen.get();// calls Screen::get()
ch = myscreen.get(0,0); // calls Screen::get(index, index)

显式指定 inline 成员函数
在类内部定义的成员函数,例如不接受实参的 get 成员,将自动作为inline 处理。也就是说,当它们被调用时,编译器将试图在同一行内扩展该函数(第 7.6 节)。也可以显式地将成员函数声明为 inline:

class Screen {
public:
typedef std::string::size_type index;
// implicitly inline when defined inside the class declaration
char get() const { return contents[cursor]; }
// explicitly declared as inline; will be defined outside theclass declaration
inline char get(index ht, index wd) const;
// inline not specified in class declaration, but can be defined inline later
index get_cursor() const;
// ...
};
// inline declared in the class declaration; no need to repeat on the definition
char Screen::get(index r, index c) const
{
index row = r * width;
// compute the row location
return contents[row + c]; // offset by c to fetch specified
character
}
// not declared as inline in the class declaration, but ok to make inline in definition
inline Screen::index Screen::get_cursor() const
{
return cursor;
}

可以在类定义体内部指定一个成员为 inline,作为其声明的一部分。或者,
也可以在类定义外部的函数定义上指定 inline。在声明和定义处指定 inline
都是合法的。在类的外部定义 inline 的一个好处是可以使得类比较容易阅读。

拷贝构造函数
是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。拷贝构造函数通常用于:
1.通过使用另一个同类型的对象来初始化新创建的对象。
2.复制对象把它作为参数传递给函数。
3.复制对象,并从函数返回这个对象。


#include <iostream>
 
using namespace std;
 
class Line
{
   public:
      int getLength( void );
      Line( int len );             // 简单的构造函数
      Line( const Line &obj);      // 拷贝构造函数
      ~Line();                     // 析构函数
 
   private:
      int *ptr;
};
 
// 成员函数定义,包括构造函数
Line::Line(int len)
{
    cout << "调用构造函数" << endl;
    // 为指针分配内存
    ptr = new int;
    *ptr = len;
}
 
Line::Line(const Line &obj)
{
    cout << "调用拷贝构造函数并为指针 ptr 分配内存" << endl;
    ptr = new int;
    *ptr = *obj.ptr; // 拷贝值
}
 
Line::~Line(void)
{
    cout << "释放内存" << endl;
    delete ptr;
}
int Line::getLength( void )
{
    return *ptr;
}
 
void display(Line obj)
{
   cout << "line 大小 : " << obj.getLength() <<endl;
}
 
// 程序的主函数
int main( )
{
   Line line(10);
 
   display(line);
 
   return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:
调用构造函数
调用拷贝构造函数并为指针 ptr 分配内存
line 大小 : 10
释放内存
释放内存

友元函数
类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。如果要声明函数为一个类的友元,需要在类定义中该函数原型前使用关键字 friend,如下所示:

#include <iostream>
 
using namespace std;
 
class Box
{
   double width;
public:
   friend void printWidth( Box box );
   void setWidth( double wid );
};
 
// 成员函数定义
void Box::setWidth( double wid )
{
    width = wid;
}
 
// 请注意:printWidth() 不是任何类的成员函数
void printWidth( Box box )
{
   /* 因为 printWidth() 是 Box 的友元,它可以直接访问该类的任何成员 */
   cout << "Width of box : " << box.width <<endl;
}
 
// 程序的主函数
int main( )
{
   Box box;
 
   // 使用成员函数设置宽度
   box.setWidth(10.0);
   
   // 使用友元函数输出宽度
   printWidth( box );
 
   return 0;
}

指向类的指针
一个指向 C++ 类的指针与指向结构的指针类似,访问指向类的指针的成员,需要使用成员访问运算符 ->,就像访问指向结构的指针一样。与所有的指针一样,您必须在使用指针之前,对指针进行初始化。

#include <iostream>
 
using namespace std;

class Box
{
   public:
      // 构造函数定义
      Box(double l=2.0, double b=2.0, double h=2.0)
      {
         cout <<"Constructor called." << endl;
         length = l;
         breadth = b;
         height = h;
      }
      double Volume()
      {
         return length * breadth * height;
      }
   private:
      double length;     // Length of a box
      double breadth;    // Breadth of a box
      double height;     // Height of a box
};

int main(void)
{
   Box Box1(3.3, 1.2, 1.5);    // Declare box1
   Box Box2(8.5, 6.0, 2.0);    // Declare box2
   Box *ptrBox;                // Declare pointer to a class.

   // 保存第一个对象的地址
   ptrBox = &Box1;

   // 现在尝试使用成员访问运算符来访问成员
   cout << "Volume of Box1: " << ptrBox->Volume() << endl;

   // 保存第二个对象的地址
   ptrBox = &Box2;

   // 现在尝试使用成员访问运算符来访问成员
   cout << "Volume of Box2: " << ptrBox->Volume() << endl;
  
   return 0;
}

类的静态成员
我们可以使用 static 关键字来把类成员定义为静态的。当我们声明类的成员为静态时,这意味着无论创建多少个类的对象,静态成员都只有一个副本。
静态成员在类的所有对象中是共享的。如果不存在其他的初始化语句,在创建第一个对象时,所有的静态数据都会被初始化为零。我们不能把静态成员的初始化放置在类的定义中,但是可以在类的外部通过使用范围解析运算符 :: 来重新声明静态变量从而对它进行初始化,如下面的实例所示。


#include <iostream>
 
using namespace std;
 
class Box
{
   public:
      static int objectCount;
      // 构造函数定义
      Box(double l=2.0, double b=2.0, double h=2.0)
      {
         cout <<"Constructor called." << endl;
         length = l;
         breadth = b;
         height = h;
         // 每次创建对象时增加 1
         objectCount++;
      }
      double Volume()
      {
         return length * breadth * height;
      }
   private:
      double length;     // 长度
      double breadth;    // 宽度
      double height;     // 高度
};
 
// 初始化类 Box 的静态成员
int Box::objectCount = 0;
 
int main(void)
{
   Box Box1(3.3, 1.2, 1.5);    // 声明 box1
   Box Box2(8.5, 6.0, 2.0);    // 声明 box2
 
   // 输出对象的总数
   cout << "Total objects: " << Box::objectCount << endl;
 
   return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值