Explicit Constructors(显式构造函数)

Explicit Constructors(显式构造函数)收藏


 
按照默认规定,只有一个参数的构造函数也定义了一个隐式转换,将该构造函数对应数据类型的数据转换为该类对象,如下面所示:
class String {
String ( const char* p );  // 用C风格的字符串p作为初始化值
//…
}
String s1 = “hello”; //OK 隐式转换,等价于String s1 = String(“hello”);
 
但是有的时候可能会不需要这种隐式转换,如下:
class String {
       String ( int n ); //本意是预先分配n个字节给字符串
String ( const char* p );  // 用C风格的字符串p作为初始化值
//…
}
 
下面两种写法比较正常:
String s2 ( 10 );    //OK 分配10个字节的空字符串
String s3 = String ( 10 ); //OK 分配10个字节的空字符串
 
下面两种写法就比较疑惑了:
String s4 = 10; //编译通过,也是分配10个字节的空字符串
String s5 = ‘a’; //编译通过,分配int(‘a’)个字节的空字符串
 
s4 和s5 分别把一个int型和char型,隐式转换成了分配若干字节的空字符串,容易令人误解。
为了避免这种错误的发生,我们可以声明显示的转换,使用 explicit 关键字:
class String {
       explicit String ( int n ); //本意是预先分配n个字节给字符串
String ( const char* p );  // 用C风格的字符串p作为初始化值
//…
}
加上 explicit,就抑制了String ( int n )的隐式转换,
 
下面两种写法仍然正确:
String s2 ( 10 );    //OK 分配10个字节的空字符串
String s3 = String ( 10 ); //OK 分配10个字节的空字符串
 
下面两种写法就不允许了:
String s4 = 10; //编译不通过,不允许隐式的转换
String s5 = ‘a’; //编译不通过,不允许隐式的转换
 
因此,某些时候, explicit 可以有效得防止构造函数的隐式转换带来的错误或者误解

----------------------------------------------------------
explicit   只对构造函数起作用,用来抑制隐式转换。如:  
  class   A   {  
          A(int   a);  
  };  
  int   Function(A   a);  
   
  当调用   Function(2)   的时候,2   会隐式转换为   A   类型。这种情况常常不是程序员想要的结果,所以,要避免之,就可以这样写:  
   
  class   A   {  
          explicit   A(int   a);  
  };  
  int   Function(A   a);  
   
  这样,当调用   Function(2)   的时候,编译器会给出错误信息(除非   Function   有个以   int   为参数的重载形式),这就避免了在程序员毫不知情的情况下出现错误。
### 构造函数重载的用法和规则 在C++中,构造函数重载是一种常见的编程实践,允许类具有多个构造函数,这些构造函数可以接受不同数量或类型的参数。通过这种方,开发者可以根据不同的需求初始化对象。 #### 1. 基本概念 构造函数重载是指在一个类中定义多个构造函数,它们的区别在于参数列表的不同。这使得可以通过多种方创建类的对象[^1]。例如: ```cpp class Foo { public: Foo(); // 默认构造函数 Foo(int value); // 接受一个整数参数的构造函数 }; ``` 上述代码展示了两个构造函数:一个是默认构造函数 `Foo()`,另一个是带有单个整型参数的构造函数 `Foo(int value)`。 --- #### 2. 使用成员初始化列表调用其他构造函数 当一个构造函数需要调用同一个类中的另一个构造函数时,可以使用成员初始化列表实现这一点。这种技术被称为委托构造函数(delegating constructors)。它简化了代码并减少了重复逻辑。 以下是具体示例: ```cpp class Foo { private: int data; public: Foo() : Foo(0) {} // 调用了带参构造函数 Foo(int value) : data(value) {} }; ``` 在这个例子中,默认构造函数 `Foo()` 实际上调用了带参构造函数 `Foo(int value)` 并传递了一个默认值 `0` 给它。 --- #### 3. 构造函数重载的规则 尽管构造函数重载提供了灵活性,但也有一些重要的规则需要注意: - **唯一性**:每个构造函数必须有唯一的签名(即参数的数量或类型组合必须不同)。 - **隐转换的风险**:如果某些构造函数被设计得过于通用,则可能导致意外的隐类型转换。为了避免这种情况,可以使用关键字 `explicit` 将构造函数标记为的。 下面是一个关于 `explicit` 的示例: ```cpp class Bar { public: explicit Bar(int x) { /* 初始化 */ } }; void func(Bar b) {} int main() { Bar bar(5); // 调用构造函数 func(bar); // 下面这一行会报错,因为无法隐转换 // func(5); } ``` 在此处,由于构造函数被声明为 `explicit`,因此不会发生从 `int` 到 `Bar` 类型的隐转换。 --- #### 4. 特殊情况下的注意事项 除了基本规则外,在涉及继承关系的情况下还需要注意一些特殊行为。例如,派生类的构造函数并不会自动调用基类的任何构造函数;相反,必须指定要调用哪个基类构造函数[^5]。 ```cpp class Base { protected: int baseValue; public: Base() : baseValue(0) {} Base(int v) : baseValue(v) {} }; class Derived : public Base { private: int derivedValue; public: Derived() : Base(), derivedValue(0) {} // 调用Base() Derived(int v) : Base(v), derivedValue(v * 2) {} // 调用Base(int) }; ``` 这里可以看到,无论何时创建 `Derived` 对象,都必须明确告诉编译器如何初始化其基类部分。 --- #### 5. 总结 构造函数重载增强了类的设计能力,使我们能够以更灵活的方实例化对象。然而,为了保持代码清晰性和可维护性,应该遵循良好的编码习惯,比如合理利用 `explicit` 关键字以及谨慎处理继承场景下基类与派生类之间的交互。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值