注册 登录
编程论坛 C++教室

C++学习笔记,已结束

naruto01 发布于 2011-05-23 01:18, 3201 次点击
篇首:
    再过一个月大二就结束了,上的大专,所以就要被赶出去找工作。感到很迷茫,也很恐慌。在这两年里,图书馆里的书乱七八糟什么都看过,犯了贪多的毛病。想学习下Windows编程,但是很反感GDI那些东西;看完PHP,又不喜欢网页的表格、排版等,到头来什么也不会做。一个多月前,看到一个愿意培养新人的公司,希望在暑假前自学完C++,前去求职学习。
    刚才看了些牛人的博客,心里也很郁闷。感觉学习的进度为什么这么慢。心里告诉自己不要急躁。但,唉。从小到大,都不敢跟别人一样,在做事之前,都拍着胸脯打保证说没问题,趁着心里面的复杂感受,赶紧壮着胆注册了帐号,希望借本帖的小地方督促自己学习,怕自己半途而废,也免得我光看一遍,没有怎么实际练习过。

    本人状况:非计算机专业  熟悉C  能看懂点汇编  没有学习过数据结构  基本算法也都不知道
    使用教材:C++ Primer 中文版(第4版)

    更新安排:23号晚上开始跟帖更新,以后每日晚上更新,由于目前已经看到 顺序容器 9.3.7  所以在近两天,如果我们不补课的话,尽快把前面补上。(只剩一个月,有好多实验和实训,还要结课,所以有点忙)

    如果此贴违反相关规定,请版主在删帖之余,麻烦发个说明给我。谢谢。

[ 本帖最后由 naruto01 于 2011-7-3 17:34 编辑 ]
40 回复
#2
Toomj2011-05-24 11:37
楼主,帮你顶哈~~~~
#3
zhoufeng19882011-05-24 17:04
呵呵,坚持是最难的。
#4
naruto012011-05-24 19:07
谢谢楼上两位的支持。我也知道坚持很不容易,所以才这样做。因为比较要面子,希望可以成为坚持下来的动力吧
昨天访问不了 现在补上昨天的笔记

程序代码:

2011.5.23
第一章    快速入门
    这一章让我见识了下C++程序是什么样子的,以及了解了一点非常重要的概念:类。大概看下,明白个大意就行了。
    在整个学习过程中,我比较注重对正确做法的理解或选择相对省事的做法,可能会省略掉一些关于错误行为的分析和解释。
    值得注意的有:
        1.C++中,每个表达式都产生一个结果。   输出操作(std::cout <<)返回值是输出流本身,输入流同样。
        2.命名空间(namespace)的概念。    在初级学习中,一般加上using namespace std;即可。
        3.从开始学习,就应该养成遵循规范的良好习惯。    比如#include <iostream>与C语言中的区别。
        4.在适当位置声明变量,更有助于规范程序思路。    比如for(int i = 1; i <= 10; ++i);

    就以最基本的一个C++程序来结束这一章的学习吧。
        //这是我的第一个C++程序
        
//code by naruto01
        #include <iostream>
        int main()
        {
            for(int i = 1; i <= 10; ++i)
                if((i % 2) != 0)
                    std::cout << "Hello,World!" << std::endl;
            return 0;
        }
        
第二章    变量和基本类型
    本章介绍了C++变量和基本类型的相关内容,比较重要的有const限定符、引用、typedef以及类类型。
    2.1基本内置类型中,值得注意的是C++中把负值赋给unsigned对象是合法的,结果是该负数对2^n(n是该类型的存储位数)求模后的值。
    2.2字面值常量,有宽字符概念,即在字面值前面加L。 宽字符是一个字符用两个字节来存放。
    2.3变量
        注意“左值”和“右值”的概念。
            左值(lvalue)可以出现在赋值语句的左边或右边。
            右值(rvalue)只能在右边。
        C++中的标识符区分大小写。
        保持一致的命名习惯。
        在变量定义时指定了初始值,称为初始化。 要注意对未初始化的变量使用可能引起运行问题,所以最安全和容易的做法是对每个内置类型变量进行初始化。
        类通过构造函数进行初始化。
        变量名的作用域 http://learn.对作用域的解释很好理解
        引用使用&进行定义,并且必须初始化,不能定义引用的引用。
        typedef的使用目的:
            为了隐藏特定类型的实现,强调目的
            简化
            允许一种类型用于多个目的
        类在定义后面需要添加分号(;)。
            类中的操作和数据称为类的成员。
            访问标号这里介绍了有public、private
            struct和class的唯一差别在于,class在没有访问标号时,任何成员都是private;struct则为public。   
        预处理时避免多重包含的技巧:
            #ifndef MY_MARK
            #define MY_MARK
            #endif
            
    一小段程序
        //myclass.h
        #ifndef MY_MARK
        #define MY_MARK
        #endif
        
        class my_class {
            public:
                std::string  str;
        };
        
        //主程序
        
//code by naruto01
        #include <iostream>
        #include "myclass.h"

        typedef class my_class my_defed;

        int main()
        {
            my_defed test;
            std::string &ref = test.str;
            std::cin >> test.str;
            std::cout << "本体:" << test.str << std::endl;
            std::cout << "引用: " << ref << std::endl;
            return 0;
        }


