定义抽象数据类型

2023-10-13 15:50
文章标签 定义 抽象数据类型

本文主要是介绍定义抽象数据类型,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

  • 1. 定义抽象数据类型
    • 1.1 设计 Sales_data 类
      • 1.1.0 使用改进的 Sales_data 类
    • 1.2 定义改进的 Sales_data 类
      • 1.2.0 定义成员函数
      • 1.2.1 引入 this
      • 1.2.2 引入 const 成员函数
      • 1.2.3 类作用域和成员函数
      • 1.2.4 定义一个返回 this 对象的函数
    • 1.3 定义类相关的非成员函数
      • 1.3.0 定义 read 和 print 函数
      • 1.3.1 定义 add 函数
    • 1.4 构造函数(constructor)
      • 1.4.0 合成的默认构造函数 ( 编译器创建的构造函数 )
      • 1.4.1 某些类不能依赖于合成的默认构造函数
      • 1.4.2 定义 Sales_data 的构造函数
      • 1.4.3 =default 的含义
      • 1.4.4 构造函数初始值列表
      • 1.4.5 在类的外部定义构造函数
    • 1.5 拷贝、赋值和析构
      • 1.5.0 某些类不能依赖于合成的版本(编译器代替人工合成)

1. 定义抽象数据类型

类可以定义自己的数据类型
通过定义新的类型来反映待解决问题中的各种概念
数据抽象能帮助我们将 对象的具体实现对象所能执行的操作 分离 开来

1.1 设计 Sales_data 类

Sales_data的接口应该包含以下操作:

  • 一个 isbn 成员函数,用于返回对象 (这里指某类书) 的 ISBN 编号
  • 一个 combine 成员函数,用于将一个 Sales_data 对象加到另一个对象上
    (同一种书,将其数量,价格等相加)
  • 一个名为 add 的函数,执行两个 Sales_data 对象的加法
  • 一个 read 函数,将数据从 istream (输入流)读入到Sales_data对象中
  • 一个 print 函数,将Sales_data对象的值输出到 ostream (输出流)

1.1.0 使用改进的 Sales_data 类

1.从标准输入读取对象保存的数据写成 read 函数
2.将相同种类书的数据对应相加的操作(total += trans),写成一个combine函数
3.标准输出对象保存的数据写成 print 函数

Sales_data total; //对象total保存销售记录(isbn书号、销售数量、单价...)
if(read(cin, total)) //从标准输入cin读取对象total保存的数据
{Sales_data trans; //对象trans保存销售记录while(read(cin, trans)) //从标准输入cin读取对象trans保存的数据{if(total.isbn() == trans.isbn()) //如果对象total和对象trans保存的isbn书号一致{total.combine(trans); //将对象trans保存的数据与对象total保存的数据对应相加}else{print(cout, total) << endl; //书号不一致,输出当前对象保存的数据total = trans; //将此书的数据移到对象total中,以便下一次循环对比}}print(cout, total) << endl;//输出最后一条销售记录
}else{cerr << "No data?!" << endl; //未读取到数据
}

1.2 定义改进的 Sales_data 类

struct Sales_data{ 
//定义在类内部的函数是隐式的内联函数(编译时直接展开函数内部的东西)
//成员函数:Sales_data对象的操作//isbn函数返回类型为stringstd::string isbn() const {return bookNo}; //isbn()后的const是修饰隐式this指针的//combine函数返回类型为引用类型Sales_data& combine(const Sales_data&);double avg_price() const;平均价格函数,const是修饰隐式this指针的
//数据成员(对象所含有的全部属性)std::string bookNo; //书号unsigned units_sold = 0; //销售数量double revenue = 0.0; //销售单价
}; //类定义别忘了最后加分号;
// Sales_data 的非成员接口函数
Sales_data add(const Sales_data&, const Sales_data&); //执行两个 Sales_data 对象的加法
//当函数返回引用类型时,没有复制返回值,相反,返回的是对象本身
//如果返回类型不是引用,在调用函数的地方会将函数返回值复制给临时对象
std::ostream &print(std::ostream&, const Sales_data&); //将Sales_data对象的值输出到 ostream (输出流)
std::istream &read(std::istream&, Sales_data&); //将数据从 istream (输入流)读入到Sales_data对象中

1.2.0 定义成员函数

