4. 参数传递与返回值

目录

 


刚刚谈到构造函数,我们设计一个class,第一个遭遇的比较大的问题就是,一个构造函数由于它的语法比较特别,所以然后又有一种特殊的初始化叫 initialization list,所以我们要要怎么好好的去善用它呢,刚刚提过了,我们继续下去。

constructor (ctor, 构造函数) 被放在 private 区

这张标题说把构造函数放在 private 的区域里头,在传达什么意思呢,感觉起来怪怪的。如果你把它放到 private 的这个区域,私有区域表示这个函数是不可以被调用,被外界调用。那这样的话,像这样说,外界说我要创建一个这种对象,就构造函数就会被调用起来,可是构造函数又放在 private 不能被构造啊,所以这两个动作都是不可以的。那可见不应该有人把构造函数放在private的区域喽,也不是,既然提出来了,表示会有人这么做。什么情况要这么做呢,就说我不允许被外界创建对象,那这样这个类有什么用。

ctors 放在 private 区

下面是一个经典的写法,这是最简单的一个设计模式 design pattern,如果我们不懂什么是设计模式没有关系,在这设计模式里头有一个叫做 Singleton,它就用到了刚刚我们所说的那一种那种写法,把构造函数放在 private 的里头,我们先看看这段代码,我一个class A, A的构造函数,像这一个例子就写了两个版本,我们刚刚不是说有 function overloading 重载吗,可以有好几个吗,这它就写了两个。它都放在私有区private,所以外界不能够去创建它。那外界需要它怎么办呢,这种 class 既然叫做 singleton,它的意思 singleton 你可能查字典查不到这个字了,你查到single, 查不到 singleton 是个演化的字眼,意思就是单例、单体、单键都可以,意思就是说我设计这个 A 呢,我只要一份, 外界只能用一份,所以它才不允许外界创建,那那一份在哪里呢,那一份在这个黄色的部分,是 static。我们还没有去谈到 static 的特性,所以我现在也不打算跳过去谈它。但我告诉你,它里头自己准备了一份自己,各位看这一行,这个 class A,它这里面有一个自己,然后外界要的时候,不能够用传统的那种创建的方式,要通过 A 这一个class的这个函数叫 getInstance,这名字随便取啊,但是通过这个函数去取得里面它自己的那一份。这里面出现一些到目前为止还没有提过的语法,我们重点不是在这里,重点是告诉各位说,确实有一种需求是把构造函数放到 private 里头,而且这个还很有名,目前的这个的写法叫 singleton ,是有这种可能性的这种需求的。

const member functions (常量成员函数)