[ 本帖最后由 naruto01 于 2011-5-24 19:56 编辑 ]
#5
紫凤双飞2011-05-24 20:19
顶一个
#6
naruto012011-05-24 21:49
程序代码:
2011.5.24
第三章    标准库类型
    在头文件中,必须总是使用完全限定的标准库名字。
    string类型的输入操作符需要注意有两点:
        1.读取并忽略开头所有的空白字符。
        2.读取字符直至再次遇到空白字符终止。   
    使用getline读取整行文本,和上述特点配合,可以有效分解句子中的单词。例子将在IO操作看到。
    string对象size()操作返回的是string::size_type类型。    这表示应使用相关库类型的配套类型,它们的使用与机器无关,并可以减少错误发生。
    在3.2.3的6中,有一例子string s5 = s1 + ", " + "world";     s1 + ", "    返回string对象,然后再与"world"进行连接。也体现了第一章的描述:C++中,每个表达式都产生一个结果。
    string对象字符处理的相关函数定义在cctype头文件中。    cname表示这个头文件源自C标准库。
   
    vector是同一种类型的对象的集合。使用应包含#include <vector>。
    vector是一个类模版,而vector<int>则是数据类型。
    vector是可以动态增长的。    简直太爽了,C写程序时一直愁数组大小不能更改,如果想动起来,就得自己进行内存管理,很麻烦,难怪vector应用很普遍。
    C++程序员习惯优先选用!=进行循环判断条件。
   
    迭代器是一种检查容器内元素并遍历元素的数据类型。    可以想象成一种智能、自适应的特殊指针,这样对于迭代器的操作,对于熟悉C的人来说,更快理解。
    每种容器通过begin和end函数返回迭代器。其中begin指向第一个元素,而end返回的是“容器末端元素的下一个”。
    任何改变vector长度的操作都会使已存在的迭代器失效。    在使用迭代器的时候,要特别注意当前使用迭代器是否仍然有效,这是很容易被忽视导致的错误。
   
    bitset类型使直接操作若干位提供了方便。
    size_t类型定义在cstddef中,是一个与机器相关的unsigned类型,大小足以保证存储内存中对象的大小。    在一些程序的源代码中经常看到这个类型,后面数组的下标也应使用size_t。
   
    //code by naruto01
    #include <iostream>
    #include <string>
    #include <cctype>
    #include <vector>

    //这里啰嗦一次,以后using namespace std;
    using std::cin;
    using std::cout;
    using std::endl;
    using std::string;
    using std::vector;

    int main()
    {
        string s;
        if(s.empty())
            cin >> s;
        s += " naruto01";
        cout << s << endl;
        //s转换成大写形式
        for(string::size_type ix = 0; ix != s.size(); ++ix)
            s[ix] = toupper(s[ix]);
        cout << s << endl;
   
        vector<int> ivec(12, 8);
        ivec.push_back(9);
        for(vector<int>::iterator iter = ivec.begin(); iter != ivec.end(); ++iter)
            *iter = 6;
      
        return 0;   
    }

第四章    数组和指针
    理解指针声明语句时,从右向左阅读方便理解。
    预处理器变量NULL在cstdlib头文件中定义。
    C++提供void*指针,只与地址值有关。
    注意区分指向const对象的指针(const int *p)、const指针(int *const p)、指向const对象的const指针(const int *const p)。    观察const修饰的对象有助于区分他们。
    在4.2.5中谈到指针和typedef给出一例:
        typedef string *pstring;
        const pstring cstr;
        其中cstr变量是什么类型?一般错误地认为是指向const string的指针是因为把typedef当作文本扩展。typedef定义了pstring是一个指针,所以const修饰指针。    这里提到并不是故意抠字眼,在编写代码中,应该尽可能帮助人理解代码意图,当然一些定义习惯还是应该学习的。
        
    C++提供了新的内存管理方法,new和delete。需要注意在new申请内存使用结束后,应及时delete进行释放。
        在释放数组时,应该的操作:delete [] pia;
        
    多维数组中,很容易搞混的就是“指针的数组”和“指向数组的指针”了。
        int (*p)[4];    //指向拥有4个int元素数组的指针(pointer to an array of 4 ints)    我感觉英文注释更直观,在此一并附上
        int *p[4];    //int指针的数组,有4个元素(array of pointers to int)
        小技巧:后缀运算符的优先级高于单目运算符,即优先级[] > *,所以先和谁结合,也就确实了是数组还是指针。    具体可以参考http://learn.
        
    这一章跟C基本没有什么差别,所以也就不再列程序了


[ 本帖最后由 naruto01 于 2011-5-24 21:53 编辑 ]
#7
Toomj2011-05-25 10:19
继续顶
#8
刘俊凯2011-05-25 10:21
hao
#9
donggegege2011-05-25 12:43
慢慢啃书本吧,
#10
naruto012011-05-25 22:53
程序代码:
2011.5.25
第五章    表达式
    一般情况下,表达式的结果是右值。    int var; ++var; var++;    这里的两个表达式结果则为左值。
    在进行expr1 && expr2或者expr1 || expr2的判断时,会有“短路求值”,所以不能指望在一个逻辑判断表达式中,可以完成所有预期的操作。    大一看过《编程卓越之道 第二卷:运用底层语言思想编写高级语言代码》,在讲到短路求值时,好像意思是说不同的语言有不同的求值顺序。在C++ Primer中说到“总是先计算其左操作数,然后再计算其右操作数”。(中文版P132),应该仅指C++。
   
    位操作,不是循环移位,而是丢弃移出去的位,用0填充。
        原来在学习C语言过程中,就不是太注意。后来接触汇编,发现右移和左移代替一些乘除法的效率简直是惊人的,了解到补码这些概念后,在尝试把一段汇编代码翻译成C的时候,才意识到对“符号位”的操作存在空白。在P134中指出:“位操作符如何处理其操作数的符号位依赖于机器。”,最佳实践值得借鉴:“对于位操作符,由于系统不能确保如何处理其操作数的符号位,所以强烈建议使用unsigned整型操作数”。   
   
    复合赋值操作符,值得注意的是左操作数只计算了一次。    可能在一些小测试中,会用到这一点来坑害百姓哦。
   
    自增和自减操作符中,讲到了关于语言本身实现前置和后置操作的差别,前置操作更有效率。应该在没有差别时,优先使用前置操作。
   
    sizeof操作符返回的是一个对象或类型名的长度,单位是字节,返回值类型是size_t,编译时是常量。
   
    结合性中讲到的“左结合性”和“右结合性”。以前看书不是太肯定,这里尝试描述一下:从左到右就是左结合。
   
    不推荐死记硬背操作符的优先级,善于利用(),不过手边有一份优先级的表还是不错的选择。 ^ ^
   
    动态内存管理中,注意delete删除指针之后,该指针变成悬垂指针,应该及时将指针置0.
   
    类型转换中,有个const_cast,可以转换掉表达式的const性质,但书中提到,使用const_cast也总是预示着设计缺陷。
   
   
    本节不做程序为例。
   
