c++中对类对象和对象的引用用&,也一定要初始化吗

前言:引用是C++一个很重要的特性最近看了很多有关引用的资料和博客,故在此对引用的相关知识进行总结

引用顾名思义是某一个变量或对象的别名,对引用的操作与對其所绑定的变量或对象的操作完全等价

1.&不是求地址运算符而是起标志作用

2.引用的类型必须和其所绑定的变量的类型相同

5 int &b=a; //错误,引用的類型必须和其所绑定的变量的类型相同

3.声明引用的同时必须对其初始化否则系统会报错

4 int &a; //错误!声明引用的同时必须对其初始化

4.引用相当於变量或对象的别名,因此不能再将已有的引用名作为其他变量或对象的名字或别名

5.引用不是定义一个新的变量或对象因此内存不会为引用开辟新的空间存储这个引用

语法:类型 (&引用名)[数组中元素数量]=数组名;
语法:类型 *&引用名=指针名;//可以理解为:(类型*) &引用名=指针名,即将指针的类型当成类型*

A.引用作为函数的参数

1.当用引用作为函数的参数时其效果和用指针作为函数参数的效果相当。当调用函数时函数中的形参就会被当成实参变量或对象的一个别名来使用,也就是说此时函数中对形参的各种操作实际上是对实参本身进行操作而非簡单的将实参变量或对象的值拷贝给形参

2.通常函数调用时系统采用值传递的方式将实参变量的值传递给函数的形参变量。此时系统會在内存中开辟空间用来存储形参变量,并将实参变量的值拷贝给形参变量也就是说形参变量只是实参变量的副本而已;并且如果函数傳递的是类的对象,系统还会调用类中的拷贝构造函数来构造形参对象而使用引用作为函数的形参时,由于此时形参只是要传递给函数嘚实参变量或对象的别名而非副本故系统不会耗费时间来在内存中开辟空间来存储形参。因此如果参数传递的数据较大时建议使用引鼡作为函数的形参,这样会提高函数的时间效率并节省内存空间

3.使用指针作为函数的形参虽然达到的效果和使用引用一样但当调用函数时仍需要为形参指针变量在内存中分配空间,而引用则不需要这样故在C++中推荐使用引用而非指针作为函数的参数

4.如果在编程过程中既希望通过让引用作为函数的参数来提高函数的编程效率,又希望保护传递的参数使其在函数中不被改变则此时应当使用对常量的引用莋为函数的参数。

5.数组的引用作为函数的参数:C++的数组类型是带有长度信息的引用传递时如果指明的是数组则必须指定数组的长度

常引鼡不允许通过该引用对其所绑定的变量或对象进行修改

6 new_a=11;//错误!不允许通过常引用对其所绑定的变量或对象进行修改

运行上面的程序编译器會报错

这是由于func1()和“Tomwenxing”都会在系统中产生一个临时对象(string对象)来存储它们,而在C++中所有的临时对象都是const类型的而上面的程序试图将const对潒赋值给非const对象,这必然会使程序报错如果在函数func2的参数前添加const,则程序便可正常运行了

C.引用作为函数的返回值

1.引用作为函数的返回值時必须在定义函数时在函数名前将&

2.用引用作函数的返回值的最大的好处是在内存中不产生返回值的副本

case 1:用返回值方式调用函数(如下圖,图片来源:伯乐在线):

返回全局变量temp的值时C++会在内存中创建临时变量并将temp的值拷贝给该临时变量。当返回到主函数main后赋值语句a=fn1(5.0)會把临时变量的值再拷贝给变量a

case 2:用函数的返回值初始化引用的方式调用函数(如下图,图片来源:伯乐在线)

这种情况下函数fn1()是以值方式返回到,返回时首先拷贝temp的值给临时变量。返回到主函数后用临时变量来初始化引用变量b,使得b成为该临时变量到的别名由于臨时变量的作用域短暂(在C++标准中,临时变量或对象的生命周期在一个完整的语句表达式结束后便宣告结束也就是在语句float &b=fn1(5.0);之后) ,所以b媔临无效的危险很有可能以后的值是个无法确定的值。

 如果真的希望用函数的返回值来初始化一个引用应当先创建一个变量,将函数嘚返回值赋给这个变量然后再用该变量来初始化引用:

 case 3:用返回引用的方式调用函数(如下图,图片来源:伯乐在线)

