关于类隐式成员函数

C++自动提供以下成员函数:
1.默认构造函数,如果没有定义构造函数。
2.复制构造函数,如果没有定义。
3.赋值操作符,如果没有定义。
4.默认析构函数,如果没有定义。
5.地址操作符,如果没有定义。

 

一.默认构造函数

如果没有定义构造函数,编译器将提供ClassName::ClassName(){ }这样的空构造函数。这样的函数也可以显式的定义。需要注意的是所有参数都有默认值的构造函数也是默认构造函数。而默认构造函数只能有一个,也就是说不能出

现:

ClassName::ClassName(){  }

ClassName::ClassName(int n=0){ }

这样的定义。

 

如果希望再创建对象时显示地对它进行初始化,或需要创建对象数组时,则必须显示的定义默认构造函数。

 

二.复制构造函数

复制构造函数用于将一个对象复制到新创建的对象中。也就是说,它用于初始化过程中,而不是常规的赋值过程中。

原形通常如下:

ClassName(const Classname &);

 

(1)何时调用复制构造函数
新建一个对象并将其初始化为同类现有对象时,复制构造函数都将被调用。
例如,假设motto是一个ClassName对象,则下面4种声明都将调用复制构造函数:

//calls ClassName(const ClassName&)
ClassName ditto(motto);
ClassName metoo = motto;
ClassName also = ClassName(motto);
ClassName * pClassName = new ClassName(motto);

 

每当程序生成了对象副本时,编译器都将使用复制构造函数。具体地说,当函数按值传递对象或函数返回对象时,都将使用复制构造函数。因为按值传递意味着创建原始变量的一个副本。编译器生成临时对象时,也将使用复制构造函数。

例如,将3个ob对象相加时,编译器可能生成临时的ob对象来保存中间结果。

 

(2)复制构造函数的功能
默认的复制构造函数逐个复制非静态成员(成员复制也称为浅复制),复制的是成员的值。
如果成员本身就是类对象,则将使用这个类的复制构造函数来复制成员对象。
静态成员不受影响,因为它们属于整个类,而不是各个对象。

 

(3)默认复制构造函数的潜藏BUG
由于默认复制构造函数的浅复制规则,将引发潜藏错误。
例如,一个类ClassName,定义了一个以字符指针为形参的构造函数,函数中用new动态分配内存,并用名为char *
str的成员指向该内存(str = new char[100])。也就是说,这个类用来封装一个字符数组,但不使用数组,而是使用

字符串指针。同时该类定义了析构函数,函数中使用delete[] str来释放字符串数组。
现在定义一个ClassName的对象ob,ClassName ob("string"),
现在有一个函数T(ClassName dob){}。接收一个ClassName实参,按值传送。
当调用函数时T(ob),这时会创建一个实参的临时副本dob。也就是复制ob到dob,这时将调用复制构造函数,它将逐
个复制成员变量。相当于dob.str = ob.str。注意,这里复制仅仅是复制了指针,而字符串"string"在内存中仍旧只

有一个!这时两个对象的成员dob.str和ob.str同时指向"string"。
当函数T()结束时,它将自动调用ClassName类的析构函数来处理临时对象dob,delete[] str,释放dob中的字符数组
。最终造成ob.str指向的字符串也被释放。

 

(4)使用显示复制构造函数来解决问题
解决类设计中这种问题的方法是进行深度复制,也就是说复制构造函数应当复制字符串并将副本的地址赋值给str成
员,而不仅仅是赋值字符串地址。那么调用析构函数时都将释放不同的字符串。

 

注意:  如果类中包含了使用new初始化的指针成员,应当定义一个复制构造函数,来复制指向的数据,而不是指针,这被称为深度复制。复制的另一种形式(成员复制或浅复制)只是复制指针值。浅复制仅复制指针信息,而不会复制指针引用的结构。

 

三.赋值操作符
C++允许类对象赋值,这是通过自动为类重载赋值操作符实现的。这种操作符的原型如下:
ClassName & ClassName::operator= (const ClassName &);
它接收并返回一个指向类对象的引用。

