有关《C++程序设计语言》的讨论

本页收集了一些朋友提出的问题,以及我就有关问题的看法。这些只是个人认识,如有错误欢迎指正。欢迎更多关心此事的朋友们多提出看法。我将会不断扩充这个页面的信息。欢迎提出意见和建议。


问题:我现在打算看《TC++PL》。可是发现它里面用了很多的stl。我以前学过c,编程一直用c风格。现在我看书很别扭,那些代码看的似懂非懂。关键问题就是看不懂stl那些语言实现。请问我该怎么办?我是不是该去看看有关stl方面的书呀?还有,我原来的c语言风格有必要改吗?谢谢。

回答:改不改风格要看你的想法。你可以一直在C里做程序,当然也就可以坚持C的风格。如果你想学习C++,那么就应该学习在C++里的编程风格,学习在C++里做程序的正确方式。广泛使用STL也是目前人们所提倡的C++编程风格的一部分。在TC++PL里作者也讨论了编程风格的转变问题。STL的基本使用并不难,应该说用起来也是很方便的。当然,如果你想理解其实现,其中牵涉到的问题很多。但这完全可以等到你更熟悉C++之后再说,开始时不必过分关心那些问题。初学时过于注意去了解细节反而会影响人对全局的理解。至于其他STL的书,我觉得可以先看,也可以不必先去看。其实目前出版的STL书籍中,讨论深度超过TC++PL一书的也不多。


问题:vc6.0下编译包含stl库文件的程序,总有很多错误,包括C++PL中的源程序,怎样解决?。

回答:你说的可能不是错误,而是警告信息,说生成的标识符长于nnn字符(大概是256字符),因此在诊断信息中将无法完全表示。在通过#include包含STL的文件之前加一句:

#pragma warning( disable : 4786 )

即可关闭这一警告。(这并非C++语言的问题,却是用VC做本书题目时常见的问题)


问题:page30Line37(空行不计) "在自由存储分配的Stack" 原书此处为:"allocated on free store".从字面上看这样翻译没问题,但是我们都知道:用new分配的内存是在heap(堆)上的。而很少有人会 用"自由存储"来指代"堆"。这里或许不是一个翻译问题,而是看是否有必要让它对国内读者更通俗些。

回答:堆通常指所有动态空间的总和,而自由空间指的是当时存储管理系统掌握的那一部分堆空间。显然,申请动态存储时,new只能在自由空间中分配。自由空间是通行的说法,我们应该了解。


问题:第5.5节(P90)中的例子在C++ Builder 6.0/VC++ 6.0的代码如下:
#include <iostream>
#include <string> 
#include <vector> using namespace std; 

struct Pair { double val; string name; }; 
vector<pair> pairs;

double& value(const string& s) { 
    for(int i=0; i < pairs.size(); i++)
        if(s == pairs[i].name) return pairs[i].val;
    Pair p={0,s};
    pairs.push_back(p);
    return pairs[pairs.size()-1].val;
}

int main(int argc, char* argv[])
{
    string buf;
    while(cin >> buf) value(buf)++; 
	for(vector<pair>::const_iterator p=pairs.begin();p!=pairs.end();++p) 
        cout << p->name << ":" << p->val << "\n"; return 0; 
}

这在CBC6及VC6中都不能通过编译。 将Pair p={0,s};一句改成:

Pair p;//={0,s}; p.name = s; p.val = 0;

就能正确编译及运行.

回答: 原书中的这个例子不错。所写的:

Pair p={0,s};

就是定义一个结构变量并给以初始化,标准允许这样做。这一初始化过程中牵涉到对string类成员的初始化,这是一个复杂操作,其中需要调用string类的构造函数。只能说你发现的是BC和VC与标准不符的一个情况,而不是书中错误。

续:谢谢回函,正如上面所言,这是编译器问题.在GNU/GCC(2.91.57,cygwin)下就能正确编译并运行.看来,下次遇到类似的情况时,我要将这三个编译器都试一试才向您报告了.

回答:即使在三个编译器里都有问题,也未必是书上错误,还是应该看有关代码是否复合语言标准。


