static
-
静态成员
:被static
修饰过的成员变量\函数 -
访问方式:
- 通过对象,如:
对象.静态成员
- 通过类访问,如:
类名::静态成员
- 通过指针,如:
对象指针->静态成员
- 通过对象,如:
静态成员
-
静态成员变量
:- 存储在数据段,即全局区,类似于全局变量,整个程序运行过程中只有一份内存
- 对比全局变量它可以设定访问权限(public、private、protected),达到局部共享的目的
- 必须初始化,且必须在类外面初始化,初始化时不能带static,如果类的声明和实现分离则需要在
.cpp
文件中初始化
静态成员函数
静态成员函数:
- 内部不能使用
this
指针,this
指针只能用于非静态成员函数内部,因为静态成员函数可以被类直接调用,所以this
指针无意义 - 不能是虚函数(虚函数只能是非静态成员函数),因为虚函数的意义在于实现多态,同样是对象来调用
- 内部不能访问非静态成员变量\函数,只能访问静态成员变量\函数
- 非静态成员函数内部可以访问静态成员变量\函数
- 构造函数、析构函数不能是静态
- 当声明和类实现分离时,实现部分不能带
static
汇编分析静态成员
可以看出静态成员变量和普通静态变量其实存储的位置基本相同,都位于数据段(data segment)
,本质上来讲静态成员变量可以看做是被加了作用域和一些访问权限的全局静态变量
继承关系中的静态成员
由于静态成员变量并不存在于对象中而是位于数据段,且是程序运行中的独一份,所以打印两个地址应该相同
如果做以下更改打印结果便不相同了
单例
单例模式
:设计模式的一种,保证某个类永远只创建一个对象
-
构造函数\析构函数私有化
-
定义一个私有的static成员变量指向唯一的那个单例对象
-
提供一个公共的访问单例对象的接口
const 成员变量\函数
const
成员:被const
修饰的成员变量、非静态成员函数
const 成员变量
const
成员变量:
- 必须初始化且在类内部初始化,可以在声明的时候直接初始化赋值
- 非
static
的const
成员变量还可以在初始化列表中初始化
const 成员函数
const
成员函数:
const
关键字写在参数列表后面,函数的声明和实现都必须带const
- 内部不能修改非
static
成员变量 - 内部只能调用
const
成员函数或者static
成员函数 - 非
const
成员函数可以调用const
成员函数 const
成员函数和非const
成员函数构成重载- 非
const
对象(指针)优先调用非const
成员函数 const
对象(指针)只能调用const
成员函数、static成员函数
拷贝构造函数
拷贝构造函数
是构造函数的一种- 当利用已经存在的一个对象来创建一个新对象的时候,就会调用新对象的拷贝构造函数进行初始化
拷贝构造函数
的格式是固定的,接收一个const
引用作为参数
使用默认的拷贝构造函数
会将传入对象的成员变量的值拷贝与新的对象
根据汇编代码来看就是从对象c1
的地址每次取4
个字节然后将值赋予到对象c2
中去,也就是将成员变量的值进行了拷贝
如果使用自定义的拷贝构造函数
没有任何操作的话,不会拷贝任何成员变量
如果要进行赋值可直接使用初始化列表,因为拷贝构造函数
也是构造函数
调用父类的拷贝构造函数
如果不写自定义的拷贝构造函数,将会把所有的值都拷贝到新对象中
拷贝构造函数注意点
Car c3 = c2
等价于Car c3(c2)
c4 = c3
这里仅仅是将c3
的8
个字节都拷贝到c4
中,但是由于c3
和c4
都是已经存在的对象所以并不符合拷贝构造函数的定义,且构造函数时一个对象进行初始化的时候来调用的
深拷贝与浅拷贝
以下这段代码是有严重的问题,在执行完test函数后,car对象已经被销毁,同时name数组的空间也被栈空间回收,但是之后依然在拿指针访问被堆空间回收的空间,同时也在访问栈空间回收的空间
更改的话首先应该将成员变量m_name指向的位置变为自己申请的堆空间,将数据拷贝进去,这样方便进行内存管理
如上图修改的话将不再涉及可能造成的野指针问题
由拷贝构造函数引发的问题
若由以上代码来执行会产生两个问题:
c1
对象修改m_name
会同步影响c2
对象的m_name
- 在销毁时可能会对
m_name
指针指向的空间释放两次
会造成以上问题的主要原因是这里使用的默认拷贝构造函数对于指针类型的成员变量进行赋值时仅仅是采用了浅复制
,也就是只将原指针中的地址拷贝过来而已,所以两个对象的m_name
指针指向了同一内存空间
浅拷贝
:
- 编译器默认提供的拷贝方法是浅拷贝
- 将一个对象中所有成员变量的值拷贝到另一对象
- 如果某个成员变量是个指针,只会拷贝指针中存储的地址值,并不会拷贝指针指向的内存空间
深拷贝
:
- 若想实现深拷贝则需要自定义拷贝构造函数
- 将指针类型的成员变量所指向的空间拷贝到新的新的内存空间
所以上面的代码如下这样修改即可
查看变量的内存可以看到此时c1和c2的成员变量m_name指向的空间已经不是一块内存了
对象作为函数参数或返回值造成的问题
对象作为函数参数
如上图所示,将对象c1作为函数test1的参数进行输入时可以发现意外调用了拷贝构造函数,原因在于test1参数部分等价于Car car = c1
根据汇编来看的话也是如此
对象作为函数返回值
当把对象作为函数返回值时,同样会创建额外的对象,但是这里与作为参数不同的地方在于这里是因为在test2
中创建的对象会拷贝一份到main
函数的栈空间内,因为函数调用完成后栈空间会回收,所以提前拷贝了一份
隐式构造
C++
中会存在隐式构造的现象:某些情况下,会隐式调用单参数的构造函数
先列出一个类的代码
根据之前的描述如果执行以下代码,应当是先调用无参构造函数,第二个是调用一个参数的构造函数,最后是调用拷贝构造函数
以上是我们熟悉的方法调用了,但是如果按Person p1 = 20
来调用呢?按照之前的经验来看感觉并不合理
但是根据汇编来看如此调用等价于Person(20)
这样来调用
那么隐式调用同样可以使用于函数参数和函数返回值
函数参数:
函数返回值:
explicit
使用隐式构造的优点是便于书写,很简洁,但是同样缺点是不易阅读,代码可读性变弱,所以如果不想让调用隐式构造则使用关键字explicit
,可以禁止隐式构造的调用
这样使用即可
友元
友元
包括:友元函数
和友元类
友元函数:
如果将函数A(非成员函数)
声明为类B
的友元函数,则函数A
就可以访问类B
对象的所有成员友元类:
如果将类A
声明为类B
的友元类,则类A
的所有成员函数都可以直接访问类B
对象的所有成员
内部类
内部类:
- 支持
public
、protected
、private
权限 - 成员函数可以访问其外部类对象的所有成员(反之则不行)
- 成员函数可以直接不带类名、对象名访问其外部类的
static
成员 - 不会影响外部类的内存布局
- 可以在外部类内部声明,在外部类外面进行定义
内部类声明与实现分离
示例1:
示例2:
示例3:
局部类
局部类:
-
在一个函数内部定义的类被称为局部类
-
局部类的特点:
- 作用域仅限于所在的函数内部
- 其所有的成员必须定义在类内部,不允许定义
static
变量 - 成员函数不能直接访问函数内部的局部变量(
static
变量除外)