面向对象编程
面对对象编程基于三个基本概念:数据抽象,继承和动态绑定.在c++中用类进行数据抽象,用类派生从一个类继承另一个类:派生类继承基类的成员.动态绑定使编译器能够爱运行时绝对是使用基类中国定义的函数还是派生类中定义的函数。
在类成员函数的声明参数表后面加上关键字 const 表示给该函数默认传递的 this 指针为 const 指针,不能修改当前对象的状态并且onst 对象不能调用非const成员函数
学友(10001) <chenlovekiss@126.com> 18:11:21
而在前面定义是该成员函数内的 成员值不能修改
15.1 面对对象编程:概述
面对对象编程的关键思想是多态性.多态性派生与一个希腊单词,意思是"许多形态",之所以称通过继承而相关联的类型为多态类型,是因为在许多情况下可以互换地使用派生类型或基类型的"许多形态".
1.继承
通过继承我们能够顶同意这样的类,他们对类型之间的关系建模,共享公共的东西,仅仅特化本质上不同的东西.派生类能够继承基类定义的成员,派生类可以无须改变而是用那些与派生类型具体特性不相关的操作,派生类可以重定义那些与派生类型相关的成员函数,将函数特化,考虑派生类型的特性。最后,除了从基类继承的成员之外,派生类还可以定义更多的成员。我们经常称因继承而相关联的类为构成一个继承层次。
在c++中,积累必须指出希望派生类重定义哪些函数,定义为 virtual 的函数是基类期待派生类重新定义的,基类希望派生类重定义的函数不能为虚函数!
2.动态绑定
通过动态绑定,我们能够编写程序使用继承层次中任意类型的对象,无须关心对象具体类型,使用这些类的程序无须区分函数是在基类还是在派生类中定义的。引用既可以指向基类对象也可以指向
派生类对象。
15.2 定义基类和派生类
15.2.1 定义基类
1.为了指明函数为虚函数,在其返回类型前面加上保留字 virtual,除了构造函数之外,任意非static成员函数都可以是虚函数。保留字只在类内部的成员函数申明中出现,不能用在类定义体之外
2.protected 成员可以被派生类对象访问,但不能被该类型的普通用户访问。可以认为是private和public的混合
15.2.3 派生类
为了定义派生类,使用类派生列表制定基类,派生列表制定了一个或多个基类,具有如下形式:
class classname:access-label base-class
这里的access-lable是public ,protected 或 private, base-class是已定义的类的名字。派生类可以指定多个基类。
派生类继承基类的成员并且可以定义自己的附加成员。每个派生类对象包含两个部分:从基类继承的成员和自己定义的成员。一般而言,派生类只(重)定义那些与基类不同或扩展基类行为的方面。
2.派生类和虚函数
尽管不是必需这样做,派生类一般会重定义所继承的虚函数,如果派生类没有重定义某个虚函数,则使用基类中定义的版本。派生类中虚函数的声明必须与基类中定义方式完全匹配,但有一个例外,返回对积累性的引用或指针的虚函数,派生类中的虚函数可以返回基类函数所返回类型的派生类的引用
一旦函数在积累中申明为虚函数,它就一直为虚函数,派生类无法改变该函数为虚函数这一事实,派生类重定义虚函数时,可以使用virtual保留字,但不是必须这样做
5.用作基类的类必须是已定义的
6.用派生类做基类
15.2.4 virtual与其他成员函数
C++中的函数调用默认不适用动态绑定,要出发动态绑定,必须满足两个条件:第一,只有指定为虚函数的成员函数才能进行动态绑定,成员函数默认为非虚函数,非虚函数不进行动态绑定; 第二,必须通过基类类型的引用或指针进行函数调用。
1.从派生类到基类的转换
因为每个派生类对象都包含基类部分,所以可将基类类型的引用绑定到派生类对象的积累部分,也可以用指向基类的指针指向派生类对象;
2.可以在运行时确定virtual函数调用
将基类类型的引用或指针绑定到派生类对象对基类对象没有影响,对象本身不会改变,任然为派生类对象。对象的实际类型可能不同于该对象引用或指针的静态类型,这是c++中动态绑定的关键,
3.在编译时候确定非virtual调用,非虚函数总是在编译时候根据调用该函数的对象,引用或者指针而确定。
4.覆盖虚函数机制
再某些情况下,希望覆盖虚函数机制并强制函数调用使用虚函数的特定版本,这时可以使用作用域操作符:
Item_base *basep = &derived;
double d = basep->Item_base::net_prite(42);
15.2.5 公用,私有和受保护的继承
每个类控制它所定义的成员的访问,派生类可以进一步限制但不能放松对所继承的成员的访问。
基类本身指定对自身成员的最小访问控制,如果成员在基类中为private,则只有基类和基类的友元可以访问该成员。派生类不能访问积累的private成员,也不能使自己的用户能够访问那些成员。如果基类成员为public或protected,则派生列表中使用的访问标号绝对该成员在派生类中的访问级别:
*如果是公用继承,积累成员保持自己的访问级别:积累的public成员为派生类中的public成员,积累的protected成员为派生类的protected成员。
*如果是受保护继承,基类的public和protected成员在派生类中卫protected成员。
*如果是私有继承,基类的所有成员在派生类中卫private成员。
15.2.6 友元关系与继承
基类或派生类可以使其他类或函数成为友元。友元可以访问类的private和protected数据
友元关系不能继承,基类的友元对派生类的成员没有特殊访问权限。如果基类被授予友元关系,则只有基类具有特殊访问权限。
15.2.4 继承与静态成员
如果基类定义了static成员,则整个继承层次中只有一个这样的成员。无论从基类派生多少个派生类,每个static成员只有一个实例。
static成员遵循常规访问控制。
15.3.1 派生类到基类的转换
如果有一个派生类型的对象,则可以使用它的地址对基类类型的指针进行赋值或初始化。同样,可以使用派生类型的引用或对象转换为基类类型的对象。
1.引用转换不同于转换对象
我们已经看到,可以将派生类型的对象传给希望接受基类引用的函数。也许会因此认为对象进行转换,但是,事实并非如此。将对象传给希望接受引用的函数时,引用直接绑定到该对象,虽然看起来在传递对象,实际上实参是该对象的引用,对象本身未被复制,并且,转换不会再任何方面改变派生类型对象,该对象任然是派生类型对象。
将派生类对象传给希望接受基类类型的对象的函数时,情况完全不容,再这种情况下,形参的类型都是固定的,在编译时和运行时形参都是鸡肋类型对象。如果用派生类型对象调用这样的函数,则该派生类对象的基类部分被复制到形参。
一个事将派生类对象转换为基类类型的引用,一个是用派生类对象对基类对象进行赋值或初始化,理解他们之间的区别很重要。
2.用派生类对象对基类对象进行初始化或赋值
对基类对象进行初始化或赋值,实际上是在调用函数: 初始化时调用构造函数,赋值时调用赋值操作符。
用派生类对象对基类对象进行初始化或赋值时,有2中可能性。第一,基类可能显示定义了将派生类对象复制或赋值给基类对象的含义,这可以通过定义适当的构造函数或赋值操作符实现
15.3.2 基类到派生类的转换
从基类到派生类的自动转换时不存在的。需要派生类对象时不能使用基类对象:
15.4.2 派生类构造函数
派生类的构造函数受继承关系的影响,每个派生类构造函数除了初始化自己的数据成员之外,还有初始化基类。
1.合成的派生类默认构造函数
派生类的合成默认构造函数与非派生类的构造函数只有一点不同: 除了初始化派生类的数据成员之外,它还初始化派生类对象的基类部分,基类部分由基类的默认构造函数初始化。
2.定义默认构造函数
因为Bulk_item具有内置类型成员,所以应定义自己的默认构造函数,这个构造函数使用构造函数初始化列表初始化成员,该构造函数还隐式的调用基类的默认构造函数初始化对象的基类部分。
5.只能初始化直接基类
一个类只能初始自己的直接基类。直接基类就是在派生列表重中指定的类。
15.4.3 复制控制和继承
1.定义派生类复制构造函数
如果派生类显示定义自己的肤质构造函数或赋值操作符,则该定义将完全覆盖默认定义,被继承类的复制构造函数和赋值操作符负责对基类成分以及类自己的成员进行复制或赋值。
如果派生类定义了自己的复制构造函数,该复制构造函数一般应显示使用基类复制构造函数初始化对象的基类部分'
3.派生类析构函数不负责撤销基类对象的成员。
15.4.4 虚析构函数
自动调用基类部分的析构函数对基类的设计有重要影响。
删除指向动态分配对象的指针时,需要运行析构函数释放对象,处理继承层次中的对象时,指针的静态类型可能与被删除对象的动态类型不同,可能会删除实际指向派生类对象的基类类型指针。如果删除基类指针,则需要运行基类析构函数并清除基类的成员,如果对象实际是派生类型的,则没有定义该行为,要保证运行适当的析构函数,基类中的析构函数必须为虚函数。
像其他虚函数一样,析构函数的虚函数性质都将继承。因此,如果层次中根类的析构函数为虚函数,则派生类析构函数将是虚函数
构造函数和赋值到佐夫不是虚函数,构造函数是在对象完全构造之前运行的,在构造函数运行的时候,对象的动态类型还不完整。
15.4.5 构造函数和析构函数中的虚函数
构造派生类对象时首先运行基类构造函数初始化对象的基类部分,在执行基类构造函数时,对象的派生类部分是未初始化的。实际上,此时对象还不是一个派生类对象。
撤销派生类对象时,首先撤销他的派生类部分,然后按照构造顺序的逆序撤销它的基类部分。
在运行构造函数或析构函数的时候对象都是不完整的,为了适应不完整,编译器将对象的类型视为在构造或析构期间发生了变化。基类构造函数或析构函数中,将派生类对象当作基类类型对象看待。
如果在构造函数或析构函数中调用虚函数,则运行的是为构造函数或析构函数自身类型定义的版本。
15.5 继承状况下的类作用域
每个类都有自己的作用域,在该作用域中定义了成员的名字。在继承情况下,派生类的作用域嵌套在基类作用域中。
15.5.2 名字冲突与继承
与基类成员同名的派生类成员将屏蔽对基类成员的直接访问。class a {int men} class b[int men]
可以使用作用域操作符访问被屏蔽成员 a::men
15.5.3 作用域与成员函数
在积累和派生类中使用同一名字的成员函数,其行为与数据成员一样; 在派生类作用域中派生类成员将屏蔽基类成员。即使函数原型不同!
15.5.4 虚函数与作用域
关键概念: 名字查找与继承。 理解c++中继承层次的关键在于理解如何确定函数调用。遵循以下步骤
(1)首先确定进行函数调用的对象,引用或指针的静态类型。
(2)在该类中查找函数,如果找不到,就在直接基类中查找,如此循着类的继承链往上找,直到找到该函数或者查找完最后一个类,如果不能再类或其他基类中找到该名字,则调用时错误的。
(3)一旦找到了改名字,就进行常规类型检查,查看如果给定找到的含义,该函数调用是否合法。
(4)假定函数调用合法,编译器就生成代码。如果函数时虚函数且通过引用或指针调用,则编译器生成代码以确定根据对象的动态类型运行哪个函数版本,否则,编译器生成代码直接调用函数。
15.6 纯虚函数
定义: 再函数形参表后面写上 =0 指定为纯虚函数
模板与泛型编程
1.所谓泛型编程就是以独立于任何特定类型的方式编写代码。模板是泛型编程的基础。使用模板时,可以无需了解模板的定义。模板定义:模板就是实现代码重用机制的一种工具,它可以实现类型参数化,即把类型定义为参数, 从而实现了真正的代码可重用性。模版可以分为两类,一个是函数模版,另外一个是类模版。
16.1.1 定义函数模板
我嗯可以不已哦那个为每一个类型定义一个新函数,而只是定义一个函数模板,函数模板是一个独立于类型的函数,可以作为一种方式,产生函数的特定类型版本,
模板定义以关键字 template开始,后面接模板形参表,尖括号括住一个或多个。
如 template<typename T> int compare(const T &v1 , const T &v2)
1.模板形参表很想函数形参表,函数形参表定义了特定函数类型的局部变量但并不初始化那些变量,在运行时在提供实参来初始化。
同样,模板形参表示可以在类或函数的定义中使用的类型或值。例如,compare函数申明了一个名为T的类型形参,在compare内部,可以使用名字T引用一个类型,T表示哪个世纪类型由编译器自己决定
模板形参可以是表示类型的类型形参,也可以是表示常量表达式的非类型形参。非类型形参在类型说明符之后,类型形参跟在关键字class或typename之后定义,例如class T是名为T的类型形参,在这里class和typename没有区别
2.使用函数模板
使用函数模板时,编译器会推断哪个模板实参绑定到模板形参上。一旦编译器确定了实际的模板实参,就称它实例化了函数模板的一个实例。
实质上,编译器将确定用什么类型代替每个类型形参,以及用什么代替每个非类型形参,推到出诗集模板形参后,编译器使用实参代替相应的模板形参长生编译该版本的函数,
16.1.3 模板形参
想函数形参一样,程序员为模板形参选择的名字没有本质含义。
1.模板形参作用域
模板形参的名字可以在申明为模板形参之后知道模板申明或定义的末尾处使用。模板形参遵循常规名字屏蔽规则.与全局作用域中申明的对象,函数或类型同名的模板形参会屏蔽全局名字
2.使用模板形参名字的限制
用作模板形参的名字不能再模板内部重用:
3模板申明
像其他任意函数或类一样,对于模板可以只申明不定义。同一模板申明和定义中,模板形参名字不必相同
16.1.4 模板类形参
类型形参由关键字class或typename后接说明符构成.再模板形参表中,这两个关键字具有相同的含义,都指出后面所接的名字表示一个类型。
1.typename与class得区别
再函数模板形参表中,关键字typename和class具有相同含义,可以互换使用,2个关键字都可以在同一模板形参表使用。typename更清楚的指明后面是一个类型名
16.1.5 非类型模板形参
模板形参不必都是类型。在调用函数时,非类型形参将用值代替,值得类型在模板形参表中指定.
16.2 实例化
模板是一个蓝图,它本身不是类或函数。编译器用模板产生指定的类或函数的特定类型版本。产生模板的特定类型实例的过程就称 实例化 ,这个属于反映了创建模板类型或模板函数的新 实例 的概念。类模板在引用实例模板类型时实例化,函数模板在调用它或用它对函数指针进行初始化或赋值时实例化。
1.类的实例化
当编写Queue<int> qi ;时,扁你其自动创建名为 Queue<int>的类。实际上,扁你其通过重新编写Queue模板,用类型int替代模板形参的每次出现而创建Queue<int>类。类模板的每次实例化都会产生一个独立的类类型。为int类型实例化的Queue与仁义其他Queue类型没有关系。
2.类模板形参时必须的
想要使用类模板,就必须显示指定模板实参 Queue qs; 错误, Queue <int> qs; ok
3.函数模板实例化
使用函数模板时,编译器通常会为我们推断模板实参
16.2.1 模板实参推断
从函数实参确定模板实参的类型和值的过程叫做模板实参推断。
1.多个类型形参的实参必须完全匹配
模板类型形参可以用作一个以上的函数形参的类型。在这种情况下,模板类型推断必须为每个对应的函数实参产生相同的模板实参类型,如果推断的类型不匹配,则调用将出错。
3应用于非模板实参的常规转换
用普通类型定义的形参可以使用常规转换。 template<class Type。 Type sum(Type &s1, int s2)
4.模板实参推断与函数指针
可以使用函数模板对函数指针进行初始化或赋值,这样做的时候编译器使用指针的类型实例化具有适当模板实参的模板版本。
16.2.2 函数模板的显示实参
在某些情况下,不可能推断模板的实参类型,当函数的返回类型必须与形参表中所用的搜游类型都不同时,最常出现这一问题,在这种情况下,有必要覆盖模板实参推断机制,并显示指定为模板形参所用的类型或值。
16.3 当编译器看到模板定义的时候,不立即产生代码,只有在看到用模板时,如调用了函数模板或类模板的对象时候,编译器才产生特定类型的模板实例。
调用函数的时候,编译器只需要看到函数的申明。类似的,定义类类型对象,类定义必须可用,但成员函数定义不是必需存在的。
模板则不同:要进行实例化,编译器必须能够访问定义模板的源代码。当调用函数模板或类模板的成员函数时候,编译器需要函数定义,需要那些通常放在源文件中的代码。
所有编译器都支持第一种模型,类定义和函数申明放在头文件,函数定义和成员定义放在源文件。包含模型和分别编译模型
1.包含编译模型
在包含编译模型中,编译器必须看到用到的所有模板的定义。一般而言,可以通过在申明函数模板或类模板的头文件中添加一条#include 指示使用定义可用,该#include引入了包含相关定义的源文件:
标准IO库
常识: IO类型在三个独立的头文件中定义:
.iostream 定义读写控制窗口的类型
.fstream 定义读写已命名文件的类型
.sstream 定义的类型则用于读写存储在内存中的string 对象
在fstream 和 sstream 里定义的美中类型都是从iostream头文件定义的相关类型派生而来.
1.国际字符支持
迄今为止,所描述的流类读写的是由char类型组成的流.此外,标准库还定义了一组类型,支持 wchar_t 类型. 每个类都加上 "w" 前缀,以此与 char 类型的版本区分开来. 于是 wostream ,wiostream类型从控制窗口读写 wchar_t 数据. 相应的文件输入输出类是 wifstream, wofstream 和 wfstream. 而 wchar_t 版本的string 输入/输出流则是 wistringstream , wostringstream 和 wstringstream. 标准库还定义了从标准输入输出读取宽字符的对象. 这些对象加上前缀 "w" ,以此跟 char 类型版本区分 ; wchar_t 类型的标准输入对象时 wcin; 标准输出是 wout;
8.2 条件状态
本节讲解IO标准库如何管理其缓冲区及其流状态的相关内容.请谨记,这些内容同样适用普通流,文件流,以及string流.
实现IO的继承是发生错误的根源,IO标准库提供一系列 条件状态 成员,用来标记给定的IO对象是否处于可用状态,或者碰到哪些错误.以下是IO标准库的条件状态
strm::iostate 机器相关的整形名,由各个iostream类定义,用于定义条件状态
strm::badbit strm::iostate类型值,用于孩子出被破坏的流
strm::failbit strm::iostate类型值,用于之处失败的IO操作
strm::eofbit strm::iostate类型的值已经到达结束符
s.eof() 如设置了流s的eofbit值,则该函数返回true
s.fail() 如设置了流s的faibit值,则该函数返回true
s.bad() 如设置了流s的badbit值,则该函数返回true
s.good() 如果流s处于有效状态,则函数返回true
s.clear() 将流s中的所有状态设置为有效状态
s.clear(flag) 将流s中的某个指定条件状态设置为有效,flag的类型是 strm::iostate
s.serstate(flag)给流s添加指定条件,flag的类型是strm::iostate
s.rdstate() 返回流s的当前条件,返回值类型为strm::iostate
cin.sync() 清除流缓冲
1.条件状态
所有流对象都包含一个条件状态成员,该成员由 setsate和clear操作管理.这个状态成员为iostate类型,则是由各个iostream类分别定义的机器行馆的整形.该状态成员以二进制位的形式使用.每个IO类还定义了三个iostate类型的常量值,跟别表示特定的位模式,这些常量值用于指出特定类型的IO条件,可以与位操作符一起使用,以便在一次操作中检查或这只多个特定标志,
badbit标志着系统级的故障,如无法恢复的读写错误.如果出现了这类错误,则该流通常就不能再继续使用了.如果出现的是可恢复的错误,如在希望获得数值型数据时输入了字符,此时则设置faiibir 标志,这种导致这是failbit的问题通常是可以修正的,eofbit是在遇到文件结束符时设置的,此同时还设置了failbit.
流的状态由bad,fail,eof和good操作揭示.如果bad,fail或者eof中任意一个位true,则检查流本身将显示该流处于错误状态.类似的,如果这三个条件没有一个味true,则good操作返回true.
3.条件状态的访问
rdstate成员函数返回一个iostate类型的值,该值对应于流当前的整个条件状态;
iostream::iostate old_state = cin.rdstate();
4.多种状态的处理
常常会出现需要设置或清除多个状态的二进制位的情况.此时,可以通过多次调用setstate或者clear函数实现.另外一种方法则是使用按位或操作符在一次调用中生成"传递三个或更多状态位的值"的值,按位或操作使用其操作数的二进制模式长生一个整形数值.对于结果中的一个二进制位,其值为1,则该操作的2个操作数至少有一个对应二进制为1
8.3 输出缓冲区的管理
每个IO对象管理一个缓冲区,用于存储程序读写的数据.
1.输出缓冲区的刷新
我们的程序已经使用过endl操纵符,用于输出一个换行符并且刷新缓冲区.初次之外,c++语言提供了另外2个类似的操纵符.一个是经常使用的 flush,用于刷新流,但不在输出中添加任何字符.第二个则是ends,这个操纵符在缓冲区中插入空字符null,然后刷新它,
比如 cout<<"hi"<<flush;
2.unitbuf操纵符
如果需要刷新所有输出,最好使用unitbuf操纵符,这个操纵符在每次执行完写操作后都刷新流:
cout<<unitbuf<<"呵呵"<<"嘎嘎"<<nounitbuf;
等价 cout<<"呵呵"<<flush<<"嘎嘎"<<flush; nounitbuf操纵符将流恢复为正常使用的缓冲区刷新方式
警告:如果程序崩溃了,则不会刷新缓冲区.在尝试调用以崩溃的程序时,通常会根据最后的输出找出程序发生错误的区域,所以,输出时应多使用 endl 而非 'n',使用endl则不会担心程序崩溃时输出是否悬而未决
3.将输入和输出绑在一起
当输入流与输出流绑在一起,任何读输入流的尝试都将首先刷新其输出流关联的缓冲区.
如cin>>ival; 导致cout关联的缓冲区被刷新
8.4 文件的输入和输出
fstream头文件定义了三种支持文件IO的类型:
ifstream,由 istream派生来,提供读文件功能
ofstream,由ostream派生来,提供写文件功能
fstream,由iostream派生而来,提供读写同一文件功能
fstream类型除了继承下来的行为,还定义了2个自己的心操作符, open 和 close 以及形参位要打开文件名的构造函数
ofstream和ifstream详细用法
ofstream是从内存到硬盘,ifstream是从硬盘到内存,其实所谓的流缓冲就是内存空间;
在C++中,有一个stream这个类,所有的I/O都以这个“流”类为基础的,包括我们要认识的文件I/O,stream这个类有两个重要的运算符:
1、插入器(<<)
向流输出数据。比如说系统有一个默认的标准输出流(cout),一般情况下就是指的显示器,所以,cout<<"Write Stdout"<<'n';就表示把字符串"Write Stdout"和换行字符('n')输出到标准输出流。
2、析取器(>>)
从流中输入数据。比如说系统有一个默认的标准输入流(cin),一般情况下就是指的键盘,所以,cin>>x;就表示从标准输入流中读取一个指定类型(即变量x的类型)的数据。
在C++中,对文件的操作是通过stream的子类fstream(file stream)来实现的,所以,要用这种方式操作文件,就必须加入头文件fstream.h。下面就把此类的文件操作过程一一道来。
一、打开文件
在fstream类中,有一个成员函数open(),就是用来打开文件的,其原型是:
void open(const char* filename,int mode,int access);
参数:
filename: 要打开的文件名
mode: 要打开文件的方式
access: 打开文件的属性
打开文件的方式在类ios(是所有流式I/O类的基类)中定义,常用的值如下:
ios::app: 以追加的方式打开文件
ios::ate: 文件打开后定位到文件尾,ios:app就包含有此属性
ios::binary: 以二进制方式打开文件,缺省的方式是文本方式。两种方式的区别见前文
ios::in: 文件以输入方式打开(文件数据输入到内存)
ios::out: 文件以输出方式打开(内存数据输出到文件)
ios::nocreate: 不建立文件,所以文件不存在时打开失败
ios::noreplace:不覆盖文件,所以打开文件时如果文件存在失败
ios::trunc: 如果文件存在,把文件长度设为0
可以用“或”把以上属性连接起来,如ios::out|ios::binary
打开文件的属性取值是:
0:普通文件,打开访问
1:只读文件
2:隐含文件
4:系统文件
可以用“或”或者“+”把以上属性连接起来,如3或1|2就是以只读和隐含属性打开文件。
例如:以二进制输入方式打开文件c:config.sys
fstream file1;
file1.open("c:\config.sys",ios::binary|ios::in,0);
如果open函数只有文件名一个参数,则是以读/写普通文件打开,即:
file1.open("c:\config.sys"); <=> file1.open("c:\config.sys",ios::in|ios::out,0);
另外,fstream还有和open()一样的构造函数,对于上例,在定义的时侯就可以打开文件了:
fstream file1("c:\config.sys");
特别提出的是,fstream有两个子类:ifstream(input file stream)和ofstream(outpu file stream),ifstream默认以输入方式打开文件,而ofstream默认以输出方式打开文件。
ifstream file2("c:\pdos.def");//以输入方式打开文件
ofstream file3("c:\x.123");//以输出方式打开文件
所以,在实际应用中,根据需要的不同,选择不同的类来定义:如果想以输入方式打开,就用ifstream来定义;如果想以输出方式打开,就用ofstream来定义;如果想以输入/输出方式来打开,就用fstream来定义。
二、关闭文件
打开的文件使用完成后一定要关闭,fstream提供了成员函数close()来完成此操作,如:file1.close();就把file1相连的文件关闭。
三、读写文件
读写文件分为文本文件和二进制文件的读取,对于文本文件的读取比较简单,用插入器和析取器就可以了;而对于二进制的读取就要复杂些,下要就详细的介绍这两种方式
1、文本文件的读写
文本文件的读写很简单:用插入器(<<)向文件输出;用析取器(>>)从文件输入。假设file1是以输入方式打开,file2以输出打开。示例如下:
file2<<"I Love You";//向文件写入字符串"I Love You"
int i;
file1>>i;//从文件输入一个整数值。
这种方式还有一种简单的格式化能力,比如可以指定输出为16进制等等,具体的格式有以下一些
操纵符 功能 输入/输出
dec 格式化为十进制数值数据 输入和输出
endl 输出一个换行符并刷新此流 输出
ends 输出一个空字符 输出
hex 格式化为十六进制数值数据 输入和输出
oct 格式化为八进制数值数据 输入和输出
setpxecision(int p) 设置浮点数的精度位数 输出
比如要把123当作十六进制输出:file1<<hex<<123;要把3.1415926以5位精度输出:file1<<setpxecision(5)<<3.1415926。
2、二进制文件的读写
①put()
put()函数向流写入一个字符,其原型是ofstream &put(char ch),使用也比较简单,如file1.put('c');就是向流写一个字符'c'。
②get()
get()函数比较灵活,有3种常用的重载形式:
一种就是和put()对应的形式:ifstream &get(char &ch);功能是从流中读取一个字符,结果保存在引用ch中,如果到文件尾,返回空字符。如file2.get(x);表示从文件中读取一个字符,并把读取的字符保存在x中。
另一种重载形式的原型是: int get();这种形式是从流中返回一个字符,如果到达文件尾,返回EOF,如x=file2.get();和上例功能是一样的。
还有一种形式的原型是:ifstream &get(char *buf,int num,char delim='n');这种形式把字符读入由 buf 指向的数组,直到读入了 num 个字符或遇到了由 delim 指定的字符,如果没使用 delim 这个参数,将使用缺省值换行符'n'。例如:
file2.get(str1,127,'A'); //从文件中读取字符到字符串str1,当遇到字符'A'或读取了127个字符时终止。
③读写数据块
要读写二进制数据块,使用成员函数read()和write()成员函数,它们原型如下:
read(unsigned char *buf,int num);
write(const unsigned char *buf,int num);
read()从文件中读取 num 个字符到 buf 指向的缓存中,如果在还未读入 num 个字符时就到了文件尾,可以用成员函数 int gcount();来取得实际读取的字符数;而 write() 从buf 指向的缓存写 num 个字符到文件中,值得注意的是缓存的类型是 unsigned char *,有时可能需要类型转换。
例:
unsigned char str1[]="I Love You";
int n[5];
ifstream in("xxx.xxx");
ofstream out("yyy.yyy");
out.write(str1,strlen(str1));//把字符串str1全部写到yyy.yyy中
in.read((unsigned char*)n,sizeof(n));//从xxx.xxx中读取指定个整数,注意类型转换
in.close();out.close();
四、检测EOF
成员函数eof()用来检测是否到达文件尾,如果到达文件尾返回非0值,否则返回0。原型是int eof();
例: if(in.eof()) ShowMessage("已经到达文件尾!");
五、文件定位
和C的文件操作方式不同的是,C++ I/O系统管理两个与一个文件相联系的指针。一个是读指针,它说明输入操作在文件中的位置;另一个是写指针,它下次写操作的位置。每次执行输入或输出时,相应的指针自动变化。所以,C++的文件定位分为读位置和写位置的定位,对应的成员函数是seekg()和seekp()。seekg()是设置读位置, seekp是设置写位置。它们最通用的形式如下:
istream &seekg(streamoff offset,seek_dir origin);
ostream &seekp(streamoff offset,seek_dir origin);
streamoff定义于 iostream.h 中,定义有偏移量 offset 所能取得的最大值,seek_dir 表示移动的基准位置,是一个有以下值的枚举:
ios::beg: 文件开头
ios::cur: 文件当前位置
ios::end: 文件结尾
这两个函数一般用于二进制文件,因为文本文件会因为系统对字符的解释而可能与预想的值不同。例:
file1.seekg(1234,ios::cur); //把文件的读指针从当前位置向后移1234个字节
file2.seekp(1234,ios::beg); //把文件的写指针从文件开头向后移1234个字节
顺序容器
顺序容器,他将单一类型元素聚集成容器,根据位置来存储和访问,顺序容器元素排列次序与元素值无关,而是由元素添加到容器里的次序决定.标准库定义了3中顺序容器类型: 他们的差别在于访问元素的方式,以及添加或删除元素的相关操作的运行代价!
vector 支持快速随即访问
list 支持快速插入删除
deque 双端列队,又能随即访问,在前后插入元素速度很高
还有3中顺序容器适配器,适配器是根据原始的容器类型所提供的操作,通过定义新的操作接口,来适应基础的容器类型
stack 后进现出栈
queue 先进先出队列
priority_queue 有优先级管理的队列
所有的容器都是类模版,要定义某种容器,必须在容器名后加一对尖括号,尖括号里面存放元素类型: vector<string> a;
9.1.1 容器元素的初始化
除了默认构造函数,容器类型还提供其他的构造函数,使程序员可以指定元素初值
C<T> a; 适用所有容器
C a(a2); 创建a2为副本的a,必须相同的容器类型,存放相同类型的元素,适用所有容器
C a(b,e); 创建a,其元素是迭代器b和e标示范围内元素的副本,适用所有容器
C a(n,t); 用N个值为t的元素创建容器a,只适用顺序容器
C a(n); 创建N个值初始化元素的容器a,只适用顺序容器
9.1.2 容器元素的类型约束
c++中,大多数类型都可以用作容器元素类型.容器元素类型必须满足一下2个约束:
1.元素类型必须支持赋值运算 2.元素类型的对象必须可以复制;
如果容器存储类类型的对象,那么只有当其元素类型提供默认构造函数时,容器才能使用这种构造函数,
容器可以接受容器类型,比如 vector< vector<string> > a; 中间的空格一定要.
9.2 迭代器和迭代器范围
下面是迭代器常用的运算
*iter 返回迭代器iter所指向的元素引用, 所有迭代器都适用;
iter->mem 对iter进行解引用,获取指定元素中名为mem的成员,等效于 (*iter).mem
++iter 或 iter++ 对iter加1,使其指向容器里的下一个元素
–iter 或 iter–同上
iter1 == iiter2iter1!= iter2 比较2个迭代器是否相等或不等,当2个迭代器都指向一个容器的超出末端的下一位置时,或指向同一容器中的同一元素时候,2个迭代器相等
vector和deque 容器的迭代器提供额外运算,因为这2种容器为其元素提供快速,随即的访问,
迭代器的算数操作以及比较操作 如: iter += iter2; iter +2; > >= < <=
9.2.1 迭代器范围
c++使用一对迭代器标记迭代器范围m这两个迭代器分别指向同一容器中的2个元素或超出末端的下一位置,通常命名first 和 last 或 beg 和 end,第二个迭代器指向最后一个元素的下一个位置,
9.3.1 容器定义的类型别名
size_type 无符号整形,足以存储容器类型的最大可能长度
iterator 此容器类型的迭代器类型
const_iterator只读迭代器
reverse_iterator逆序寻址的迭代器
const_reverse_iterator只读的逆序迭代器
difference_type足够存储2个迭代器差值的有符号整数,可为负数
value_type 元素类型
reference 元素的左值类型
9.3.3 顺序容器添加元素
所有顺序容器都支持 push_back 表操作,提供在尾部插入一个元素.使容器长度加1,
c.push_back(t)容器尾部添加元素t,返回void类型
c.pish_front(t)在容器前段添加t元素,只适用于list 和deque容器
c.insert(p,t) 在迭代器p所指向的元素前面插入t,返回新元素的迭代器
c.insert(p,n,t)在迭代器p前面加入n个为t的元素,返回void
c.insert(p,b,e)在迭代器p所指向的元素前面插入 由b和e标记的范围内的元素
9.3.5 所有容器提供大小操作
c.size() 返回容器c中元素个数,返回类型为 c::size_type ;
c.max_size() 返回容器c可容纳的最多元素个数,返回类型同上
c.empty() 返回标记容器大小是否为0,为0返回ture
c.resize(n) 调整容器c的长度大小,使其容纳N个元素,如果N小于容器元素,则删除多余元素
c.resize(n,t) 调整容器c长度大小,使其能容纳N个元素,所有新添加的元素值为T
9.3.6 访问元素
如果容器非空,那么c.front()和c.back()将返回容器第一个或最后一个元素的引用.
c[n] 下表操作返回引用,适用于 vector 和deque
c.at(n) 同上,但是会检查下表是否越界,
9.3.7 删除元素
c.erase(p) 删除迭代器P所指向的元素,返回一个迭代器,指向被删除元素后面的元素,
c.erase(b,e) 删除迭代器b和e所标记的范围内的元素
c.clear() 删除容器内所有元素
c.pop_back() 删除最后一个元素,返回void
c.pop_front(0 删除第一个元素,只适用list或deque
9.3.8 赋值与swap
与赋值相关的操作都作用整个容器, 除了swap,
赋值操作符首先删除左操作数容器中所有元素,然后将右操作数容器的所有元素插入到左边容器,会更改容器长度
c1 = c2 删除c1所有元素,将c2元素复制给c1,类型和元素类型必须相同
c1.swap(c2) 交换内容,调用完该函数后,c1中存放的事c2原来的元素,c2存放的是c1的元素,比上诉操作快
c.assign(b,e) 重新设置c的元素,将迭代器b和e标记的范围内所有元素复制到c中,b和e必须不是指向c中的元素的迭代器
c.assign(n,t) 将容器c中心设置为n个值为t的元素
9.7 容器适配器
容器适配器让一种已存在的容器类型采用另一种不同抽象类型的工作方式实现,如stack 适配器可使任何一种顺序容器以栈的方式工作
size_type 一种类型,存储对象长度
value_type 元素类型
container_type基础容器的类型,适配器在此容器类型以上实现
A a; 创建一个新的空适配器,名为a
A a(c) 创建一个a适配器,初始化为容器c的副本
关系操作符 所有都支持 == != <= >=
使用时也必须包含头文件比如 #incluide<stack> #include<queue>
1.适配器的初始化
所有适配器定义2个构造函数,一个默认构造函数创建空对象,一个 带 容器参数的构造函数, 如 stack<int> stk(deq)
2.覆盖容器类型
默认的stack 和 queue 都基于 deque容器实现,而 priority_queue 则在 vector容器上实现.在创建适配器时,将一个顺序容器指定为适配器的第二个类型实参,可覆盖其关联的基础容器类型
stack< string , vector<string> > a;
stack可以建立所有容器之上,而queue适配器要求关联的基础容器必须提供 push_front 运算,而 priority_queue 适配器要求提供随即访问功能,所以可建立在 vector 或deque 容器上
9.7.1 栈适配器
s.empty() 如果栈为空,则返回true
s.size() 返回栈中元素个数
s.pop() 删除栈顶元素,但不返回其值
s.top() 返回栈顶元素的值,但不删除
s.push(item) 在栈顶压入新元素
9.7.2 列队和优先级列队
标准库使用了先进现出(FIFO)的存储和检索策略.进入队列的对象被放置在尾部,下一个被取出的元素则取自队列的首部
priority_queue 允许用户为列队的元素设置优先级,这种队列不是直接将元素放在列队尾部,而是放在比他优先级低的元素前面,默认使用 < 操作符来确定他们的优先级
要使用这2中队列,必须包含 queue头文件,
q.empty() 如列队为空,则返回true
q.size(0 返回队列中元素的个数
q.pop() 删除对首元素的值,但不返回值
q.front() 发挥对首元素的值,不删除,只适合队列
q.back() 返回对尾的值,不删除,只适合队列
重载操作符
14.1 重载操作符定义 :
重载操作符是具有特殊名称的函数 : 保留字 operator 后接需定义的操作符符号。重载操作符具有返回类型和形参表. 例如: MyClass operator+(const MyClass& , const MyClass&); 重载了加号操作符
重载操作符的形参数目(包括this指针)与操作符的操作数数目相同。并且必须具有一个类类型的操作数,内置类型的操作数,比如int不能重定义
重载操作符的优先级和结合性是固定的,比如 x==y+z; 总是将y和zbanding到operator+ ,并且将结果用作 operator==的右操作数
重载操作符不保证操作数的求值顺序,尤其是内置逻辑 && ,|| ,一般不重载他们
重载操作符可定义为普通非成员函数 和 类的成员函数 ,类成员函数少一个形参,用作隐式的this
一般讲非成员函数的重载 定义为友元关系
也能跟调普通函数一样,调用重载操作符
5.选择成员或非成员重载操作符的小技巧
* 赋值(=),下标([]),调用(()) 和成员访问箭头(->) 灯操作符必须定义为成员函数,否则编译器
报错
*像赋值一样,复合赋值操作符通常应定义为类的成员,与赋值不同的是,不一定非得这样做
*改变对象状态或给定类型紧密联系的其他一些操作符,入自增,自健和解引用,同城定义为类成员函数
*对称的操作符,如算数操作符,相等操作符,关系操作符和位操作符,最好的定义为普通非成员函数
14.2 输入和输出操作符
为什么要重载输入和输出操作符,因为之父I/O操作的类所提供的I/O操作接口,一般应该与标准库为内置类型定义的接口相同
输出重载操作符 << 的重载,为了与io标准库一直,操作符应接受ostream& 作为第一个形参,对类类型const对象的引用作为第二个形参,并返回对osteram形参的引用
重载输出操作符定义如下 :
ostream& operator<<(osteram& os, const ClassType &object)
{
os << //…
return os;
}
IO操作符必须为非成员函数,因为我们不能将该操作符定义为类的成员,否则,左操作数只能是类类型的对象
14.3 算数操作符和关系操作符
一般而言,将算数和关系操作符定于为非成员函数,并且不允许返回一个引用
14.4 赋值操作符
重载操作符也可以重载,赋值操作符必须定义为成员函数,必须返回对*this的引用
14.5 下标操作符
下标操作符必须是成员函数,类定义下标操作室一般需要2个版本,一个非const成员,一个const成员
14.6 成员访问操作符
为了支持指针类型,如迭代器,c++允许重载解引用操作符(*)和箭头操作符(->),箭头操作符必须为类成员函数,
14.7 自增自减操作符
也通常用于迭代器。分为前缀和后缀式,比如 i++ , ++i ,前缀是采用默认写法,返回*this引用,无参数,后缀形式为了与前缀区分,加上一个 int 形参,后缀式返回值,而不是引用,而且返回的是旧值,也就是没有被自增的值
14.8 调用操作符和函数对象
可以为类类型的对象重载函数调用操作符,比如重载() 就好像调用函数
14.9 转换与类类型
可以从类类型到其他类型的转换
14.9.1 转换操作符
转换操作符是一种特殊的类成员函数,它定义将类类型值转化为其他类型值得转换。
定义:转换操作符在类定义体中申明,保留字operator之后跟着转换的目标类型,转换函数不能返回值和形参表, 例如 operator int() {return val;},虽然转换函数不能返回类型,但是每个转换函数必须显示返回一个指定类型的值,例如 operator int 返回int值,转换函数一般不应该改变被转换的对象,因此转换操作符通常定义为const成员
14.9.2 使用类类型转换
只要定义了转换函数,编译器可以自动使用内置转换调用他们,比如用一个类类型跟整数型做比较,互相复制等等
14.9.3 实参匹配和转换
避免二义性转换,比如一个 operator int 和 operator double 这样会让编译器糊涂,避免二义性的最好方法就是避免编写互相提供隐式转换的成对的类
泛型算法
泛型算法必须包含头文件 #include<algorithm> <numeric> 和 <functional> 组成
标准库还定义了一组泛化的算数算法,使用它必须包含头文件 #include<numeric>
大部分算法都在一段范围内的元素上操作,我们将这段范围称为"输入范围"
<algorithm> 是所有STL头文件中最大的一个,他是由一大堆末班函数组成的,可以认为每个函数在很大程度上都事独立的,其中用到的功能范围涉及:比较交换,查找,遍历操作,复制,修改,移除,翻转,排序,合并等等
<functional>则定义了一些末班类,用以声明函数对象
算法函数
find(迭代器开始,迭代器结束的下一个,要查找的容器)
for_each() 对每个元素调用函数
find_if() 诈骗出第一个满足的元素
comnt() 统计元素的出现次数
comnt_if() 统计与指定词匹配的元素
replace() 用新值取代元素
replace_if() 用新值取代满足指定词的元素
copy() 复制元素
unique_copy() 复制元素,去掉重复
sort() 对元素排序
equal_range() 找到所有具有等价值的元素
merge() 归并排序的序列
/****************************
输入和输出迭代器
流迭代器属于#include<algorithm> 头文件中
istream_iterator (用于读取输入流)
ostream_iterator (用于输出迭代器)
ostream_iterator<int> output(cout,'/n') //将以换行符为一行的输出流,建立与 output 中
copy(迭代器开始,迭代器结束,output) //将容器的开头到结束都以每一个元素一换行的方式输出
/*****************************
前向,双向,随机迭代器
前向迭代器即具有输入迭代器的功能,同时也具有输出迭代器的功能.所以前向迭代器即支持数据元素的读取,也支持数据元素的写入功能.不过前向迭代器只提供数据元素的单向遍历功能.
双向迭代器具有前向迭代器的所有功能,不过双向迭代器在2个方向(前后)都可以对数据进行遍历.标准库容器中提供的迭代器都至少达到双向迭代器的要求,比如list,set,map. 双向迭代器要求能够增减,如reverse(0算法要求2个双向迭代器作为参数,
比如reverse(vdouble.begin(),vdouble.end()) 对容器进行逆向排序
随即访问迭代器具有双向迭代器的所有功能,不过随即访问迭代器在双向迭代器的基础上,能够在任意访问容器任意位置的功能,比如vector,deque,string 迭代器是随即访问迭代器.
/******************************
插入迭代器用于将值插入到容器中,他们也叫做适配器,因为他们将容器适配或转换为一个迭代器,并用于copy()这样的算法中,例如,一个程序定义了一个链表和一个向量容器 list<int> a; vector<double> b;
通过使用 front_inserter迭代器对象,可以只用单个copy()语句完成向量中的对象插入到链表前段的操作:
copy(b.begin(),b,end(),front_inserter(a)); ,自然还有Back_inserter ,将对象插入到集合的尾部.
/*******************************
混合迭代器函数
在涉及到容器和算法的操作中,还有2个迭代器函数非常有用:
advance(); 按制定的数目增加迭代器.
distance(); 返回到达一个迭代器所需(递增)操作的数目;
advance函数接受2个参数,第二个参数事向前推进的数目,对于前推迭代器,该值必须为正,而对于双向迭代器和随即访问迭代器,该值可以为负.
使用distance函数来返回到达另一个迭代器所需的步骤,主意这个函数事迭代的,也就是说,它底层第三个参数,因此比必须初始化该参数
/*******************************
迭代器适配器
适配器事用来修改其他组件接口的STL组件,事带有一个参数的类模版,STL定义了3种形式的适配器: 容器适配器,迭代器适配器,函数适配器.
STL中定义了2类迭代器适配器:
reverse_iterator 逆向迭代器 如 vector<int>::reverse_iterator
random_iterator随机迭代器提供随机读写功能.是功能最强大的迭代器
1.逆向迭代器事一种适配器,他通过重新定义递增运算和递减运算,使其行为正好倒置,这样,使用这类迭代器,算法将以逆序次序处理元素,所有标准库容器都允许使用逆向迭代器遍历元素
2.插入型迭代器用来将赋值操作转换为插入操作,通过这种迭代器,算法可以执行插入行为而不是覆盖行为,C+++标准库提供了3种插入型迭代器,后插入迭代器,他们之间的差别仅在于插入的位置,后插入的迭代器将一个元素追加到容器尾部,C++标准库中只有向量容器,双端队列容器,列表容器和字符串容器支持后插入迭代器,前插入迭代器将一个元素追加到容器的头部.
3种插入迭代器 back_inserter 后插入迭代器 frontinserter 前插入迭代器 general_inserter 普通插入迭代器
/********************************
STL通用算法调用形式
STL标准模版库中的算法大致分为4类:
1.非可变序列的算法,这类算法在对容器进行操作时不会改变容器内容,
2.可变序列算法,一般会改变他们所操作的容器的内容
3.排序相关算法,包括排序算法和合并算法,二分查找算法依稀序序列的集合操作算大等
4.通用数值算法,一般比较少
1.不可变算法的函数
for_each 对区间内的每个元素进行操作
find 循环查找
find_if 循环查找符合特定条件者
基础
术语{
rgument(实参) 传递给被调用函数的值
lock(快) 花括号扩起来的语句序列
uffer(缓冲区)一段用来存放数据的存储区域
lass(类)用于自定义数据结构的c++机制
omment(注视)编译器会忽略的程序文本
ondition(条件)求值为真或假的表达式.
urly brace(花括号)
ostream(输入输出流)
unction(函数) 有名字的计算单元
eader(头文件)
xpression(表达式)
ata structure(数据结构)
anipulator(操纵符) 在读或写事操纵流本身的对象
ember function(成员函数)类定义的操作
amespace(名字空间)
arameter list(形参表)函数定义的组成部分
reprocessor derective(预处理指示)
ource file(源文件)
tatement(语句) 一半以分号结束
igned(带符号)
nsigned(无符号)带符号的可以是正数或者负数,默认大部分都是带符号,申明无符号 unsigned int
ddress(地址) 一个数字,通过该数字可在存储器上找到一个字节
rray(数组)存储一组可通过下标访问的数据结构
onstructor(构造函数)用来初始化新建对象的特殊成员函数
explicit 函数或变量, 可以阻止不应该允许的经过转换构造函数进行的隐式转换的发生
}
算数类型{
bool 布尔型 真假 false 和 true
har 字符型 8位
char_t 宽字符型 16位
hort 短整型 16位
nt 整型 16位
ong 长整型 32位
loat 单精度浮点 6位有效数字
ouble 双精度浮点 10位有效数字
ong double 扩展精度浮点 10位有效数字 }
小提示{
1. 0开头的字面值整数常量表示八进制,0X开头的表示十六进制
//增加后缀定义不同类型 30U 30后面加U定义unsigned无符号型,定义L是long型,也可以加在一起UL
// 'a' 是char类型,在前面加上L'a' 就能得道wchar_t类型
2.转义字符
n 换行符
v 纵向制表符
r 回车符
a 报警符
? 疑问好
" 双引号
t 水品制表符
b 退格符
f 进纸福
\ 反斜线
' 单引号
3.左值右值
//左值可以出现在赋值语句左右2边,右值只能出现在右边
//变量是左值,数字字面值是右值,
4.命名习惯,变量一般小写字母表示,标识符应该大写能够放、帮助记忆的名字,最重要的是保持一致
5.通常把对象定义在它首次使用的地方 , extern 申明变量
6. const 把对象转换成一个常量
7枚举, enum 枚举类型名 {成员1,成员2…}; 默认的第一个成员是0,后面依次比前一个大1
8.定义一个类 class 类名{public /.. private /… };
//使用 class 定义类,默认成员访问属性为 private ,使用struct 为 public;
9名字空间: using std::cin;
10迭代器, 每种容器类型都有自己的迭代器类型 比如 vector<int>::iterator a;
每种容器都定义一对 begin 和 end 函数,用于返回迭代器, begin返回迭代器指向的第一个元素, end 返回迭代器指向最后一个元素的下一个
迭代器可使用解引用操作符(*操作符) 来访问迭代器所指向的元素 *a = 0;
11.宏定义 #define hehe 10; 定义的hehe 就代表10
11.typedef vector<int>::iterator L1; 将迭代器类型用L1代替
typedef void (*ZHIZHEN)(char* , int &);
12.动态数组! 指定类型和数组长度, int *p = new int[10]; 动态空间的释放 delete[] p;
13.构造函数与析构函数 ! 函数名与类名一样无参数的是构造器,构造函数前面加上 ~就是析构函数
14.条件操作符,允许将简单的if-else判断嵌入表达式 格式为: cnd ? expr1 : expr2;
cond 如果为0,则条件为false,非0为true,然后计算expr1,或expr2 其中一个表达式
i > j ? i : j 如果i>j 返回i ,否则返回j
15.强制类型转换 static_cast<int>
16.元友类, friend class 类名 ;,元友类虽然不是类名的基类,但是可以访问他
17.多个函数参数, 3个…
18.内联函数: 在函数返回类型前面加上 inline 就可以声明 ,他很耗费内存但是速度快
19.顺序容器类型,顺序容器的元素排列与元素值无关 vector 支持快速随机访问 , list 支持快速插入删除, deque 双端队列
stack 后进先出, queue 先进先出 , priority_queue 有优先级管理的列队
所有容器都是类模板,要定义容器必须在后面加上尖括号 vector<int> a;
20.迭代器 *iter 返回迭代器iter所指向的元素的引用 iter->mem 对iter进行解引用 ,获取制定元素中名为mem的成员等同(*iter).mem
++iter 给iter加1,使其指向容器中得下一个元素
只有vector 和 deque 类型迭代器支持算数运算和比较操作. iter + n 在迭代器上加n,将产生指向容器中前面的第n个元素迭代器
每种容器类型都有自己的迭代器,如 vector<int>::iterator iter; 这条语句定义了名为iter的变量,他的数据类型由 vector<int>定义的iterator 类型
begin 和 end 操作 , vector<int>::iterator iter = ivec.begin(); 吧iter 初始化由名为begin 的vector 操作返回的值,
迭代操作器可用解引用来访问迭代器所指向的元素, *iter = 0;
容器.rbegin() 返回一个逆序迭代器,它指向容器的最后一个元素, 容器.rend() 它指向容器的第一个元素前面的位置
容器.push_back() 在容器尾部创建一个新元素,并且赋值
容器.insert(p,t) 在迭代器p所指向的元素前面插入值为t的新元素,返回指向新元素的迭代器
容器.insert(p,n,t)在迭代器p所指向的元素前面插入n个值为t的新元素
容器.insert(p,b,e)在迭代器p所指向的元素前面插入迭代器b和e标记范围内的元素
容器.size() 返回容器中元素个数,返回类型为容器::size_type
容器.max_size() 返回容器最多可容纳元素个数
容器.empty() 返回标记容器大小是否为0的布尔值
容器.resize(n) 调整容器的长度,使其能容纳n个元素
容器.resize(n,t) 调整容器的长度,使其容纳n个元素,所有新添加的元素值为t
容器.back() 返回容器最后一个元素引用
容器.front() 返回容器第一个元素引用
容器[n] 返回下标为n的元素引用
容器.at[n] 返回下标为n的元素引用,如果下标越界,则该操作未定义
容器.erase(p) 删除迭代器p所指向的元素 ,返回一个指向被删除元素后面的元素的迭代器
容器.rease(b,e) 删除迭代器b和e所标记的范围内所有元素,其他同上
容器.clear() 删除容器所有元素
容器.pop_back() 删除容器最后一个元素 | vector容器可能不支持
容器.pop_front() 删除容器第一个元素 | vector容器可能不支持
c1 = c2 删除容器c1的所有元素,将c2的元素付给c1
c1.swap(c2) 交换内容,调用该函数后,c1中存放的是c2原来的函数,c2中存放的是c1原来的函数
c.assign(b,e) 中心设置c的元素 ,将迭代器b和 e标记的范围内所有元素复制到c中
c.assign(n,t) 将容器c重置为n个值为t的元素
c.capacity() 范围在重新分配前可以存储的元素总数
c.reserve() 高速vector容器应该预留多少个元素存储空间
21.find(a,b,c) 第一个迭代器,最后面的迭代器,形成一个范围,在这个范围内查找c,返回指向c的迭代器,需要包含头文件 algorithm
22.关联容器 通过键(key)存储和读取元素
map的元素以键-值(key-value)对的形式组织:键用作元素在map中得索引,而值则表示所存储和读取的数据.
set仅包含一个键,并有效的支持关于某个键是否存在的查询
multimap 支持同一键多次出现的map类型 multiset 支持同一键多次出现的set类型
pair类型,在<utility>头文件中定义
pair<t1,t2> p1; 创建一个空的pair对象,他的2个元素分别是t1和t2类型,采用值初始化
pair<t1,t2> p1(v1,v2); 创建一个pair对象,他的2个元素分别是t1和t2类型,其中first成员初始化为v1,而second成员初始化v2
make_pair(v1,v2) 以v1和v2值创建一个新的pair对象,其元素类型分别是v1和v2类型
p1 < p2 2个pair对象之间的小余运算,定义其字典顺序:如果p1.first<p2.first 或 !(p2.first<p1.first) && p1.second<p2.second,则返回true
p.first 返回p中名为first的(公有)数据成员
p.second 返回p中名为second(公有)数据成员
创建pair对象时,必须提供2个类型名 pair<string,int> a;
23.mp3 使用它必须包含<map>头文件,在定义map对象时m必须分别指明键和值的类型 比如: map<string,int> a;string类型键索引,
构造函数 map<k,v> m; 创建一个名为 m 的空map 对象,其键和值跟别时k和v
map<k,v> m(m2); 创建一个m2的副本m,m和m2必须有相同的键类型和值类型
map<k,v> m(b,e); 创建map类型的对象m,存储迭代器b和e标记的范围内所有元素的副本
map<string,int> ma; ma["Anna"] = 1 ; 查询键为Anna 的对应值,将其赋为1, 如果没有则插入
map容器的insert成员, m.insert(e) e是一个在m上的value_type类型的值,如果e.first不再m中,则caruso值为e.second的新元素
m.insert(beg,end) beg和end是标记元素范围的迭代器,其中的元素必须为m.value_type类型的键-值对
m.insert(iter,e) e是一个用在m上的value_type类型的值,如果键(e.first)不再m中,则创建爱你新元素,并以迭代器iter为起点搜索新元素存储的位置
m.count(k)返回m中k出现的次数, m.find(k) 如果容器中存在按k索引的元素,则返回指向该元素的迭代器
m.erase(k) 删除m中键为k得元素,返回size_type类型得值,表示删除的元素个数
m.erase(p) 从m中删除迭代器p所指向的元素,p必须指向m中确实存在的元素,而且不能等于m.end()
m.erase(b,e) 从m中删除迭代器b和e标记的范围内的元素
}
class {
1.对于没有定义构造函数,并且全体数据成员均为public的类,可以采用数组初始化成员的方式;
2.友元: 允许一个类对其非公有成员的访问权授予制定的函数或类,友元的申明以关键字friend 开始,它只能出现在类定义的内部,友元申明可以出现在类中任何地方
3.static ; 静态成员,也可以是成员函数,不过静态成员函数没有this形参,它可以直接访问所属类的static成员,但不能直接使用非static成员
}
特别不会注意的地方
目录 :
条款5: c++默认编写并调用哪些函数
你没有声明的时候,编译器会自己声明 一个复制构造函数,一个复制赋值操作符,一个析构函数
此外,如果你没有申明任何构造函数,编译器会为你声明一个默认构造函数.这些函数都是public且 inline的,注意,编译器产生的西沟函数是 no-virtual的;
条款6: 若不想使用编译器自动生成的函数,就该明确拒绝.可将相应的成员函数声明为private并且不与实现.
条款7: 为多态基类声明 virtual 析构函数. c++明白指出,当派生类对象经由一个基类指针被删除,而该基类带着一个non-virtual析构函数,其结果没有定义. 消除这个问题的做法就是给基类一个virtual析构函数.没有虚析构函数的类型不能用作基类.
个人认为理解基类指向派生类的指针,派生类指向基类的指针,以及将派生类指针强制转换为基类指针和将基类指针强制转换为派生类指针的问题,只要理解一点,那就是无论是基类指向派生类还是派生类指向基类,重点就是哪个类的指针就调用哪个类的方法,而输出的是指针指向的对象。
条款8:析构函数对对不要吐出异常.
条款9:构造和析构函数时不要调用虚函数(virtual)
条款10:赋值采用右结合律,可以连锁赋值,比如 x=y=z=15 等于 x=(y=(z=15)); 为了实现连锁赋值,赋值操作符 operator= 必须返回一个引用,只想操作符的左侧实参.比如 *this,
条款11:当对象自我赋值时 operator= 需要确定 来源对象和目标对象不是同一地址,以免自我赋值,可以巧妙的运用 复制和交换函数解决
条款12:当你写一个复制函数时,请确保赋值所有成员变量,和调用基类的复制函数.不要用赋值操作符调用复制函数,如果他们有相同的代码,请放进第三个函数中,供他们调用
———————-3 资源管理
条款13:用对象管理资源,利用类的析构函数释放资源.这种观念叫"资源取得时便是初始化时机".
auto_ptr 是智能指针,类指针对象. 其析构函数自动对其所指对象调用delete, std::auto_ptr<函数返回指针类型> Pinv资源指针变量名(返回资源的函数());
auto_ptr 被销毁时会自动删除它所指之物,所以不能让他同时指向同一对象,否则对象会被删除一次以上.所以auto_ptr又一样咯性质,如果通过国复制构造函数或赋值操作符复制他们,他们会变成NULL,而复制所得的指针将取得资源的唯一拥有权.
还有一种智能指针,tr1::shared_ptrs,使用方法同上,但是允许赋值和复制
条款14:复制RAII对象必须一并复制它所管理的资源,所以资源的复制行为决定RAII对象的复制行为.
条款16:如果你在new表达式中使用[],必须在相应的delete表达式中也是用[],反之毅然.