Archive for the ‘C++简明基础教程’ Category

C++简明基础教程 – 结语

Tuesday, March 31st, 2009

零零散散地写了七篇教程,本想再好好写一篇结语的,可是这两天突然发烧了,也没啥精力写了,只好这样了……

教程可能分布得比较散,所以有了新的Category,可以直接从那里浏览到所有教程,但是突然发现因为使用了<pre>标签的原因,代码显示出来太难看了……好吧,在这里重新把教程的所有链接贴出来:

C++简明基础教程 – 第一篇 Dev-C++安装与配置

C++简明基础教程 – 第二篇 编程语言学习引论

C++简明基础教程 – 第三篇 C++ in a Nutshell(代码部分)

C++简明基础教程 – 第三篇 C++ in a Nutshell(测试用例部分)

C++简明基础教程 – 第三篇 C++ in a Nutshell(解释部分)

C++简明基础教程 – 第四篇 STL In A Nutshell

C++简明基础教程 – 第五篇 面向对象设计

C++简明基础教程 – 第六篇 C++ and Its World

C++简明基础教程 – 第七篇 补充代码部分

顺便在这里说一句,不要试图用“教程”标签找到所有教程,因为真的有几篇我忘了打这个标签了,也不想改了……

还有,把原来写的第一篇关于Dev-C++安装的内容放出来吧,其实是很多张截图而已。俗话说,授之以鱼,不如授之以渔,还是把详细的手工操作的流程拿出来给大家看比较好。(点击这里获取截图压缩包)

最后,为了证明我的身份,在这里写这么一行字。太囧了~我真是挂线的!

C++简明基础教程 – 第七篇 补充代码部分

Saturday, March 28th, 2009

这是本系列教程的最后一篇内容了(还会有一篇结语)。为了让读者对C++有一个更直接的认识,我编写了几段很小很精炼的程序,当做是对第三篇中使用到的关键字的补充内容吧。多看看这些代码,多动手自己写一些东西,把C++当做一门语言,像学英语一样来学,不久之后你就会提高很多的。

// hello.cpp
// print message "Hello World"

#include <iostream>
using namespace std;

int main(int argc, char* argv[])
{
    cout << "Hello World" << endl;
    return 0;
}

// sort.cpp
// input n integers, sort and print

#include <iostream>
#include <algorithm>
using namespace std;

int main()
{
    int n;
    cin >> n;
    int *a = new int[n];
    for (int i = 0; i < n; ++ i)
        cin >> a[i];
    sort(a, a + n);
    for (int i = 0; i < n; ++ i)
        cout << a[i] << ' ';
    delete[] a;
    return 0;
}

// reverse.cpp
// input a string, print the reversed

#include <iostream>
#include <string>
using namespace std;

int main()
{
    string s;
    getline(cin, s);
    int i = s.length();
    while (i)
        cout << s[--i];
    return 0;
}

// file.cpp
// read a file, print all the content

#include <iostream>
#include <fstream>
#include <string>
using namespace std;

int main(int argc, char* argv[])
{   // usage: file <FIlENAME>
    if (argc != 2) return -1; // error
    ifstream fin(argv[1]);
    string line;
    while (fin.good() && !fin.eof())
    {
        getline(fin, line);
        cout << line << endl;
    }
    fin.close();
    return 0;
}

// copy.cpp
// copy from one file to another

#include <fstream>
#include <string>
using namespace std;

int main(int argc, char* argv[])
{   // usage: file <SOUR> <DEST>
    if (argc != 3) return -1; // error
    ifstream fin(argv[1]);
    ofstream fout(argv[2]);
    string line;
    while (fin.good() && !fin.eof())
    {
        getline(fin, line);
        fout << line << endl;
    }
    fout.close();
    fin.close();
    return 0;
}

// calc.cpp
// a simple calculator

#include <iostream>
using namespace std;

int main()
{
    double a, b;
    char c;
    cin >> a >> c >> b;
    if (c == '+')
        cout << a + b << endl;
    else if (c == '-')
        cout << a - b << endl;
    else if (c == '*')
        cout << a * b << endl;
    else if (c == '/')
        cout << a / b << endl;
    else
        cout << "ONLY + - * /" << endl;
    return 0;
}

// mul9x9.cpp
// print out the famous table...

#include <iostream>
using namespace std;

int main()
{
    cout << '\t';
    for (int j = 1; j <= 9; ++ j)
        cout << '\t' << j;
    cout << endl;
    for (int i = 1; i <= 9; ++ i)
    {
        cout << i;
        for (int j = 1; j <= 9; ++ j)
            cout << '\t' << i*j;
        cout << endl;
    }
    return 0;
}

// guess.cpp
// a simple guessing game

#include <iostream>
using namespace std;

int main()
{
    int ans = rand()%100;
    int n;
    do
    {
        cin >> n;
        if (n > ans)
            cout << "too high" << endl;
        else if (n < ans)
            cout << "too low" << endl;
        else
            cout << "right on" << endl;
    }
    while (n != ans);
    return 0;
}


