C++:结构和类

Sales_item类的作用是表示一本书的总销售额、售出册数和平均售价。

使用关键字class和关键字struct定义类唯一的区别就是默认的访问权限。如果使用struct定义类,则定义在第一个访问说明符之前的成员是public的;相反,如果使用class关键字,则这些成员是private的。出于统一编程风格的考虑,当希望定义的类的所有成员是public的时,使用struct;反之,如果希望成员是private的,使用class。

类有两项基本能力:一是数据抽象,即定义数据成员和函数成员的能力;二是封装,即保护类的成员不被随意访问的能力。通过将类的实现细节设为private,我们就能完成类的封装。类可以将其他类或者函数设为友元,这样它们就能访问类的非公有成员了。

封装

封装的优点:1)确保用户代码不会无意间破坏封装对象的状态;2)被封装的类的具体实现细节可以随时改变,而无须调整用户级别的代码。

一旦把数据成员定义成private的,类的作者就可以比较自由地修改数据了。当实现部分改变时,只需要检查类的代码本身以确认这次改变有什么影响;换句话说,只要类的接口不变,用户代码就无须改变。如果数据是public的,则所有使用了原来数据成员的代码都可能失败,这时我们必须定位并重写所有依赖于老版本实现的代码,之后才能重新使用该程序。

把数据成员的访问权限设成private还有另外一个好处,这么做能防止由于用户的原因造成数据被破坏。如果我们发现有程序缺陷破坏了对象的状态,则可以在有限的范围内定位缺陷:因为只有实现部分的代码可能产生这样的错误。因此,将查错限制在有限范围内将能极大地降低维护代码及修正程序错误的难度。

尽管当类的定义发生改变时无须更改用户代码,但是使用了该类的源文件必须重新编译。

数据结构

从最基本的层面理解,数据结构是把一组相关的数据元素组织起来然后使用它们的策略和方法。再进一步解释,数据结构就是数据及在数据上所允许的操作的一种逻辑组合。在C++语言中,可以通过结构(Struct)和类(Class)的形式自定义数据结构。

预处理器

确保头文件多次包含仍能安全工作的常用技术是预处理器(Preprocessor),它由C++语言从C语言继承而来。预处理器是在编译之前执行的一段程序,可以部分地改变我们写的程序。

C++程序会用到的一项预处理功能是头文件保护符(Header Guard),头文件保护符依赖于预处理变量。预处理变量由两种状态:已定义和未定义。#define指令把一个名字设定为预处理变量,另外两个指令则分别检查某个指定的预处理变量是否已经定义:#ifdef当且仅当变量已定义时为真,#ifndef当且仅当变量未定义时为真。一旦检查结果为真,则执行后续操作直至遇到#endif指令为止。如下所示,使用这些功能可以有效地防止头文件重复包含的发生:

头文件:include_name.h

#ifndef INCLUDE_NAME_H
#define INCLUDE_NAME_H

/* ... */

#endif

第一次包含头文件include_name.h时,#ifndef的检查结果为真,预处理器将顺序执行后面的操作直至遇到#endif为止。此时,预处理变量INCLUDE_NAME_H的值将变为已定义,而且include_name.h也会被拷贝到我们的程序中来。后面如果再一次包含include_name.h,则#ifndef的检查结果将为假,编译器将忽略#ifndef#endif之间的部分。如此,就有效地防止了头文件重复包含的发生。

类(class)

类的基本思想是数据抽象(Data Abstraction)和封装(Encapsulation)。数据抽象是一种依赖于接口(Interface)和实现(Implementation)分离的编程技术。类的接口包括用户所能执行的操作;类的实现则包括类的数据成员、负责接口实现的函数体以及定义类所需的各种私有函数。封装实现了类的接口和实现的分离。封装后的类隐藏了它的实现细节,也就是说,类的用户只能使用接口而无法访问实现部分。类要想实现数据抽象和封装,需要首先定义一个抽象数据类型(Abstract Data Type)。在抽象数据类型中,由类的设计者负责类的实现过程;使用该类的程序员则只需要抽象地思考做了什么,而无须了解类型的工作细节。

注意:(1)程序员们常把运行其程序的人称作用户(user)。类似的,类的设计者也是为其用户设计并实现一个类的人;显然,类的用户是程序员,而非应用程序的最终使用者。

为了使用类,我们不必关心它是如何实现的,而只需要知道3点:1)类名是什么?2)它是在哪里定义的?3)它支持什么操作?

类的作者定义了类对象可以执行的所有动作。

对于一个类来说,在创建其对象之前该类必须被定义过,而不能仅仅被声明。

直到类被定义之后数据成员才能被声明成这种类类型。换句话说,必须首先完成类的定义,然后编译器才能知道存储该数据成员需要多少空间。因为只有当类全部完成后类才算被定义,所以一个类的成员类型不能是该类自己。然而,一旦一个类的名字出现后,它就被认为是声明过了(但尚未定义),因此类允许包含指向它自身类型的引用或指针。如:

class Link_screen{
  Screen window;
  Link_screen* next;//允许包含自身类型的指针
  Link_screen* prev;
}

类的作用域

每个类都会定义它自己的作用域,事实上,一个类就是一个作用域。。在类的作用域之外,普通的数据成员和函数成员只能由对象、引用或者指针使用成员访问运算符来访问。对于类类型成员则使用作用域运算符访问。不论哪种情况,跟在运算符之后的名字都必须是对应类的成员:

Screen::pos ht=24,wd=80;//使用Screen定义的pos类型
Screen scr(ht,wd,' ');
Screen* p=&scr;
char c=scr.get();      //访问scr对象的get成员
c=p->get();            //访问p指针所指对象的get成员

