C Plus 总复习笔记
2014.6.25.Chitry
第一章 面向对象介绍
2014年6月25日 09:28:36
1. 结构化程序设计:程序 = 算法 + 数据结构
基本思想:自顶向下,逐步细化的设计方法和单入单出的控制结构
重点:函数
缺点:a.程序难以管理
b.数据修改存在问题
c.程序可重用性差
d.用户难以在系统分析阶段准确定义,致使系统在交付使用时产生许多问题
e.用系统开发的每个阶段的成果来进行控制,不能适应事物变化的要求
缺陷根源: 数据与数据处理相分离
2.面向对象程序设计: 程序 = 对象 + 对象 + 对象 + … + 消息传递
“以对象为中心的思维方式”
将系统看成通过交互作用来完成特定功能的对象的集合,每个对象用自己的方法来管理数据,亦即:只有对象内部的代码能够操作对象内部的数据
模拟自然界认知和处理事物的方法,将数据和对数据的操作方法放在一起,形成一个相对独立的整体——抽象(object)
对象 = [ 算法 + 数据结构 ] “结合,适应变化,封装的,呈现的行为时稳定的,仅借口暴露 ”
对象(内容) = 属性 + 行为
同类对象可抽象出共性,形成类(class)
一个类中的数据通常只能通过本类提供的方法进行处理,这些方法成为该类与外部的接口
对象之间通过消息(message)进行通讯
3.面向对象程序设计的基本特征、特性、及其优缺点:
A.面向对象程序设计的基本特征: 抽象、封装、继承、多态
B.面向对象编程的特性: a.程序设计的重点在数据而不是函数
b.程序由对象对象组成,建立对象不是为了完成一个步骤,而是为了描述某个事物在整个解决问题的步骤中行为
c.对象之间通过相互协作来完成功能
e.函数与相关数据紧密结合
e.数据可以被隐藏
f.很容易扩充新的数据和函数
C.面向对象编程的优缺点:
优点:a.易维护
b.质量高
c.效率高
d.易拓展
缺点:相对面向过程,运行效率会下降10%左右
4.面向结构化和面向对象的不同之处:
根本在于其抽象的级别不同
A.结构化程序设计方法应用的是过程抽象,是将问题域中具有明确功能定义的操作抽取出来,并将其作为一个实体看待
B.面向对象对象设计方法应用的是数据抽象,是较过程抽象更高级别的抽象方式,将抽象客体的属性和行为绑定在一起,实现统一的抽象,从而实现对现实世界客体的真正模拟。
5.编程语言四大需求:
1>.效率:速度
2>.灵活
3>.抽象:大型的系统开发的适应
4>.生产力:开发效率
* C :效率、灵活
C Plus:效率、灵活、抽象
Java & .Net :抽象、生产力
6.世界上第一种面向对象编程语言: SmallTalk
第二章 从C到C++(C99,C++11)
2014年6月25日11:20:26
1.输入输出流: cin >> 变量 输入对象:键盘
cout << 数据 输出对象:显示屏
“>>” ”<<”仍然保留“按位移”功能
I/O流输入输出的是一串字节
2.命令空间:using namespace std;
若预处理命令为 .h 头文件形式,则无需申请命令空间
两种头文件不能混用!!
(尖括号为根目录下寻找头文件,圆括号为当前目录下查找头文件)
3.输出操纵符:“endl” 插入换行符,并刷新流(!!!endl不能用在输入流中,否则会报错!!!)
4.格式控制符:
cout << hex << x << ‘ ’ << dec << x << ‘ ’<< oct << x << endl;
hex:十六进制输出
dec:十进制输出
oct:八进制输出
*作用域一直到程序结束
# cout << setw(6) << 456 << endl; 以六位域宽输出“456”
但是必须加入iomanip.h头文件
5.续行符:“\” 续接过长的语句
6.C Plus中可以随时声明定义变量(C中要求要统一列与程序前几句)
7.结构、联合和枚举名可以直接作为类型名
8.const限定符:
与宏(#define)的区别:define是完全替换,不作整合总结
const 是先整合,后替换
默认为 const (int)i = 2100;
*这个常量i是有类型的 ,占存储单元的,有地址的,可以用指针指向它,但是不能修改它(常量的不可修改性)<内存空间被固化>
#*#
A. const char * name = “lin”; 指向常量的指针 固化指针所指向的空间
B. char * const name = “lin”; 常指针 固化指针的指向
C. const char * const name = “lin”; 指向常量的常指针 固化指针的指向和指针所指向的空间
&*&%:void型指针;void * p; 不确定类型 “通用型指针”
获值后,必须进行显示转换处理
9.内联函数:空间换时间!!
inline int show (int i, int j)“声明和函数体都必须加上inline关键字”
A. 内联函数体内不允许出现循环和switch语句(if可以出现)
B.内敛函数具有宏定义的相似机理,但更安全
宏:先替换语句,后传递参数
内敛函数:先传递参数,后替换语句
10.带缺省参数的函数:
int abc (itn x = 5,float y = 5.3);
调用:abc(100,79.8);//x = 100, y = 79.8
abc(25);//x = 25,y = 5.3
abc( );//x = 5,y = 5.3
*声明函数时,所有指定默认值的参数都必须出现在不指定默认值的参数的右边(实参和形参的结合时从左到右顺序进行的),因此指定参数的形参必须放在参数列表中的最右端
int fun(int i,int j = 5,int k); //Error!!
*调用时,不允许出现某个参数省略,再给其后参数指定参数值的情况
abc( , 21.5); //Error!!
*函数声明和定义中只要求出现一次缺省值的定义就可以了,必须是在调用以前定义好,之后不必要再定义一次,否则会出现“重复指定默认值”的错误信息
11.函数重载: 静态的多态
前提:同一作用域中,形参个数和形参类型不同(与返回值无关)
*重载函数中尽量不要使用缺省值,可能引起二义性
void abc(int i = 0,int j = 0,int k = 0);
void abc(int i);
调用:abc(20);
编译系统无法确定调用哪一个函数
*重载函数调用时,若给出的实参与形参类型不相符,C++编译器就会自动做类型转换工作,有可能的错误:
void abc(int x);
void anc(long x);
调用:abc(5.56);
编译无法确定应该将5.56转换为何种类型
12.(作用)域运算符: “::”
“扩大/改变局部变量的作用范围”
::aver 调用已被局部同名变量覆盖的全局变量
13.无名联合: 类比匿名对象
C++一种特殊联合(union),它在关键字union后面没有给出联合名,它可是一组数据成员共享同一内存地址
14.new/delete运算符:
A.new运算符用于创建堆空间(类似C中的malloc函数“动态分配”)
**分配成功返回空间首地址,失败提示异常(VS中)(而不是NULL空指针(VC中))**
语法:a.指针变量 = new 数据类型; 创建一个数据类型的元素
Int *p;p = new int;
b.指针变量 = new 数据类型[长度n]; 创建n个数据类型的元素
Int *p;p = new char[50];//数组大小必须写,new不能为之初始化
# new动态分配多维数组空间:
Int *p = new int[2][3][4]; 必须提供所有维度的大小
*其中,第一维可以使任意整数表达式
Int i =3;
Int *p = new int[i][3][4];
c. new 可以在为简单变量分配内存的同时,进行初始化
指针变量 = new 数据类型(初值)
Int *p;p = new int(99); *p = 99
B.delete运算符用于释放堆空间(类似C中的free函数)
语法:delete 指针变量;
delete[] 指针变量
C. new一个新对象: 1>.内存分配(operator new)
2>.调用构造函数
Delete释放一个对象: 1>.调用析构函数
2>.释放内存(operator delete)
15.引用: 给变量起别名 本质是指针
类型 & 引用名 = 已定义的变量名
Int i= 5;
Int & j = i; //j是i的引用,j与i共用一段存储空间,改变i、j任何一个都会改变对应内存空间上的值
*a.除用作函数参数或返回值类型外,声明引用时,必须立即对它进行初始化
*b.可以为一个变量起多个别名
Int i= 5;
Int & j = i;
Int & k = j; //i、j、k共用一段空间,引用j、k本身是不分配内存的
*c.引用在初始化后不能再被重新声明为另一个变量的引用(别名),亦即一个别名不能被多个变量同时使用
*d.不允许建立void类型的引用
*e.不能建立引用数组
*f.不能建立引用的引用,不能建立指向引用的指针
Int n = 3;
Int && r = n;//Error!
Int &* p = n;//Error!
*g.可以将引用的地址赋给一个指针,此时指针指向原来的变量
Int num = 50;
Int & ref = num;
Int * p = & ref;//*p = num &在此为取地址运算符,而不是引用运算符
#引用的最大用处体现在函数返回值和形参上
函数声明的时候变量名可以省略,但是在函数体第一行中不能省略
第三章 类和对象
2014年6月25日19:37:29
类是一种数据类型,是抽象概念,而对象是自然实体
1.访问控制符:信息隐藏
public:类与外部接口,任何外部函数都可以访问共有类型数据和函数
private:只允许本类中的函数访问,二类外部的任何函数都不能访问
protected:与private相似,其差别表现在继承与派生时对派生类的影响不同
*访问控制符对当前类内部无效
2.成员函数的定义:
A.类内部,访问控制符下直接定义
B.类外部,在函数名前加上类名和域限定符(如:int Point :: get())
<::get()和get()都是普通函数>
此种情况下,在类中的声明可以不带形参变量名,但有要类型
在类外定义成员函数又称作 隐式内联声明
*以内联方式定义成员函数体(显式内联)
类中声明时,函数返回值前的inline关键字可以略去
但类外定义的函数体第一行前面的inline关键字不能省去
“较短的函数才适合内联函数,必须满足内联函数的使用前提”
3.类与结构体的区别:未指定访问权限时,class默认是私有的,而struct是公有的
结构体是一种特殊的类!
4.对象的定义和使用:
1》.定义方式:
A.全局对象定义:类似全局结构体
在类的定义之后,立刻定义对象,随后用分号封装类
如:
class Point{
……
}op1,op2;
op1 和op2就是全局定义的对象
B.声明类以后,在使用时定义对象,若在main函数以内,则为局部对象
Point op1,op2;
在没有重写构造函数的情况下,此种对象定义已经分配好堆空间,这是与Java中不同的地方
2》.访问方式:
A.对象名.数据成员名
对象名.成员函数名[(实参表)]
*其中“.”称作“简点运算符”
op1 .get(); 完全等价于 op1 .Point :: get();
B.指向标识符:类似结构体指针
如:
Data d,*p;定义了指向类Data的指针变量p
p = &d;//使指针p指向对象d
Cout << p ->year; //输出p指向对象中的成员year
5.构造函数和析构函数:
1》.构造函数是特殊的成员函数
创建类类型的新对象,系统会自动调用构造函数
其存在意义是:保证对象的每个数据成员都能被正确的初始化
定义: 1、函数名与类名完全相同
2、不能定义构造函数的类型(返回值类型),即使是void
3、通常情况下构造函数为共公有函数(私有构造函数有特殊用途)
4、可以有任意类型和任意个数的参数,一个类可以有多个构造函数(重载),一个类至少有一个构造函数
**在建立对象的同时,采用构造函数给数据成员赋初值,通常有两种形式:
形式1:
类名 对象名[(实参表)] 如:Point A(1.1,2.2);
形式2:
类名 * 指针变量名 = new 类名[(实参表)] 使用new运算符建立的是动态对象 如:Point * pa = new Point(1.1,2.2);该对象是无名对象,但是该对象有地址,这个地址存放在指针变量pa中,访问用new动态建立的对象一般不用对象名,而是通过指针访问
cout << pa->get() << endl;
#注意动态分配的堆空间一定要使用delete运算符释放空间!!!
构造函数的调用:1、构造函数由系统自动调用(不需要用“对象名.”形式调用,初始化对象同时调用构造函数)
2、一个对象仅能调用一次(一个)
3、构造函数可以不带参数
2》.成员初始化列表初始化数据成员:
带有成员初始化列表的构造函数的一般形式:
类名 ::构造函数名[(实参表]) [ : (成员初始化列表)]
{
//构造函数体
}
成员初始化列表的一般形式:
数据成员名1 (初始值1),数据成员名2(初始值2),……
举例类外成员函数:
Point ::Point(double r,double i) : real(r),imag(i)
{
}
*应用:const修饰的数据成员 或是 引用类型的数据成员(以及其他不允许使用赋值语句赋值的情况)
*#*类是一个抽象,系统不会为之分配空间,是以,在类的定义中,不能进行赋值初始化操作!!!!
3》.析构函数:
定义; 1、函数名和类名类似,前面加一个字符“~”
2、没有返回类型
3、没有参数
4、若没有定义析构函数,则系统会自动生成一个默认析构函数,撤销对象时,自动调用,无需对象名调用
注意:1、析构函数与delete的区别:
如:
Test *t = new Test[2];//创建两个对象,未赋值
Delete [] t;
运行的结果是:Initializing……
……
Destory……
……
亦即:析构函数是真正的释放内存空间,而delete是销毁内存空间
*在栈区中创建的对象,在生存期结束时会自动调用析构函数
*在堆上创建的对象,要有程序员显式调用delete释放该内存空间
2、析构函数可以显式调用:
如:
Test t;//在对象生命期结束以后,系统会自动调用析构一次
t. ~Test();//亦即,此处析构第二次
6.对象数组和对象指针:
1》.对象数组;
类名 数组名[下标表达式]
如:
Point p[10];
赋值:Point p[4] = {11,22,33,44};
多个值的情况:
Point p[3] = {
Point(11,22);
Point(33,44);
Point(55,66);
};
访问:数组名[下标].成员名 如:ob[2].get();
*每个数组元素消亡时,都会自动调用一次析构函数
2》.对象指针:
类名 * 对象指针名
如:
Point op;//定义了一个对象
Point *p;//定义了一个对象指针变量p
p = &op;//使对象指针指向对象op(由于类是一种数据类型,指针的类型与其所要指向的变量的数据类型必须一致,所以定义时候用相同的类进行定义)
p ->get();//调用成员函数
*用对象指针访问对象数组元素时,必须要对指针指向进行移位
3》.this指针:自引用指针
This指针是一个const指针,不能进行修改和赋值
This指针式一个局部数据,其作用域仅在一个对象内部
7.向函数传递对象:
1、使用对象作为函数参数:
void abc(Point opp);//定义对象作为形参以接收传参
2、使用对象指针作为函数参数:
定义:void abc(Point *opp);//定义对象指针作为形参以接收传参
调用:abc(&op);//传入对象的地址
3、使用对象引用作为函数参数:
定义:void abc(Point &opp);//定义对象引用作为形参以接收传参
调用:abc(op);
8.对象的赋值和拷贝:
1、对象的赋值:浅拷贝
Point p1,p2;
P1.set(20,5);
P2 = p1;
*赋值运算符已经重载
注意: a.两个对象的类型(数据类型/类类型)必须相同
B.赋值以后两个对象时相互分离的,互补影响
C.此种赋值方式在类中含有指针的时候可能会产生错误
2、对象的拷贝;
拷贝构造函数:深拷贝
使用一个已经存在的对象来初始化一个新的统一类型的对象
声明:只有一个参数,并且参数为该类对象 的引用
*倘若类中没有说明拷贝构造函数,则系统自定生成一个缺省拷贝构造函数
定义:
类名 ::类名(const 类名 & 对象名)
{
//拷贝构造函数的函数体
}
如:(在Point类中)
Point(const Point &p)
{
X = 2*p.x;
Y = 2*p.y;
}
调用:
Test t(10);
Test tt(t);//若未定义拷贝构造函数,则系统自动调用默认拷贝构造函数,完全复制
*参数为引用,减少内存的复制,对象的拷贝,可加速
拷贝构造函数的调用情况: 1、当用一个对象去初始化另一个对象时,拷贝构造函数会被自动调用 Point p2(p1)代入法 或者 p2 = p1;
2、当函数的形参是类的对象,在调用函数进行形参和实参结合时,拷贝构造函数将会被调用
3、当函数的返回值是类的对象,在函数调用完毕将返回值(对象)带回函数调用处时,此时就会调用拷贝构造函数,将此对象复制给一个临时对象并传到该函数的调用处。
9.静态成员:
非static数据成员存在于类类型的每个对象中,static数据成员独立该类的任意对象存在,它是与类关联的,不与类对象关联
Static成员优点:1、成员的名字在类作用域中,因此可以避免与其他类成员或全局对象名字冲突
2、可以封装,static成员是私有的,而全局对象不可以
定义:
Static 数据类型 数据成员名
static成员需要在类定义体外进行初始化与定义
特殊的整型static const成员:其可以在类定义体中初始化该成员,可以不在类体外定义
Static成员函数: 1、无this指针
2、非静态成员函数可以访问静态成员,反之,不可
10.友元: 破坏信息隐藏的一种手段
友元是一种允许非类成员函数访问类的非公有成员的一种机制
可以把一个函数指定为类的友元,也可以把整个类指定为另一个类的友元
友元的作用在于:提高程序的运行效率
1〉.友元函数:
友元函数在类作用域外定义,但它需要在类体中进行说明
为了与该类成员函数想区别,定义的方式是在类中用关键字friend说明该函数,格式如下:
friend 类型 友元函数名 (参数表)
注意事项:
1/友元函数不是类的成员函数,在函数体中访问对象的成员,必须用 对象名 + 运算符“.” +对象成员名
但友元函数可以访问类中的所有成员,而一般的函数只能访 问类中的公有成员
2/友元函数不受类中访问权限关键字的限制,可以把类的公有,私有,保护部分,但是结果一样
3/某类的友元函数的作用域并非该类作用域,若该类的友元函数是另一类的成员函数,则其作用域为另一类的作用域,否则与一般函数相同
4/友元函数破坏了类的封装性,是以,友元函数鄙视必须是以哦那个,则尽可能少使用。或用其他手段保证封装性
2〉.友元类:
若某类B的成员函数会频繁的存取另一个类A的数据成员,而A的数据成员的 Private/Protected 限制会造成 B 的存取麻烦, B只能通过 A 的Public 成员函数 进行间接存取
1/把 B 做成 A类的友元类,即 A类 向 B类 开发 其 private/protected 内容 ,让B直接存取
2/友元类:一个类可以作为另一个类的友元
3/友元类的所有成员函数都是另一个类的友元函数
4/友元类的声明:
friend class 类名
5/注意事项:
1.友元关系是单向的(A是B的友元类,并不代表B是A的友元类)
2.友元关系不能被传递(A是B的友元类,B是C的友元类,并不代表A是C的友元类)
3.友元关系不能被继承(A是B的友元类,C继承自A,并不代表C是B的友元类)
11.类的组合(对象的嵌入):
在一个类中内嵌另一个类的对象作为数据成员,是为 类的组合,该内嵌对象称为对象成员
如果一个类具有内嵌的对象成员,那么内嵌对象成员也将被自动创建,因此,在创建对象时既要对本类的基本数据成员初始化,又要对内嵌对象成员进行初始化
如:
class {
类名1 对象成员1;
类名2 对象成员2;
……
类名n 对象成员n;
};
一般来说,类X的构造函数的类外定义形式为:
X :: X(形参表0) : 对象成员1(形参表 1),对象成员2(形参表2),…… //后半部分称作初始化表
{
//类X的构造函数体
}
当调用这个构造函数的时候,首先按各内嵌对象成员在类声明中的顺序依次调用它们的构造函数,对这些对象进行初始化,然后再执行类X的构造函数体,初始化类X中的其他成员,析构函数的调用顺序与之相反,这是一种压栈思想。
12.常类型: 再谈const
1〉.常引用:const 类型 & 引用名
如:
Int a = 5;
const int & b = a;
则b是一个常引用,它所指向的对象不允许更改(固化引用指向的内存空间),即出现 b = 12是非法的
实际中常引用被用作形参,此为常形参,便不会产生对实参的不希望的更改(形参不允许更改)
2〉.常对象:
类名 const 对象名[(参数表)];
或者
Const 类名 对象名[(参数表)];//意味着该对象是一个常量
常对象的数据成员值在对象的整个生存期内不能被改变
在定义对象时必须被初始化,而且不能被更新
*注意: 通过const对象只能调用它的const常成员函数,而不能调用它的普通成员函数,常成员函数是常对象唯一的对外接口,此为C++从语法机制上对常对象的保护(构造和析构除外)
*常对象的数据成员不允许被普通成员函数访问和改变值
*常成员函数可以访问普通数据成员,常数据成员和常对象的数据成员,但是,均不允许改变其值
*用mutable修饰的数据成员即使是在const对象或在const成员函数中都可以被修改!!!
3〉.常对象成员:
A.常数据成员: 只能在初始化时,改变值
如果在一个类中说明了常数据成员,(不允许任何赋值),那么构造函数就只能通过成员初始化列表对该数据成员进行初始化,而其他任何函数都不能对该成员赋值
B.常成员函数:
类型说明符 函数名 (参数表) const;
Const是函数类型的一个组成部分,因此在声明函数和定义函数时都要有关键字const,而在调用时不必加const
常成员函数不会修改对象的状态
常成员函数只能访问数据成员的值,而不能修改它
常成员函数可以与非常成员函数构成重载
在不要求修改数据数据成员的值时,尽量使用const成员函数
第四章 派生类和继承
2014年6月28日13:18:49
1.关于继承和派生: 继承——提高软件/代码的重用性
继承:新类具有基类中已有属性和方法
派生:在基类基础上加上满足新类特定要求所需的新成员
基类称为父类,子类称为派生类;
2.派生类的定义:
格式:
class <派生类名>:[<继承方式>] <基类名>
{
<派生类新定义>
};
其中<继承方式>有:
public
private
protected
继承方式的不同,确定了派生类成员以及类外对象对于从基类继承来的成员的访问权限
若为public继承方式,则派生类成员和类外对象都可以访问父类中的非私有成员
若为protected继承方式,则仅派生类成员可以访问父类成员,而类外对象并不能访问父类中的非私有成员
若为private继承方式……(具体看表格)
基类中成员在派生类中的访问权限
继承方式 |
基类特性 |
派生类特性 |
派生类中的成员函数 |
派生类的对象 |
公有继承 |
public protected private |
public protected 不可访问 |
可访问基类中的公有成员 和保护成员 |
可访问基类和派生 类中的公有成员 |
私有继承 |
public protected private |
private private 不可访问 |
可访问基类中的公有成员 和保护成员 |
不能访问基类中的 所有成员 |
保护继承 |
public protected private |
protected protected 不可访问 |
可访问基类中的公有成员 和保护成员 |
不能访问基类中的 所有成员 |
*默认为private私有继承
例:如果A 是基类,B 是A 的派生类,那么B 将继承A 的数据和函数。
class A{
public:
void Func1(void);
void Func2(void);
};
class B : public A{
public:
void Func3(void);
void Func4(void);
};
注意:
基类中的成员函数可以访问基类的所有成员,但不能访问派生类的成员
基类的对象只能访问基类的公有数据成员和调用基类的公有成员函数,基类对象不能访问派生类的成员
派生类中的成员函数可以访问派生类的所有成员,也能访问其基类的public成员和protect成员
派生类的对象能访问派生类的公有数据成员和调用派生类的公有成员函数,公有继承的派生类对象还能够访问其基类的public成员和protect成员
****C++的“继承”特性可以提高程序的可重用性。
要防止乱用“继承”:
1、如果类A 和类B 毫不相关,不可以为了使B 的功能更多些而让B 继承A 的功能。
2、如果类B 有必要使用A 的功能,则要分两种情况考虑:
(1)若在逻辑上B 是A 的“一种”(a kind of ),则允许B 继承A 的功能。
(2)若在逻辑上A是B的“一部分”(a part of),则不允许B 继承A 的功能,而是要用A和其它东西组合出B 。
*继承的好处:
复用编好的代码和设计好的数据结构,而不用重复编写(软件重用和接口重用);
使程序易于维护,因为相关的数据和代码集中放在某处;
有效模拟了实际生活中的许多关系,如人与男人,交通工具和汽车等。
3.派生类对象的初始化:
定义了一个派生类后,它将继承其基类中的全部成员。则派生类对象中不仅包含派生类的数据成员,还包含从父类继承的全部数据成员。
*派生类对继承而来的成员的调整:
1/改变基类成员在派生类中的访问属性(通过继承方式实现)
2/重定义基类中的非私有成员(覆盖定义)
<注意:倘若是重定义成员函数,不仅应使函数名相同,还应使函数的参数表也相同,否则视作重载>
3/访问声明:私有继承后者保护继承时,在派生类的公有关键字下将基类的非私有成员通过作用域运算符调整为公有继承
如:class B :private A{
Public:
……
A ::print;//void print()在A类中为公有成员函数,此处调整为私有派生类B的公有成员函数
*派生类对象的初始化:与类的组合相对照理解
派生类对象初始化时,既要对派生类本身的数据成员初始化,又要对基类继承来的数据成员初始化。
C++语言定义派生类构造函数的格式为:
派生类名(参数总表(包括类型)):父类名(参数表) ;
//冒号前为定义当前类的构造函数,冒号后为调用基类的构造函数,是以,形参名前不用缀上数据类型
在冒号“:”后面是父类构造函数表(原则上有多少个父类就有多少个父类构造函数),以及对象成员初始化列表。
*特殊:含有对象成员(子对象)的派生类的构造函数:(即类的继承与类的组合相结合)
派生类名(参数总表(包括类型)):父类名(参数表0) ,对象成员名1(参数表1),……对象成员名n(参数表n);
冒号前为定义当前类的构造函数,需要缀上数据类型,而冒号后面为单纯的调用,所以不需要缀上数据类型
调用顺序由它们在类中的声明顺序决定
注意:
(1) 当使用基类参数的构造函数来完成基类成员的初始化时,即使派生类构造函数本身无需完成任何工作(函数体为空),也必须定义派生类的构造函数。
(2) 如果基类定义有缺省构造函数,则在派生类构造函数可省略基类初始列表。
(3) 派生类构造函数的执行顺序是:先父母(执行基类构造函数),再客人(初始化对象成员),最后自己(初始化派生类本身的普通数据成员)。
(4) 派生类析构函数的执行顺序与派生类构造函数的执行顺序刚好相反
4.多重继承:
定义:
多继承下派生类的定义格式如下:
class <派生类名>:<继承方式1> <基类名1>,<继承方式2> <基类名2>,......
{
//<派生类新定义的成员>
};
多个基类名间用逗号隔开,继承方式同单继承。
多重继承的初始化:
多继承下派生类的构造函数成员初始化列表中应包含所有基类的构造函数,其定义格式:
<派生类构造函数名>(参数总表):<基类名1>(参数表1),<基类名2>(参数表2),…,<对象成员名>(参数表n)
多重继承派生类构造函数的执行顺序(析构函数相反):
所有基类的构造函数(基类间以定义派生类时顺序为准)
对象成员的构造函数
派生类的构造函数
*类比单继承理解
5.虚基类:
*C++继承机制:
不允许继承自身
不允许同时直接继承和间接继承同一个基类
*1/多继承的二义性:
二义性:无法确定性、不唯一性
A.多重继承中可能出现的两种二义性:
同名引起的二义性:当多重继承的派生类的不同父类中含有同名成员时,这些成员都会被派生类所继承,派生类在使用这些成员,就会产生二义性。
B.公共基类带来的二义性(重复继承,直接+间接)
同一数据被继承两次,导致的二义性
解决二义性方法:作用域运算符:: ,虚基类
*作用域运算符解决二义性的方法:
调用疑似同名的成员时,在成员名前缀上所属的类,具体哪个类的成员
**虚基类: 注:虚基类的特性只有在重复继承下才能体现。
虚基类使派生类从公共基类只继承一个数据成员,即从不同路径继承过来的同名数据成员和成员函数在内存中只有一个拷贝。
虚基类是对派生类进行声明,格式为:
Class 派生类名 : virtual 继承方式 基类名
{
……
};
虚基类的初始化:
与一般的多继承语法一样
注意:
(1)必须在最新派生出来的派生类的初始串列中,调用虚基类的构造函数,以初始化在虚基类中定义的数据成员;
(2)虚基类的构造函数由最新派生出来的派生类负责调用,其构造函数仅调用一次;
(3)初始串列中各个基类构造函数的调用顺序是:先调用虚基类构造函数,然后调用非虚基类构造函数;
(4) 如果在最新派生出来的派生类的初始串列中,没有显式调用虚基类构造函数,则编译程序将调用虚基类的缺省构造函数。
(5) 关键字virtual和派生方式关键字的先后顺序无关紧要,它只说明是“虚拟派生”
(6) 一个基类在作为某些派生类虚基类的同时,又作为另一些派生类的非虚基类,这种情况是允许存在的。
6.赋值兼容规则:
在一定条件下,不同类型的数据之间可以进行类型转换,例如可以将整形数据赋给双精度型变量,在赋值之前,先把整形数据转换为双精度数据,然后再把它赋给双精度变量。这种不同类型数据之间的自动转换和赋值,称为赋值兼容。
定义:用公有派生类对象替换基类对象;
理由:公有继承的派生类有基类中除构造函数和析构函数外的所有成员和相应的访问权限;
规则中的替换包括:
派生类的对象可以赋值给基类对象
派生类的对象可以初始化基类的引用
派生类对象的地址可以赋给指向基类的指针
例:class B{};
class D:public B{};
B b1,*ptr;
D d1;
下列操作是正确的: b1=d1;
B &b2=d1;
ptr=&d1;
注意:
1/声明为指向基类对象的指针可以指向它的公有派生的对象,但不允许指向它的私有派生对象
2/允许将一个声明为指向基类的指针指向其公有派生类的对象,但是不能将一个生命为指向派生类对象的指针指向其基类的一个对象。
第五章 多态性
2014年6月28日17:55:59
1.话说多态:
多态:
不同对象收到相同的消息时,产生不同的响应。
通俗地说:
同一个函数名具有不同的实现(如函数重载)
同一个运算符具有不同的功能(如运算符重载)
C++支持两种多态性:
编译时的多态性:静态联编--重载
运行时的多态性:动态联编--继承和虚函数
“同一接口,多种方法”
多态性的好处:实现了更高级、更自然的抽象
进一步减少了信息冗余
提高了程序的可重用性、可扩充性和可维护性
2.重载:
“重载”是面向对象程序设计的基本特点之一;
“重载”类似于自然语言中的“一词多义”;
在进行面向对象的程序设计时使用两种形式的重载:
函数重载
运算符重载
重载(Overloading):
函数重载是指在同一作用域内的若干个参数特征不同的函数可以使用相同的函数名字
运算符重载:
运算符重载是指同一个运算符可以施加于不同类型的操作数上面。
例如:11/4=2 和 11.0/4.0=2.75
这里运算符“/”的意义不同。
对于重载的运算符,也是在编译时,根据被操作数的类型,决定使用该运算符的哪种语义
3.运算符重载:
目的:使得运算符的功能,能够针对新的数据类型而进行改进
C++语言中运算符重载实际上是通过定义重载的运算符成员函数或友元函数来完成的。
通常把重载运算符的成员函数或友元函数,统称为运算符函数
运算符重载成员函数定义:
〈函数返回值类型〉 operator <运算符> ( 形参表) { … }
运算符重载友元函数定义:
friend <函数返回值类型> operator <运算符 >(形参表){ …}
说明:
<返回类型>为运算符函数运算结果的返回值类型;
operator为运算符重载时必须使用的关键字,它和被重载的运算符连在一起,作为运算符函数的专用函数名;
*运算符重载成员函数的形参:
1/单目运算符重载(++、--、!、~、-(负号))
前置单目(++i),参数表为空
void operator ++();
后置单目(++、--),参数表中有一个整型形参
void operator ++(int);
在这种情况下,当前对象(即调用该运算符函数的对象)作为该运算符唯一的操作数。如下调用(myX为X的对象):
++myX myX.operator++()
myX++ myXoperator++(0)
2/二元(二目)运算符重载
有一个参数。此时当前对象作为该运算符的左操作数,参数作为右操作数。
如前例:
复数对象c1,c2,c3有操作 c3=c1+c2 , 等价于
c3=c1.operator+(c2)
*运算符重载为类的友元函数:
class X{
friend <函数返回值类型> operator <运算符 >(形参表){ …}
}
1/在函数原型加关键字friend。
2/由于是友元函数,因此在类的公有段和私有段说明此函数的效果相同。
3/用友元函数重载运算符时参数表中参数个数,比用成员函数重载运算符时参数表中的参数个数多一个。
*运算符重载友元函数形参:
1、 单目运算符
1/ 前置单目:一个形参
形参为类类型,是此运算符的操作数
2/后置单目:两个形参
一形参为类类型,是此运算符的操作数;另一形参为int
2、 二元运算符:
两个参数,分别为左右操作数
例:复数自加运算c1++,相应的运算符重载友元函数可为
void operator++(Complex& o,int t)
{ o.real=o.real+1;
o.imag=o.imag+1;
}
*赋值运算符重载:
赋值运算符是双目运算符
可以使用缺省的赋值运算符实现类的赋值
如:Complex c1(20,10),c2;
c2=c1
表示将c1的数据成员逐个赋给c2的对应数据成员,即
c2.real=20;
c2.imag=10;
C++中 “=”, “&”, “,” 三种运算符,不必重载可直接使用
*对运算符重载的限制:
C++语言对运算符重载规定了下述一些限制:
(1) 只能重载C++语言中已有运算符;
(2) · ,* ,∷ ,?:四个运算符不能重载;
(3) 不能改变原运算符的操作数个数;
(4) 不能改变原运算符原有的优先级和结合特性;
(5) 不能改变原运算符对预定义类型数据的操作方式。
(6) =,→,(),[] 四个运算符只能为成员函数重载,不能为友元函数重载。
(7) >>,<<两个运算符只能为友元函数重载,不能为成员函数重载
(8)不可使用缺省参数
(9) 赋值运算符重载不能继承
*成员重载函数必须定义为公有,而友元重载函数访问控制权限可随意,因为它不属于所在的类
4.再谈指针悬挂的问题:
-*解决指针悬挂的方法:
1/运算符重载
通过重载赋值运算符可以解决指针悬挂问题;
C++规定:赋值运算符必须使用成员函数重载,且重载赋值运算符的成员函数operator=不能被继承。
解决上述问题的指导思想是:
(1)在赋值之前,先释放s1.p原先指向的内存空间;
(2)s1.p重新申请内存空间;
(3)内容传递:对目的对象s1的数据成员指针p(即s1.p)的赋值,是把源对象s2的数据成员指针p (即s2.p)所指向的内容传递给它,而不是简单地传递指针值;
2/使用拷贝函数
5.类型转换: 就是将一种类型转换为另一种类型值
(1)隐式类型转换: 当两个操作数类型不一致时:
int a;
double d = 13.5;
a = d;
cout << a << endl;
在编译第三句时出现警告:
warning : conversion from ‘ double’ to ‘ int’, possible loss of data
(2) 显式类型转换
显式类型转换有两种方式:
强制转换法 (类型名)表达式
函数法 类型名(表达式)
*类类型与系统预定义类型间的转换:
1/转换构造函数:
作用:将一个其他类型的数据转换成它所在类的对象
单参数的构造函数,具有将参数类型转换为该类类型的功能;
例:
class D
{ public:
D( ) { d=0; cout<<“缺省构造函数\n"; }
D( double i) { d=i; cout<<“单参数构造函数\n";}
void Print( ) { cout<<d<<endl; }
private:
double d;
};
void main()
{ D myD;
myD = 12; // 类型转换
myD.Print( );
}
特征: 1.单个参数的构造函数
2.将其他类型转换为类类型
3.类的构造函数只有一个参数是非常危险的,因为编译器可以使用这种构造函数把参数的类型隐式转换为类类型
4.
*带一个参数的构造函数: 1.普通构造函数(初始化)
2.转换构造函数(初始化 + 类型转换)
*explicit:
只提供给类的构造函数使用的关键字
作用;阻止隐式转换
编译器不会将声明为explicit的构造函数用于隐式转换,它只能在程序代码中显式创建对象,亦即作用仅限初始化
*话说初始化:
1.构造函数的初始化列表:〈推荐在该列表中进行初始化〉
构造函数的执行分为两个阶段; 1>.初始化阶段
2>.普通计算阶段
2.对象成员及其初始化:
若一个对象没有默认构造函数,则必须在初始化列表中进行初始化
3.const成员/引用成员的初始化:
1〉.常量必须在初始化列表中进行初始化(const成员)
2〉.引用成员的初始化也只能在初始化列表中进行初始化
3〉.对象成员(对象所对应的类没有默认构造函数,也只能在初始化列表中进行)
2/类型转换函数:
定义类类型转换函数的格式如下:
operator 目的类型( );
目的类型(即要转换成的类型)既可以是自定义类型也可以是预定义类型。
class Try
{ int a;
public:
Try(int a1){a=a1;}
operator double(){ return double(a); }
};
void main()
{ Try t(10);
double s1=4.5,d;
d=s1+t;
cout<<d<<endl;
}
注意: 1/类型转换函数只能定义为一个类的成员函数而不能定义为类的友元函数。类型转换函数也可以在类题中声明函数原型,而将函数体定义在类的外部
2/类型转换函数既没有参数,也没有在函数名前面指定函数类型
3/类型函数中必须有return语句,即必须送回目标类型的数据作为函数的返回值
4/一个类可以定义多个类型转换函数。C++编译器将根据类型转换函数名自动地选择一个合适的类型转换函数
6.虚函数:
虚函数是重载的另一种表现形式,这是一种动态的重载方式
虚函数允许函数调用与函数体之间的联系在运行时才建立,也就是在运行时才决定如何动作,即所谓的动态联编
1/引入:
在静态联编时,编译程序根据指针的声明(例如base *ptr),认定ptr只能指向对象中基类的空间,因此ptr只能找到并调用Base::who();
问题:ptr指向obj2对象时,如何通过ptr调用派生类中的who()函数?
2/定义:
虚函数的声明:virtual 函数原型
class Base
{ public:
virtual void who()
{ cout<<“Base"<<endl; }
};
class Derive : public Base
{ public:
void who()
{ cout<<“Derive”<<endl; }
};
void main(){
Base obj1, *ptr;
Derive obj2;
ptr = &obj1;
ptr->who();
ptr = &obj2;
ptr->who();
}
结果:
Base
Derive
3/虚函数改变了联编方式;
将成员函数声明为虚函数相当于告诉编译程序:由指针实际指向的对象类型决定调用哪个类中定义的函数
4/使用虚函数时的注意事项:
(1)在基类(不一定是最高)中声明虚函数,用虚函数实现多态性时,派生类应从基类公有派生;
(2)派生类中同型(与基类虚函数原型完全相同)的成员函数自动成为虚函数;
(3)只有非静态成员函数可以声明为虚函数;
(4)虚函数声明只出现在类声明中的函数原型中,而不能在成员的函数体实现的时候;
(5)虚函数由成员函数调用或通过指针、引用来访问;
5/虚析构函数:
* 构造函数可以重载
析构函数不能重载
构造函数不能虚化
析构函数可以虚化
class A{
public:
A( ){ }
~A( ) { cout<<"~A() is called!"<<endl; }
};
class B : public A
{ int *p;
public:
B( ) { p=new int[10]; }
~B( )
{ delete []p;
cout<<"~B() is called!"<<endl;
}
};
void main( )
{ A *a=new B; delete a; }
运行结果:~A() is called!
原因:指针a是基类指针,delete a只调用基类A的析构函数,不调用派生类B的析构函数,从而导致指针p指向的空间没有释放。
改进:
class A{
public:
A( ){ }
virtual ~A( ) { cout<<"~A() is called!"<<endl; }
};
class B : public A
{ int *p;
public:
B( ) { p=new int[10]; }
~B( )
{ delete []p;
cout<<"~B() is called!"<<endl;
}
};
void main( )
{ A *a=new B; delete a; }
程序执行动态联编的方式,在用delete运算符撤销派生类的无名对象时,先调用派生类的析构函数,再调用基类的析构函数,可以将基类的析构函数声明为虚析构函数。
6/纯虚函数和抽象类:(相当于java 中德抽象方法)
如果在基类中不能为虚函数给出一个有意义的定义,则可以将其说明为纯虚函数;
纯虚函数的定义格式如下:
virtual <返回类型> <函数名>(形参)=0;
例:virtual void GetArea()=0;
纯虚函数没有具体实现,即在定义它的基类中不给出它的具体实现,而在其派生类中必须提供它的实现代码;
纯虚函数是一种特殊的虚函数,它仅起到为派生类提供一个一致接口的作用;
注意与空的虚函数区别:
virtual void GetArea(){};
至少包含一个纯虚函数的类称为抽象类;
抽象类只能作为基类来派生新类,不能说明抽象类的对象,但可以说明指向抽象类对象的指针;
例:class Shape{
virtual void GetArea()=0;
};
Shape s1; Error!!
Shape *s2; Right!!
*注意:如果一个类至少有一个纯虚函数,那么就称这个类为抽象类
1〉.由于抽象类中至少包含有一个没有功能的纯虚函数,因此抽象类只能用作其他类的基类,不能建立抽象类对象
2〉.抽象类不能用作参数类型,函数返回类型或显式转换的类型,但可以声明指向抽象类的指针变量,此指针可以指向它的派生类,进而实现多态性
3〉.如果在抽象类的派生类中没有重新说明纯虚函数,则该函数在派生类中仍然为纯虚函数,这个派生类仍然还是一个抽象类。
第六章 模板与异常处理
2014年6月28日 20:15:37
1.模板:
模板是一种对类型进行参数化的工具。
通常有两种形式:函数模板和类模板
函数模板针对仅参数类型不同的函数;类模板针对仅数据成员和成员函数类型不同的类。
1/函数模板:
函数模板是对一组函数的抽象,定义函数模板的格式如下:
template <class T1,class T2,…>/〈typename 类型参数〉
<返回类型> 函数名(参数表)
{
<函数体>
}
注:class不是类,它是一个标志,表示其后为参数化的类型名。
与函数模板的参数表相匹配的函数调用称为一个模板函数。
在使用函数模板时,要先对模板参数实例化。
*注意:
1/在模板函数中允许使用多个类型参数,但是应当注意template定义部分的每个类型参数前必须有关键字typename(或class)
2/在template语句与函数模板定义语句之间不允许有别的语句。
3/模板函数类似于重载函数,只是更加严格,函数被重载的时候,在每个函数体内可以执行不同的操作,但同一函数模板实例化后的所有模板函数都必须执行相同的操作
4/同一般函数一样,函数模板也可以重载
5/函数模板与同名的非模板函数可以重载
*函数模板与重载函数一起使用:
调用的约定:
(1)先寻找函数模板。
(2)如找不到相应的函数模板则找重载函数。
(3)如再找不到重载函数则进行强制类型转换,此时可能丢失精度。
2/类模板:
类模板的定义格式:
template <class T1,class T2,…>/〈typename 类型参数〉
class 类名 {
…
};
类模板定义了一组类,这组类中数据成员的类型或某个成员函数的类型不局限于某一个具体类型。
类模板的使用:用下列方式代替类名:
类名 <具体类型名>
在类体外定义的成员函数,应定义成函数模板
注意:
倘若在模板类中的成员函数需要在类外定义,C++油哦一下规定: 1.需要在成员函数定义之前加上模板声明(就是类前的模板声明)
2.在成员函数名前缀上“类名〈类型参数〉 ::”
在每个类模板定义前都需要在前面加上模板声明
模板类可以有多个类型参数
2.异常处理:
程序运行中有些错误是可以预料但不可避免的,如内存空间不足、硬件上文件已被移动、打印机未连接好等系统运行环境造成的错误——运行环境错误;
有些函数本身无法排除错误,引发异常,由调用函数处理;
允许用户排除错误,继续运行程序;至少给出适当的提示信息,不能轻易出现死机,更不能出现灾难性后果——容错能力;
程序在运行过程中出现的错误统称为异常。
编程时充分考虑各种意外情况,并给予恰当的处理——异常处理。
传统的异常处理方法:采用判断、分支语句实现。
C++异常处理的方法是:
异常的判断和处理不在同一个函数中进行
在被调用函数中出现异常,就发出信息(异常的引发),传给它的上一级(调用函数) ,处理由调用函数来解决。如果调用函数处理不了,则异常可以一直向上传播,直到被解决或由C++运行系统处理(自动调用运行函数terminate,由它调用函数abort终止程序)
采用异常引发和处理相分离的机制
三部分组成:检查、抛出和捕获
三个子句:try、throw、catch
异常的抛出:(被调用函数)
throw 表达式; 某段程序出现了异常,抛给调用者
异常处理:(主钓函数)
Try 检查(引发)异常
复合语句 可能出现异常的语句放在try后
catch(异常类型声明) 捕获throw抛出的异常
复合语句 异常处理程序
catch(异常类型声明)
复合语句
……
注意: 1:抛掷异常与异常处理程序间是按数据类型的严格匹配来捕获的,如果程序中有多处要抛掷异常,则应用不同的操作数类型来相互区别,操作数的值不能用来区别不同的异常;
2:try后紧跟一个或多个catch块,目的是对发生的异常进行处理;
3:异常处理的目的是尽可能减少因错误而造成的破坏,并妥善处理它们,而不去影响其它部分程序的执行。
第七章 C++流类库与输入输出:
2014年6月28日 21:04:17
目的:把对象保存到磁盘文件中并从磁盘文件重建对象。
数据从一个对象到另一个对象的传送被抽象为“流”。数据的输入/输出就是通过输入/输出流来实现的。
1.C++的基本流类体系 :
流类体系:以抽象类模板basic_ios为基类,流类模板派生体系见图9.1。整个流类模板体系的标准I/O在头文件<iostream>中说明,包含头文件<ios>、<streambuf>、<istream>和<ostream>。而输入输出文件流部分在头文件<fstream>中说明。
流类体系说明:basic_streambuf不是basic_ios的派生类,而是一个独立的类,只是basic_ios有一个保护访问限制的指针指向它。 类basic_streambuf的作用是管理一个流的缓冲区。 标准输入/输出流对象:在C++的流类库中定义了四个全局流对象:cin,cout,cerr和clog。可以完成人机交互的功能。
cin标准输入流对象,键盘为其对应的标准设备。 cout标准输出流对象,显示器为标准设备。
cerr和clog标准错误输出流,输出设备是显示器。
其中cin、cout和clog是带缓冲区的,缓冲区由streambuf类对象来管理。而cerr为非缓冲区流,一旦错误发生立即显示。要使用这四个功能,必须包含<iostream>文件。 重载的提取运算符“>>”和插入运算符“<<”,执行输入/输出操作。 “提取”的含义是指输入操作,可看作从流中提取一个字符序列。 “插入”的含义是指输出操作,可看作向流中插入一个字符序列。 cin使用提取运算符。cout、cerr和clog使用插入运算符。
2. 输入/输出的格式控制 :
格式提供了许多功能:设置显示域宽、设置显示精度、设置域填充字符、刷新流、设置和清除格式化标志、插入换行符等。
C++在类ios中提供格式化输入输出。这些格式是对所有文本方式的输入输出流均适用。 输入输出流格式控制标志:
protected:
int x_precision; //标志浮点数精度,默认为6位
int x_width; //输出域宽,默认域宽为0,
//重设域宽只对其后第一输出项有效,如域宽不足,则不受限制 char x_fill; //标志域宽有富余时填入的字符
*************************************
常用流操作子表(*表示默认的流状态):
操作符 含义
boolapha 把true和false表示为字符串
*noboolalpha 把true和false表示为0、1
Showbase 产生前缀,指示数值的进制基数
*noshowbase 不产生进制基数前缀
showpoint 总是显示小数点
*noshowpoint 只有当小数部分存在时才显示小数点
showpos 在非负数值中显示“+”
*noshowpos 在非负数中不显示“+”
*skipws 输入操作符跳过空白字符
noshipws 输入操作符不跳过空白字符
uppercase 在十六进制下显示OX,科学计数法中显示E
*nouppercase 在十六进制下显示Ox,科学计数法中显示e
*dec 一十进制显示 hex 以十六进制显示 oct 以八进制显示
left 将填充字符加到数值的左边
Right 将填充字符加在数值的右边
Internal 将填充字符加到符号和数值的中间
*fixed 以小数形式显示浮点数
scientific 以科学计数法形式显示浮点数
flush 刷新ostream缓冲区
ends 插入字符串结束符,然后刷新ostream缓冲区
endl 插入换行符,然后刷新ostream缓冲区
以下这些参数化的流操作子要求#include<iomanip >
setfill(ch) 用ch填充空白字符
setprecision(n) 将浮点精度设置为n
setw(n) 按照n个字符来读或者写
setbase(b) 以b为进制基数输入整数值
*************************************
注意:绝大多数流操作子仅适用于新的C++标准流类库(头文件不带.h)。
3.标准设备的输入/输出 :
标准设备输入使用要点:
1/. cin为缓冲流。键盘输入的数据保存在缓冲区中,当要提取时,是从缓冲区中拿。如果一次输入过多,会留在那儿慢慢用,如果输入错了,必须在回车之前修改,如果回车键按下就无法挽回了。只有把输入缓冲区中的数据取完后,才要求输入新的数据。不可能用刷新来清除缓冲区,所以不能输错,也不能多输!
2/. 输入的数据类型必须与要提取的数据类型一致,否则出错。出错只是在流的状态字state(枚举类型io_state)中对应位置位(置1),程序继续。所以要提高稳健性,就必须在编程中加入对状态字state的判断。
3/. 空格和回车都可以作为数据之间的分格符,所以多个数据可以在一行输入,也可以分行输入。但如果是字符型和字符串,则空格(ASCII码为32)无法用cin输入,字符串中也不能有空格。回车符也无法读入。
4/ 输入数以后再输入字符或字符串:如果数后直接加回车,应该用cin.get()提取回车。如果还有空格,则要清空缓冲区。 输入流成员函数声明。
字符输入:int istream::get();//提取一个字符,包括空格,制表,backspace和回车等,
//与cin有所不同.注意返回为整型 istream&istream::get(char &);
istream&istream::get(unsigned char &); 提取一个字符,放在字符型变量中
get系列函数要求单独提取结束字符。getline提取字符串时如遇到指定结束符则提取该结束符,但不保存在串中。
其他函数:
函数gcount()返回最后一次提取的字符数量,包括回车:int istream::gcount();
函数ignore()读空(指定一个大的数量)缓冲区:istream&istream::ignore(int=1,int=EOF); 第一个参数为要提取的字符数量,默认为1;第二个参数为结束字符,提取该结束字符,但对所提取的字符不保存不处理,作用是空读。第二个参数的默认值EOF为文件结束标志。 在iostream中EOF定义为-1,在int get()函数中,读入输入流结束标志Ctrl+Z(^Z)时,函数返回EOF,为了能表示EOF的“-1”值,返回类型为int。采用cin.eof()函数,当前所读为EOF则返回非零,注意函数自身未从流中读取。 输出流成员函数声明:
ostream&ostream::put(char); //输出参数字符
ostream&ostream::put(unsigned char); ostream&ostream::put(signed char);
ostream&ostream::flush(); //刷新一个输出流,用于cout和clog
重载插入和提取运算符:重载必须保留原来的使用特性。重载只能在用户定义类中,将重载的运算符的函数说明为该类的友元函数:
friend istream&operator>>(istream&,className&); friend ostream&operator<<(ostream&,className&);
函数的返回值是对输入或输出流的引用,这是为了保证在cin和cout中可以连续使用“>>”或“<<”运算符,与所有“>>”和“<<”重载函数一致。第一个参数是输入或输出流的引用,作为“>>”或“<<”的左操作数;第二个参数为用户定义类的引用,作为右操作数,流用作函数参数,必须是引用调用,不能是传值调用。
5.文件的输入与输出 :
C++根据文件内容的数据格式,可分为两类:二进制文件和文本文件。文本文件由字符序列组成,也称ASCII码文件,在文本文件中存取的最小信息单位为字符,而二进制文件中存取的最小信息单位为字节。
C++把每一个文件都看成一个有序的字节流,每一个文件或者以文件结束符结束,或者在特定的字节号处结束。
当打开一个文件时,该文件就和某个流关联起来了。对文件进行读写实际上受到一个文件定位指针的控制。
输入流的指针也称为读指针,每一次提取操作将从读指针当前所指位置开始,每次提取操作自动将读指针向文件尾移动。 输出流指针也称写指针,每一次插入操作将从写指针当前位置开始,每次插入操作自动将写指针向文件尾移动。
文件使用步骤:
1/.说明一个文件流对象,这又被称为内部文件:
ifstream ifile;//只输入用
ofstream ofile;//只输出用
fstream iofile;//既输入又输出用
2/.使用文件流对象的成员函数打开一个磁盘文件。这样在文件流对象和磁盘文件名之间建立联系。文件流中说明了三个打开文件的成员函数。
void ifstream::open(const char*,int =ios::in,int=filebuf::openprot);
void ofstream::open(const char *,int=ios::out,int=filebuf::opernprot);
void fstream::open(const char*,int,int=filebuf::openprot);
第一个参数为要打开的磁盘文件名。第二个参数为打开方式,有输入(in),输出(out)等,打开方式在ios基类中定义为枚举类型。第三个参数为指定打开文件的保护方式,一般取默认。所以第二步可如下进行:iofile.open(“myfile.txt”,ios::in|ios::out);
文件打开方式:
是由在ios类中定义的公有枚举成员决定:
enum open_mode{ in=0x01, out=0x02, ate=0x04, app=0x08, trunc=0x10, binary=0x80 };
打开方式解释:
in标识打开文件用于输入操作(从文件读取)。打开方式只要含in,如文件不存在则返回失败。在打开为输入输出方式时(同时用out),编程应注意判断是否失败,失败时千万不可再写入文件。
out标识打开文件用于输出操作(写入文件)。如文件不存在,则建立新文件,如文件存在,未同时设app, in则文件清空。
trunc标识打开文件,并清空它(文件长度截为0)。文件不存在则建立新文件,与out默认操作相同。但与in配合,文件不存在则返回失败。
app标识打开文件用于输出,原文件内容保留,新数据接在尾部
ate意思是at end,标识打开文件,文件指针在文件尾,但文件指针可以移动,即新数据可写到任何位置。文件是否清空由其它标识决定。
以上三个标识最好配合out、in等一起用,因为不同的C++平台,要求不同,一起用不会出错。不一起用,至少VC++不认这种格式。
binary标识以二进制方式打开文件。同时用out时,如文件不存在,则建立新文件,并且新文件能用,不必清状态字。
打开文件也应该判断是否成功,若成功,文件流对象值为非零值,不成功为0(NULL),文件流对象值物理上就是指它的地址
3/.使用提取和插入运算符对文件进行读写操作,或使用成员函数进行读写,这在下一节中讨论。
4/.关闭文件。
三个文件流类各有一个关闭文件的成员函数 :
void ifstream::close();
void ofstream::close(); void fstream::close();
使用很方便,如:iofile.close();
关闭文件时,系统把该文件相关联的文件缓冲区中的数据写到文件中,保证文件的完整,收回与该文件相关的内存空间,可供再分配,把磁盘文件名与文件流对象之间的关联断开,可防止误操作修改了磁盘文件。如又要对文件操作必须重新打开。
关闭文件并没有取消文件流对象,该文件流对象又可与其他磁盘文件建立联系。文件流对象在程序结束时,或它的生命期结束时,由析构函数撤消。它同时释放内部分配的预留缓冲区。 文本文件的顺序读写:顺序读写可用C++的提取运算符(>>)和插入运算符(<<)进行。 资源获取是由构造函数实现,而资源释放是由析构函数完成。
文件结束判断:
读函数并不能知道文件是否结束,可用状态函数int ios::eof()来判断文件是否结束。必须指出系统是根据当前操作的实际情况设置状态位
*#*二进制文件优点:
可以控制字节长度,读写数据时不会出现二义性,可靠性高。同时不知格式是无法读取的,保密性好。文件结束后,系统不会再读(见eofbit的说明),但程序不会自动停下来,所以要判断文件中是否已没有数据。如写完数据后没有关闭文件,直接开始读,则必须把文件定位指针移到文件头。如关闭文件后重新打开,文件定位指针就在文件头。
文件的随机访问:在C++中可以由程序控制文件指针的移动,从而实现文件的随机访问,即可读写流中任意一段内容。一般文本文件很难准确定位,所以随机访问多用于二进制文件。 流的指针位置类型streampos和流的指针偏移类型streamoff定义为长整型,也就是可访问文件的最大长度为4G。
为了便于记忆,函数名中g是get的缩写,而p是put的缩写。对输入输出文件定位指针只有一个但函数有两组,这两组函数功能完全一样。
6.字符串流 :
字符串也可以看作字符流。可以用输入输出操作来完成串流的操作。串流与内存相关,所以也称内存流。串流类包括ostrstream、istrstream、strstream,它们在<strstrea.h>中说明。串流类对象可以保存字符,也可以保存整数、浮点数。串流类对象采用文本方式。 其构造函数常用下面几个: istrstream::istrstream(const char * str); istrstream::istrstream(const char * str,int); ostrstream::ostrstream(char *,int,int=ios::out);
strstream::strstream(char *,int,int);
其中第二个参数说明数组大小,第三参数为文件打开方式。
7.文件与对象 规范化操作:
在面向对象的程序设计中,信息总是放在对象的数据成员里。这些信息最终应该保存到文件中。当程序开始运行时要由打开的文件重新创建对象。在运行过程中,放在对象的数据成员里的信息得到利用和修改。运行结束时必须把这些信息重新保存到文件中,然后关闭文件。 在面向对象的C++程序设计中,文件应该在构造函数中打开,并创建对象;而在析构函数中保存和关闭文件,并撤销对象。
第八章 面向对象程序设计方法与实例
2014年6月28日 20:59:41
1.面向对象的开发过程包括:
面向对象的分析(OOA)
寻求问题域中的需求,建立对象模型
面向对象的设计(OOD)
对象的分解过程,设计逻辑模型与物理模型、静态模型与动态模型
面向对象的实现(OOP)
用计算机语言描述类及其关系,形成由相互联系的对象构成的程序
2.面向对象建模:
为什么要建立模型?
完全、彻底地理解问题
什么是模型?
对事物的一种抽象,用一组图示符号和相应的组织规则形式化的描述具体的事物。
目的:理解事物。
用面向对象方法开发软件,通常要建立三种形式的模型:
对象模型:描述系统数据结构
动态模型:描述系统控制结构
功能模型:描述系统功能
3.类间关系的表示:
类间、对象间的关系可以概括为继承关系、组合关系及关联关系三种关系
泛化关系(继承关系):“是一种”(IS_A)
“一般─特殊”关系,反映了一个类与若干个互不相容的子类之间的分类关系。
组合关系
组合关系就是“整体─部分”关系,它反映了对象之间的构成关系。组合关系也称为聚集关系。
在C++语言中,通常是在一个类中包含另一个类的对象成员来实现这种关系。
4.面向对象分析:
什么是面向对象分析?
抽取和整理用户需求并建立问题域精确模型的过程。
面向对象分析工作大体上按照下列顺序进行:
寻找类—对象
识别类间关系
确定属性
确定方法
5.面向对象设计:
把分析阶段得到的需求转变成符合成本和质量要求的、抽象的程序实现方案的过程。
是一个逐渐扩充模型的过程。
在实际的软件开发过程中分析和设计的界限是模糊的,许多分析结果可以直接映射成设计结果,而在设计过程中又往往会加深和补充对系统需求的理解,从而进一步完善分析结果。
OOD主要完成下述工作:
1.建立类等级
面向对象程序的一个突出优点来源于继承性。应该尽量抽取出相似类的公共属性和公共服务,以建立这些相似类的父类,并在类等级的适当层次中正确地定义各个属性和服务。
2.定义属性
所谓定义属性就是要确定每个属性的数据类型和数据结构,同时还要确定每个属性的访问权限(通常被定义在保护部分或私有部分)。
3.定义服务
6.面向对象设计原则:
1/模块化:对象就是模块。
2/抽象:类是一种抽象数据类型
3/信息隐藏:信息隐藏通过对象的封装性实现
4/弱耦合
耦合:一个软件结构内不同模块之间互连的紧密程度。
弱耦合是优秀设计的一个重要标准。
在面向对象方法中,对象是最基本的模块,因此,耦合主要指不同对象之间相互关联的紧密程度。
对象之间的耦合可分为两大类 :交互耦合、继承耦合。
交互耦合
即对象之间的耦合通过消息连接来实现,交互耦合应尽量松散。遵循的原则:
(1)尽量降低消息连接的复杂程度(参数个数和参数的复杂度)
(2)减少对象发送或接收的消息数
继承耦合
是一般化类与特殊类之间耦合的一种形式。越紧密越好。
5/强内聚
内聚衡量一个模块内各个元素彼此结合的紧密程度。
在设计时应该力求做到高内聚。
6/可重用
软件重用是提高软件开发生产率和目标系统质量的重要途径。
重用有两方面的含义:
(1)一是尽量使用已有的类(包括开发环境提供的类库,及以往开发类似系统时创建的类)
(2)二是如果确实需要创建新类,则在设计这些新类的协议时,应该考虑将来的可重复使用性。
7.面向对象的实现:
面向对象实现主要包括下述两项工作:
1. 把面向对象设计的结果翻译成用某种面 向对象程序语言书写的面向对象程序。
2. 测试和调试编写出的面向对象程序。
面向对象的语言:C++、Java等
注:程序的质量主要由设计的质量决定,但是所选用的程序语言的特点对程序质量也有重要影响。(应该将大量的时间花在系统分析与设计上)