Effective C++ 2e Item19

遥感影像建筑物变化检测 阅读详情

 

条款19: 分清成员函数,非成员函数和友元函数

成员函数和非成员函数最大的区别在于成员函数可以是虚拟的而非成员函数不行。所以,如果有个函数必须进行动态绑定(见条款38),就要采用虚拟函数,而虚拟函数必定是某个类的成员函数。关于这一点就这么简单。如果函数不必是虚拟的,情况就稍微复杂一点。

看下面表示有理数的一个类:

class Rational {
public:
  Rational(int numerator = 0, int denominator = 1);
  int numerator() const;
  int denominator() const;

private:
  ...
};

这是一个没有一点用处的类。(用条款18的术语来说,接口的确最小,但远不够完整。)所以,要对它增加加,减,乘等算术操作支持,但是,该用成员函数还是非成员函数,或者,非成员的友元函数来实现呢?

当拿不定主意的时候,用面向对象的方法来考虑!有理数的乘法是和Rational类相联系的,所以,写一个成员函数把这个操作包到类中。

class Rational {
public:

  ...

  const Rational operator*(const Rational& rhs) const;
};

(如果你不明白为什么这个函数以这种方式声明——返回一个const值而取一个const的引用作为它的参数——参考条款21-23。)

现在可以很容易地对有理数进行乘法操作:

Rational oneEighth(1, 8);
Rational oneHalf(1, 2);

Rational result = oneHalf * oneEighth;   // 运行良好

result = result * oneEighth;             // 运行良好

但不要满足,还要支持混合类型操作,比如,Rational要能和int相乘。但当写下下面的代码时,只有一半工作:

result = oneHalf * 2;      // 运行良好

result = 2 * oneHalf;      // 出错!

这是一个不好的苗头。记得吗?乘法要满足交换律。

如果用下面的等价函数形式重写上面的两个例子,问题的原因就很明显了:

result = oneHalf.operator*(2);      // 运行良好

result = 2.operator*(oneHalf);      // 出错!

对象oneHalf是一个包含operator*函数的类的实例,所以编译器调用了那个函数。而整数2没有相应的类,所以没有operator*成员函数。编译器还会去搜索一个可以象下面这样调用的非成员的operator*函数(即,在某个可见的名字空间里的operator*函数或全局的operator*函数):

result = operator*(2, oneHalf);      // 错误!

但没有这样一个参数为int和Rational的非成员operator*函数,所以搜索失败。

再看看那个成功的调用。它的第二参数是整数2,然而Rational::operator*期望的参数却是Rational对象。怎么回事?为什么2在一个地方可以工作而另一个地方不行?

秘密在于隐式类型转换。编译器知道传的值是int而函数需要的是Rational,但它也同时知道调用Rational的构造函数将int转换成一个合适的Rational,所以才有上面成功的调用(见条款M19)。换句话说,编译器处理这个调用时的情形类似下面这样:

const Rational temp(2);      // 从2产生一个临时
                             // Rational对象

result = oneHalf * temp;     // 同oneHalf.operator*(temp);

当然,只有所涉及的构造函数没有声明为explicit的情况下才会这样,因为explicit构造函数不能用于隐式转换,这正是explicit的含义。如果Rational象下面这样定义:

class Rational {
public:
  explicit Rational(int numerator = 0,     // 此构造函数为
                    int denominator = 1);  // explicit
  ...

  const Rational operator*(const Rational& rhs) const;

  ...

};

那么,下面的语句都不能通过编译:

result = oneHalf * 2;             // 错误!
result = 2 * oneHalf;             // 错误!

这不会为混合运算提供支持,但至少两条语句的行为一致了。

然而,我们刚才研究的这个类是要设计成可以允许固定类型到Rational的隐式转换的——这就是为什么Rational的构造函数没有声明为explicit的原因。这样,编译器将执行必要的隐式转换使上面result的第一个赋值语句通过编译。实际上,如果需要的话,编译器会对每个函数的每个参数执行这种隐式类型转换。但它只对函数参数表中列出的参数进行转换,决不会对成员函数所在的对象(即,成员函数中的*this指针所对应的对象)进行转换。这就是为什么这个语句可以工作:

result = oneHalf.operator*(2);      // converts int -> Rational

而这个语句不行:

result = 2.operator*(oneHalf);      // 不会转换
                                    // int -> Rational

第一种情形操作的是列在函数声明中的一个参数,而第二种情形不是。

尽管如此,你可能还是想支持混合型的算术操作,而实现的方法现在应该清楚了:使operator*成为一个非成员函数,从而允许编译器对所有的参数执行隐式类型转换:

class Rational {

  ...                               // contains no operator*

};

// 在全局或某一名字空间声明,
// 参见条款M20了解为什么要这么做
const Rational operator*(const Rational& lhs,
                         const Rational& rhs)
{
  return Rational(lhs.numerator() * rhs.numerator(),
                  lhs.denominator() * rhs.denominator());
}

Rational oneFourth(1, 4);
Rational result;

result = oneFourth * 2;           // 工作良好
result = 2 * oneFourth;           // 万岁, 它也工作了!

这当然是一个完美的结局,但还有一个担心:operator*应该成为Rational类的友元吗?

这种情况下,答案是不必要。因为operator*可以完全通过类的公有(public)接口来实现。上面的代码就是这么做的。只要能避免使用友元函数就要避免,因为,和现实生活中差不多,友元(朋友)带来的麻烦往往比它(他/她)对你的帮助多。

然而,很多情况下,不是成员的函数从概念上说也可能是类接口的一部分,它们需要访问类的非公有成员的情况也不少。

让我们回头再来看看本书那个主要的例子,String类。如果想重载operator>>和operator<<来读写String对象,你会很快发现它们不能是成员函数。如果是成员函数的话,调用它们时就必须把String对象放在它们的左边:

// 一个不正确地将operator>>和
// operator<<作为成员函数的类
class String {
public:
  String(const char *value);

  ...

  istream& operator>>(istream& input);
  ostream& operator<<(ostream& output);

private:
  char *data;
};

String s;

s >> cin;                   // 合法, 但
                            // 有违常规

s << cout;                  // 同上

这会把别人弄糊涂。所以这些函数不能是成员函数。注意这种情况和前面的不同。这里的目标是自然的调用语法,前面关心的是隐式类型转换。

所以,如果来设计这些函数,就象这样:

istream& operator>>(istream& input, String& string)
{
  delete [] string.data;

  read from input into some memory, and make string.data
  point to it

  return input;
}

ostream& operator<<(ostream& output,
                    const String& string)
{
  return output << string.data;
}

注意上面两个函数都要访问String类的data成员,而这个成员是私有(private)的。但我们已经知道,这个函数一定要是非成员函数。这样,就别无选择了:需要访问非公有成员的非成员函数只能是类的友元函数。

本条款得出的结论如下。假设f是想正确声明的函数,C是和它相关的类:

·虚函数必须是成员函数。如果f必须是虚函数,就让它成为C的成员函数。

·operator>>和operator<<决不能是成员函数。如果f是operator>>或operator<<,让f成为非成员函数。如果f还需要访问C的非公有成员,让f成为C的友元函数。

·只有非成员函数对最左边的参数进行类型转换。如果f需要对最左边的参数进行类型转换,让f成为非成员函数。如果f还需要访问C的非公有成员,让f成为C的友元函数。

·其它情况下都声明为成员函数。如果以上情况都不是,让f成为C的成员函数。

搞定ESD(四):ESD干扰耦合路径深入分析(二) 静电放电过程中静电干扰主要通过三种间接耦合方式干扰敏感源,即电场耦合、磁场耦合、地弹。前文已经深入分析了静电放电过程中的电场耦合,今天就谈谈静电放电过程中的磁场耦合,也可以理解环路耦合。熟话说的好〝人往高处走,水往低处流〞,静电电流也会选择阻抗最低的路径返回到源端。静电电流流会源端的路径实际上就构成了电流环路,当敏感信号形成的电流环路与静电电流环路存在共环路时,就会将静电放电干扰耦合到敏感信号环路内,干扰敏感信号。 阅读详情

相关推荐

STM32使用内部RC振荡器作为系统时钟开发项目--内晶振启动模板工程

STM32使用内部RC振荡器时,OSC32_IN,OSC32_OUT接法: 1)对于100脚或144脚的产品,OSC_IN应接地,OSC_OUT应悬空。 2)对于少于100脚的产品,有2种接法:    2.1)OSC_IN和OSC_OUT分别通过10K电阻接地。此方法可提高EMC性能。    2.2)分别重映射OSC_IN和OSC_OUT至PD0和PD1,再配置PD0和PD1为推挽输出

