当前位置: 首页 > 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;通常选项前面会添加一个符号…...

SciencePlots——绘制论文中的图片

文章目录 安装一、风格二、1 资源 安装 # 安装最新版 pip install githttps://github.com/garrettj403/SciencePlots.git# 安装稳定版 pip install SciencePlots一、风格 简单好用的深度学习论文绘图专用工具包–Science Plot 二、 1 资源 论文绘图神器来了&#xff1a;一行…...

day52 ResNet18 CBAM

在深度学习的旅程中&#xff0c;我们不断探索如何提升模型的性能。今天&#xff0c;我将分享我在 ResNet18 模型中插入 CBAM&#xff08;Convolutional Block Attention Module&#xff09;模块&#xff0c;并采用分阶段微调策略的实践过程。通过这个过程&#xff0c;我不仅提升…...

蓝桥杯 2024 15届国赛 A组 儿童节快乐

P10576 [蓝桥杯 2024 国 A] 儿童节快乐 题目描述 五彩斑斓的气球在蓝天下悠然飘荡&#xff0c;轻快的音乐在耳边持续回荡&#xff0c;小朋友们手牵着手一同畅快欢笑。在这样一片安乐祥和的氛围下&#xff0c;六一来了。 今天是六一儿童节&#xff0c;小蓝老师为了让大家在节…...

第一篇:Agent2Agent (A2A) 协议——协作式人工智能的黎明

AI 领域的快速发展正在催生一个新时代&#xff0c;智能代理&#xff08;agents&#xff09;不再是孤立的个体&#xff0c;而是能够像一个数字团队一样协作。然而&#xff0c;当前 AI 生态系统的碎片化阻碍了这一愿景的实现&#xff0c;导致了“AI 巴别塔问题”——不同代理之间…...

拉力测试cuda pytorch 把 4070显卡拉满

import torch import timedef stress_test_gpu(matrix_size16384, duration300):"""对GPU进行压力测试&#xff0c;通过持续的矩阵乘法来最大化GPU利用率参数:matrix_size: 矩阵维度大小&#xff0c;增大可提高计算复杂度duration: 测试持续时间&#xff08;秒&…...

12.找到字符串中所有字母异位词

&#x1f9e0; 题目解析 题目描述&#xff1a; 给定两个字符串 s 和 p&#xff0c;找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义&#xff1a; 若两个字符串包含的字符种类和出现次数完全相同&#xff0c;顺序无所谓&#xff0c;则互为…...

MySQL 8.0 OCP 英文题库解析(十三)

Oracle 为庆祝 MySQL 30 周年&#xff0c;截止到 2025.07.31 之前。所有人均可以免费考取原价245美元的MySQL OCP 认证。 从今天开始&#xff0c;将英文题库免费公布出来&#xff0c;并进行解析&#xff0c;帮助大家在一个月之内轻松通过OCP认证。 本期公布试题111~120 试题1…...

【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分

一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计&#xff0c;提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合&#xff1a;各模块职责清晰&#xff0c;便于独立开发…...

Swagger和OpenApi的前世今生

Swagger与OpenAPI的关系演进是API标准化进程中的重要篇章&#xff0c;二者共同塑造了现代RESTful API的开发范式。 本期就扒一扒其技术演进的关键节点与核心逻辑&#xff1a; &#x1f504; 一、起源与初创期&#xff1a;Swagger的诞生&#xff08;2010-2014&#xff09; 核心…...

使用Matplotlib创建炫酷的3D散点图:数据可视化的新维度

文章目录 基础实现代码代码解析进阶技巧1. 自定义点的大小和颜色2. 添加图例和样式美化3. 真实数据应用示例实用技巧与注意事项完整示例(带样式)应用场景在数据科学和可视化领域,三维图形能为我们提供更丰富的数据洞察。本文将手把手教你如何使用Python的Matplotlib库创建引…...