从零学习C++第六章:数据抽象——对象与类

6.1 概述

6.1.1 数据抽象与封装

  • 数据抽象(data abstraction):是对数据的一种描述方式,使得数据的使用者只需要知道对数据所能实施的操作,以及这些操作之间的关系,而不必知道数据的具体表示。
  • 数据封装(data encapsulation):是指把数据和对数据的操作作为一个整体定义,并通过封装机制把数据的具体表示对使用者隐藏起来,数据使用者只能通过封装体提供的接口对数据进行访问
    • 数据封装保证了数据抽象的实现

基于数据抽象与封装,可以实现数据的自动初始化

 

6.1.2 面向对象程序设计

  • 面向对象程序设计(object-oriented programming,包括:
    • 对象与类
      • 对象(object):由数据与所能进行的操作所构成的封装体,对象是类的实例
      • 类(class):对象的特征由类描述,类是对象的集合
    • 继承(inheritnce):是指在定义一个类时,可以利用已有类的一些特征描述。具有继承关系的两个类存在一般与特殊的关系
    • 多态性与动态绑定
      • 多态性(polymorphism):某一论域中的一个元素存在多种解释,包括:
        • 一名多用:同一作用域中用同一名字为不同程序实体命名,从而有多种含义。
          • 通过重载(overloading)实现,包括函数名重载,操作符重载
        • 类属性(genericity):一个程序实体能对多种类型的数据进行描述或描述的特性。
          • 具有类属性的程序实体:类属函数(通过指针和类模版实现)类属类型(通过联合类类型和类模版实现)
      • 面向对象中基于继承机制的多态:对象类型的多态,对象标识的多态,消息的多态
      • 绑定(binding):多态元素确定使用的过程
        • 静态绑定(static binding):在编译时刻确定对多态元素的使用
        • 动态绑定(dynamic binding):在运行时刻确定对多态元素的使用
      • 多态的好处:
        1. 代码复用
        2. 增强语言的可扩充性

 

6.1.3 面向对象程序设计与过程式程序设计的对比

  • 软件开发方法或技术的评价标准:开发效率与软件质量
  • 软件开发手段:抽象,封装,模块化,软件复用,软件维护,软件模型的自然度等

 

  1. 抽象(abstraction)(程序实体的外部行为)
    • 过程式程序设计:把程序功能抽象为子程序,功能分解、逐步精华的过程抽象(procedural abstraction)技术
      • 不足:数据的描述和操作分离
    • 面向对象程序设计:数据抽象(data abstraction)
      • 优势:有利于程序的设计、理解、维护

 

  1. 封装(encapsulation)(程序实体的内部实现,体现了信息隐藏(information hiding)的原则)
    • 过程式程序设计:过程封装(procedure encapsulation),对数据操作的封装,通过子程序实现
      • 不足:操作所需的数据公开,缺乏对数据的保护
    • 面向对象程序设计:数据封装(data encapsulation),把数据的表示和操作作为整体来描述,并把数据隐藏起来
      • 优势:加强了对数据的保护

 

  1. 模块化(modularity)
    • 模块:包含接口和实现两部分
    • 模块化:低耦合高内聚划分
    • 过程式程序设计:依据子程序划分模块
      • 不足:自由度大,边界模糊
    • 面向对象程序设计:依据对象类划分模块
      • 优势:模块边界清晰,划分模块结构比较稳定

 

  1. 软件复用(software reuse)
    • 过程式程序设计:采用子程序库进行代码复用
      • 不足:子程序粒子太小、不能完全符合要求、参数传递过程的不一致和效率低
    • 面向对象程序设计:通过类库和继承机制来实现代码复用
      • 优势:类的功能较大、类具有通用性、对象拥有局部变量,减少了大量数据在不同操作之间传递带来的不一致和效率问题

 

  1. 软件维护
    • 过程式程序设计:基于功能分解
    • 面向对象程序设计:基于对象和类,比较稳定

 


6.2 类

  • 对象构成了面向对象程序的基本计算单位,其特征由类描述
  • 定义类时,需要显式定义它的操作集
  • 类被称为抽象数据模型(abstract data type)

 

类的存储

  • 类的实例——对象所占空间大小由类的数据成员大小决定
  • 注意:
    • 在C++实现中,为了提高计算机指令对对象及其成员的访问效率,在为对象分配内存空间时,对对象数据成员所占空间按某种方式进行地址对齐,使得数据成员之间可能存在空隙。
    • 为了提高对象数据成员的存储效率,可把不需要对齐的成员放在一起,编译器一般能让用户自己设置对齐方式

 

6.2.1 数据成员

  • 数据成员(data member)是指类的对象所包含的数据,可以是常量或变量
  • 不能赋初始值,在构造函数中进行初始化

 

6.2.2 成员函数

  • 成员函数(member function)描述了对类中定义的数据成员所能实施的操作
  • 可以在类中定义,也可在类外定义
  • 类成员函数名可重载

 

6.2.3 成员的访问——信息隐藏

  • 成员访问控制修饰符:public、private、protected
    • public:不受限制,可在程序任一地方访问
    • private(默认):成员只能在本类和友元中访问
    • protected:成员只能在本类、友元和派生类中访问

private属性,public方法


6.3 对象

  • 类属于类型范畴的程序实体,存在于静态的面向对象程序中
  • 动态的面向对象程序由对象构成

 

6.3.1 对象的创建

  • 对象由类创建,两种方式:
    1. 直接方式:<类名> <对象名>
    2. 间接方式:在程序运行时刻,通过new创建,称为动态对象(dynamic object,内存空间在中分配,用指针变量来标识,用delete撤销
      • 使用new创建动态对象,先申请内存,再调用构造函数;使用delete撤销对象,先调用析构函数,再释放内存
      • 如果用malloc函数来动态创建对象,则系统不会调用该类的构造函数对其初始化,所以动态对象一般不用malloc创建;
      • 如果用库函数free撤销动态变量,系统不会调用对象的析构函数,所以动态对象不用free撤销
      • 撤销动态的对象数组时,如果delete的“[]”没写,则只有数组的第一个对象的析构函数被调用
      1. 单个动态变量的创建与撤销

//创建A类的指针变量

A *p;  //p为一个指针变量

p=new A;  //创建一个A类的动态变量,p指向它

delete p;  //撤销p所指向的动态变量

  1. 动态对象数组的创建与撤销

A *p;

p=new A[100];  //创建一个动态对象数组

delete []p;  // 撤销p所指的动态对象数组

 

6.3.2 对象的操作

  • 非动态对象:<对象>.<类成员>
  • 动态对象:<对象指针>-><类成员>

如果在类中的成员函数中创建该类的对象,则该对象可访问该类的所有成员

  • 对象可进行赋值、取地址、作为实参传递给函数、作为函数的返回值

 

6.3.3 this指针

  • 类定义中描述的数据成员对该类的每个对象都有一个拷贝(每当创建一个对象时,该对象拥有一块独立的内存空间,存储该对象的数据成员)
  • 类定义中的成员函数对该类的所有对象只有一个拷贝,所以成员函数有一个隐藏的行参this,指针类型:<类名> *const this; ,当通过对象调用成员函数时,编译器会把该对象的地址作为实参传给成员函数的隐藏行参this
    • 一般情况“this->”可省略
    • 如果在成员函数中要把this所指向的对象作为整体来操作,则必须显式使用this指针

 

Section3.h

class Section3{
private:
	inta ,x;
	float b;
	char c;

public:
	voidf();
	void g(int i);
};


Section3.cpp

void func(Section3 *p){

}

void Section3::f(){

	//在成员函数f中创建类的实例——对象
	Section3 section3;
	//该对象可访问该类的所有成员
	section3.b=34;

}

void Section3::g(int i){
	x=i;
	func(this);//将对象作为整体操作

}



int main(){

	//创建非动态对象
	Section3 section3;
	section3.f();  //编译为f(§ion3)
	
	//创建动态对象
	Section3 *p;
	p = new Section3;
	p->f();
	delete p;
	
	Section3 s1,s2;
	s1.g(1);//调用s1.g(i),在Section3中调用func(&s1)
	s2.g(2);//调用s2.g(i),在Section3中调用func(&s2)
	
	return 0;
}

6.4 对象的初始化与消亡

6.4.1 构造函数

  • 引入构造函数的原因:
    1. 数据成员一般为私有的(private),不能通过对象直接赋值
    2. 使用对对象初始化的普通函数时,不方便(显式调用)和不安全(忘记调用),且无法对常量成员和引用类型成员进行初始化
  • 构造函数(constructor)(解决私有数据成员初始化问题)
    • 定义:
      • 在对象类中定义或声明的与类同名、无返回值类型的成员函数
    • 创建:
      • 若不创建,调用编译器提供的默认构造函数(default constructor,不带参数的构造函数)进行初始化;若创建,构造函数可重载
    • 调用:
      • 对象创建时自动调用,对象创建后不能再调用了
      • 在创建动态的对象数组时,只能用默认构造函数来初始化
    • 成员访问控制修饰符:
      • 一般为公开的(public)
      • 若为私有的(private),其作用是限制创建该类对象的范围,这时只能在本类友元中创建该类对象
  • 成员初始化表(member initialize list)(解决常量数据成员和引用数据成员初始化)
    • 创建:
      • 在构造函数的函数头和函数体之间加入成员初始化表,用于对数据成员的初始化
        • 如果数据成员中有常量或引用类型,只能通过成员初始表进行初始化,若没有自定义构造函数或在自定义构造函数中没有对其初始化,编译器不会生成默认构造器,该类无法创建对象
  • 数据成员的初始化顺序:
    • 由数据成员在类中定义的说明次序来决定
//Section.h
class Section4{
private:
	int a,b,c;
	const float f;
	int &y;
public:
	Section4();
	Section4(int a1,int b1);
	Section4(int a1,int b1,int c1);
	voi dprint_member();
};


//Section.cpp
Section4::Section4():y(a),f(1.0){//对引用类型和常量进行初始化&y=a,f=1.0
}

Section4::Section4(inta1,intb1):y(b),f(2.0){
	a=a1;
	b=b1;
}

Section4::Section4(inta1,intb1,intc1):y(c),f(3.0){
	a=a1;
	b=b1;
	c=c1;
}

voidSection4::print_member(){
	cout<<"a:"<

6.4.2 析构函数

  • 析构函数(destructor)
    • 含义:名为“~<类名>”、没有参数和返回值类型的一个特殊成员函数
    • 功能:撤销对象所占的内存空间
    • 调用:
      • 对象消亡时,隐式自动调用(不需自定义和手动调用);
      • 如果对象申请了一些资源,并在消亡前没有归还这些资源(默认析构函数只能释放该对象占有的资源),应自定义析构函数,并在函数体中释放占有的资源,以在对象消亡前归还对象申请的资源
        • 例:如果该对象数据成员由指针类型数据,系统为该对象分配的内存空间只是存储了该指针的地址(指针的值),并没有存储该指针指向的数据,需要对象申请空间存放该指针指向的数据,所以应在其消亡前释放申请的内存空间
      • 如果不想撤销对象,只是归还该对象申请的资源,可通过显式调用对象类的析构函数实现

 

6.4.3 成员对象的初始化

  • 一个类(A)可以包含另一个类(B)的对象作为其数据成员,该对象(B类对象)就为该类(A)的成员对象
    • 成员对象默认由成员对象的默认构造函数初始化
    • 可通过成员初始化表显式调用非默认构造函数
  • 当一个对象包含多个成员函数时,对成员对象构造函数的调用次序由成员对象在类中的说明次序决定,与它们在成员初始化表中的说明次序无关
  • 当对含有成员对象的对象初始化时,先调用该对象的构造函数,再调用成员对象的构造函数
  • 撤销含有成员对象的对象时,先调用成员对象的析构函数,再调用该对象的析构函数
//A.h
class A{
private:
	int m;
public:
	A();
	A(inti);
	~A();
	void print_member();
};

//A.cpp
usingnamespacestd;
A::A(){
	cout<<"A的默认构造函数\n";
}

A::~A(){
	cout<<"A的析构函数\n";
}

A::A(inti){
	m=i;
	cout<<"A的自定义构造函数\n";
}

voidA::print_member(){
	cout<

6.4.4 拷贝构造函数

  • 拷贝构造函数(copy constructor):
    • 调用:用同类的对象创建一个新的对象,调用拷贝构造函数
    • 原型:<类名>(const<类名>&;
    • 使用场景
      1. 定义对象

A a1;

A a2(a1);  //或 A a2=a1;

  1. 把对象作为值参数传递给函数

void f(A x);

A a;

f(a);  //创建行参对象x,并调用拷贝函数用对象a对行参x进行初始化

  1. 把对象作为返回值

A f( ){

A a;

return a;  //创建一个A类的临时对象,并调用拷贝函数用对象a对其初始化

}

  • 定义:
    • 一般情况下不需要在类中定义,编译器会提供一个隐式的拷贝构造函数,进行逐个成员拷贝初始化(member-wise initialization(当包含成员对象时,调用成员对象的拷贝构造函数实现成员对象的初始化)
    • 当类中含有指针成员数据时,如果调用拷贝函数对另一个对象进行初始化,则两个对象的成员指针将指向同一内存区域,影响程序运行。
      • 解决方法:定义显式拷贝构造函数,在函数体中申请另一块内存空间给新的对象使用,并将原对象指针指向的内存区域复制到新申请的内存区域中
  • 注意:
    • 当类中包含成员对象时,隐式拷贝构造函数会调用成员对象的拷贝构造函数自定义拷贝构造函数则默认调用成员对象的默认构造函数
      • 解决方法:在自定义拷贝构造函数的成员初始化表中显式指出调用成员对象的拷贝构造函数
    • 用一个临时对象对另一个对象进行初始化时,编译器会对拷贝过程进行优化

6.5 类作为模块

6.5.1 类模块的组成

  • 一个C++模块由两个源文件构成,“.h”文件(存放类的定义)和“.cpp”文件(存放类外定义的成员函数)
  • mian函数没有“.h”文件的原因:main是主模版,不提供任何东西给外界使用
  • C++程序必须有main函数,存粹的面向对象程序是没有全局函数的

 

6.5.2 Demeter法则

  • Demeter法则(Law of Demeter)
    • 基本思想:一个类的成员函数除了能访问自身类结构的直接字结构外,不能以任何方式依赖于任何其他类的机构;并且每个成员函数只应向某个有限集合的对象发送消息。(仅与你的直接朋友交谈)
    • 表达形式:
      1. 类表达形式(L1)(适合静态类型的面向对象语言(如C++),在编译时刻检查是否满足法则)

对于类C的任何成员函数M,M中能直接访问或引用的对象必须属于下列类之一:

  1. 类C本身
  2. 成员函数M的参数类
  3. M或M所调用的成员函数所创建的对象类
  4. 全局对象所属的类
  5. 类C的成员对象所属的类
  1. 对象表达形式(L1)(适合动态类型的面向对象语言(如Smalltalk),在运行时刻检查是否满足法则)

对于类C的任何成员函数M,M中能直接访问或引用的对象必须属于下列对象之一:

  1. this指向的对象
  2. 成员函数M的参数对象
  3. M或M所调用的成员函数所创建的对象
  4. 全局变量中包含的对象
  5. C类的成员对象

 


6.6 对象与类的进一步讨论

6.6.1 对常量对象的访问——常(const)成员函数

  • 对象的成员函数分为:修改对象状态的成员函数和获取对象状态的成员函数
  • 常成员函数(const member function
    • 在获取对象状态的同时不会改变对象数据成员的值
    • 注意:
      1. 在其函数体中不能修改数据成员的值(但可改变指针类型数据指向值的值)
      1. 常量对象只能调用常成员函数
      1. 把const成员函数定义在类外时,函数声明和定义的地方都要加上const
      2. 两个名称、参数个数、参数类型相同的函数,一个是const一个是非const,它们也算是重载
  • 常量对象:用于函数的行参类型说明,防止函数修改实参对象
		class Section6{
		private:
			int a;
		public:
			voidf()const;//声明一个常成员函数
		};

void Section6::f() const{
	a=10;//error
}

6.6.2 同类对象之间的数据共享——静态(static)数据成员

  • C++采用静态成员(static member实现同一类对象共享数据的问题
  • 静态成员包括:
    1. 静态数据成员(static data member)
      • 在类中声明,在类的外部给出定义并进行初始化(静态整型常量和枚举类型常量可在类中进行初始化)
      • 类定义中的静态数据成员对于该类的所有对象只存在一个拷贝
      • 与全局变量相比,静态数据成员比较安全
    2. 静态成员函数(static member function)
      • 静态成员函数只能访问静态成员(静态成员函数没有this指针,静态数据成员只有一个拷贝,所以不需知道具体是哪个对象)
      • 访问静态成员的方式:
        1. 通过对象访问

A a;  a.set_shared();

  1. 通过类名访问

A::get_shared();

	class Section6{
	private:
		int a;
		static int s;//声明静态数据成员
	public:
		static void print_static_member();//静态成员函数
	};
	
	int Section6::s=1;
	
	void Section6::print_static_member(){
		cout<

6.6.3 提高对象私有数据成员的访问效率——友元(friend)

  • 在C++的一个类定义中,可以指定某个全局函数(友元函数)、某个其他类(友元类)或某个其他类的某个成员函数(友元类成员函数)能直接访问该类的私有(private)和保护(protected)成员,这三种统称友元(friend)
  • 友元的作用:提高面向对象程序设计的灵活性。是保护数据和数据存取效率之间的一个折衷方案
  • 友元不具有传递性
  • 应用:全局操作符重载函数

 

补充:https://blog.csdn.net/zzq1824837536/article/details/79485603

 

6.6.4 对象拷贝构造过程的优化——转移构造函数

  • 当用一个即将消亡的对象去初始化另一个同类的对象时,有时拷贝构造函数的效率不高,所以C++11引入一种新的构造函数——转移构造函数(move constructor来实现资源的转移

A&& x{  //参数类型为右值引用类型

p=x.p;  //把参数对象x的p所指向的空间作为新对象的p所指向的空间

x.p=NULL;  //使得参数对象x的p不再拥有原来所指的空间

}

  • 转移构造函数的参数类型为右值引用(r-value reference&&)类型,该类型要求相应的实参只能是临时对象或即将消亡的对象

 

6.6.5 操作符重载

 

  1. 操作符重载概述
    1. 操作符重载的需要性
      • 通过操作符重载机制可以重新定义操作符,实现按照习惯对某个类的对象进行操作,增强语言的灵活性和可扩充性
    2. 操作符重载的方式
      • C++中操作符重载(operator overloading通过定义操作符重载函数,对某个操作符进行额外的解释实现。
        • 该函数以“operator <某个操作符>”为函数名,操作数类型为参数类型,操作结果类型为返回值类型
      • 操作符重载函数可为成员函数,也可以为全局函数
        • 由于成员函数已有一个隐藏的参数:this
        • 作为全局函数定义操作符重载函数时,由于该函数中需要访问作为参数的类的私有成员,所以一般把全局的操作符重载函数说明为该类的友元
    3. 操作符重载的基本原则
      1. 遵循已有操作符的语法
        • 单目操作符只能重载单目,双目操作符只能重载成双目,操作符重载后并不改变操作符原有的优先级和结合性规定
      2. 遵循已有操作符的语义
      3. 可重载的操作符
        • 除了“.”(成员选择符),“ .* ”(间接成员选择符),“ : : ”(域解析符),“ ? : ”(条件操作符),“ sizeof ”(数据占内存大小)外,其他操作符都可重载

 

  1. 双目操作符重载
    1. 作为成员函数重载双目运算符
      • 成员函数已有隐藏参数this,所以只要给出一个参数(作为第二操作数)就行了,参数类型可以是除void以外的任意类型
      • 声明格式:

class <类名> {

...

<返回值类型> operator # (<类型>);

}

  • 定义格式:

<返回值类型><类名>::operator # (<类型> <参数>) { ... }

  • 使用格式:

<类名>a; <类名> b; a #b 或 a.operator#(b)

  1. 作为全局函数重载双目运算符
    • 给出的两个操作数的类型,至少有一个为类、结构、枚举或它们的引用类型
    • 大部分情况下,双目操作符必须以全局函数来重载(必须说明为相应类的友元)才能满足某些使用上的要求
    • 定义格式:

<返回值类型>operator #(<类型1><参数1> , <类型2><参数2>)  { ... }

  • 使用格式:

<类型1> a; <类型2> b; a#b;或 operator #(a,b)

 

  1. 单目操作符重载
    1. 作为成员函数重载单目操作符
      • 成员函数重载单目操作符不需要给出参数(已有this指针)
      • 声明格式:

class <类名>{

...

<返回值类型>operator # ( );

}

  • 定义格式:

<返回值类型><类名>::operator # ( ) { ... }

  • 使用格式:

<> a; #a 或 a.operator# ()

  1. 作为全局函数重载单目运算符
    • 需要指定一个参数,该参数必须是类、结构、枚举或它们的引用类型
    • 定义格式:

<返回值类型> operator # (<类型> <参数>) { ... }

  • 使用格式:

<类型>a; #a或operator #(a)

 

  1. C++中特殊操作符的重载
    1. 辅助操作符“=”重载
      • 两个同类型的对象之间逐个成员进行赋值操作(member-wise assignment),含义是用一个对象的状态改变另一个对象的状态
      • 拷贝构造函数和赋值操作符“=”重载函数的区别:
        1. 创建一个新的对象时用另一个已存在的同类对象对其初始化,则调用拷贝构造函数
        2. 两个已存在的对象,用其中一个对象改变另一个对象的状态,调用赋值操作符“=”重载函数
      • 在类中定义转移赋值操作符(move assignment operator)重载函数,可实现资源的转移
        • 该函数的参数类型为右值引用(rvalue reference,&&)类型,要求相应的实参必须是临时对象或即将消亡的对象
    2. 数组元素访问操作符“[ ]”重载
      • 实现对数组下标的检查,避免下标越界
    3. 动态分配存储与去配操作符new和delete重载
      • new和delete调用系统的对内存管理来实现创建和撤销对象,系统的堆内存管理效率不高,对于某个类可重载操作符new和delete来实现堆内存管理
      • new两个功能:动态对象分配空间和调用对象类的构造函数;delete两个功能:调用对象类的析构函数和释放对象的空间(对于操作符new和delete重载时,重载的是它们的分配空间和释放空间的功能,不影响对构造函数的析构函数的调用)
      • C++中new和delete只能作为类的静态成员函数来重载
        • 重载操作符new
          • 格式:void* operator new ( size_t size,··· );
            • 返回值必须为void*
            • size是需要分配的内存空间大小,其类型为size_t(unsigned int)
          • 使用:A *p=new A;
            • 过程:系统首先计算对象所占内存大小,然后将其作为实参调new的重载函数,函数通过行参size为对象分配空间。
            • 在new的重载函数返回分配的内存空间地址前,会自动调用对象类的构造函数对对象进行初始化
          • new的重载函数可以有多个(参数有所不同),如果在类中重载了new以后,使用new创建该类的对象不再调用系统提供的new操作来分配空间,而是调用自己的new操作符重载函数来分配空间
        • 重载操作符delete
          • 格式:void operator delete (void *p , size_t size);
          • delete重载函数要根据new重载函数如何分配空间来归还相应的空间
          • delete的重载函数只能有一个,如果在类中重载了delete以后,使用delete撤销该类的对象不再调用系统提供的delete操作,而是调用自己的delete操作符重载函数

 

1、使用重载操作符new和delete来管理某类对象的堆空间

基本思想:创建该类的第一个对象时,调用系统的堆存储分配功能从系统的堆区申请一个较大的空间,将该空间划分为若干个小空间,每个小空间存储一个对象,用链表管理这些小空间。今后创建第二个、第三个对象时,就无需调用系统堆存储分配来为对象分配空间了,而是在第一次申请的大空间中为其分配内存空间。当该类的对象消亡时,对象的空间暂不归还到系统堆空间,而是归还到大空间中。等程序结束,再归还到堆空间中。

#include
#include
const int NUM=32;
class A{
private:
	int a;
	double b;
	static A *p_free;//用于指向A类对象的自由空间链表
	A*next;//用于实现自由空间结点的链接
public:
	static void* operatornew(size_tsize);//声明new重载函数
	static void operatordelete(void*p);//声明delete重载函数
};

A*A::p_free = nullptr;

void* A::operatornew(size_tsize){
	A *p;
	if(p_free == nullptr){//第一次创建对象或上次申请的空间已用完
		p_free = (A*)malloc(size*NUM);//申请能存储NUM个A类对象的大空间
		for(p=p_free ; p!=p_free+NUM-1 ; p++){//建立自由结点链表
			p->next = p+1;
			}
		p->next = nullptr;
		}
	//在自由结点链表中为当前对象分配空间
	p = p_free;
	p_free = p_free->next;
	memset(p,0,size);//把对象空间初始化为全"0"。如果类中有构造函数,这个操作可以不要
	return p;
}

void A::operator delete(void *p){
	//把p指向的对象空间归还到自由结点链表中(没有归还到堆中)
	((A *)p)->next = p_free;
	p_free = (A*)p;
}
  1. 函数调用操作符“()”重载
  • C++中将函数调用作为操作符看待,并且可以针对某个类来重载。重载函数调用操作符后,相应类的对象可以当作函数来使用
  • 函数调用操作符重载主要用于只有一个操作的对象——函数对象(functor),该对象除了具有一般函数的行为外,他还可以拥有状态,可实现含有static存储类的局部变量功能
  • C++的匿名函数(ℷ表达式)就是通过创建一个函数对象来实现的
  1. 类成员访问符操作符“->”重载
    • “->”是一个双目运算符,其第一个操作数是一个指向类或结构的指针,第二个操作数是第一个操作数所指类或结构的成员
    • 通过对类成员访问操作符重载,可以实现智能指针(smart pointer,能在访问的时候做一些额外的事
  1. 操作符重载的实例——字符串类String的一种实现

 


6.7 小结

  • 数据抽象与数据封装
    • 数据抽象使得数据使用者只需要知道对数据所能实施的操作以及这些操作之间的关系,而不必知道数据的具体表示。
    • 数据封装是指把数据和对数据的操作作为一个整体来定义,并把数据的具体表示对使用者隐藏起来,对数据的访问只能通过封装体对外接口中的操作完成。
  • 面向对象程序设计
    • 内容:
      • 把程序构造成由若干个对象组成
      • 每个对象由一些数据以及对这些数据所能实施的操作构成
      • 对数据的操作是通过向包含数据的对象发送消息来实现(调用对象的操作函数)
      • 对象的特征(数据与操作)由相应的类来描述,一个类所描述的对象特征可以从其他类继承
    • 特点
      • 面向对象程序设计实现了数据的抽象与封装,对程序的模块化、复用性和易维护性等有更好的支持
    • 一个类描述了一组具有相同特征的对象。类定义包含了数据成员和成员函数的描述,成员函数可以重载
    • 在C++中,类成员的访问控制有三种:public、private、protected
      • public成员构成了类与外界的一种接口
  • 对象
    • 对象是类的实例。对象的操作一般通过对象名受限调用对象类的public成员函数来实现。
    • 对类成员的访问除了要受到类成员访问控制的限制外,还要受到类作用域的限制,在类定义外使用类定义中的标识符时,须通过对象名或类名受限
    • 类中描述的非静态数据成员对该类的每个对象都有一个拷贝,成员函数对该类的所有对象只有一个拷贝,成员函数通过隐含指针this来访问相应的对象
    • 类的静态成员
      • 静态数据成员对所有对象只有一个拷贝,它们被该类的所有对象共享
      • 静态成员函数没有this指针,所以只能访问静态数据成员
    • 创建对象时,先申请堆区内存空间,然后调用构造函数对其初始化;撤销对象时,先调用析构函数,然后归还对象申请的资源(包括内存资源)
      • 成员初始化表使用场景:
        1. 对类中的常量类型和引用类型进行初始化
        2. 对类中的成员对象进行初始化(显示调用该成员对象的非默认构造函数)
    • 当对含有成员对象的对象初始化时,先调用该对象的构造函数,再调用成员对象的构造函数
    • 撤销含有成员对象的对象时,先调用成员对象的析构函数,再调用该对象的析构函数
  • 拷贝构造函数用于创建对象时,用一个同类的对象对其初始化
  • 常成员函数只能获取对象的状态,不能改变对象的状态
  • 为了提高对类的private成员的访问效率,可把与一个类关系密切的全局函数、其他类或其他类的成员函数说明成该类的友元。一个类的友元可以访问该类的所有成员。
  • 利用转移构造函数右值引用类型的参数可以实现对象拷贝构造时的资源转移,从而提高拷贝构造函数的效率。
  • 操作符重载
    • 操作符重载属于程序设计中一种多态机制,它可以实现用已有的操作符来对自定义类型的数据进行操作。
      • 在C++中,操作符重载函数可以作为一个类的成员函数或作为全局函数来实现。
        • 作为全局函数来实现操作符重载函数时,其参数至少要有一个具有类、结构、枚举以及其引用类型的参数,并且,如果在函数中需要访问参数类的非public成员,则要把该全局操作符重载函数说明成参数类的友元
    • 应用场景:
      1. 重载操作符“=”可实现特殊的对象赋值操作
      2. 重载操作符“[ ]”可实现以下标形式访问具有线性关系的对象成员
      3. 重载操作符new与delete可以实现程序自行管理动态对象的堆空间
      4. 重载操作符“()”可以实现函数对象
      5. 重载操作符“->”可以实现智能指针
      6. 重载类型转换操作符可以实现类到基本数据的转换

 


参考:《程序设计教程:用C++语言编程》 陈家骏,郑滔

你可能感兴趣的:(c++)