30

卡在ATM模拟的编程练习上。理由如下:
1. 银行卡太少
2. ATM使用得太少
3. 在ATM机前排队的时候从未思考过这个问题...

从28号开始做编程练习,已经过去了3天,披荆斩棘到ATM机前倒下。没想到之前在它跟前滚了N遍又蹲了N个小时后,我仍对它一筹莫展,毫无头绪。

反馈信息是,再看一遍...

23

April 20 ~ April 23

  1. It's better to return a class type object using constructor to initialize a temporary class object rather than creating a temporary one in class method. Humm, this approach is very useful ! Remember to take full use of constructors !

  2. Remember to return type bool value at the end of the overloading relational operations function.

  3. There is no argument with overloading '~'operator function.

19

4月14日~4月19日,五天时间完成第十章。其中,做课后题三天。复习题10题,编程练习8题。

今天早晨终于想明白了最后一道编程题的函数指针部分,原来是要把 void visit(void(*pf)(Item &)) 放入自定义类的 public 部分作为类方法来用(之前还以为必须要放在外部...怎么都想不明白有什么用),再创建一个外部函数实现一些诸如计算之类的小功能。这道编程题算是自己最近编写得比较满意的程序。实现了自己想要实现的几个功能(定义一个类,往一个类似 stack 固定长度的空间里输入类对象,空了或满了、输入或删除对象都要实时报告内部可使用的空间个数,并且可以实现循环修改存入的数据并报告出来)。

要多回头看才能温故而知新。

16

不得不写一下对于《C++ Primer Pluse》第六版,修改第五版例题但是不增加解释的一点看法。从昨天到今天,让我快忍受不了了。各种把 C++11 直接放入书中,感觉并未融入教材中,还有一些改了例题不改显示输出,小问题小错误都可以理解。但是作为一本非常非常基础的 C++ 教材,面向的大都是对 C++ 甚至是编程语言都不甚了解的人。既然如此,擅自增加例题难度,修改例题(相对第五版)却不加任何注解,让我非常不解。这相当于在数学解题的时候,新版例题增加难度在解题过程中还跳步。

例题如下(分为 3 个文件,一个 header file,两个 source file):


// stock20.h

#ifndef STOCK00_H_
#define STOCK00_H_
#include <string>

class Stock
{
private:
    std::string company;
    long shares;
    double share_val;
    double total_val;
    void set_tot() { total_val = shares * share_val; }
public:
    Stock();
    Stock(const std::string & co, long n = 0, double pr = 0.0);
    ~Stock();
    void buy(long num, double price);
    void sell(long num, double price);
    void update(double price);
    void show() const;
    const Stock & topval(const Stock & s) const;
};

#endif

// stock20.cpp

#include <iostream>
#include "stock20.h"

Stock::Stock()
{
    std::cout < < "Default constructor called\n";
    company = "no name";
    shares = 0;
    share_val = 0.0;
    total_val = 0.0;
}

Stock::Stock(const std::string & co, long n, double pr)
{
    std::cout << "Constructor using " << co << " called\n";
    company = co;
    if (n < 0)
    {
        std::cout << "Number of shares can't be negative; "
                      << company << " shares set to 0.\n";
        shares = 0;
    }
    else
    {
        shares = n;
    }
    share_val = pr;
    set_tot();
}

Stock::~Stock()
{
}

void Stock::buy(long num, double price)
{
    if (num < 0)
    {
        std::cout << "Number of shares purchased can't be negative."
                      << "Transaction is aborted.\n";
    }
    else
    {
        shares += num;
        share_val = price;
        set_tot();
    }
}

void Stock::sell(long num, double price)
{
    using std::cout;
    if (num < 0)
    {
        cout << "Number of shares purchased can't be negative."
                << "Transaction is aborted.\n";
    }
    else if (num > shares)
    {
        cout << "You can't sell more than you have!"
                << " Transaction is aborted.\n";
    }
    else
    {
        shares -= num;
        share_val = price;
        set_tot();
    }
}

void Stock::update(double price)
{
    share_val = price;
    set_tot();
}

void Stock::show() const
{
    using std::cout;
    using std::ios_base;

    ios_base::fmtflags orig = cout.setf(ios_base::fixed, ios_base::floatfield);
    std::streamsize prec = cout.precision(3);

    cout << "Company: " << company
            << " Shares: " << shares << '\n';
    cout << " Share Price: $" << share_val;
    cout.precision(2);
    cout << " Total Worth: $" << total_val << '\n';
    cout.setf(orig, ios_base::floatfield);
    cout.precision(prec);
}

const Stock & Stock::topval(const Stock & s) const
{
    if (s.total_val > total_val)
    {
        return s;
    }
    else
    {
        return *this;
    }
}

