More Effective C++ (运算符)

4.1:谨慎定义类型转换函数

<1>容易的方法是利用一个最新的编译器特性:explicit关键字

<2>C++编译器把">>"作为一个符号来解释,在两个">"间没有空格,语句会产生语法错误。

<3>隐式类型转换函数

示例代码如下:

 1 #include<iostream>
 2 using namespace std;
 3 
 4 template<class T>
 5 class Array
 6 {
 7 public:
 8     class ArraySize
 9     {
10         public:
11             ArraySize(int numElements):theSize(numElements)
12             {
13                 cout<<"flag ArraySize(int  numElements)"<<endl;
14             }
15             int size() const
16             {
17                 cout<<"flag  size()"<<endl; 
18                 return theSize;
19             }
20         private:
21             int theSize;
22     };
23 
24     Array(int lowBound,int highBound)
25         {};
26 
27     Array(ArraySize arsize)
28     {
29         cout<<"flag Array"<<endl;
30     };
31 };
32 void main()
33 {
34     Array<int>  a(10);
35 }
36 
37 /*
38 flag ArraySize(int  numElements)
39 flag Array
40  */

4.2:自增,自减操作符前缀形式和后缀形式

<1>重载函数间的区别决定于它们的参数类型上的差异,但是不论increament或者decrement的前缀还是后缀都只有一个参数。

为了解决这个语言问题,C++规定后缀形式有一个int类型参数,当函数被调用时,编译器传递一个0作为int参数的值给该函数。

<2>前缀自增:增加然后取回;后缀自增:取回然后增加

<3>前缀形式

示例代码如下:

 1 UPInt & UPInt::operator++()
 2 {
 3     *this += 1;   //增加
 4     return *this; //取回值
 5 }
 6 const UPInt UPInt::operator++(int)
 7 {
 8     UPInt oldValue = *this;   //保留原值
 9     ++(*this);          //增加
10     return oldValue;    //返回原值
11 }

<4>后缀操作符函数没有使用它的参数。这个参数仅仅只是为了区别前缀函数调用。

<5>如果在函数内部没有使用参数,许多编译器会显示警告信息。为了避免这些信息,最常用的方式就是省略掉参数名称

<6>很明显后缀函数必须返回一个对象,但是为什么是const对象呢?假设不是const:

假设不是 const 对象,下面的代码就是正确的:

这组代码与下面的代码相同: 

i.operator++(0).operator++(0);  

很明显, 第一个调用的operator++函数返回的对象调用了第二个operator++函数。 有两个理由导致我们应该厌恶上述这种做法:

第一,是与内置类型行为不一致。当设计一个类遇到问题时,一个好的准则是使该类的行为与int 类型一致。而int 类型不允许连续进行两次后缀 increment: 

int i; 

i++++; // 错误! 

第二个原因,是使用两次后缀 increment 所产生的结果与调用者期望的不一致。

如上所示,第二次调用operator++改变的值是第一次调用返回对象的值,而不是原始对象的值。因此如果:

i++++; 是合法的,i 将仅仅增加了一次。这与人的直觉相违背,使人迷惑(对于int类型和 UPInt 都是一样),所以最好禁止这么做。

C++禁止int 类型这么做,同时你也必须禁止你自己写的类有这样的行为。

最容易的方法是让后缀 increment 返回 const 对象。当编译器遇到这样的代码:  

i++++; // same as  

i.operator++(0).operator++(0);

它发现从第一个 operator++函数返回的 const 对象又调用 operator++函数,然而这个函数是一个 non-const 成员函数, 所以 const 对象不能调用这个函数。

如果你原来想过让一个函数返回 const 对象没有任何意义,现在你就知道有时还是有用的,后缀 increment和 decrement 就是例子。 (更多的例子参见 Effective C++ 条款 21) 


如果你很关心效率问题, 当你第一次看到后缀 increment函数时,你可能觉得有些问题。

