第十章——对象和类

面向对象编程(OOP)是一种特殊的、设计程序的概念性方法,下面是最重要的OOP特性:

  • 抽象
  • 封装和数据隐藏
  • 多态
  • 继承
  • 代码的可重用性

为了实现这些特性并将它们组合在一起,C++所做的最重要的是提供了类

采用过程性编程首先考虑 需要遵循的步骤,然后考虑如何表示这些数据

采用OOP方法时,首先从用户的角度考虑对象——描述对象所需的数据以及描述用户与数据交互所需的操作,完成对接口的描述后,需要确定如何实现接口和数据存储

抽象和类

生活中充满复杂性,处理复杂性的方法之一是简化和抽象

类型是什么 

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

  • 决定数据对象需要的内存数量
  • 决定如何解释内存中的位(long 和float在内存中占用的位数相同,但将它们转换为数值的方法不同)
  • 决定可使用数据对象执行的操作或方法 

C++中的类

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

下面来看一个表示股票的类

将某人当前持有的某种股票作为一个基本单元,数据表示中包含他持有的股票数量,将其可执行的操作限制为

  • 获得股票
  • 增持
  • 卖出股票
  • 更新股票价格
  • 显式关于所持股票的信息

可以根据上述清单定义stock类的公有接口,为支持该接口,需要存储一些信息

  • 股票的公司名称
  • 所持股票的数量
  • 美股的价格
  • 股票总值 

接下来定义类,一半类由两个部分组成

  • 类声明:以数据成员的方式描述数据部分,以成员函数(方法)的方式描述共有接口
  • 类方法定义:描述如何实现类成员函数 

 通常将类定义放在头文件中,并将实现(类方法的代码)放在源代码文件中

stock00.h

#ifndef STOCK00_H_
#define STOCK00_H_

#include<string>
class Stock
{
private:
	std::string company;
	long shares;
	double share_val;
	long total_val;
	void set_tot() { total_val = shares * share_val; }
public:
	void acquire(const std::string& co, long n, double pr);
	void buy(long num, double price);
	void sell(long num, double price);
	void update(double price);
	void show();
};
#endif

 C++关键字class指出这些代码定义了一个类设计,这种语法指出,Stock是这个新类的类型名。该声明让我们能够声明Stock类型的变量——称为对象或实例,每个对象都表示一只股票,比如下面创建了两个Stock对象

Stock sally;
Stock solly;

1.访问控制

我们可以看见关键字private和public,他们描述了对类成员的访问控制。使用类对象的程序都可以直接访问共有部分,但只能通过公有成员函数(或友元函数)来访问对象的私有成员。例如要修改Stock类的shares成员,只能通过Stock的成员函数。因此公有成员函数是程序和对象的私有成员之间的桥梁,提供了对象和程序之间的接口,防止程序直接访问数据被称为数据隐藏。

类设计尽可能将共有接口与实现细节分开,共有接口表示设计的抽象组件,将实现细节放在一起并将它们与抽象分开被称为封装。数据隐藏(将数据放在类的私有部分中)也是一种封装。

 数据隐藏不仅可以防止直接访问数据,还让开发者(类的用户)无需了解数据是如何被表示的

2.控制对成员的访问:共有还是私有

无论类成员是数据成员还是成员函数,都可以在类的共有部分或私有部分中声明它。但由于隐藏数据是OOP的主要目标之一,因此数据项通常放在私有部分,组成类接口的成员函数放在共有部分

类和结构它们之间的唯一区别是,结构的默认访问访问类型是public,而类为private

实现类成员函数

还需要创建类描述的第二部分:为那些由类声明中的原型表示的成员函数提供代码。

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

  • 定义成员函数时,使用作用域解析运算符(::)来标识函数所属的类
  • 类方法可以访问类的private组件

stock00.cpp 

#include<iostream>
#include"stock00.h"

