C++程序设计基础学习笔记:以人为本:用类与对象诠释现实世界

Posted 呆呆象呆呆

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++程序设计基础学习笔记:以人为本:用类与对象诠释现实世界相关的知识,希望对你有一定的参考价值。

相关博客目录

C++程序设计基础学习笔记:(1)初识C++语言:从认识变量和常量开始,数据的表示
C++程序设计基础学习笔记:(2)计算:从数据运算开始,数据简单运算
C++程序设计基础学习笔记:(3)分支结构:无处不在的抉择
C++程序设计基础学习笔记:(4)循环结构:周而复始,求同存异
C++程序设计基础学习笔记:(5)数组:实现算法的利器
C++程序设计基础学习笔记:(6)指针:所向披靡的“金箍棒” 魂
C++程序设计基础学习笔记:(7)函数:面向过程的基础
C++程序设计基础学习笔记:(8)文件:让数据流动起来
C++程序设计基础学习笔记:(9)以人为本:用类与对象诠释现实世界
C++程序设计基础学习笔记:(10)从生物遗传说起,取其精华——继承与多态

第9章 以人为本:用类与对象诠释现实世界

C++是一种承前启后的语言

承前:面向过程编程
启下:面向对象编程

面向过程编程

数据和函数抽象完后就结束

面向对象程序设计

本质:实现类和基于类实例化对象

在面向对象的世界中:

世界通过实体构成
实体的共有属性抽象为数据
实体的行为抽象为函数
数据和函数结合=>类

从c++定义的数据类型到自定义数据类型

9.1.1 类和对象的定义(上)

类的定义

数据与数据的处理函数封装在一起->类
类的定义包含四要素

类的定义_1:访问限定符

访问限定符(Access Specifier):说明类成员的可访问范围

三种关键字出现的次数和先后序都都没有限制。

若某个成员前面没有上述关键字,缺省为私有成员

private:

public:

protected:

类的定义_2:成员

类的数据成员定义在类内,形式为:

数据类型 成员名;

类的定义_3:成员函数的定义

类的成员函数有两种定义方法:

直接在类内完成函数定义

在类内声明,类外完成函数定义。类外定义时,函数名前必须添加前缀“类名:: ”

类的成员函数能够访问:

当前对象的全部数据、函数
同类其它对象的全部数据、函数

举例:复数类

类的封装

层次1:在语法规则中明确“数据-数据、数据-函数、函数-函数”联系

数据与数据的处理函数放置在类中
类的函数成员可以直接使用类中数据成员,也可调用其他函数成员

层次2:约束外部访问,保护类中的数据

提供类的操作接口:public成员函数
类的private成员的外部访问只能通过类的合法接口(public函数)来完成

9.1.2 类和对象的定义(下)

类:用户自定义数据结构

对象:类数据类型的变量

对象的定义

类名 对象名;

每个对象有独立的存储空间,独立的数据成员
对象所占用的内存空间大小等于所有数据成员变量的大小之和

对象的操作

运算

支持的运算符:“=”:对象之间可以相互赋值,赋值运算需要谨慎
不支持的运算符:除赋值之外的运算符,除非这些运算符经过了“重载”

访问对象的成员

对象名.成员名

9.2.1 构造函数和析构函数(上)

对象的创建

主要任务:

为对象的数据成员分配存储空间
为对象的数据成员赋初值

构造函数是用于创建对象的特殊成员函数,当执行对象定义语句时,系统自动调用构造函数

构造函数的作用

初始化对象:为对象的数据成员分配空间& 赋初值;
请求其他资源

构造函数

语法

类名()  ;
类名() 形参表 :

构造函数是类的public 成员(因为需要外部调用)
构造函数名与类名相同:类名(为了区分和方便系统调用)
构造函数可以重载(可以设置多的构造函数来进行不同输入情况下的构造)
构造函数可以有任意类型的参数,但没有返回值
函数头部无返回值类型说明

类没有定义构造函数时,系统提供缺省版本的构造函数
类名( ) :无参数,函数体为空,不做任何操作

构造函数示例

默认构造函数

无参构造函数称为默认构造函数

如果希望在对象创建的同时初始化为一个特定状态,就必须定义类自己的构造函数

如果类有自己的构造函数,所有该类对象的初始化都将通过这些构造函数完成,不再使用系统提供的缺省构造函数

带默认形参的构造函数

构造函数的调用

属于隐性调用:对象的定义语句即为构造函数的调用语句

9.2.2 构造函数和析构函数(下)

析构函数

当一个对象的生命周期结束时,C++会自动调用析构函数(destructor)

析构函数在对象消亡前做善后工作,比如释放分配的空间等

