类和对象

1.c++面向对象的三大特性:封装、继承、多态

2.c++认为万物皆可对象,对象上有其属性和行为

3.具有相同性质的对象,我们可以抽象称为类

4.封装

(1)意义:

  • 将属性和行为作为一个整体,表现生活中的事物

    在设计类的时候,属性和行为写在一起

    class 类名{ 访问权限: 属性 / 行为 };
    //属性使用变量表示
    //行为一般用函数表示

    在类的概念下,具体的一个物体(整体),称作对象。【类似结构体】

  • 将属性和行为加以权限控制

    访问权限有三种:

    1.public(公共权限):类内可以访问,类外可以访问

    2.protected(保护权限):类内可以访问,类外不可以访问,子类可以访问

    3.private(私有权限):类内可以访问,类外不可以访问,子类不可以访问

(2)struct和class区别(默认访问权限不同)

  • struct默认权限为公共,可以定义成员函数
  • class默认权限为私有

(3)成员属性设置为私有

优点1:将所有成员属性设置为私有,可以自己控制读写权限【通过设置公有权限下函数控制读写权限】

优点2:对于写权限,我们可以检测数据的有效性

(4)对象的初始化和清理

C++中的面向对象来源于生活,每个对象也都会有初始设置以及对象销毁前的清理数据的设置。

对象的初始化和清理也是两个非常重要的安全问题,一个对象或者变量没有初始状态,对其使用后果是未知。同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题。

C++利用了构造函数和析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供。

编译器提供的构造函数和析构函数是空实现。

1.构造函数:

主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。

默认情况下,c++一般给一个类添加三个函数:默认构造函数,默认析构函数,默认拷贝构造函数

语法:

类名(){函数体}
  • 构造函数,没有返回值也不写void
  • 函数名称与类名相同
  • 构造函数可以有参数,因此可以发生重载
  • 程序在调用对象时候会自动调用构造,无须手动调用,,而且只会调用一次

构造函数的分类及调用:

1.两种分类方式:

  • 按参数分:有参构造和无参构造(又称默认构造参数)

  • 按类型分:

    普通构造

    拷贝构造(将其他对象的属性作为默认值拷贝过来)

    类名(const 类名 &对象名){函数体}

    c++中拷贝构造函数调用时机通常有三种情况:

    1. 使用一个已经创建完毕的对象来初始化新对象
    2. 值传递的方式给函数参数传值
    3. 以值方式返回局部对象

2.三种调用方式:

  • 括号法:通过有无括号和参数个数调用对应的构造函数,无括号调用默认构造函数

  • 显示法:通过“=”号调用对应构造函数,左侧是“类名 对象名”,右侧带入具体内容,无左侧内容和“=”号的,为匿名对象,特点是当前行执行结束后,系统会立刻回收掉匿名对象

    *不要利用拷贝构造函数,初始化匿名对象!编译器会认为是重定义

  • 隐式转换法:隐藏掉显示法”=“右侧的类名和括号,是显示法的简化写法

3.调用规则

  • 用户定义有参构造函数,c++ 不提供无参构造,但会提供默认拷贝构造
  • 用户定义拷贝构造函数,c++不提供其他构造函数

4.深浅拷贝

  • 深拷贝:在堆区重新申请空间,进行拷贝操作

    【使用new在堆区开辟新空间(一般变量在栈上),并返回一段空间的首地址,一般用指针接收,需要手动在析构函数中用“delete”释放这一段空间,并且将指针变为空指针(指向NULL),如果用浅拷贝就会直接拷贝地址,从而发生重复释放,这时需要用深拷贝模仿操作开辟一个新空间】

    pic-2

  • 浅拷贝:简单的赋值拷贝操作

2.析构函数:

主要作用在于对象销毁前系统自动调用,执行一些清理工作。

语法:

~类名(){函数体}
  • 没有返回值也不写void
  • 函数名称与类名相同,在名称前加上符号~
  • 析构函数不可以有参数,因此不可以发生重载
  • 程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次

3.初始化列表——初始化属性

