《effective c++》读书笔记

本文详细介绍了C++编程中的各种最佳实践,包括如何使用const、inline等关键字优化代码,构造和析构函数的最佳实践,资源管理策略,以及设计与声明方面的建议。此外,还讨论了继承与面向对象设计的原则。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

###1 让自己习惯C++重点内容

####条款2:尽量以const, enum, inline替换#define
1.#define的记号没有进入记录表,不利于调试追踪;
2.宏替换会出现目标码的多份拷贝,生成较大代码量;
3.无法利用#define创建一个class专属常量,#deine不能提供封装性;
4.对于static的成员变量,声明在头文件,定义在实现文件,并给它赋初值;
5.用inline函数替换宏完成计算;

####条款3:尽量使用const
1.注意const对于指针而言,用在星号前还是星号后的区别;
2.const用在迭代器上的效果;
2.将函数返回值定义为const,如operator*返回值,让用户定义类型与内置类型保持兼容;
3.Const成员函数,可让接口容易理解,有利编译器排错;
4.Const成员函数,让操作const对象成为可能;
5.non-const operator[]返回类型应该是reference to char, 不是char, 否则如th[0] = ‘x’,无法通过编译(见左值和右值);
5.两个成员函数因常量性不同,可被重载(但要注意,两个成员函数的参数的常量性不同,是不能被重载的,但是!如果参数是引用,那常量性不同还是可以重载的)
6.const成员函数中不能改变成员变量的值;
6.Const成员函数返回引用时,所指内容有可能会被修改;
7.改善C++程序效率的一个根本办法是以pass by reference-const方式传递对象,注意有reference和没有reference的区别;
7.让non-const成员函数调用const成员函数避免重复;
8.mutalbe对象可在const函数中被改变。
9.为什么处理的代码完全一样,还是会同时有const和non-const函数,个人认为这是为了操作const对象。

####条款4:确定对象被使用前已先被初始化
1.永远在使用对象前先就给它初始化;
2.确保每一个构造函数都将对象的每一个成员初始化;
3.对象的成员变量的初始化动作发生在进入构造函数本体之前,采用初始化成员列表比赋值更有效率,为一致性,应把所有成员用初始化列表初始化;
4.Const和references成员变量一定需要初始化,不能赋值;
5.成员变量总是以声明次序被初始化,最好总是以其声明次序为次序初始化;
6.定义于不同编译单元的non-local static对象的初始化相对次序无明确定义,可使用局部static对象(Singleton模式),但多线程不安全。

###2 构造/析构/赋值运算
####条款5:了解C++默默编写并调用哪些函数
1.如果你没有声明,编译器会自动为一个类声明default构造函数,copy构造函数,copy assignment操作符和析构函数;
2.唯有大概这些函数被需要(被调用),它们才会被编译器创建出来;
3.编译器产出的析构函数是non-virtual的;
4.一旦声明了一个构造函数,编译器就不再为它创建default构造函数;
5.编译器拒绝为“内含referece或const成员变量”的类生成copy assignment操作符;
6.如果base class把copy assignment操作符声明为private,那么编译器拒绝为derived类生成copy assignment操作符;

####条款6:若不想编译器自动生成的函数,就该明确拒绝
1.若对象是独一无二的,就应该阻止其拷贝,通过把copy构造函数和copy assignment操作符声明为private,并故意不实现,或使用uncopyable类继承,可办到。

####条款7:为多态基类声明virtual析构函数
1.当一个具有non-virtual析构函数的基类指针指向一个派生类时,用delete删除该基类指针时,调用的是基类的析构函数,因此会引发诡异的“局部删除”。
2.更广泛的说,当non-virtual函数被指针调用时,实际上是调用指针的类的成员函数,而virtual函数实际上是调用指针实际所指的对象的成员函数。
3.任何class只要带有virtual函数就几乎可以确定应该有一个virtual析构函数;
4.如果一个class不含virtual函数,通常表示它不意图用作一个base class。把不企图当做base类的class声明virtual是个馊主意,因为会增加内存。

####条款8:别让异常逃离析构函数
1.当异常抛出时,会进行栈展开以匹配catch,在展开过程中会销毁局部对象,若在销毁过程中局部对象的析构函数再次抛出异常,会导致不明确行为。因此不应该在析构函数抛出异常;
2.如一个vector被销毁时,内含的一个对象在析构中抛出了异常,其他的内部对象也会被销毁(调用析构函数),若第二个对象的析构函数又抛出异常,则会导致不明确行为;
3.在析构函数中处理异常的两个方法(没有太大吸引力),一是调用abort终止程序;而是用catch“吞咽”异常;
4.可以给客户提供一个机会处理异常(详见书本例子)。

