入门以后如何深入学习 C++ ?有哪些建议?

假设你决定开始将C++用于工作中,而你还没有怎么接触过C++。你可能参加过培训或者读过一两本有关C++的书,也可能两者都试过。你也已经写过第一个C++程序:

#include <iostream.h>
int main()
{
        cout << "Hello world" << endl;
}

接下来再做什么呢?

下面是一点非正规的忠告,希望能帮助你精练而快乐地使用C++。

1 找当地的专家

向那些已经学会了你要学的东西的人请教,这对你学习这种新东西是大有好处的。如果知道附近有谁能回答你的问题并能帮助你解决问题,学C++就会容易得多。另外,由于C++源自C,所以附近如果有C用户社群,也可以帮助你学习C++。

首先要确认你觉得是专家的人确实知道他们在说什么。所幸的是,有一个简单且通常很有效的办法来辨别:专家就是那种不仅理解你所试图掌握的东西,而且还能给你解释清楚的人。不能清楚地回答你提出的问题的人,并不是你所想的专家。还有人能清楚地解释事情,但却是完全错误的,幸亏这种人很少。

类似的忠告也适用于语言之外的事物,比如许多运行在各种操作系统之上的C++实现。在学习使用某种关于语言和系统的特定组合时,冒出来的问题和系统有关,所以必须有既懂得语言又懂系统的人。记住,任何有用的程序都必须和外部世界交流。

2 选一种工具包并适应它

能否向当地专家请教的意义比特定编程环境的客观优缺点更重要。但是,最后分析得出,编程环境中最重要的因素还是使用者。一旦使用某种工具到了一定时间,你就会适应这种工具,并能提高使用效率,也就更不愿意换用另一种工具了。

只要你不谎称它是不可逾越的,那么改变使用工具就一点问题也没有了。如果一旦有最新的工具可用就急着用,你就没有时间做别的事情了。如果你靠评价编程工具谋生,这样做当然很好,否则就会掉进一个危险的陷阱。工具是获取结果的手段,如果你只注意手段而忽视了结果,就是在浪费时间。

当然,仅仅因为新事物是新的就拒绝接受也是危险的。最好采用有用的新工具,而不是因为其他的原因。要做到这一点,有一个办法就是要有明显的证据说明新提供的工具值得你费劲去学习如何使用它。如果不能确定,就别为这个工具伤脑筋。我们再一次将理解作为实用性的衡量标准。

3 C的某些部分是必需的

C++建立在C的基础上。C中的部分思想是任何想有效地使用C++的人必须理解的。如果你已经使用C很长时间并很适应它,这些思想就会成为习惯。如果不是这样,就努力做到这样。

例如,C++程序比起相应的C程序要做更多的声明。理解C程序时,很大一部分精力是花费在阅读组成程序的函数上。而由于C++直接支持数据抽象,所以理解C++程序的工作主要是识别某种标识符的类型,然后阅读该类型的声明。

因此理解声明对C++比对C重要得多。不应该一次又一次地区分指针数组和指向数组的指针之间的区别,以及指向常量的指针和常量指针之间的差别。你应该一劳永逸地掌握。

除了与众不同的声明语法外,C定义了数组和指针之间的一种独特关系。除了C和C++外,没有哪门主流语言用指针操作来定义数组。事实上C的所有程序都运用了这种关系;要彻底学会并理解这一点,而不要每次都从头开始。

由于数组和指针之间的关系,C程序员倾向于用指针运算来表达数组操作。这种与C的++、--操作相关的操作会使程序很短小,但需要花一定精力来理解。

例如,下面是一个C函数,接受一个“指向函数的指针数组”作为参数,并且调用每一个数组元素所指向的函数:

void call_each(void (**a)(void), int size)
{
        int n = size;

        while (--n >= 0) {
                void (*p)(void) = *a++;
                if (p)
                        (*p)();
        }
}

尽管a从概念上说是个数组,但实际上是一个指针——此时是一个指向“函数指针”的指针。这解释了a的声明中的两个*。看看声明我们就可以很容易地发现这一点,并说:“**a的值是一个函数,可以无参调用,并且没有返回值。”所以a必须是一个指向函数指针的指针。

接下来,注意表达式--n >= 0,如果这个表达式用于这种情况,就是一种将操作重复n次的常用方法。另外,我们可以通过令n=0和n=1来运行该循环,从而证实这一点,同时还可以清楚循环体执行的次数。

然后是关于p的声明——如果你理解a的声明,在这儿就应该没有问题。稍微难懂的是*a++的含义:它取回a所指向的元素,再增加a使之指向序列中的下一个元素。还有,用法if(p)是if(p!=0)的简写形式。

最后,表达式(*p)()是用来决定p指向哪个函数,然后再调用该函数。

这些事情本身并不复杂;它们都是C的一部分,而且被使用了很久。问题在于,如果你开始学习C++,起码要掌握C的这部分知识。好好理解C的这些知识是有好处的,这样你在学习C++时就不用总是停下来去查找关于它们的资料。

4 C的其他部分不是必需的