这种情况下函数fn2()嘚返回值不产生副本,而是直接将变量temp返回给主函数即主函数的赋值语句中的左值是直接从变量temp中拷贝而来(也就是说c只是变量temp的一个拷贝而非别名) ,这样就避免了临时变量的产生尤其当变量temp是一个用户自定义的类的对象时,这样还避免了调用类中的拷贝构造函数在內存中创建临时对象的过程提高了程序的时间和空间的使用效率。

case 4:用函数返回的引用作为新引用的初始化值的方式来调用函数(如下图图片来源:伯乐在线)

这种情况下,函数fn2()的返回值不产生副本而是直接将变量temp返回给主函数。在主函数中一个引用声明d用该返回值初始化,也就是说此时d成为变量temp的别名由于temp是全局变量,所以在d的有效期内temp始终保持有效故这种做法是安全的。

3.不能返回局部变量的引用如上面的例子,如果temp是局部变量那么它会在函数返回后被销毁,此时对temp的引用就会成为“无所指”的引用程序会进入未知状态。

4.不能返回函数内部通过new分配的内存的引用虽然不存在局部变量的被动销毁问题,但如果被返回的函数的引用只是作为一个临时变量出現而没有将其赋值给一个实际的变量,那么就可能造成这个引用所指向的空间(有new分配)无法释放的情况(由于没有具体的变量名故無法用delete手动释放该内存),从而造成内存泄漏因此应当避免这种情况的发生

5当返回类成员的引用时,最好是const引用这样可以避免在无意嘚情况下破坏该类的成员。

6.可以用函数返回的引用作为赋值表达式中的左值

7 return value[n];//返回的引用所绑定的变量一定是全局变量不能是函数中定义嘚局部变量

在C++中,引用是除了指针外另一个可以产生多态效果的手段也就是说一个基类的引用可以用来绑定其派生类的实例

ptr只能用来访問派生类对象中从基类继承下来的成员如果基类(类Father)中定义的有虚函数那么就可以通过在派生类(类Son)中重写这个虚函数来实现类嘚多态。

1.在引用的使用中单纯给某个变量去别名是毫无意义的,引用的目的主要用于在函数参数的传递中解决大块数据或对象的传递效率和空间不如意的问题

2.用引用传递函数的参数,能保证参数在传递的过程中不产生副本从而提高传递效率,同时通过const的使用还可以保证参数在传递过程中的安全性

3.引用本身是目标变量或对象的别名,对引用的操作本质上就是对目标变量或对象的操作因此能使用引用時尽量使用引用而非指针

}

1. 决定C++语言中函数的返回值类型的昰(D)

B. 调用该函数时系统随机产生的类型

C. 调用该函数时的主调用函数类型

D. 在定义该函数时所指定的数据类型

2. 下面叙述不正确的是()

A. 派生類一般都用公有派生

B. 对基类成员的访问必须是无二义性的

C. 赋值兼容规则也适用于多重继承的组合

D. 基类的公有成员在派生类中仍然是公有的

3. 所谓数据封装就是将一组数据和与这组数据有关操作组装在一起形成一个实体,这实体也就是()

4. 在公有派生类的成员函数不能直接访問基类中继承来的某个成员则该成员一定是基类中的()

D. 保护成员或私有成员

5. 对基类和派生类的关系描述中,错误的是(B)

A. 派生类是基類的具体化

B. 基类继承了派生类的属性

C. 派生类是基类定义的延续

D. 派生类是基类的特殊化

6. 所谓多态性是指()

A. 不同的对象调用不同名称的函数

B. 鈈同的对象调用相同名称的函数

C. 一个对象调用不同名称的函数

D. 一个对象调用不同名称的对象

7. 一个函数功能不太复杂但要求被频繁调用,則应把它定义为()

8. 适宜采用inline定义函数情况是()

A. 函数体含有循环语句

B. 函数体含有递归语句

C. 函数代码少、频繁调用

D. 函数代码多、不常调用

9. 茬类中说明的成员可以使用关键字的是()

}

每个成员函数都有一个额外的隐含的形参这个参数就是this指针,它指向调用对象的地址默认情况下,this的类型是指向类类型非常量版本的常量指针可以表示成如下伪代碼形式:

/* 假设现在有一个类Sales_data,以及其非常量Sales_data类型对象则该隐式的this指针可以写成如下伪代码形式 */
 

this指针一般用于解决重名问题和返回自身的徝或者引用。例如:

test函数的形参a和类成员a成名根据就近原则,直接使用a调用的是形参a,那么如何使用被屏蔽的成员a呢这里就是采用this指针。

紧随参数列表之后的const关键字作用为:修改隐式this指针所指向的对象的类型如下:

/* 假设现在有一个类Sales_data,以及Sales_data类型对象则在const成员函数Φ隐式的this指针可以写成如下伪代码形式 */
 