// usestok2.cpp
#include <iostream>
#include "stock20.h"

const int STSK = 4;

int main(void)
{
    Stock stock[STSK] =
    {
        Stock("NanoSmart", 12, 20.0),
        Stock("Boffo Object", 200, 2.0),
        Stock("Monolithic Obelisks", 130, 3.25),
        Stock("Fleep Enterprises", 60, 6.5)
    };

    std::cout << "Stock holdings:\n";
    int st;
    for (st = 0; st < STSK; ++st)
    {
        stock[st].show();
    }

    const Stock * top = &stock[0];
    for (st = 0; st < STSK; ++st)
    {
         top = &top->topval(stock[st]);
    }
    std::cout << "\nMost valuable holding:\n";
    top->show();
    return 0;
}

第五版例题这部分不是这样的,第五版直接用书中例题之前介绍的比较两对象大小的新方法:通过函数直接访问和间接访问两对象实现比较并返回较大值。

第六版的例题:注意橙色加粗部分。
const Stock * top = &stock[0];
for (st = 0; st < STSK; ++st)
{

top = &top->topval(stock[st]);

}
std::cout << "\nMost valuable holding:\n";
top->show();

第五版的例题对应部分如下:
Stock top = stock[0];
for (st = 0; st < STSK; ++st)
{

top = top.topval(stock[st]));

}
std::cout<< "\nMost valuable holding:\n";
top.show();

这样写和前文对于新比较方法的介绍做到前后呼应。让我莫名其妙的是,第六版中前面内容未做修改,但是“锦上添花”地把例题比较部分的对象改成用指针表示。自己理解不能,看了很久很久很久。我只能自己推断,然后是否是这样来理解。这样的感觉非常不好。

这些问题源于可爱的 this 指针。看过该书的同学应该知道 total_val 其实是 this->total_val 的缩写或速写法。在这里,充分说明“->”的含义,英文为 indirect membership operator; “.”是 direct membership operator。这里把直接访问和间接访问表现得淋漓尽致。若 s.total_val 是 s 直接访问成员 total_val,话说因为 s 是对象 stocks[1](以第二个数组元素为例)的 reference,所以,访问对象自己的私有成员,没一点问题。但是,this 作为一个指针,特权就不够了。虽然和那些数组元素是一个 class 里的对象,但是,指针就是指针,即使该指针里储存的是指向对象的地址,想要访问指向对象的成员,只能间接。在例题中,突然标新立异用一个 top 指针指向 stock[0],然后修改例题......看了无数遍后,我明白了作者的良苦用心。作者应该是觉得第五版用直接隐式访问对象的方式来阐明这个比较大小的新方法不够突出 this 指针的运作原理。那么,就改一改吧,用指针来指向本应直接隐式访问的对象。这下...难度提高了,也混乱了。这都不要紧,作者是否考虑在原版的基础上加一两句说明呢?不知为何,只字未提。不是自己对照第五版还不知道这题被改了。

言归正传,既然用了这种方法,那就使劲想吧。

首先是这句:top = &top->topval(stock[st]);应该这样理解 top = &(top->topval(stock[st]));

接下来,top->topval() 理解为 topval() 间接隐式访问top对象。(为何这样隐晦?敢不敢直接点?不解...)

然后,由于 this 指针储存的是 调用成员函数的对象地址。这样看来,this 才不管是直接调用还是间接调用。只要是调用的那个对象的地址就归它储存。问题是,调用 topval() 函数的是 top 指针啊!那么 this 里储存的应该是 top 指针本身的地址啊! 希望也像我这样理解的同学注意一下。其实,不是的...这种情况下,this = top; 即 top 指针把它储存的地址赋值给 this 指针,那个地址是对象的地址,不是top指针的地址。我一直不理解为什么是这样。这样看来,top->topval() 其实并不是 top 调用函数,而是 to p指向的对象调用函数。而 top 在这里就像是它指向对象的 alias。指针怎么可能是对象的别名?!因为...const 的原因。

简而言之,当用指针指向对象时,这时要用指针表示其指向对象调用成员函数就用“指针->成员函数”这样的句法来完成。至于为什么不用“对象 . 成员函数”这样简单的句法来完成,that's another story...

14

From April 9th to 13th, that's it, five days.

Struggle for the number of chapter more than ten...

7

From March 18th to April 7th...

It took me twenty-one days after finishing Chapter 8 (one day for translation).

7

以下内容为 Ninaaaron 本人翻译,

仅供大家学习交流使用,严禁商业用途!

• 感谢 YesSan 对本文提出宝贵建议并耐心校对。

• 模版限制

假设一个模版函数:

template <class T>   // 或 template <template T>

void f(T a, T b)

{ … }

