我们之前就学过隐式类型转换,他会创建一个临时变量用来储存,这个临时对象具有常属性,下面我们首先来看一个我们之前学过的例子:
#include<iostream>
using namespace std;
int main()
{
int i = 0;
double d = i;
//这里不可以写成
double& ref = i;
//因为隐式类型转换会创建临时变量,临时变量具有常属性
//这样会造成权限放大,正确写法如下:
const double& ref = i;
return 0;
}之前我们都是内置类型转换成内置类型,下面我们来了解一下内置类型转换成类类型对象:
C++ 支持内置类型隐式类型转换为类类型对象,需要有相关内置类型为参数的构造函数。
下面我们来看代码解释(注意看注释):
#include<iostream>
using namespace std;
class A
{
public:
A(int a1)
:_a1(a1)
{}
private:
int _a1 = 1;
int _a2 = 2;
};
//C++在用类类型做形参的时候不建议用传值传参
//传值传参会调用拷贝构造是一种浪费
void func(const A& aa = 1)
//1为什么可以作为这里的缺省值?
//函数形参是const A& 类型,而1是int类型,C++ 会通过A类中以int为参数的构造函数
//将1隐式转换为临时的A对象,这个临时对象可以绑定到const引用上,因此1能作为缺省值。
{
//..
//这里我们传普通对象可以传过去但是传const对象就不可以
//所以我们尽量加上const
}
class Stack
{
public:
void Push(const A& a)
{}
};
int main()
{
//这是我们常说的用构造来初始化,直接构造
A a1(1);
//用整型去做参数可以构造一个A对象
//隐式类型转换:用int构造临时A对象,再拷贝构造给a2
//1会先去构造一个临时对象,临时对象再去拷贝构造给a2
A a2 = 1;
const A& ref1 = a1;
//引用的是中间产生的临时变量
const A& ref2 = 1;
func(a1);
func(1);
//这是我们之前学的往栈里面插入一个数据
//首先我们需要定义一个栈类型的变量st1
//接下来我们要去定义一个A类型的a3,并且构造为3
//最后我们便可以调用st1中栈里的函数Push,并且将a3传过去
Stack st1;
A a3(3);
st1.Push(a3);
//当我们学完隐式类型转换可以直接写成
//用int类型的3构造一个临时的A对象,再将这个临时对象传递给Push函数的形参
st1.Push(3);当然了编译器也会进行一定的优化:
#include<iostream>
using namespace std;
class A
{
public:
A(int a1)
:_a1(a1)
{ }
A(const A& aa)
{
cout << "A(const A & aa)" << endl;
}
private:
int _a1 = 1;
};
int main()
{
//构造
A a1(1);
//优化
//隐式类型转换
//2为参数构造临时对象,临时对象拷贝构造a2 -> 优化为直接构造
A a2 = 2;
//不会优化
const A& ref1 = 3;
return 0;
}这里
const A& ref1 = 3;不会被优化的原因是: C++ 的 “拷贝省略” 优化(如A a2 = 2;直接构造而非先临时对象再拷贝),针对的是对象初始化场景(用临时对象拷贝构造新对象);而const A& ref1 = 3;是引用绑定场景—— 这里的临时对象是为了绑定到const引用而创建的,它本身是一个 “右值临时对象”,C++ 标准要求这个临时对象必须存在(因为引用需要绑定到实际对象上),所以不会对 “临时对象的创建” 做优化。 简单总结:
A a2 = 2;:属于 “用临时对象拷贝构造新对象”,符合拷贝省略的优化条件;const A& ref1 = 3;:属于 “引用绑定临时对象”,临时对象必须存在,因此无优化。构造函数前面加 explicit 就不再支持隐式类型转换。
explicit关键字后,该构造函数将无法触发 “隐式类型转换”(即不能通过内置类型 / 其他类对象自动转换为当前类的对象)。A a = 1;(假设A有A(int)构造函数)可以隐式转换,加explicit后必须显式写A a(1);或A a = A(1);才合法。#include <iostream>
using namespace std;
// 无 explicit 修饰的类
class A {
public:
int _a;
// 普通构造函数(支持隐式转换)
A(int a) : _a(a) {
cout << "A(int a) 构造函数调用" << endl;
}
};
// 有 explicit 修饰的类
class B {
public:
int _b;
// explicit 修饰的构造函数(禁用隐式转换)
explicit B(int b) : _b(b) {
cout << "explicit B(int b) 构造函数调用" << endl;
}
};
int main() {
// 1. 无 explicit 的情况:支持隐式转换
A a1 = 10; // 合法:int 10 隐式转换为 A 类对象
A a2(20); // 显式构造(始终合法)
const A& refA = 30; // 合法:临时 A 对象绑定到 const 引用
cout << "a1._a: " << a1._a << ", a2._a: " << a2._a << ", refA._a: " << refA._a << endl;
// 2. 有 explicit 的情况:禁用隐式转换
// B b1 = 10; // 编译报错:无法从 int 隐式转换为 B
B b2(20); // 显式构造(仍合法)
// const B& refB = 30; // 编译报错:无法隐式转换生成临时 B 对象
const B& refB = B(30); // 合法:显式构造临时对象再绑定引用
cout << "b2._b: " << b2._b << ", refB._b: " << refB._b << endl;
return 0;
}#include<iostream>
using namespace std;
class A
{
public:
//单参数
A(int a1)
:_a1(a1)
{
cout << "A(int a1)" << endl;
}
A(const A& aa)
{
cout << "A(const A& aa)" << endl;
}
//多参数
A(int a1, int a2)
:_a1(a1)
,_a2(a2)
{ }
private:
int _a1 = 1;
int _a2 = 2;
};
//C++在用类类型做形参的时候不建议用传值传参
//传值传参会调用拷贝构造是一种浪费
void func(const A& aa = 1)
//1为什么可以作为这里的缺省值?
//函数形参是const A& 类型,而1是int类型,C++ 会通过A类中以int为参数的构造函数
//将1隐式转换为临时的A对象,这个临时对象可以绑定到const引用上,因此1能作为缺省值。
{
//..
//这里我们传普通对象可以传过去但是传const对象就不可以
//所以我们尽量加上const
}
class Stack
{
public:
void Push(const A& a)
{
}
};
int main()
{
//对于多参数
A a3(1, 1);
A a4 = { 1,1 };
//ref2引用的是中间的临时对象
const A& ref1 = { 1,1 };
}这段代码是多参数构造函数下的隐式类型转换场景,核心含义可以拆解为: 1. 多参数构造的两种写法
A a3(1, 1);:显式调用多参数构造函数创建对象;A a4 = {1,1};:C++11 及以后支持的列表初始化,本质是通过多参数构造函数进行隐式转换(用{1,1}构造临时A对象,再初始化a4)。 2. 引用绑定临时对象
const A& ref1 = {1,1}; 的逻辑是:
{1,1}隐式构造一个临时的A对象;const A&类型的引用ref1上;{1,1}构造出的临时A对象(因为引用必须绑定到实际对象,所以临时对象会被创建出来)。 补充说明
如果A的多参数构造函数被explicit修饰,那么A a4 = {1,1};和const A& ref1 = {1,1};都会编译失败(禁用了隐式转换),必须显式写A a4({1,1});或const A& ref1 = A{1,1};。
我们之前学的都是内置类型和内置类型之间的隐式类型转换;内置类型与类类型之间的隐式类型转换,下面我们来看第三种类类型之间的隐式类型转换:
类类型的对象之间也可以隐式转换,需要相应的构造函数支持。
#include<iostream>
using namespace std;
class A
{
public:
//单参数
A(int a1)
:_a1(a1)
{
cout << "A(int a1)" << endl;
}
A(const A& aa)
{
cout << "A(const A& aa)" << endl;
}
//多参数
A(int a1, int a2)
:_a1(a1)
,_a2(a2)
{ }
//不去修改的我们尽量带上const
int Get() const
{
return _a1 + _a2;
}
private:
int _a1 = 1;
int _a2 = 2;
};
class B
{
public:
B(const A& a)
:_b(a.Get())
{
}
private:
int _b = 0;
};
int main()
{
A a1(1, 1);
//A和B是不能转换的,就算是强制类型转换也不行
//那怎么样才能转换呢?
//有对应的构造即可
B b1 = a1;//这里是直接构造(编译器优化的结果)
const B& ref1 = a1;
}关键结论
B(const A& a));B b1 = a1; 是隐式转换,但编译器优化后直接构造,无临时对象和拷贝;const B& ref1 = a1; 是隐式转换生成临时 B 对象,再绑定引用(临时对象必须存在);explicit即可注:有一定关联才能转换,例如内置类型与内置类型间的转换,整型家族之间,整型和浮点数都表示数据大小;整型和指针之间,指针是地址的编号,但是浮点数和指针之间便不可以转换(没有关联关系)
#include<iostream>
using namespace std;
class A
{
public:
// 构造函数explicit就不再⽀持隐式类型转换
// explicit A(int a1)
A(int a1)
:_a1(a1)
{
}
//explicit A(int a1, int a2)
A(int a1, int a2)
:_a1(a1)
, _a2(a2)
{
}
void Print()
{
cout << _a1 << " " << _a2 << endl;
}
int Get() const
{
return _a1 + _a2;
}
private:
int _a1 = 1;
int _a2 = 2;
};
class B
{
public:
B(const A& a)
:_b(a.Get())
{
}
private:
int _b = 0;
};
int main()
{
// 1构造⼀个A的临时对象,再⽤这个临时对象拷⻉构造aa3
// 编译器遇到连续构造+拷⻉构造->优化为直接构造
A aa1 = 1;
aa1.Print();
const A& aa2 = 1;
// C++11之后才⽀持多参数转化
A aa3 = { 2,2 };
// aa3隐式类型转换为b对象
// 原理跟上⾯类似
B b = aa3;
const B& rb = aa3;
return 0;
}我们在C语言中就学过static,他的作用是让局部变量存静态区,还能限制全局变量 / 函数的作用域为当前文件;在C++中 static 可修饰变量 / 函数,核心是使其属于类而非单个对象,支持类域直接访问,且静态函数无 this 指针、仅能访问静态成员。
下面我们来看一段代码来初步了解一下static(注意看注释):
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
class A
{
public:
private:
int _a1 = 1;
int _a2 = 1;
//不存在对象当中,存在静态区的
//受到类域和访问限定符限制的专属的全局变量
//他的生命周期是全局的,不能给缺省值
static int _count;
};
class B
{
public:
private:
//声明
int _b1 = 1;
int _b2 = 1;
public:
//声明
static int _count;
};
//定义 -- 这时候可以初始化
int B::_count = 0;
int main()
{
A aa1;
cout << sizeof(aa1) << endl;
//我们看到sizeof结果是8,所以计算大小时,并没有加上count
//私有的不可以访问
cout << aa1._count << endl;
//公有的情况下指定类域就可以访问
B bb1;
cout << bb1._count << endl;
cout << B::_count << endl;
return 0;
}#include<iostream>
using namespace std;
class A
{
public:
A(int a = 0)
:_a1(a)
,_a2(a)
{
++_count;
}
A(const A& t)
{
++_count;
}
//没有this指针了,就不可以改变量了
static int GetCount()
{
//_a1++; 不能访问非静态成员,没有this指针
return _count;
}
private:
int _a1 = 1;
int _a2 = 1;
//public:
//声明
static int _count;
};
int A::_count = 0;
int main()
{
A aa1;
cout << aa1.GetCount() << endl;
cout << A::GetCount() << endl;
return 0;
}// 实现⼀个类,计算程序中创建出了多少个类对象?
#include<iostream>
using namespace std;
class A
{
public:
A()
{
++_scount;
}
A(const A& t)
{
++_scount;
}
~A()
{
--_scount;
}
static int GetACount()
{
return _scount;
}
private:
// 类⾥⾯声明
static int _scount;
};
// 类外⾯初始化
int A::_scount = 0;
int main()
{
cout << A::GetACount() << endl;
A a1, a2;
A a3(a1);
cout << A::GetACount() << endl;
cout << a1.GetACount() << endl;
// 编译报错:error C2248: “A::_scount”: ⽆法访问 private 成员(在“A”类中声明)
//cout << A::_scount << endl;
return 0;
}设已经有A,B,C,D4个类的定义,程序中A,B,C,D构造函数的调用顺序为()
设已经有A,B,C,D4个类的定义,程序中A,B,C,D析构函数的调用顺序为()
C c;
int main()
{
A a;
B b;
static D d;
return 0;
}答案:
构造顺序:CABD
析构顺序:BADC
解析:
全局变量在main之前就要初始化,这里C的初始化就要调用构造,所以C一定先构造,接下来进入main函数,我们要知道局部static对象,都是在第一次运行到定义位置时,才初始化,所以构造顺序就是CABD;析构顺序肯定B在A之前,因为析构顺序与构造顺序相反(同时定义在栈里的变量后定义的先析构),C是在main函数之前就初始化,所以C的析构应该是在main函数之后,D在静态区,而main函数结束之后局部的、静态的会先析构,故D在C之前(static D d;是局部静态变量,其生命周期是程序结束前,但会在main函数执行完毕后、全局变量析构之前析构)故析构顺序为BADC