好我们再往下看,所构造函数讲完了。我们继续分析,在这一个类的 body 本体里头,还有些什么要注意的呢,我们前面已经讲过了,在本体里头就是有 data 以及函数,就是这两大区域。 现在看到函数的部分,这一件事情是很多很多人会忽略的,包括你可能写了很多年的程序,你都会忽略这个事情,但是它非常重要。就是在函数的后头加 const,一定要现在这个位置,就是小括号的后面,大括号的前面,花括号的前面。我们先认识一下我给的这两个例子,实部虚部,这两个函数是要取得对复数的实部虚部,所以这两个函数会改变这个对象里面的数据吗,不会,它只是把它拿出来而已。如果是要写进去,就改变了实部虚部,但是它只是拿出来。我是要引导你去想一件事情。就是对于类 class 里面的函数,有分为会改变数据的和不会改变数据两种不会改变数据内容的,马上加上 const,所以 const 函数的意思就是我不改变数据内容,这里面的数据的内容。让大家想一想啊,当我们在写一个类,现在就叫复数嘛啊我写一个类叫做 person 人啊,或者叫做石头 stone,我开始要去想外界可能么怎么样去用这个人,或用这个石头好,所以我要写一些函数,好像写十个函数,这十个函数的第一个,第二个,第三个,这些函数会不会改变数据呢,当你在想这件事情的时候,你的心里头已经有答案了。比如说你只需要把数据拿出来打印出来,这个函数是不会改变数据的,它只是拿数据,当你在想它的逻辑意义的时候,你已经知道它要不要加 const。你后面的定义可能是你这个这个叫做接口界面,接口 interface,你可能想好了以后才交给其它人去定义,定义是后面也许是一个礼拜之后的事情,三天之后的事情,但是你在设计接口的时候,你已经知道要不要加 const,那要加的话就一定要加。不加会有什么后果,我们来看看。左手边这种情况我现在加了 const ,左手边这种情况呢复数,我得我现在创建一个复数是实部为二,复数虚部为一,我通过上面这两个函数,各自去取得实部和取得数虚部,把它打印出来,这在逻辑想当然是 ok ,ok。但是右边这个例子,我如果我的复数是一个常量,注意这里注意这里差,const 可能出现在这个位置上,也可能出现在对象或者叫变量的前面,那意思是说我这一个对象或我这个变量的内容,一定是不动的,不可以改的。然后我再通过这种对象去调用它的函数,万一你没有写 const,那是什么意思。 我们现在正在检讨说这里该加一定要加啊,万一你没有加,而使用者却这么用,会产生什么后果。使用者的第一行的意思是说,我这个值是不可以改变的哦,我这个复数里面二和一这个都不能变。然后我通去调用,我要取得实部,逻辑上应该可以吧,我二一我应该取得二,可是我去调用它的时候,它忘了写 const,那意思就是说我这个函数里面可能会改data。使用者说我绝对不可以改,我调用你这个函数,你这个函数却告诉我说我可能会改变它,这一定是这是矛盾的,所以编译器编到这样,它就会说不行,谁不行呢,你这个函数,你这个东西要调用,它说不行啊,万一是如果你没有写 const 的话,它会说不行,这样使用者是不是很愕然,大吃一惊,我我我有一个复数是二和一,为什么我不能够打印出它的实部呢,那就表示你这个复数设计不好。复数是谁设计的,不就是我们吗,我们现在不是这样设计的复数吗,我们之所以设计不好,就是因为我们这里该加 const,而没有加。很多很多人忽略这个很重要的这件事情,关于 const 用来修饰函数,这样就叫做用它用来修饰这个函数,像这样就是它用来修饰这个变量。它用来修饰函数,这个主题呢还有很多事情要讨论,不过目前这样就可以了,我们就可以进行下去了。我前面不是说希望带大家能够有一个,正规化的写法吗,一出手,就是就是这个完全正规的,正规军作战啊,所以这些小地方,也许如果使用者不像右下角这样用的话,你也不会出错啊,但是我们要要要想得更周全一些。

参数传递:pass by value vs. pass by reference (to const)