这个函数必须建立一个临时对象以做为它的返回值, (参见条款M19) ,上述实现代码建立了一个显式的临时对象(oldValue) ,这个临时对象必须被构造并在最后被析构。前缀 increment 函数没有这样的临时对象。

由此得出一个令人惊讶的结论,如果仅为了提高代码效率,UPInt 的调用者应该尽量使用前缀 increment,少用后缀 increment,除非确实需要使用后缀 increment。

让我们明确一下,当处理用户定义的类型时,尽可能地使用前缀 increment,因为它的效率较高。


我们再观察一下后缀与前缀 increment 操作符。它们除了返回值不同外,所完成的功能是一样的,即值加一。

简而言之,它们被认为功能一样。那么你如何确保后缀increment和前缀increment的行为一致呢?

当不同的程序员去维护和升级代码时,有什么能保证它们不会产生差异?除非你遵守上述代码里的原则,这才能得到确保。

这个原则是后缀 increment和 decrement 应该根据它们的前缀形式来实现。你仅仅需要维护前缀版本,因为后缀形式自动与前缀形式的行为一致。


正如你所看到的,掌握前缀和后缀 increment和decrement 是容易的。

一旦了解了他们正确的返回值类型以及后缀操作符应该以前缀操作符为基础来实现的规则,就足够了。

4.3:不要重载“&&”,“||”,或“,”

<1>char *p; if((p!=0)&&(strlen(p)>10))......

注意这个if条件的写法顺序

<2>运算符重载要求:

4.4:理解各种不同含义的new和delete

<1>new operator 称为new操作符与operator new称为new操作

string *ps = new string("Memory Management");

使用的是new operator ,即就是new操作符,这种用法是不可改变的,功能总是相同的。

完成两部分任务:第一,分配足够的内存以便容纳所需类型的对象;第二,它调用构造函数初始化内存中的对象。

而我们所能改变的是如何为对象分配内存。

new操作符调用一个函数来完成必需的内存分配,你能够重写或重载这个函数来改变它的行为。

new操作符为分配内存所调用函数的名字是 operator new。

原型如下:

void * operator new(size_t size);

调用形式与其他函数一样:

void *rawMemory=operator new(sizeof(string));

placement new旨在完成一种情况,那就是假如我们已经申请到了内存,想要在该内存上构建对象,就选择placement new

三种情况代码如下:

 1 #include<iostream>
 2 using namespace std;
 3 
 4 class  Test
 5 {
 6     int a;
 7 public:
 8     Test(int data = 10):a(data)
 9     {
10         cout<<"Construction :"<<this<<endl;
11     }
12     ~Test()
13     {
14         cout<<"Destroy :"<<this<<endl;
15     };
16 };
17 void  main()
18 {
19     //new操作符(new operator)
20     Test *pa = new Test[2];  //完成两部分任务
21     delete []pa;
22     pa = NULL;
23     
24     //new函数(operator  new)  
25     void  *pb = operator  new(sizeof(Test));  //仅仅只是分配一块内存,类似于malloc
26     operator  delete(pb);
27     pb = NULL;
28     
29     //placement new 
30     void  *suffer = malloc(sizeof(Test));  
31     Test *pc = new(suffer)  Test(100);  //在已经申请的内存上建立自己的对象
32     pc->~Test();
33     free(suffer);
34     suffer = NULL;
35 }

总结:

如果想在堆上创建一个对象,应该用new操作符,它分配内存,同时又为对象调用构造函数。

如果仅仅想分配内存,就用operator new函数,它不会调用构造函数

如果你想定制自己的在堆对象被建立时的内存分配过程,应该重载写你自己的operator new函数,new操作符会调用你定制的operator new

如果想在一块已经分配好的内存里建立一个对象,使用placement new

<2>delete操作符与operator delete的关系与new操作符与operator new的关系一样

delete p;

导致编译器生成类似的代码:

p->~Class();

operator delete(p);

 