####条款9:绝不在构造和析构函数中调用virtual函数
1.derived构造函数会先调用base构造函数,在base构造函数中被调用的virtual函数是base版本的;析构函数同理。

####条款10:令operator=返回一个reference to *this
1.内置类型及标准库提供的类型,赋值操作符都会返回一个reference指向操作符的左侧实参,自定义的类无特殊理由也应该遵循此协议。

####条款11:在operator=中处理“自我赋值”
1.由于指针或引用及继承体系,有时“自我赋值”行为并不明显;
2.在operator=的实现中,要注意自我赋值的情况,避免先将自身删除,另外要注意异常抛出的处理,可使用swap。

####条款12:复制对象时勿忘其每一个成分
1.若我们没有声明copy构造函数和copy assignment操作符,编译器会自动生成:将被拷贝对象的所有成员变量都做一份拷贝;
2.如果你自己声明copying函数,当你的实现代码几乎必然出错时,编译器也不会做出警告;
3.当你为自己的类添加新的成员变量时,自己实现copying函数将不会自动添加相应代码;
4.若你自己撰写derived class的copying函数,则必须手动将实参赋给base class的copying函数,否则derived class中的base成分将不会被拷贝;
5.总而言之,当你编写一个copying函数,请确保(1)复制所有local成员变量 (2)调用所有base classes内的适当的copying函数;
6.令copying assignment操作符调用copy构造函数,或用copy构造函数调用copying assignment操作符都是不合理的,千万别尝试。

###3 资源管理
####条款13:以对象管理资源
1.RAII:获得资源后立刻放进管理对象,管理对象运用析构函数确保资源被释放;
2.靠手动释放资源存在隐患,应使用auto_ptr管理资源,可自动调用析构函数释放资源;
3.若通过copy构造函数或copy assignment操作符复制auto_ptr,他们会变成null,而复制所得的指针将取得资源的唯一拥有权;
4.引用计数型指针shared_ptr可避免上述auto_ptr的缺点;
5.Auto_ptr和shared_ptr不能使用于array上。

####条款14:在资源管理类中小心copying行为
1.auto_ptr和shared_ptr一般只适用于heap-based资源,对于非heap-based类资源,你应该自己编写资源管理类;
2.当你自己编写RAII对象时,应该要注意其copying问题,一般有如下选择:1.禁止copying行为,如lock;2.对底层资源使用“引用计数法”(shared_ptr);3.复制资源管理对象时一并复制底部资源;4.转移底部资源拥有权,如auto_ptr。
3.Class的析构函数会自动调用其non-static成员变量的析构函数;
4.Copying函数(包括copy构造函数和copy assignment操作符)有可能由编译器创造出来,除非编译器做了你想做的事,否则应该自己编写。

####条款15:在资源管理类中提供对原始资源的访问
1.资源管理类是对抗资源泄露的堡垒,但有时要绕过资源管理类直接访问原始资源;
2.Auto_ptr和shared_ptr提供get成员函数提供原始指针,也重载了operator->和operator*,它们允许隐式转换至底部原始指针;
3.有时需要提供RAII对象内的原始资源,可提供一个显示转换函数,也可提供隐式转换函数,但隐式转换容易出错;
4.RAII class内返回原始资源的函数,与封装性矛盾,但RAII classes并不是为了封装某物而存在,它们的存在是为了确保一个特殊行为——资源释放。

####条款16:成对使用new和delete时要采取相同形式
1.当new一个数组时,应使用delete[],否则结果是未定义的;
2.避免typedef一个数组,因为可能会对数组使用delete而不是delele[]。

####条款17:以独立语句将newed对象置入智能指针
1.shared_ptr构造函数是explicit构造函数,无法将原始指针隐式转换为shared_ptr;
2.应将“new对象”的操作和“构造shared_ptr智能指针”操作连在一起,否则若两个操作中间有代码并出现异常,则会引起内存泄露。(参考书中例子)

###4 设计与声明
####条款18:让接口容易被正确使用,不易被误用
1.可通过建立新类型,限制类型上的操作,束缚对象值,加const等方法来防止客户错误的使用接口;
2.尽量让接口保持一致性,与内置类型的行为兼容;
3.可让factory函数直接返回shared_ptr,可防止客户资源泄露问题;
4.Shared_ptr智能指针可指定删除器;
5.Shared_ptr可消除cross_DLL problem?

####条款19:设计class犹如设计type

####条款20:宁以pass-by-reference-to-const替换pass-by-value
1.pass-by-value会耗费一次或多次构造函数和析构函数时间,有时代价是非常昂贵的;
2.pass-by-reference-to-const没有任何构造函数和析构函数被调用,效率要高得多;
3.当一个derived class对象以pass-by-value方式传递给bass class对象时,只会调用bass class构造函数,因此引起“对象切割”问题,而以pass-by-reference-to-const方式则可以解决;
4.对于内置类型,STL的迭代器和函数对象,使用pass-by-value更合适;
5.即使是“小型用户自定义类型”,也不推荐使用pass-by-value。

####条款21:必须返回对象时,别妄想返回其reference
1.当函数返回时,local对象即被销毁,因此函数返回local对象的reference或指针都是严重错误;
2.函数返回在Heap中new的对象的reference或指针,会存在资源泄露问题;
3.函数返回reference或指针指向一个定义与函数内部的static对象,首先会因此多线程安全问题,另外还有更深层的瑕疵;
4.一个“必须返回新对象”的函数的正确写法是:就让那个函数返回一个新对象。

####条款22:将成员变量声明为private
1.如果成员变量不是public,客户唯一访问对象的办法是访问成员函数,即public接口的每样东西都是函数,这是从语法一致性角度。
2.使用函数可以让你对成员变量的处理有更精确的控制,你可以实现“不准访问”,“只读访问”,“读写访问”,甚至“唯写访问”。
3.最重要的是封装性,将成员函数隐藏在函数接口背后,可以为“所有可能的实现”提供弹性,只有成员函数可以影响客户代码,若不隐藏成员变量,会破坏大量的客户代码;
4.成员变量的封装性与“成员变量改变时所破坏的代码数量”成反比,protect和public一样不能提供封装性。

####条款23:宁以non-member,non-friend替换member函数
1.作为一种粗糙的测量,越多函数能访问class内的成员数据,数据的封装性越低;
2.能够访问private成员变量的函数只有class内的member函数和friend函数,而non-menber,non-friend函数并不增加“能够访问class内之private成分”的函数数量,因此non-menber,non-friend函数能提供更好的封装性;
3.在C++中,一般的做法是让non-member函数位于class所在的同一个namespace中;
4.Namespace可跨越多个文件,利用namespace和头文件,可将一个class的多个“便利函数”分离,并有较低的编译相依关系。
条款24:若所有参数皆需类型转换,请为此采用non-member函数
1.令class支持隐式转换通常是个糟糕的主意,但也有例外,例如将整数转换为有理数class;
2.有关operator*的例子,具体请看书。

####条款25:考虑写出一个不抛异常的swap函数
(难度较大,可参看书本)
1.如果swap的缺省实现码对你的class或class template提供可接受的效率,就不要做额外的事(太难写了);
2.如果缺省实现版效率不足,则试着提供一个public swap成员函数;
3.成员版swap绝不可抛出异常,因为swap的一个最好应用是帮助classes提供强烈的异常安全性保障;
4.这一约束只施行于成员版!不可施行于非成员版,因为swap缺省版本是以copy构造函数和copy assignment操作符为基础,而这两者都允许抛出异常。

###5 实现
####条款26:尽可能延后变量定义式的出现时间
1.当你定义一个变量而其类型带有构造函数和析构函数,那么当程序控制流到达这个定义式时,你便得承受构造成本,当变量离开作用域时,你便得承受析构成本;
2.尽量延后变量定义式时间,可避免没必要的构造和析构;
3.“通过default构造函数构造出一个对象然后对它赋值”比“直接在构造时指定初值”效率差。
4.分析然后确定定义在循环内还是循环外。

####条款27:尽量少做转型操作
尽量少用就是了…

####条款28:避免返回handle指向对象内部成员
1.class的函数(public)不应返回内部成员变量或函数(private)的reference、指针和迭代器等,因为这样外部调用者可以通过这些handle修改对象的内部成员数据,降低其封装性,也会导致“虽然调用const成员函数却造成对象状态被修改”。
2.“返回一个handle代表对象内部成分”还会导致当对象被销毁后,handle成为“虚吊”的危险。

####条款29:为“异常安全”而努力是值得的
1.当异常抛出时,要注意保证:(1)不泄露任何资源(如锁);(2)不允许数据败坏;
2.声明throw()并不能保证绝不会抛出异常,那些性质由函数的实现决定;
3.应以智能指针管理对象内部的指针成员;
4.一个异常安全的策略是“copy and swap”;