经常需要根据操作对象的不同类型进行相应的假设。例如,假设以下语句赋值已定义,如果 T 是内置数组类型,则该语句不成立:

a = b;

类似地,假设以下语句中 > 已定义,如果T是普通结构,则该语句不成立:

if (a > b)

虽然操作符 > 已定义,但由于数组名即是地址,以上语句无法把两个数组的地址作比较。同理,假设以下语句乘法操作符已定义,如果 T 是数组、指针,或结构,则该语句不成立:

T c = a * b;

简而言之,很容易写出无法处理某些类型的模版函数。另一方面,有时即使C++句法不合理,泛化也讲得通。例如,虽然+操作符在结构中未定义,但是把类型匹配的结构相加也说得通。在 C++ 中,处理方法之一:让结构重载 + 操作符,这样即可在结构或类的特殊形式中使用(第11章中将讨论相关内容)。然后,需要使用 + 操作符的模版就能处理包含重载 + 操作符的结构。处理方法之二:为特殊类型提供特殊化的模版定义(specialized template definitions)。

译者:对应原版 P424-425

• 创建自定义选择

在某些情况下,可写出合适的函数调用让编译器自行选择。仔细分析程序 8.15,该程序在文件开头用模版函数定义取代函数原型。正如常规函数一样,如果把模版函数定义放在使用该函数之前,可充当该函数的原型。

程序 8.15 choices.cpp

// choices.cpp -- choosing a template

#include <iostream>

template<class T>

 lesser(T a, T b)                                    // #1

{

  return a < b ? a : b;

}

int lesser(int a, int b)                            // #2

{

       a = a < 0 ? -a : a;

       b = b < 0 ? -b : b;

       return a < b ? a : b;

}

int main(void)

{

       using namespace std;

       int m = 20;

       int n = -30;

       double x = 15.5;

       double y = 25.9;

       cout << lesser(m, n) << endl;               //使用 #2

       cout << lesser(x, y) << endl;                //使用 #1 double

       cout << lesser<>(m, n) << endl;         //使用 #1 int

       cout << lesser<int>(x, y) << endl;      //使用 #1 int

       return 0;

}

以下是程序输出:

20

15.5

-30

15

程序 8.15 中包含一个模版和一个标准函数。前者返回两值中的较小值,后者返回两绝对值的较小值。如果函数定义放在第一次使用该函数之前,该定义则充当其函数原型。因此,程序中可省略函数原型。考虑以下语句:

cout << lesser(m, n) << endl;              //使用 #2

函数调用要在模版函数和非模版函数中匹配参数类型,编译器选择非模版函数,返回值为20。

接下来,函数调用在模版中匹配参数类型,T的类型被设定为double:

cout << lesser(x, y) << endl;         //使用 #1 double

考虑以下语句:

cout << lesser<>(m, n) << endl;  //使用 #1 int

该语句lesser<>(m, n)中的尖括号表明编译器应该选择模版函数,不选择非模版函数。同时,编译器也将注意到实参是int类型,因此把T用模版实例化的方法转化成int类型。

最后的语句:

cout << lesser<int>(x, y) << endl;

该语句表明,使用函数来完成显式实例化,把T转化成int类型。因为x和y的值都被转化成int类型,函数返回值也为int类型,因此程序输出为15而非15.5。

译者:对应原版 P437-438

• 模版函数演变

在早期的C++ 中,大多数人还未意识到模版函数和模板类的功能强大,益处多多。不过,聪明专注的程序员们把模版的技巧发挥得淋漓尽致,尽可能地扩大了它的应用范围。根据他们开发模版的使用反馈,模版的变化被合并进C++98标准,同样也添加至标准模版库中。从此,模版程序员们就可继续研发各模版应用范围可能性,偶尔也会增加一些限制。他们的反馈促成了C++11标准中的一些变化。以下将会讨论相关的一些问题及解决方案。

什么是类型?

写出模版函数后,存在一个问题:它不一定总是C++98中已知的声明类型。分析以下程序片段:

template<class T1, class T2>

void ft(T1 x, T2 y)

{

?type? xpy = x + y;

}

xpy 是什么类型?我们事先并不知道 ft( )可能是什么类型。之前的类型可能是 T1 或者 T2,或者其他类型。例如,T1 可能是 double, T2 可能是 int,它们相加后的类型将被转化为 double。如果 T1 是 short,T2 是 int,它们相加后的类型将被转化为int 。如果T1是short,T2是char,它们相加后程序将调用自动整型提升将其结果转化为int 。可为结构和类重载 + 操作符,复杂操作符亦可。因此,C++98 中无法为 xpy 选择类型。

• decltype 关键字(C++11)

C++11 为解决以上问题增加了一个新的关键字:decltype。按以下方式使用:

int x;

decltype(x) y;             // y 和 x 类型相同

