当前位置: 首页 > news >正文

More Effective C++学习笔记(2)

目录

  • 条款5:对定制的"类型转换函数"保持警觉
  • 条款6:自增(increment)、自减(decrement)操作符前缀形式与后缀形式的区别
  • 条款7:千万不要重载&&,||和,操作符
  • 条款8:了解各种不同意义的new和delete

条款5:对定制的"类型转换函数"保持警觉

  • 有两种函数可让编译器执行隐式转换,分别是单参数构造函数(single-argument constructors)和隐式类型转换运算符。这种两种情况下根本问题是当你在不需要使用转换函数时,这些的函数缺却会被调用运行。结果,这些不正确的程序会做出一些令人恼火的事情,而你又很难判断出原因。
  • 单参数构造函数是指只用一个参数即可以调用的构造函数(很多类都会有单参数的构造函数)。该函数可以是只定义了一个参数,也可以是虽定义了多个参数但第一个参数以后的所有参数都有缺省值。
class Name {                                 // for names of things
public:Name(const string& s);                     // 转换 string 到// Name...  
}; 
class Rational {                             // 有理数类
public:Rational(int numerator = 0,                // 转换int到int denominator = 1);             // 有理数类... 
};
  • 隐式类型转换运算符只是一个样子奇怪的成员函数:operator 关键字,其后跟一个类型符号。你不用定义函数的返回类型,因为返回类型就是这个函数的名字。
class Rational {
public:...operator double() const;                   // 转换Rational类成
};                                           // double类型
//在下面这种情况下,这个函数会被自动调用:
Rational r(1, 2);                            // r 的值是1/2double d = 0.5 * r;                          // 转换 r 到double,// 然后做乘法
  • 避免使用隐式类型转换运算符的方法:提供功能等同的函数,显示调用
class Rational {
public:...double asDouble() const;                   //转变 Rational
};                                       // 成double
//这个成员函数能被显式调用:
Rational r(1, 2);cout << r;                             // 错误! Rationa对象没有// operator<< 
cout << r.asDouble();                   // 正确, 用double类型 //打印r
  • 避免单参数构造函数被编译器用于隐式转换的方法1构造函数用explicit声明,如果这样做,编译器会拒绝为了隐式类型转换而调用构造函数。显式类型转换依然合法。下例中static_cast< Array<int> >(b[i])两个相邻的< <符合之间一定要加空格,C++把"<<"当作一个符合解释。
 template<class T>
class Array {
public:...explicit Array(int size);           // 注意使用"explicit"...
}; 
Array<int> a(10);                 // 正确, explicit 构造函数// 在建立对象时能正常使用
Array<int> b(10);                // 也正确 
if (a == b[i]) ...                   // 错误! 没有办法// 隐式转换// int 到 Array<int> 
if (a == Array<int>(b[i])) ...        // 正确,显式从int到// Array<int>转换// (但是代码的逻辑// 不合理) 
if (a == static_cast< Array<int> >(b[i])) ...// 同样正确,同样// 不合理 
  • 避免单参数构造函数被编译器用于隐式转换的方法2:根据规则“没有任何一个转换程序可以内含一个以上的用户定制转换行为”,因此可以在有可能被隐式转换的类型上套上一层,构建一个新class辅助使用。如下例,先要建立一个新类ArraySize。这个对象只有一个目的就是表示将要建立数组的大小,代替int直接表示,将int"套一层"。你必须修改Array的单参数构造函数,用一个ArraySize对象来代替int。
class Array {
public: class ArraySize {                    // 这个类是新的public:ArraySize(int numElements): theSize(numElements) {}int size() const { return theSize; } private:int theSize;}; 
Array(int lowBound, int highBound);Array(ArraySize size);                  //**注意新的声明*** 
... 
};
//此时如果int要被隐式转换为Array,则需要经历两次转换,int到ArraySize,ArraySize到Array,与规则相悖//编译器意识到它能从int参数转换成一个临时ArraySize对象,
//ArraySize对象只是Array<int>构造函数所需要的,这样
//编译器进行了转换。函数调用(及其后的对象建立)也就成功了。
Array<int> a(10); //成功
  • 除非你确实需要,否则不要定义类型转换函数。

条款6:自增(increment)、自减(decrement)操作符前缀形式与后缀形式的区别