第六章    语句
    在故意省略case后面的break语句是很罕见的,因此应该提供一些注释说明其逻辑。
    for循环语句,以前一直存在一个误区,以为必然会首先执行一次,类似do-while形式,其实是初始化语句(init-statement)结束后,求解判断条件(condition)。
    try/throw/catch在之前看的国内C教材,只在关键字中看到过,这里简单了解一下。主要有<stdexcept>中定义的若干标准异常类,定义了一个what的操作,没有任何参数,返回异常提供更详细的文字描述。
   
    预处理器调试时的常量:__FILE__    文件名、__LINE__    当前行号、__TIME__    文件被编译的时间、__DATE__    文件被编译的日期
    assert宏定义在cassert头文件,它的有效性取决于NDEBUG是否定义。使用assert来测试“不可能发生”的条件,如果assert宏求解条件表达式为false,则输出信息并且终止程序。
   
    对于异常处理这块,不是很感冒,所以也仅作了解。第一章程序已经使用了for、if,这里在此基础上仅仅简单地增加使用break。代码都会用到这几章的内容,是最普遍不过的基本组成部分了,所以也算是让自己好看点。多少复制点代码吧 - -#  唯一有价值的感觉就在注释的那句话。真不好意思。之后章节将只提供代码片段,重点快来咯。
   
    //code by naruto01
    #include <iostream>
    int main()
    {
        for(int i = 1; i <= 10; ++i)
            if((i % 2) != 0)
                std::cout << "Hello,World!" << std::endl;
            else if(7 == i)    //防止误写成i = 7的好办法
                        break;
        return 0;
    }   
#11
fz199101252011-05-26 09:44
LZ加油啊 ,我俩的情况相同啊 想您看齐。。。加油加油
#12
W1393072011-05-26 10:44
顶···  分享学习经验的帖子很少
#13
naruto012011-05-26 18:59
to fz19910125:用您都太抬举我了。  我们一起加油吧!
to W139307:谢谢你的支持。不过用搜索引擎就会发现分享学习经验的人大大的哦。
程序代码:
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指针,不会修改其成员数据。声明方式为一般函数声明后增加 constbool 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个小时的时间,其中也有自己写了些小程序来验证一些想法。在学习过程中,应该善于让编译器来帮我们验证一些问题。
#14
naruto012011-05-28 01:26
现在已经看到第13章 复制控制。开始感觉比较吃力,希望自己可以坚持住。  在407页13.1.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
但是下面中文翻译出:如果构造函数是显式的,则初始化失败;如果构造函数不是显式的,则初始化成功。

希望有哪位朋友可以指点一下。
欢迎各位朋友对我学习中的一些错误提出批评和指导。非常感谢你们的关注!  
另外补充关于这个笔记的说明:我已经比较熟悉C,也之前看过一些国内的C++资料,大概也知道继承、重载等的概念,并且算是略读这本书,希望对C++有个权威、系统的了解。如果你感觉有点过于简略,那么也开始你的学习笔记之旅吧!
程序代码:
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时,空白符将会忽略(这里补充下空白符的概念:空格、制表符、垂直制表符、回车符、换行符和进纸符)
        
#15
naruto012011-05-29 00:36
程序代码:
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操作返回栈顶元素的值,但不删除该元素。
   
    在容器中添加或删除元素可能会使已存在的迭代器失效。
#16
爱德华2011-05-29 21:39
顶啊!写的好!
#17
naruto012011-05-29 22:57
to 爱德华:都是摘抄书上的  - -#  不过还是谢谢
程序代码:
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类的设计将将在下篇仿着实现。


[ 本帖最后由 naruto01 于 2011-5-30 17:46 编辑 ]
#18
雨的帝国2011-05-30 14:58
顶你是必须的
#19
naruto012011-05-30 17:45
to 雨的帝国:谢谢
程序代码:
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类的使用代码就不实现了。
    在这次练习中,印象最深刻的是对作用域的感受。在十二章,有专门讲解作用域一节。


[ 本帖最后由 naruto01 于 2011-5-30 17:49 编辑 ]
#20
naruto012011-05-30 17:45
程序代码:
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。


