《effective c++》笔记

《effective c++》 目录 https://blog.youkuaiyun.com/KangRoger/article/details/44706403

0 术语:
0.1 声明
0.1.1 变量: 非class中extern int x; 在类中int x
0.1.2 函数:  class GraphNode;
          int numDigits(int number);
0.2 定义:提供代码本体
int x; 在类外
int numDigits(int number)
{
......
}
class Widget {
......
};
0.3 初始化
构造函数
class B{
 public:
 B(int x=0,bool b= true);
}
0.4 调用
bool hasAcceptableQuality(Widget w); 声明
Widget aWidget; 定义
if (hasAcceptableQuality(aWidget)) 调用 

Widget w1;
Widget w2(w1)   定义=copy构造函数=编译
w1=w2;          赋值构造=执行
Widget w3=w2;   定义非赋值

item1 :
c++ 由4部分组成。c,object-oriented c++ ; template c++; stl
item2 :
2.1 const 和#define 区别

因为define在预处理器处理宏,所以不在编译的.o中的符号表中
前提补充,预编译如何处理#define,将宏展开,将符号替换,以致符号表中没有宏
const不在预处理中处理,const在编译中处理。
const double AspectRatio=1.653 这是变量定义。
访问权限:指类or对象与main无关。

2.2 inline 函数代替define 宏
#define 定义的类似函数,如#define CALL_WITH_MAX(a,b) f((a) >(b) ?(a):(b))
则会出错
用inline实现,会做参数检查等。不会出错。
item3:const
const 定义赋值有且仅有一次,不能改变。其他变量都可以重新赋值.const 引用也是一样,不可更改引用对象.
前后限制,整体意义法则
const char *p 值是固定的
char *const p 指针固定
限制参数const则在子函数内不会被改动。
传值是主函数 a=10 ; 子函数set (int a ){a=0};主不变;
const_cast:将常量转成非常量.
const char * c = "sample text";
char *cc = const_cast<char *> (c) ;

static_cast:静态转换,类c转换。
double d=3.14159265;
int i = static_cast<int>(d);

dymatic_cast: 向上转换
CBase* pb;
CDerived d;
pb = dynamic_cast<CBase*>(&d);

reinterpret_cast: 任意转换

item4:确定对象使用前已先被初始化
初始化列表有两点:1)是按照定义顺序init,不是按照初始化列表顺序init 2)const变量不能在构造函数中赋值,所以只能在初始化列表中或者类中定义而且同时赋值.
这条还涉及单例模式,没看懂,if 等于null就初始化创建?
https://blog.youkuaiyun.com/qq_29505369/article/details/101435132

 

三: item5-11 构造,拷贝,赋值,析构函数
0  拷贝构造函数的入参为什么是引用类型,不用是对象?
首先注意一点,传值被产生传值拷贝,函数中的对象是入参对象的副本,不是原来的对象.
使用对象会发生传值拷贝,对象值拷贝就会发生再次调用拷贝构造函数,造成无限递归.
传指针也是值拷贝。拷贝构造函数不能传值,与传入对象中有没有指针没关系,全是基本类型也不行。
CExample(CExample ex)     //传值
{
    m_nTest = ex.m_nTest;
}
CExample aaa(2);
Cexmaple ccc=aaa;相当于Cexmaple ccc(aaa);//调用CExample ccc的构造函数CExample(CExample ex) ; 
Cexmaple ex=aaa;相当于Cexmaple ex(aaa);//调用CExample ex的构造函数CExample(CExample ex) ;
Cexmaple ex1=aaa;这步是关键,ccc和ex是不同的对象,所以构造函数(成员函数)的临时变量是不同的,尽管名称看着一样.
//对象ccc的构造函数临时变量是ex, 对象ex的构造函数临时变量是ex1.
同理嵌套递归:Cexmaple ex2=aaa;//ex1和ex是不同对象,所以临时变量又不同。
无线递归.所以拷贝构造函数定义传值的时候会编译错误。
其他参考:https://blog.youkuaiyun.com/hackbuteer1/article/details/6545882