Fix@2009.4.5: 增加一个筛法求素数的例子。
#include <iostream>
#include <vector>
using namespace std;

int main()
{   // pre  : n >= 1
    // post : print all prime numbers
    //          less than n
    int n;
    cin >> n;
    // first, assume all the numbers
    //  are prime numbers
    vector<bool> b(n, true);
    // then, choose a number i >= 2,
    //  multiply j >= 2, get i*j,
    //  so i*j is a composite number
    for (int i = 2; i < n; ++ i)
        for (int j = 2; i*j < n; ++ j)
            b[i*j] = false;
    // finally, we print them out
    for (int i = 2; i < n; ++ i)
        if (b[i]) cout << i << ' ';
    return 0;
}


C++简明基础教程 – 第六篇 C++ and Its World

Friday, March 27th, 2009

本篇对C++世界的关键之处作以补充,便于初学者对C++有一个直观的了解,可以作为第三篇的参考材料。

每个C++ 程序通常分为两个文件。一个文件用于保存程序的声明(declaration),称为头文件。另一个文件用于保存程序的实现(implementation),称为定义(definition)文件。C++程序的头文件以“.h”为后缀,C++程序的定义文件通常以“.cpp”为后缀(也有一些系统以“.cc”或“.cxx”为后缀)。头文件主要包括一些预编译命令以及函数、类结构的声明等。定义文件则包含了对一些头文件的引用,还有程序的实现体(包括数据和代码)。

头文件(header)的作用大致有两点:(1)通过头文件来调用库功能。某些情况下源代码不向用户公布,只要提供编译后的二进制库文件和相应的头文件即可,用户按照头文件中对接口的声明来使用库,编译器会从库中提取相应的代码(二进制)。(2)头文件能加强类型安全检查。如果某个接口被实现或被使用时,其方式与头文件中的声明不一致,编译器就会指出错误,这一简单的规则能大大减轻程序员调试、改错的负担。

C++中的运算符有数十个,清楚它们的优先级与结合律对编写逻辑复杂的代码至关重要(当然,过于复杂的表达式逻辑,强烈建议适当地使用括号,以增强代码的可读性)。下表简要说明了各运算符的优先级与结合律。

优先级 运算符 结合律





( ) [ ] -> . 从左至右
! ~ ++ — (类型) sizeof
+ – * &
从右至左
* / % 从左至右
+ - 从左至右
<< >> 从左至右
< <= > >= 从左至右
== != 从左至右
& 从左至右
^ 从左至右
| 从左至右
&& 从左至右
|| 从右至左
?: 从右至左
= += -= *= /= %= &= ^=
|= <<= >>=
从左至右

if语句是C++中最常用也最简单的语句,需要注意的有:(1)else子句是可选的;(2)多级if的排版要注意美观以防止发生错误;(3)在不过于冗余的情况下尽量多地使用花括号,以保证代码结构严谨;(4)if的条件是与零比较,非零的值都表示条件成立。

for语句在越专业的C++代码中出现的频率就越高,因为它极为灵活。对于初学者最需要注意的就是,for的括号中声明的变量在for语句之外是不可用的。比如“for(int i = 1; i <= 10; ++ i) { cout << i << endl; }”将打印出1~10的值,此后i就“消失”了,再写比如“cout << i << endl;”就是错误的了。

上一段的论述是片面的,下面考虑真正的带有“域”的情况。C++中一个域(scope)就是由花括号包围起来的一个相对独立的代码区间(当然也包含省略了花括号的单条语句)。变量作用范围对应着相应的域,即变量作用域,其核心概括起来有三点:(1)同一域内声明的变量对于声明语句之后的语句可用;(2)上级域中声明的变量对下级域(子域)可用,而反之则不可用(作用域终止);(3)下级域(子域)内声明的变量会覆盖上级域中的同名变量,但域终止后不会对以后有影响。

常量的定义与声明是在一起的(或者说声明的同时就初始化),使用像“const float PI = 3.14;”的格式声明常量。不要使用C语言中遗留下来的#define预编译命令来定义所谓的常量,因为它只是简单地进行格式替换,并不包含语法信息,编译器也不会报告潜在的语义错误。同样,简短的方法应使用inline内联定义,而不是#define。

C++函数参数可以按值传递(pass by value),也可以按引用传递(pass by reference)。引用(reference)是C++中很重要的一个概念,与之紧密相关的指针则是C++深入学习的重头戏。我们在这里简单地看一下引用的一个应用:“void swap(int &a, int &b) { int t = a; a = b; b = t; }”。这个swap()函数用来交换两个int的值,可以这样使用:“int x = 1, y = 2; swap(x, y);”。上面的语句执行完后,x的值变为2而y的值变为1。而如果把swap()声明部分的“&”符号去掉,则对传入的值并没有影响。

回顾我们最开始的“Hello World”程序(注:在这个版本的教程中这个入门示例从第一篇砍掉了,我会在本教程的结束篇把最初版本的关于环境配置的图片打包发布,其中就包含了这个程序):

#include <iostream>
using namespace std;
int main()
{
    cout << "Hello World!" << endl;
    return 0;
}