[ 本帖最后由 naruto01 于 2011-5-31 23:42 编辑 ]
#21
起点de崛起2011-05-31 08:17
顶一下  楼主威武
#22
naruto012011-06-01 22:21
明天把“类”这一章的笔记写完,就跟我的学习进度同步了。最近状态很差,上课都是在画重点,没办法看书熬课上时间了  进度也变慢了,原先一天20页,这星期总共看了20页,看完16章就可以了。下星期开始复习,计划一周更新一章。25号结束整个学习笔记。
谢谢大家的关注。
程序代码:
2011.6.1
第12章    类
    成员可以是数据、函数或类型别名。
    构造函数初始化列表跟在构造函数的形参表之后,并以冒号开头。
    在类内部,声明成员函数是必需的,而定义成员函数则是可选的。
        类内部定义的函数默认为inline。
        成员函数有一个附加的隐含实参——this指针。
    const必须同时出现在声明和定义中。
   
    类背后蕴含的基本思想是数据抽象和封装。
    封装是一项将低层次的元素组合起来形成新的、高层次实体的技术。
    一个访问标号可以出现的次数通常是没有限制的。
    数据抽象和封装的两个重要有点:
        避免类内部出现无意的、可能破坏对象状态的用户级错误。
        随时间推移可以根据需求改变或缺陷报告来完善类实现,而无须改变用户级代码。
        
    类可以定义自己的局部类型名字,将这个类型设为public,就允许用户使用这个名字。
    成员函数只能重载本类的其他成员函数。
    不在类定义体内定义的inline成员函数,其定义通常应放在有类定义的同一头文件中。
        对于不完全类型,不能定义该类型的对象,只能用于定义指向该类型的指针及引用,或者用于声明使用该类型作为形参类型或返回类型的函数。
    定义类型时不进行存储分配。
    因为在类定义之后可以接一个对象定义列表。定义必须以分号结束。
   
    成员函数不能定义this形参。
    当我们需要将一个对象作为整体引用而不是引用对象的一个成员时,需要显式引用this。
    const成员函数只能返回*this作为一个const引用。
   
    可变数据成员永远都不能为const,const成员函数可以改变mutable成员。
   
    定义类型的成员,如Screen::index(称为完全限定名),使用作用域操作符来访问。
    返回类型出现在成员名字前面,如果返回类型使用由类定义的类型,则必须使用完全限定名。
    在C++程序中,所有名字必须在使用之前声明。
    如果类作用域中使用的名字不能确定为类成员名,则在包含该类或成员定义的作用域中查找,以便找到该名字的声明。
    一旦一个名字被用作类姓名,该名字就不能被重复定义。
    尽管类的成员被屏蔽了,但仍然可以通过用类名来限定成员名或显式使用this指针来使用它。
    尽管全局对象被屏蔽了,但通过用全局作用域确定操作符(::)来限定名字,仍然可以访问。
#23
aikongfu2011-06-02 12:52
顶,楼主一定会找到好工作的
#24
wyfeng20102011-06-02 19:59
顶楼主,我也刚开始学C++,向你学习,坚持做笔记!
#25
naruto012011-06-02 23:39
to aikongfu:借你吉言咯。
to wyfeng2010:加油!
程序代码:
2011.6.2
    只要创建类类型的新对象,都要执行构造函数。
    构造函数不能声明为const。
    创建类类型的const对象时,运行一个普通构造函数来初始化该const对象。
    构造函数可以定义在类的内部或外部。
    构造函数初始化式只在构造函数的定义中而不是声明中指定。
    构造函数分两个阶段执行:(1)初始化阶段 (2)普通的计算阶段
        计算阶段由构造函数体中的所有语句组成。
        类类型的数据成员总是在初始化阶段初始化。
        初始化发生在计算阶段开始之前。
    有些成员必须在构造函数初始化列表中进行初始化。对于这样的成员,在构造函数函数体中对它们赋值不起作用,没有默认构造函数的类类型成员,以及const或引用类型成员,都必须在构造函数初始化列表中进行初始化。
        初始化const或引用类型数据成员的唯一机会是在构造函数初始化列表中。
    不指定初始化执行的次序,成员被初始化的次序就是定义成员的次序。
        按照与成员声明一致的次序编写构造函数初始化列表是个好主意。
        尽可能避免使用成员来初始化其他成员。
   
    一个类哪怕只定义了一个构造函数,编译器也不会再生成默认构造函数。
    如果每个构造函数将每个成员设置为明确的已知状态,则成员函数可以区分空对象和具有实际值的对象。
    如果定义了其他构造函数,则提供一个默认构造函数几乎总是对的。
    初级C++程序员常犯的一个错误:Sales_item myobj();    这是函数声明,不是一个对象。
        正确做法:Sales_item myobj;    或者    Sales_item myobj = Sales_item();
   
    可以通过将构造函数声明为explicit,来防止在需要隐式转换的上下文中使用构造函数。
        explicit关键字只能用于类内部的构造函数声明上,类的定义体外部所做的定义上不再重复。
    任何构造函数都可以用来显式地创建临时对象。
    通常,除非有明显的理由想要定义隐式转换,否则,单形参构造函数应该为explicit。
   
    友元(friend)机制允许一个类将对其非公有成员的访问权授予指定的函数或类。
        只能出现在类定义的内部。
        一般在开始或结尾。
    友元可以是普通的非成员函数,或前面定义的其他类的成员函数或整个类。
    必须先定义包含成员函数的类,才能将成员函数设为友元。另一方面,不必预先声明类和非成员函数来将它们设为友元。    现在看怎么这么饶,大概意思应该是对于友元是成员函数,应该先定义包含该成员函数的类;友元是类和非成员函数,则不必预先声明。
    类必须将重载函数集中每一个希望设为友元的函数都声明为友元。
   
    类可以定义类静态成员。
    static数据成员独立于该类的任意对象而存在,它是与类关联的对象。
    static成员函数没有this形参。
        它可以直接访问所属类的static成员,但不能直接使用非static成员。
    static成员是类的组成部分但不是任何对象的组成部分。
    static成员函数不能被声明为const或虚函数。
    static数据成员必须在类定义体的外部定义。
        static成员在定义时进行初始化。
        将static数据成员的定义放在包含类的非内联成员函数定义的文件中。
    一旦成员名出现,static成员的定义就是在类作用域中。
    static关键字只能用于类定义体内部的声明中,定义不能标示为static。
    一般而言,类的static成员,像普通数据成员一样,不能在类的定义体中初始化。
        例外是const static数据成员可以在类的定义体内初始化。
        该数据成员仍必须在类的定义体之外进行定义,不必再指定初值。
        
    通过将类的实现所用到的数据和函数设置为private来封装类。
#26
test_l2011-06-09 17:15
LZ加油。我也和你一样什么也不懂,也正在学习C++。希望我们都会成功的,
#27
naruto012011-06-12 17:17
to test_l:有志者事竟成
程序代码:
2011.6.12
第13章    复制控制
    如果没有显式定义复制构造函数或赋值操作符,编译器(通常)会为我们定义。
    复制构造函数具有单个形参,该形参(常用const修饰)是对该类类型的引用。    复制构造函数最主要的识别特征!
        当将该类型的对象传递给函数或从函数返回该类型的对象时,将隐式使用复制构造函数。
    赋值操作符可以通过指定不同类型的右操作数而重载。
    编译器自动实现复制控制(复制构造函数、赋值操作符和析构函数),也可以定义自己的版本。
    当类具有指针成员,需要类定义自己的复制控制成员。
   
    复制初始化首先使用指定构造函数创建一个临时对象,然后用复制构造函数将临时对象复制到正在创建的对象。
    vector<string> svec(5);    容器这种构造方式使用了默认构造函数和复制构造函数。
    对于类类型数组,如果希望不指定实参或指定多个实参,就需要使用完整的构造函数语法。
   
    编译器在即使我们定义了其他构造函数,也会合成复制构造函数。
    复制数组时合成复制构造函数将复制数组的每一个元素。
    有些类必须对复制对象时发生的事情加以控制:1.类经常有一个数据成员是指针;2.一些类在创建新对象时必须做一些特定工作。
   
    有些类需要完全禁止复制,类必须显式声明其复制构造函数为private。
    如果连友元和成员中的复制也禁止,则声明private但不对其定义。
   
    重载操作符是一些函数,名字为operator后跟着所定义的操作符的符号。
    当操作符为成员函数时,它的第一个操作数隐式绑定到this指针。
    内置类型的赋值运算返回对右操作数的引用。 赋值操作符的返回类型应该与内置类型赋值运算返回的类型相同。
    合成赋值操作符返回*this,对左操作数对象的引用。
   
    撤销类对象时会自动调用析构函数。
        容器中的元素总是按逆序撤销。
    三法则:如果类需要析构函数,则它也需要赋值操作符和复制构造函数。    这是一个经验法则。
    合成析构函数并不删除指针成员所指向的对象。
    不能重载析构函数。
    即使编写了析构函数,合成析构函数仍然运行。
   
    中间的消息处理示例跳过,管理指针成员看的迷迷糊糊,大概意思明白了,当时看这一章时,感受十分不好,现在写笔记回头看,思路清晰了许多,也不再赘述,是一种方法。   
    最后以书上的一句话结束本章:定义复制控制函数最为困难的部分通常在于认识到它们的必要性。
   