构造函数(Constructor)

每个类都分别定义了它的对象被初始化的方式,类通过一个或几个特殊的成员函数来控制其对象的初始化过程,这些函数叫做构造函数。构造函数的任务是初始化类对象的数据成员,无论何时只要类的对象被创建,就会执行构造函数。

构造函数的名字和类名相同。和其他函数不一样的是,构造函数没有返回类型;除此之外类似于其他的函数,构造函数也有一个(可能为空的)参数列表和一个(可能为空的)函数体。类可以包含多个构造函数,和其他重载函数差不多,不同的构造函数之间必须在参数数量或参数类型上有所区别。

不同于其他成员函数,构造函数不能被声明成const的,当创建类的一个const对象时,直到构造函数完成初始化过程,对象才能真正取得其“常量”属性。因此,构造函数在const对象的构造过程可以向其写值。

合成的默认构造函数(Synthesized Default Constructor)

有些类并没有定义任何构造函数,可使用了这些类对象的程序仍然可以正确地编译和运行,类对象中的成员数据也会被初始化。但类的作者并没有为这些对象提供初始值,因此,可以知道它们执行了默认初始化。类通过一个特殊的构造函数来控制默认初始化过程,这个函数叫做默认构造函数(Default Constructor)。

默认构造函数在很多方面都有其特殊性。其中之一是,如果类没有显式地定义构造函数,那么编译器就会为此类隐式地定义一个默认构造函数。编译器创建的构造函数又被称为合成的默认构造函数(Synthesized Default Constructor)。对于大多数类来说,这个合成的默认构造函数将按照如下规则初始化类的数据成员:1)如果存在类内的初始值,用它来初始化成员;2)否则,默认初始化该数据成员。

成员函数

成员函数是定义为类的一部分的函数,有时也被成为方法(method)。成员函数通常以一个类对象的名义来调用。item1.isbn();使用点运算符(.)来表达需要“名为item1的对象的isbn成员函数”。点运算符只能用于类类型的对象。其左侧运算对象必须是一个类类型的对象,右侧运算符对象必须是该类型的一个成员名,运算结果为右侧运算对象指定的成员。当用点运算符访问一个成员函数时,通常是想调用该函数。我们使用调用运算符(())来调用一个函数。调用运算符是一对圆括号,里面放置实参(Argument)列表(可能为空)。成员函数isbn并不接受参数,因此,item1.isbn(),调用名为item1的对象的成员函数isbn,此函数返回item1中保存的ISBN书号。

访问控制与封装

如果给类定义了接口,且没有任何机制强制用户使用这些接口,则说明此类还没有封装。也就是说,用户可以直达此类对象的内部并且控制它的具体实现细节。

在C++语言中,使用访问说明符(Access Specifiers)加强类的封装性。在类中说明符可以出现多次,每个访问说明符指定了接下来的成员的访问级别,其有效范围直到出现下一个访问说明符或者到达类的结尾处为止。

  • 定义在public访问说明符之后的成员可以被类的所有用户访问。通常情况下,只有现实类的接口的函数才被设为public

  • 定义在private访问说明符之后的成员只能被类的友元和类的成员函数访问,但是不能被使用该类的代码访问,private部分封装了(即隐藏了)类的实现细节。数据成员以及仅供类本身使用而不作为接口的功能函数一般设为private

结构(struct)

结构的定义语法

struct structName
{
  int a=0;
  /* ... */
};

结构的定义以关键字struct开始,紧跟着结构名结构体(结构体部分可以为空),以分号";"作为定义的结束。结构体由花括号包围形成了一个新的作用域。结构内部定义的名字必须唯一,但是可以与类外部定义的名字重复。结构体右侧的表示结束的花括号后必须写一个分号,这是因为结构体后面可以紧跟变量名以示对该类型对象的定义,所以分号必不可少:struct Sales_data { /* ... */ } accum, trans, *salesptr;。分号表示声明符的结束。一般来说,最好不要把对象的定义和结构的定义放在一起。这么做无异于把两种不同实体的定义混在了一条语句里,一会儿定义结构,一会儿又定义变量,显然这是一种不被建议的行为。

现以定义一个Sales_data结构为例,Sales_data在头文件sales_data.h的初步定义如下:

头文件:sales_data.h

#ifndef SALES_DATA_H
#define SALES_DATA_H
#include 
struct Sales_data
{
  std::string bookNo;
  unsigned units_sold=0;
  double revenue=0.0;
};//注意:定义结构时,不要忘记此分号
#endif

结构数据成员(Data Member)

结构体定义结构的成员,我们的结构Sales_data只有数据成员。结构的数据成员定义了结构的对象的具体内容,每个对象有自己的一份数据成员拷贝。修改一个对象的数据成员,不会影响其他该结构的对象。

定义数据成员的方法和定义普通变量一样:首先说明一个基本类型,随后紧跟一个或多个声明符。我们的结构有3个数据成员:一个名为bookNo的string成员、一个名为units_sold的unsigned成员和一个名为revenue的double成员。每个Sales_data的对象都将包含这3个数据成员。

结构Sales_data的使用

源程序文件:main.cpp

#include 
#include 
//把定义结构Sales_data的头文件包含进来
#include "sales_data.h"

using namespace std;

int main(int argc,char** argv)
{
  //定义两个Sales_data结构类型的对象
  Sales_data data1,data2;
  //书的单价,用于计算销售收入
  double price=0;
  //读入第1笔交易:ISBN、销售数量、单价
  cin>>data1.bookNo>>data1.units_sold>>price;
  //计算销售收入
  data1.revenue=data1.units_sold*price;

  return 0;
}

你可能感兴趣的:(C++,C++,结构,类)