C++ Primer Plus第六版-第十章-学习笔记

第 10 章 对象和类

10.2 抽象和类

指定基本类型完成3项工作:

  • 决定数据对象需要的内存数量
  • 决定如何解释内存中的位(long和float在内存中占用的位数相同,但将它们转化为数值的方法不同)
  • 决定可使用数据对象执行的操作和方法
    对于内置类型来说,有关操作的信息被内置到编译器中。但在C++中定义用户自定义的类型时,必须自己提供这些信息。

C++中的类

类是一种将抽象转换为用户定义类型的C++工具,它将数据表示和操纵数据的方法组合成一个整洁的包。

类规范
  • 类声明:以数据成员的方式描述数据部分,以成员函数(被称为方法)的方式描述公有接口。
  • 类方法定义:描述如何实现类成员的函数。
    类声明提供了类的蓝图,而方法定义则提供了细节
    将数据和方法组合成一个单元是类最吸引人的特性。
访问控制

使用类对象的程序都可以直接访问公有部分,但只能通过公有成员函数(或友元函数)来访问对象的私有成员,公有成员函数是程序和私有对象之间的桥梁,防止程序直接访问数据被称为数据隐藏。
将实现细节放在一起并将它们与抽象分开被称为封装
从使用类的角度,需要知道的是各种函数的功能(需要知道成员函数接收什么样的参数以及返回什么类型的值),原则是将实现细节从接口设计中分离出来

控制对成员的访问:公有还是私有

数据项通常放在私有部分,组成类接口的成员函数放在公有部分

类和结构
C++对结构进行了扩展,使之具有与类相同的特性,它们之间的区别是,结构默认访问类型是public,而类为private,C++程序员通常使用类来实现类描述,而把结构限制为指标是纯粹的数据对象

实现类成员函数

成员函数定义与常规函数定义非常相似,它们有函数头和函数体,也可以有返回类型和参数,但是它们还有两个特殊的特征:

  • 定义成员函数时,使用作用域解析符(::)来标识函数所属的类;
  • 类方法可以访问类的private组件。
void Stock::update(double price); //update()函数是Stock类的成员

简单的update()是全名的缩写(unqualified name),它只能在类作用域中使用

内联方法

其定义位于类声明中的函数都将自动成为内联函数
如果愿意,也可以在类声明之外定义成员函数,并使其成为内联函数,只需在类实现部分中定义函数时,使用inline 限定符即可
确保内联函数对多文件程序中的所有文件可用、最简便的方法是:将内联定义放在定义类的头文件中。

方法使用哪个对象

调用成员函数时,它将使用被用来调用它的对象的数据成员
所创建的新对象都有自己的存储空间,用于存储其内部变量和类成员;但同一个类的所有对象共享同一组类方法。

使用类

C++的目标是使得使用类与使用基本的内置类型(如int和char)尽可能相同

小结

指定类设计的第一步是提供类声明。类声明类似结构声明,可以包括数据成员和函数成员。声明的私有部分成员只能通过成员函数进行访问;声明公有部分成员可被使用类对象的程序直接访问。
类声明格式如下:

class className
{
private:
	data member declarations
public:
	member function prototypes
};

公有部分的内容构成了设计的抽象部分——公有接口
将数据封装到私有部分中可以保护数据的完整性,这被称为数据隐藏
C++通过类使得实现抽象、数据隐藏和封装等OOP特性很容易。
指定类设计的第二步是实现类成员函数
要创建对象(类的实例),只需将类名视为类型名即可:

Bozo bozetta;

这样做是可行的,因为类是用户定义的类型。类成员函数(方法)可通过类对象来调用。为此,需要使用运算符句点:

cout << bozetta.Retort();

10.3 类的构造函数和析构函数

C++的目标之一是让使用类对象就像使用标准类型一样,但由于类的数据部分的访问状态是私有的,这意味着程序不能直接访问数据成员,因此需要设计合适的成员函数,才能成功的将数据对象初始化。
C++提供了一个特殊的成员函数——类构造函数,专门用于构造新对象、将值赋给它们的数据成员。
类构造函数名称与类相同
构造函数的原型和函数头有一个有趣的特征——虽然没有返回值,但没有被声明为void类型

声明和定义构造函数

原型位于类声明的公有部分
构造函数