第14章    重载操作符与转换
    保留字operator后接需定义的操作符符号。
    重载操作符具有返回类型和形参表。
    重载操作符的形参数目(包括成员函数的隐式this)与操作符的操作数数目相同。
        函数操作符( operator() )可以接受任意数目的操作数。
    重载操作符必须具有一个类类型或枚举类型操作数。
        用于内置类型的操作符,其含义不能改变。
        不能为任何内置类型定义额外的新的操作符。
    操作符的优先级、结合性或操作数数目不能改变。
    +,-,*和&取决于操作数数目。
    函数调用操作符之外,重载操作符时使用默认实参是非法的。
    重载操作符并不保证操作数的求值顺序。
    作为类成员的重载函数,隐含的this形参,限定为第一个操作数。
    操作符定义为非成员函数时,通常必须将它们设置为所操作类的友元。
   
    如果重新定义&&和||,将失去操作符的短路求值特征。
    重载逗号、取地址、逻辑与、逻辑或等操作符通常不是好做法。
    当一个重载操作符的含义不明显时,给操作取一个名字更好。对于很少用的操作,使用命名函数通常也比用操作符更好。
    复合赋值操作符通常应定义为类的成员。
    自增、自减和解引用应定义为类成员。
    对称的操作符,如算术操作符、相等操作符、关系操作符和位操作符,最好定义为普通非成员函数。
   
    输出操作符<<返回类型是一个ostream引用。
    一般而言,输出操作符应输出对象的内容,进行最小限度的格式化,它们不应该输出换行符。让用户自己控制输出细节。
    必须使输出或输入操作符称为非成员操作符。
    输入操作符必须处理错误和文件结束的可能性。
    输入期间的错误:
        (1)任何读操作都可能因为提供的值不正确而失败。这次的读入以及流的后续使用都将失败。
        (2)任何读入都可能碰到输入流中的文件结束或其他一些错误。
    设计输入操作符时,如果可能,要确定错误恢复措施,这很重要。
    通常错误最好留给IO标准库自己来指出。
   
    为了与内置操作符保持一致,加法返回一个右值,而不是一个引用。    这里总结一下:对于重载操作符,要遵循标准库定义的习惯,保持使用习惯上的统一。
    相等和不等操作符一般应该相互联系起来定义,让一个操作符完成比较对象的实际工作,而另一个操作符只是调用前者。
    赋值操作符必须定义为成员函数,且必须返回对*this的引用。
    一般而言,赋值操作符与复合赋值操作符应返回左操作数的引用。
    下标操作符必须定义为类成员函数。
        一般需要定义两个版本,一个为非const成员并返回引用,另一个为const成员并返回const引用。
        这里讨论一下书上的例子:
            int &operator[] (const size_t);
            const int &operator[] (const size_t) const;
                重载的判决因素应该有该成员函数是否为const。
    箭头操作符必须定义为类成员函数。
        必须返回指向类类型的指针,或者返回定义了自己的箭头操作符的类类型对象。
    解引用操作符不要求定义为成员。
    自增/自减操作符的前缀与后缀的区别方法:
        后缀式操作符函数接受一个额外的(无用的)int型形参。    使用后缀式操作符时,编译器提供0作为这个形参的实参。
    为了与内置类型一致,前缀式操作符应返回被增量或减量对象的引用,后缀式操作符应返回旧值,且应作为值返回。
        一般而言,最好前缀式和后缀式都定义。
    函数调用操作符必须声明为成员函数。一个类可以定义函数调用操作符的多个版本,由形参的数目或类型加以区别。
        定义了调用操作符的类,其对象常称为函数对象,即它们是行为类似函数的对象。
        函数对象经常用作通用算法的实参。
    标准库函数对象类型在functional头文件中定义。
        每个标准库函数对象类表示一个操作符。
        给出一个使用例子:
            sort(svec.begin(), svec.end(), greater<string>);
        函数适配器种类:
            绑定器(binder),它通过将一个操作数绑定到给定值而将二元函数对象转换为一元函数对象。
                bind1st, bind2nd        名字表现出绑定的实参位置。
            求反器(negator),它将谓词函数对象的真值求反。
                not1, not2    名字表现出对几元函数对象的真值求反。
   
    一个类可以定义自己的转换,应用于其类类型对象。
    转换操作符是一种特殊的类成员函数。
        operator type();
        转换函数必须是成员函数,不能指定返回类型,并且形参表必须为空。
        转换函数一般不应该改变被转换的对象。因此,转换操作符通常应定义为const成员。
    类类型转换之后不能再跟另一个类类型转换。
   
    之后内容跳过。
#28
bad_boy2011-06-12 18:51
求链表详解。。。
#29
plp3131312011-06-13 21:02
我也在看C++primer ,好厚,所以,共同努力吧
#30
一点温柔2011-06-15 18:56
要向楼主看齐!
#31
naruto012011-06-17 22:47
开始考试,我都还没复习。我不想最后一学期挂科啊~~~~~~  先停止C++。25号之后,再做计较