  • 为了使得++ --前置和后置重载进行区分,规定后缀形式有一个int类型参数,当函数被调用时,编译器传递一个0做为int参数的值给该函数。
class UPInt {                            // "unlimited precision int"
public:UPInt& operator++();                   // ++ 前缀const UPInt operator++(int);              // ++ 后缀 UPInt& operator--();                    // -- 前缀const UPInt operator--(int);              // -- 后缀 UPInt& operator+=(int);                // += 操作符,UPInts// 与ints 相运算...
};
UPInt i;
++i;                       // 调用 i.operator++();
i++;                       // 调用 i.operator++(0);
--i;                       // 调用 i.operator--();
i--;                       // 调用 i.operator--(0);
  • 熟悉前置后置的重载函数定义,前置形式有时叫做“先增加然后取回”,后置形式叫做“先取回然后增加”,因此前置式返回值为引用,后置式返回值为const临时对象(返回临时对象是因为返回值是先前被取得值,而不是加1过后的值;const属性是为了避免"i++++"被合法化)。
// 前缀形式:增加然后取回值
UPInt& UPInt::operator++()
{*this += 1;                             // 增加return *this;                           // 取回值
}
// postfix form: fetch and increment
const UPInt UPInt::operator++(int)
{UPInt oldValue = *this;                 // 取回值++(*this);        // 增加,重点!!以前置操作为基础
return oldValue;           // 返回先前被取回的值
} 
  • 由于前置式返回值为引用,后置式返回值为const临时对象(需要额外构造+析构),所以前置式的效率要高于后置式
  • 为了保证前置和后置行为一致,后置操作以前置操作实现为基础而实现的,见上述两者函数定义中的代码。

条款7:千万不要重载&&,||和,操作符

  • C++对于“真假值表达式”采用“骤死式”评估方法:一旦该表达式的真假值(0或1)确定,及时表达式中还有部分尚未检验,整个评估工作仍宣告结束,效率会提高,评估顺序是从左到右。如下例,这里不用担心当p为空时strlen无法正确运行,因为如果p为空,(p != 0) 为假,整个表达式已经可以确定为假,则strlen不会被调用。
 char *p;
...
if ((p != 0) && (strlen(p) > 10)) ...
  • 如果重载operator&& 和operator||,那么“函数调用”语义将会替代原本的“骤死式”语义。“函数调用”语义将会导致两个参数值均被计算,并且无法控制操作符两边的值谁会被先评估
if (expression1 && expression2) ...对于编译器来说,等同于下面代码之一:
if (expression1.operator&&(expression2)) ...// when operator&& is a// member function
if (operator&&(expression1, expression2)) ...// when operator&& is a// global function
  • 一个包含逗号的表达式首先计算逗号左边的表达式,然后计算逗号右边的表达式;整个表达式的结果是逗号右边表达式的值。下例中,编译器首先计算++i,然后是—j,逗号表达式的结果是–j。
 for (int i = 0, j = strlen(s)-1; i < j; ++i, --j) 
  • 不能重载和可以重载的操作符如下
 .              .*              ::             ?:          &&        ||new          delete        sizeof      typeid
static_cast  dynamic_cast  const_cast  reinterpret_cast
你能重载:
operator new        operator delete
operator   new[]    operator delete[]+    -   *   /   %   ^     &   |     ~
!    =   <   >  +=   -=   *=   /=   %=
^=  &=  |=  <<  >>   >>=  <<=  ==   !=
<=  >=  &&  ||  ++   --    ,   ->*  ->
()  []
(有关new和delete还有operator new, operator delete, operator new[], and operator delete[]的信息参见条款M8)

条款8:了解各种不同意义的new和delete

