[C++——lesson10.explicit关键字]

前言

          在我们自己平时写 C++ 代码的时候,较少会用到 explicit关键字 。但是C++相关的标准类库中,看到explicit关键字的频率还是很高的既然出现的频率这么高,那么我们就来看看explicit关键字的作用到底是干什么的。

一、explicit关键字是什么? 

 explicit是C++中的一个关键字,它用来修饰只有一个参数的类构造函数,以表明该构造函数是显式的,而非隐式的。当使用explicit修饰构造函数时,它将禁止类对象之间的隐式转换,以及禁止隐式调用拷贝构造函数,让代码更安全、更具可读性。 
       这能这么说,大家不太好理解,既然解释中提到了 类的构造函数  那么下面我将从构造函数中详细的给大家,讲解explicit其中的含义。

二、构造函数还具有类型转换的作用

         在理解 explicit 关键字 之前,我们必须要了解构造函数的类型转换作用,以便于我们更好的理解 explicit 关键字 ,如果有不懂构造函数的朋友,可以来看看这篇文章:[C++类的默认成员函数——lesson5.构造函数&&析构函数]

2.1📖单参构造函数

还是来说说老朋友日期类,我们通过下面这个日期类进行讲解

class Date
{
public:
// 构造函数
	Date(int year)
		:_year(year)    // 初始化列表
	{}
 
private:
	int _year;
	int _month = 3;
	int _day = 31;
};
  • 对于下面的 d1 很清楚一定是调用了有参构造进行初始化,不过对于 d2 来说,也是一种构造方式
int main()
{
    // d1 和 d2 都会调用构造函数
	Date d1(2022);   
 
	Date d2 = 2023;
	
	return 0;
}
  • 我们依旧通过调试来看就会非常清晰,这种 【Date d2 = 2023】 写法也会去调用构造函数

          此时,大家可能会产生疑问,这种构造方式从来没有见过,为什么 【Date d2 = 2023】会调用 构造函数呢?  其实这都是因为有【隐式类型转换】的存在,下面我将从一个简单的例子来为大家讲解。

  • [C++——lesson3.引用&],我有提到过【隐式类型转换】这个概念,像下面将一个int类型的数值赋值给到一个double类型的数据,此时就会产生一个隐式类型转换
int i = 1;
double d = i;
  • 对于类型转换而言,这里并不是将值直接赋值给到左边的对象,而是在中间呢会产生一个临时变量例如右边的这个 i 会先去构造一个临时变量,这个临时变量的类型是 [double] 。把它里面的值初始化为 1,然后再通过这个临时对象进行拷贝构造给d这就是编译器会做的一件事

  • 那对于这个 d2 其实也是一样,2023会先去构造一个临时对象,这个临时对象的类型是[Date]把它里面的year初始化为2023,然后再通过这个临时对象进行拷贝构造给到d2

 💬小蛋:不是说构造函数有初始化列表吗?拷贝构造怎么去初始化呢?

//拷贝构造
Date(const Date& d)
	:_year(d._year)
	,_month(d._month)
	,_day(d._day)
{}
  •   同学,别忘了【拷贝构造】也是属于构造函数的一种哦,也是会有初始化列表的

         刚才说到了中间会产生一个临时对象,而且会调用构造 + 拷贝构造,那此时我们在Date类中写一个拷贝构造函数,调试再去看看会不会去进行调用

  • 很明显没有,我在进入Date类后一直在按F11,但是却进不到拷贝构造中,这是为什么呢?

  •  原因其实在于编译器在这里地方做了一个优化,将【构造 + 拷贝构造】优化成了【一个构造】,因为编译器在这里觉得构造再加拷贝构造太费事了,干脆就合二为一了。其实对于这里的优化不同编译器是有区别的,像一下VC++、DevC++可能就不会去优化,越是新的编译器越可能去进行这种优化。在本文的最后一个模块我还会详细展开分析

💬小蛋:但您是怎么知道中间赋值这一块产生了临时对象呢?如果不清楚编译器的优化机制这一块肯定就会认为这里只有一个构造

  • 这点确实是,若是我现在不是直接赋值了,而是去做一个引用,此时会发生什么呢?
Date& d3 = 2024;
  • 可以看到,报出了一个错误,原因就在于d3是一个Date类型,2024则是一个内置类型的数据

  • 但若是我在前面加一个const做修饰后,就不会出现问题了,这是为什么呢?
  • 其实这里的真正原因就在于产生的这个【临时变量】(临时变量具有常性),它就是通过Date类的构造函数构造出来的,同类型之间可以做引用。还有一点就是临时变量具有常性,所以给到一个const类型修饰对象不会有问题 

三、✨ 引出 explicit 关键字 

         但若是你不想让这种隐式类型转换发生怎么办呢?此时就可以使用到C++中的一个关键字叫做explicit         

  • 它加在构造函数的前面进行修饰,有了它就不会发生上面的这一系列事儿了,它会【禁止类型转换】