语法:

构造函数():属性1(值1),属性2(值2)……{构造函数的函数体}

(5)类对象作为类成员

c++类的成员可以是另一个类的对象,我们称该成员为对象成员

B类中有对象A作为成员,A为对象成员,构造时,先构造类的对象,再构造自身,析构时先析构自身,再析构类的对象

(6)静态成员

在成员变量和成员函数前加上关键字static,称为静态成员,分为:静态成员变量和静态成员函数

  • 静态成员变量

    1.所有对象共享同一份数据

    2.在编译阶段分配内存

    3.类内声明(static 变量类型 静态成员变量名;),类外初始化(变量类型 类名::静态成员变量名 = 初值;)

  • 静态成员函数

    1.所有对象共享同一个函数

    2.静态成员函数只能访问静态成员变量

静态成员函数的访问:

  1. 通过对象访问
  2. 通过类名访问

*静态成员函数同样具有访问权限

(7)c++对象模型和this指针

成员变量和成员函数分开存储:在c++中,类内的成员变量和成员函数分开储存,只有非静态成员变量才属于类的对象

空对象占用内存空间为:1,为了区分每个空对象所占内存的位置

非空对象占用内存空间决定于其包含成员所占字节数。

每一个非静态成员函数只会诞生-份函数实例,也就是说多个同类型的对象会共用一块代码那么问题是:这一块代码是如何区 分那个对象调用自己的呢?

C+ +通过提供特殊的对象指针,this指针, 解决上述问题。this指针指向被调用的成员函数所属的对象

this指针是隐含每一个非静态成员函数内的一种指针this指针不需要定义,直接使用即可

this指针的用途:

●当形参和成员变量同名时,可用this指针来区分

●在类的非静态成员函数中返回对象本身,可使用return *this

c++中,空指针也是可以调用成员函数的,但是需要注意有没有用到this指针,如果用到的话,需要加以判断保证代码的健壮性。

常函数:

●成员函数后加const后我们称为这个函数为常函数

●常函数内不可以修改成员属性

●成员属性声明时加关键字mutable后, 在常函数中依然可以修改

常对象:

●声明对象前加const称该对象为常对象

●常对象只能调用常函数

(8)友元

在程序里,有些私有属性也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术

友元的目的就是让一个函数或者类访问另一个类中私有成员,关键字为friend

友元的三种实现:

●全局函数做友元

●类做友元

●成员函数做友元.

(9)运算符重载

重载声明是指一个与之前已经在该作用域内声明过的函数或方法具有相同名称的声明,但是它们的参数列表和定义(实现)不相同。

当调用一个重载函数或重载运算符时,编译器通过把所使用的参数类型与定义中的参数类型进行比较,决定选用最合适的定义。选择最合适的重载函数或重载运算符的过程,称为重载决策。

可以重定义或重载大部分 C++ 内置的运算符。这样就能使用自定义类型的运算符。

重载的运算符是带有特殊名称的函数,函数名是由关键字 operator 和其后要重载的运算符符号构成的。与其他函数一样,重载运算符有一个返回类型和一个参数列表。

Box operator+(const Box&);

声明加法运算符用于把两个 Box 对象相加,返回最终的 Box 对象。大多数的重载运算符可被定义为普通的非成员函数或者被定义为类成员函数。如果我们定义上面的函数为类的非成员函数,那么我们需要为每次操作传递两个参数,如下所示:

Box operator+(const Box&, const Box&);

可重载运算符:

双目算术运算符 + (加),-(减),*(乘),/(除),% (取模)
关系运算符 ==(等于),!= (不等于),< (小于),> (大于),<=(小于等于),>=(大于等于)
逻辑运算符 ||(逻辑或),&&(逻辑与),!(逻辑非)
单目运算符 + (正),-(负),*(指针),&(取地址)
自增自减运算符 ++(自增),–(自减)
位运算符 | (按位或),& (按位与),~(按位取反),^(按位异或),,<< (左移),>>(右移)
赋值运算符 =, +=, -=, *=, /= , % = , &=, |=, ^=, <<=, >>=
空间申请与释放 new, delete, new[ ] , delete[]
其他运算符 ()(函数调用),**->(成员访问),,(逗号),[]**(下标)