Stock::Stock(const string& co, long n, double pr)
{
}

程序声明对象时,将自动调用构造函数
构造函数的参数表示的不是类成员,而是赋给类成员的值
为避免混乱,常见的做法是在数据成员名中使用m_前缀,或者_后缀。

使用构造函数

C++提供了两种使用构造函数来初始化对象的方式。

  1. 显式的调用构造函数
Stock food = Stock("World Cabbage", 250, 1.25);
  1. 隐式的调用构造函数
Stock garment("Furry Mason", 50, 2.5);

构造与new一起使用的方法:

Stock* pstock = new Stock("Electroshock Games", 18, 19.0);

默认构造函数

默认构造函数是在未提供显示初始值时,用来创建对象的构造函数。

Stock fluffy_the_cat;  //使用默认构造函数

如果没有提供任何构造函数,则C++将自动提供默认构造函数,它是默认构造函数的隐式版本,不做任何工作。默认构造函数可能如下:

Stock::Stock() {}

如果为类定义了构造函数,程序员就必须为它提供默认构造函数
定义默认构造函数的方式有两种:

  1. 给已有构造函数的所有参数提供默认值
Stock(const string& co = "Error", int n = 0, double pr = 0.0);
  1. 通过函数重载来定义另一个构造函数——一个没有参数的构造函数:
Stock();
Stock::Stock()
{
	company = "no name";
	shares = 0;
	share_val = 0.0;
	total_val = 0.0;
}

隐式地调用默认构造函数时,不要使用圆括号。

Stock third;

析构函数

析构函数完成清理工作
例如:如果构造函数使用new来分配内存,则析构函数将使用delete来释放这些内存。否则,实际上没有需要完成的任务,只需让编译器生成一个什么都不做的隐式析构函数即可。
析构函数的名称:在类名前加~
析构函数可以没有返回值和声明类型,没有参数

Stock::~Stock()
{
}
Stock stock1("NanoSmart", 12, 20.0); //隐式调用构造函数
Stock stock2 = Stock("Boffo Objects", 2, 2.0);//调用构造函数创建临时对象,然后
//将其复制到stock2,调用析构函数,删除临时对象
Stock stock2 = Stock("Boffo Objects", 2, 2.0);//初始化,创建有指定值的对象,可能会创建临时对象
stock1 = Stock("Nifty Foods", 10, 50.0);//赋值,使用构造函数总会导致在赋值前创建一个临时对象

通常不应在代码中显式地调用析构函数:

  • 静态存储对象:析构函数将在程序结束时被调用
  • 自动存储类对象:将在程序执行完代码块时自动被调用
  • 对象是通过new创建:它将驻留在栈内存或自由存储区中,当使用delete来释放内存时,其析构函数将自动被调用。
  • 程序创建临时对象完成特定的操作:程序将在结束对该对象的使用时自动调用其析构函数。
    由于在类对象过期时析构函数会自动被调用,因此必须有一个析构函数。如果程序员没有提供析构函数,编译器将隐式声明一个默认析构函数,并在发现导致对象被删除的代码后,提供默认析构函数的定义。
    自动变量被放在栈中,因此最后创建的对象将最先被删除,最先创建的对象将最后被删除
C++11列表初始化

在C++11中,可以将列表初始化语法用于类
只要提供与某个构造函数的参数列表匹配的内容,并用大括号将它们括起来:

Stock hot_tip = {"Derivatives Plus Plus", 100, 45.0};
Stock jock {"Sport Age Storage, Inc"};
Stock temp {};

在前两个声明中,用大括号括起来的列表与下面的构造函数匹配:

Stock::Stock(const std::string& co, long n = 0, double pr = 0.0);

C++还提供了名为std::initialize_list的类,可将其用作函数参数或方法参数的类型。

const成员函数
void stock::show() const //promises not to change invoking object

以这种方式声明和定义类函数被称为const成员函数
就像尽可能将const引用和指针用作函数形参一样,只要类方法不修改调用对象,就将其声明为const

小结

如果构造函数只有一个参数,则将对象初始化为一个与参数类型相同的值时,该构造函数将被调用
假设有这样一个构造函数

cout << trip;

则可以使用下面的任何一种形式来初始化对象:

Bozo dribble = Bozo(44); //primary form
Bozo roon(66); //secondary form
Bozo tubby = 32; //special form for one-argument constructos

