Effective C++

Effective C++,第1张

文章目录
  • 前言
  • 一、写在前面的话
  • 二、第一章 让自己习惯C++
    • 条款01:视C++为一个语言联邦
    • 条款02:尽量以const,enum,inline替换#define
  • 总结


前言

记录《Effective C++》的学习。


一、写在前面的话

术语:

extern int x;   //对象object声明式
std::size_t numDigits(int number);  //函数function声明式
class Widget;
template  //模板 template声明式
  class GraphNode;

size_t 位于命名空间std内;

初始化由构造函数执行。所谓default构造函数是一个可被调用而不带任何实参者,这样的构造函数要不没有参数,要不就是每个参数都有缺省值。

class A
{
  public:
    A();
}

class B
{
  public:
    explicit B(int x = 0, bool b = true);
}
构造函数被声明为explicit,这可阻止它们被用来执行隐式类型转换`(implicit type conversions)`,但它们仍可被用来执行显示类型转换`(explicit type conversions)`。

拷贝构造函数 copy构造函数被用来“以同类型对象初始化自我对象”。

拷贝引用 copy assignment *** 作符被用来“从另一个同类型对象中拷贝其值到自我对象”。

class Widget
{
public:
    Widget(); //default构造函数
    Widget(const Widget& rhs); //copy构造函数
    Widget& operator=(const Widget& rhs); //copy assignment *** 作符

};

Widget w1;  //调用default构造函数
Widget w2(w1); //调用copy构造函数
w1 = w2; //调用copy assignment *** 作符

当你看到赋值符号时要小心,因为“=”语法也可用来调用copy构造函数:
  Widget w3 = w2;  //调用copy构造函数!
  
拷贝copy构造和拷贝copy赋值的区别:
  如果一个新对象被定义 如w3,一定会有一个构造函数被调用,不可能调用赋值 *** 作。
  如果没有新对象被定义 如w1 = w2, 就不会有构造函数被调用,是赋值 *** 作被调用。
  
拷贝构造函数,定义了一个对象如何以值传递(passed by value)。
例子:
bool hasAcceptableQuality(Widget w);
...
Widget aWidget;
if(hasAcceptableQuality (aWidget))
...

参数w是以传值by value的方式传递给函数hasAcceptableQuality。所以在上述调用中aWidget被复制到w体内。
这个复制动作有Widget的copy构造函数完成。
Pass-by-value意味着“调用copy构造函数“。
一般不推荐使用by value以传值的方式传递。

构造函数ctor 析构函数dtor


二、第一章 让自己习惯C++
条款01:视C++为一个语言联邦

c++面向对象,多重范型编程语言,一个同时支持过程形式procedural、面向对象形式object-oriented、函数形式functional、泛型形式generic、元编程形式Metaprogramming的语言。

C++的次语言sublanguage:

  1. C.

    C++以C为基础。区块blocks ,语句statements,预处理preprocessor,内置数据类型built-in data types,数组arrays,指针pointers。但是C没有模板templates,没有异常exceptions,没有重载overloading。

  2. Object-oriented C++.面向对象

    类classes(构造函数与析构函数)、封装encapsulation、继承inherititance、多态polymorphism、virtual虚函数(动态绑定)等等。

  3. Template C++. 泛型编程部分

    templates威力强大,它们带来了崭新的编程范型programming paradigm,也就是所谓的template Metaprogramming(TMP,模板元编程)。

  4. STL. template程序库

    它对容器containers、迭代器iterators、算法algorithms、以及函数对象function objects的规约有极佳的紧密配合与协调。

pass-by-value 传值, pass-by-reference传参。

用户自定义 user-defined


条款02:尽量以const,enum,inline替换#define
  1. 这个条款或许改为“宁可以编译器替换预处理器”比较好。
  2. 因为或许#define 不被视为语言的一部分。
#define ASPECT_RATIO 1.653

 记号名称ASPECT_RATIO也许从未被编译器看见;也许在编译器开始处理源码之前它就被预处理移走了。
 于是记号ASPECT_RATIO有可能没进入记号表symbol table内。
 于是在运用这个常量但获得一个编译错误信息,因为这个错误信息也许会提到1.653而不是ASPECT_RATIO。
 这个问题也可能出现在记号式调试器symbolic debugger中,原因相同,多使用的名称可能并未进入记号表symbol table 中。
 
解决办法:
    以一个常量替换上述的宏#define;
    const double AspectRatio = 1.653;   //大写名称通常用于宏,因此这里改变名称写法。
    
   作为一个语言常量,AspectRatio肯定会被编译器看到,当然会被进入记号表内。
   此外,对于浮点常量floating point constant 而言,使用常量可能比使用#define 导致较小量的码。
   因为预处理器”盲目地将宏名称ASPECT_RATIO替换为1.653“,可能导致目标码object code出现多份1.653,
   若改用常量AspectRatio绝不会出现相同情况。

  1. 以常量替换#define的两种情况:

    第一个是定义常量指针constant pointers.

    由于常量定义式通常被放在头文件内(以便被不同的源码含入),因此有必要将指针(而不只是指针所指之物)声明为const。

