《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
void swap(Widget& rhs); //详见条款29
Widget::operator=(const Widget& rhs)
{
Widget temp(rhs);
swap(temp);
return *this;
}
原理和《高质量c++程序设计》中关于防止赋值构造函数异常的论述一样. 先保存一份,防止中间异常指向了temp变量,出了子函数变成指向null.原来的值还丢失了.
以后参看:https://wenku.baidu.com/view/b2eaf97932687e21af45b307e87101f69e31fbb2.html