C++之再谈类与对象
- [1. 类的6个默认成员函数](#1. 类的6个默认成员函数)
-
- [1.1 构造函数](#1.1 构造函数)
-
- [1.1.1 构造函数的特性](#1.1.1 构造函数的特性)
- [1.1.2 构造函数的应用](#1.1.2 构造函数的应用)
- [1.2 析构函数](#1.2 析构函数)
-
- [1.2.1 析构函数的特性](#1.2.1 析构函数的特性)
- [1.2.2 析构函数的应用](#1.2.2 析构函数的应用)
- [1.3 拷贝构造函数](#1.3 拷贝构造函数)
-
- [1.3.1 拷贝构造函数的特性](#1.3.1 拷贝构造函数的特性)
- [1.3.1 拷贝构造函数的应用](#1.3.1 拷贝构造函数的应用)
- [1.4 赋值运算符重载](#1.4 赋值运算符重载)
-
- [1.4.1 赋值运算符重载使用说明](#1.4.1 赋值运算符重载使用说明)
- [1.4.2 赋值运算符重载的特性](#1.4.2 赋值运算符重载的特性)
- [1.4.3 赋值运算符重载的应用](#1.4.3 赋值运算符重载的应用)
- [1.4.4 重载函数和构造函数的区别](#1.4.4 重载函数和构造函数的区别)
- [1.5 取地址及const取地址运算符重载](#1.5 取地址及const取地址运算符重载)
- [2. const成员函数](#2. const成员函数)
-
- [2.1 const成员函数的特性](#2.1 const成员函数的特性)
- [3. 再谈构造函数](#3. 再谈构造函数)
-
- [3.1 构造函数体赋值](#3.1 构造函数体赋值)
- [3.2 初始化列表](#3.2 初始化列表)
-
- [3.2.1 初始化列表的特性](#3.2.1 初始化列表的特性)
- [3.2.2 explicit关键字](#3.2.2 explicit关键字)
- [4. Static成员](#4. Static成员)
- [5. 友元](#5. 友元)
-
- [5.1 友元函数](#5.1 友元函数)
-
- [5.1.1 友元函数的特性](#5.1.1 友元函数的特性)
- [5.2 友元类](#5.2 友元类)
-
- [5.2.1 友元类的特性](#5.2.1 友元类的特性)
- [6. 内部类](#6. 内部类)
-
- [6.1 内部类的特性](#6.1 内部类的特性)
- [7. 再次理解类和对象](#7. 再次理解类和对象)
-
- [7.1 再次理解类](#7.1 再次理解类)
- [7.2 再次理解对象](#7.2 再次理解对象)
上篇文章详细讲述了类与对象的基础知识,相信阅读过《C++之类与对象》的小伙伴们都已经对于C++面向对象编程以及类的基本内容耳熟能详了吧~,本篇文章是对《C++之类与对象》的补充以及拓展。学习完本文小伙伴们才能真正学会并掌握了类与对象的所有内容,才能真正攻克C++学习之旅的第一个重难点---类与对象,后续的学习才能平步青云o(  ̄▽ ̄ )ブ
1. 类的6个默认成员函数
默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。
类的6个默认成员函数分别是构造函数、析构函数、拷贝构造函数、赋值运算符重载、取地址运算符重载、const 取地址运算符重载。
注:6个默认成员函数不能写在全局。
1.1 构造函数
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。
1.1.1 构造函数的特性
构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象。
- 函数名与类名相同。
- 无返回值。(也不写void)
- 对象实例化时编译器自动调用对应的构造函数。
- 构造函数可以重载。
- 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
- 编译器生成默认的构造函数会对自定类型成员调用的它的默认成员
函数。(内置类型不做处理或内置类型成员变量在类中声明时可以给默认值/缺省值,自定义类型会去调用它们的默认构造) - 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
注:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数(不传参就可以调用的函数)都可以认为是默认构造函数。
1.1.2 构造函数的应用
需要写构造函数的情况:
1.一般情况下,构造函数都需要我们自己写:在一般情况下,当类中包含一些需要特殊初始化逻辑的成员变量时,比如动态分配内存的指针变量、需要特定配置才能初始化的资源相关变量等, 就需要自己写构造函数。
不需要写构造函数的情况:
1.内置类型成员都有缺省值,且初始化符合我们的要求:如果已经给内置类型成员变量设置了合适的缺省值,且这些缺省值满足程序的使用需求,那么默认构造函数就能正常工作。
2.全是自定义类型的构造,且这些类型都定义默认构造:当创建包含这些自定义类型成员的类对象时,编译器生成的默认构造函数会自动调用这些自定义类型成员的默认构造函数。
1.2 析构函数
析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
1.2.1 析构函数的特性
析构函数是特殊的成员函数,其特征如下:
- 析构函数名是在类名前加上字符 ~。
- 无参数无返回值类型。
- 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
- 对象生命周期结束时,C++编译系统系统自动调用析构函数
- 编译器生成的默认析构函数,对自定类型成员调用它的析构函数
- 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数。有资源申请时,一定要写,否则会造成资源泄漏。
1.2.2 析构函数的应用
1.一般情况下,有动态申请资源,就需要显示写析构函数释放资源。
2.没有动态申请的资源,不需要写析构
3.需要释放资源的成员都是自定义类型,不需要写析构
1.3 拷贝构造函数
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
1.3.1 拷贝构造函数的特性
- 拷贝构造函数是构造函数的一个重载形式。(内置类型直接拷贝
自定义类型必须调用拷贝构造完成拷贝) - 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。
- . 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
- 类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。
注 :在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的。
内置类型成员完成值拷贝/浅拷贝;自定义类型成员会调用它的拷贝构造。 浅拷贝缺点:1析构两次,报错;2.一个修改会影响另一个
1.3.1 拷贝构造函数的应用
- 使用已存在对象创建新对象
- 函数参数类型为类类型对象
- 函数返回值类型为类类型对象
注:为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用尽量使用引用。
1.4 赋值运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
1.4.1 赋值运算符重载使用说明
- 不能通过连接其他符号来创建新的操作符:比如operator@
- 重载操作符必须有一个类类型参数(内置类型可以直接比较,自定义类型比较可用运算符重载)
- 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
- 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
- .* :: sizeof ?: . 注意以上5个运算符不能重载。
注:操作符是几个操作数,重载函数就有几个参数
1.4.2 赋值运算符重载的特性
- 赋值运算符重载格式
参数类型 :const T&,传递引用可以提高传参效率
返回值类型 :T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
检测是否自己给自己赋值
返回*this :要复合连续赋值的含义 - 赋值运算符只能重载成类的成员函数不能重载成全局函数
- 用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。
1.4.3 赋值运算符重载的应用
1.如果类中未涉及到资源管理,赋值运算符是否实现都可以
- 涉及到资源管理则必须要实现赋值重载运算符。
1.4.4 重载函数和构造函数的区别
赋值重载运算符-重载函数:已经存在的两个对象之间复制拷贝
拷贝构造函数-构造函数:用一个已经存在的对象初始化另一个对象
1.5 取地址及const取地址运算符重载
取地址运算符重载及const取地址运算符重载这两个默认成员函数一般不用重新定义 ,编译器默认会生成。只有特殊情况,才需要重载,比如想让别人获取到指定的内容!
cpp
class Date
{
public :
Date* operator&() //取地址运算符重载
{
return this ;
}
const Date* operator&()const//const取地址运算符重载
{
return this ;
}
private :
int _year ; // 年
int _month ; // 月
int _day ; // 日
};
补充 :流插入cout,ostream:可以支持内置类型是库里面实现了
可以支持自定义识别类型是因为函数重载。
2. const成员函数
将const修饰的"成员函数"称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。
1.成员函数后面加const以后,普通对象和const对象都可以调用。
2.要修改成员变量的函数不能加const。即只要成员函数内部不
修改成员变量,都应该加const
2.1 const成员函数的特性
1.const对象不可以调用非const成员函数 。因为const
对象承诺不会修改对象的状态,而非const
成员函数可能会修改对象成员,所以const
对象只能调用const
成员函数。
-
非const对象可以调用const成员函数 。非
const
对象本身可以修改状态,调用const
成员函数(不修改对象状态)是允许的。 -
const成员函数内不可以调用其它的非const成员函数 。
const
成员函数保证不修改对象,而非const
成员函数可能修改对象,若在const
成员函数内调用非const
成员函数,就可能破坏const
成员函数的约束。 -
非const成员函数其它内可以调用的const成员函数 。非
const
成员函数可以修改对象,但调用const
成员函数(不修改对象)不会破坏对象状态,是允许的。
3. 再谈构造函数
在构造函数中,对成员变量的初始化有两种方式:构造函数体赋值和初始化列表。
3.1 构造函数体赋值
构造函数体中的代码是在对象创建时(成员变量已完成默认初始化后)执行的,本质上是对已初始化的成员变量进行二次赋值。
这种方式适用于大多数普通成员变量,但对于const 成员变量、引用成员变量、没有默认构造函数的自定义类型成员,无法通过构造函数体赋值初始化(因为它们必须在定义时初始化,而构造函数体是赋值操作,晚于初始化时机)。
在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值。
构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值。
3.2 初始化列表
初始化列表是成员变量定义并初始化的地方,位于构造函数参数列表之后、函数体之前,用冒号:开头,成员初始化项之间用逗号分隔。它在对象创建时优先于构造函数体执行,直接完成成员变量的初始化。
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。
3.2.1 初始化列表的特性
- 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
- 类中包含以下成员,必须放在初始化列表位置进行初始化:
引用成员变量
const成员变量
自定义类型成员(且该类没有默认构造函数时) - 尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。
- 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关(建议声明和定义顺序保持一致)
3.2.2 explicit关键字
构造函数不仅可以构造与初始化对象,对于接收单个参数的构造函数,还具有类型转换的作用。接收单个参数的构造函数具体表现:
- 构造函数只有一个参数
- 构造函数有多个参数,除第一个参数没有默认值外,其余参数都有默认值
- 全缺省构造函数
用explicit修饰构造函数,将会禁止构造函数的隐式转换(内置类型转换为自定义类型)。
4. Static成员
声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化。
Static成员的特性
- 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
- 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
- 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
- 静态成员函数没有隐藏的this指针,不能访问任何非静态成员(指定类域和访问限定符就可以访问)
- 静态成员也是类的成员,受public、protected、private 访问限定符的限制
注 :成员变量:属于每一个类对象,储存在对象里面。
静态成员变量:属于类的每个对象共享,存储在静态区,生命周期是全局的。
5. 友元
友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。
友元分为:友元函数和友元类
5.1 友元函数
友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。
5.1.1 友元函数的特性
- 友元函数可访问类的私有和保护成员,但不是类的成员函数
- 友元函数不能用const修饰
- 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
- 一个函数可以是多个类的友元函数
- 友元函数的调用与普通函数的调用原理相同
5.2 友元类
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
5.2.1 友元类的特性
- 友元关系是单向的,不具有交换性
- 友元关系不能传递
- 友元关系不能继承
6. 内部类
概念:如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。
注意:内部类就是外部类的友元类,参见友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。
6.1 内部类的特性
- 内部类可以定义在外部类的public、protected、private都是可以的。
- 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。
- sizeof(外部类)=外部类,和内部类没有任何关系
7. 再次理解类和对象
7.1 再次理解类
类是对某一类实体(对象)来进行描述的,描述该对象具有那些属性,哪些方法,描述完成后就形成了一种新的自定义类型,采用该自定义类型就可以实例化具体的对象。
7.2 再次理解对象
有名对象-生命周期在当前函数局部域。
匿名对象-生命周期在当前行。
匿名对象具有常性 当用const引用时可延长匿名对象生命周期,变为当前函数局部域。
写在最后 :到这里,关于C++类与对象的进阶知识就讲解完毕啦~ 从类的默认成员函数,到const成员函数、初始化列表,再到static成员、友元、内部类等内容,相信小伙伴们对类与对象有了更深入、更全面的认识。类与对象是C++面向对象编程的核心基石,掌握好这些知识,就为后续深入学习继承、多态等更复杂的面向对象特性筑牢了根基。希望大家在C++的学习之路上,能带着对类与对象的透彻理解,继续探索更多精彩内容,不断提升编程能力~