这段代码将打印出一行“Hello World!”字样。我们称“”Hello World!””为字符串字面量(string literal)。字符串,顾名思义,就是一串字符,用来显示一些数字之外的有意义的信息。从C到C++,字符串一直没有被很好地支持:C中没有原生的字符串,使用字符(char)数组来表示“串”;C++中有了STL实现的string,大多数情况下可以当做原生的类型使用,但与C字符数组等的关系又很复杂,其间涉及到非常关键的指针的内容。所以,关于字符串,真正在基础范围内的东西很少,几乎也只有一个字面量而已;如果把字符串“基础”化了,必然会使读者入了歧途,所以希望读者可以自己参考相关的权威书籍(本篇末尾会推荐)。

那么,如果不展开去讲字符串,回顾这个“Hello World”干嘛呢?其实是让你真正看看C++世界与现实世界边缘的一些东西。考虑下面的代码:

#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
    cout << "argc = " << argc << endl;
    for (int i = 0; i < argc; ++ i)
        cout << "argv[" << i << "] = " << argv[i] << endl;
    return 0;
}

亲自从命令行运行上面的代码。比如存盘为cpptut6.cpp,编译后为cpptut6.exe,在命令行下输入“cpptut6 blabla hahaha”,观察输出结果。更换命令行参数多试几次,体会一下主函数main()的这种声明格式的作用。

好了,这篇差不多也应该结束了。有朋友说教程太过“简”,根本没学明白。那么我就在这里推荐一些书籍留作后续的学习。在看这些书籍之前,保证你看过本教程两遍以上(我指有不同体验的两遍),才有提升的效果。注意,第六篇不是最后一篇,还有第七遍,不过那将是关于C++的一些代码例子,让你更加深入熟悉C++的,所以本篇是最后一篇说明性的文字了。嗯,不啰嗦了,列出推荐书籍如下:

  • The C++ Programming Language —— C++发明者编写的书籍
  • Professional C++ —— 精通C++的首选
  • The C++ Standard Library —— 关于库的使用(我在第四篇发过了)
  • C++ Primer —— 很厚,如果以上三本书实在不愿意看,看Primer吧

(本篇完;做人要厚道,转载请注明出处)

C++简明基础教程 – 第五篇 面向对象设计

Thursday, March 19th, 2009

从本篇起系列更名为《C++简明基础教程》

面向对象设计

1.动机

面向对象概念在计算机编程领域已经很流行,它能够带来软件可交换性、可重用性、易修改性以及软件部件的易于连接性。

面向对象编程的一个关键概念是软件或数据能够被“封装”。每样东西都可以装到一个“盒子”里。盒子还可以封装盒子。

在最简单的传统编程中,一个编程步骤等同于一条指令;在面向对象编程中,每一步骤可能会是一盒子指令。

下面介绍一些面向对象设计中使用的关键术语。

属性

包含于对象内的数据变量

包含

两个对象实例之间的关系,其中包含者含有一个指向被包含者的指针

封装

对象实例的属性和服务与外部环境隔离开来。服务只能通过名字调用,属性只能通过服务访问

继承

两个对象类之间的关系,子类可以访问父类的属性和服务

接口

和对象类紧密相关的描述。接口包含方法定义(没有实现)和常量值。接口不能实例化为一个对象

消息

对象交互的方式

方法

对象部分的组成过程,可在对象外部激活实现某一功能

对象

现实世界实体的抽象

对象类

共享相同名字、属性集、服务的有名对象集

对象实例

一个赋予属性值的对象类的具体成员

多态

指使用相同的服务名,对外表现相同接口却代表不同类型实体的多个对象存在

服务

作用于对象实现某一操作的函数

2.面向对象的概念

面向对象设计的中心概念是对象。对象是一个独特的软件单元,包含相关的变量(数据)和方法(过程)的集合。

一般来说,这些变量和方法在对象外面不是直接可见的。不过,定义明确的接口允许其他软件访问这些数据和过程。

一个对象代表了某些事务,也可能是一个物理实体,一个概念,一个软件模型,或是某些动态模型,例如一个TCP连接。

对象中的变量值表示对象所代表的事物的已知信息。方法包含那些执行会影响对象中的值和可能影响所代表的事物的过程。

2.1 对象结构

通常,对象中包含的数据和过程被相应地称为变量和方法。对象中的变量表示对象所知道的每件事,对象中的方法表示对象所能做的每件事。

一个对象中的变量,也称为属性,一般是简单的标量或表格。每个变量有一个类型,可能是可用值的集合,可能是常量或变量(习惯上变量也用来表示常量)。变量的访问约束可根据某些用户、某类用户或是应用情形来设定。

对象中的方法是可以从外部触发执行某些功能的过程。方法可以改变对象的状态,更新一些变量,或是作用于对象可以访问的外部资源。

对象之间通过消息交互。消息包含发送对象消息名、接收对象消息名、接收对象方法名和限定方法执行所需的任何参数。

消息只能用来激活对象内部的方法。访问对象内部数据的唯一方法是通过对象方法。这样,方法可产生一个动作或是使得对象变量可以访问。