不可重载运算符:

  • **.**:成员访问运算符
  • .*, ->*:成员指针访问运算符
  • **::**:域运算符
  • sizeof:长度运算符
  • **?:**:条件运算符
  • **#**: 预处理符号

(5)继承

面向对象程序设计中最重要的一个概念是继承。继承允许我们依据另一个类来定义一个类,这使得创建和维护一个应用程序变得更容易。这样做,也达到了重用代码功能和提高执行效率的效果。

当创建一个类时,您不需要重新编写新的数据成员和成员函数,只需指定新建的类继承了一个已有的类的成员即可。这个已有的类称为基类,新建的类称为派生类

继承代表了 is a 关系。例如,哺乳动物是动物,狗是哺乳动物,因此,狗是动物,等等。

pic-3

基类 & 派生类:

一个类可以派生自多个类,这意味着,它可以从多个基类继承数据和函数。定义一个派生类,我们使用一个类派生列表来指定基类。类派生列表以一个或多个基类命名,形式如下:

class derived-class: access-specifier base-class

其中,访问修饰符 access-specifier 是 public、protectedprivate 其中的一个,base-class 是之前定义过的某个类的名称。如果未使用访问修饰符 access-specifier,则默认为 private。

访问控制和继承

派生类可以访问基类中所有的非私有成员。因此基类成员如果不想被派生类的成员函数访问,则应在基类中声明为 private。

我们可以根据访问权限总结出不同的访问类型,如下所示:

访问 public protected private
同一个类 yes yes yes
派生类 yes yes no
外部的类 yes no no

一个派生类继承了所有的基类方法,但下列情况除外:

  • 基类的构造函数、析构函数和拷贝构造函数。
  • 基类的重载运算符。
  • 基类的友元函数。

继承类型:

当一个类派生自基类,该基类可以被继承为 public、protectedprivate 几种类型。继承类型是通过上面讲解的访问修饰符 access-specifier 来指定的。

我们几乎不使用 protectedprivate 继承,通常使用 public 继承。当使用不同类型的继承时,遵循以下几个规则:

  • 公有继承(public):当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有保护成员来访问。
  • 保护继承(protected): 当一个类派生自保护基类时,基类的公有保护成员将成为派生类的保护成员。
  • 私有继承(private):当一个类派生自私有基类时,基类的公有保护成员将成为派生类的私有成员。

多继承:

多继承即一个子类可以有多个父类,它继承了多个父类的特性。

C++ 类可以从多个类继承成员,语法如下:

class <派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>,…
{
<派生类类体>
};

其中,访问修饰符继承方式是 public、protectedprivate 其中的一个,用来修饰每个基类,各个基类之间用逗号分隔,如上所示。现在让我们一起看看下面的实例:

(6)多态

多态按字面的意思就是多种形态。当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态。

多态分为两类:

●静态多态:函数重载和运算符重载属于静态多态,复用函数名

●动态多态:派生类和虚函数实现运行时多态

静态多态和动态多态区别:

●静态多态的函数地址早绑定- 编译阶段确定函数地址

●动态多态的函数地址晚绑定- 运行阶段确定函数地址

C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。

每个子类都能有一个函数 area() 的独立实现。这就是多态的一般使用方式。有了多态,您可以有多个不同的类,都带有同一个名称但具有不同实现的函数,函数的参数甚至可以是相同的。

虚函数

虚函数 是在基类中使用关键字 virtual 声明的函数。在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数。

我们想要的是在程序中任意点可以根据所调用的对象类型来选择调用的函数,这种操作被称为动态链接,或后期绑定

纯虚函数

您可能想要在基类中定义虚函数,以便在派生类中重新定义该函数更好地适用于对象,但是您在基类中又不能对虚函数给出有意义的实现,这个时候就会用到纯虚函数。