未完待续
C++ Primer总结
第一章
1.endl是一个特殊的值,称为操作符,将它写入输出流是,不仅有输出换行的效果,还会刷新与设备相关联的缓冲区,这样用户可立即看到写入到流中的输出。
2.printf(“”)中,注释/* */是不起作用的,在cout<< < 3.C++可以实现未知数目的输入 while(std::cin>>buf) { } std::cin>>的返回值仍是cin,当遇到文件结束符(windows下是contrl+z)或者无效流(buf是int型,但从键盘输入了一个char)时,返回的流对象无效,则退出循环。 第二章 1.string类至少提供了两个构造函数,其中一个允许通过字符串字面值初始化string对象,另一个允许我们通过字符和计数器初始化,string(n,‘a’)。 2.名字的作用域:全局作用域,局部作用域和语句作用域(for()中定义的变量,只能在for语句中使用),同名的全局和局部变量,局部变量会屏蔽全局变量。 3.非const对象默认为extern,即可以被其他文件访问的,只需要在其他文件使用的地方写:extern type name; const对象默认为文件的局部变量,即不可以被其他文件访问,如果其他文件需要访问,必须在定义const对象前加extern,在其他文件使用的地方加 extern const type name; 4.非const引用只能绑定到同类型的对象,而不能是数字或字符。 const引用则可以绑定到不同类型对象或数字(因为会产生中间变量),const引用是只读的。 所有引用,只能绑定一次。 eg1:double a=3.14; const int& b=a; 修改b值不能改变a的值,因为其中产生了一个中间变量。 eg2: int a=3; int &b=a; const int& c=10; b=c; 可以,c是只读的 a=c; 可以,c是只读的 c=a; 不可以,c是const 5.枚举成员的初始化或赋值,只能通过枚举成员或同一个枚举类型的对象来进行。 6.定义类成员时,只能指定该数据成员的名字和类型,不能在定义中初始化数据成员,而是通过构造函数来控制初始化。 7.用struct和class定义类的唯一差别在于默认访问级别,默认情况下,struct成员为public,class成员为private。 8.头文件中不能放定义。 eg:extern int a=10;虽然有extern关键字,但它有初始化,因此是一个定义语句 int a;虽然没有初始化,但没有extern,会为a分配空间,也是定义语句。 同一个程序中有两个以上文件含有上述任一个定义都会导致多重定义链接错误。 9.用常量表达式初始化的const变量,由于该变量也可以作为常量表达式,编译器必须知道该变量的初始化值,因此一般放在头文件中. eg:const int a=3+2; 由于const变量默认是定义它的文件的局部变量,因此头文件被多次包含后也是合法的。 10.头文件中不应含有定义,有三个例外:类,常量表达式初始化的const,inline函数。 第三章 1.从标准输入读取string 读取并忽略开头的所有空白字符 读取字符知道再次遇到空白字符,读取结束 如果输入到屏幕上的是“ hello world!”输出时只会显示“hello”。 2.用getline读取整行文本 并不忽略开头的换行符,只要遇到换行符,getline会停止读入并返回。 3.string.size()返回字符串中字符的个数,返回值是string::size_type类型,是string的配套类型。 4.用push_back()向vector添加元素。 5.迭代器.迭代器就是每个容器为了方便用户遍历数据而提供的数据类型,每个容器迭代器的内部具体实现都是不同的,但是用处都一样就是遍历数据。 第四章 1.数组的初始化只有三种:整形字面值常量,枚举常量,用常量表达式初始化的const对象。 eg:const int a=10; int b=20; int c[a];//正确,a是常量表达式时初始化的const对象 int d[b];//错误,非const对象只有在运行时才能获取它的值 2.指针的初始化或赋值可以使用0值常量表达式:字面值常量0和0值的const对象 eg:int a=0; const int b=0; int *p; p=a;//error:将整型变量值赋给指针 应该是p=0; p=b;//ok;b是0值的常量表达式 总结:由于用const修饰的对象是不可以在之后修改的,对于编译器,它和字面值数字有同等地位。 3.除了0,也可以用NULL,NULL=(void*)0,编译时会自动被数组0替换。 4.指针只能初始化或赋值为同类型的变量地址或另一指针 eg: double a; double *b=&a; int *c=b;//error c=&a;//error 5.只要两个指针指向同一数组或有一个指向该数组末端的下一单元,C++支持这两个指针做减法操作 int a[]={1,2,3,4}; int *b=a+2;// 指针上加(或减)一个整数n等效于获得一个新指针,该指针指向指针原来指向的元素之后(或之前)的第n个元素 int c=b-a;//c=2 6.const修饰指针还是修饰对象,看const放在谁的前面 7.const和typedef结合的特殊情况 typedef int *INT; const INT a; a是什么类型? 一般理解为cons int*类型,a是指向const对象的指针,不能通过a修改对象的值 错误!!不能把typedef当做简单的字符替换,按上面的理解应该是这么写: typedef const int *INT; INT b; 但对于a,const是修饰类型的,或者理解的时候想为 INT const a;a是类型其实是int* const,即a是一个const指针。 8. 用new[]动态分配数组时,可以用new[]()加上一对空的圆括号来对所有值进行初始化,但不能像一般数组那样用{}对每一个值进行初始化。 用new分配一个数据时,可以new(value)为该值进行初始化 第五章 1.类型转换 int a=3.9+5; a=8,int+double 为保证精度会将int转换为double,double赋给int,进行截尾,只留了8,这里不存在四舍五入这种东西。 第七章 1.每次调用函数时,都会重新创建该函数的形参,并用实参初始化。 2.函数调用时,如果函数使用非引用非const形参,可以传递const的实参,因为初始化只复制初始化的值,这是const的一个例外。 形参是引用的话不可以,非const引用形参只能与完全同类型的非const对象绑定。只有下面是可以的,const引用形参可以与非const对象绑定。 const int a=10,b=20; int func(int a,int b){ return a+b; } *3.为了对C语言的兼容,两个相同函数具有非const形参和const形参是同一个,不够成函数重载,因为只是形参只是对实参的简单拷贝而已,但是非const引用形参和const引用形参是两个不同的函数,代表的实际对象是不同的性质。 4.引用并不会进行复制,只是起别名。 5.非const引用作为形参时,不接受右值和需要类型转换的,必须严格与完全同类型的对象绑定,不灵活,因此如果在函数内不会对该引用的对象进行修改,都定义为const引用。 6.返回引用的函数返回一个左值。 7.定义函数的源文件应包含声明该函数的头文件,这样编译器可以检查该函数的定义和声明是否一致。 8.由于默认实参只能用来替换函数调用缺少的尾部实参,因此位置是很重要的,如果一个形参有默认参数,齐后面所有的形参都必须有默认参数!设计带有默认实参的函数,其中的部分工作就是排列形参,使最少使用默认实参的形参排在前面,最可能使用默认实参的形参排在最后。 9.编译器隐式的将类内定义的成员函数默认为内联函数,即成员函数前默认都加上inline关键字,但编译器根据函数内容复杂度有权利不进行内联。 10.类的成员函数后面加const,具体实现是将this这个const指针改变为了指向const对象的const指针,这样就不能通过this改变该对象内的数据成员。 11.不手动提供默认构造函数,编译器会自动创建默认构造函数成为合成构造函数,它对类类型的数据成员调用该类的默认构造函数,对基本类型的数据成员根据对象所在位置来进行初始化,如果对象在全局作用域或定义为静态局部变量,则全部初始化为0,若对象定义为局部变量,则不进行初始化。 12.屏蔽。在一个作用域内声明的名字将屏蔽外层作用域内同名的名字,屏蔽只要名字一样就屏蔽,无论什么类型。 int init(int a){ return a; } main(){ int init=0; cout< } 这里的init是int型变量,init()函数被屏蔽了 在函数重载中,局部作用域中的函数会屏蔽所有作用域外的同名函数,而不构成重载。 13.函数匹配,多个函数构成重载,编译器会根据调用的实参类型选择不同的函数,这个过程叫函数匹配,如果多个函数满足条件,则选择最佳匹配。 最佳匹配优先级:精确匹配 > 类型提升匹配(short char—int float—double)> 类型转换的匹配。 eg: void func(){} void func(int){} void func(double ,double b=3.14){} func(2.3); 此时调用的是void func(double ,double b=3.14){},因为这是精确匹配,而调用void func(int{}需要类型转换。 多个参数的重载函数在选择最佳匹配函数时的原则为: 1.每个参数的匹配都不劣于其他可行函数 2.至少有一个参数的匹配优于其他可行函数 否则就构成二义性 eg:voidfunc(int,int); voidfunc(double,double); func(2,3.14); 此时构成二义性,找不到最匹配的重载函数。 14.函数重载中,仅仅当形参为引用或指针时,形参是否为const才有影响。 15.返回指向函数的指针 int (*func(int))(int*,int) 或者利用ytpedef typedef int(*P)(int*,int) P func(int); 第八章 第九章 顺序容器 1.容器是容纳特定类型元素的集合。Vector类型就是一种容器,并且是顺序容器,即根据容器的元素添加次序来决定排列次序,而与元素值无关,同时标准库还提供了三种容器适配器,stack(后进先出),queue(先进先出)和priority_queue(优先级队列),个人认为适配器是可以与容器随意搭配的,体现了数据存储和读取的一个顺序。 2.容器元素的初始化可以: C c(b,e);C是容器类型名,c是新创建的容器名称,其元素是迭代器b和e标示的范围内元素的副本。这里不要求迭代器所在的容器类型与C相同,也不要求容器内的元素类型完全相同,只要它们能相互兼容。e也可以像迭代器的end一样指向超出末端的下一位置。 3.将一个容器复制给另一个容器时,类型必须是完全匹配的。 4.容器定义的类型别名: size_type 足以存储此容器的最大可能长度 iterator 此容器的迭代器类型 difference_type 足以存储两个迭代器差值的类型,可以为负数 value_type 元素类型,因为容器一般用于泛型程序,因此用这个类型无需知道实际存储的类型 reference 相当于&value_type 元素的引用 *5.容器操作的特殊要求 当容器存储类类型的元素,并指定元素个数时,该元素必须提供默认构造函数。当只存储一个元素时,不需要。 vector vector vector 注意:上述实参中的5,是对于容器来说,容器中构建5个元素,不要把5当做fun类中的构造器参数。 6.引用不支持一般的赋值运算,因此容器元素不能是引用类型。 这里的赋值运算是指该引用变量本身,由于引用变量是其他变量的一个别名,对引用赋值实际上是对它指向的变量赋值,对引用取地址实际上是对它指向的变量取地址,对于引用变量本身,是无法进行赋值和取地址的。 7.任何添加或删除容器内元素的操作,都可能导致整个容器的重新加载,这样的话,该容器涉及的所有迭代器都会失效,包括迭代器begin和end指向的元素。 8.容器之间可以进行比较,但必须是相同的容器类型,而且相同的元素类型,同时该元素类型可以进行比较(类类型要提供比较运算)。 比较原则为: 1.两个容器长度相同,元素相同,则两个容器相等。 2.两个容器长度不同,但其中较短容器内所有元素都等于另一容器中对应的元素,则短容器<长容器 3.两个容器长度不同,也都不是对方的初始子序列(如2),则比较结果取决于所比较的第一个不相等的元素。 9.容器内调用begin和end,返回的是迭代器,即是首元素和末尾元素的下一位置的地址,调用front和back是返回首元素和尾元素的引用。 10.swap交换两个容器内所有元素值,速度很快。注意一点:经过swap后元素在内存里并没有移动,因此原来迭代器的值不会失效。(个人看法:容器内的元素在内存中存储,swap只需交换两个容器首元素的位置即可,所有元素都无需进行移动) 11. vector容器在内存中为了快速的随机访问,其元素以连续的方式存储,全部都紧挨着,如果向该容器内添加元素,此时容器内没有多余的空间,则会重新开辟一块更大的空间,将原先里面的数据全部复制进来,然后添加元素,并将旧地址释放。这样每次添加都要重新分配空间效率太低,因此vector容器实际分配的容量会比定义时的大一些,capacity()表示该容器可以最大存放的元素个数,size()表示目前容器内的元素个数,用户用reserve()函数可以自定义容器容量大小。 list是非连续存放的容器,它的元素采用链表的形式存储,则没有vector的上述问题,但不支持随机访问,不能用a[2]下标的方式访问元素。 实际运用中vector通过额外分配空间的方式,表现要优于list。 12.string的字符也是连续存储的,并且提供容器操作。 13.erase操作会返回被删除元素的下一元素,因此用迭代器来遍历删除的话,经过该操作后无需更新。 14.适配器。留疑问 第十章 关联容器 1.关联容器和顺序容器的本质差别在于:关联容器通过键(key)存储和读取元素,顺序容器通过元素在容器中的位置顺序存储和访问元素。关联容器类型包括:map,set,multimap,multiset。 2.pair类型,是一种类型,里面包含两个数值,可以用first和second来访问到。 3.使用关联容器时,它的键类型必须有一个比较函数,可以用<来实现键的比较。 (个人认为这里应该和迭代器的++遍历有关,这样迭代器才有访问元素的顺序) 4.value_type表示容器内元素的类型,时刻谨记一点,map容器的value_type是pair 5.用map迭代器进行解引用将产生pair类型的对象。 6.给map添加元素的两种方法:利用下标和insert eg:map 利用下标:word[“hello”]=1;首先会在word中遍历寻找是否有“hello”这个键值,如果有,则将它对应数值改为1;如果找不到该键值,则创建一个新的pair类型,键为“hello”,将对应的数值初始化为0,插入到map容器中,之后读取该键对应的数值,将它设置为1。 利用insert。word.insert(map 为了书写简便,可以用word.insert(make_pair(“hello”,1))或者 typedef map word.insert(valtype(“hello”,1)); 同时insert函数返回值比较麻烦,会返回一个pair类型,包括一个迭代器和bool类型,迭代器指向插入的元素或者该元素键值已存在,指向尾元素的下一位置,bool表示插入元素是否存在。即pair 7.用迭代器指向map容器中的每对元素,将迭代器++来遍历每个元素,内部实现已封装。 8.通过键值来找容器内的元素时,也可以采用下标的方式,因为obj[key]它默认的行为是:在obj容器中找key为键值的元素,如果不存在,则新创建一个pair类型插入进去,如果存在则返回指向的值。 9.set类型,只存储键值,可以方便应用于只处理键值的情况,因为没有键值对应的数值,因此不可以使用下标。由于键值的唯一性,set中没有同名的键值。 10.去掉string类型最后一个字符的方法可以用resize(a.size()-1);则会进行自动截短。 11.cout<<这个操作符可以输出普通变量值,指针指向内容,如果要输出指针本身地址,则cout<<(void*)c; char *cp="123"; cout<<*cp相当于<<(char c),所以输出1 cout< 12.while (cin>>s)按ctrl+z退出,但这个退出符还在缓冲区里,你再cin时还是退出,因此结束要刷新一下缓存区cin.clear(); 13.multimap和multiset类型。不同于map和set,键必须是唯一的,multi的作用就在于这两种容器内一个键可以对应多个值,即存在多个pair元素,其键值相同,因此不能用下标的方式访问元素。 14.multimap和multiset类型,如果某个键对应多个实例,则这些实例在容器中相邻存放。