####条款30:透彻了解inlining的里里外外
1.使用inline函数虽然可以避免函数调用所带来的开销,但也很可能会增加你的目标码,引起代码膨胀;
2.Inline函数通常一定置于头文件,因为大多数建置环境在编译过程中进行inlining。
3.Templates通常也被置于头文件内,但它的具现化与inlining无关?
4.Inline只是个申请,编译器可以加以忽略;
5.当程序要取某个inline函数的地址时,编译器必须为此函数生成一个outline本体,这意味着对一个函数调用有可能被inlined,也可能不被inlined;
6.即使你未使用函数指针,inline函数还是有可能未被inlined,如构造函数和析构函数;
7.即使无任何代码的构造函数,其也不太可能是inlined,因为编译器至少会在内调用其他成员和base class两者的构造函数。析构函数亦如此;
8.Inline函数无法随着程序库的升级而升级,若f是程序库的一个inline函数,当程序库改变f时,客户端程序必须重新编译。而对于non-inline函数,客户端只需重新连接即可;
9.大部分调试器对inline函数束手无策;
10.应将inlining限制在小型,被频繁调用的函数上。

####条款31:将文件间的依存关系降至最低
1.编译器必须在编译期间知道对象的大小,编译器获得这项信息的唯一方法是查询class的定义式;
2.使用pimpl(Pointer to implementation)设计,即在main class内只含一个指针成员,指向实现类,这样,main class与其实现类中的成员的实现细目分离,那些classes的任何实现的改变都不需要使用main class的客户端重新编译;
3.分离的关键在于以“声明的依存性”替换“定义的依存性”,有两个策略:1.如果使用object reference或object pointer可以完成任务,就不要使用object。2.如果能够,尽量以class声明式替换class定义式;
4.声明函数时,只需参数的声明式而无需定义式,只有当函数被调用时(函数定义式),参数的定义式才要曝光;
5.为声明式和定义式提供不同的头文件。如Data类,“Datafwd.h”只声明,“Data.h”定义具体Data类结构,“Data.cpp”做具体实现;
6.也可以用Interface class解除接口与实现之间的耦合,从而降低文件间的编译依存性;
7.用handle class和interface class都会增加额外的空间和时间成本,但不应因此而放弃他们;
8.不论handle class和interface class,一旦脱离inline函数,都无法有太大作为?

###6 继承与面向对象设计
####条款32:确定你的public继承塑模出is-a关系
1.“public继承”意味is-a。适用于base class身上的每一件事情也一定适用于derived class身上,因为每一个derived class对象也一定是base class对象。

####条款33:避免遮掩继承而来的名称
1.derived class 内的名称会遮掩base class内的名称。在public继承下从来没有人希望如此;
2.为了让被遮掩的名称再见天日,可使用using声明式或转交函数。

####条款34:区分接口继承与实现继承
1.声明一个pure virtual函数是为了derived class只继承函数接口;
2.声明impure virtual函数是为了让derived class继承函数的接口和缺省的实现;
3.声明non-virtual函数是为了令derived class继承函数的接口和一份强制性实现。

####条款35:考虑virtual函数以外的选择
1.使用NVI(non-virtual interface)方法,也就是“令客户通过public non-virtual成员函数间接调用private virtual函数”,可替换virtual函数,一个优点是可以在virtual函数调用前后做一些工作;
2.Derived class可以重新定义virtual函数,即使它是private;
3.将virtual函数替换为“函数指针成员变量”,这是Strategy模式的一种分解表现形式;
4.Strategy模式可提供更大的弹性,但也有弱点,因为当non-member成员函数访问class的non-public成分时,必须弱化封装性;
5.可由tr1::function完成Strategy模式。

####条款36:绝不重新定义继承而来的non-virtual函数
1.non-virtual函数是静态绑定的,例如,当pB被声明为一个pointer-to-B,通过pB调用的non-virtual函数永远是B定义的版本,即使pB指向一个类型为“B派生之class”对象;
2.Virtual函数是动态绑定的,通过pB调用的virtual函数是pB实际所指向的对象的版本,这就是所谓多态性;
3.条款7是此条款的一个特例。

####条款37:绝不重新定义继承而来的缺省参数值
1.清楚理解什么是“静态绑定”和“动态绑定”;
2.绝对不要重新定义一个继承而来的缺省参数值,因为缺省参数值都是静态绑定,而virtual函数确是动态绑定。

####条款38:通过复合塑模出has-a或“根据某物实现出”
1.“public继承”带有“is-a”,而复合意味着“has-a”或“is- implemented-in-term-of”。

