当前位置: 首页 > news >正文

C++的类

类的性质

上文的例子中用到了类,也知道了类的定义方法,其实类还有更多的性质,这些更多的性质完整支持了面向对象编程。

封装

以前说过,程序就是数据和代码的组合。而C++又正好提供了对数据的封装功能,这就可以很好的完成数据和代码的组合。还是先上代码:

#include <stdlib.h>
#include <stdio.h>
#include <iostream>class A
{
public:A(){_data = 10;std::cout << "create A, _data is " << _data << std::endl;}~A(){std::cout << "destory A" << std::endl;}void setData( int v ){_data = v;}int getData()const{return _data;}protected:int _data;
};int main ( int argc, char *argv[] )
{A a;// 输出a中变量的值std::cout << "a._data is " << a.getData() << std::endl;a.setData( 20 );  // 改变值为20std::cout << "a._data is " << a.getData() << std::endl;A *a1 = new A;  // 动态创建A的另一个变量delete a1;     // 删除这个变量,这时候应该有输出"destory A"a1 = NULL;return 0;
}

编译后输出:

create A, _data is 10
a._data is 10
a._data is 20
create A, _data is 10
destory A
destory A

这段简单的代码,定义了一个名叫A的类,此类中有一个int类型的变量,叫做_data,这个变量的访问属性为protected,说明在类的外部是无法访问此变量的。

构造函数

这个叫做A的类中有一个和类名相同的函数叫做A();这个函数叫做构造函数,在定义A的变量时候被调用,本例中在A a;和A *a1 = new A这两行调用。

要注意,构造函数没有返回类型。

如果没有手动定义构造函数,C++总是自动添加一个没有任何参数的默认构造函数,如下代码:

class B
{public:void setData(int v ){_data = v;};int getData()const{return _data;};protected:int _data;
};

这个例子中没有B的构造函数,这时候_data没有被初始化,则里面的值不确定。可以使用

B b;
std::cout << b.getData() << std::endl; 

做个测试,每次程序运行时,都会输出不同的值。

除了不带参数的构造函数,还可以再定义一个或多个带参数的构造,当然函数名还是和类名相同,如下代码:

class C
{public:C(){ _data = 0; };C( int v ){ _data = v;};int getData()const{ return _data;};protected:int _data;
};int main( int argc, char *argv[] )
{C c;std::cout << c.getData() << std::endl;C c1(10);std::cout << c1.getData() << std::endl;return 0;
}

编译后输出:

0
10

可以看到,C类有两个构造函数,一个是默认构造函数,一个是带参数的构造函数。带参数的构造函数把定义C时给的10放到了_data变量里。这时候就相当于给了另一个默认值10,很多时候都会用到带参数的构造函数。

析构函数

回到最开始的例子可以看到还有另一个“~A()”这个函数,叫做析构函数,此函数会在清理掉A的变量时候被调用,即被销毁的时候被调用。这个函数在类的定义过程中最多只有一个,或是不定义析构函数,这时候C++编译器也会添加一个默认的什么功能也没有的析构函数。一般情况下需要定义这个析构函数用于清理一些自己创建的变量,向第一个例子中main函数上一行中增加如下代码:

class C
{
public:C(){_a = new A;};~C(){delete _a;};protected:A *_a;};
// 修改main函数代码如下
int main ( int argc, char *argv[] )
{{C c;}return 0;
}

编译运行:

create A, _data is 10
destory A

可以看到,变量_a的析构函数被调用,也就是_a被销毁了。

修改代码,注释掉

  //  ~C(){//      delete _a;//  };

编译后再运行:

create A, _data is 10

这时可以看到_a的析构函数没有被调用。

其实在这里主要是_a的生成方式不同。在这里是用的new操作符动态创建的。一般使用new操作符分配的变量,一定要在不用的时候配合delete操作符把分配的变量删除。C++并不知道什么时候删除无用的变量,一定要程序员手动添加删除代码。

成员函数

在定义类的变量的时候,_data的访问属性是protected类型,在上一节可以知道,这种类型的变量无法在类的外部访问,也就是没有办法直接使用这个变量,为了能访问它,在代码中添加了两个函数

  • setData:此函数带一个参数,函数内部用这个参数给_data新值。
  • getData:此函数功能更简单,只是把_data值返回给调用者。

而getData和setData的访问属性为public,也就是在类的外部可以调用。而在main函数中,也确实是调用成功了。这样程序就有了修改类A中_data变量的方法,但是也只能通过这两个函数来访问A中的变量。