转载于:https://www.cnblogs.com/Braveliu/archive/2013/01/06/2847167.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/379021.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

大纲(二)

一、数据结构就是逻辑结构存储结构(物理结构)相应操作(算法实现) 二、逻辑结构 集合1:1 线性结构1:n 树m:n 图 主要是可以画到纸上进行分析的结构图就是逻辑结构&#xff0c;分析问题可以得出唯一一个逻辑结构 三、存储结构(物理结构) 顺序存储结构(例如:线性表)链式存储结…

php微信获取mediaid超出限制_Python实现每日微信自动打卡

众所周知&#xff0c;因为疫情的原因&#xff0c;很多高校和公司都要求员工每日在微信上进行打卡&#xff0c;来汇报自己的当前身体状态和所处地区。但绝大多数情况下&#xff0c;每天打卡的信息其实是不会变的&#xff0c;我们要做的就是进入公众号——自动登录点进打卡页面—…

[原创]INI文件的读写操作

INI文件的读写操作在程序开发中&#xff0c;很多人会把参数保存为ini文件格式&#xff0c;ini文件的一个好处是可以保存为一个结构体主结构&#xff0c;如 [User] Nametest UserIdtest [Server] ServerIp127.0.0.1 ServerPort80 …… 很方便也很容易区分&#xff0c;而且不同节…

Java ResourceBundle keySet()方法及示例

ResourceBundle类keySet()方法 (ResourceBundle Class keySet() method) keySet() method is available in java.util package. keySet()方法在java.util包中可用。 keySet() method is used to get all the existing keys from this ResourceBundle and its super bundles to …

ffplay 分析(音频从Frame(解码后)队列取数据到SDL输出)

《ffplay的数据结构分析》 《ffplay分析&#xff08;从启动到读取线程的操作&#xff09;》 《ffplay分析&#xff08;视频解码线程的操作&#xff09;》 《ffplay分析&#xff08;音频解码线程的操作&#xff09;》 《ffplay分析 &#xff08;视频从Frame(解码后)队列取数据到…

绪论(一)

一、问题驱动—>画出唯一的逻辑结构—>定义存储结构—>实现相应的操作 二、算法—>(定义\特点)步骤—>实现—>评价标准 算法有五大特点&#xff1a; 可行性确定性有穷性(有限性)0个或0个以上的输入至少一个以上的输出 评价标准&#xff1a; 时间复杂度 …

利用 dbghelp.dll 生成 dump 文件

dbghelp.dll windows的系统目录system32下&#xff0c;都有dbghelp.dll&#xff0c;但在实际使用时&#xff0c;往往会让exe加载自己目录下的dll&#xff0c;以避免系统目录下的dll版本不一导致的程序异常。 故一般都是用LoadLibrary()的方式加载Dll&#xff0c;先加载当前目录…

pandas 根据列名索引多列数据_Pandas 数据聚合与分组运算[groupby+apply]速查笔记

利用Pandas将数据进行分组&#xff0c;并将各组进行聚合或自定义函数处理。Pandas中Groupby分组与聚合过程导入模块import pandas as pd缩写df表示Dataframe对象分组df.groupby(col1)&#xff1a; 根据col1列将df全部列分组&#xff08;默认&#xff1a;axis0行&#xff09;df[…

杀毒软件对InstallShield编译过程以及安装包运行的影响

版权声明: 可以任意转载,转载时请务必以超链接形式标明文章原始出处和作者信息。在某些情况下&#xff0c;杀毒软件会导致InstallShield编译过程崩溃。比如 McAfee VirusScan Enterprise 8.5.0i版本&#xff0c;在某些情况下会在InstallScript工程编译过程中&#xff0c;将ISSe…

Java FileInputStream close()方法与示例

FileInputStream类close()方法 (FileInputStream Class close() method) close() method is available in java.io package. close()方法在java.io包中可用。 close() method is used to close this FileInputStream and free all system resources linked with this stream. c…