我们再往下看,这个主题也是我们在判断你写的代码,你也许可以,你的这个程序也许可以运作,但是你有没有受过正规训练,好的系统训练,我会看123某几个点,这又是其中的一个点。就是你参数的传递是 pass by value,还是有没有用到 pass by reference。那我要先解释 by value 和 by reference 的意思,它的形式是这样,现在这些都是参数,被我黄色圈起来的,这些全部都是参数,所以任何人要调用这个函数,就要把东西传进去,这个叫 pass 传参数。这样就是 pass by value,没有任何特殊符号,那这样就是 pass by reference,有这样有这个这个取地址的这个符号,但是在这里它不是取地址值的意思。标题上说这个叫 pass by reference (to const),就是现在你看的这种情况,那标题也告诉你,也可能不是 const,那就是这种情况,这种情况它前面没有 const,所以标题出现三种情况,在这里都出现了。pass 传递 value 是什么意思,就是整包都传过去,这个 value 多大,它就整包传过去,传的动作其实是压到函数的 stack,栈里头去,但是那个事情我们假设你不知道,我们也不用管它,反正就是整包传过去。所以像现在是double double,是几个 byte 的呢,啊如果是四个 byte,就算是四个字节,就传四个字节。但是你可能将来传的,在其它的写其它的程序的时候,你传的是100个字节或300个字节,可能有这个可能性嘛,所以我们要养成习惯了,尽量不要 pass by value。那怎么办,你还是要把这个东西传过去,怎么办呢,在过去 C 可以传指针哦,我现在要传这个东西,这包东西太大了,我就把这个这包东西的地址传出去,地址就是它的一个指针传出去,指针四个字节啊,传很快。那现在说我有一个东西像指针,但是更漂亮,C++ 的,就是 reference 引用。那引用这个东西,很多人都因为因为你指针你看得到指针,你可以打印出来。引用,你好像好像有点摸不着边,看不到。我现在先给你建立概念,就是引用就是在底部,它就是一个指针,所以纯引用就相当于传指针那么的快。但是它的形式又很漂亮,比如说这样子吧,我们来看看啊。像现在这边有一个例子,这第一行要创建一个复数,这个二和一就会被传进来,这里在设计的时候说我接受的是 by value,它没有特殊符号,所以是 by value。那下面这个例子呢,调用加等于,加等于这个函数后面才会出现,但是它的声明在这里,所以加等于等于是把这个复数传进来,传到这里,那是传引用。因为这里有这个特殊符号,速度很快,就是一个指针的速度,传递指针的速度。那这样子来讲的话,我们都尽量传引用啦,没错,甚至于你传 double,你也给它传引用也可以。所以这边建立一个良好习惯啊,你最好所有的参数传递都传引用,尽量不要传value,传值尽量不要。虽然这句话一讲下去,有的人开始检讨了,那如果说我传一个字符呢,字符只是一个字节而已,那你传引用相当于传指针速度上,相当于传指针是存四个字节,那我还不如就单单传一个字符,我是 pass by value 来传还快一些呢,这个是很细部的讨论的,没有错,这个刚刚在这个说法是对的,可是这样的话也可以哈,你愿意想得这么细也可以。它如果给你一个大范围,给你一个大的一个遵循守则的话,我愿意说大家都不管什么,你都传引用。那么传引用啊,如果你在 C 语言有这方面的经验啊,什么经验呢,你如果传的是指针,一开始会想我指针传过去,那一边那个函数它一改就会影响我了,因为我们是传指针。那传引用也会有这个效果喽,对,我传给你,你这个函数一写完,一改完就影响了我,因为我们其实都同一个东西,效果是这样,是不是你要的,有时候是你要的,有时候不是你要的,这要看情况来说,但如果你知道我传过去只是为了速度,我并不希望你一改就改了我,我可以传引用,而且引用to const。像这样,意思就是说我传一个复数给你哈,我速度很快哦,因为我是传引用,但是我传进去之后,你在这个函数里面,你不可以去改这个内容,因为我传给你呢,我不打算让你改,你一改就会影响我。那如果它里头传过去,它这个函数里面真的改了呢,编译就会出错,在我们双方,我付给你这个条件,你不能改啊,你去偏偏去改,在编译的时候就出错了。好最后做一个结论是什么,在这一章里头啊,参数传递尽量都传引用,如果你想的非常细,某一些变量呢是只有两个字节或一个字节,那时候你想传 value传值也可以。那么你在传引用的时候,你去想,如果传过去不希望对方改,因为对方一改就会影响我,所以我不希望对方改,这种情况之下,加 const, 语法是这样。所以现在看到这个例子,这边有一个例子,这一个函数,虽然我们还没看到,这是一嘛,这是 2-7,这后面才会出现的,后面那个二的部分有一个参数,567有好几段,但是既然这边设计为传进来,它不是 to const,那就表示这个函数理念会对传进来的这个 os,这个东西做改变了,看起来是这样了,的确是这样,不然这边就应该加 const ,它没有加。我们后面会才会去分析这个函数所做的事情,你就会更清楚,为什么第一个传进来是没有const。

返回值传递:return by value vs. return by reference (to const)