到这里,所谓的封装可能也就很清楚了。即隐藏数据,提供访问方法。这样的思想在本例中可能很简单,但是放到更复杂的代码环境中就会发现使用这种封装的方法管理数据在逻辑上非常清楚。

继承

所谓的继承,也是面向对象编程中一个很重要的概念。先说一个为什么要继承。首先是在代码上可以重用,再就是功能上可以扩展,而再增加虚函数概念后会发现,通过继承还能改变类的行为。虚函数这个东西以后再说,本节先把继承搞清楚。
上一个简单的代码:

#include <stdlib.h>
#include <stdio.h>
#include <iostream>class A
{
public:A(){_data = 10;std::cout << "create A, _data is " << _data << std::endl;}~A(){std::cout << "destory A" << std::endl;}void setData( int v ){_data = v;};int getData()const{return _data;};protected:int _data;
};class C : public A
{
public:C(){_data2 = 0;}void setData2( int v ){_data2 = v;};int getData2()const{return _data2;};int sum()const{return _data + _data2;}
protected:int _data2;};int main ( int argc, char *argv[] )
{C c;c.getData();c.getData2();std::cout << c.sum() << std::endl;return 0;
}

观察代码,可以看到本例中定义了两个类, A和C。
其中C的定义后面有 “public A”。
这就是表示类C是从类A继承而来。
而“public”表示此继承是公有继承,同样,“public”可以换成"protecte"和"private"。在本例中不能换,换后的效果可以自己做测试。
然后在main函数中定义了一个C的变量c。
这时候使用此c变量可以成功调用A的getData函数和自己的getData2函数。说明类C拥有了类A的功能,这就是对类A功能的继承,同样里面的数据也一起继承了过来,即在C中可以直接操作A的成员变量_data。

因为继承这种从上到下的关系,一般会把A叫做C的父类, 类关系图如下:

A
- _data
+A()
+setData()
+getData()
C
- _data2
+C()
+setData2()
+getData2()

多态

在初识C++程序前面的代码例子中已经看到过virtual这个关键字。当时是为了让某个函数有和父类函数不同的功能而添加。

虚函数

使用virtual修饰的类的函数,叫做虚函数,父类中的虚函数可以由子类重新实现,这样在外部调用时会找到子类的虚函数定义。
虚函数又分为两种,普通虚函数,纯虚函数,比如下面两个类的定义:

class A
{public:A(){};virtual void fun(){printf("from a fun\n");};
};
class B : public A
{public:B(){};virtual void fun(){printf("from b fun\n");};
}

在这里B类的fun函数会覆盖掉A的fun函数。如下的调用方式:

   A *lpA = new B;lpA->fun(); // 这里调用的是B的fun函数,虽然是用的A类型的指针,但是最终也是调用B的fun函数。A *lpA2 = new A;lpA2->fun(); // 这里调用的是A的fun函数。

需要注意的是,只要父类中的任何一个函数声明为虚函数,则子类中不管是否使用virtual修饰同样的函数,此函数都一直是虚函数。

虚函数的使用

更多的时候,虚函数的定义是为了提供一致的函数调用方法,也叫做定义接口。大多数时候程序员的工作是为了提供一个运行框架,而更多的功能是围绕这个框架添加,这时一致的接口调用就非常重要,在C的时候程序员可能会用函数指针来解决这个问题。而C++语言提供了虚函数这个特性,那么在不修改主框架代码的情况下,偷偷的替换里面的某一段代码功能就成为可能,比如下面这种情况:

#include <stdlib.h>
#include <stdio.h>
#include <iostream>class Computer
{public:Computer(){_a = 0;_b = 0;}void setA( int a ){_a = a;}void setB( int b ){_b = b;}virtual void work(){printf("use Computer work\n");_r = _a + _b;};void printResult(){printf("%d\n", _r);}protected:int _a;int _b;int _r;
};class Computer2 : public Computer
{public:Computer2(){}virtual void work(){printf("use Computer2 work\n");_r = _a * _b;}};int main ( int argc, char *argv[] )
{std::string workType = argv[1];Computer *lpComputer = NULL;int a = 10;int b = 3;if ( workType == "2" ){lpComputer = new Computer2( );}else{lpComputer = new Computer( );}lpComputer->setA( a );lpComputer->setB( b );lpComputer->work();lpComputer->printResult();delete lpComputer;return 0;
}

编译运行:

$ ./virtualclass.exe 1
use Computer work
13Administrator@WIN-R9MT13JQHOK /e/workspace/book
$ ./virtualclass.exe 2
use Computer2 work
30