所有成员必须在类内部声明
成员函数体既可在类内,也可在类外

成员函数isbn

//返回 Sales_data 对象的bookNo数据成员
std::string isbn() const { return bookNo; } //const的作用是修饰隐式this指针的类型

isbn是如何返回 Sales_data 对象的bookNo数据成员?

1.2.1 引入 this

对成员函数isbn的调用

total.isbn(); //对象total调用(.)成员函数isbn,返回对象total的bookNo

编译器负责把对象total的地址传递给成员函数isbn的隐式形参this
等价地认为编译器将该调用重写成如下形式:

//伪代码,用于说明调用成员函数的实际执行过程
//类名::成员函数(传入对象地址)
作用域运算符(::)说明成员函数isbn()被声明在Sales_data类的作用域内
Sales_data::isbn(&total);//伪代码,隐式形参this指针存着对象的地址

还能把isbn函数定义成如下形式:

std::string isbn() const { return this -> bookNo; }  //const的作用是修饰隐式this指针的类型
//等价于return (*this).bookNo 
//this指针存着对象total的地址,对this解引用获得对象total
//等价于return total.bookNo

因为 this 的目的总是指向 “这个” 对象,所以 this是一个常量指针(所存的地址不能被改变),我们不允许改变this中保存的地址

1.2.2 引入 const 成员函数

默认情况下,this的类型是指向类类型非常量版本的常量指针

例如:Sales_data成员函数中,this的类型(常量指针)是Sales_data *const
不能把this绑定到一个常量对象上,这样我们不能在一个常量对象上调用普通成员函数 (个人理解:常量对象的属性(数据成员)都固定,而成员函数是对对象的属性进行操作)

如果isbn是一个普通函数且this是一个普通指针参数,我们应把this声明成const Sales_data *const
第一个const:在isbn函数体内不会改变this所指的对象。
第二个const:this指针所存地址不能被改变,即所指对象不能改变

紧跟在参数列表后的const表示this是一个指向常量的指针,这样使用const的成员函数被称为 常量成员函数

//伪代码,说明隐式的this指针是如何使用的
//下面代码非法,因为不能显式地定义自己的this指针
//返回类型String,作用域运算符(::)说明成员函数isbn()被声明在Sales_data类的作用域内
std::string Sales_data::isbn(const Sales_data *const this){ //非法:不能显式地定义自己的this指针return this->bookNo; //等价于(*this).bookNo等价于total.bookNo
}

this是指向常量的指针(上述形参中第一个const),所以常量成员函数不能改变调用它的对象的内容

1.2.3 类作用域和成员函数

当在类外部定义成员函数时,成员函数的定义必须与成员函数声明(类内部)的返回类型、参数列表和函数名完全一致

//作用域运算符(::)说明成员函数avg_price()被声明在Sales_data类的作用域内
//形参列表后const的作用是修饰隐式this指针的类型
double Sales_data::avg_price() const {if(units_sold)//如果售出数量非0return revenue/units_sold; //隐式this指针将指向对象的revenue和units_sold(Sales_data的数据成员),计算完成后返回给对象else //售出数量为0return 0; //结束函数
}

1.2.4 定义一个返回 this 对象的函数

//当函数返回引用类型时,没有复制返回值,相反,返回的是对象本身
//如果返回类型不是引用,在调用函数的地方会将函数返回值复制给临时对象
//作用域运算符(::)说明成员函数combine()被声明在Sales_data类的作用域内
//lhs左侧运算对象 rhs右侧运算对象
Sales_data& Sales_data::combine(const Sales_data &rhs){units_sold += rhs.units_sold; //把rhs的数据成员(对象的某一属性)加到this对象的数据成员上(对象的某一属性)revenue += rhs.revenue;return *this; //this存着所指对象的地址,解引用获得对象,该函数返回类型为引用//返回对象的引用//使用this来把对象当成一个整体访问,而非直接访问对象的某个数据成员
}

调用函数

//total为combine函数隐式形参this所指的对象,trans为右侧运算对象
total.combine(trans);

解释上述调用combine