overflyme的博客 1万+

C++中的成员函数与非成员函数

1.成员函数和非成员函数 其实简单来说成员函数是在类中定义的函数,而非成员函数就是普通函数,即不在类中定义的函数,其中非成员函数比较典型的是友元函数。 2.详细解释 成员函数是类定义的一部分,通过特定的对象来调用。成员函数可以隐式访问调用对象的成员,而无须使用成员操作符。 友元函数不是类的组成部分,因此被称为直接函数调用。友元函数不能隐式访问类成员,而必须将成员操作符用于作为参数传递的对象。 3.总结 1.成员函数是面向对象的概念,所谓的成员函数,是指一个函数作为类的成员,公

zxmyoung的博客 8418

PLL锁相环性能上不去?可能是电荷泵的电流失配在捣鬼(附Jae-Shin Lee经典方案解析)

本文深入分析了PLL锁相环性能瓶颈的根源——电荷泵电流失配问题,详细解析了Current Mismatch和Current Deviation的区别及其对系统的影响。重点介绍了Jae-Shin Lee的多级电流镜技术,该方案通过运放构建虚拟短路和四级电流镜结构,显著降低了电流失配,使参考杂散改善15dB以上。文章还提供了从仿真到流片的完整优化流程,帮助工程师系统解决电荷泵性能问题。

weixin_30684743的博客 371

成员函数、非成员函数和友元函数

1.成员函数和非成员函数最大的区别在于成员函数可以是虚拟的而非成员函数不能,成员函数的优势是能够方便地进行动态绑定,实现多态。 说明一个函数为一个类的友元函数则该函数可以访问此类的私有数据和方法。 2.成员函数: --->显式构造函数 C++中的explicit关键字用来修饰类的构造函数,表明该构造函数是显式的,隐式构造函数能够实现将该构造函数对应数据类型的数据转换为该类对象. c...

weixin_34187862的博客 263

Kerberos基础及KDC服务(理论知识)

kerberos基础理论讲解+KDC服务

Siobhan_Mxin的博客 9715

Vivado新手避坑指南:搞懂Generate Output Products和Create HDL Wrapper到底在干啥

本文深入解析Vivado设计流程中的Generate Output Products和Create HDL Wrapper两大关键步骤,帮助新手理解其核心功能与操作要点。通过详细讲解文件生成类型、关键选项设置及常见问题解决方案,指导开发者正确使用这两个功能,避免设计流程中的常见错误,提高FPGA开发效率。

weixin_29207149的博客 313

【C++ 成员函数与非成员函数】

在C++编程中,成员函数和非成员函数都是关键的概念。它们提供了不同的方法来组织和处理代码,具有各自的优势和用途。本文将深入研究成员函数和非成员函数,以帮助您了解何时使用它们以及如何做出正确的选择。

Guojiahui的博客 862

【FPGA】Verilog代码实现温湿度传感器DHT11

#今天也是咸鱼的一天~ 因为参加Robei 杯 是要做一个机器人,先不说这个机器人具体怎么机器法,但是和外界互通的传感器肯定少不了,通过获取外界环境数据,进行处理,然后做出各种各样的功能。 先来讲个简单的模块,温湿度传感器DHT11。 DHT11介绍 DHT11是一款价格便宜,易于使用的温度湿度测量二合一传感器。它具有超小体积、极低功耗的特点。它使用单根总线与单片机进行双向的串行数据传输,信号传...

Ninquelote的博客 9163

C++-使用类(作为成员函数还是非成员函数)

使用类 (3) 重载运算符: 作为成员函数还是非成员函数 对于很多运算符来说,可以选择使用成员函数或非成员函数来实现运算符重载。 一般来说,非成员函数应是友元函数,这样它才能直接访问类的私有数据。 例如,Time类的加法运算符在Time类声明中的原型如下: Timeoperator+(constTime &amp; t) const;    //成员函数版本 这个类也可以使用下面的原型: ...

Ezra1991的博客 5462

PyTorch实战(20)——生成对抗网络(Generative Adversarial Network,GAN)