第一次运行时给程序带了一个外部参数“1”,第二次运行时给程序带了一个部分参数“2”。最终得到了两个不同的输出。
此代码在main函数中还能看到是通过判断不同的输入而生成了不同Computer实例。在这里表现的并不明显,可以接着做个改进,把创建Computer指针的代码封装成一个函数,如下:

Computer *createComputer( std::string const& t )
{Computer *lpComputer = NULL;if ( t == "2" ){lpComputer = new Computer2( a, b );}else{lpComputer = new Computer( a, b );}return lpComputer;
}int main ( int argc, char *argv[] )
{std::string workType = argv[1];Computer *lpComputer = createComputer( workType );int a = 10;int b = 3;lpComputer->setA( a );lpComputer->setB( b );lpComputer->work();lpComputer->printResult();return 0;
}

在这个例子中,main函数的工作过程不变,但是当要切换计算方法时,还是只要给出不同的工作类型,2,或是1,就能让main函数的工作方式完全改变。
这时候,如果再给一个新的工作方式“3”,只需要修改createComputer的实现而不用修改主函数的任何代码。这个结构就给了对程序进行动态扩展的能力。比如把createComputer这个函数和计算类放到另一个动态库中,由其他人开发这个动态库,他只要在有新的功能时把这个动态库更新就可以给整个程序做功能升级。

纯虚函数的使用

纯虚函数的定义方法如下:

class A
{public:A(){};virtual void fun() = 0;
}

这时候不能直接定义类A的变量,只能定义指针:

   A a;///这一行在编译时报错.A *lpA = NULL;// 这一行可以正确编译

