C++类与对象(四)

345 阅读8分钟

static

  • 静态成员:被static修饰过的成员变量\函数

  • 访问方式:

    • 通过对象,如: 对象.静态成员
    • 通过类访问,如: 类名::静态成员
    • 通过指针,如: 对象指针->静态成员

静态成员

  • 静态成员变量:

    • 存储在数据段,即全局区,类似于全局变量,整个程序运行过程中只有一份内存
    • 对比全局变量它可以设定访问权限(public、private、protected),达到局部共享的目的
    • 必须初始化,且必须在类外面初始化,初始化时不能带static,如果类的声明和实现分离则需要在.cpp文件中初始化

image.png

image.png

静态成员函数

静态成员函数:

  • 内部不能使用this指针,this指针只能用于非静态成员函数内部,因为静态成员函数可以被类直接调用,所以this指针无意义
  • 不能是虚函数(虚函数只能是非静态成员函数),因为虚函数的意义在于实现多态,同样是对象来调用
  • 内部不能访问非静态成员变量\函数,只能访问静态成员变量\函数
  • 非静态成员函数内部可以访问静态成员变量\函数
  • 构造函数、析构函数不能是静态
  • 当声明和类实现分离时,实现部分不能带static

汇编分析静态成员

image.png

image.png

可以看出静态成员变量和普通静态变量其实存储的位置基本相同,都位于数据段(data segment),本质上来讲静态成员变量可以看做是被加了作用域和一些访问权限的全局静态变量

继承关系中的静态成员

由于静态成员变量并不存在于对象中而是位于数据段,且是程序运行中的独一份,所以打印两个地址应该相同

image.png

image.png

如果做以下更改打印结果便不相同了

image.png

image.png

单例

单例模式:设计模式的一种,保证某个类永远只创建一个对象

  • 构造函数\析构函数私有化

  • 定义一个私有的static成员变量指向唯一的那个单例对象

  • 提供一个公共的访问单例对象的接口

image.png

const 成员变量\函数

const成员:被const修饰的成员变量、非静态成员函数

const 成员变量

const成员变量:

  • 必须初始化且在类内部初始化,可以在声明的时候直接初始化赋值
  • staticconst成员变量还可以在初始化列表中初始化

image.png

const 成员函数

const 成员函数:

  • const关键字写在参数列表后面,函数的声明和实现都必须带const
  • 内部不能修改非static成员变量
  • 内部只能调用const成员函数或者static成员函数
  • const成员函数可以调用const成员函数
  • const成员函数和非const成员函数构成重载
  • const对象(指针)优先调用非const成员函数
  • const对象(指针)只能调用const成员函数、static成员函数

image.png

image.png

image.png

拷贝构造函数

  • 拷贝构造函数是构造函数的一种
  • 当利用已经存在的一个对象来创建一个新对象的时候,就会调用新对象的拷贝构造函数进行初始化
  • 拷贝构造函数的格式是固定的,接收一个const引用作为参数

使用默认的拷贝构造函数会将传入对象的成员变量的值拷贝与新的对象 image.png

根据汇编代码来看就是从对象c1的地址每次取4个字节然后将值赋予到对象c2中去,也就是将成员变量的值进行了拷贝

image.png

image.png

如果使用自定义的拷贝构造函数没有任何操作的话,不会拷贝任何成员变量

image.png

如果要进行赋值可直接使用初始化列表,因为拷贝构造函数也是构造函数

image.png

调用父类的拷贝构造函数

image.png

如果不写自定义的拷贝构造函数,将会把所有的值都拷贝到新对象中

image.png

拷贝构造函数注意点

image.png

Car c3 = c2等价于Car c3(c2)

c4 = c3这里仅仅是将c38个字节都拷贝到c4中,但是由于c3c4都是已经存在的对象所以并不符合拷贝构造函数的定义,且构造函数时一个对象进行初始化的时候来调用的

深拷贝与浅拷贝