item5:
1 class缺省会创建构造函数,拷贝构造函数,赋值构造函数,析构函数. 默认为浅拷贝和浅赋值.
<高质量c++程序指南>的第9章有详细介绍如何实现这些构造函数,
或者C++笔试题 String类的实现: https://www.cnblogs.com/jwyue0520/archive/2012/12/03/2800160.html.
2 自定义对象的相应构造函数,则default不会产生。

item6:
为驳回编译器自动(暗自)提供的机能,可将相应的成员函数声明为private并且不予实现。
比如:默认拷贝构造函数是浅拷贝,赋值构造函数是浅赋值。​​
防止缺省构造函数浅拷贝而没有深拷贝发生错误,通常采用禁止缺省构造函数的方法.即将拷贝构造函数和赋值构造函数设置成delete.
class Uncopyable
{
Uncopyable(const Uncopyable &) =delete; // 阻止copying
Uncopyable &operator=(const Uncopyable &)=delete;
};
item 7
因为c++明白指出,当derived class对象经由一个base class指针删除,而该base class带着一个non-virtual析构函数,
其结果未有定义--实际执行时通常发生的是对象的derived成分没有被销毁。
而子类的析构函数也没被调用。
1 给base class 一个virtual 析构函数。此后删除derived class和基类部分
(虚函数表上的所有的函数,这上面有子有基)
vptr----子实现1----子实现2---基1---基2
2 抽象类不能实例化
3 最深层派生的那个class其析构函数最先被调用,然后是其每一个base class的析构函数被调用。
(和子类调用积累的构造函数,正好相反,是先执行基类的构造函数)

base class 应该声明一个virtual析构函数。如果class 带有任何virtual函数,它就应该拥有一个virtual析构函数。
(有virtual就会基指针指向子类)
classes 的设计目如果不是作为base classes使用,或不是为了具备多态性,就不该声明virtual析构函数。

item8:
析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们(不传播)或结束程序
如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class 应该提供一个普通(而非在析构函数中)执行操作
(try--catch)

item9
在构造和析构期间不要调用virtual函数,因为这类调用从不下降至derived class(比起当前执行构造函数和析构函数的那层)
P49 eg1。 本来基类是接口,希望调用子类的实现函数。但是在构造函数期间,先执行基类的,子类还没有创建。这样会出现下面两种情况。
1 由于 logTransaction 是transaction内的一个pure virtual函数,当pure virtual函数被调用,大多执行系统会中止程序
2 如果logTransaction 是个正常的virtual函数并在Transaction内带有一份实现代码。则发现子类本意使用自己的logTraction,结果却是积累的。
会出现指向子类的指针,本意call子类重载的函数,实际调用的是基类的函数.

solution:
无法使用virtual函数从base classes向下调用,在构造期间,你可以籍由“令derived classes将必要的构造信息向上传递至base class构造函数”

item10
令赋值操作符返回一个reference to *this

item11
同《高质量c++》 9.4和9.6的例子

item12 (没看例子)
copying 函数应该确保复制“对象内的所有成员变量”及“所有base class成分”
不要尝试以某个copying函数实现另一个copying函数。应该将共同机能放进第三个函数中,并由两个copying函数共同调用.

item13

智能指针是当local对象指针p,离开函数的时候。不用手动delete 对象指针p。会自动delete p。
智能指针share_ptr有引用计数。

item15 显示转化 a.get() 返回成员函数
隐式转换 operator A ()

item16
如果你在new表达式中使用[],必须在相应的delete表达式中也使用[].如果你在new表达式中不使用[],一定不要在相应的delete表达式中使用[]