void Stock::acquire(const std::string& co, long n, double pr)
{
	company = co;
	if (n < 0)
	{
		std::cout << "Number of shares can't be negative; "
			<< company << " shares set to 0.";
		shares = 0;
	}
	else
		shares = n;
	share_val = pr;
	set_tot();
}
void Stock::buy(long num, double price)
{
	if (num < 0)
	{
		std::cout << "Number of shares purchased can't be negative. "
			<< "Transaction is aborted.\n";
	}
	else
	{
		shares += num;
		share_val = price;
		set_tot();
	}
}
void Stock::sell(long num, double price)
{
	using std::cout;
	if (num < 0)
	{
		cout << "Number of shares can't be negative. "
			<< "Transsaction is aborted.\n";
	}
	else if (num > shares)
	{
		cout << "You can't sell more than you have! "
			<< "Transaction is aborted.\n";
	}
	else
	{
		shares -= num;
		share_val = price;
		set_tot();
	}
}
void Stock::update(double price)
{
	share_val = price;
	set_tot();
}
void Stock::show()
{
	std::cout << "Company: " << company << '\n';
	std::cout << "Shares: " << shares << '\n';
	std::cout << "Shares Price: " << share_val << '\n';
	std::cout << "Total Worth: " << total_val << '\n';
}

1.内联方法

位于类声明中的函数会自动成为内联函数,也可以在类声明之外定义成员函数,并使其成为内联函数,为此只需要在类实现部分中定义函数时使用inline限定符即可:

class Stock
{
private:
	...
		void set_tot();			//definition kept separate
public:
	...
};
inline void Stock::set_tot()	//use inline in definition
{
	total_val = shares * share_val;
}

 2.方法使用哪个对象

创建的每个新对象都有自己的存储空间,用于存储内部变量和类成员;但同一个类的所有对象共享同一组方法,即每种方法只有一个副本 

 使用类

知道如何定义类及其方法后,来创建一个程序,它创建并使用类对象。C++的目标是使得使用类与使用基本的内置类型(int、double等)尽可能相同

usestock00.cpp

#include<iostream>
#include"stock00.h"
int main()
{
	Stock stock1;
	stock1.acquire("NanoSmart", 20, 12.5);
	stock1.show();
	stock1.buy(15, 25.5);
	stock1.show();
	stock1.sell(400, 18.8);
	stock1.show();
	stock1.buy(1000000, 30.25);
	stock1.show();
	stock1.sell(500000, 10.55);
	stock1.show();
	return 0;
}

 

main()函数只是用来测试Stock类的设计。当Stock类的运行情况与预期的相同后,便可以在其他程序中将Stock类作为用户定义的类型来使用 。

 小结

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

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

共有部分的内容构成了设计的抽象部分——共有接口。将数据封装到私有部分中可以保护数据的完整性,这被称为数据隐藏。C++通过类使得实现抽象、数据隐藏和封装等OOP特性很容易

指定类设计的第二步是实现类成员函数。可以在类声明中提供完整的函数定义,而不是函数原型,但是通常的做法是单独提供函数定义(除非函数很小)。在这种情况下,需要使用作用域解析运算符来指出成员函数属于哪个类。

类的构造函数和析构函数

 C++的目标之一是让使用类对象就像使用标准类型一样,但是到现在为止,前面提供的代码还不能让我们像初始化int那样来初始化Stock对象

