C++类机制
从现在开始从程序员的角度学习C++类的基础语法规则
类基础
1.组成
类是由数据成员加函数组成的一种新的数据类型
2.类成员的访问
通过this指针,可以使成员函数可以访问成员变量,this指针作为成员函数的第一个隐式参数,当实例化类调用函数时,会将变量的地址传入this。
this的默认类型是指向非常量类型的常量指针,也就是说this本身不能变化,但是只能指向非常量类型,也就是const修饰的类类型无法使用,为了改善这一点,需要在函数参数后加上const关键字进行修饰。将this声明为指向常量类型的常量指针。
如:std :: string isbn() const { return bookNo; }
const修饰一个类也可以有更加细腻的访问控制,mutable关键字修饰的类成员可以改变
3.类成员的访问之作用域
访问数据需要保证数据的可达性,也就是作用域,需要考虑数据的声明以及定义
类内的访问规则为:成员函数体可以随意访问成员变量,不需要考虑定义的位置。成员函数也可以定义在类的外部,但是需要和类声明相匹配。需要加类修饰符,类似于命名空间。
4.类初始化
类使用一个成员函数称为构造函数来进行类类型变量的初始化工作。若未定义构造函数,则编译器会默认提供一个称为默认构造函数。规则如下:类内数据若有初始值则直接初始化,否则进行默认初始化。只有当类没有任何构造函数,编译器才会生成。
5.访问控制
public,和privite比较基础,不多说了
另一种特殊的是友元规则,提供了更加细致的访问规则设置
6.更深入特性
如:关于以构造函数为基本原理的隐式类型转换,静态成员,名字查找(编译原理相关),委托构造函数,函数重载等等。
资源管理
开始以五个函数来展开,分别是拷贝构造函数,拷贝复制函数,移动构造函数,移动赋值函数,析构函数
1.拷贝构造函数
会调用拷贝构造函数:
拷贝初始化
将一个对象作为实参传递给一个非引用类型的形参
从一个返回类型为非引用类型的函数返回一个对象
用花括号列表初始化一个数组中的元素或一个聚合类中的成员(参见7.5.5节,第
266页)很好解释了:
在函数调用过程中,具有非引用类型的参数要进行拷贝初始化(参见6.2.1节,第188
页)。类似的,当一个函数具有非引用的返回类型时,返回值会被用来初始化调用方的结
果(参见6.3.2节,第201页)。拷贝构造函数被用来初始化非引用类类型参数,这一特性解释了为什么拷贝构造函数
自己的参数必须是引用类型。如果其参数不是引用类型,则调用永远也不会成功 – 为了
调用拷贝构造函数,我们必须拷贝它的实参,但为了拷贝实参,我们又需要调用拷贝构造
函数,如此无限循环。
例子:
vector<int> v1(10); //正确:直接初始化 |
2.析构函数
如同构造函数有一个初始化部分和一个函数体(参见7.5.1节,第257页),析构函数
也有一个函数体和一个析构部分。在一个构造函数中,成员的初始化是在函数体执行之前
完成的,且按照它们在类中出现的顺序进行初始化。在一个析构函数中,首先执行函数体,
然后销毁成员。成员按初始化顺序的逆序销毁。在对象最后一次使用之后,析构函数的函数体可执行类设计者希望执行的任何收尾工
作。通常,析构函数释放对象在生存期分配的所有资源。在一个析构函数中,不存在类似构造函数中初始化列表的东西来控制成员如何销毁,
析构部分是隐式的。成员销毁时发生什么完全依赖于成员的类型。销毁类类型的成员需要
执行成员自己的析构函数。内置类型没有析构函数,因此销毁内置类型成员什么也不需要
做。
3.两个重要法则
- 如果一个类需要一个拷贝构造函数,几乎可以肯定它也需要一个拷贝赋值运算符。反之亦然 – 如果一个类需要一个拷贝赋值运算符,几乎可以肯定它也需要一个拷贝构造函数。然而,无论是需要拷贝构造函数还是需要拷贝赋值
运算符都不必然意味着也需要析构函数。 - 如果一个类需要自定义析构函数,几乎可以肯定它也需要自定义拷贝赋值运算符和拷贝构造函数。
4.阻止拷贝
5.类值的拷贝和类指针的拷贝
以是否共享一个对象作为区分,需要在类的内部维护一定的状态
面向对象程序设计
面向对象程序设计(object-oriented programming)的核心思想是数据抽象、继承和动态绑定。通过使用数据抽象,我们可以将类的接口与实现分离(见第7章);使用继承,可以定义相似的类型并对其相似关系建模;使用动态绑定,可以在一定程度上忽略相似类型的区别,而以统一的方式使用它们的对象。
关于基类和派生类的定义基本和类相同,不同的是存在继承和细致的访问控制以及函数的特殊性。
基类会继承派生类所有东西,但是不一定所有元素都可以使用,可以通过protect等关键字进行调整。
1.初始化
需要注意一点,每个对象都对自己的数据负责,继承而来的数据,需要调用父类的构造函数来完成初始化操作。
2.虚函数
需要理解静态类型和动态类型,一个是编译时,一个是runtime。其次就可以理解什么是动态绑定了,说白了就是通过指针定位虚表来调用相应的虚函数,但是指针的实际类型只有在运行时才能知道,因此基本可以忽略静态类型,只考虑动态类型进行函数定位(以上讨论只能基于指针)
3.析构
需要将析构函数定义为虚函数,否则无法正确删除对象
4.访问规则
在 C++ 中,继承的成员访问规则主要取决于继承方式(public
、protected
、private
)和成员的访问权限(public
、protected
、private
)。总结如下:
- 继承方式对成员访问的影响
- Public 继承:
- 基类的
public
成员在派生类中仍然是public
。 - 基类的
protected
成员在派生类中仍然是protected
。 - 基类的
private
成员不能在派生类中直接访问。
- 基类的
- Protected 继承:
- 基类的
public
成员在派生类中变为protected
。 - 基类的
protected
成员在派生类中仍然是protected
。 - 基类的
private
成员不能在派生类中直接访问。
- 基类的
- Private 继承:
- 基类的
public
和protected
成员在派生类中都变为private
。 - 基类的
private
成员不能在派生类中直接访问。
- 基类的
- 成员访问权限的影响
- Public 成员:在
public
继承中,基类的public
成员可以被派生类对象和派生类外部访问。 - Protected 成员:只能被基类和派生类中的成员函数访问,外部无法直接访问。
- Private 成员:只能在基类中访问,派生类和外部均无法直接访问。