如果类没有定义析构函数,系统提供缺省版本的析构函数(无参数,函数体为空,不做任何操作)

~类名( ) ;

析构函数是public成员函数

析构函数名为:~ 类名

析构函数没有参数,也没有返回类型

1个类只有1个析构函数

注意:对象析构顺序与构造顺序相反

构造函数与析构函数

构造函数与析构函数都由系统自动调用
对象的析构顺序与构造顺序相反

默认的构造函数与析构函数

但只要类中定义了一个构造/析构函数,系统就不会自动生成默认的构造/析构函数。

构造函数与析构函数示例:动态内存申请& 回收

9.3 复制构造函数

复制构造函数

对象创建时,可用同类对象来初始化新对象
这种对象创建方式所调用的构造函数称为复制构造函数(Copy Constructor)

举例

功能

将形参对象的数据成员值复制给要创建的新对象的数据成员

语法形式

类名(const 类名 & 引用名, …)

复制构造函数只有1个形参:本类对象的引用
const :只读,保护实参对象

举例

注意:赋值运算非对象创建,不调用任何构造函数,对象之间的赋值运算

复制构造函数调用的三种情况

用户没有定义复制构造函数,系统提供缺省版本的复制构造函数,形参对象的数据成员值原封不动地复制给新对象(与普通缺省版本构造函数不同,不同之处在于因为需要调用赋值构造函数的情况非常多,如果每一个复制构造函数缺省版本都没有实际功能,需要用户自己定义,那么就会变得很麻烦)

情况一:当用类的一个对象去初始化该类的另一个对象时系统自动调用复制构造函数实现拷贝赋值

情况二:若函数的形参为类对象,调用函数时须将实参赋值给形参,系统自动调用复制构造函数

情况三:当函数的返回值是类对象时,系统创建临时对象保存返回值,自动调用复制构造函数。

浅复制与深复制

浅复制

实现对象间数据元素的一一对应复制。

深复制

当被复制的对象数据成员是指针类型时,不是复制该指针成员本身,而是将指针所指的数据进行复制

浅复制问题举例

深复制举例

9.4 静态成员

静态数据成员(解决一个类的不同对象之间的访问)

静态数据:静态数据不属于具体的对象,而是属于类,无论类衍生多少对象,类的静态数据成员只在全局数据区分配一次内存

静态数据成员的定义

对象只能访问本类中的静态成员(因为只有这个静态成员是在对象初始化过程中声明过的)

这种定义方式的原理:

由于静态数据成员不属于任何对象,那么对象创建时就不会为它分配存储空间。
为此,静态数据成员在类体内声明后,必须在类体外定义或者初始化,才能完成在全局数据区的存储空间分配。

静态成员实例:借书卡类

静态成员函数

静态数据成员不属于对象

如果需要在对象创建之前操作静态数据成员,只能使用静态成员函数

静态成员函数和静态数据成员一样,属于类而不是某一个对象

静态成员函数只能访问类中的静态成员

静态成员函数示例

静态成员函数的使用

静态函数成员(访问属性为public)的外部访问形式有2种

静态成员函数总结

出现在类体外的函数定义不能指定关键字static

静态成员之间可以相互访问,包括静态成员函数访问静态数据成员和访问静态成员函数

非静态成员函数(普通成员函数)可以任意地访问静态成员函数和静态数据成员

静态成员函数不能访问非静态成员函数和非静态数据成员

静态函数成员(访问属性为public)的外部访问形式

类名:: 静态函数成员名(实际参数表)
对象名. 静态函数成员名(实际参数表)
指针名-> 静态函数成员名(实际参数表)

9.5 对象指针与数组

对象指针

声明形式

类名 *对象指针名;

通过指针访问对象成员

对象指针名->成员名

对象数组

声明

类名 数组名[元素个数];

访问方法

数组名[下标].成员名

初始化

数组中每一个元素对象被创建时,系统都会调用类构造函数初始化该对象。

举例:(固定长度数组)

举例:(动态数组)

9.6 类组合

举例

组合类的构造函数:对象的创建顺序

组合类的数据成员包括

对象
非对象成员

组合类构造函数

组合类构造函数中,非对象成员的初始化方法和非组合类构造函数是一样的

组合类构造函数中,对象成员的初始化对使用初始化列表,初始化列表调用对象成员类的构造 (复制构造) 函数进行初始化,初始化列表为空,调用对象成员类的默认构造函数

类名::类名(对象成员所需的形参,其他数据成员形参):对象1(参数表),对象2(参数表),......//冒号后面为初始化列表

    本类其他数据成员初始化

组合类对象的创建次序(由小及大)

先创建对象成员(调用对象成员类的构造函数)
再创建组合类对象(执行组合类的构造函数)

