文章目录
前言
- 一、类型转换
- 二、static
- 三、<< && >> 运算符重载与友元函数
- 四、内部类
- 五、匿名对象
- 六、对象拷贝时的编译器优化
前言
c++自定义类型类型转换、static定义、static成员变量与static成员函数、<< && >> 运算符重载与友元函数、内部类、匿名对象、对象拷贝时的编译器优化
一、类型转换
1.类型转换分类:
(1):强制类型转换
(double)1
强制类型转换语法: (类型)
(2):隐式类型转换
double a = 1;
将int类型的数据赋值给double类型的变量,会产生隐式类型转换
const double& a = 1;
使用double类型的const引用去引用int类型的数据,会产生隐式类型转换
2.类型转换的过程
(1):(double)1
编译器根据double(要转换的类型)开辟空间,
1(被转换的数据)转换到开辟的空间,形成临时对象
(2):double a = 1;
编译器根据double(要转换的类型)开辟空间
1(被转换的数据)转换到开辟好的空间,形成临时对象
再将临时对象赋值给a
(3):const double& a = 1;
编译器根据double(要转换的类型)开辟空间
1(被转换的数据)转换到开辟好的空间,形成临时对象
最终a引用的是临时对象
注:临时对象具有常性,所以要使用const引用
临时对象一旦被引用,临时对象的生命周期就跟随着引用走
3.内置类型转换自定义类型
(1):内置类型转换自定义类型的过程
编译器根据自定义类型(要转换的类型)开辟空间
使用内置类型构造这一块空间,形成临时对象
再使用临时对象拷贝构造自定义类型
编译器优化后:直接使用内置类型的数据构造自定义类型
所以内置类型转换为自定义类型必须有对应的构造函数
(2):单一内置类型转换为自定义类型
(3):多个内置类型转换为自定义类型
(4):内置类型转换为自定义类型的本质:
使用内置类型的数据构造自定义类型对象
所以要使得内置类型能够转换为自定义类型
需要有对应的构造函数
4.自定义类型1转换为自定义类型2
(1):自定义类型1转换为自定义类型2的过程
编译器根据自定义类型2开辟空间
再使用自定义类型1去构造这一块空间
最后再将这一块空间的数据拷贝构造自定义类型2
编译器优化后:使用自定义类型1构造自定义类型2
所以要实现自定义类型1转换为自定义类型2
必须拥有对应的构造函数
(2):自定义类型1转换为自定义类型2的本质
使用自定义类型1去构造自定义类型2
所以为了实现自定义类型1可以转换为自定义类型2
必须有对应的构造函数
5.关于自定义类型的转换都是通过构造函数完成
6.如果不想使得构造函数可以完成类型转换构造函数前面加explicit就不再支持类型转换
二、static
1.作用域和生命周期
(1):作用域
局部对象的作用域:局部对象所在的局部范围
全局对象的作用域:整个程序
(2):生命周期
局部对象的生命周期:进入局部对象的作用域生命周期开始
出了局部对象的作用域生命周期结束
全局对象的生命周期:整个程序
2.static作用
(1):static修饰局部变量
static修饰的局部变量会在静态区创建
static修饰的局部变量生命周期是整个程序
static修饰的局部变量作用域不变
static修饰的局部变量只会在执行到这一条语句时创建一次
使用场景:如果在函数内部、类域内部需要一个像全局变量一样出来作用域不销毁
并且值一直保留,就使用static修饰局部变量
(2):static修饰全局变量
static修饰的全局变量会由外部链接属性转换为内部链接属性
static修饰的全局变量只可以在本文件中使用
static修饰的全局变量不会进入符号表
(3):static修饰函数
static修饰的函数由外部链接属性转换为内部链接属性
static修饰的函数只可以在本文件中使用
static修饰的函数不会进入符号表
3.static成员变量
(1):类中可以声明static成员变量,static成员变量只可以在类外定义初始化
(2):static成员变量不是单独属于某一个对象
static成员变量属于整个类(可以理解为类的全局变量)
static成员变量属于全部的对象
(3):static成员变量存储在静态区
static成员变量生命周期是整个程序
static成员变量受类域和成员访问限定符的限制
(4):static成员变量属于类,不单独属于某个对象,属于全部对象
所以对象在开辟空间时,不会为static成员变量开辟空间
计算对象的大小也不会计算static成员变量
而且static成员变量存储在静态区,而对象在栈区
(5):a.因为static成员变量存储在静态区,而对象存储在栈区
所以static成员变量不属于某一个对象
但是static成员变量属于类,所以对象都可以访问static成员变量
所以static成员变量属于全部的对象,属于整个类
b. 因为static成员变量存储在静态区
所以static成员变量必须在类外定义初始化
在类内只可以使用构造函数的初始化列表和函数体
为对象初始化,static成员变量不属于某一个对象
所以static成员变量不可以出现在构造函数初始化列表和函数体
所以static成员变量只可以在类外通过指定类域进行定义初始化
c.因为static成员变量存储在静态区
所以static成员变量不可以给缺省值/默认值
给成员变量缺省值/默认值是成员变量在经历初始化列表时使用的
而static成员变量存储在静态区,不属于某一个对象
所以static成员变量不会走初始化列表
因此static成员变量不可以给缺省值/默认值
(6):使用场景
如果类中需要一个像全局变量一样生命周期整个程序,并且数据一直存在
那么就是用static成员变量,static成员变量可以更好的维护类的封装
4.static成员函数
(1):static成员函数没有this指针,所以static成员函数不可以访问普通的成员变量
(2):static函数可以访问static成员变量
因为static成员变量存储在静态区,static成员变量属于整个类
所以static成员函数可以访问static成员变量
(3):访问static成员函数使用对象.static成员函数 || 类域::static成员函数都可以
因为static成员函数属于类,所以对象.sataic成员函数是可以的
因为static成员函数没有this指针,所以static成员函数不需要对象也可以调用
static成员函数只需要指定类域就可以调用
普通的成员函数必须通过对象.成员函数进行调用
因为普通的成员函数拥有this指针,如果不是通过对象调用
那么普通成员函数的this指针就没办法确定
所以普通成员函数必须通过对象.成员函数进行调用
不同成员函数不可以通过指定类域进行调用
5.static局部变量和全局变量
(1):static局部变量和全局变量生命周期都是整个程序
static局部变量和全局变量都在静态区存储
(2):static局部变量受作用域限制
全局变量不受作用域限制
(3):全局变量在main函数之前创建
static局部变量在运行到static局部变量时创建
static局部变量只会创建一次
(4):函数栈帧销毁时,函数内部的局部变量会一起销毁
当main函数栈帧销毁时,main函数的局部变量会一起销毁
当main函数栈帧销毁完毕(main函数内部的局部变量销毁完毕时)
再去静态区销毁全局变量和static局部变量
有限销毁static局部变量再销毁全局变量
三、<< && >> 运算符重载与友元函数
1.所需运算符重载特性:
(1):运算符重载函数可以给运算符一个新的含义
(2):运算符重载函数只能重载已经存在的运算符
运算符重载函数不可以定义新的运算符
(3):运算符重载函数是为了使得自定义类型的对象也可以使用运算符
运算符重载函数参数中至少有一个是自定义类型的对象
(4):二元运算符重载函数的参数,第一个参数 == 左操作数 第二个参数 == 右操作数
2.cout和cin
(1):cout是ostream类型的全局对象
cin 是istream类型的全局对象
(2):<< 是在ostream类型中运算符重载函数重载过后的
>>是在istream类型中运算符重载函数重载过后的
(运算符重载函数可以给运算符一个新含义)
(3):cout << i == cout.operator<<(i)
之所以cout和cin自动识类型是因为函数重载
3.自定义类型的<< && >> 运算符重载函数
(1):自定义类型的<< && >> 运算符重载函数不推荐写成成员函数
因为<< && >> 运算符重载函数右两个参数
第一个参数是ostream/istream类型对象的引用,第二个参数是自定义类型的引用
如果重载成自定义类型的成员
第一个参数 == this指针 == 自定义类型的对象
不符合,c++运算符重载函数的参数位置是写死的
(第一个参数:左操作数 第二个参数:右操作数)
(2):自定义类型的<< && >> 运算符重载函数推荐写成全局函数
因为<< && >> 运算符重载函数
第一个参数必须是ostream/istream类型对象的引用
第二个参数必须是自定义类型对象的引用
写成全局函数参数就可以使得第一个参数是ostream/istream类型对象的引用
第二个参数是自定义类型对象的引用
(3):当将自定义类型的<< && >> 运算符重载函数写成全局函数之后,
在类外自定义类型的私有是不可以被访问的
<< && >> 运算符重载函数多多少少会使用到自定义类型的私有
那么此时就可以将<< && >> 运算符重载函数在自定义类型中声明成友元全局函数
4.友元
(1):友元的作用
友元提供了一种突破成员访问限定符限制的方式
在类外的函数/类想使用类的私有就可以在类中声明友元函数和友元类
在类外像使用类的私有就可以使用友元
(2):友元函数/友元类的声明
在类中任意位置声明
friend 返回类型 函数名 (参数);
friend class 类名;
(3):友元函数可以访问类的私有/保护成员
友元函数仅仅是一种声明,友元函数不是类的成员函数
(4):友元可以在类中的任意地方声明,不受类的成员访问限定符的限制
(5):一个函数可以是多个类的友元函数
(6):类的成员函数也可以是另一个类的友元函数
可以访问另一个类的私有/保护
(7):友元类的关系是单向的,不具有交换性
A类是B类的友元,但是B类不是A类的友元
(8):友元关系不具有传递性
A类是B类的友元 B类是C类的友元 A类不是C类的友元
(9):友元会增加耦合度,友元会破坏封装不宜多用
四、内部类
1.内部类的概念
在类中定义一个类,那么该类称作内部类
2.理解内部类的方式
使用理解域的思维理解内部类
3.内部类和外部类是两个独立的类
内部类只是收到外部类的类域和类的成员访问限定符的限制
外部类的对象不会包含内部类,外部类和内部类是像个独立的类
4.内部类默认是外部类的友元类,内部类可以访问外部类的私有/保护
外部类不可以访问内部类的私有/保护
5.如果内部类放置在外部类的public位置,那么只需要突破类域使用域作用限定符就可以使用
如果内部类放置是外部类的priveat/protected位置
那么该内部类就是外部类的专属内部类,不可以在其他地方使用
五、匿名对象
1.对象分类:
有名对象、临时对象、匿名对象
2.有名对象
类型 对象名();
有名对象的生命周期 == 作用域
3.临时对象(临时对象是由编译器自动生成的,临时对象具有常性)
类型转化、传值返回、表达式运算中都会产生临时对象
临时对象的生命周期:当前产生临时对象的语句指向完毕后,临时对象销毁
当临时对象被引用时,临时对象的生命周期 == 引用的生命周期
临时对象的引用使用const引用
4.匿名对象(匿名对象是由程序员自己生成的,匿名对象具有常性)
类型(参数);
匿名对象的生命周期:执行匿名对象的这一行
开始执行匿名对象这一行代码时,匿名对象创建,执行完匿名对象这一行代码时,
匿名对象销毁
当匿名对象被引用时,匿名对象的生命周期 == 引用的生命周期
匿名对象的引用使用const引用
如果当前只需要定义一个对象用一下即可,就可以使用匿名对象
例:只需要一个对象用来调用成员函数,就可以使用匿名对象直接调用
六、对象拷贝时的编译器优化
1.现代编译器为了尽可能提升程序的效率,在不影响正确的情况下
会尽可能减少一些传参和传值返回过程中的拷贝
2.如何优化c++没有规定
主流:
(1):一个表达式中连续拷贝会进行合并优化
(2):跨行跨表达式优化
3.详情:
(1):传值传参 构造 + 拷贝构造
(2):隐式类型转换 构造 + 拷贝构造 ---> 直接构造
(3):一个表达式中,连续的构造 + 拷贝构造 ---> 优化为一个构造
(4):构造/拷贝构造 + 赋值运算符重载 == 无法优化