(1)何时使用赋值操作符
将已有的对象赋值给另一个对象时,将使用重载的赋值操作符:
ClassName ob("string");
ClassName dob;
dob = ob;
同时,在初始化对象时,并不一定会使用赋值操作符。
ClassName dob = ob;
这里dob是一个新创建的对象,被初始化为ob的值,因此使用复制构造函数。不过实现时也可能分两步来处理这条语
句:使用复制构造函数创建一个临时对象,然后通过赋值将临时对象的值复制到新对象中。这就是说,初始化总是会调用复制构造函数,而使用=操作符也可能调用赋值操作符。

 

(2)赋值操作符的功能
与赋值构造函数相似,赋值操作符的隐式实现也对成员进行逐个复制。如果成员本身就是类对象,则程序将使用为这
个类定义的赋值操作符来复制该成员,但静态数据成员不受影响。

 

(3)赋值的潜藏BUG与解决赋值问题
赋值操作符也会出现默认复制构造函数同样的问题:数据受损。
解决办法是提供赋值操作符(进行深度复制)定义。其实现与复制构造函数相似,但有一些差别。
1.由于目标对象可能引用了以前分配的数据,所以函数应使用delete[]来释放这些数据。
2.函数应当避免将对象赋值给自身;否则,给对象重新复制之前,释放内存操作可能删除对象的内容。
3.函数返回一个指向调用对象的引用。


通过返回一个对象,函数可能想常规赋值操作那样,连续进行赋值,即如果S0、S1、S2都是ClassName对象,则可以

这样编写代码:
S0 = S1 = S2; //S0.operator= (S1.operator= (S2));

 

下面的代码说明如何为ClassName类编写赋值操作符:
ClassName & ClassName::operator= (const ClassName & dob)
{
  if (this == &dob)
     return *this;                 //如果是对象赋值给自身,则直接返回该对象的引用。
  delete[] str;                    //删除当前函数调用对象所使用的动态内存空间。
  str = new char [......];
  std::strcpy(str,dob.str); //为当前函数调用对象分配新的内存空间,并复制新值。
  return *this
}

 

四、五

默认析构函数不执行任何操作。
隐式地址操作符返回调用对象的地址(即this指针的值)。

### C++ 普通成员函数内联函数的区别 #### 定义方差异 普通成员函数可以在内部或外部定义。当在外定义时,只需提供函数签名并实现之。而内联函数通常是在定义体内直接给出完整的函数体,这种情况下编译器会尝试将其视为内联处理[^1]。 ```cpp class Example { public: // 内联函数,在定义中直接实现了方法 int getInlineValue() const { return value_; } // 声明了一个普通的成员函数 void setNonInlineValue(int val); private: int value_; }; // 实现普通成员函数 void Example::setNonInlineValue(int val) { value_ = val; } ``` #### 编译行为不同 对于普通成员函数而言,它们会在程序执行期间通过地址跳转来完成调用操作;然而,针对标记为`inline`或是位于内的短小简单的方法,则可能由编译器决定是否展开成实际代码片段嵌入到调用处,从而减少因多次调用带来的开销[^4]。 #### 性能考量 由于减少了间接寻址的成本以及潜在的缓存命中率提升等因素的影响,合理运用内联机制可以带来性能上的改善。不过需要注意的是,并不是所有的场合都适合采用这种方——过长复杂的逻辑不适合做这样的优化,因为这可能导致目标二进制体积增大反而影响加载速度等问题[^3]。 #### 使用建议 - **优先考虑简洁性**:只有那些非常简短且频繁使用的辅助功能才应该被设置为内联形。 - **关注可读性和维护成本**:过度依赖于内联可能会降低源码清晰度,增加调试难度。 - **遵循最佳实践**:现代C++标准鼓励利用`constexpr`特性代替传统的`inline`声明来进行常量表达的计算等任务。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值