自生成对抗网络 (Generative Adversarial Network, GAN) 提出以来,一直是一个活跃的研究和开发领域。本节探索了 GAN 的基本概念,重点解析了其两大核心组件——生成器与判别器的架构设计,深入阐述了 GAN 模型的整体工作机制,并使用 PyTorch 从零开始实现了深度卷积生成对抗网络 (Deep Convolutional GAN, DCGAN)。

盼小辉丶的博客 3560

华为od 面试八股文_C++_01_含答案

华为od 面试八股文_C++_01_含答案

MISAYAONE的博客 5596

成员函数和非成员函数

成员函数:一个类里面定义地函数; 非成员函数:不是在类里面定义地函数; 那么是选择类的成员函数还是类的非成员函数呢? 面向对象则要求是,将操作数据的函数与数据放在一起。但这不意味着要选择成员函数。从封装的角度看,成员函数的moringAction封装性比非成员函数要低。如果某些东西被封装,它就不再可见。越多东西被封装,越少人可以看到它。所以使用非成员函数的类,封装性较低。而越少人看到它,我们就有越大弹性去变化它,因为我们的改变仅仅直接影响看到改变的那些人事物。因此,越多东西被封装,改变哪些东西能力越大。 在

10000day 829

运动控制软件:Siemens Sinamics二次开发_(6).通信接口与协议详解

PROFIBUSPROFINETModbus TCPCANopenEtherCAT每种通信接口都有其特定的应用场景和优势。选择合适的通信接口可以显著提高系统的性能和可靠性。在选择通信协议时,需要根据具体的应用场景和需求综合考虑。每种通信协议都有其特定的优势和局限性,因此在设计运动控制系统时,应选择最适合的协议,以确保系统的性能、可靠性、成本和维护性。通过以上对比和分析,希望开发者能够更好地理解和选择适合的通信接口和协议,从而优化运动控制系统的开发和应用。

2401_87715305的博客 901

Effective C++ 2e Item7

条款7:预先准备好内存不够的情况operator new在无法完成内存分配请求时会抛出异常(以前的做法一般是返回0,一些旧一点的编译器还这么做。你愿意的话也可以把你的编译器设置成这样。关于这个话题我将推迟到本条款的结尾处讨论)。大家都知道,处理内存不够所产生的异常真可以算得上是个道德上的行为,但实际做起来又会象刀架在脖子上那样痛苦。所以,你有时会不去管它,也许一直没去管它。但你心里一定还是深深

lostmouse的专栏 1668

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

Effective C++ 2e Item1

 从C转向C++对每个人来说,习惯C++需要一些时间,对于已经熟悉C的程序员来说,这个过程尤其令人苦恼。因为C是C++的子集,所有的C的技术都可以继续使用,但很多用起来又不太合适。例如,C++程序员会认为指针的指针看起来很古怪,他们会问:为什么不用指针的引用来代替呢?C是一种简单的语言。它真正提供的只有有宏、指针、结构、数组和函数。不管什么问题,C都靠宏、指针、结构、数组和函数来解决。而C

lostmouse的专栏 1488

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

Effective C++ 2e Item5

内存管理C++中涉及到的内存的管理问题可以归结为两方面:正确地得到它和有效地使用它。好的程序员会理解这两个问题为什么要以这样的顺序列出。因为执行得再快、体积再小的程序如果它不按你所想象地那样去执行,那也一点用处都没有。“正确地得到”的意思是正确地调用内存分配和释放程序;而“有效地使用”是指写特定版本的内存分配和释放程序。这里,“正确地得到”显得更重要一些。然而说到正确性,C++其实从C继承

lostmouse的专栏 1199

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

Effective C++ 2e Item44

条款44: 说你想说的;理解你所说的在本章关于 "继承和面向对象设计" 的简介中,我曾强调,理解不同的面向对象构件在C++中的含义十分重要。这和仅仅知道C++语言的规则有很大的不同。例如,C++规则说,如果类D从类B公有继承,从D的指针到B的指针就有一个标准转换;B的公有成员函数将被继承为D的公有成员函数,等等。这些规则都是正确的,但在将设计思想转化为C++的过程中,它们起不到任何作用。相反,

lostmouse的专栏 1148

Effective C++ 2e Item15