组合类构造函数的示例

组合类构造函数的执行顺序

执行初始化列表

依据初始化列表调用对象成员类的构造(复制构造)函数,创建并初始化对象成员
如果初始化列表为空,则调用对象成员类的默认构造函数(个人想法,这个情况就相当于说在后面的其他数据成员初始化中进行类的初始化,在使用上文的style处进行类的创建并作为一个所需要构造的对象成员,为什么要这么做呢,我找到了一个材料C++ 中使用构造函数初始化列表的原因_lizhi200404520的专栏-CSDN博客

执行组合类构造(复制构造)函数的函数体代码

初始化其他数据成员

举例

1、初始化a,b,调用两次构造函数;a,b传参数给ps,pe作为临时变量,调用两次复制构造函数

2、初始化列表,初始化p1,p2,调用两次复制构造函数

组合类的数据

对象成员的私有成员只能被对象类的成员函数访问

组合类的析构函数

组合类析构函数的定义

和非组合类相同

组合类对象的析构& 析构函数的调用

从大到小
先析构组合类对象:调用组合类的析构函数
再析构组合类对象的对象成员:调用对象成员类的析构函数

9.7 友元

友元vs封装

通过在类A中将外部函数f 或者其他类B 声明为本类的友元,函数f 和类B就能够直接使用类A的私有成员

友元是C++提供的一种突破数据封装和数据隐藏的机制

使用友元目的是基于程序的运行效率
运算符重载的某些场合需要使用友员
友元可以是函数,也可以是类

定义声明

friend 友元类名或函数名;

友元类举例

友元的效率更高(个人认为是不是因为不需要重复过多的不必要的复制构造函数)

友元函数举例

友元的使用要点

为了确保数据的完整性,及数据封装与隐藏的原则,建议尽量不使用或少使用友元。

友元关系是单向的

友元关系不能传递

9.8.1 运算符重载(上)

对象的运算

在C++中,基本数据类型变量可以任意使用各类运算符

对象之间像基本类型变量那样进行运算,有2种方法

在类中定义能完成运算的成员函数,然后调用该函数实现运算,举例如下:

对运算符进行重载

运算符重载

重载运算符函数可以对运算符作出新的解释,但原有基本语义不变

运算符可以重载为类的成员函数,也可以重载为非成员函数,如果需要在类中使用可以重载为友元函数,重载为成员函数,参数个数为运算符目数减一

不改变运算符的优先级

不改变运算符的结合性

不改变运算符所需要的操作数

不能创建新的运算符

运算符重载的实质仍然是函数调用,当C++编译器遇到重载的运算符,就调用其对应的运算符重载函数,并将运算符的操作数转化为运算符重载函数的实参以满足调用需求

运算符重载语法

operator 重载的运算符

重载为成员函数

参数个数为运算符目数减一

通常将运算符重载为类的成员函数,这种重载意味着第一个操作数必须是该类的对象

运算符重载为友元函数

如果上述条件(第一个操作数必须是该类的对象)不能满足,运算符就需被重载为友元函数(重载为普通函数不能访问类的私有成员,所以不考虑)

9.8.2 运算符重载(中)

流运算符的重载

语法框架:返回的是流运算的引用,操纵的也是对象的引用

自增、自减运算符的重载

自增运算符++、自减运算符–有前置/后置之分

前置运算符作为单目运算符重载
重载为成员函数:函数声明为“ 类名operator 运算符( ) “
重载为友元函数:函数声明为“ 类名operator 运算符( 类名&) “

后置运算符作为二目运算符重载

重载为成员函数:函数声明为“ 类名operator 运算符( int) “
重载为友元函数:函数声明为“ 类名operator 运算符( int, 类名&) “
注:int为伪参数

9.8.3 运算符重载(下)

未定义赋值运算符的重载可能会出现的问题

涉及到内存空间操作的时候,会出现因为普通赋值导致的内存泄漏和重复释放内存的问题

赋值运算符的重载

一文说尽C++赋值运算符重载函数(operator=) - 同勉共进 - 博客园

主要有几个疑惑为什么要用const 和为什么要用引用 在上面的这个blog里面有比较清楚的解释

9.9 类和对象的应用

目标性能

框架

构造函数

写接口

读接口

析构函数

测试demo

以上是关于C++程序设计基础学习笔记:以人为本:用类与对象诠释现实世界的主要内容,如果未能解决你的问题,请参考以下文章

java基础知识学习笔记

C++5笔记

学习日报 2018.7.27

菜鸟笔记 -- Chapter 6 面向对象

学习笔记实用类String的基本应用即常用方法

20165223 《JAVA程序设计》第六周学习总结