item20 宁以pass-by-reference-to-const替换pass-by-value
下面这个论述是正确的,传值会产生总共是六次构造和六次析构。传引用没有。
https://blog.youkuaiyun.com/kangroger/article/details/42965331#t0
item22
1 封装(set,get)
2 访问权限限制,有的需要private

item23 c++可以有non-member的函数。java只有成员函数
namespace和class不同,前者可跨越多个源码文件而后者不能
分离将每个便利函数声明于各自头文件。分割non-member函数。但是不能分割成员函数,因为类是一个整体
item24 
result=oneHalf.operator*(2);
result=2.operator*(oneHalf);
论述很好
隐式转换的论述
const Rational temp(2);
result=oneHalf*temp
和igt的y=1一样

item25 : 考虑写出一个不抛出异常的swap函数

namespace std{ 
    template<typename T> 
    void swap(T& a, T& b) 
    { 
         T temp(a); 
          a=b; 
          b=temp; 
    } 
}

采用这种交换方式,3条执行语句将执行一次拷贝构造函数,两次赋值构造函数. 将产生3次深拷贝.
但是对于某些类型而言,这些复制可能无一必要。例如,class中含有指针,指针指向真正的数据。这种设计常见的表现形式是所谓的“pimpl手法“(pointer to implementation,条款31). 即仅仅需要复制类中指针内容.

class WidgetImpl{
public:
    ……
private:
    int a,b,c;              //数据很多,复制意味时间很长
    std::vector<double> b;
    ……
};

下面是pimpl实现

class Widget{
public:
    Widget(const Widget& rhs);
    Widget& operator=(const Widget& rhs
    {
        ……          //复制Widget时,复制WidgetImpl对象              
        *pImpl=*(ths.pImpl);
        ……
    }
    ……
private:
    WidgetImpl* pImpl;//指针,含有Widget的数据
};

如果置换两个Widget对象值,只需要置换其pImpl指针,但STL中的swap算法不知道这一点,它不只是复制三个Widgets,还复制WidgetImpl对象,非常低效。
我们希望告诉std::swap,当Widget被置换时,只需要置换其内部的pImpl指针即可,下面是基本构想,但这个形式无法编译(不能访问private)。即不需要拷贝内存,仅仅交换指针指向,没有内存操作.

namespace std{
    template<>      //这是std::swap针对T是Widget的特换版本,
    void swap<Widget>(Widget& a, Widget& b) //目前还无法编译
    {       //只需要置换指针
        swap(a.pImpl, b.pImpl); 
    }
}

基本原理就是描述的,之后的内容就是如何在语法层面将pImpl和template(需要先看temple使用),变成swap合法.不抛出异常参考Item19.
本item参考:https://blog.youkuaiyun.com/kangroger/article/details/43677283
实例分析:vector<int>类型变量vi, 则vector<int>(vi).swap(vi)是什么意思;
vector 内存空间只会增长,不会减少.
比如vi有10000 item。其中9999个erase。仅仅一个有效。但是空间仍然是10000.
    {
     std::vector<int> tmp;  
     ivec.swap(tmp);
    }    
    加一对大括号是可以让tmp退出{}的时候自动析构
 vi将一个有效的元素,存入tmp中。这样tmp仅仅有一个空间(无效部分不会copy)。然后i和tmp交互。则i只有一个空间。tmp有10000.然后tmp在出大括号后析构.
 (一句话,实际是3,4句话含义)
item 29
什么是 copy-and-swap 

  1. void swap(Widget& rhs); //详见条款29
  2. Widget::operator=(const Widget& rhs)
  3. {
  4. Widget temp(rhs);
  5. swap(temp);
  6. return *this;
  7. }

原理和《高质量c++程序设计》中关于防止赋值构造函数异常的论述一样. 先保存一份,防止中间异常指向了temp变量,出了子函数变成指向null.原来的值还丢失了.

以后参看:https://wenku.baidu.com/view/b2eaf97932687e21af45b307e87101f69e31fbb2.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值