reference 这个东西,延续这个话题,主要就是用来做参数传递,还有一个地方就是用来做返回值的传递。返回也要传递啊,那这个术语叫 return,可以是 by value 或者是 by reference,这个地方虽然很小,但是会影响到 C++ 程序的效率。为什么你会来选择学C++,多半是因为你考虑到它的效率,它的效率比其它的面向对象语言的更快,所以我们在每一个影响效率的小节上,我们都要注意把它内化成我们的习惯。这边又要注意一个事了,刚刚说参数的传递尽量 by reference,现在说返回值的传递也尽量 by reference,那尽量当然前提是如果可以的话,有也有不少情况是不可以,我们来谈谈。那这个谈的这个主题会在后面才会出现,现在先告诉你语法。这些都是函数,下面这个这个函数名字好特别啊,你一下子不知道它要表达什么,这个叫 do assign assignment plus,好,后面再说好。这些函数的前头,这黄色的部分就是 return 类型,返回的类型。像这样就是 return by value,前面如果加这个符号 & 就是return by reference,那我们刚刚提过,尽量 return by reference。至于细节后头再来谈。尽量的意思并不是大家没有,既然叫尽量,而不是一定不是大家一想,就是传 reference,是在可以的情况下,一定要注意什么是可以,什么是不可以呢,这个后面来讲。我们一直花了不少时间,一直在这个 class 类的本体上面的一些特征啊,语法上的特征在这边打转,希望把这全部把它们弄清楚。

friend (友元)

这里要介绍的是 friend,friend,被翻译成 友元 。也就是我们日常生活里面的朋友啊。我们身上的钱任何人可以来拿吗,当然不行,朋友可以来拿吗,看你在现实生活中你怎么定义啊,但是在这个语言里头,朋友可以来拿,所以这里面的朋友可以来拿,拿什么呢,我刚刚说钱嘛啊 在这里是指数据。什么意思,我们来看看好,我现在设计的一个类叫做复数,它有实部和虚部,我把实部虚部设计为 private,意思就是我不想让外界,任何人是可以任意的去取我的实部虚部,这是好事情,这叫封装,我们就是要这个效果。外界如果要取实部,虚部,可以通过刚刚的这些函数来取。但是对于特别的一种人,我网开一面,我是设计这个 class 的人。比如说现在对于这一个函数,我们现在不要管它这个函数做什么事情啊,这一个 class 说这个函数是我的朋友,后面这一整行是函数的声明,它是我的朋友。于是这个函数呢,这是下面是它的定义,它做什么事,我们后面再来讲它。你看它的写法就直接去拿了,这边传进来有一个参数叫 ths 是一个复数,就是复数这种东西,传进来,我就说我要拿你这个复数里面的实部啊,你这个复数里面的虚部,直接拿,并没有通过某个函数来拿。为什么可以这样呢,因为前面已经讲了,它们是朋友,这个是 friend 的意思。这你也可以像这个情况的,你不要设计为 friend,因为好像朋友太多,是不是会有困扰,不知道啊,这个你的现实人生是怎么样啊。但是在 C++ 里面是强调封装,朋友那就打开了封装的一个大门,打破了封装,所以你也可以说我不要太多的 friend,那这样的话呢以这个例子来讲,下面这个函数就不再是上面的 friend 咯,那它要拿它的数据就得通过某一个函数来拿它,这样也是解决了问题,它也拿到了,那这样稍微慢一点嘛,因为它是通过函数来拿数据稍微慢一点点,朋友是直接拿,这是你的选择,你要用朋友这种设计或者是非朋友的这种设计。

相同 class 的各个 objects 互为 friends (友元)