在多数情况下纯虚函数只是为了规范一系列类的接口,比如有一个文件操作类:

	class File{public:File(){};virtual ~File(){};virtual bool open() = 0; //打开文件virtual bool close() = 0;//关闭文件virtual bool write() = 0;//写文件 virtual bool read() = 0;//读文件};

这样一个文件操作类,使用者只知道这个类有四个可用的功能函数,而每个函数的具体实现,就要根据文件类型分别实现。使用者能做的就是使用文件指针调用这几个函数完成文件操作功能,而不必关心里面的实现,这在一定程度上也把程序的复杂度降了下来,又对细节做了封装隔离。
一般来说对它会有如下简单的扩展

File
+open()
+close()
+write()
+read()
TxtFile
+open()
+close()
+write()
+read()
ImgFile
+open()
+close()
+write()
+read()
XMLFile
+open()
+close()
+write()
+read()

使用这种方法隔离实现细节。

到这里,面向对象编程的三个常用的特性就都介绍完了,再重复一次:

面向对象编程
封装
继承
多态

如果学过C语言,就会知道在C语言中也可以自定义数据结构,就是使用叫做struct的关键字。在C++里面,可以把struct看成是所有内容都是public属性的类。

相关文章:

C++的类

类的性质 上文的例子中用到了类&#xff0c;也知道了类的定义方法&#xff0c;其实类还有更多的性质&#xff0c;这些更多的性质完整支持了面向对象编程。 封装 以前说过&#xff0c;程序就是数据和代码的组合。而C又正好提供了对数据的封装功能&#xff0c;这就可以很好的完…...

【网络】- TCP/IP四层(五层)协议 - 网际层(网络层) - 划分子网、构造超网

目录 一、概述二、分类IP地址不合理的地方三、划分子网四、无分类编址方法 一、概述 前面的文章介绍了网络层的网际协议IP&#xff0c;介绍了IP地址的定义&#xff0c;知道了IP地址分为网络标识(网络地址)、主机标识(主机地址)两部分&#xff0c;也清楚了最初IP地址是按照分类被…...

1-网络初识——网络发展史

目录 1.独立模式 2.网络互联 2.1.局域网&#xff08;Local Area Network&#xff0c;简称LAN&#xff09; ①基于网线直连 ②基于集线器组建 ③基于交换机组建 ④基于交换机&#xff08;网口很多&#xff09;和路由器组建 2.2.广域网&#xff08;Wide Area Network&…...

《Spring Guides系列学习》guide35 - guide40

要想全面快速学习Spring的内容&#xff0c;最好的方法肯定是先去Spring官网去查阅文档&#xff0c;在Spring官网中找到了适合新手了解的官网Guides&#xff0c;一共68篇&#xff0c;打算全部过一遍&#xff0c;能尽量全面的了解Spring框架的每个特性和功能。 接着上篇看过的gu…...

《算法导论》拓展之 一维二维最近点对问题

一维点对问题 描述&#xff1a;一维最近点对问题是指在给定的一维点集中找到距离最近的两个点。具体来说&#xff0c;给定一维坐标轴上的 n 个点&#xff0c;要找出其中的两个点&#xff0c;使它们的距离最小。 解决办法&#xff1a;解决这个问题的一种常见方法是使用排序和线…...

【C++】动态存储分配

动态存储分配是指在程序运行时根据需要动态地分配和释放内存空间。 C中提供了两个关键的运算符用于动态存储分配&#xff1a;new和delete。 使用new运算符可以在堆&#xff08;heap&#xff09;上动态地分配内存空间&#xff0c;并返回所分配内存的首地址。语法如下&#xff1…...

小狗避障-第14届蓝桥杯省赛Scratch中级组真题第4题

[导读]&#xff1a;超平老师的《Scratch蓝桥杯真题解析100讲》已经全部完成&#xff0c;后续会不定期解读蓝桥杯真题&#xff0c;这是Scratch蓝桥杯真题解析第139讲。 小狗避障&#xff0c;本题是2023年5月7日举行的第14届蓝桥杯省赛Scratch图形化编程中级组编程第4题&#xf…...

GPT学习笔记-Embedding的降维与2D,3D可视化

嵌入&#xff08;Embedding&#xff09;在机器学习和自然语言处理中是一种表示离散变量&#xff08;如单词、句子或整个文档&#xff09;的方式&#xff0c;通常是作为高维向量或者矩阵。嵌入的目标是捕捉到输入数据中的语义信息&#xff0c;使得语义相近的元素在嵌入空间中的距…...

Nautilus Chain上线主网,为DeFi和流支付的未来构建基础

近日&#xff0c;加密行业权威平台 Coinmarketcap 发表了一篇名为“Zebec 模块化 Layer3 链 Nautilus Chain上线主网&#xff0c;为 DeFi 和流支付的未来构建基础”的文章&#xff0c;文中对 Zebec 生态公链 Nautilus Chain 的生态进展进行了简要的报道&#xff0c;并对其进行了…...

java设计模式之命令设计模式的前世今生

命令设计模式是什么&#xff1f; 命令设计模式是一种行为型设计模式&#xff0c;它允许将请求封装为对象&#xff0c;并将其传递给调用者&#xff0c;从而使调用者可以在不知道请求具体细节的情况下进行操作。命令模式的主要目的是解耦请求的发送者和接收者&#xff0c;以及通…...

离散系统函数零积点分析

离散系统函数零积点分析 在 Matlab中&#xff0c;系统函数的零极点就可以通过函数 roots 得到。 函数的零极点也可以通过函数 tf2zp 获得&#xff0c;其调用格式为&#xff1a;[Z, P, K] tf2zp(B, A)&#xff0c;函数 tf2zp 可以将H(z)的有理分式转换为零极点增益形式&#…...

Karl Guttag:苹果VST MR头显也无法突破AR的物理局限

据近期的爆料、传闻显示&#xff0c;苹果将6月份的WWDC2023上首次公布AR/VR头显。对此&#xff0c;AR/VR光学专家Karl Guttag持怀疑态度&#xff0c;他此前在DisplayDaily的文章中写道&#xff0c;苹果研发AR/VR头显更像是担心错过新技术趋势。回顾过去的一些关键的AR产品&…...

mysql倒库操作遇到的问题

背景&#xff1a;本地windows 10安装了mysql数据库后&#xff0c;需要把远程库的表结构和数据全部导入进来。 操作&#xff1a;导出数据库&#xff0c;导入数据库。 第一步&#xff1a;导出数据库 使用dump命令即可。 登陆mysql数据库 mysql -hhost --default-character-s…...

ELK企业级日志分析系统

ELK概述 为什么要使用 ELK 日志主要包括系统日志、应用程序日志和安全日志。系统运维和开发人员可以通过日志了解服务器软硬件信息、检查配置过程中的错误及错误发生的原因。经常分析日志可以了解服务器的负荷&#xff0c;性能安全性&#xff0c;从而及时采取措施纠正错误。 …...

华为OD机试真题 Java 实现【基站维修工程师】【2023Q1 200分】,附详细解题思路

一、题目描述 小王是一名基站维护工程师,负责某区域的基站维护。 某地方有n个基站(1<n<10),已知各基站之间的距离s(0<s<500),并且基站x到基站y的距离,与基站y到基站x的距离并不一定会相同。 小王从基站1出发,途径每个基站1次,然后返回基站1,需要请你…...

SSM 如何使用 TCC 机制实现分布式事务?

SSM 如何使用 TCC 机制实现分布式事务&#xff1f; 分布式事务是现代分布式系统中必不可少的一部分&#xff0c;而 TCC 机制&#xff08;Try-Confirm-Cancel&#xff09;是一种常用的分布式事务处理方式。在 SSM 框架中&#xff0c;我们可以使用 TCC 机制来管理分布式事务。本…...

如何在上架App之前设置证书并上传应用

App上架教程 在上架App之前想要进行真机测试的同学&#xff0c;请查看《iOS- 最全的真机测试教程》&#xff0c;里面包含如何让多台电脑同时上架App和真机调试。 P12文件的使用详解 注意&#xff1a; 同样可以在Build Setting 的sign中设置证书&#xff0c;但是有点麻烦&…...

华清远见 day04

break 打破循环,再也不执行 continue 跳出本次循环,继续执行下一次循环; ​ 常量 字面常量 宏常量 #define A 100 //定义一个宏常量, 名为:A 值为:100 位置 在 头文件 下面 ,文件开头 ​ ​ 输入时间秒 得到 小时 分钟 秒的时间输出 用到 三运算符; 宏常量 Mi 是60 t1 /Mi>6…...

如何处理Vue应用程序中的错误和异常情况?

处理Vue应用程序中的错误和异常情况是开发中非常重要的一环&#xff0c;但是对于新手来说&#xff0c;这往往是一个比较棘手的问题。不过别担心&#xff0c;下面我将为大家详细解答。 首先&#xff0c;我们需要知道的是&#xff0c;在Vue中&#xff0c;错误和异常情况是两个不…...

javascript基础十六:Ajax 原理是什么?如何实现?

一、是什么 AJAX全称(Async Javascript and XML) 即异步的JavaScript 和XML&#xff0c;是一种创建交互式网页应用的网页开发技术&#xff0c;可以在不重新加载整个网页的情况下&#xff0c;与服务器交换数据&#xff0c;并且更新部分网页 Ajax的原理简单来说通过XmlHttpRequ…...

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…...

FFmpeg 低延迟同屏方案

引言 在实时互动需求激增的当下&#xff0c;无论是在线教育中的师生同屏演示、远程办公的屏幕共享协作&#xff0c;还是游戏直播的画面实时传输&#xff0c;低延迟同屏已成为保障用户体验的核心指标。FFmpeg 作为一款功能强大的多媒体框架&#xff0c;凭借其灵活的编解码、数据…...

让AI看见世界:MCP协议与服务器的工作原理

让AI看见世界&#xff1a;MCP协议与服务器的工作原理 MCP&#xff08;Model Context Protocol&#xff09;是一种创新的通信协议&#xff0c;旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天&#xff0c;MCP正成为连接AI与现实世界的重要桥梁。…...

今日学习:Spring线程池|并发修改异常|链路丢失|登录续期|VIP过期策略|数值类缓存

文章目录 优雅版线程池ThreadPoolTaskExecutor和ThreadPoolTaskExecutor的装饰器并发修改异常并发修改异常简介实现机制设计原因及意义 使用线程池造成的链路丢失问题线程池导致的链路丢失问题发生原因 常见解决方法更好的解决方法设计精妙之处 登录续期登录续期常见实现方式特…...

Device Mapper 机制

Device Mapper 机制详解 Device Mapper&#xff08;简称 DM&#xff09;是 Linux 内核中的一套通用块设备映射框架&#xff0c;为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程&#xff0c;并配以详细的…...

MySQL账号权限管理指南:安全创建账户与精细授权技巧

在MySQL数据库管理中&#xff0c;合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号&#xff1f; 最小权限原则&#xf…...

七、数据库的完整性

七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...

自然语言处理——文本分类

文本分类 传统机器学习方法文本表示向量空间模型 特征选择文档频率互信息信息增益&#xff08;IG&#xff09; 分类器设计贝叶斯理论&#xff1a;线性判别函数 文本分类性能评估P-R曲线ROC曲线 将文本文档或句子分类为预定义的类或类别&#xff0c; 有单标签多类别文本分类和多…...

面试高频问题

文章目录 &#x1f680; 消息队列核心技术揭秘&#xff1a;从入门到秒杀面试官1️⃣ Kafka为何能"吞云吐雾"&#xff1f;性能背后的秘密1.1 顺序写入与零拷贝&#xff1a;性能的双引擎1.2 分区并行&#xff1a;数据的"八车道高速公路"1.3 页缓存与批量处理…...

怎么开发一个网络协议模块(C语言框架)之(六) ——通用对象池总结(核心)

+---------------------------+ | operEntryTbl[] | ← 操作对象池 (对象数组) +---------------------------+ | 0 | 1 | 2 | ... | N-1 | +---------------------------+↓ 初始化时全部加入 +------------------------+ +-------------------------+ | …...