//rhs绑定到trans上
//combine函数的隐式参数:this指针(存着所指对象的地址)
Sales_data& Sales_data::combine(const Sales_data &rhs){//左侧为this所指对象的数据成员(对象的某一属性)//右侧为传入的对象,该对象调用数据成员(对象的某一属性)units_sold += rhs.units_sold;
//等价于total.units_sold = total.units_sold + rhs.units_sold;revenue += rhs.revenue;
//等价于total.revenue = total.revenue + rhs.revenuereturn *this; //this存着所指对象的地址,解引用获得对象,该函数返回类型为引用//返回对象的引用//使用this来把对象当成一个整体访问,而非直接访问对象的某个数据成员
}

1.3 定义类相关的非成员函数

类的作者需要定义一些辅助函数,如add、read、print
尽管这些函数定义从概念上属于类的接口的组成部分,但它们实际不属于类本身

定义非成员函数和其他函数一样,将函数的声明和定义分离开来

一般来说,如果非成员函数是类接口的组成部分,则这些非成员函数的声明应该与在同一个头文件中

1.3.0 定义 read 和 print 函数

输入的交易信息包括ISBN、售出总数、售出价格

//IO类属于不可拷贝类型,所以只能通过引用来传递它们
//read函数从给定流(istream)中将数据读到给定的对象里
//将item绑定到传来的对象上
istream &read(istream &is, Sales_data &item)
{double price = 0;//给定流is读取对象的数据成员(对象的属性)is >> item.bookNo >> item.units_sold >> price;//价格*单价的结果赋值给对象的总价item.revenue = price * item.units_sold;return is; //返回is流的引用
}
//print函数负责将给定对象的内容打印到给定的流中(ostream)
//将item绑定到传来的对象上
ostream &print(ostream &os, const Sales_data &item)
{	//将对象的内容传到os流中os << item.isbn() << " " << item.units_sold << " " << item.revenue << " " << item.avg_price();return os; //返回os流的引用
}

1.3.1 定义 add 函数

//lhs左侧运算对象 rhs右侧运算对象
//实参传入两个对象给形参
//lhs绑定到传入的左侧实参上,rhs绑定到传入的右侧实参上
Sales_data add(const Sales_data &lhs, const Sales_data &rhs)
{Sales_data sum = lhs; //把lhs的数据成员拷贝给sumsum.combine(rhs); //把rhs的数据成员加到sum中//lhs.combine(rhs)return sum; //返回两个对象的和(售出数量的和和价格总和,具体见combine函数体内的内容)//返回类型为非引用类型,所以返回sum的副本
}

1.4 构造函数(constructor)

类通过一个或几个特殊的成员函数来控制其对象的初始化过程,这些函数叫构造函数

构造函数的任务:
初始化类对象的数据成员(个人将数据成员理解为对象的属性)只要类的某一对象被创建,就会自动执行构造函数

构造函数的特点:

  • 名字与类名相同
  • 无返回类型
  • 不能被声明成const

当创建类的一个const对象时,直到构造函数完成初始化过程,对象才能真正取得其"常量"属性。因此,构造函数在const对象的构造过程中可向其写值

(构造过程中,属性还未被const修饰,所以此过程中可向对象的属性进行写值,一旦构造完成,所有属性均不可修改)

1.4.0 合成的默认构造函数 ( 编译器创建的构造函数 )

类通过一个特殊的构造函数来控制默认初始化过程,这个函数叫做默认构造函数(default constructor) 默认构造函数无须任何实参

如果类没有显式地定义构造函数,那编译器就会为我们隐式地定义一个默认构造函数,编译器创建的构造函数又被称为合成的默认构造函数(synthesized default constructor)

对大多数类,这个合成的默认构造函数将按照如下规则初始化类的数据成员(对象的属性):

  • 如果存在类内的初始值,用已有初始值来初始化对象的数据成员
  • 否则,默认初始化该对象的数据成员

1.4.1 某些类不能依赖于合成的默认构造函数

合成的默认构造函数只适合非常简单的类
对于一个普通的类来说,必须定义它自己的默认构造函数

1.4.2 定义 Sales_data 的构造函数

对于 Sales_data 类,使用下面参数定义4个不同的构造函数

  • istream& :从中读取一条交易信息
  • const string& :表示ISBN编号
  • unsigned :表示售出的图书数量
  • double :表示图书的售出价格
  • const string& :表示ISBN编号;编译器将赋予其他成员默认值
  • 空参数列表(即默认构造函数)