条款15: 让operator=返回*this的引用C++的设计者Bjarne Stroustrup下了很大的功夫想使用户自定义类型尽可能地和固定类型的工作方式相似。这就是为什么你可以重载运算符,写类型转换函数(见条款M5),控制赋值和拷贝构造函数,等等。他做了这么多努力,那你最少也该继续做下去。让我们看看赋值。用固定类型的情况下,赋值操作可以象下面这样链起来:int w, x, y,

lostmouse的专栏 1079

Effective C++ 2e Item50

条款50: 提高对C++的认识C++中有很多 "东西":C,重载,面向对象,模板,例外,名字空间。这么多东西,有时让人感到不知所措。怎么弄懂所有这些东西呢?C++之所以发展到现在这个样子,在于它有自己的设计目标。理解了这些设计目标,就不难弄懂所有这些东西了。C++最首要的目标在于:· 和C的兼容性。很多很多C还存在,很多很多C程序员还存在。C++利用了这一基础,并建立在 ---- 我是

lostmouse的专栏 1376

Effective C++ 2e Item33

条款33: 明智地使用内联内联函数------多妙的主意啊!它们看起来象函数,运作起来象函数,比宏(macro)要好得多(参见条款1),使用时还不需要承担函数调用的开销。你还能对它们要求更多吗?然而,你从它们得到的确实比你想象的要多,因为避免函数调用的开销仅仅是问题的一个方面。为了处理那些没有函数调用的代码,编译器优化程序本身进行了专门的设计。所以当内联一个函数时,编译器可以对函数体执行特

lostmouse的专栏 1141

Effective C++ 2e Item30

条款30: 避免这样的成员函数:其返回值是指向成员的非const指针或引用,但成员的访问级比这个函数要低使一个成员为private或protected的原因是想限制对它的访问,对吗?劳累的编译器要费九牛二虎之力来确保你设置的访问限制不被破坏,对不对?所以,写个函数来让用户随意地访问受限的成员没多大意义,对不对?如果你确实认为有意义,那么请反复阅读本段,直到你不这样认为为止。实际编程中很容易

lostmouse的专栏 1627

Effective C++ 2e Item4

条款4:尽量使用C++风格的注释旧的C注释语法在C++里还可以用,C++新发明的行尾注释语法也有其过人之处。例如下面这种情形:    if ( a > b ) {      // int temp = a;    // swap a and b      // a = b;      // b = temp;    }假设你出于某种原因要注释掉这个代码块。从软件工程的角度看,写这段代码的程

lostmouse的专栏 1080

Effective C++ 2e Item24

条款24: 在函数重载和设定参数缺省值间慎重选择会对函数重载和设定参数缺省值产生混淆的原因在于,它们都允许一个函数以多种方式被调用:void f();                             // f被重载void f(int x);f();                                  // 调用f()f(10);                 

lostmouse的专栏 1012

Effective C++ 2e Item28

条款28: 划分全局名字空间全局空间最大的问题在于它本身仅有一个。在大的软件项目中,经常会有不少人把他们定义的名字都放在这个单一的空间中,从而不可避免地导致名字冲突。例如,假设library1.h定义了一些常量,其中包括:const double LIB_VERSION = 1.204;类似的,library2.h也定义了:const int LIB_VERSION = 3;很

lostmouse的专栏 1076

Effective C++ 2e Item31

条款31: 千万不要返回局部对象的引用,也不要返回函数内部用new初始化的指针的引用本条款听起来很复杂,其实不然。它只是一个很简单的道理,真的,相信我。先看第一种情况:返回一个局部对象的引用。它的问题在于,局部对象 ----- 顾名思义 ---- 仅仅是局部的。也就是说,局部对象是在被定义时创建,在离开生命空间时被销毁的。所谓生命空间,是指它们所在的函数体。当函数返回时,程序的控制离开了这

lostmouse的专栏 1058

Effective C++ 2e Item10

 条款10. 如果写了operator new就要同时写operator delete让我们回过头去看看这样一个基本问题:为什么有必要写自己的operator new和operator delete?答案通常是:为了效率。缺省的operator new和operator delete具有非常好的通用性,它的这种灵活性也使得在某些特定的场合下,可以进一步改善它的性能。尤其在那些需要动态分

lostmouse的专栏 1190
上一篇: Effective C++ 2e Item18
下一篇: Effective C++ 2e Item20
lostmouse
博客等级 码龄26年 22粉丝 63原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值