接受一个参数的构造函数允许使用赋值语法将对象初始化为一个值
默认构造函数没有参数,因此如果创建对象时没有进行显式地初始化,则将调用默认构造函数。
如果程序中没有提供任何构造函数,则编译器会为程序定义一个默认构造函数;否则,必须自己提供默认构造函数
默认构造函数可以没有任何参数;如果有,则必须给所有参数都提供默认值

10.4 this 指针

每个类成员函数有时可能涉及到两个对象,在这种情况下需要使用C++的this指针。
要让程序知道存储的数据,最直接的方式是让方法返回一个值。为此,通常使用内联代码:

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

定义一个成员函数,它查看两个Stock对象,并返回股价较高的那个对象的引用。假设将该方法命名为topval(),用于比较的方法原型:

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

该函数隐式地访问一个对象,而显式地访问另一个对象,并返回其中一个对象的引用。括号中的const表明,该函数不会修改被显式访问的对象;而括号后的const表明,该函数不会修改被隐式访问的对象。由于该函数返回了两个const对象之一的引用,因此返回类型也应为const引用。
this指针指向用来调用成员函数的对象(this被作为隐藏参数传递给方法),所有类方法都将this指针设置为调用它的对象的地址。
每个成员函数(包括构造函数和析构函数)都有一个this指针,指针指向调用对象。

const Stock& Stock::topval(const Stock& s) const
{
	if (s.total_val > total_val)
		return s; //argument object
	else
		retuen *this; //invoking object
}

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

10.5 对象数组

声明对象数组的方法与声明标准类型数组相同:

Stock mystuff[4]; //creates an array of 4 Stock objects

可以用构造函数来初始化数组元素。在这种情况下,必须为每个元素调用构造函数:

const int STKS = 4;
Stock stocks[STKS] = {
	Stock("NanoSmart", 12.5, 20),
	Stock("Boffo Objects", 200, 2.0),
	Stock("Monolithic Obelisks", 130, 3.25),
	Stock("Fleep Enterprises", 60, 6.5)
	};

这里的代码使用标准格式对数组进行初始化。
如果类包含多个构造函数,则可以对不同元素使用不同的构造函数
初始化对象数组的方案是,首先使用默认构造函数创建数组元素,然后花括号中的构造函数将创建临时对象,然后将临时对象的内容复制到相应的元素中。

10.6 类作用域

在类中定义的名称的作用域为整个类。
类作用域意味着不能从外部直接访问类的成员,公有成员函数也是如此。要调用公有成员函数必须通过对象
在定义成员函数时,必须使用作用域解析运算符:

void Stock::update(double price)
{
	...
}

在类声明或成员函数定义中,可以使用未修饰的成员名称(未限定的名称),就像sell()调用set_tot()成员函数那样。构造函数名称在被调用时,才能被识别,因为它的名称与类名相同。

作用域为类的常量

类声明中创建一个由所有对象共享的常量:

  1. 在类中声明一个枚举:
class Bakery
{
private:
	enum {Months = 12};
	double costs[Months];
	...

但所有对象中都不包含枚举
这里使用枚举只是为了创建符号常量,所以不需要提供枚举名。

  1. 使用关键字 static
class Bakery
{
private:
	static const int Months = 12;
	double costs[Months];
	...

这里创建一个名为Months的常量,该常量与其他静态变量存储在一起,被所有Bakery对象共享。

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

为防止两个枚举定义中枚举量发生冲突,C++11提供了一种新枚举,其枚举量的作用域为类。

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

C++11还提高了作用域内枚举的类型安全。
在有些情况下,常规枚举将自动转换为整型,但作用域内枚举不能隐式地转换为整型:
但必要时,可以进行显示类型转换:

int Frodo = int(t_shirt::Small);

C++11作用域内枚举的底层类型为int,另外

enum class : short pizza {Small, Medium, Large, XLarge}; //:short将底层类型指定为short

在C++11中也可使用这种语法指定常规枚举的底层类型,如果没有指定,随实现而异。

10.7 抽象数据类型

栈的特征:

  • 可创建空栈
  • 可将数据项添加到栈顶(压入)
  • 可从栈顶删除数据(弹出)
  • 可查看栈是否填满
  • 可查看栈是否为空
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

笑着的程序员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值