struct Sales_data{
//相比之前新增的构造函数//希望构造函数等同于合成默认构造函数,则在构造函数的参数列表后添加 = defaultSales_data() = default;//下面两个构造函数的 {函数体} 为空,因为构造函数的唯一目的就是为数据成员赋初值,如果没有其他任务需要执行,函数体也就为空了//构造函数初始值列表(冒号:和花括号{函数体}之间的代码)Sales_data(const std::string &s) : bookNo(s) { }//类名(形参列表): 构造函数初始值列表 { }//构造函数初始值列表为新建对象的数据成员赋初值//没有出现在构造函数初始值列表的数据成员,将通过类内定义的初始值进行初始化Sales_data(const std::string &s, unsigned n, double p) : bookNo(s), units_sold(n), revenue(p*n) { }//如果是只接受一个string参数的构造函数,则可以写为://Sales_data(const std::string &s) : bookNo(s), units_sold(0), revenue(0) { } Sales_data(std::istream &);//原来已有的其他成员//isbn()后的const是修饰隐式this指针的std::string isbn() const { return bookNo; }//当函数返回引用类型时,没有复制返回值,相反,返回的是对象本身//如果返回类型不是引用,在调用函数的地方会将函数返回值复制给临时对象Sales_data& combine(const Sales_data&);//avg_price() 后的const是修饰隐式this指针的double avg_price() const;std::string bookNo;unsigned units_sold = 0;double revenue = 0.0;
}; //勿忘此处分号

1.4.3 =default 的含义

如果类中已经有其他非默认构造函数,现在又想要使用默认构造函数,则在默认构造函数后面添加 =default

//希望构造函数等同于合成默认构造函数,则在构造函数的参数列表后添加 = defaultSales_data() = default;

如果=default在类的内部,则默认构造函数是内联
如果=default在类的外部,则该成员默认情况下不是内联

1.4.4 构造函数初始值列表

类名(形参列表): 构造函数初始值列表 { }

//下面两个构造函数的 {函数体} 为空,因为构造函数的唯一目的就是为数据成员赋初值,如果没有其他任务需要执行,函数体也就为空了//构造函数初始值列表(冒号:和花括号{函数体}之间的代码)Sales_data(const std::string &s) : bookNo(s) { }//类名(形参列表): 构造函数初始值列表 { }//构造函数初始值列表为新建对象的数据成员赋初值Sales_data(const std::string &s, unsigned n, double p) : bookNo(s), units_sold(n), revenue(p*n) { }//如果是只接受一个string参数的构造函数,则可以写为:Sales_data(const std::string &s) : bookNo(s), units_sold(0), revenue(0) { } 

1.4.5 在类的外部定义构造函数

//::作用域运算符表明构造函数Sales_data(std::istream &is)在类Sales_data的作用域内
//构造函数的名字与类名一致
//这里没有构造函数初始值列表对对象的数据成员进行初始化,但由于执行了构造函数的 {函数体} ,所以对象的数据成员(属性)仍能被初始化
Sales_data::Sales_data(std::istream &is){read(is,*this); //read函数的作用是从is(输入流)中读取一条交易信息然后存入this所指的对象中//this指针存着对象的地址,解引用this获得对象,使用this来把对象当成一个整体访问,而非直接访问对象的某个数据成员
}

1.5 拷贝、赋值和析构

类需要控制拷贝、赋值以及销毁对象过程中发生的行为

1.对象在何种情况下会被拷贝

  • 初始化变量
  • 以值的方式传递
  • 返回一个对象

2.使用赋值运算符时会发生对象的赋值操作

total = trans; //将对象trans赋值给对象total(连同对象的成员一并赋值)
//等价于
//对象.对象的数据成员
total.bookNo = trans.bookNo;
total.units_sold = trans.units_sold;
total.revenue = trans.revenue;

3.当对象不存在时执行销毁操作,例如:

  • 局部对象会在创建它的块结束时被销毁
  • vector对象(或数组)销毁时存储在其中的对象也会被销毁

1.5.0 某些类不能依赖于合成的版本(编译器代替人工合成)

如果我们不主动定义拷贝、赋值、销毁这些操作,则编译器将替我们合成它们。尽管编译器能替我们合成这些操作,但要清楚一点,对于某些类来说合成的版本无法正常工作。特别是,当类需要分配类对象之外的资源时,合成的版本常常会失效
例如:管理动态内存的类通常不能依赖于上述操作的合成版本