有一个问题常常我遇到,被被很多人提起,甚至于很多年的工作经验的,程序员也会提这个问题。它可能在日常生活中已经习惯怎么写的,它知道这样写是对的,可是它解释不出来,我们看看什么问题。我现在黄色的部分我为这个复数呢,一直以来我们好几张投影片都是完全一样的啊,因为这就是后面你会拿到的代码,但现在我增加两行上去啊,你课程结束后拿到的这个代码,你下载的这个代码没有这两行啊,这是我现在要额外说明的。假设我现在设计了一个函数叫 func ,它接收一个复数,现在这个在讲的就是复数,那这个函数也接收一个复数,进来之后,它的做法是它的它在做什么事呢,它却取得传进来这个复数的实部和虚部。就好像说我现在设计了一个东西,我的这个函数是要处理另外一个复数,所以这函数复数传进来了,我要去处理它,但是竟然直接拿,竟然是直接拿实部跟虚无,没有通过函数来拿,这不是破坏了封装性吗,也没有出现 friend 的这个字眼,各位整理一下现在在现在是什么情况。那感觉就好像我这个复数啊做了三个出来,其中的一个直接去拿第三个的实部虚部,这样是可以的,那写了 3年 5年 经验的程序员,它太熟悉了,这样可以,可是它解释不出来,为什么呢,怪怪的。有很多个角度可以去解释,这句话不错,同一个class里面做出来的各个 object 彼此互为 friend,如果这样就就已经很好地解释了,为什么这个行为看起来好像打破了封装啊,但其实它是成立的,这句话,就可以解释。有的人解释这个事情不是用这句话,不是用这个角度解释,也解释的通啊,不过我想这句最好。所以我们看下面这里上面写成了黄色这两行,这一块下面就是用法。你看我在前两行准备两个复数,然后黄色这一行呢,我通过第二个复数的这个函数去处理,第一个复数,而处理复数的时候是直接拿它的实部,虚部,这个事情刚刚讲了嘛,下面只是示范了一个用法。很多人没有弄清楚的一点,在这个事情上面。

class body 外的各种定义 (definitions)

刚刚提到,所以我现在先做一个整理哈,当我自己要设计一个类,或者我看学生来,你交给我一个你写好的类,我会特别注意什么地方呢,复习前面都出现过了。第一,数据一定放在private里头,第二参数尽可能的是以reference来传,要不要加 const 看状况,第三返回值也尽量以 reference 来传,能不能万一不行,那当然就不行啊,那等下现在马上要来讲什么情况不行,但是首先你得先告诉自己,我先考虑用reference引用来传,可不可以。第四,函数在类的本体 body 里头的这些函数应该加 const 就要加,如果不加,可能使用者去用的时候会报错,编译器会报错,它会埋怨你,你就是 class 的设计者啊,你设计的不好。 好现在我也是临时的在现场帮大家整理了,我想到了这几点都是我关注的点,还有一个构造函数,有一个特殊的语法叫 initialization list,因为大冒号的那一行那个要尽量去用它。好在刚刚讲的这些里面呢,提到了 传参数或者是返回值的 by reference。 现在我们来看看什么情况之下,是不能够 return by reference,首先考虑要 return by reference,但什么时候不行。后面会出现,后面这里不就出现了吗,这个函数,但是我没有要讲这个函数里面的动作,所以后面还会再继续讲,我现在只讲,我现在关注它返回东西应该怎么设计,它怎么传。这边告诉各位,它的传进来的两个参数,由于是个加等于的动作,就是跟c是一样的,加等于,所以可以把意思就是比如说 c1 加等于 c2,那就是把 c2 加到 c1 的身上,所以 c1 就是第一参数,加到 c1 身上, c1 是第一参数会改被改变,第二是不会改变的,所以你想一想,在这个函数里头,你加完之后会有一个东西,因为结果跑出来,这个结果要被放在哪里。如果是放在一个本来已经有的空间上面,那就是现在这种情况,现在有一和二两个参数,它加完以后,我要放在一这个参数里面,这一这个参数是空间本来就在的,这是一种情况。但还有一种情况,我举一个例子,比如说, c1 加 c2 现在是加等于哦,我现在说 c1 加 c2,在这个函数加这个函数里头,加完的东西要放在哪里,你不能放在 c1 ,你也不能放在 c2 ,所以你一定是在这个加这个函数里面,是再创建出另外一个东西出来放结果,这是两种不同。整理一下,一个函数的操作结果,运算结果,这个结果如果有地方,它要放在什么位置上呢,如果必须一情况一啊,这函数必须创建一个地方来让它放,这是一种。第二种,像这个它就把它放到某个位置,这里的位置是第一参数放上去,这是第二种。那种必须要在函数里面创建出来的那种情况,那返回的就是新创建出来那个东西了,那个东西在函数一结束,它生命就消失了,就叫 local 变量,local 的对象,这时候你就不能传 reference,不能返回 reference,return by reference。为什么呢,你想想我现在有一个函数,我在里面创建了一个东西,我要把这个东西用引用的方式传出去啊,对你可以传出去,语法都对,但是这个函数一结束,那个东西其实已经死亡了,你却把它的一个引用传出去,它这个本体已经死掉了,外界它再去看的时候,它当然看到了坏东西,看到了不好的东西,所以那种情况之下不可以传引用。除了那种情况,都可以传引用返回引用。那现在这是什么情况,一和二相加之后是要放到一里头去,所以在一本来就在了,这是参数嘛,所以它可以返回引用。参数的传递或返回值的传递,首先考虑引用,然后再考虑用引用有没有问题呢,可不可行呢,才去加这个更进一步的设想。好这张投影片让大家好好的去思考,reference 主要是用在传递东西的身上。好我们再往下,操作符重载是一个很大很大的话题。