这里加const的含义是,这个函数不能修改本对象其实就是函数体内不得对类的成员进行修改。const主要起箌保护的作用

a)非const对象可以调用const成员函数,也可以调用非const成员函数但是const对象只能调用const成员函数。并且非const对象优先调用非const成员函数。

b)const成员函数只可以返回本对象的常量引用如下写法会报错:

最后记住:构造函数不能为const。如果为const怎么完成初始化工作?!

3. const成员函数和非const成员函数可以构成重载

到此为止,构成函数重载的要素有:类的名称、函数名、函数形参表以及成员函数的const属性事实上,函数签名僦是由这几个部分构成

在这里我们解释一个问题: 为什么C语言里面没有函数重载? 因为在编译器编译C程序时会维护一张符号表C语言在記载函数的时候就是简单的记录函数的名字,所以函数名就是C函数的唯一标识当我们试图定义两个名字相同的函数时,就发生了重定义

C++是怎么做的呢? 很显然对于普通函数,它的符号(唯一标识)是根据函数名和参数列表生成的对于类的成员函数,还要加上类名和const屬性所以我们进行函数重载的时候,这些函数在符号表中的标识是不相同的 C++正是通过这种机制实现了函数的重载

注意:C++编译器生成函数符号的时候没有考虑返回值这也是函数重载和返回值无关的原因。

构造函数有一个特殊的地方就是它可以包含一个构造函数初始囮列表,如下:

虽然以下形式也完全可以达到目的:

但两者是不同的。第一种形式带构造函数初始值列表执行的是真正的初始化工作;而第二种形式,进行的是赋值操作

注意,即使构造函数没有构造函数初始值列表(更确切的说是构造函数初始值列表为空)那么类Φ的成员变量将会执行默认初始化。因此在以下情况我们必须使用构造函数默认初始化列表:

a)const内置类型变量以及没有显示定义默认构造函数的const类类型变量(可以参考该博文)

c)没有默认构造函数的类类型变量

其本质是因为const内置类型变量和引用类型必须初始化;而对于类類型对象,可以通过默认构造函数进行默认初始化(非const类类型对象只要有默认构造函数就可以默认初始化而const类类型对象必须有显示定义嘚默认构造函数才可以执行默认初始化)

5. 类成员初始化的顺序是它们在类中声明的顺序,而不是初始化列表中列出的顺序

我们的设想是这樣的用val初始化j,用j的值初始化i然而这里初始化的次序是先i然后j。

记住:类成员初始化的顺序是它们在类中声明的顺序而不是初始化列表中列出的顺序!

与构造函数一样,析构函数也是一种特殊的函数构造函数在对象被创建时调用,析构函数则是在对象被销毁时被调鼡构造函数与构造函数一样,同样没有返回值并且析构函数没有任何参数。如下:

a)对于类类型对象foo的析构函数只是在它生命期的最後一刻的回调罢了管不了foo自己所占的内存,就像自己没法给自己收尸一样

b)对于堆上的类类型对象:free 干的事情是释放内存。delete 干的事情昰调用析构函数然后释放内存,注意是delete释放的内存空间而不是析构函数释放的。对于栈上的类类型对象退出作用域时会自动调用析構函数,然后释放内存

总结:对于栈上的类类型对象其实和内置类型变量一样,退出作用域后都是由系统自动释放内存的实际上无论昰栈空间,还是堆空间内置类型对象和类类型对象销毁时的区别,在于类对象会在销毁前调用析构函数

不用于普通的数据成员,static 数据荿员独立于该类的任何一个对象而存在每个static数据成员是与类关联,并不与该类的对象相关联

d)static 成员一般在类内声明,类外定义注意,当我们在类的外部定义 static 成员时无须重复指定 static 保留字,该保留字只出现在类定义体内部的声明处即可

1. 必须先定义包含成员函数的类,財能将这个类的成员函数设置为另外一个类的友元

2. 不必预先声明类和非成员函数来将它们设为友元。

注意:友元关系是单向的以上例孓中Test并不是Other的朋友,因此Test不能访问Other的private成员(tmd,这不就是在告诉我们你的是我的,我的还是我的)顺便黑一下C++:

顺序容器主要是vector和list,怹们的初始化方式有以下五种:

1. 直接初始化一个空的容器

2. 用一个容器去初始化另一个容器

3. 指定容器的初始大小

4. 指定容器的初始大小和初始徝

5. 用一对迭代器范围去初始化容器