int year = 2001;
struct thing 
{
    char *pb;
    int m;
};
thing anb ={"wodget",-23};    //valid initialization
Stock hot ={"Sussy",200,50.5; //error

不能像上面那样初始化Stock对象的原因在于,数据部分的访问状态是私有的,这意味着程序不能直接访问数据成员。程序只能通过成员函数来访问数据成员,因此需要设计合适的成员函数,才能将对象初始化。

C++提供了一个特殊的成员函数——类构造函数,专门用于构造新对象、将值赋给它们的数据成员

声明和定义构造函数

现在需要创建Stock的构造函数。由于需要为Stock对象提供3个值,因此应为构造函数提供3个参数。(第四个值total_val成员,是根据shares和share_val计算得到的,因此不必为构造函数提供这个值),下面是构造函数的一种可能定义

Stock::Stock(const string & co, long n ,double pr)
{
    company = co;
    if(n < 0)
    {
        std::cout<<"Number of shares can't be negative; "
                << company <<" shares set to 0";
        shares = 0;
    }
    else
        shares = n;
    share_val = pr;
    set_tot();
} 

不熟悉构造函数的可能会写出下面的语句

Stock::Stock(const string & company, long shares ,double share_val)
{
...
}

这是错误的。构造函数的参数表示的不是类成员,而是赋给类成员的值,因此参数名不能与类成员相同。

使用构造函数

C++提供了两种使用构造函数来初始化对象的方式。第一种方式是显式地调用构造函数:

Stock food = Stock("World Cabbage",250,1.25);

这将food对象的company成员函数设置为字符串”World Cabbage“,将shares成员设置为250,以此类推

另一种方式是隐式地调用构造函数:

Stock garment("Furry Mason",50 ,2.5);

默认构造函数

默认构造函数是在未提供显式初始值时,用来创建对象的构造函数,也就是下面这种情况

Stock stock1; 

这条语句管用的原因在于,如果没有提供任何构造函数,则C++将自动提供默认构造函数。

仅当程序中没有提供任何构造函数时,编译器才会提供默认构造函数 。

定义默认构造函数的方式有两种。一种是给已有构造函数的所有参数提供默认值

Stock(const string & co ="Error" ,int n = 0, double pr = 0.0);

 另一种方式是通过函数重载来定义另一个构造函数—— 一个没有参数的构造函数

Stock();

由于只能有一个默认构造函数,因此不要同时采用这两种方式。 

(在设计类时通常应提供对所有类成员做隐式初始化的默认构造函数。)

使用上述任何一种方式(没有参数或所有参数都有默认值)创建了默认构造函数后,便可以声明对象变量,而不对它们进行显式初始化.

析构函数

 用构造函数创建对象后,程序负责跟踪该对象直到过期为止。对象过期时,程序将自动调用一个特殊的成员函数——析构函数。析构函数完成清理工作

例如,如果构造函数使用new来分配内存,则析构函数将使用delete来释放这些内存。Stock的构造函数没有使用new,因此析构函数实际上没有需要完成的任务,只需要让编译器生成一个什么都不做的隐式析构函数即可

析构函数的名称:在类名前加上~。因此Stock()类的析构函数为~Sock()。

与构造函数不同的是,析构函数没有参数,因此Stock析构函数的原型必须是这样的:~Stock();

由于在类对象过期时析构函数将自动被调用,因此必须有一个析构函数。如果程序员没有提供析构函数,编译器将隐式地声明一个默认析构函数。

 const 成员函数

请看下面的代码段

const Stock land = Stock("Kuudorn");
land.show();

对于当前的C++来说,编译器将拒绝执行第二行代码。因为show()的代码无法确保调用对象不被修改——调用对象和const一样,不应被修改。我们以前通过将函数参数声明为const引用或指向const的指针来解决这种问题,但是这里存在语法问题:show()函数没有任何参数。需要一种新的语法——保证函数不会修改调用对象。C++的解决方法是将const关键字放在函数的括号后面

void show() const;

 以这种方式声明和定义的类函数被称为const成员函数,只要类方法不修改调用对象,就应将其声明为const

构造函数和析构函数小结

构造函数是一种特殊的类成员函数,在创建类对象时被调用。构造函数的名称和类名相同,但通过函数重载,可以创建多个同名的构造函数,条件是每个函数的特征标(参数列表)都不同。另外构造函数没有声明类型。。通常,构造函数用于初始化类对象的成员,初始化应与构造函数的参数列表匹配

默认构造函数没有参数,因此如果创建对象时没有进行显式地初始化,则将调用默认构造函数。如果程序中没有提供任何构造函数,则编译器会为程序定义一个默认构造函数;否则必须自己提供默认构造函数。

默认构造函数可以没有任何参数;如果有,则必须给所有参数都提供默认值

就像对象被创建时程序将调用构造函数一样,当对象被删除时,程序将调用析构函数,每个类只能有一个析构函数。析构函数没有返回值,也没有参数,其名称是类名称前加上~。

this指针

 到目前为止,每个类成员函数都只涉及一个对象,即调用它的对象,但有时方法可能涉及到两个对象,在这种情况下需要使用C++的this 指针

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

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

第一种格式隐式地访问stock1,显式地访问stock2;第二种格式显式地访问stock1,隐式地访问stock2;无论使用哪一种方式,都将对这两个对象进行比较,并返回股价总值较高的那一个对象。实际上这种方法有些混乱,如果可以使用关系运算符>来比较这两个对象将较为清晰。

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

 s.total_val 是作为参数传递的对象的总值,total_val是用来调用该方法的对象的总值。如果s.total_val大于total_val,则函数将返回指向s的引用;否则将返回用来调用该方法的对象,问题在于如何称呼这个对象

C++解决这种问题的方法是:使用被称为this的特殊指针,。this指针指向用来调用成员函数的对象

这样函数调用stock1.topval(stock2)将this设置为stock1对象的地址

 

 所有的类方法都是将this指针设置为调用它的对象的地址。

现在我们可以利用this指针完成上面的方法定义了

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

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

类作用域

前面我们介绍过全局作用域和局部作用域。可以在全局变量所属文件的任何地方使用它,而局部变量只能在其所属的代码块中使用

C++引入了一种新的作用域:类作用域

在类中定义的名称(如类数据成员名和类成员函数名)的作用域都为整个类,作用域为整个类的名称只在该类中是已知的,在类外是不可知的。因此可以在不同类中使用相同的类成员名而不会引起冲突

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/36659.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

OpenCV 入门教程:像素访问和修改

OpenCV 入门教程&#xff1a;像素访问和修改 导语一、像素访问1.1 获取图像的大小1.2 访问图像的像素值1.3 修改图像的像素值 二、示例应用2.1 图像反转2.2 阈值化操作 三、总结 导语 在图像处理和计算机视觉领域&#xff0c;像素级操作是非常重要和常见的任务之一。通过像素访…

Oracle的CentOS安装

1.CentOS环境 阿里云轻量型服务器 2核-4g内存-80G系统盘 2.Oracle下载 Oracle下载 Oracle 数据库免费 CPU 限制 Oracle 数据库免费版自动将自身限制为两个内核进行处理。例如&#xff0c;在具有 2 个双核 CPU&#xff08;四个核&#xff09;的计算机上&#xff0c;如果大量…

mac批量在文件名前面加相同文字?

mac批量在文件名前面加相同文字&#xff1f;你平时在使用电脑进行工作或者学习的时候&#xff0c;是不是需要做一些关于文件整理和保存的操作呢&#xff0c;并且还需要对一大堆的文件进行重名呢&#xff1f;相信很大多数小伙伴都要面对这些&#xff0c;经常需要将大量文件的名称…

SpringBoot学习——追根溯源servlet是啥,tomcat是啥,maven是啥 springBoot项目初步,maven构建,打包 测试

目录 引出追根溯源&#xff0c;过渡衔接servlet是啥&#xff1f;tomcat是啥&#xff1f; 前后端开发的模式1.开发模式&#xff1a;JavaWeb&#xff1a;MVC模型2.Web&#xff1a;Vue&#xff0c;MVVC模型3.后端相关3.1 同步与异步3.2 Controller层3.3 Service层&#xff1a;要加…

阐述kubernetes部署:基础设施安装

基础设施部署 持久卷的建立 请参考&#xff1a;《持久卷的建立》 elasticsearch部署 一、设置远程扩展字典 不使用自定义字典请忽略此步骤 首先更改ES中IK插件的配置&#xff1a; vi/opt/kubernetes/es/IKAnalyzer.cfg.xml 按您的实际设置的秘钥配置secret_value&#xff1a; …

华为配置LLDP基本功能

华为配置LLDP基本功能 1.什么是lldp协议 定义 LLDP(Link Layer Discovery Protocol)是IEEE 802.1ab中定义的链路层发现协议。LLDP是一种标准的二层发现方式,可以将本端设备的管理地址、设备标识、接口标识等信息组织起来,并发布给自己的邻居设备,邻居设备收到这些信息后将…

git提交只单个或者某几个文件的指令

git status --查看目前本地和远程仓库的差异&#xff1b; git add --提交某的文件&#xff0c;多次执行可以提交多个文件 文件名称替换 git stash -u -k --其他文件保留到本地暂存区&#xff0c;不进行提交 git commit -m ---针对本次修改添加注释并提交到远程仓库 gi…

在安卓手机搭建kali环境,手机变成便携式渗透神器

简介 kali是著名的黑客专用系统&#xff0c;一般都是直接装在物理机或者虚拟机上&#xff0c;我们可以尝试把kali安装在手机上&#xff0c;把手机打造成一个便携式渗透神器。 我们需要下载以下3款软件&#xff1a; (1).Termux(终端模拟器) (2).AnLinux(里边有各种安装liunx…

从零实现深度学习框架——带Attentiond的Seq2seq机器翻译

引言 本着“凡我不能创造的,我就不能理解”的思想,本系列文章会基于纯Python以及NumPy从零创建自己的深度学习框架,该框架类似PyTorch能实现自动求导。 要深入理解深度学习,从零开始创建的经验非常重要,从自己可以理解的角度出发,尽量不使用外部完备的框架前提下,实现我…

学校公寓管理系统/基于微信小程序的学校公寓管理系统

摘 要 社会的发展和科学技术的进步&#xff0c;互联网技术越来越受欢迎。手机也逐渐受到广大人民群众的喜爱&#xff0c;也逐渐进入了每个学生的使用。手机具有便利性&#xff0c;速度快&#xff0c;效率高&#xff0c;成本低等优点。 因此&#xff0c;构建符合自己要求的操作…

ChatGPT训练流程

图源&#xff1a;State of GPT - Microsoft Build 笔者翻译上图如下&#xff1a; 阶段子阶段目标备注Pre-Training--------语言建模Instruction Finetuning---------让模型能够理解自然语言指令RLHFReward Modeling奖励建模&#xff0c;用来代替人工打分&#xff0c;降低标注…

5G理论概述

文章目录 SA组网架构及协议栈4-5G核心网侧融合交互5G网络网元和设备类型&#xff0c;接口1、AMF(Access and Mobility Management Function)&#xff0c;接入和移动管理功能2、SMF&#xff08;Session Management function&#xff09;&#xff0c;会话管理功能3、AUSF&#xf…

2023数学建模国赛常用算法-Topsis优劣解距离法

更多国赛数学建模资料思路&#xff0c;关注文末&#xff01; 1 优劣解距离法&#xff08;TOPSIS&#xff09;简介 1.1 概念 TOPSIS 法是一种常用的组内综合评价方法&#xff0c;能充分利用原始数据的信息&#xff0c;其结果能精确地反映各评价方案之间的差距。基本过程为基于…

多路转接高性能IO服务器|select|poll|epoll|模型详细实现

前言 那么这里博主先安利一下一些干货满满的专栏啦&#xff01; Linux专栏https://blog.csdn.net/yu_cblog/category_11786077.html?spm1001.2014.3001.5482操作系统专栏https://blog.csdn.net/yu_cblog/category_12165502.html?spm1001.2014.3001.5482手撕数据结构https:/…

AppSpider Pro 7.4.053 for Windows - Web 应用程序安全测试

AppSpider Pro 7.4.053 for Windows - Web 应用程序安全测试 Rapid7 Dynamic Application Security Testing (DAST) 请访问原文链接&#xff1a;https://sysin.org/blog/appspider/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin…

微信小程序学习之数据绑定,事件绑定,事件传参与数据同步的学习记录

数据绑定&#xff0c;事件绑定&#xff0c;事件传参与数据同步 1. 数据绑定1.1. 在data中定义数据1.2. 在wxml中渲染数据 &#xff08;mustache语法&#xff09; 2. 事件绑定2.1. 事件2.2. 常用的事件2.3. 事件对象的属性列表2.4. target 和 currentTarget的区别 3. 事件传参与…

基于LLM大模型开发Web App生成器

随着越来越多的代码生成模型公开可用&#xff0c;现在可以以我们以前无法想象的方式进行文本到网络甚至文本到应用程序。 本教程介绍了一种通过流式传输和渲染内容来生成 AI Web 内容的直接方法。 推荐&#xff1a;用 NSDT设计器 快速搭建可编程3D场景。 1、在 Node 应用程序中…

按日,周,年统计,无的数据补充0

需求&#xff1a;按日-周-年统计。统计涉及到3张表数据。 写sql。先把3张表数据摘取出来&#xff0c;只需对3张表的时间做分组统计即可。 按日统计 select DAY(dateff) as time,IFNULL(count(id),0)as num from(select create_time as dateff,id as id from cz_taxi_orders…

通过列排斥能比较两组迭代次数

( A, B )---3*30*2---( 1, 0 )( 0, 1 ) 让网络的输入只有3个节点&#xff0c;AB训练集各由6张二值化的图片组成&#xff0c;让差值结构中有6个1, 行分布是0&#xff0c;1&#xff0c;1&#xff0c;1&#xff0c;1&#xff0c;2列分布是2&#xff0c;2&#xff0c;2.统计迭代次…

【单片机】msp430f5529 万年历,数字时钟,矩阵键盘修改时间,7针OLED显示,内部温度读取

文章目录 功能实物图原理图一些程序片段矩阵键盘内部温度读取将年月日转为星期 功能 1 显示万年历&#xff0c;利用内部RTC模块 2 按键修改时间 3 显示芯片内部的温度数值 实物图 原理图 一些程序片段 矩阵键盘 https://qq742971636.blog.csdn.net/article/details/1316505…