####条款39:明智而审慎地使用private继承
1.如果class之间的继承关系是private,则编译器不会自动将一个derived class对象转换为base class对象,其二,由private base class继承而来的所有成员,在derived class中都会变成private属性,纵使它们在base class中原来是protected或public属性;
2.Private继承意味着implemented-in-terms-of,用条款34的术语,private继承意味着只有实现部分被继承,接口部分应略去;
3.尽量使用复合而非private,首先,复合可以“阻止derived class重新定义virtual函数”,第二,复合更有利于降低编译依存性;
4.Private继承只用于“当derived class需要访问protected base class的成员,或需要重新定义继承而来的virtual函数”,或者某些“激进情况”。

基于数据挖掘的音乐推荐系统设计与实现 需要一个代码说明,不需要论文 采用python语言,django框架,mysql数据库开发 编程环境:pycharm,mysql8.0 系统分为前台+后台模式开发 网站前台: 用户注册, 登录 搜索音乐,音乐欣赏(可以在线进行播放) 用户登陆时选择相关感兴趣的音乐风格 音乐收藏 音乐推荐算法:(重点) 本课题需要大量用户行为(如播放记录、收藏列表)、音乐特征(如音频特征、歌曲元数据)等数据 (1)根据用户之间相似性或关联性,给一个用户推荐与其相似或有关联的其他用户所感兴趣的音乐; (2)根据音乐之间的相似性或关联性,给一个用户推荐与其感兴趣的音乐相似或有关联的其他音乐。 基于用户的推荐和基于物品的推荐 其中基于用户的推荐是基于用户的相似度找出相似相似用户,然后向目标用户推荐其相似用户喜欢的东西(和你类似的人也喜欢**东西); 而基于物品的推荐是基于物品的相似度找出相似的物品做推荐(喜欢该音乐的人还喜欢了**音乐); 管理员 管理员信息管理 注册用户管理,审核 音乐爬虫(爬虫方式爬取网站音乐数据) 音乐信息管理(上传歌曲MP3,以便前台播放) 音乐收藏管理 用户 用户资料修改 我的音乐收藏 完整前后端源码,部署后可正常运行! 环境说明 开发语言:python后端 python版本:3.7 数据库:mysql 5.7+ 数据库工具:Navicat11+ 开发软件:pycharm
MPU6050是一款广泛应用在无人机、机器人和运动设备中的六轴姿态传感器,它集成了三轴陀螺仪和三轴加速度计。这款传感器能够实时监测并提供设备的角速度和线性加速度数据,对于理解物体的动态运动状态至关重要。在Arduino平台上,通过特定的库文件可以方便地与MPU6050进行通信,获取并解析传感器数据。 `MPU6050.cpp`和`MPU6050.h`是Arduino库的关键组成部分。`MPU6050.h`是头文件,包含了定义传感器接口和函数声明。它定义了类`MPU6050`,该类包含了初始化传感器、读取数据等方法。例如,`begin()`函数用于设置传感器的工作模式和I2C地址,`getAcceleration()`和`getGyroscope()`则分别用于获取加速度和角速度数据。 在Arduino项目中,首先需要包含`MPU6050.h`头文件,然后创建`MPU6050`对象,并调用`begin()`函数初始化传感器。之后,可以通过循环调用`getAcceleration()`和`getGyroscope()`来不断更新传感器读数。为了处理这些原始数据,通常还需要进行校准和滤波,以消除噪声和漂移。 I2C通信协议是MPU6050与Arduino交互的基础,它是一种低引脚数的串行通信协议,允许多个设备共享一对数据线。Arduino板上的Wire库提供了I2C通信的底层支持,使得用户无需深入了解通信细节,就能方便地与MPU6050交互。 MPU6050传感器的数据包括加速度(X、Y、Z轴)和角速度(同样为X、Y、Z轴)。加速度数据可以用来计算物体的静态位置和动态运动,而角速度数据则能反映物体转动的速度。结合这两个数据,可以进一步计算出物体的姿态(如角度和角速度变化)。 在嵌入式开发领域,特别是使用STM32微控制器时,也可以找到类似的库来驱动MPU6050。STM32通常具有更强大的处理能力和更多的GPIO口,可以实现更复杂的控制算法。然而,基本的传感器操作流程和数据处理原理与Arduino平台相似。 在实际应用中,除了基本的传感器读取,还可能涉及到温度补偿、低功耗模式设置、DMP(数字运动处理器)功能的利用等高级特性。DMP可以帮助处理传感器数据,实现更高级的运动估计,减轻主控制器的计算负担。 MPU6050是一个强大的六轴传感器,广泛应用于各种需要实时运动追踪的项目中。通过 Arduino 或 STM32 的库文件,开发者可以轻松地与传感器交互,获取并处理数据,实现各种创新应用。博客和其他开源资源是学习和解决问题的重要途径,通过这些资源,开发者可以获得关于MPU6050的详细信息和实践指南
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值