您的位置:首页 > 编程语言 > C语言/C++

对“Professional C++” 中若干知识点的小结与思考

2014-03-22 10:09 281 查看
书是非常好的书,很适合有一定cpp基础想较深入学习的孩子们。广告不多说,直接正文。

1.在栈中调用默认构造函数来创建对象时,不要使用圆括号(即不要用函数调用语法)。

例: Cell myCell();//错误用法,但该行不报错,相当于声明了一个myCell无参函数,其中返回值为Cell

myCell.setValue(6);//该行报错,编译器认为你将函数名当成对象来使用

而在堆中时。需要使用圆括号(函数调用语法)。

2.在构造函数体内对某对象赋值时,并没有实际创建该对象,而只是改变其值而已。因此,对于没有默认构造函数的对象数据成员,应当放到ctor-initializer(构造函数初始化器)中进行初始化。

/*

* ctor-initializer形如

* Cell::Cell(): mValue(0),mString("") //ctor-initializer, 能在创建数据成员的同时赋初值

* { //normal init }

*/

3.const方法的工作原理是将方法内用到的数据成员都标记为const引用。因此试图修改数据成员时,编译器报错。

4.用explicit关键字标记构造函数从而禁止特定类型隐式转换为该类的对象。

/*

* explicit Cell (const string& initValue);

* 注意只能在类定义当中(而非函数定义)使用,且只适用于只有一个参数的构造函数(毕竟是防隐式转换,对吧)

*/

5.使用接口类可以彻底将接口与实现分离,同时对具体实现类所作的任何改动均不影响接口,故包含该头文件的客户不需重新进行编译。

接口类给出与实现类一样的public方法,且只有一个数据成员:即指向实际实现类对象的一个指针

//至于此处为什么不用c++大力倡导的引用而是用指针呢?

//我的想法是,结合设计模式考虑,引用在创建时必须初始化,这样就无法进行灵活的“延迟初始化”了。

6.当子类中的方法与父类中的同名方法的参数列表不同时,实际上是定义了一个全新的方法。

此时可用using关键字显示包含该方法的超类定义。

/*

*using mySuperClass::someMethod;

*virtual void someMethod(int i);

*/

若只有新方法的话,将隐藏原始方法,即使子类对象也无法调用。

/*

*而使用overide关键字能避免子类因创建新的同名方法导致原始方法被隐藏,编译器会报错

*实际上当超类的参数列表修改了而忘记修改所有子类的相应参数列表时这种情况也就发生了

*virtual void someMethod(int i) overide;

*/

7.无法重写静态方法,方法不能既是虚的又是静态的。

若子类中存在的静态方法与超类中的静态方法同名,实际上是两个独立的方法。

当超类引用的实际指向是子类对象时,改引用对静态方法的调用并非子类版本,而是超类版本。原因是当调用静态方法时,编译器不关心对象实际是什么,只关心编译时类型。

8.子类是能够重写超类中的private和protected方法从而改变一些内在行为的。

9.子类能将超类的public方法重写为protected,此时子类对象无法调用该方法,但超类引用(或指针)能。

同理,超类中的protected方法若被重写为public,则子类对象能访问,而超类引用不能。

也即是说,方法的对外访问权限是依据编译时类型,而不是实际对象。

10.方法为virtual时,相应类会使用名为虚表的特点内存区域调用正确的实现(而非在编译时硬编码),虚表有微量开销。//Java默认将所有方法都声明为virtual。

11.无法创建未命名值的引用,除非该引用是一个const值。

//如 const int& ref = 5;

12.双&号 && 表示右值引用,作为函数参数时能接受右值实参。

13.移动语义是只对成员变量进行表层复制(而非深层复制),然后转换已分配内存的所有权,从而阻止悬挂指针以及内存泄漏,提高移动的性能。

当第二个对象是在复制或赋值后被销毁的临时对象,编译器就会使用移动构造函数和移动赋值运算符。他们将成员变量从源对象表层复制到新对象,然后将源对象变量的值设为空值。此时内存权转移,而源对象销毁时也无法释放该内存。

可通过std::move() 显式调用。

template< typename T>

void swapMove(T& a, T& b){

T temp(std::move(a));

a = std::move(b);

b = std::move(temp);

}

14.const关键字应用于直接位于它左边的任何内容。

int const * const ip = nullptr;

前一个const表明指针所指的值无法改变。而后一个是指指针本身无法改变。

另外将前一个const放在开头的 int 的左端和右端是等价的。即上语句等价于

const int * const ip = nullptr;

15.c++中每个源文件都是单独编译的,编译得到的目标文件彼此链接,源文件中每个名称都有一个内部或外部的链接。外部链接意味着这个名称在其他源文件中也有效,内部链接(也称为静态链接)意味着在其他源文件中无效。

默认情况下,函数及全局变量都拥有外部链接,可用static 将名称指定为内部。//当然,是用匿名名称空间也有同样效果

16.在抛出一个异常的时候,被抛出的值或者对象会被复制,即通过复制构造函数从旧对象构造新对象。

复制是必须的,因为原始对象在栈中的位置较高,因此可能在异常被捕获前超出作用域而被销毁,其所占内存被回收。

若动态分配了内存,则必须编写析构函数、复制构造函数以及赋值构造函数

17.异常被抛出,并找到相应的catch 的时候,栈会释放所有中间栈帧,并直接回到定义catch处理程序的栈层。栈的释放会正确销毁所有局部变量,但动态分配的内存(//准确地说是直接在堆中分配给指针变量的动态内存)不会被销毁,从而可能导致内存泄漏。 //比如原本要释放内存的代码行还没被执行就抛出异常了。

在c++11以前,该问题只能用“捕获,清理并重新抛出”这样的繁琐方法。

现在可以使用智能指针这一特性。

智能指针对象在栈中而非堆中分配,无论什么时候销毁该对象,对会释放底层的资源。

unique_ptr<string> str(new string("hello"));

18.若异常离开构造函数,对象的析构函数将不被调用。因此异常出现在构造函数当中且离开构造函数之前,需清理所有资源,并释放构造函数中已分配的所有内存。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: