LZ加油啊 ,我俩的情况相同啊 想您看齐。。。加油加油
2011.5.26 第七章 函数 在C++中,使用引用形参则更安全和更自然。 应该将不需要修改的引用形参定义为const引用。 C++程序员倾向通过传递指向容器中需要处理的元素的迭代器来传递容器。 完整main函数定义: int main(int argc, char **argv) {..} return语句用于结束当前正在执行的函数,并将控制权返回给调用此函数的函数。 注意不要返回局部变量的引用。 返回引用的函数返回一个左值。 递归函数需要注意必须定义一个终止条件。 如果有一个形参具有默认实参,那么它后面所有的形参都必须有默认实参,所以默认实参只能用来替换函数调用缺少的尾部实参。 通常应在函数声明中指定默认实参,并将该声明放在合适的头文件中。 如果在函数定义的形参表中提供默认实参,包含该函数定义的源文件中调用该函数时,默认实参才有效。 应把一些简单的销操作定义为inline函数。 容易理解操作目的、方便修改、代码重用 内联说明对于编译器来说只是一个建议,可以被忽略。 内联函数应该在头文件中定义。 类的成员函数其函数原型必须在类中定义。 编译器隐式地将在类内定义的成员函数当作内联函数。 类的成员函数可以访问该类的private成员。 这句话需要注意,是访问该类的成员,则意味只要两个对象属于相同的类,则可以访问对方的private成员。 验证代码class test { int i; public : void copy(test &b) { i = b.i; } void set(int m) { i = m; } void print() { cout << i << endl; } }; 每个成员函数(static函数除外)都有一个额外的、隐含的形参this。 const成员函数的this是const指针,不会修改其成员数据。声明方式为一般函数声明后增加 const:bool same_isbn(const Sales_item &rhs) const 构造函数和类同名,而且没有返回类型,完成创建类类型对象时的初始化操作。 可以有多个构造函数。 例子:Sales_item(): units_sold(0), revenue(0.0) { } //Sales_item为定义好的一个类 units_sold是unsigned的类成员 revenue是double的类成员 在冒号和花括号之间的代码称为“构造函数的初始化列表”,每个成员后面括号中的是初始值。 如果没有一个类显式定义的构造函数,编译器将自动为这个类生成默认构造函数。 注意这点与书上表达的不同。 重载函数需要注意的是区别函数是否重载在于形参的个数和形参的类型。书中对重载的步骤以及实参类型转换带来的细节操作进行了讨论。 仅当形参是引用或指针时,形参是否为const才有影响。 指向函数的指针中,指向不同函数类型的指针之间不存在转换。 指针调用函数可以(*p)(参数)或者p(参数),在把函数指针做形参时,也有对应的两种方式。 一般这个函数叫做回调函数(callback)。 当函数返回指向函数的指针时,最佳方法是从声明的名字开始由里而外的理解。比如int (*ff(int))(int*, int); 则声明了ff(int),其返回int (*)(int*, int)的指针。typedef可以简化。 以前看过一个关于指针的文章,在最后出题时,就刁钻地出这个,当时感觉相当怪异。 函数的指针类型必须与重载函数的一个版本精确匹配。 写到这里,我发现竟然用了2个小时的时间,其中也有自己写了些小程序来验证一些想法。在学习过程中,应该善于让编译器来帮我们验证一些问题。
Sales_item item = string("9-999-99999-9"); Sales_item类有一构造函数:Sales_item(const string&) 如果这个构造函数被定义为explicit,那么上面语句的构造函数就不是显式的,应该初始化失败。对吧? 书中代码注释是:This initialization is okay only if the Sales_item(const string&) constructor is not explocot 但是下面中文翻译出:如果构造函数是显式的,则初始化失败;如果构造函数不是显式的,则初始化成功。
2011.5.27 第八章 标准IO库 当一个类继承另一个类时,这两个类通常可以使用相同的操作。 如果函数有基类类型的引用形参时,可以给函数传递其派生类类型的对象。 wchar_t类型的标准输入对象是wcin;标准输出是wcout;而标准错误是wcerr。 IO对象不可复制或赋值。 只有支持复制的元素类型可以存储在vector或其他容器类型里。 形参或返回类型不能为流类型。 如果想传递或返回IO对象,则必须传递或返回指向该对象的指针或引用。 8.2.2给出代码中while(cin >> ival, !cin.eof()) 解释下逗号操作符的求解过程:首先计算它的每一个操作数,然后返回最右边操作数作为整个操作的结果。 flush操纵符不添加任何字符刷新流。 ends操纵符插入null。 endl输出一个换行符。 如果程序崩溃,则不会刷新缓冲区,所以使用endl是个良好的习惯。 使用tie函数可以将输入流和输出流绑在一起,任何读输入流的尝试都将首先刷新其输出流关联的缓冲区。 例如cin.tie(&cout); ostream *old_tie = cin.tie(); cin.tie(0); IO标准库使用C风格字符串作为文件名。 在尝试打开新文件之前,必须先关闭当前的文件流。 如果遇到文件结束符或其他错误,将设置流的内部状态,以便之后不允许再对该流做读写操作。即使关闭流也不能改变流对象的内部状态。而调用clear后,就像重新创建了该对象。 ifstream infile; ofstream outfile; infile.open("in"); outfile.open("out"); //通常检查打开是否成功,是个好习惯 if(!infile && !outfile) { cerr << "error: unable to open input or output file: " << endl; return -1; } infile.close(); //关闭文件流 infile.clear(); //重置流状态 infile.open("next"); 以binary模式打开的流则将文件以字节序列的形式处理,而不解释流中的字符。 以out模式打开的文件会被清空。如果要保存文件中已存在的数据,唯一方法是显示地指定app模式打开。 ofstream outfile("file", ofstream::app); stringstream提供的转换或格式化: 该对象的一个常见用法是,需要在多种数据类型之间实现自动格式化时使用该类类型。 int val1 = 512, val2 = 1024; ostringstream format_message; //format_message内容:val1: 512\nval2: 1024\n format_message << "val1: " << val1 << "\n" << "val2: " << val2 << "\n"; //自动将数值型数据的字符表示方式转换为相应的算术值 istringstream input_istring(format_message.str()); string dump; input_istring >> dump >> val1 >> dump >> val2; //一般情况下,使用输入操作符读string时,空白符将会忽略(这里补充下空白符的概念:空格、制表符、垂直制表符、回车符、换行符和进纸符)
2011.5.28 第九章 顺序容器 容器容纳特定类型对象的集合。 标准库中的三种顺序容器:vector、list、deque 使用顺序容器需要包含头文件:#include <vector> <list> <deque> 将一个容器复制给另一个容器时,类型必须匹配:容器类型和元素类型都必须相同。 使用迭代器时,不要求容器类型相同。容器内的元素类型也可以不相同,只要它们相互兼容。 指针就是迭代器。 元素初始化式必须是可用于初始化其元素类型的对象的值。 初始化,元素类型必须是内置或复合类型,或者是提供了默认构造函数的类类型。 接受容器大小做形参的构造函数只适用于顺序容器,而关联容器不支持这种初始化。 除了引用类型外,所有内置或复合类型都可做元素类型。 除输入输出(IO)标准库类型以及auto_ptr类型之外,所有其他标准库类型都是有效的容器元素类型。 特别地,容器本身也满足上述要求。 在指定容器元素为容器类型时,必须使用空格:vector< vector<string> > lines; 只有vector和deque容器提供两种重要的运算集合:迭代器算术运算、除了==和!=之外的关系操作符来比较两个迭代器。(==和!=这两种关系运算符适用于所有容器)。 不理解迭代器相加有什么实际意义。网上查资料,也没有什么结果。 C++使用一对迭代器标记迭代器范围,这个范围是左闭合区间,即[beg, end),end被作为哨兵。 在使用迭代器编写程序时,必须留意哪些操作会使迭代器失效。 在逆序迭代器上做++运算将指向容器中的前一个元素。 所有顺序容器都支持push_back操作。 在容器中添加元素时,系统是将元素值复制到容器里。 当编写循环将元素插入到vector或deque容器中时,程序必须确保迭代器在每次循环后都得到更新。 假设所有迭代器失效是最安全的做法。 比较的容器必须具有相同的容器类型,而且其元素类型也必须相同。 C++语言只允许两个容器做其元素类型定义的关系运算。 resize操作指定的新长度小于当前容器长度,则该容器后部的元素会被删除。 back操作返回容器的最后一个元素的引用。 erase(p)删除迭代器p指向的元素,返回指向被删除元素后面的元素。 在不同(或相同)类型的容器内,元素类型不相同但是相互兼容,则其赋值运算必须使用assign函数。 assign操作首先会删除容器内原来存储的所有元素。 swap操作不会删除或插入任何元素,而且保证在常量时间内实现交换,迭代器不会失效。 capacity操作获取在容器需要分配更多的存储空间之前能够存储的元素总数。 reserve操作告诉vector容器应该预留多少个元素的存储空间。 通常来说,除非找到选择使用其他容器的更好理由,否则vector容器都是最佳选择。 使用迭代器,而不是下标,并且避免随机访问元素,可以很方便地将程序从使用vector容器修改为使用list容器。 可讲string类型视为字符容器。 适配器是使一事物的行为类似于另一事物的行为的一种机制。 说白了就是一般是站着尿的,也可以蹲着尿 - -# 栈容器适配器中pop操作删除栈顶元素,但不返回其值。top操作返回栈顶元素的值,但不删除该元素。 在容器中添加或删除元素可能会使已存在的迭代器失效。
2011.5.29 第10章 关联容器 关联容器通过键(key)存储和读取元素。 map的元素以键-值(key-value)对的形式组织。 set仅包含一个键。 pair类型在utility头文件中定义。 对于pair类,可以直接访问其数据成员,分别命名为first和second。 容器元素根据键的次序排列。 使用map对象,包含头文件map。 它的键不但有一个类型,而且还有一个相关的比较函数。 键类型必须定义<操作符,而且该操作符应能“正确地工作”。 value_type是存储元素的键以及值的pair类型,而且键为const。 用下标访问不存在的元素将导致在map容器中添加一个新元素。 map::insert(e) 如果改建在map类型中已存在,则保持不变。返回一个pair类型对象以及一个bool类型的对象,表示是否插入了该元素。 下标操作符副作用:不必要的初始化。 含有一个或一对迭代器形参的insert函数版本并不说明是否有或有多少个元素插入到容器中。 不能依靠下标访问来确认该元素是否存在。 使用set对象,包含头文件set。 set不支持下标操作符,而且没有定义mapped_type类型。 value_type不是pair类型,而是与key_type相同的类型。 set容器的每个键都只能对应一个元素。 在获得指向set中某元素的迭代器后,只能对其做读操作。 multimap和multiset分别用的是map和set头文件。 multimap不支持下标运算。 带有一个键参数的erase版本将删除拥有该键的所有类型,并返回删除元素的个数。 TextQuery类的设计将将在下篇仿着实现。
2011.5.30 TextQuery类的实现 主要思路:现将输入文件用vector<string>保存副本,文件的每一行都是该vector对象的一个元素;然后构建map容器,其value_type为string(对应文件中一个单词)和set容器(该单词出现的行号,自动排序)。 这里照抄下书上给出的TextQuery类定义: calss TextQuery { public: typedef std::vector<std::string>::size_type line_no; //下标对应相应行号 //完成上述主要思路 void read_file(std::ifstream &is) { store_file(is); build_map(); } //获得一组所查找的单词出现的行号 std::set<line_no> run_query(const std::string &) const; //输出出现该单词的每一行 std::string text_line(line_no) const; private: //保存副本 void store_file(std::ifstream &); //构建map容器 void build_map(); //文本映像 std::vector<std::string> lines_of_text; //map容器 std::map< std::string, std::set<line_no> > word_map; }; 下面对每一个成员函数进行实现: 1.存储输入文件 void TextQuery::store_file(ifstream &is) { string textline; while(getline(is, textline)) lines_of_text.push_back(textline); } 2.建立map void TextQuery::build_map() { for(line_no num = 0; num != lines_of_text.size(); ++num) { istringstream line(lines_of_text[num]); string word; while(line >> word) word_map[word].insert(num); } } 3.查询操作 set<TextQuery::line_no> TextQuery::run_query(const string &query_word) const { //进行查询操作,不会发生修改,所以使用const_iterator //通过find进行查找 map<string, set<line_no> >::const_iterator site = word_map.find(query_word); if(word_map.end() == site) return set<line_no>(); //查询单词没有找到,书上直接建立一个空set返回。 else return site->second; } 4.输出单词出现行的内容 string TextQuery::text_line(line_no line) const { if(line < lines_of_text.size()) //检查行数是否合法 return lines_of_text[line]; throw std::out_of_range("line number out of range"); } TextQuery类的使用代码就不实现了。 在这次练习中,印象最深刻的是对作用域的感受。在十二章,有专门讲解作用域一节。
2011.5.31 第11章 泛型算法 “泛型”指的是它们可以操作在多种容器类型上。 算法有着一致的结构,了解算法的设计可使我们更容易学习和使用它们。 泛型算法本身从不执行容器操作,只是单独依赖迭代器和迭代器操作实现。 算法从不直接添加或删除元素。 包含头文件#include <algorithm> 在写容器元素的算法中,必须确保算法所写的序列至少足以存储要写入的元素。 对指定数目的元素做写入运算,或者写到目标迭代器的算法,都不检查目标的大小是否足以存储要写入的元素。 确保算法有足够的元素存储输出数据的一种方法是使用插入迭代器。 谓词是做某些检测的函数,返回用于条件判断的类型,指出条件是否成立。 插入迭代器是一种迭代器适配器,带有一个容器参数,并生成一个迭代器,用于在指定容器中插入元素。 back_inserter、front_inserter、inserter 可以比较两个istream迭代器是否相等,而ostream迭代器则不提供比较运算。 下面给出一个流迭代器常用的例子: istream_iterator<int> cin_it(cin); //在创建时,可直接绑定到一个流上。 istream_iterator<int> end_of_stream; //创建时不提供实参,则该迭代器指向超出末端位置。 ostream_iterator<Sales_item> output(outfile, " "); //分隔符必须是C风格字符串 提供输入操作符(>>)的任何类型都可以创建istream_iterator对象。 流迭代器的重要限制: 不可能从ostream_iterator对象读入,也不可能写到istream_iterator对象中。 一旦给ostream_iterator对象赋了一个值,写入就提交了。此外,每个不同的值都只能正好输出一次。 ostream_iterator没有->操作符。 sort调用完成后,重复输入的数就会相邻存储。 流迭代器不能创建反向迭代器。 反向迭代器用于表示的范围,和普通迭代用于表示的范围不对称。 迭代器种类:输入迭代器、输出迭代器、前向迭代器、双向迭代器、随机访问迭代器 除了输出迭代器,其他类别的迭代器形成了一个层次结构:需要低级类别迭代器的地方,可使用任意一种更高级的迭代器。 关联容器的键是const对象,因此关联容器不能使用任何写序列元素的算法。 在处理算法时,最好将关联容器上的迭代器视为支持自减运算的输入迭代器,而不是完整的双向迭代器。 对于每一个形参,迭代器必须保证最低功能。 算法最基本的性质是需要使用的迭代器种类。 四种形式: alg(beg, end, other parms); alg(beg, end, dest, other parms); alg(beg, end, beg2, other parms); alg(beg, end , beg2, end2, other parms); 1)dest形参是一个迭代器,用于指定存储输出数据的目标对象。 算法假定无论需要写入多少个元素都是安全的。 通常要使用插入迭代器或者ostream_iterator来。 ostream_iterator则实现写输出流的功能,无需考虑所写的元素个数。 2)beg2视为第二个输入范围的首元素 算法假定以beg2开始的范围至少与beg与end指定的范围一样大 重新对容器元素排序的算法使用<操作符,则第二个重载版本带有一个额外的形参,表示用于元素排序的不同运算。 检查指定值的短发默认使用==操作符,带有谓词函数形参的算法,名字带有后缀_if。 无论算法是否检查它的元素值,都可能重新排列输入范围内的元素。将元素写到指定的输出目标,在名字中添加后缀_copy。