有些C的编程技术直接移植到了C++中——但是其他的没有。尽管通常可以将C程序稍作改动或者根本不作修改地当作C++程序来运行,然而也可能将C++程序向C所不允许的方向扩展。因此,存在一些编程技术,即使它们在C中是合法的,也最好不要在C++程序中使用它们——因为它们的存在会有限制作用,使这些程序无法向非C风格发展。

C有比以前的语言更强的类型检查能力,C++则具有更强的类型检查能力。例如,假设我们要将一个元素类型为T的对象数组复制到另一个数组中。我们假设N是一个合适的常量:

T source[N];
T dest[N];
int i;

for (i = 0; i < N; i++)
        dest[i] = source[i];

或者,我们可以用指针来编写这个循环:

int* p = dest;
int* q = source;

while (p!= dest+N)
        *p++ = *q++;

这两种循环都能在C和C++中生效。

然而,C程序员更愿意用这种方案:

T source[N];
T dest[N];

memcpy(dest, source, N * sizeof(T));

这种做法在C中会很好地工作,但在C++中就会带来灾难。要知道原因,我们必须看看C和C++中关于如何存储值的概念。

在C中每个值都可以看作是位序列。对于任何类型T,表达式sizeof(T)告诉我们需要多少个字节。另外,调用

memcpy(p, q, n)

会将由q寻址的位置开始的区域内的n个字节复制到由p寻址的位置上。

因此,在C中,对于任何类型T,如果我们进行如下声明:

T x, y;

我们可以确信

x = y;

和下面语句的效果一样

memcpy(x, y, sizeof(T));

C程序员经常用这种现象来复制对象,而不用知道对象是何种类型[1]

通常这种技术在C++中是行不通的,因为复制一个类的对象并不总等价于按位复制组成这个对象的字节。相反,复制构造函数和赋值操作符决定要怎样复制这个类的对象。

即使类中没有明确包含复制构造函数或者赋值操作符,这也可能成立。例如:

struct PersonnelRecord {
        String name;
        String address;
        int id;
        // …
}

可以定义一个与此类似的甚至没有写构造函数的记录,因为C++会缺省地逐成员复制对象。这样,最好假定类String中有一个复制构造函数,这样复制一个PersonnelRecord就不会等同于按位复制构成对象的字节了。

怎么能知道哪些类可以安全地使用memcpy来进行复制,哪些不行呢?如果非问不可,答案是最好别用memcpy。应该只依赖于完全理解和肯定的东西。

5 给自己设一些问题

如果你只依赖自己完全理解的那部分知识,就必须想办法扩展你的理解范围。增加知识储备的有效方法就是用已知的方法尝试新问题。选择个你还不理解的C++特性,使用这个特性写个程序,但是注意除此之外,只使用你已掌握的东西。然后做一些其他的工作,帮助你确信程序在做什么,以及为什么。

比如,你不确定自己是否理解复制构造函数和赋值操作符之间的区别,就试着写一个程序,来证明你理解两者的区别。设计这样的程序将会比只看书更加有效。

应该怎样写这样的程序呢?一种方法就是设计一个既用到复制构造函数,又用到赋值操作符的类:

class Test {
public:
        Test(const Test&);
        Test& operator=(const Test&);
};

然后可以在不同的情况下使用这个类的对象,来观察类是怎样工作的。例如,如果从前面的程序开始,只要输入

Test t;

很快就会发现一个问题。尽管这个类有一个复制构造函数,但它不是缺省构造函数。所以,你至少要给出一个缺省构造函数:

class Test {
public:
        Test();
        Test(const Test&);
        Test& operator=(const Test&);
};

当然,你必须定义赋值操作符和两个已经声明了的构造函数:

Test::Test()
{
        cout << "Test default constructor" << endl;
}

Test::Test(const Test&)
{
        cout << "Test copy constructor" << endl;
}
Test& Test::operator=(const Test&)
{
        cout << "Test assignment operator" << endl;
        return *this;
}

这样,通过编写使用这个类的对象和认真地观察结果,你能够学到很多关于构造函数和赋值操作符如何工作的知识。

如果为你的类添加了一个析构函数,你就会学到更多知识。然后就能确定每个创建的对象都被销毁了。当然,这并不能说明创建的对象和销毁的对象是一模一样的,因此我们必须想法区分每个对象。这里有一个可行方法(模仿第27章中的设计):

class Test {
public:
        Test();
        Test(const Test&);
        ~Test();
        Test& operator=(const Test&);

private:
        static int count;
        int id;
}

int Test::count = 0;

Test:Test()
{
        id = ++count;
        cout << "Test " << id
             << " default constructor" << endl;
}

Test::Test(const Test& t)
{
        id = ++count;
        cout << "Test " << id
             << " copied from " << t.id << endl;
}

Test& Test::operator=(const Test& t)
{
        cout << "Test " << id
             << " assigned from " << t.id << endl;
        return * this;
}

Test::~Test()
{
        cout << "Test " << id << " destroyed" << endl;
}

你的类现在能报告对象的创建、销毁、赋值以及每个对象一个的识别号。例如,考虑下面这个简单的主程序:

int main()
{
        Test s;
        Test t(s);
        s = t;
}

运行时程序打印:

Test 1 default constructor
Test 2 copied from 1
Test 1 assigned from 2
Test 2 destroyed
Test 1 destroyed

如果你能够理解为什么这样输出,就说明你知道了许多关于构造函数、析构函数和赋值操作符的知识。构造这种小巧的类在许多领域都是加强理解的好办法。

C++书籍推荐

C++ Primer Plus 第6版 中文版

本书在介绍C++特性的同时,还讨论了基本C语言,使两者成为有机的整体。书中介绍了C++的基本概念,并通过短小精悍的程序来阐明,这些程序都很容易复制和试验。书中还介绍了输入和输出,如何让程序执行重复性任务,如何让程序做出选择,处理数据的多种方式,以及如何使用函数等内容。另外,本书还讲述了C++在C语言的基础上新增的很多特性,包括:

  • 类和对象;
  • 继承;
  • 多态、虚函数和RTTI(运行阶段类型识别);
  • 函数重载;
  • 引用变量;
  • 泛型(独立于类型的)编程,这种技术是由模板和标准模板库(STL)提供的;

 处理错误条件的异常机制;

  • 管理函数、类和变量名的名称空间。

大约20年前,C Primer Plus开创了优良的初级教程传统,本书建立在这样的基础之上,吸收了其中很多成功的理念。

  • 初级教程应当是友好的、便于使用的指南。
  • 初级教程不要求您已经熟悉相关的编程概念。
  • 初级教程强调的是动手学习,通过简短、容易输入的示例阐述一两个概念。
  • 初级教程用示意图来解释概念。
  • 初级教程提供问题和练习来检验您对知识的理解,从而适于自学或课堂教学。

基于上述理念,本书帮助您理解这种用途广泛的语言,并学习如何使用它。

  • 对何时使用某些特性,例如何时使用公共继承来建立is-a关系,提供了概念方面的指导。
  • 阐释了常用的C++编程理念和技术。
  • 提供了大量的附注,如提示、警告、注意等。

本书的作者和编辑尽最大的努力使本书简单、明了、生动有趣。我们的目标是,您阅读本书后,能够编写出可靠、高效的程序,并且觉得这是一种享受。

C++语言的设计和演化

本书是C++的设计者Bjarne Stroustrup关于C++ 语言的最主要著作之一(另一本是《C++程序设计语言》)。在这本书中,作者全面论述了C++ 的历史和发展,C++中各种重要机制的本质、意义和设计背景,这些机制的基本用途和使用方法,讨论了C++ 所适合的应用领域和未来发展前景。本书在帮助人们深入理解C++ 语言方面的地位无可替代,值得每个关心、学习和使用C++ 语言的专业工作者、科研人员、教师和学生阅读。在这本书中,作者还从实践的角度出发,讨论了许多与程序设计语言、系统程序设计、面向对象的技术和方法、软件系统的设计和实现技术等有关的问题,值得每一个关心这些领域及相关问题的计算机工作者和学生们阅读参考。

C++并发编程实战

《C++并发编程实战》是一本基于C++11新标准的并发和多线程编程深度指南。内容包括从std::thread、std::mutex、std::future和std::async等基础类的使用,到内存模型和原子操作、基于锁和锁数据结构的构建,再扩展到并行算法、线程管理,最后还介绍了多线程代码的测试工作。本书的附录部分还对C++11新语言特性中与多线程相关的项目进行了简要的介绍,并提供了C++11线程库的完整参考。

《C++并发编程实战》适合于需要深入了解C++多线程开发的读者,以及使用C++进行各类软件开发的开发人员、测试人员。对于使用第三方线程库的读者,也可以从本书后面的章节中了解到相关的指引和技巧。同时,本书还可以作为C++11线程库的参考工具书。

C++沉思录

聆听大师教诲,掌握编程精髓。

1.基于作者在知名技术杂志发表的技术文章、世界各地发表的演讲以及斯坦福大学的课程讲义整理、写作而成。

2.著名技术伉俪十年编程生涯的真知灼见。

3.本书重点关注的是一些重要的C++思想和编程技巧,旨在让读者理解C++编程中的一些原理(why),而不仅仅是工作机制(how),无论你是否是C++编程专家,都会在本书中发现重要的与C++编程有关的技巧和思考。

4.C++之父 Bjarne Stroustrup 倾力推荐。

《C++沉思录》基于作者在知名技术杂志发表的技术文章、世界各地发表的演讲以及斯坦福大学的课程讲义整理、写作而成,融聚了作者10多年C++程序生涯的真知灼见。

《C++沉思录》分为6篇,共32章,分别对C++语言的历史和特点、类和继承、STL与泛型编程、库的设计等几大技术话题进行了详细而深入的讨论,细微之处几乎涵盖了C++所有的设计思想和技术细节。本书通过精心挑选的实例,向读者传达先进的程序设计方法和理念。

猜你喜欢

转载自blog.csdn.net/epubit17/article/details/121255979