《C++ Primer》读书笔记

来源:互联网 发布:数据挖掘异常检测算法 编辑:程序博客网 时间:2024/05/24 22:45

一,基本语言

1,编译C++源码时,在Windows下面用命令cl -GX prog1.cpp,生成和源文件名相同的.exe文件。

2,float型只能保证6位有效数字,而double型至少可以保证10位有效数字。
3,在一行的末尾加一反斜线/符号可将此行和下一行当作同一行处理。
4,在C++中,存在两种初始化,一是复制初始化:int varl = 1024;二是直接初始化:int ival(1024);且在C++中,初始化和复制是两种不同的操作。
5,在C++中,变量的定义和声明是不同的。可以通过使用extend关键字声明变量名而不定义它。
                     extend   int   i;// declares but does not define i  ,不分配存储空间
                     int   i;             //declares and defines  i        ,分配存储空间
      只有当extend声明位于函数外部时,才可以含有初始化式。
6,在C++中,局部变量s1可以屏蔽(hide)全局变量s1。
7,非const变量默认为extern,也就是可以在其他文件通过extend声明访问。而const变量必须在定义的时候显示的指定为extern,才能在其他文件中访问。
8,引用必须用该引用同类型的对象初始化。更具体一点就是,非const引用只能绑定到与该引用同类型的对象,const引用可以绑定到不同但相关的类型的对象或绑定到右值。
9,在C++枚举类型中,枚举成员可以是不唯一的。枚举类型的对象的初始化或赋值,只能通过其枚举成员或同一枚举类型的其他对象来进行。
10,在结构(struct)和类(class)定义完成后,要有一个分号(;)。两者的唯一差别在于:默认情况下,struct的成员为public,而class的成员为private。
11,头文件用于声明而不是定义。有三个例外,头文件可以定义类,在编译时可以知道其值的const对象和inline函数。
12,为了避免名字冲突,预处理器变量经常用全大写字母表示。
13,用getline读取整行文本while(getline(cin,line))。getline会丢弃换行符。
14,string中的size操作返回的值的类型是string::size_type类型,最好不要把size的返回值赋给一个int变量。string的下标需要是一个size_type的类型。在C++中,两个string类型的对象也可以通过+号相连,但前提是两个中必须至少有一个是string类型的(非字面值)。
15,在vector中,虽然可以对给定元素个数对象预分配内存,但更有效的方法是先初始化一个空vector对象,然后再动态的增加元素。如果没有指定元素的初始化式,那边标准库将自行提供一个元素初始值进行值初始化:使用默对象类型的默认构造函数初始化,若有自定义构造函数但没有默认的构造函数,则要提供元素初始值。另外若没有定义任何构造函数,则会按照值初始化对该对象中每个成员初始化。
16,vector中的成员函数size返回相应vector类定义的size_type的值。如vector<int>::size_type。特殊的是,在vector中,必须是已经存在的元素才能用下标操作符来进行索引。通过下标操作进行赋值时,不会添加任何元素。
17,任何改变vector长度的操作都会使以存在的迭代器失效。
18,bitset类型的count函数返回的是size_t,和数组的下标一样。
19,数组的初始化方式:(1)在函数体外定义的内置数组,其元素均初始化为0;(2)在函数体内定义的内置数组,其元素无初始化;(3)不管数组是在哪里定义,如果其元素为类类型,则自动调用该类的默认构造函数进行初始化;如果该类没有默认构造函数,则要显示初始化。
20,建议:若必须分开定义指针和其所指向的对象,则将指针初始化为0。一旦删除了指针所指向的对象,立即将指针置为0,这样就非常清楚地表明指针不再指向任何对象。
21,C++允许计算数组或对象超出末端的地址,但不允许对此地址进行解引用操作。一般是用作哨兵元素。
22,可以将指针当作数组的迭代器使用。
22,由于动态数组的定义只会出现在函数内部。所以动态数组创建时,如果是类类型,则用默认构造函数初始化,若为内置类型,则无初始化。但我们经常需要内置类型的初始化,我们可以在数组长度后面跟一对圆括号,对数组元素做值初始化。如int *pia = new int[10]();
23,逗号表达式是一组由逗号分隔的表达式,这些表达式从左向右计算。逗号表达式的结果是其最右边表达式的值。
24,表达式中的signed型数值会被转换为unsigned型。
25,在switch结构中,一个标号不能独立存在,他必须位于语句之前。如果switch结构以default标号结束,而且default分支不需要完成任何任务,那么该标号后面必须有一个空语句。case标号必须是整形常量表达式,如果两个case标号具有相同的值,同样也会导致编译错误。对于switch结构,只能在它的最后一个case标号或default标号后面定义变量。
26,使用预处理进行调试。预处理器定义了四种在调试时非常有用的常量:__FILE__,__LINE__,__TIME__,__DATE__。另外还可以使用assert预处理宏。在assert(expr)中,若expr为false,程序会退出。
27,函数不能返回另一个函数或者内置数组类型,但可以返回指向函数的指针,或指向数组元素的指针的指针。
28,在函数中,应该将不需要修改的引用形参定义为const引用。普通的非const引用形参在使用时不太灵活。这样的形参既不能用const对象初始化,也不能用字面值或者产生右值的表达式形参初始化。
29,在函数中,通过传递vector的迭代器,而不是vector对象来提高性能。
30,通过引用传递数组时,数组大小成为形参和实参类型的一部分。如void printValue(int (&arr)[10]){ }。传递给这个函数的数组必须有十个元素,否则会报错。
31,函数中,千万不能返回局部变量的引用,千万不能返回指向局部对象的指针。
32,如果有一个形参具有默认实参,那么,它后面所有的形参都必须有默认实参。也就是说,默认实参的匹配时从后面开始的。
33,在头文件中加入或修改内联函数时,使用了该头文件的所有源文件都必须重新编译。
34,在函数重载中,有const引用形参的函数与非const引用形参的函数是不同的。类似的。如果函数带有指向const类型的指针形参,则与带有指向相同类型的非const对象的指针形参的函数不相同。
35,输出缓冲区的刷新:(1),cout<<"hi!"<<flush;刷新缓冲区,不增加任何数据。(2),cout<<"hi!"<<ends;增加一个null,然后刷新缓冲区。(3),cout<<"hi!"<<endl;增加一个换行,然后刷新。
36,使用unitbuf刷新缓冲区。cout<<unitbuf<<" first "<<" second "<< nounitbuf;
37,如果程序员需要重用文件流读写多个文件,必须在读另一个文件之前调用clear清除该流的状态。
38,在文件读写中,文件模式如下:in  打开文件做读操作; out  打开文件做写操作;  app  在每次写之前找到文件尾;  ate   打开文件后立即将文件定位于文件尾;  trunc  打开文件时清空以存在的文件流;binary  以二进制模式进行IO操作。 
二,容器和算法
1,在使用容器的容器时,必须用空格隔开两个相邻的>符号,以示这是两个分开的符号,否则,系统会认为>>是单个右移符号。如vector< vector<string> > lines;
2,只有vector和deque支持关系操作符(>,>=,<,<=)和+n,-n操作,因为只有这两种容器为其元素提供快速,随机的访问。c[n]和c.at(n)只适用于vector和deque。
3,push_front(t)只适用于list和deque容器类型。pop_front()只适用于deque。
4,在容器中,任何push和insert操作都可能是迭代器失效,当编写循环将元素插入到vector或deque容器中时,程序必须确保迭代器在每次循环后都得到更新。且不要存储end操作返回的迭代器。
5,所有的容器都支持用关系操作符来实现两个容器的比较。就好比与string一样。
6,通常来说,除非找到选择使用其他容器的更好理由,否则vector容器都是最佳选择。
7,所有适配器都支持全部关系操作符。
8,默认的stack和queue都基于deque容器实现的。而priority_queue是在vector容器上实现的。在创建适配器时,通过将一个顺序容器指定为适配器的第二个类型实参,可覆盖其关联的基础容器类型:stack< string, vector<string> > str。
stack适配器可以关联任何顺序容器,queue容器需要支持push_front操作,所以只能是list和deque。而priority_queue需要随机访问功能,所以只能建立在vector或者deque上。
9,在map容器中,对于键类型,唯一的约束就是必须支持 < 操作符,比较函数必须满足:当一个键和自身比较时,结果必定是false;当两个键之间都不存在“小于”关系时,则容器将之视为相同的键。也就是说,map内的元素按键值升序排列。至于是否支持其他的关系或相等运算,则不作要求。map类定义的类型有:map<K,V>::key_type , map<K,V>::mapped_type , map<K,V>::value_type。value_type是pair类型。
10,在multiple_map中,lower_bound返回的迭代器不一定指向拥有特定键的元素。如果该键不在容器中,则lower_bound返回在保持容器元素顺序的前提下该键应被插入的第一个位置。
11,算法不直接修改容器大小,如果需要添加或者删除元素,则必须使用容器操作。
12,标准库提供了在iostream对象上使用的迭代器,istream_iterator用于读取输入流,而ostream_iterator则用于写输出流。特别的是流迭代器不能创建反向迭代器,因为不能反向遍历流。
           istream_iterator<int> cin_it(cin);            //reads ints from cin 
           istream_iterator<int> end_of_stream;    //end iterator value
           ostream_iterator<Sales_item>  output(cout,delim);  //使用delim(C风格字符串)作为分隔符
13,反向迭代器的base函数使其变成正向迭代器,但是所指向的元素成了下一个元素。 
14,算法要求用于指定范围的两个迭代器必须具有完全一样的类型。roster.end()返回的迭代器依赖于roster的类型。如果该容器是const对象,则返回的迭代器是const_iterator类型,否则,返回的只是一个普通的迭代器。如果我们将it定义为cosnt_iterator,如果roster不是const对象,则无法通过编译。
15,对于list对象,应该优先使用list容器特有的成员版本,而不是泛型算法。list的成员版本和泛型算法的不同在于:成员版本的操作能删除和添加元素。list容器提供的merge和splice运算会破环他们的实参:当实参对象的元素合并到调用merge函数的list对象时,实参对象的元素被移出并删除。  
三,类和数据抽象
1,若数据成员被声明为mutable,则在const函数中,可以修改该数据成员。
2,注意:形参表和函数体处于类作用域中,而函数返回类型不一定在类作用域中:如果函数在类定义之外定义,则用于返回类型的名字在类作用域之外,如果返回类型使用由类定义的类型,则必须使用完全限定名。
3,在构造函数中,必须对任何const或引用类型成员以及没有默认构造函数的类类型的任何成员使用初始化式。在初始化列表中,成员初始化的次序是定义成员的次序。
4,explicit关键字可以抑制由构造函数定义的隐式转换,但只能用于类内部的构造函数声明上,在类的定义体外部所做的定义不在重复它。
5,当我们将成员函数声明为有元时,函数名必须用该函数所属的类名字加以限定。
6,static成员函数不能声明为const和虚函数。static数据成员必须在类定义体的外部定义(正好一次)。不像普通数据成员,static成员不是通过类构造函数进行初始化,而是应该在定义时进行初始化。
7,const static数据成员在类的定义体中初始化时,该数据成员仍必须在类的定义体之外进行定义。
8,只有单个形参,而且该形参是对本类类型对象的引用(常用const修饰),这样的构造函数称为复制构造函数。当形参或返回值为类类型时,由复制构造函数进行复制。如Foo(const Foo&)
9,有些类必须对复制对象时发生的事情加以控制,这样的类经常有一个数据成员是指针,或者有成员表示在构造函数中分配的其他资源。而另一些类在创建新对象时必须做一些特定工作,这两种情况下,都必须定义复制构造函数。
10,如果想禁止用户复制该类型的对象,可以将复制构造函数声明为private,如果还想禁止友元和成员中的复制,可以只声明,而不定义。
11,不允许复制的类对象只能作为引用传递给函数或从函数返回,他们也不能用作容器的元素。
12,如果类需要析构函数,则他也需要赋值操作符和构造函数,这是一个有用的经验法则。析构函数与复制构造函数或赋值操作符之间的一个重要区别是,即使我们编写了自己的析构函数,合成析构函数仍然会运行。先执行自己编写的析构函数,然后再运行合成析构函数。
13,包含指针的类需要特别注意复制控制,原因是复制指针时只复制指针中的地址,而不会复制指针指向的对象。
14,重载操作符必须具有至少一个类类型或枚举类型的操作数,这条规则强制重载操作符不能重新定义用于内置类型对象的操作符的含义。操作符重载后不再具有短路求职的特性。