ffplay分析 (视频从Frame(解码后)队列取数据到SDL输出)

《ffplay的数据结构分析》 《ffplay分析&#xff08;从启动到读取线程的操作&#xff09;》 《ffplay分析&#xff08;视频解码线程的操作&#xff09;》 《ffplay分析&#xff08;音频解码线程的操作&#xff09;》 《ffplay 分析&#xff08;音频从Frame(解码后)队列取数据到…

线性结构节点类型(三)

一、线性结构 特点 第一个数据元素没有前驱最后一个数据元素没有后继1:1逻辑上相邻、物理上也相邻 类型 线性表(就是一张二维表)(为主研究对象)栈队列 学习方法 画逻辑结构—>定义存储结构—>实现相应的操作 二、线性表 线性结构 逻辑上的1:1存储结构 顺序存储结…

PL/SQL详细介绍

PL/SQL笔记PL/SQL块中只能直接嵌入SELECT,DML(INSERT,UPDATE,DELETE)以及事务控制语句(COMMIT,ROLLBACK,SAVEPOINT),而不能直接嵌入DDL语句(CREATE,ALTER,DROP)和DCL语句(GRANT,REVOKE) 1.检索单行数据 1.1使用标量变量接受数据 v_ename emp.ename%type; v_sal emp.sal%…

redis 备份导出rdb_Redis数据迁移利器之redisshake

“当需要进行Redis实例或集群数据迁移时&#xff0c;我们可以采用导出/导入的方式进行数据迁移&#xff0c;但当需要做数据异地灾备或双活时&#xff0c;再使用传统的方式就不合适了&#xff0c;我们需要借助工具(如redis-port/redis-shake)来完成。”redis-shake介绍redis-sha…

从Live Space搬家到这里

听说Live Space很快要关闭了&#xff0c;所以从http://peking2toronto.spaces.live.com/搬家到这里。转载于:https://www.cnblogs.com/pentest/archive/2010/08/29/1811726.html

java 方法 示例_Java Collectionsfrequency()方法与示例

java 方法 示例集合类的frequency()方法 (Collections Class frequency() method) frequency() method is available in java.util package. frequency()方法在java.util包中可用。 frequency() method is used to return the frequency of the given Object (obj) to the give…

线性结构常规操作(四)

定义存储结构(以单向链表为主) 对于链表的定义&#xff0c;通过结构体进行定义&#xff0c;包括两部分&#xff0c;一是数据域&#xff0c;另一个就是指针&#xff0c;用于指向下一个节点。 1&#xff0c;创建链表 定义链表&#xff1a; struct nodesq{int data;//数据域&a…

ffplay分析 (暂停 / 播放处理)

《ffplay的数据结构分析》 《ffplay分析&#xff08;从启动到读取线程的操作&#xff09;》 《ffplay分析&#xff08;视频解码线程的操作&#xff09;》 《ffplay分析&#xff08;音频解码线程的操作&#xff09;》 《ffplay 分析&#xff08;音频从Frame(解码后)队列取数据到…

源码 状态机_[源码阅读] 阿里SOFA服务注册中心MetaServer(1)

[源码阅读] 阿里SOFA服务注册中心MetaServer(1)0x00 摘要0x01 服务注册中心1.1 服务注册中心简介1.2 SOFARegistry 总体架构1.3 为什么要分层0x02 MetaServer2.1简介2.2 问题0x03 代码结构0x04 启动运行4.1 集成部署4.2 独立部署0x05 总体逻辑5.1 程序主体5.2 配置0x06 启动6.1…

HttpService远程校验

今天学了下HttpService&#xff0c;和大家分享一下。HttpService是用来读取远程数据的一个对象&#xff0c;数据格式为XML。 我做了一个登陆校验的功能&#xff0c;主要是通过HttpService将服务器端的用户数据得到&#xff0c;然后在客户端判断输入的用户名和密码是否存在。 主…