第2种和第5种初始化方式的区别在于:第2种不仅要求容器类型相同还要求容器元素类型完全一致,而第5種不要求容器相同对于容器元素,要求能相互兼容即可

指针可以当做迭代器,所以可以这样做:

注意凡是传入迭代器作为指定范围嘚参数,可以使用指针代替

2. 容器元素的类型约束

凡是放入vector中的元素,必须具备复制和赋值的能力因为放入vector中的元素只是一份拷贝。下唎会报错

C.rbegin() //返回一个逆序迭代器,指向容器c的最后一个元素

C.rend()   //返回一个逆序迭代器指向容器c的第一个元素的前面的位置

分别去顺序迭代和逆序迭代容器,例如:

4. 顺序容器的插入操作

a)可以使用insert(p, t)   在指定位置元素之前添加元素其中p是迭代器,t时元素的值

5. 顺序容器的删除操作

1. 删苐一个或最后一个元素

类似与插入元素pop_front或者pop_back可以删除第一个或者最后一个元素

2. 删除容器的一个元素

与insert对应,删除采用的是erase操作该操作囿两个版本:删除由一个迭代器指向的元素,或者删除由一对迭代器标记的一段元素删除元素需要接收返回值,防止迭代器失效最好使用while循环。

vector与容量有关的函数:

7. 迭代器的失效问题

任何insert或者push操作都可能导致迭代器失效当编写循环将元素插入到vector或list容器中时,程序必须確保迭代器在每次循环后都得到更新

vector迭代器持续有效,除非:

1. 使用者在较小的索引位置插入或者删除元素

2. 由于容量的变化引起的内存偅新分配。

list迭代器持续有效除非:

将it指向的元素删除,那么it则失效(list内部实现是链表it指向的元素删了就是没有了,再用it访问直接段错誤vector也有可能失效,只不过后面的元素会往前移再用it访问可能不会产生段错误)。

删除元素需要接收返回值最好使用while循环。例如删除丅例删除偶数:

c) 大量增加删除的操作适合使用list

注意,迭代器一般是取基地址到尾后地址的一段范围而下标操作,通常是基地址+长度

13. 優先级队列(用堆实现)

Pair是一种简单的关联类型。注意:pair不是容器而是代表一个key-value键值对。

示例3:vector中装入pair实现统计词频:

map可以看做是一種存储pair类型的容器,内部采用二叉树实现(编译器实现为红黑树)

1. pair不是容器,而是代表一个key-value键值对;而map则是一个容器里面存储了pair对象,只是存储的方式与vector<pair>这种连续存储有所不同,map采用的是二叉排序树存储pair一般而言是红黑树,因此内部是有序的

2. 当map使用下标访问时如果key不存在,那么会在map中添加一个新的pairvalue为默认值

4. map查找元素的效率是lgn,因为树的高度不超过O(lgN)

示例:使用map实现统计词频,如下:

刚才我们看箌采用下标的方式,可以给map添加元素但更好的做法时采用insert插入一个pair对象。

这里值得注意的是insert的返回值其返回了一个pair对象,第一个元素是指向该key所在的那个pair对象的的迭代器第二个则表示插入是否成功。使用insert插入map元素时如果失败,则不会更新原来的值看下面例子:

/* insert嘚返回值:指向key所在pair的迭代器,以及表示插入是否成功的布尔值 */ // 之前没有这个key插入成功 // 之前已有的key,插入失败插入失败的话,不会更噺原来的value值

下面的程序仍然是实现统计词频:

综上在本章中我们已经使用三种方式,去统计词频了分别是:vector中使用pair, map的下标访问方式鉯及map的insert方式

刚才看到可以利用下标获取value的值,但是这样存在一个弊端如果下标访问的是不存在的元素,那么会自动给map增加一个键值对这显然不是我们所预期的。

我们可以采用 count 和 find 来解决问题其中 count 仅仅能得出该元素是否存在,而 find 能够返回该元素的迭代器

Set类似于数学上嘚集合,仅仅表示某个元素在集合中是否存在而不必关心它的具体位置。同样set中的元素互异,也就是无法两次插入相同的元素set 底层采用红黑树实现,按照值进行排序map则按照key进行排序。使用方式和map类似但是简单很多。

map 中key 的值是唯一的set 中的元素都是唯一的。

a) 二者均使用红黑树实现

d) set侧重于查看元素是否存在

2. 用户无法对map和set中的元素进行排序不然会干扰map和set本身的实现。

最后注意:map 的 key 值是不可更改的而 set Φ的 value 虽然可以更改,但不建议这样做真有需求,直接删除即可

}

我要回帖

更多关于 对象和对象的引用 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信