对于局部对象,向一个对象传递消息就像调用一个对象方法一样。当对象是分布式对象时,传递消息才真正名副其实。

对象接口是对象所支持的共有方法的集合。接口没有实现任何东西;不同类的对象使用相同的接口,却可以有不同的实现。

对象仅仅通过消息与外界交互这一性质称为封装。对象的方法和变量是封装的,且仅可通过基于消息的通信方式访问。封装有两个特点:

  • 保护对象变量不被其他对象破坏。包括对非法访问的保护和并发访问所引起的如死锁和值的不一致性问题。
  • 隐藏了对象的内部结构,使得对象交互相对简单并可标准化。此外,如果修改了对象过程的内部结构而没有改变外部功能,则其他对象不受影响。
  • 2.2 对象类

    实际上,许多对象典型地代表了同一类型的事物。例如,如果一个对象代表一个进程,那么系统中每一进程都会有一个对象对应。很明显,每一个这样的对象需要自己的变量集合。然而,如果对象的方法是可重入的过程,则所有类似的对象可以共享相同的方法。而且,对每一个新的类似的对象的方法和变量都重新定义是没有效率的。

    这些难题的解决方法是将对象类和对象实例区分开来。对象类是定义可以包含在一个特定对象类型中的方法和变量的模板。对象实例是实际对象,包含了类中所定义的特征。对象含有类对象中所定义的变量的值。实例化是创建对象类的一个新对象实例的过程。

  • 继承
  • 对象类的概念是很有用的,因为对象类可以实现用最小的代价创建许多新的对象实例。使用继承机制可使得这一概念更加有用。

    继承可以在已有类上定义新的对象类。称为子类的新(更低级别)类,将自动包含称为父类的原始(更高级别)类所定义的方法和变量。子类在许多方面不同于父类:

    1. 子类可以包含父类中所没有的方法和变量。
    2. 子类可以通过重新定义来重载父类中任何相同名字的方法或变量。这提供了处理特例的简单有效的方法。
    3. 子类可以对从父类继承的方法或变量在某些方面加以限制。

    继承机制是递归的,可以实现子类成为其子类的父类。这样,就建立了一种继承层次。从概念上可以认为继承层次是定义了一种方法和变量的搜索技术。当一个对象收到一个消息,而执行没有在其类中定义的方法时,对象将沿继承层次向上搜索直至找到该方法。类似地,如果一种方法的执行构成相关变量没有在该类中定义,则对象就会沿着继承层次向上搜索变量名。

  • 多态性
  • 多态性是一个很有趣且很有用的特性,它使得把不同实现隐藏于共同接口之后成为可能。两个多态的对象使用相同的方法名字,对其他对象呈现相同的接口。

    多态性典型地应用于一个父类的多个子类的相同方法,每一方法都有一个不同的具体实现。

    多态性实现了高级别的对象通过相同的消息格式使用许多低级别对象完成类似的功能。通过多态性,在已存在的对象上做最小的改变就可以增加新的低级别对象。

  • 接口
  • 接口使得子类对象能够使用父类的功能。有时需要定义一个具有多个父类功能的子类。这可能会相应产生一个子类继承多个父类。C++是一种允许多重继承的语言。

    典型接口定义的语法和类定义看上去很像,除了没有定义方法的代码,仅仅有方法名、传递的参数、返回值的类型。接口可由一个类实现,这和继承实现一样。如果一个类实现了一个接口,则类中必须定义接口的属性和方法。实现方法的代码可以是任何样式,只要名字、参数、每一方法的返回类型和接口中的定义相同。

    2.3 包含

    包含其他对象的实例称为复合对象。包含关系可以通过在一个对象中包含指向另一对象的指针来实现。复合对象的优点是可以表示复杂的结构。例如,包含在复杂对象中的对象本身也是个复杂对象。

    复杂对象建立的结构一般局限于树型拓扑结构;也就是说,不允许循环引用,每个“子”对象实例只能有一个“父”对象实例。

    清楚对象类的继承层次和对象实例的包含层次之间的区别是很重要的。二者是不相关的。使用继承就是以最小的代价定义许多不同类型的对象。使用包含则可建立复杂的数据结构。

    3.面向对象设计的优点

  • 内部复杂性的更好组织
  • 通过使用继承,可以有效地定义相关概念、资源和其他对象。通过使用包含,可以构造反映下面任务的任意数据结构。面向对象编程语言和数据结构使得设计者能够以反映设计者所理解的方式来描述系统的资源和功能。

  • 通过复用减少开发开销
  • 复用别人编写、测试、维护的对象类,能够缩短开发、测试、维护时间。

  • 系统更易扩展和维护
  • 通常维护,包括产品增强和修补消耗了任何产品生命周期中大约65%的花销。面向对象设计可使得这一百分比下降。面向对象软件的使用可帮助减少软件不同部分潜在的交互数量,确保一个类实现的改变对系统的其余部分影响很小。

    (本篇完;以上内容整理自电子工业出版社《操作系统——精髓与设计原理(第五版)》附录B)

    C++简明基础教程 – 第四篇 STL In A Nutshell

    Wednesday, March 18th, 2009

    距离上一篇教程足有半个多月了。关于STL其实需要讲的很多,连我自己也不能说是精通,但作为基础教程,我想并不需要讲太多理论上的东西;STL的存在更多的是为了方便编码的。

    STL可以讲得很结构化的,因为它只是一个规划比较完善的库而已,具有程序的结构化特征。这篇教程大致摘取了这里(C++ Reference)的一些用法并做了一些调整,如果希望继续深入学习,这个链接是个很好的去处。另外也可以阅读《The C++ Standard Library》一书。

    STL是Standard Template Library的缩写,意为标准模板库。首先我们需要清楚C++强大的核心力量,那就是模板。模板允许我们在C++中进行泛型编程,它规定了一种统一的格式,然后在编译时(而不是运行时)自动产生所需代码。先看一个例子:

    
    template <class T>
    inline const T& max(const T& a, const T& b)
    {
        // if a < b then use b else use a
        return a < b ? b : a;
    }
    // ...
    // we use it here
    int x = 2, y = 3;
    int z = max<int>(x, y);
    // ...
    

    例子很短,注释得很清楚,主要就是看那个template。很容易看明白,使用模板就好像把东西“嵌”进去了一样,而声明的时候我们就抽象出来一个T来泛指所有类型了(也就是泛型)。

    上面的代码使用模板的部分,在编译时(重新强调,不是运行时)会产生类似下面的代码:

    
    inline const int& max(const int& a, const int& b)
    {
        // if a < b then use b else use a
        return a < b ? b : a;
    }
    

    我想这下你应该大概理解模板了。

    下面我们来说STL。

    STL里都有什么?STL提供了对常用数据结构、常用算法、迭代器、函数对象、内存管理以及一些实用方法的支持。其中数据结构占了主体部分,因为操作都是对于数据的。STL提供的数据结构可以分为三类:序列、容器适配器、关联容器。序列包括了向量、列表、双端队列;容器适配器包括了栈、队列、优先队列;关联容器包括了位集、映射、集合。

    需要提到的是我们用的string也是STL的一部分。但你可以就把它当作一个已有的类型,跟int一样。

    下面我们对常用数据结构、常用算法、迭代器分别举例介绍,而函数对象、内存管理的内容不在基础范围内,如有需要请自行查阅相关资料。

    1. 向量
    2. 我们使用向量最多的地方就是作为一个可变长度的数组使用。C++中的数组大小可以动态分配,但是分配完后不能动态改变,如果重新分配,原来的数据就没有了(因为换了一块内存)。而STL中的vector则提供了这一有用的特性。

      
      // 不断读取输入,如果是零就结束程序,
      // 否则把读入的数字添加到向量末尾
      // 程序结束之前打印向量的内容
      
      #include <iostream>
      #include <vector>
      
      using namespace std;
      
      int main()
      {
          vector<int> v;
          int n;
      
          cin >> n;
          while (n)
          {
              v.push_back(n);
              cin >> n;
          }
      
          for (int i = 0; i < v.size(); ++ i)
              cout << v[i] << endl;
      
          return 0;
      }
      
      
    3. 栈是一种非常实用的数据结构,保持有先进后出(First In, Last Out)特性。

      
      // 类似上面的例子,不过把输入的数字逆序输出(FILO)
      
      #include <iostream>
      #include <stack>
      
      using namespace std;
      
      int main()
      {
          stack<int> s;
          int n;
      
          cin >> n;
          while (n)
          {
              s.push(n);
              cin >> n;
          }
      
          while (! s.empty())
          {
              cout << s.top() << endl;
              s.pop();
          }
      
          return 0;
      }
      
      
    4. 映射
    5. 目前标准的STL还没有Hash(但已经可以找到很多编译器厂商自己的版本),而STL中的map使用最多的也能也是作为哈希表使用(至少我是这样的~)。

      
      // 读取5行数据,每行一个字符串和一个整数
      // 再读取一个字符串,输出对应的整数
      // 如果没有,报错……key not found!
      
      #include <iostream>
      #include <string>
      #include <map>
      
      using namespace std;
      
      int main()
      {
          string s;
          int n;
          map<string, int> m;
      
          for (int i = 0; i < 5; ++ i)
          {
              cin >> s >> n;
              m[s] = n;
          }
      
          cin >> s;
          if (m.count(s))
              cout << m[s] << endl;
          else
              cerr << "key not found!" << endl;
      
          return 0;
      }
      
      
    6. 排序算法
    7. C标准库有个函数叫qsort,C++的STL则提供了更加一般化的算法sort。它们两个都是基于快速排序的实现,sort的速度要比qsort快,据我所知仅慢于手写的尾递归快排(我试验过而且写出了比sort快5倍左右的QuickSort~),能满足于99%以上情况下的需要。

      
      // 读取一个n,再读取n个整数,从小到大排序后输出
      
      #include <iostream>
      #include <algorithm>
      
      using namespace std;
      
      int main()
      {
          int n;
          cin >> n;
      
          int *a = new int[n];
      
          for (int i = 0; i < n; ++ i)
              cin >> a[i];
      
          sort(a, a + n);
      
          for (int i = 0; i < n; ++ i)
              cout << a[i] << endl;
      
          delete[] a;
      
          return 0;
      }
      
      
    8. 迭代器的使用
    9. 我们习惯了在for循环中用一个i来表示index(索引),迭代器把我们从具体的i中解放出来,提供更直接的接口便于我们操作。在多数情况下,迭代器对于降低编程复杂度是很有效的。

      
      // 用一种很麻烦的方法计算数字0~9的和
      // (只是为了演示迭代器的使用)
      
      #include <iostream>
      #include <vector>
      
      using namespace std;
      
      int main()
      {
          vector<int> the_vector;
          vector<int>::iterator the_iterator;
      
          for (int i = 0; i < 10; ++ i)
              the_vector.push_back(i);
      
          int total = 0;
          the_iterator = the_vector.begin();
          while (the_iterator != the_vector.end())
          {
              total += *the_iterator;
              ++ the_iterator;
          }
      
          cout << "Total=" << total << endl;
      
          return 0;
      }
      
      

    关于STL的例子举这么多就差不多了,STL的初步学习是可以一通百通的。当然这也只是会用一些现成的东西而已,那些更高级的C++话题可都是关于STL的。

    (本篇完;做人要厚道,转载请注明出处)

    C++简明基础教程 – 第三篇 C++ in a Nutshell(解释部分)

    Saturday, February 28th, 2009

    这是本篇的第三部分。第一部分是代码在这里,第二部分是测试用例在这里,这里是代码的解释部分。

    首先从直观上看代码,每个字符都是等宽的,字体是Courier New,打代码要习惯这种字体。颜色上,有三种,黑色、绿色、蓝色。黑色的包含了很多内容,但都不特殊。绿色的是注释(Comment),没有实际执行意义,是给人看的不是给机器看的。蓝色的一些是预编译命令,另一些是关键字(Keyword),也是保留字(ReservedWord),就是保留有固定的意义,不能用作其它用途。

    C++中的注释有两种,你也看到了,两个斜线(“//”)表示从这里开始到这行末尾所有文字都是注释,而另一种是以“/*”开始以“*/”结束,表示它们中间的文字都是注释。后者来源于C语言。

    预编译命令是辅助代码编译的前期阶段处理代码的命令。代码编译成可执行程序经过三个阶段,第一阶段经过预处理器(Preprocessor)初步处理代码,第二阶段将每个源文件独立编译(Compile)成对象(Object)文件,第三阶段将对象文件链接(Link)到一起构成可执行程序。预编译命令就是用作第一阶段的。

    #include命令是C++中最常用也几乎是唯一能用到的预编译命令,它表示将某个头文件(Header)包含进来,从而使包含它的代码可以使用头文件提供的各种东西。<iostream>头文件提供了C++的输入输出流机制。流(Stream)是C++抽象出来的一种机制,可以把它想象成一个“槽”,扔到“槽”里的东西会输出,同样也可以从“槽”里获取输入。

    C++提供了三个标准的流:cin、cout和cerr,它们分别代表标准输入流、标准输出流、标准错误输出流。对流进行操作的时候,我们一般使用“>>”和“<<”两个操作符(Operator),前者代表从流中获取数据,后者代表将数据输出到流。可以通过箭头的方向来判断数据的流向,从而理解它们的意义。

    标准输入输出流是针对用户的命令行窗口的,<fstream>则提供了针对文件的流操作。输入文件流用ifstream声明,输出文件流用ofstream声明。类似地,可以把一个字符串当作一个流,<sstream>提供了这种支持,我们在代码中只用到了istringstream,还有ostringstream是向字符串做流输出用的。流跟其他的量一样使用前都需要声明(Declaration)。

    这里不妨说明一下“using namespace std;”,这一行表示的是引用命名空间std。命名空间机制可以解决同一个域(Scope)内名称重复的问题,具体的设计思想不在“基础”范围内。这里只是需要说明像cin、cout这些流以及ifstream、ofstream这些流的类型都是包含在std这个命名空间里的,所以要使用它们必须要using这个std。如果不using的话,cin就应当写成“std::cin”来表明我们用的是这个cin而不是某个别的什么甚至没有定义的东西。

    代码中出现了C++中常用的也是很基础的关键字,包括class public protected private virtual const inline void bool char int if else while for break return true false。下面分组简要说明,更详细的需要通过阅读代码(不止是这个代码,还有本系列以后的代码)来自己体会理解。

    首先是void bool char int,先不看void,后三个都是数据类型,分别表示布尔型、单字符型、整型。C++中常用的还有short(短整数) long(长整数) float(单精度浮点数) double(双精度浮点数)以及unsigned short、unsigned int、unsigned long,它们是short、int、long三种整数类型所对应的无符号(unsigned)类型,也就是占用同样大的存储空间,但是只表示非负数。数据类型可以作为函数的返回值,而void表示函数没有返回值。这里所说的函数,跟数学意义上的函数是一样的,就是给它一些参数作为输入,它就返回数据作为输出(返回值)。而返回值用到另一个关键字就是return。

    函数的一般声明格式是“数据类型 函数名 参数列表”。所以像“int main(int argc, char** argv)”就是一个相当标准的函数声明的例子,其中main是函数的名字,argc和argv是两个参数的名字。main函数也是C++程序一个很特殊的函数,因为它是整个程序执行的入口,也就是程序最开始运行的地方就是main。main函数的参数列表也是约定好的,argc表示从命令行运行该程序所提供的参数个数,argv则是一个字符串的数组,数组的每一项是对应的参数。比如我们的代码保存为cpptut3.cpp,编译后生成cpptut3.exe文件,命令行下进行测试时我们输入的是“cpptut3 tc1”,这里cpptut3就是第一个参数,tc2就是第二个参数。注意,第一个参数一般都是程序本身的名字,所以虽然我们只需要输入文件名tc1,但还是会有两个参数传进来。

    从这里接着说下去,我想你应该就明白 if 这个关键字的作用了。顾名思义,它用来判断某件事情是否成立,然后决定做什么。else 关键字配合 if 使用,它说明了如果表达式(Expression)的值为假(false),也就是不成立,程序应该做什么。而到底做什么,则是另一些语句(Statement)组合起来形成的一个块(Block)。C++中用一对花括号({})来标明一个块,一个块等价地构成一个域(Scope)。另外,C++中的语句末尾都有一个分号“;”。

    if条件语句用来判断真假并执行一次块的内容,while则不断地根据条件真假循环地执行块的内容。如果不想循环了,还可以用break语句跳出循环,执行while以后的东西。另外还可以用continue语句结束本轮循环(这个代码里没用到)。if和while很相似,它们都有相同的语义结构:“if(…){…}else{…}”表示“如果…的值是真(true)就执行…否则执行…”,而“while(…){…}”表示“当/只要…的值是真就执行…”。else子句加了斜体,因为它并不是必须的。

    除了最原始的while循环语句,C++中还有一个很强大的for循环语句。for语句的结构是“for(初始化; 循环条件; 增量){执行体}”。比如我们想输出100以内的所有偶数,我们可以从0开始每次递增2并输出这个值,用for语句就写成“for(int i = 0; i <= 100; i += 2) { cout << i << endl; }”。其中i是for语句域内的变量,所以声明int i放在for语句的初始化部分;循环条件是i<=100;增量部分我们用到了C++的简化操作符“+=”,相当于写成“i = i + 2”;循环体我们向cout流输出i的值,同时还用endl输出一个换行符。代码中的for语句例子更加复杂,需要好好体会,弄清楚循环的真正含义。

    下面我们来看class 以及 public protected private这四个关键字,它们都是类定义中很常用的关键字。所谓的类,就是某些具有相同特征的东西抽象出来的一个统一类型。代码例子中我们写了一个类App,用来对我们需要实现的程序进行包装。我们会在以后的篇目里进一步讨论这个类本身,这里只是关注形式上的东西。

    首先,类声明是一个语句,所以最后的}还要跟一个;才对。写一个类与写一个函数一样分为声明和定义两部分,声明的大体框架是“class 类名{类声明体};”。类声明体中我们声明类的方法(Method)和成员(Member)。方法就是写在类里面的函数, 比如App::App App::~App App::run App::process App::print App::log。成员就是写在类里面的变量或者常量,比如in_ out_ data_。无论方法还是成员,都需要限定谁能访问它,于是我们用到了public protected private三个访问限定符(Access Specifer)。用public限定的东西,在类的内外都可以用;用protected限定的东西,类里面可以用,从类派生的子类(SubClass)也可以用;用private限定的东西,只能在类本身里使用。关于子类,以后的篇目会详细说明;virtual关键字也与此有关,暂略。而const则表示方法不会改变类成员的值;需要注意const书写的位置。

    类的定义一般与声明分开,比如App::run App::process等等,都是像一般的函数一样定义。而App::log我们却写在类的声明里面,我们称之为内联(Inline)方法。同时我们还使用了关键字inline说明它是一个内联方法。函数也可以内联,声明的方法跟内联方法相同。内联适合一些很小规模的内容,编译器在编译的时候会把它们展开应用到每个使用到它的地方。但这也是不一定的。关于内联的知识很复杂,不在“基础”范围内,这里用到只是为了指出这个语言现象。

    我们眼前的类一定有两个方法让你觉得特别刺眼,那就是App::App和App::~App。因为C++中的名字首先不能重复,其次名字只能是“字母或下划线开头的字母、数字与下划线组合”。但这两个方法确实是合法的,因为它们特殊。App::App叫做构造函数(Constructor),App::~App叫做析构函数(Destructor)。从它们的名字就可以大概理解它们的作用了。

    构造函数在用一个类创建一个新的对象时被调用,应用到这个对象,负责对象的初始化构建工作。析构函数则在这个对象超出其作用域时被调用,负责对象的清理工作。现在可以看主函数main中的“App myApp(argv[1]);”这一行,这里我们就用类App创建了一个叫做myApp的对象,另外我们还给构造函数提供了一个参数argv[1]。创建对象后我们就可以使用它了,“myApp.run();”就调用了run函数。而这一切都是在“int main(…){…}”这个域中工作的。

    C++中的任何东西都有一个使用范围,这个范围用域来结构化。当前域可以使用域外声明的东西,当前域可以使用域内已经声明的东西,当前域内声明的东西会覆盖掉域外相同名称的东西。理解好这三条,就是一个很好的开始。

    回到之前的问题。当超出了main函数定义的域后,对象myApp就超出了它的作用域,此时就会对myApp调用析构函数。为了更好理解构造函数和析构函数,我们不妨看看它们到底都做了些什么:

    (代码需要查看永久链接页面才能看到)

    综合上面的代码和注释,一个很显然的执行过程就出现了。而在打开、关闭文件之间,我们的run()函数被调用来进行一些操作。我们把输入文件按行处理,line是一个字符串(String)表示当前读取的行,line_count则是行号计数。我们用getline函数读入一行内容到line中。注意,我们没用“in_ >> line; ”是因为默认情况下这样读取字符串只是到一个空白符为止,而我们需要处理的文件的每一行是有很多空格的,所以这样会导致错误。

    process()函数处理一行命令。我们允许的命令格式已经在那个C风格的注释中简要说明了,结合测试用例我想你应该能明白。process()函数的大体框架是好几个互斥的if条件语句,用来区分对不同的命令进行处理。在每次处理的最后,如果发生错误会返回false到run()函数的执行流程里,示意终止对输入文件的处理,如果顺利执行,则调用print()函数打印(指向控制台输出)被操作的数组(Array)的值。

    整个程序处理的核心对象是很多个数组,我们允许为每个数组命名,并通过这个名字来对相应的数组进行操作。这听上去是个很强大却又很复杂的东西,但我们只用了不到二百行就实现了。如此高效的原因在于我们使用了STL(Standard Template Library)。关于STL的描述我们将在下一篇中进行。

    好了,到此为止对这段代码的说明应该差不多了,具体的执行流程不在语法范围内,需要你好好阅读,仔细思考。

    (本篇完;做人要厚道,转载请注明出处)

    C++简明基础教程 – 第三篇 C++ in a Nutshell(测试用例部分)

    Friday, February 27th, 2009

    这是本篇的第二部分。第一部分是代码在这里,第三部分是代码解释在这里,这里是测试用例。

    ——————– tc1.txt ——————–

    sort ary_not_def

    ——————– tc2.txt ——————–

    array ary1       4 34  9  2 1
    sort  ary1
    array ary1 0

    ——————– tc3.txt ——————–

    array arr 3 4 1 1 2 3 4
    size arr
    access arr 100

    ——————– tc4.txt ——————–

    array a 93 8 21  32 21  10  84  0  0 0   0 0  283  4  2 1 3
    size a
    access a 0
    sort a
    array b 0 0 0 0 0 1 2 3 4 8 10 21 21 32 84 93 283
    sort b

    在命令行下运行的情况:

    ——————– console.txt ——————–
    C:UsersjxDocumentscpptut3>cpptut3
    *** error: need argv[1] for filename!

    C:UsersjxDocumentscpptut3>cpptut3 tc1
    *** error: array doesn’t exist!
    *** error detected at line 1

    C:UsersjxDocumentscpptut3>cpptut3 tc2
    array ary1 = [4, 34, 9, 2, 1]
    array ary1 = [1, 2, 4, 9, 34]
    *** error: array already defined!
    *** error detected at line 3

    C:UsersjxDocumentscpptut3>cpptut3 tc3
    array arr = [3, 4, 1, 1, 2, 3, 4]
    size of array arr is 7
    array arr = [3, 4, 1, 1, 2, 3, 4]
    *** error: index out of range!
    *** error detected at line 3

    C:UsersjxDocumentscpptut3>cpptut3 tc4
    array a = [93, 8, 21, 32, 21, 10, 84, 0, 0, 0, 0, 0, 283, 4, 2, 1, 3]
    size of array a is 17
    array a = [93, 8, 21, 32, 21, 10, 84, 0, 0, 0, 0, 0, 283, 4, 2, 1, 3]
    a[0] = 93
    array a = [93, 8, 21, 32, 21, 10, 84, 0, 0, 0, 0, 0, 283, 4, 2, 1, 3]
    array a = [0, 0, 0, 0, 0, 1, 2, 3, 4, 8, 10, 21, 21, 32, 84, 93, 283]
    array b = [0, 0, 0, 0, 0, 1, 2, 3, 4, 8, 10, 21, 21, 32, 84, 93, 283]
    array b = [0, 0, 0, 0, 0, 1, 2, 3, 4, 8, 10, 21, 21, 32, 84, 93, 283]

    运行后输出的日志文件:

    ——————– tc1.log ——————–

    (空白)

    ——————– tc2.log ——————–

    array ary1 defined
    array ary1 sorted

    ——————– tc3.log ——————–

    array arr defined

    ——————– tc4.log ——————–

    array a defined
    array a sorted
    array b defined
    array b sorted