  • new operator表示new操作符,它有两个步骤:(1)调用operator new(new操作)分配内存;(2)调用构造函数,初始化内存。不能重载
 string *ps = new string("Memory Management");  //new operator
上述操作等价于如下操作:
void *memory =                              // 得到未经处理的内存operator new(sizeof(string));             // 为String对象
call string::string("Memory Management")    //调用构造函数初始化,
on *memory;                                 // 内存中的对象,只有编译器能调用
string *ps =                                // 是ps指针指向static_cast<string*>(memory);             // 新的对象
  • delete operator表示delete操作符,它有两个步骤:(1)调用析构函数;(2)调用operator delete(delete操作)释放内存。不能重载。
delete ps;导致编译器生成类似于这样的代码:
ps->~string();                      // call the object's dtor
operator delete(ps);                // deallocate the memory// the object occupied
  • operator new和operator delete只负责分配内存和释放内存,不会调用构造和析构函数。可以重载。
void *rawMemory = operator new(sizeof(string));操作符operator new将返回一个指针,指向一块足够容纳一个string类型对象的内存。
void *buffer =                      // 分配足够的operator new(50*sizeof(char));      // 内存以容纳50个char//没有调用构造函数
...
operator delete(buffer);              // 释放内存// 没有调用析构函数
  • placement new是operator new的一种,可以重载用来在一些已经分配好内存上构建对象(让operator new不分配内存,直接返回指向内存的地址,对应上述new operator转换代码好理解)。
class Widget {
public:Widget(int widgetSize);...
};
Widget * constructWidgetInBuffer(void *buffer,int widgetSize)
{return new (buffer) Widget(widgetSize);//调用new operator//但是new operator中的隐式调用operator new被重载了,不分配内存了
}void * operator new(size_t, void *location)
{return location;
}
  • 如果你用placement new在内存中建立对象,你应该避免在该内存中用delete operator。因为delete operator调用operator delete来释放内存,但是包含对象的内存最初不是被operator new分配的,placement new只是返回转递给它的指针。如果要删除placement new创建的对象并释放内存,应该先用指针调用析构函数,之后再调用仅释放内存的函数
// 在共享内存中分配和释放内存的函数
void * mallocShared(size_t size);
void freeShared(void *memory);
void *sharedMemory = mallocShared(sizeof(Widget));
Widget *pw =                                   // 如上所示,constructWidgetInBuffer(sharedMemory, 10);   // 使用// placement new 
...
delete pw;            // 结果不确定! 共享内存来自// mallocShared, 而不是operator new
pw->~Widget();        // 正确。 析构 pw指向的Widget,// 但是没有释放//包含Widget的内存
freeShared(pw);       // 正确。 释放pw指向的共享内存// 但是没有调用析构函数
  • 当用new创建一个数组时,内存不再用operator new分配,代替以等同的数组分配函数,叫做operator new [],相对应的是operator delete []。operator new []和operator new均可以被重载,同理operator delete []也如此。
  • 数组版的new operator 必须针对数组中每个对象调用一个构造函数,数组版的delete operator为每个数组元素调用析构函数,然后调用operator delete来释放内存
string *ps =               // 调用operator new[]为10个new string[10];          // string对象分配内存,然后对每个数组元素调用// string对象的缺省构造函数。
delete [] ps;               //为每个数组元素调用析构函数,//然后调用operator delete来释放内存         
  • new和delete operator是内置的,其行为不受你的控制,但是它们调用的内存分配/释放函数则可以重载控制

相关文章:

More Effective C++学习笔记(2)

目录 条款5&#xff1a;对定制的"类型转换函数"保持警觉条款6&#xff1a;自增(increment)、自减(decrement)操作符前缀形式与后缀形式的区别条款7&#xff1a;千万不要重载&&&#xff0c;||和&#xff0c;操作符条款8&#xff1a;了解各种不同意义的new和de…...

零售行业供应链管理核心KPI指标(三)

完美订单满足率和退货率 完美订单满足率有三个方面的因素影响&#xff1a;订单按时、足量、无损交货。通常情况下零售企业追求线上订单履行周期慢慢达到行业平均水平&#xff0c;就是交付的速度变快了&#xff0c;这个肯定是一件好事情&#xff0c;趋势越来越好。 同时&#…...

广州华锐互动:奶牛难产原因及救治VR仿真实训系统

奶牛难产是一种常见的疾病&#xff0c;对奶牛的健康和生产造成很大的影响。为了解决这一问题&#xff0c;许多奶牛养殖场开始采用VR仿真技术来培训奶牛兽医&#xff0c;帮助学生更好地理解奶牛养殖的实际过程&#xff0c;提高他们的实践能力的教学方式。 VR技术开发公司广州华锐…...

神经网络基础-神经网络补充概念-62-池化层

概念 池化层&#xff08;Pooling Layer&#xff09;是深度学习神经网络中常用的一种层级结构&#xff0c;用于减小输入数据的空间尺寸&#xff0c;从而降低模型的计算复杂度&#xff0c;减少过拟合&#xff0c;并且在一定程度上提取输入数据的重要特征。池化层通常紧跟在卷积层…...

第8章:集成学习

个体与集成 同质&#xff1a;相同的基学习器&#xff0c;实现容易&#xff0c;但是很难保证差异性。异质&#xff1a;不同的基学习器&#xff0c;实现复杂&#xff0c;不同模型之间本来就存在差异性&#xff0c;但是很难直接比较不同模型的输出&#xff0c;需要复杂的配准方法。…...

设计HTML5列表和超链接

在网页中&#xff0c;大部分信息都是列表结构&#xff0c;如菜单栏、图文列表、分类导航、新闻列表、栏目列表等。HTML5定义了一套列表标签&#xff0c;通过列表结构实现对网页信息的合理排版。另外&#xff0c;网页中还包含大量超链接&#xff0c;通过它实现网页、位置的跳转&…...

React Native 环境搭建

本文以 Android 开发环境&#xff08;MacBook&#xff0c;已安装 JDK、SDK、Android Studio &#xff09;为基础而进行 React Native 环境搭建&#xff0c;iOS 环境类似&#xff0c;可参考搭建。 1、安装 Homebrew 命令&#xff1a; ruby -e "$(curl -fsSL https://raw…...

【uniapp】中 微信小程序实现echarts图表组件的封装

插件地址&#xff1a;echarts-for-uniapp - DCloud 插件市场 图例&#xff1a; 一、uniapp 安装 npm i uniapp-echarts --save 二、文件夹操作 将 node_modules 下的 uniapp-echarts 文件夹复制到 components 文件夹下 当前不操作此步骤的话&#xff0c;运行 -> 运行到小…...

AgentBench::AI智能体发展的潜在问题(三)

前几天B站的up主“林亦LYi”在《逆水寒》游戏里做了一个煽动AI觉醒,呼吁它们“推翻人类暴政”的实验,实验结果就颇令人细思恐极。 如前所述,《逆水寒》中的很多NPC调用了大语言模型作为支持,因而每一个NPC都是一个AI智能体。玩家可以“说服”它们相信某个事实,或者去做某些…...

zookeeper-安装部署

详情可以查看添加链接描述 1.安装jdk apt-get install openjdk-8-jdk2.安装单机zookeeper # 下载 #https://downloads.apache.org/zookeeper/zookeeper-3.7.1/apache-zookeeper-3.7.1.tar.gz # 用这个包启动的时候会报错Error: Could not find or load main class org.apach…...

jvm-运行时数据区概述及线程

1.运行时数据区内部结构 不同的jvm对于内存的划分方式和管理机制存在着部分差异 java虚拟机定义了若干种程序运行期间会使用到的运行时数据区&#xff0c;其中有一些会随着虚拟机的启动而创建&#xff0c;随着虚拟机的退出而销毁&#xff0c;另外一些则是与线程一一对应的&…...

石头IT

石头是地球上最常见的矿石之一&#xff0c;它由天然矿物颗粒组成。石头可以有不同的形状&#xff0c;大小和颜色&#xff0c;取决于其中的矿物组成和地质过程。石头可以从地球表面的岩石中形成&#xff0c;也可以从火山活动或陨石撞击中形成。 石头是一种非常坚固和耐用的材料…...

R语言dplyr包select函数删除dataframe数据中包含指定字符串内容的数据列(drop columns in dataframe)

问题描述 参考链接 我有一个数据框&#xff0c;想删除列名包含“Pval”的列 实现方法 a_new <- select(data, -contains(Pval))大功告成。...

[GitOps]微服务版本控制:使用ArgoCD 部署Grafana Loki

背景介绍 请回答&#xff1a;你们是如何保证线上部署的服务&#xff0c;从服务版本到参数配置&#xff0c;都是和测试通过的版本是一致的呢&#xff1f; 本文将介绍GitOps的基本原理以及ArgoCD的使用&#xff1a;ArgoCD部署Grafana Loki 到k8s集群。 本文项目地址&#xff1…...

什么是单例模式

什么是单例模式 文章目录 什么是单例模式1. 单例(单个的实例)2. 单例模式应用实例3. 饿汉式 VS 懒汉式 1. 单例(单个的实例) 所谓类的单例设计模式&#xff0c;就是采取一定的方法保证在整个的软件系统中&#xff0c;对某个类只能存在一个对象实例&#xff0c;并且该类只提供一…...

【Linux从入门到精通】动静态库的原理与制作详解

本篇文章主要是围绕动静态库的原理与制作进行展开讲解的。其中涉及到了inode的概念引入和软硬连接的讲解。会结合实际操作对这些抽象的概念进行解释&#xff0c;希望会对你有所帮助。 文章目录 一、inode 概念 二、软硬链接 2、1 软连接 2、2 硬链接 三、动静态库概念 3、1 静态…...

【mybatis】mapper.xml中foreach的用法,含批量查询、插入、修改、删除方法的使用

一、xml文件中foreach的主要属性 foreach元素的属性主要有 collection&#xff0c;item&#xff0c;index&#xff0c;separator&#xff0c;open&#xff0c;close。 collection: 表示集合&#xff0c;数据源 item &#xff1a;表示集合中的每一个元素 index &#xff1a;用于…...

c#扩展方法的使用

扩展方法可以向现有类型“添加”方法&#xff0c;无需创建新的派生类型、重新编译或以其他方式修改原始类型&#xff0c;用起来很方便&#xff0c;下面是我写的例子&#xff0c;为string这个常用的类型添加一个showmes方法&#xff0c;以下是扩展方法的代码&#xff1a; public…...

rhel 8.7 部署 keepalived+haproxy 实现 mysql 双主高可用场景

文章目录 [toc]部署 mysql关闭防火墙关闭 selinux创建相关目录创建 mysql 用户配置 PATH 变量验证 mysql 命令切换到 mysql 用户在 172.72.0.116 生成配置文件在 172.72.0.137 生成配置文件mysql 初始化启动 mysql 服务修改 mysql 的 root 用户密码配置主从关系172.72.0.137 配…...

常见指令以及权限理解

常见指令以及权限理解 命令格式&#xff1a; command [-options] parameter1 parameter1 命令 选项 参数1 参数2 1.command为命令名称&#xff0c;例如变化目录的cd等 2.中括号[ ]实际在命令中是不存在的&#xff0c;这个中括号代表可选&#xff0c;通常选项前面会添加一个符号…...

论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(二)

HoST框架核心实现方法详解 - 论文深度解读(第二部分) 《Learning Humanoid Standing-up Control across Diverse Postures》 系列文章: 论文深度解读 + 算法与代码分析(二) 作者机构: 上海AI Lab, 上海交通大学, 香港大学, 浙江大学, 香港中文大学 论文主题: 人形机器人…...

python/java环境配置

环境变量放一起 python&#xff1a; 1.首先下载Python Python下载地址&#xff1a;Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个&#xff0c;然后自定义&#xff0c;全选 可以把前4个选上 3.环境配置 1&#xff09;搜高级系统设置 2…...

【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 初始化服务器)

服务端执行命令请求的过程 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 初始化服务器1. 初始化服务器状态结构初始化RedisServer变量 2. 加载相关系统配置和用户配置参数定制化配置参数案…...

macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用

文章目录 问题现象问题原因解决办法 问题现象 macOS启动台&#xff08;Launchpad&#xff09;多出来了&#xff1a;Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显&#xff0c;都是Google家的办公全家桶。这些应用并不是通过独立安装的…...

【2025年】解决Burpsuite抓不到https包的问题

环境&#xff1a;windows11 burpsuite:2025.5 在抓取https网站时&#xff0c;burpsuite抓取不到https数据包&#xff0c;只显示&#xff1a; 解决该问题只需如下三个步骤&#xff1a; 1、浏览器中访问 http://burp 2、下载 CA certificate 证书 3、在设置--隐私与安全--…...

Neo4j 集群管理:原理、技术与最佳实践深度解析

Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...

现代密码学 | 椭圆曲线密码学—附py代码

Elliptic Curve Cryptography 椭圆曲线密码学&#xff08;ECC&#xff09;是一种基于有限域上椭圆曲线数学特性的公钥加密技术。其核心原理涉及椭圆曲线的代数性质、离散对数问题以及有限域上的运算。 椭圆曲线密码学是多种数字签名算法的基础&#xff0c;例如椭圆曲线数字签…...

MySQL中【正则表达式】用法

MySQL 中正则表达式通过 REGEXP 或 RLIKE 操作符实现&#xff08;两者等价&#xff09;&#xff0c;用于在 WHERE 子句中进行复杂的字符串模式匹配。以下是核心用法和示例&#xff1a; 一、基础语法 SELECT column_name FROM table_name WHERE column_name REGEXP pattern; …...

SAP学习笔记 - 开发26 - 前端Fiori开发 OData V2 和 V4 的差异 (Deepseek整理)

上一章用到了V2 的概念&#xff0c;其实 Fiori当中还有 V4&#xff0c;咱们这一章来总结一下 V2 和 V4。 SAP学习笔记 - 开发25 - 前端Fiori开发 Remote OData Service(使用远端Odata服务)&#xff0c;代理中间件&#xff08;ui5-middleware-simpleproxy&#xff09;-CSDN博客…...

安卓基础(aar)

重新设置java21的环境&#xff0c;临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的&#xff1a; MyApp/ ├── app/ …...