explicit Date(int year)
	:_year(year)
{}


3.1🧐多参构造函数

对于上面所讲的都是基于单参的构造函数,接下去我们来瞧瞧多参的构造函数 

//多参构造函数
Date(int year, int month ,int day = 31)
	:_year(year)
	,_month(month)
	,_day(day)
{}
  • 根据从右往左缺省的规则,我们在初始化构造的时候要给到2个参数,d1没有问题传入了两个参数,但是若是像上面那样沿袭单参构造函数这么去初始化还行得通吗?很明显不行,编译器报出了错误

小蛋:那要怎么办呀,对于一定要传入多参数进行构造的场景

  •  这个时候就要使用到我们C++11中的新特性了,在对多参构造进行初始化的时候在外面加上一个{}就可以了,可能你觉得这种写法像是C语言里面结构体的初始化,但实际不是,而是在调用多参构造函数
Date d2 = { 2023, 3 };

  • 不仅如此,对于下面这种也同样适用,调用构造去产生一个临时对象
const Date& d3 = { 2024, 4 };
  •  那要如何去防止这样的隐式类型转换发生呢,还是可以使用到explicit关键字吗?
//多参构造函数
explicit Date(int year, int month ,int day = 31)
	:_year(year)
	,_month(month)
	,_day(day)
{}
  • 可以看到,加上explicit关键字做修饰,同样可以起到【禁止类型转换】的作用

  • 还有一种例外,当缺省参数从右往左给到两个的时候,此时只需要传入一个实参即可,那也就相当于是单参构造explicit关键字依旧可以起到作用·
explicit Date(int year, int month = 3,int day = 31)
	:_year(year)
	,_month(month)
	,_day(day)
{}

四、✨为什么需要explicit关键字? 

        在 C++ 中,explicit关键字主要用于修饰类的构造函数,目的是防止隐式类型转换,提高代码的安全性和可读性。具体原因如下:

  • 防止意外的类型转换:当类的构造函数只接受一个参数时,如果没有标记为explicit,则该构造函数可以被用于隐式类型转换,这可能导致意外的行为和不必要的对象创建。例如std::vector<int> v = 10;,可能被误认为是初始化 10 个元素,实际是分配容量为 10,使用explicit可避免此类问题。
  • 明确指定对象创建方式:某些类可能有多个构造函数,每个构造函数参数类型和含义不同。若某个构造函数标记为explicit,则需显式调用它来创建对象,能避免使用者产生歧义和错误选择,让代码意图更清晰。
  • 避免不必要的性能开销:隐式类型转换可能会导致不必要的对象创建,从而产生性能损失。通过使用explicit关键字,强制要求使用者显式地处理类型转换,可避免隐式转换带来的性能开销。
  • 提高代码的可读性和可维护性:使用explicit关键字可以明确地传达类的设计意图,提醒开发者在使用构造函数时要显式创建对象,使代码行为更易理解和维护,减少产生意外结果的可能性。

五、✨ 怎么使用explicit关键字?

  • 修饰单参数构造函数:当类的构造函数只有一个参数时,若不使用explicit,编译器会允许隐式类型转换。加上explicit后,将禁止这种隐式转换,必须显式调用构造函数来创建对象。例如:
class MyString {
public:
    explicit MyString(int size) { /* 根据大小创建字符串 */ }
};
void printString(const MyString& s) {}
int main() {
    MyString s1(10); // 正确:显式调用构造函数
    MyString s2 = 10; // 错误:explicit禁止隐式转换
    printString(10); // 错误:explicit禁止隐式转换
}
  • 修饰多参数构造函数(C++11 起):C++11 支持初始化列表,多参数构造函数也可能用于隐式转换,此时可用explicit禁止。例如:
class Point {
public:
    explicit Point(int x, int y) : x_(x), y_(y) {}
private:
    int x_, y_;
};
void drawPoint(const Point& p) {}
int main() {
    Point p1{1, 2}; // 正确:直接初始化
    Point p2 = {1, 2}; // 错误:explicit禁止隐式初始化列表转换
    drawPoint({3, 4}); // 错误:explicit禁止隐式转换
}
  • 修饰转换运算符(C++11 起)explicit也可用于转换运算符,防止类对象隐式转换为其他类型。例如:
class SafeBool {
public:
    explicit operator bool() const { return true; }
};
int main() {
    SafeBool sb;
    if (sb) { // 正确:上下文转换为bool(如条件语句)
        //...
    }
    bool b1 = sb; // 错误:explicit禁止隐式转换
    bool b2 = static_cast<bool>(sb); // 正确:显式转换
}

总结 

结束语

以上就是我对C++ explicit关键字的理解

感谢你的三连支持!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值