问题:在P103页:
default: 
    if(isalpha(ch)) { 
        string_value = ch;
		while(cin.get(ch)&&isalnum(ch)) string_value.push_back(ch); 
	    cin.putback(ch) ; 
		return curr_tok = NAME ; 
	} 
	error("bad token"); 
	return curr_tok = PRINT ; 

的程序段里对string类型的变量用了push_back() 操作 string_back() 是对deque, list , vector进行的操作,从而导致编译错误。

回答:对string用push_back没有问题,C++标准库的string类确实有这个操作(另见523页中间)。如果在你的系统上编译出错,只能说明你所用的系统在这一点上不符合标准。


问题:书的203页上半部分程序中, 对Date构造函数的定义中怎么又出现了另外一个Date的对象today, 我想如果在初始化today的过程中,岂不是又要调用其构造函数吗,是否调用其缺省构造函数呢?但类的声明中也没有出现缺省构造函数。

回答:这个类确实有默认构造函数,就是你所说的那个函数。但该函数的调用将依赖于全局量today。这也是作者在这里想提出的问题。因此,这个程序并不好,因为它导致类定义、全局变量和成员函数定义之间的相互缠绕。然而也请注意,如果写了:

class Date {
    int d, m, y; 
public: 
    Date(int dd = 0, int mm = 0, int yy = 0);
    // ... ...
};


Date today(2002,9,11);


Date::Date(int dd, int mm, int yy) {
    d = dd ? dd : today.d;
    m = mm ? mm : today.m; 
    y = yy ? yy : today.y;
}

使用这个类时应该保证:1)定义全局变量 today 时可以看到类定义;2)定义 Date::Date 时可以看到全局变量today的声明;3)创建 today 时不用默认参数;4)创建 today 必须在创建其他可能用到默认值的Date对象之前。如果这些都能保证,这个程序就可以工作。

另外,还有朋友来函说试验书上204页的有关程序时出现错误信息:

unresolved external symbol "private: static class Date Date::default_date"

这就是因为忘记定义静态变量Data::default_date。


问题: (P461)请帮我讲解一下以下代码:
template<class R, class T> mem_fun_t<R,T> mem_fun(R(T::*f)()) 
{ 
    return mem_fun_t<R,T>(f); 
}

void draw_all(list<Shape*>& lsp) {
    for_each(lsp.begin(),lsp.end(),mem_fun(&Shape::draw)); 
} 
其中(&Shape::draw)与(R(T::*f)的对应关系是什么?

回答:第一行最后是模板函数 mem_fun 的参数表,其中的参数是 f,其类型是指向类 T 的成员函数的“成员函数指针”;被指成员函数的返回值为R类型,而且被指函数应是无参函数。 在 for_each 算法最后是对 mem_fun 的调用,以 Shape::draw 作为实际参数。这个成员函数符合上述类型要求。

 


问题:第686页关于 Handle 类对指针的提取/绑定的例子(见最下)中的 else 里边的:

pcount = new int(1);

是什么意思? 既然*pcount!=0,为何要重新开始计数呢?

 
template<class X> class Handle{
    //... 
    X* get_rep() { return rep; } 
    void bind(X* pp) { 
        if (pp != rep) { 
            if (--*pcount == 0) { delete rep; *pcount = 1; } 
            else   pcount = new int(1); 
            rep = pp; 
        }
    }
};

回答:这里的问题是要将本句柄对象约束到另一个表示对象,最后的 rep == pp; 完成此事。内层条件语句处理引用计数器,其中考虑了在可能情况下重复使用原计数器的问题。如果引用计数减一后计数器为0,那就可以销毁原来的表示对象,并直接使用原有计算器(将其值置为1,因为下面只引用 pp,计数值应该为1。如果减一后非 0,表示有其他句柄引用那个表示对象。这时就需要另分配一个计数器,new int(1) 完成此事。


本页由裘宗燕建立和维护,保留所有权利。

这里的材料可自由地用于个人学习或普通教学活动。其他方式的使用应事先得到作者书面认可。