to bad_boy:数据结构的链表么? 数据结构书上都讲得很详细吧?我菜鸟。放过我
to plp313131:加油~不过现在别学我 - -# 连课本都看不进去。。
to 一点温柔:像一点温柔童鞋看齐!
#32
naruto012011-07-03 17:31
程序代码:
2011.7.3
第15章    面向对象编程
    面向对象编程(Object-oriented programmingm OOP)基于三个基本概念:数据抽象、继承和动态绑定。
    多态性,通过继承而相关联的类型为多态类型,是因为在许多情况下可以互换地使用派生类型或基类型的“许多形态”。
    在C++中,多态性仅用于通过继承而相关联的类型的引用或指针。
    在C++中,基类必须指出希望派生类重定义哪些函数,定义为virtual的函数是基类期待派生类重新定义的,基类希望派生类继承的函数不能定义为虚函数。
    在C++中,通过基类的引用(或指针)调用虚函数时,发生动态绑定。
    继承层次的根类一般都要定义虚析构函数即可。
    在非虚函数的调用在编译时确定。除了构造函数之外,任意非static成员函数都可以是虚函数,保留字virtual不能用在类定义体外部出现的函数定义上。
    protected成员可以被派生类对象访问但不能被该类型的普通用户访问。
    派生类只能通过派生类对象访问其基类的protected成员,派生类对其基类类型对象的protected成员没有特殊访问权限。
        提供给派生类型的接口是protected成员和public成员的组合。
    类派生列表指定了一个或多个基类。
        访问标号决定了对继承成员的(最大)访问权限。        这句话自己添加的不是很确切,主要是指继承成员的访问权限等于或严格于派生类的访问标号、
            如果想要继承基类的接口,则应该进行public派生。        经常见到的也是这种派生方式。
    派生类一般会重定义所继承的虚函数。
        如果派生类没有重定义,则使用基类中定义的版本。
    派生类必须对想要重定义的每个继承成员进行声明。
        派生类中虚函数的声明必须与基类中的定义方式完全匹配。    一个例外:返回对基类型的引用(或指针)的虚函数,派生类中的虚函数可以返回基类函数所返回类型的派生类的引用(或指针)。
        派生类重定义虚函数时,可以使用virtual,但不是必须这样做。
   
    已定义的类才可以用作基类。
        暗示着不可能从类自身派生出一个类。
    基类本身可以是一个派生类。
    如果需要声明(但并不实现)一个派生类,则声明包含类名但不包含派生列表。
    可将基类类型的引用绑定到派生类对象的基类部分,指针也是如此。        这里注意是基类的引用或指针可以绑定到派生类上,反之则不行。
    任何可以在基类对象上执行的操作也可以通过派生类对象使用。
    引用和指针的静态类型与动态类型可以不同,这是C++用以支持多态性的基石。        静态类型:static type, 在编译时可知的引用类型或指针类型。        动态类型:dynamic type, 指针或引用绑定的对象的类型,这是仅在运行时可知的)。
        如果调用非虚函数,则无论实际对象是什么类型,都执行基类类型所定义的函数。
        只有通过引用或指针调用,虚函数才在运行时确定。
    非虚函数总是在编译时根据调用该函数的对象、引用或指针的类型而确定。
    只有成员函数中的代码才应该使用作用域操作符覆盖虚函数机制。
        最常见的理由是为了派生类虚函数调用基类的版本。
    通过基类的引用或指针调用虚函数时,默认实参为在基类虚函数声明中指定的值,如果通过派生类的指针或引用调用虚函数,则默认实参是在派生类的版本中声明的值。
   
    派生类可以进一步限制但不能放松对所继承的成员的访问。
    无论派生列表中是什么访问标号,所有继承Base的类对Base中的成员具有相同的访问。
    在继承层次的设计中,有一个重要概念:是一种(Is A) 有一个(Has A)
    为了使size在Derived中成为public,可以在Derived的public部分增加一个using声明:using Base::size; , 从而恢复继承成员的访问级别。
    私有继承相当罕见,所以如果确实需要,显式指定可避免误解。    struct派生则默认为public, class派生则默认为private。
   
    友元可以访问类的private和protected数据。
        友元关系不能继承。如果基类被授予友元关系,则只有基类具有特殊访问权限。
   
    每个static成员只有一个实例。
    确定派生类到基类转换的可访问性取决于在派生类的派生列表中指定的访问标号。
    从基类到派生类的自动转换是不存在的。        如果确定可以,需要使用static_case强制转换。
   
    构造函数和复制控制成员不能继承。
    派生类构造函数的初始化列表只能初始化派生类的成员,不能直接初始化继承成员。
        可以将基类包含在构造函数初始化列表中间接初始化。
        class Bulk_item : public Item_base {
        public:
            Bulk_item(const std::string& book, double sales_price,
                                std::size_t qty = , double disc_rate = 0.0) :
                                Item_base(book, sales_price),
                                min_qty(qty), discount(disc_rate) { }
        };
    一个类只能初始化自己的直接基类。        直接基类就是在派生列表中指定的类。
    派生类应通过使用基类构造函数尊重基类的初始化意图。
   
    具有指针成员的类一般需要定义自己的复制控制来管理成员。
    派生类析构函数不负责撤销基类对象的成员。
    基类中的析构函数必须是虚函数。
        即使析构函数没有工作要做,继承层次的根类也应该定义一个虚析构函数。
        强调之前,虚函数必须在基类和派生类中具有相同的形参。
   
    在继承情况下,派生类的作用域嵌套在基类作用域中。
    与基类成员同名的派生类成员将屏蔽对基类成员的直接访问。
        可以使用作用域操作符来访问被屏蔽的基类成员。
        设计派生类时,只要可能,最好避免与基类成员的名字冲突。
    在派生类作用域中派生类成员屏蔽基类成员,即使函数原型不同,基类成员也会被屏蔽。
   
    成员函数(无论虚还是非虚)可以重载。
        如果派生类重定义了重载函数,则通过派生类型只能访问派生类中重定义的那些成员。
        派生类不用重定义所继承的每一个基类版本,可以为重载成员提供using声明,一个using声明只能指定一个名字。
    通过基类类型的引用或指针调用函数时,编译器将在基类中查找该函数而忽略派生类。
   
    在函数形参表后面写上=0以指定纯虚函数。
        含有(或继承)一个或多个纯虚函数的类是抽象基类(Abstract base class)。
        不能创建抽象类型的对象。
        
    后面关于指针或引用管理的内容跳过。
   
第16章    模板与泛型编程
    这一章之所以阅读的主要原因在于看一位大牛的网站,他写的数据结构学习笔记,一直使用template的东西,简单阅读这一章,做到可以看懂代码即可,所以仅做了解。
   
    泛型编程就是以独立于任何特定类型的方式编写代码。
    模板是泛型编程的基础。
    函数模板是一个独立于类型的函数。
        template <typename T>
        int compare(const T &v1, const T &v2)
        {
            if (v1 < v2) return -1;
            if (v2 < v1) return 1;
            return 0;
        }
    inline函数模板,需要注意inline的位置:
        template <typename T> inline T min(const T&, const T&);
   
    书上16.1.2中,又一次提醒了关于重载条件的复习,详见P378.
   
    模板形参遵循常规名字屏蔽规则。与全局作用域中声明的对象、函数或类型同名的模板形参会屏蔽全局名字。
    每个非类型形参前面必须带上类型名字,省略关键字或类型说明符是错误的:
        template <typename T, U> T calc (const T&, const U&);
        改正为:template <typename T, typename U> T calc (const T&, const U&);
    typename显式指明一个名字是一个类型。所以在类型之前指定typename没有害处。
    在调用函数时非类型形参将用值代替,值的类型在模板形参表中指定。
        这里书上例子,很能说明问题:
            template <class T, size_t N> void array_init(T (&parm)[N])
            {
                for (size_t i = 0; i != N; ++i) {
                    parm[i] = 0;
                }
            }
            
            编译器从数组实参计算非类型形参的值:
            int x[42];
            double y[10];
            array_init(x);    // 实例化为array_init(int(&)[42])                            因为后面不再继续写了,所以这里提一下实例化的概念:产生模板的特定类型实例的过程称为实例化。
            array_init(y);    // 实例化为array_init(double(&)[10])
            对模板的非类型形参而言,求值结果相同的表达式将认为是等价的。调用的是相同的实例——array_init<int, 42>:
            int x[42];
            const int sz = 40;
            int y[sz + 2];
            array_init(x);
            array_init(y);
   
    编写泛型代码的两个重要原则:
        模板的形参是const引用。
        函数体中的测试只用<比较。        在本章笔记开头的代码就体现了这种优势。
            详细的原因,参见P534-P535