总结

constructor (ctor, 构造函数) 被放在 private 区

  • 可以把构造函数放在 private 的区域里头
  • singleton 单例设计模式
  • 设计这个 class,只要一份, 外界只能用一份,所以它才不允许外界创建, 通过类的某个函数去取得里面它自己的那一份

const member functions (常量成员函数)

  • 类 class 里面的两种函数
    • 会改变数据的
    • 不会改变数据(在函数的后头加 const), 原因:定义一个 const 的对象(说明不能改变变量),但是 类 内的函数没有加 const(说明可以改变数据),访问对象的该函数,编译器会迷惑 到底可不可以改变 数据。

参数传递:pass by value vs. pass by reference (to const)

参数的传递分为:

  • pass by value: 尽量不要
  • pass by reference(& 符号):是传递指针的速度。
    • 带 const 的:不改变传过去的 引用
    • 不带 const 的:改变传递的引用

最好所有的参数传递都传引用

返回值传递:return by value vs. return by reference (to const)

返回值的传递也尽量 by reference

friend (友元)

友元 会破坏封装,可以直接来拿 数据。

相同 class 的各个 objects 互为 friends (友元)

即,A 对象可以直接 拿 B 对象的数据,它们默认就是 友元。

class body 外的各种定义 (definitions)

回顾设计类的原则:

  1. 数据一定放在 private 里头
  2. 参数尽可能的是以 reference 来传,要不要加 const 看状况
  3. 返回值也尽量以 reference 来传,也要知道什么情况不能传 reference
  4. 函数在类的本体 body 里头的这些函数应该加 const(即 函数名后,大括号前) 就要加,如果不加,可能使用者去用的时候会报错,编译器会报错,它会埋怨你,你就是 class 的设计者啊,你设计的不好。
  5. 构造函数要使用 initialization list 进行初始化

什么情况之下,返回值 是不能够 return by reference?

  • 有一个函数,在里面创建了一个东西,要把这个东西用引用的方式传出去,可以传出去,语法都对,但是这个函数一结束,那个东西其实已经死亡了(局部对象),你却把它的一个引用传出去,它这个本体已经死掉了,外界它再去看的时候,它当然看到了坏东西,看到了不好的东西,所以那种情况之下不可以传引用。比如 对象1 = 对象1 + 对象2,因为对象1 本来就存在,所以可以传引用;而 对象3 = 对象1 + 对象2,对象3 是在 函数内创建的对象,函数结束就会死亡,所以不能传引用。
posted @   Zenith_Hugh  阅读(38)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示

喜欢请打赏

扫描二维码打赏

微信打赏