[C++] 类和对象(4): 初始化列表、构造函数细节、static成员、友元、内部类...
一、构造函数补充
1.1 初始化列表
默认构造函数
, 是对象定义(实例化)时, 自动调用对 对象成员变量进行初始化的函数对对象整体的定义
, 构造函数的内容是对象成员变量的赋值
对象的成员变量是在哪里定义的?
初始化列表
. 对象成员变量的定义就是在这里实现的1.1.1 什么是初始化列表
初始化列表
, 是位于构造函数 ()
之下,{}
之上, 定义对象成员变量的一个列表.
具体的位置是在这里:冒号:
开始, 以逗号,
分割成员变量, 成员变量以此形式位列其中: 成员变量名(初始化内容)
当调用默认构造函数, 但不进入默认构造函数内容时, 成员变量就已经定义好了
成员变量在初始化列表中未显式定义时, 编译器也是会自动经过初始化列表定义的, 但是未作初始化
这也是
编译器自动生成的默认构造函数对内置类型变量不做处理, 对自定义类型调用其默认构造函数处理
的原因默认构造函数都是编译器自动生成的, 初始化列表肯定也是自动生成的 自动生成的初始化列表只对内置类型成员变量进行定义, 而不初始化
初始化列表中未显式定义的成员变量, 编译器也会自动定义
1.1.2 初始化列表的用途
对于一般的内置类型
, 好像没有必要使用初始化列表进行赋初值, 赋初值的操作在构造函数内部也能够实现
那么, 初始化列表是不是就没有用呢?不是所有的变量都可以定义和赋值分离的
const修饰的
和&引用类型的
很显然定义与赋值是不能分离的
, 定义时必须初始化
类A的对象
由于没有默认构造函数, 显然也是必须传参才能实例化的必须要显式在初始化列表进行定义并初始化
的缺省值就是给初始化列表使用的
:1.1.3 初始化列表执行顺序
输出结果
是什么?1
和 随机值
_a1
的定义在_a2
之前, 为什么_a1
有数值 _a2
是随机值?成员变量的初始化顺序是按照声明顺序来的, 并不是变量在初始化列表中的编写顺序
_a2
先初始化为_a1
, _a1
后初始化, 所以 _a1
有数值, _a2
是随机值1.2 构造函数中的隐式类型转换 及 explicit
隐式类型转换的过程
这种方式其实是先将 2022
使用构造函数构造了一个临时对象
, 再将这个临时对象
拷贝构造至c2
(编译器未优化)
如果编译器对这个过程进行了优化, 那么就直接是 对c2
进行构造了explicit
修饰
可以禁止隐式类型转换:1.2.1 构造函数中隐式类型转换的意义
直接 类引用数值呢
?const
关键词修饰:const
修饰就会报错:直接使用数值进行对象实例化, 数值会先构造成一个临时对象, 临时对象其实是具有常性的
如果不用const
修饰就加以引用, 其实是一种权限放大的操作, 是错误的另外,
给临时对象起别名, 会使临时对象的生命周期延长至别名生命周期结束
直接使用数值(常量)是可以进行对象实例化的, 也就同时意味着可以直接使用常量 传参, 但是参数的声明需要用
const
修饰PS: 直接使用常量传参在
string
中, 非常有意义:
二、static 修饰类成员
static
在类内使用, 用来修饰成员变量或者成员函数, 被修饰的成员会被存放入静态区
static
的类成员称为类的静态成员
用static
修饰的成员变量, 称之为静态成员变量
用static
修饰的成员函数, 称之为静态成员函数
2.1 static 修饰成员变量
static
修饰 成员变量, 与其修饰普通变量的用法不同static
修饰成员变量时, 需要在类外手动定义(不用加static
)之后才能使用成员变量 是
在对象实例化时, 才会通过初始化列表定义的
。类中只是成员变量的声明, 并不是定义并且,
静态成员变量
是不通过初始化列表定义的初始化列表的作用是, 定义每个对象的成员变量, 使对象成功实例化。每个对象都有其各自的成员变量
而
静态成员变量
是被static
修饰的成员变量, 是不能频繁被定义的, 只能被定义一次 所以并不是每个对象都有各自的静态成员变量, 静态成员变量整个类中只有一个
所以静态成员变量
的定义不经过初始化列表, 而是需要在类外手动定义经过定义的
静态成员变量
属于整个类
即,属于此类的所有对象都可以访问类的公用的静态成员变量
默认构造函数
, 静态成员变量_x
自增1_x
自增四次, 所以四个对象访问的_x
都是 4
_x
也是没问题的可以证明,
静态成员变量不属于任何一个对象, 而是属于整个类
2.2 static 修饰成员函数
一般都会将成员变量设为私有, 包括静态成员变量
静态成员变量设置为私有的话, 是无法通过某个对象或类来直接访问的
:静态成员变量
通常会通过函数来专门操作静态成员变量
, 它是可以在没有对象的情况下通过类来访问的
而一般的成员函数只能通过对象来调用
为了可以 直接通过类调用成员函数, 可以在成员函数前加上 static
进行修饰, 被称为 静态成员函数
静态成员函数 可以通过类直接调用
静态成员函数 只能操作静态成员变量
静态成员函数, 只能访问静态成员变量, 不能访问非静态成员变量
, 因为静态成员函数没有 this指针
三、友元
友元
分为: 友元函数
、友元类
友元函数或友元类, 即使定义在类外部, 也可以操作类的成员
会破坏类的封装, 会提高类的耦合度
。而类追求的是 低耦合,高内聚
, 所以友元能少用就少用3.1 友元函数
最好在必要情况下在使用友元, 否则不要轻易使用
3.1.1 日期类 >>、<< 重载
流提取>>
、流插入<<
这两个运算符重载, 进而实现类似与内置类型一般 cin 直接输入
和cout 直接输出
的功能cout
与 cin
的功能:cout <<
和 cin >>
, 其中的 <<
和 >>
也是重载, 因为其原本的意义应该是<< 按位左移
、>> 按位右移
cout
和 cin
就属于操作数, cout
和 cin
是什么类型的操作数呢?cout
属于 ostream类
, cin
属于 istream类
流插入<<
和 流提取>>
应该这样定义重载函数:ostream&
和 istream&
作为类型, 分别取 _cout
、_cin
作 cout
、cin
的别名运算符重载默认, 第一个参数为左操作数, 第二个参数为右操作数
所以 对象d
需作左操作数, ostream 类型的 cout
需作右操作数ostream 类型的 cout
作为左操作数
所以, 就只能在类外重载>>
和 <<
运算符在类内, 非静态成员函数 默认第一个参数是
this指针
, 这个规则是无法修改的 所以, 要想实现正常的cout
和cin
, 那么只能在类外实现>>
和<<
的重载函数
友元函数
就可以了!>>
和 <<
的重载, 想要正常使用两个运算符, 就必须使用友元:友元函数
关键词friend
重新声明的函数友元函数
有几个规则需要注意:
- 友元函数, 虽在类内声明, 并且可以访问类内私有和保护的成员, 但
友元函数不属于类的成员函数
- 一个函数, 可以同时作为多个类的友元函数
- 友元函数不能被
const
修饰 因为友元函数没有 this指针
- 友元函数可以在内类任意位置声明, 且不受类访问限定符的限制
3.2 友元类
友元类
也就非常容易理解了类可以作为另一个类的友元类
将日期类作为时间类的友元类
PS: 友元类只有单向性: 例如,
Date类 作为 Time类 的友元类, 使 Date类的成员函数 可访问 Time类的成员, 但是 Time类的成员函数 是不能访问 Time类的成员的
四、内部类
内部类
, 顾名思义就是 定义在类内部的类
内部类是一个独立的类, 它
不属于外部类
, 所以外部类的成员函数无法访问内部类的成员
并且,
内部类就是外部类的友元类
, 但 外部类不是内部类的友元类
内部类可以定义在外部类的
public
、private
、protected
, 并且受这些访问限定符的限制内部类可以直接访问外部类的 static修饰成员、枚举成员等,
不需要 类名或对象
对右内部类的类使用
sizeof()
, 结果是只有外部类的大小, 不算内部类
。 因为内部类是独立的, 并不属于外部类
sizeof(A)
不计算 类B的大小
作者: 哈米d1ch 发表日期:2022 年 6 月 28 日