例如若要在头文件内定义一个常量的(不变的)char* based字符串,必须写两次const:
  const char* const authorName = "Scott Meyers";
  
  string对象通常比其前辈char*-based合宜。所以上述的authorName往往定义为这样更好些:
    const std::string authorName("Scott Meyers");

  第二个是class专属常量。

    为了将常量的作用域`scope` 限制于class内,必须让常量作为class的一个成员member;而为了确保常量至多(最多)只有一份实体,必须将常量声明为一个static成员:
class GamePlayer
{
  private:
    static const int NumTurns = 5;  //常量声明式
    int scores[NumTurns];           //使用该常量
    ...
}

然而NumTurns 的声明式而不是定义式。
通常C++要求对所使用的任何东西都提供一个定义式,
但是如果它是个class专属常量又是static且为整数类型 integral type,例如ints, chars, bools.需要进行特殊处理。

只要不取它们的地址,可以声明并使用它们,不用提供定义式。
如果要取某个class专属常量的地址,必须另外提供定义式:
    const int GamePlayer::NumTurns; //NumTurns的定义
    这个式子放入实现文件,而不是头文件中,由于class常量已经在声明时获得初值。因此在定义时不可以再设初值。 


      请注意,我们无法利用#define创建一个class专属常量,因为#defines并不重视作用域(scope)。一旦宏被定义,它就在其后的编译过程中有效(除非在某处被#undef)。这意味#defines不仅不能够用来定义class专属常量,也不能够提供任何封装性,也就是说没有所谓private#define这样的东西。而当然const成员变量是可以被封装的,NumTurns就是。

    旧式编译器也许不支持上述语法,它们不允许static成员在其声明式上获得初值。此外所谓的"in-class初值设定”也只允许对整数常量进行。如果你的编译器不支持上述语法,你可以**将初值放在定义式**:
class CostEstimate
{
private:
  static const double FudgeFactor; //static class常量声明
  ...                             //位于头文件内
};
const double                          //static class 常量定义
    CostEstimate::FudgeFactor = 1.35; //位于实现文件内
  1. 唯一例外是当你在class编译期间需要一个class常量值,例如在上述的GamePlayer::scores的数组声明式中(是的,编译器坚持必须在编译期间知道数组的大小)。这时候万一你的编译器(错误地)不允许"static整数型class常量”完成"in class初值设定”,可改用所谓的"the enum hack"补偿做法。其理论基础是:“一个属于枚举类型(enumerated type)的数值可权充ints被使用”,于是GamePlayer可定义如下:
class GamePlayer 
{
private:
  enum{NumTurns = 5};  //"the enum hack"--令NumTurns成为5的一个记号名称。
  
  int scores[NumTurns];
 };


  第一,enum hack的行为某方面说比较像#define而不像const,有时候这正是你想要的。例如取一个const的地址是合法的,但取一个enum的地址就不合法,而取一个#define的地址通常也不合法。

    如果你不想让别人获得一个pointer或 reference指向你的某个整数常量,enum可以帮助你实现这个约束。

  第二,实用主义。事实上"enum hack"是template metaprogramming(模板元编程)的基础技术。
  1. 另一个常见的#define误用情况是以它实现宏(macros).

    宏看起来像函数,但不会招致函数调用(function call)带来的额外开销。下面这个宏夹带着宏实参,调用函数f:

//以a和b的较大值调用f
#define CALL_WITH_MAX(a,b)  f((a)>(b) ? (a) : (b))
这般长相的宏有着太多缺点,光是想到它们就让人痛苦不堪。会在调用的时候可能遭遇麻烦。
--------------------------------------
纵使所有实参加上小括号,也会出现神奇的事情:
int a = 5, b = 0;
CALL_WITH_MAX(++a, b); //a被累加二次
CALL_WITH_MAX(++a, b+10); //a被累加一次
在这里,调用f之前,a的递增次数竟然取决于“它被拿来和谁比较”
---------------------------------------
解决方法:
 利用template inline函数:
   template
   inline void callWithMax(const T& a, const T& b)
   {//由于不知道T是什么,所以采用pass-by-refenrence-to-const。
     f(a > b ? a : b);
   }
  这个template产出一整群函数,每个函数都接受两个同型对象,并以其中较大者调用f。
  这里不需要在函数本体中为参数加上括号,也不需要 *** 心参数被核算(求,值)多次……等等。
  此外由于callWithMax是个真正的函数,它遵守作用域(scope)和访问规则。
  例如你绝对可以写出一个"class内的private inline函数”。一般而言 宏 无法完成此事。

有了consts、enums和inlines,我们对预处理器(特别是#define)的需求降低了,但并非完全消除。#include仍然是必需品,而#ifdef/#ifndef也继续扮演控制编译的重要角色。

对于单纯常量,最好以const对象或enums替换#defines。
对于形似函数的宏(macros),最好改用inline函数替换#defines


总结

以上就是今天要讲的内容。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/717980.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-25
下一篇 2022-04-25

发表评论

登录后才能评论

评论列表(0条)

保存