《C++ primer plus》第10章:对象和类(2)

this 指针

要让程序直到类存储的私有数据,最直接的方式是定义一个接口返回一个值。为此,经常使用内联代码,如下例所示:

class Stock{
private:
	...
	double total_val;
	...
public:
	double total() const { return total_val; }
	...
};

声明一个下面这样的方法:

const Stock & topval(const Stock & s) const;

该函数隐式地访问一个对象,而显式地访问另一个对象,并返回其中一个对象的引用。括号中的const表明,该函数不会修改被显式地访问的对象;而括号后的 const 表明,该函数不会修改被隐式地访问的对象。由于该函数返回了两个const对象之一的引用,因此返回类型也应为 const 引用。

假设要对 Stock 对象 stock1 和 stock2 进行比较,并将其中股价总值较高的那一个赋给 top 对象,则可以使用下面的两条语句之一:

Stock top;
top = stock1.topval(stock2);
top = stock2.topval(stock1);

同时,还要注意的是 topval()的实现,它将引发一个小问题。下面的部分实现强调了这个问题:

const Stock & Stock::topval(const Stock & s) const{
	if (s.total() > total_val)
		return s;
	else
		return ?????;
}

问号处应该返回调用该方法的对象,问题是,如何称呼这个对象?

C++解决这种问题的方法是:使用被称为this的特殊指针。this指针指向用来调用成员函数的对象(this被作为隐藏参数传递给方法)。这样,函数调用 stock1.topval(stock2)this 设置为 stock1 对象的地址。一般来说,所有方法都将this指针设置为调用它的对象的地址。实际上,topval()中的total_val 只不过是this->total_val 的简写(第 4 章使用 -> 运算符,通过指针来访问结构成员。这也适用于类成员)

注意:每个成员函数(包括构造函数和析构函数)都有一个 this 指针。this 指针指向调用对象。如果方法需要引用整个调用对象,则可以使用表达式 *this。在函数的括号后面使用 const 限定符将 this 限定为 const,这样将不能使用 this 来修改对象的值。

然而,要返回的并不是 this ,因为 this 是对象的地址,而不是对象本身,应该返回*this

现在,可以完成前面的函数定义:

const Stock & Stock::topval(const Stock & s) const{
	if ( s.total() > total_val)
		return s;
	else
		return *this;
}

返回类型为引用意味着返回的是调用对象本身,而不是其副本。

对象数组

对象数组的声明方法和标准类型数组的声明方法一样:

Stock mystuff[4];

对象数组的初始化需要使用初始化列表,

  1. Stock mystuff[4]{
    
    }
    
  2. Stock mystuff[4]={
    
    }
    

两种都可以

列表中每个元素的初始化和单个对象的初始化相同:既可以使用参数列表,也可以调用构造函数,如果提供了默认构造函数,还可以省略不写。

类作用域

除了全局作用域(文件作用域)和局部作用域(代码块作用域),C++类引入了一种新的作用域:类作用域。

作用域为类的常量

如果创建一个由所有对象共享的常量呢?
像下面这样是行不通的:

class Bakery{
private:
	const int Months = 12;	// declare a constant?FAILS
	double costs[Months];
}

因为声明类只是描述了对象的形式,并没有创建对象。因此,在创建对象前,将没有用于存储值的空间,如果没有存储值的空间,自然Months这个值为12在创建对象之前就无法知道,C++虽然提供了成员初始化,也就是说在创建对象时,Months的值会默认初始化为12。但是由于开辟存储空间在初始化之前,这样在创建对象时,数组的声明仍然不知道Months的值,就没有办法知道要创建对象的大小。

然而,由两种方式可以实现这个目标,并且效果相同。

  1. 第一种方式是在类中声明一个枚举。

    class Bakery{
    private:
    	enum {Months = 12};
    	double costs[Months];
    	...
    

    用这种方式声明枚举并不会创建类数据成员,也就是说,所有对象中都不包含枚举。Months只是一个符号名称,在作用域为整个类的代码中遇到它时,编译器将用12来替换它。由于这里使用枚举只是为了创建符号常量,并不打算创建枚举类型的变量,因此不需要提供枚举名。

    在很多实现中,ios_base 类在其公有部分中完成了类似的工作,诸如 ios_base_fixed 等标识符就来自这里。其中fixedios_base类中定义的典型的枚举量。

  2. C++提供了另一种在类中定义常量的方式——使用关键字 static

    class Bakery{
    private:
    	static const int Months = 12;
    	double costs[Months];
    	...
    

    这将创建一个名为 Months 的常量,该常量将与其他静态变量存储在一起,而不是存储在对象中。因此,只有一个Months常量,被所有 Bakery 对象共享。

作用域内枚举(C++11)

传统的枚举存在一些问题,其中之一是两个枚举定义中的枚举量可能发生冲突:

enum egg { Small, Medium, Large, Jumbo };
enum t_shirt { Small, Medium, Large, Xlarge };

这将无法通过编译,因为 egg Small 和 t_shirt Small 位于相同的作用域内,它们将发生冲突。

为避免这种问题,C++11 提供了一种新枚举,其枚举量的作用域为类。

这种枚举的声明类似于下面这样:

enum class egg { Small, Medium, Large, Jumbo };
enum class t_shirt { Small, Medium, Large, Xlarge };

也可使用关键字 struct 代替 class。无论使用哪种方式,都需要使用作用域限定符来使用枚举量:

egg choice = egg::Large;
t_thirt Floyd = t_shirt::Large;

枚举量的作用域为类后,不同枚举定义中的枚举量就不会发生冲突了。

在有些情况下,常规枚举能够自动转换为整型,如将其赋给int变量或用于比较表达式时。但作用域内枚举不能被隐式地转换为整型。

默认情况下,C++11作用域内枚举的底层类型为 int,另外,还提供了一种语法,可用于做出不同的选择:

enum class : short pizza { Small, Medium, Large, XLarge };

:short 将底层类型指定为 short。但无论如何,底层类型必须为整型。

抽象数据类型

栈:

  • 可创建空栈
  • 可将数据项添加到堆顶(压入)
  • 可从栈顶删除数据项(弹出)
  • 可查看栈是否填满
  • 可查看栈是否为空
    可将上述描述转换为一个类声明,其中公有成员函数提供了表示栈操作的接口,而私有数据成员负责存储栈数据。类概念非常适合于 ADT(抽象数据类型,abstract data type)方法。

私有部分必须表明数据的存储方式。例如,可以使用常规数组、动态分配数组或更高级的数据结构(如链表)。然而,公有接口应隐藏数据表示,而以通用的术语来表达,如创建栈、压入等。

总结

面向对象编程强调的是程序如何表示数据。使用OOP方法解决编程问题的第一步是根据它与程序之间的接口来描述数据,从而指定如何使用数据。然后,设计一个类来实现该接口。一般来说,私有数据成员存储信息,公有成员函数提供访问数据的唯一途径。类将数据和方法组合成一个单元,其私有性实现数据隐藏。

通常,将类声明分成两部分组成,这两部分通常保存在不同的文件中。类声明应放到头文件中。定义成员函数的源代码放在方法文件中。这样边疆接口描述与实现细节分开了。从理论上说,只需知道公有接口就可以使用类。当然,可以查看实现方法(除非只提供了编译形式),但程序不应依赖于其实现细节,如知道某个值被存储为 int。只要程序和类只通过定义接口的方法进行通信,程序员就可以随意地对任何部分做独立的改进,而不必担心这样做会导致意外的不良影响。

类是用户定义的类型,对象是类的实例。这意味着对象是这种类型的变量,例如由 new 按类描述分配的内存。C++ 试图让用户定义的类型尽可能与标准类型类似,因此可以声明对象、指向对象的指针和对象数组。可以按值传递对象、将对象作为函数返回值、将一个对象赋给同类型的另一个对象。如果提供了构造函数,则在创建对象时,可以初始化对象。如果提供了析构函数方法,则在对象消亡后,程序将执行该函数。

每个对象都存储自己的数据,而共享类方法。如果 mr_object 是对象名,try_me是成员函数,则可以使用成员运算符句点调用成员函数:my_object.try_me()。在OOP中,这种函数调用被称为将try_me()消息发送给mr_object对象。在try_me()方法中引用类数据成员时,将使用 mr_object 对象相应的数据成员。同样,函数调用 i_object.try_me() 将访问 i_object 对象的数据成员。

如果希望成员函数对多个对象进行操作,可以将额外的对象作为参数传递给它。如果方法需要显式地引用调用它的对象,则可以使用 this 指针。由于 this 指针被设置为调用对象的地址,因此 *this 是该对象的别名。

类很适合用于描述 ADT。公有成员函数接口提供了 ADT 描述的服务,类的私有部分和类方法的代码提供了实现,这些实现对类的客户隐藏。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值