decltype的参数可用表达式表示,因此在ft()例中可按如下方式编写代码:

decltype(x + y) xpy;   // xpy 和 x + y 类型相同

xpy = x + y;

也可把两个语句合二为一为初始化:

decltype(x + y) xpy = x + y;

因此,可以修改ft()模版:

template<class T1, class T2>

void ft(T1 x, T2 y)

{

decltype(x + y) xpy = x + y;

}

与以上的例子相比,decltype 的使用可能会更复杂些。编译器不得参与查看清单以决定其类型。假设有以下语句:

decltype(expression) var;

这里为清单提供稍简化版本。

第 1 阶:如果 expression 为无圆括号标识符(即没有加圆括号),则 var 与标识符类型相同,包括限定符,如const:

double x = 5.5;

double y = 7.9;

doyble & rx = x;

const double & pd;

decltype(x) w;           // w 为 double 类型

decltype(rx) u = y;     // u 为 double & 类型

decltype(pd) v;         // v 为 double * 类型

第2阶:如果 expression 是函数调用,则 var 的类型为函数返回值类型:

long indeed(int);

decltype(indeed(3)) m;    // m 为 long 类型

注意:调用 expression 不可评估。该例中,编译器检查原型获得返回值类型;实际上没必要调用函数。

第3阶:如果 expression 是左值 (lvalue),则 var 是表达式类型的 reference。这可能表明之前的例子中 w 也应该是 reference 类型。不过,记住第 1 阶的类型转换。在该阶段应用,expression 不能是无圆括号标识符。那会是什么?很显然,可能是带括号标示符:

double xx = 4.4;

decltype((xx)) r2 = xx;      // r2 是 double &

decltype(xx) w = xx;         // w 是 double (第 1 阶匹配)

另外,圆括号不会改变值,也不会改变表达式的左值。例如,如下两个语句表达的效果相同:

xx = 98.6;

(xx) = 98.6;   // () 不会影响 xx 的使用

第 4 阶:如果都没有之前的特殊转化应用,则 va r与 expression 的类型相同:

int j = 3;

int & k = j;

int & n = j;

decltype(j + 6) i1;      // i1 为 int 类型

decltype(100L) i2;      // i2 为 long 类型

decltype(k + n) i3;     // i3 为 int 类型

注意到,虽然 k 和 n 是 reference,表达式 k + n 并不是 reference,它只是两个 int 类型之和,因此 k + n 是 int 类型。

如果需要多个类型声明,可用 decltype 使用 typedef:

template<class T1, class T2)

{

       …

      typedef decltype(x + y) xytype;

      xytype xpy = x + y;

      xytype arr[10];

      xytype & rxy = arr[2];              // rxy 是reference

• 替换函数句法(C++11 追踪返回类型)

decltype 机制遗留下一个悬而未决的问题。分析以下未完成的模版函数:

template<class T1, class T2>

?type? gt(T1 x, T2 y)

{

return x + y;

}

该例中x 和y相加后返回的类型事先还是未知。似乎可以使用 decltype(x + y) 来获得返回值类型。可是,在该例中,参数列表中的x和y都未声明,所以它们不在作用域中(可见但无法编译)。必须在参数声明后decltype说明符才能起作用。因此,C++11 使用一种新的句法声明和定义函数。下例中使用内置类型进行说明。函数原型

double h(int x, float y);

可用另一种句法编写:

auto h(int x, float y) -> double;

这把返回值类型移至参数声明之后。复合符号 -> double 被称为追踪返回类型(trailing return type)。该例中,auto 是 C++11 新加的关键字,用来为追踪返回类型提供类型的占位符。函数定义也使用相同的形式:

template<class T1, class T2>

auto gt(T1 x, T2 y) -> decltype(x + y);

{

     …

     return x + y;

}

   这时,由于 decltype 在参数声明之后,因此 x 和 y 在作用域中,可以使用。

译者:对应原版 P438-442

25

CODE COMPLETE 2

超级经典之书,无需多言。原版书的纸质、印刷、封面设计果然一流。

24

noppoo mini

noppoo mini


Cherry MX Brown Switch

Cherry MX Brown Switch

机械键盘的手感真是无与伦比。

Cherry茶轴,打字手感超凡。

苹果的薄膜键盘可以休长假了。

16

  1. Recursion...Recursion...Recursion...
    It makes me so confused...

  2. Note the following statement :
    const double * (*ps[3])(const double *, double) = {f1, f2, f3};

    *ps[3] says ps is an array of three pointers.The rest of the declaration indicates what each pointer points to: a function with a signature of const double *, double and a return type of const double *.

  3. Remember there are two syntax to understand the following statement:
    const double *(*(*pd)[3])(const double *, double) = &pa;

Go Ahead!