#33
naruto012011-07-03 17:34
本次对C++的正式初探算是告一段落。离真正掌握还差的很远,希望自己可以坚持不懈!
另外谢谢您的关注。
祝我们好运~
#34
specilize2011-07-03 18:55
看楼主发的帖子,亦深有体会,觉得学这东西,还是慢慢来,学这东西真是急不得,急了效果反而不太好,好像在这曾看过一个帖子,说看一本书不在于你看完了没有,看完了没什么,关键是你学会了多少,祝楼主好运啊,大家一起坚持啊
#35
玩出来的代码2011-07-03 20:26
LZ有时间还是看下数据结构吧,这东西很重要。若在公司用纯C++,那你不学界面也行,但在windows下搞编程的话还是了解下他的东西好。汇编那玩意估计会很少用到,自己调试程序还行。看书的话你可以自己总结一些东西,精简书上的内容或自己的看法,记录下来。以后必定会有用。需要学的东西还很多。
#36
specilize2011-07-03 21:11
回复 35楼 玩出来的代码
之前看过一点数据结构,看到树和图那里一大堆递归的东西,感觉递归那东西看着容易,动手起来有难度,所以有点吃不消,个人感觉递归在数据结构中蛮重要的,尽管知道是栈的机制,还是不会,不知道玩出来的代码有没有比较好的学习这东西的方法,或给个例子说明下,请教
#37
玩出来的代码2011-07-03 22:09
恩,递归是挺重要的,树和图用递归的也多,不过这可不是栈的机制,栈就是个先进先出,你可以找算法一类的书看看,算法书上都有讲解递归的。要理解递归就将他的每一步都搞清楚,单步看他每一次递归后的数据变化,这只是了解递归的过程,实际用递归时只需考虑临界条件,也就是递归出口,细节就不用考虑了。应用递归的如八皇后、排列组合、斐波那契额数列、整数分解、树图等。或许不是好的算法,这些例子都可以很好的理解递归。
说起递归也想起以前看过的一个比较牛的递归程序。
程序代码:

#include<stdio.h>
int p(int i,int N)
{
    return printf("%d\n",i) && (N>i) && ( p(i+1,N) || p(i,i) );
}
int main()
{
    p(1,9);
    return 0;
}

这个程序如能不执行就知道它的结果,那也很牛了。
#38
naruto012011-07-03 23:27
to 玩出来的代码:您说的对我很有帮助。   我也曾经拿起过一些数据结构的书本进行学习,但是在一些内容上存在障碍,所以也不是特别积极。 正准备打算拜读Weiss的大作,不过又想想,还是踏实点,先系统学习下离散数学吧。 我这人比较注重理解,不喜欢记忆,也不是很赞同有人说理解了自然就记住了。这个问题取决了一些个人其他的因素,所以我还是比较尊重自己,learning by doing。如果我能够找到一些我想要真正实现的东西,我相信自然这些枯燥的东西会立刻容光散发的。   
    同样我想多说点的,编程在我的理解还是以解决自己需求为主要目的才能够真正给我带来乐趣。迄今为止,可以说编程让我感到唯一的愉快体验就是打麻将没有色子,简单地用个伪随机数来仿真,正像MIT的公开课程《计算机科学及编程导论》开篇引导学生的那样。我想学习编程,首先需要想清楚到底想用编程干什么?这也是我现在仍然在思考的问题,它可能会浪费一些时间,或者让自己有点不知所措,但是这个问题会伴随自己的编程生命,也是编程的主要理由和动力。
    谢谢您的指教,同样很巧,我们是老乡。
to specilize:呵呵~一起加油
#39
摇滚乐园2011-07-05 03:04
谢谢你
#40
噼里啪啦2011-07-08 23:50
加油!~~!!
#41
naruto012011-10-13 00:24
以下是引用naruto01在2011-5-28 01:26:17的发言:

现在已经看到第13章 复制控制。开始感觉比较吃力,希望自己可以坚持住。  在407页13.1.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
但是下面中文翻译出:如果构造函数是显式的,则初始化失败;如果构造函数不是显式的,则初始化成功。
  
希望有哪位朋友可以指点一下。
欢迎各位朋友对我学习中的一些错误提出批评和指导。非常感谢你们的关注!   
另外补充关于这个笔记的说明:我已经比较熟悉C,也之前看过一些国内的C++资料,大概也知道继承、重载等的概念,并且算是略读这本书,希望对C++有个权威、系统的了解。如果你感觉有点过于简略,那么也开始你的学习笔记之旅吧!
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时,空白符将会忽略(这里补充下空白符的概念:空格、制表符、垂直制表符、回车符、换行符和进纸符)
        

回头看这个问题,当时应该是被绕晕了。  没有问题~  在此回复一下
1