以下这段代码是有严重的问题,在执行完test函数后,car对象已经被销毁,同时name数组的空间也被栈空间回收,但是之后依然在拿指针访问被堆空间回收的空间,同时也在访问栈空间回收的空间 image.png

更改的话首先应该将成员变量m_name指向的位置变为自己申请的堆空间,将数据拷贝进去,这样方便进行内存管理

image.png

如上图修改的话将不再涉及可能造成的野指针问题

由拷贝构造函数引发的问题

image.png

image.png

image.png

若由以上代码来执行会产生两个问题:

  • c1对象修改m_name会同步影响c2对象的m_name
  • 在销毁时可能会对m_name指针指向的空间释放两次

会造成以上问题的主要原因是这里使用的默认拷贝构造函数对于指针类型的成员变量进行赋值时仅仅是采用了浅复制,也就是只将原指针中的地址拷贝过来而已,所以两个对象的m_name指针指向了同一内存空间

浅拷贝:

  • 编译器默认提供的拷贝方法是浅拷贝
  • 将一个对象中所有成员变量的值拷贝到另一对象
  • 如果某个成员变量是个指针,只会拷贝指针中存储的地址值,并不会拷贝指针指向的内存空间

深拷贝:

  • 若想实现深拷贝则需要自定义拷贝构造函数
  • 将指针类型的成员变量所指向的空间拷贝到新的新的内存空间

所以上面的代码如下这样修改即可 image.png

查看变量的内存可以看到此时c1和c2的成员变量m_name指向的空间已经不是一块内存了

image.png

对象作为函数参数或返回值造成的问题

对象作为函数参数

image.png

如上图所示,将对象c1作为函数test1的参数进行输入时可以发现意外调用了拷贝构造函数,原因在于test1参数部分等价于Car car = c1

根据汇编来看的话也是如此

image.png

对象作为函数返回值

image.png

当把对象作为函数返回值时,同样会创建额外的对象,但是这里与作为参数不同的地方在于这里是因为在test2中创建的对象会拷贝一份到main函数的栈空间内,因为函数调用完成后栈空间会回收,所以提前拷贝了一份

隐式构造

C++中会存在隐式构造的现象:某些情况下,会隐式调用单参数的构造函数

先列出一个类的代码

image.png

根据之前的描述如果执行以下代码,应当是先调用无参构造函数,第二个是调用一个参数的构造函数,最后是调用拷贝构造函数

image.png

以上是我们熟悉的方法调用了,但是如果按Person p1 = 20来调用呢?按照之前的经验来看感觉并不合理

image.png

但是根据汇编来看如此调用等价于Person(20)这样来调用 image.png

那么隐式调用同样可以使用于函数参数和函数返回值

函数参数:

image.png

函数返回值:

image.png

explicit

使用隐式构造的优点是便于书写,很简洁,但是同样缺点是不易阅读,代码可读性变弱,所以如果不想让调用隐式构造则使用关键字explicit,可以禁止隐式构造的调用

这样使用即可 image.png

友元

友元包括:友元函数友元类

  • 友元函数:如果将函数A(非成员函数)声明为类B的友元函数,则函数A就可以访问类B对象的所有成员
  • 友元类:如果将类A声明为类B的友元类,则类A的所有成员函数都可以直接访问类B对象的所有成员

image.png

image.png

内部类

内部类:

  • 支持publicprotectedprivate权限
  • 成员函数可以访问其外部类对象的所有成员(反之则不行)
  • 成员函数可以直接不带类名、对象名访问其外部类的static成员
  • 不会影响外部类的内存布局
  • 可以在外部类内部声明,在外部类外面进行定义

image.png

内部类声明与实现分离

示例1:

image.png

示例2:

image.png

示例3:

image.png

局部类

局部类:

  • 在一个函数内部定义的类被称为局部类

  • 局部类的特点:

    • 作用域仅限于所在的函数内部
    • 其所有的成员必须定义在类内部,不允许定义static变量
    • 成员函数不能直接访问函数内部的局部变量(static变量除外)

image.png