从现在开始从程序员的角度学习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); //正确:直接初始化
vector<int> v2=10;//错误:接受大小参数的构造函数是explicit的
void f(vector<int>);//f的参数进行拷贝初始化
f(10);//错误:不能用一个explicit的构造函数拷贝一个实参
f(vector<int>(10));// 正确:从一个int直接构造一个临时vector

2.析构函数

如同构造函数有一个初始化部分和一个函数体(参见7.5.1节,第257页),析构函数
也有一个函数体和一个析构部分。在一个构造函数中,成员的初始化是在函数体执行之前
完成的,且按照它们在类中出现的顺序进行初始化。在一个析构函数中,首先执行函数体,
然后销毁成员。成员按初始化顺序的逆序销毁。

在对象最后一次使用之后,析构函数的函数体可执行类设计者希望执行的任何收尾工
作。通常,析构函数释放对象在生存期分配的所有资源。

在一个析构函数中,不存在类似构造函数中初始化列表的东西来控制成员如何销毁,
析构部分是隐式的。成员销毁时发生什么完全依赖于成员的类型。销毁类类型的成员需要
执行成员自己的析构函数。内置类型没有析构函数,因此销毁内置类型成员什么也不需要
做。

3.两个重要法则

  • 如果一个类需要一个拷贝构造函数,几乎可以肯定它也需要一个拷贝赋值运算符。反之亦然 – 如果一个类需要一个拷贝赋值运算符,几乎可以肯定它也需要一个拷贝构造函数。然而,无论是需要拷贝构造函数还是需要拷贝赋值
    运算符都不必然意味着也需要析构函数。
  • 如果一个类需要自定义析构函数,几乎可以肯定它也需要自定义拷贝赋值运算符和拷贝构造函数。

4.阻止拷贝

5.类值的拷贝和类指针的拷贝

以是否共享一个对象作为区分,需要在类的内部维护一定的状态

面向对象程序设计

面向对象程序设计(object-oriented programming)的核心思想是数据抽象、继承和动态绑定。通过使用数据抽象,我们可以将类的接口与实现分离(见第7章);使用继承,可以定义相似的类型并对其相似关系建模;使用动态绑定,可以在一定程度上忽略相似类型的区别,而以统一的方式使用它们的对象。

关于基类和派生类的定义基本和类相同,不同的是存在继承和细致的访问控制以及函数的特殊性。

基类会继承派生类所有东西,但是不一定所有元素都可以使用,可以通过protect等关键字进行调整。

1.初始化

需要注意一点,每个对象都对自己的数据负责,继承而来的数据,需要调用父类的构造函数来完成初始化操作。

2.虚函数

需要理解静态类型和动态类型,一个是编译时,一个是runtime。其次就可以理解什么是动态绑定了,说白了就是通过指针定位虚表来调用相应的虚函数,但是指针的实际类型只有在运行时才能知道,因此基本可以忽略静态类型,只考虑动态类型进行函数定位(以上讨论只能基于指针)

3.析构

需要将析构函数定义为虚函数,否则无法正确删除对象

4.访问规则

在 C++ 中,继承的成员访问规则主要取决于继承方式(publicprotectedprivate)和成员的访问权限(publicprotectedprivate)。总结如下:

  1. 继承方式对成员访问的影响
  • Public 继承
    • 基类的 public 成员在派生类中仍然是 public
    • 基类的 protected 成员在派生类中仍然是 protected
    • 基类的 private 成员不能在派生类中直接访问。
  • Protected 继承
    • 基类的 public 成员在派生类中变为 protected
    • 基类的 protected 成员在派生类中仍然是 protected
    • 基类的 private 成员不能在派生类中直接访问。
  • Private 继承
    • 基类的 publicprotected 成员在派生类中都变为 private
    • 基类的 private 成员不能在派生类中直接访问。
  1. 成员访问权限的影响
  • Public 成员:在 public 继承中,基类的 public 成员可以被派生类对象和派生类外部访问。
  • Protected 成员:只能被基类和派生类中的成员函数访问,外部无法直接访问。
  • Private 成员:只能在基类中访问,派生类和外部均无法直接访问。