如果类包含vector或string成员,则其拷贝、赋值、销毁的合成版本能够正常工作
当我们对含有vector成员的对象执行拷贝或赋值操作时,vector类会设法拷贝或赋值成员中的元素
当这样的对象被销毁时,将销毁vector对象,也就是一次销毁vector中的每一个元素

这篇关于定义抽象数据类型的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/204284

相关文章

Spring 源码解读:自定义实现Bean定义的注册与解析

引言 在Spring框架中,Bean的注册与解析是整个依赖注入流程的核心步骤。通过Bean定义,Spring容器知道如何创建、配置和管理每个Bean实例。本篇文章将通过实现一个简化版的Bean定义注册与解析机制,帮助你理解Spring框架背后的设计逻辑。我们还将对比Spring中的BeanDefinition和BeanDefinitionRegistry,以全面掌握Bean注册和解析的核心原理。

浙大数据结构:树的定义与操作

四种遍历 #include<iostream>#include<queue>using namespace std;typedef struct treenode *BinTree;typedef BinTree position;typedef int ElementType;struct treenode{ElementType data;BinTree left;BinTre

类和对象的定义和调用演示(C++)

我习惯把类的定义放在头文件中 Student.h #define _CRT_SECURE_NO_WARNINGS#include <string>using namespace std;class student{public:char m_name[25];int m_age;int m_score;char* get_name(){return m_name;}int set_name

c++ 定义二位数组

在 C++ 中,定义二维数组有几种常见的方式。以下是几个示例: 1. 静态二维数组 定义: int array[3][4]; 这里,array 是一个 3 行 4 列的整数二维数组。 初始化: int array[3][4] = {{1, 2, 3, 4},{5, 6, 7, 8},{9, 10, 11, 12}}; 2. 动态二维数组 使用指针和动态内存分配: 定义:

java类中定义接口的有哪些好处

第一步:首先是是定义一个类,同时里面定义接口 public class Util { public interface Worker { void work(int a); } } 第二步:定义一个类去实现第一步类中定义的接口 public class Demo implements Worker { @Override public void work(int a) { System

数据结构中抽象数据类型如何实现?

抽象数据类型可以通过固有的数据类型(如整形,实型,字符型等)来表示和实现。 即利用处理器中已存在的数据类型来说明新的结构,用已经实现的操作来组合新的操作。 用类c语言(介于伪码和c语言之间)作为描述工具。 例如:抽象数据类型“复数”的实现           typedef  sturt{                float  realpart;      //实部

vue3 为组件的 emits 标注类型,defineEmits基于类型的定义的简单理解

1)在 <script setup> 中,emit 函数的类型标注也可以通过运行时声明或是类型声明进行。 2)基于类型的: const emit = defineEmits<{ (e: 'change', id: number): void (e: 'update', value: string): void }>() 说明:e: 指定了方法名,id:数字型的参数,这个就是限定了方法名及

python 字符串的定义和操作方法

str='  why is money  ' # 获取字符串对应索引的值 print(f"{str[0]}") print(f"{str[-1]}") #获取对应字符元素的数量 num=str.count('y') print(f"字符y的数量:{num}") #对应元素所在的索引 index=str.index("is") print(f"{index}")

医院检验系统LIS源码,LIS系统的定义、功能结构以及样本管理的操作流程

本文将对医院检验系统LIS进行介绍,包括LIS系统的定义、功能结构以及样本管理的操作流程方面。 LIS系统定义 LIS系统(Laboratory Information System)是一种专门为临床检验实验室开发的信息管理系统,其主要功能包括实验室信息管理、样本管理、检验结果管理、质量控制管理、数据分析等。其主要作用是管理医院实验室的各项业务,包括样本采集、检验、结果录入和报告生成等。Li

c++通用模板类(template class)定义实现详细介绍

有时,有两个或多个类,其功能是相同的,仅仅是数据类型不同,如下面语句声明了一个类:class Compare_int { public : Compare(int a,int b) { x=a; y=b; } int max( ) { return (x>y)?x:y; } int min( ) { return (x&... 有时,有两个或多个类,其功能是相同的,仅仅是数