C++笔记 第十四天 2007年4月10日
1、对文件的分类
(1)文本文件:每个字节都是有效的可显示的ASCII码 ,getline() , >>
(2)二进制文件:字节是连续的,不能用vi , more查看文件内容,read按字节数读取 , write
100 是整数的100,占4个字节
“100”是字符数组,占3个字节
2、异常
(1)人为错误:由于程序员编码不当
客观错误:不能避免的错误
(2)通过返回值判断程序的错误情况,对调用者的要求高,要写if()else()判断,而且对于返回值的含义要非常的清楚
所以C++中提供了异常处理机制
3 异常的工作原理:
1) 程序员写的代码在出现意外的地方自动产生一个异常,然后抛出一个异常对象。
2) 对象被传递到负责异常处理的地方。
throw 1; //抛出异常,让调用者处理
3) 由负责异常处理的代码进行统一的异常处理。 try{}catch(){}
4) 异常对象包含有意外发生的详细信息。
4 异常代码的格式:
抛出: throw 异常名字;
处理: ppt368
try {
} catch( 异常1 int) {
处理代码;
} catch( 异常2 const char* ) {
处理代码;
}
程序正常的时候,catch块什么也不做,当出现异常的时候,程序从产生异常的地方跳到catch块中
异常处理完毕之后,不会回到发生异常的地方。
用try{}catch(){}处理异常是一种强制手段,出现异常,进程结束
catch()中的异常类型如果与抛出的异常类型不一致,则捕获不到
5 每个catch只能处理一种类型的异常,catch块会依次执行。
6 catch(...){
处理代码;
}
可以捕获任意类型的异常,但是它不能判断是什么类型的异常,一般把它放在最后一个catch块。
但这种捕获没有针对性
7 异常的传播特性:
层级传播:异常会逐层抛出
产生异常之后,程序立即跳转到最近的一层捕获异常的语句,如果当前没有捕获语句,或者没有匹配的catch块,那么程序会跳出当前的函数回到调用的地方。
如果向上跳到了main函数,还是没有处理异常,程序就会终止进程。
8、封装异常(1)使用字符串描述异常
(2)制定异常号 error No.
(3)disp();
当捕获的异常存在继承关系的时候,要先捕获子类异常,再捕获父类异常
9、内联类
把一个类写到另一个类的内部
异常一般会做成内联类 A::exp ,靠前缀制定自己的命名空间
内联类,private修饰只能在本来中使用,对外是隐藏的,只有public 修饰的才能在外面使用
10、异常总结
(1)检查错误的一种手段
(2)可以用简单数据类型定义,还可以自定义类型
(3)产生 throw e; 异常对象
处理 try{}catch(异常类型){} --- 异常类型要与throw抛出的一样
(4)传播
11、银行项目 --- 面向对象
需求分析的时候,要画用力图
Biz Object 完成业务逻辑
Value Object 操作数据(Account)
Menu 收集用户信息,用户选择的业务
Biz 针对某项业务收集信息
DA 提供文件访问服务
Account 保存数据
1、在头文件中
#ifndef _ACCOUNT_ //预编译选项,表示如果没有定义这个宏
#define _ACCOUNT_ //创建以_ACCOUNT_命名的宏
并声明类
#endif
2、链表
(1)解决数组必须连续存储的问题
链表是可以不连续的,通过每个节点的指针连接
(2)节点中一部分空间用于存放数据,另一部分是一个指向下一个节点的指针
(3)每个节点都是一个结构
struct node{
int data; //存储数据
node* next; //指向下一个节点的指针,是自己这个结构的类型
}
(4)尾节点 --- 链表中的最后一个节点 --- 指针指向NULL
头节点 --- 要访问链表中的元素,必须要知道头节点的位置
把地址放在一个指针中 --- 头指针指向头节点,只是一个指针 --- 是必须存在的元素
(5)对链表的常见操作 --- 增删改查
(6)链表与数组的区别
数组:空间必须连续,数组是定长的,插入和删除需要遍历整个数组,效率不高。
取元素可直接使用下标,访问方便
链表:空间在内存中不必连续,通过指针连接
链表是不定长的,可以随时添加新节点,通过指针关联
对链表的插入删除,不需要移动节点位置,只对指针操作即可
访问元素,要从头指针开始遍历
当数据需要频繁的插入删除的时候,需要使用链表
当改动不大,查询频繁的时候,使用数组
潜规则 : 能用数组就不用链表
======================================================================
link.h
======================================================================
#ifndef _LINK_
#define _LINK_
using namespace std;
class Node{ //节点类
public :
int val; //保存数据
Node* next ; //保存下一个节点的地址
Node(){ //构造函数,把指针初始化为NULL
next = NULL;
}
};
class Link{
protected :
Node* head; //头指针
public :
Link();
~Link();
void insertTail(int);
void insertHead(int);
void del(int);
int indexOf(int); //查询一个元素的下标
void update(int , int);
void disp();
};
#endif
======================================================================
link.cc
======================================================================
#include "link.h"
#include <iostream>
using namespace std;
Link::Link(){
head = NULL;
}
Link:: ~Link(){//释放空间,从头向尾释放
if(head != NULL){
Node *p = head;
head = head->next; //把头节点向后移动
delete p; //抛弃原来的那个头节点
cout << "delete one ... " << endl;
}
}
//尾插入
void Link::insertTail(int v){
Node *p = new Node;
p->val = v;
if(head == NULL){
head = p; //让新节点的指针指向新节点,即把新节点的地址保存在头指针中
return ;
}
Node * temp = head ; //用一个临时指针,从头节点开始找到 尾
while(temp -> next != NULL){ //表示temp不是尾节点
temp = temp -> next ; //用temp后面的一个指针为自己赋值,即指向下一个节点
}
temp -> next = p; //尾插入,最后一个节点的指针保存新节点的地址
}
//头插入
void Link::insertHead(int v){
Node *p = new Node; //创建新节点
p->val = v ; //保存数据
p->next = head; //让新节点的指针和头指针一样指向第一个节点
head = p; //让头节点指向新节点
}
void Link::del(int v){ //找到被删除的节点 ,
if(head == NULL ){
return ;
}
if(head -> val == v){
Node *p = head;
head = head->next;
delete head;
}
Node *p1 = head->next; //找值相同的一个
Node *p2 = head ; //跟在p1后面
while(p1 != NULL){
if(p1->val == v){
p2->next = p1 -> next;
delete p1;
break;
}
p1 = p1->next;
p2 = p2->next;
}
}
int Link::indexOf(int v){ //查询一个元素的下标
Node * p = head ;
int counter = 0 ;
while( p != NULL ){
if( p->val == v ){
return counter ;
}
p=p->next ;
counter++ ;
}
return -1 ;
}
void Link::update(int v1 , int v2){
Node * p = head ;
while( p != NULL ){
if( p->val == v1 ){
p->val = v2 ;
}
p = p->next ;
}
}
void Link::disp(){
Node *p = head;
while(p != NULL){
cout << p->val << " " ;
p = p->next;
}
cout << endl;
}
3、二叉树
每个节点最多只有两个分支的树,它有一个根指针,要指向这棵树的根节点(最顶端的节点).
左子树上的值小于其父节点的值,右子树上的值都大于其父节点上的值。 --- 排序二叉树
(1)周游(遍历) :先序 --- 中左右
中序 --- 左中右
后序 --- 左右中
(2)非常方便查找
二叉查找树的常见操作:
1) 插入. 示例代码如下:
Node* Tree::_insert(int v, Node* r){ //真正实现插入操作,返回插入以后的根
if(r == NULL){ //是一棵空树 (空子树)
Node* p = new Node(v); //创建新节点
r = p; //让新节点成为根或者子节点
return r;
}
if( v < r->val){ //插到左子树上
r->left = _insert(v,r->left);
return r;
}else{ //插到右子树上
r->right = _insert(v,r->right);
return r;
}
}
2) 查找. 示例代码如下:
Node* & find( bnode* & root, const DATA& cd )
{
if( root==NULL ) // 如果root节点是空,则为空树
return root; // 返回root指向的地址,即NULL
else if( root->data==cd ) // 如果root节点就是要查找的数值
return root; // 返回root指向的地址,为了清晰,和上面的分开写
else if( cd < root->data ) // 如果root节点指向的值大于要查找的值
return find( root->left, cd ); // 返回查找root的左子树返回的地址
else
return find( root->right, cd ); // 否则返回查找root的右子树返回的地址
}
3) 删除. 示例代码如下:
被删除的是树根(1)则选择右子树的树根做新树根,左子树可以整个挂在右子树最左侧的一个左节点上
右子树中最左边的一个节点,是最靠近左子树的树根的
(2)让左子树中的最大节点做新树根
Node* _del( int value , Node* r ){
if( r == NULL ){ //删除空树
return r ;
}
if( r->value == value ){ //删除树根
if(r->left==r->right){ //左右子树都是NULL的情况下
delete r ;
return NULL;
}else if( r->right == NULL ){ //只有右子树,没有左子树的时候
Node * p = r;
r = r->left ;
delete p ;
return r ;
}else if( r->left == NULL ){ //只有右子树,没有左子树
Node *p = r ;
r=r->right ;
delete p ;
return r ;
}else{ //左右子树都有
Node * p1 = r -> right ;
Node * p2 = r -> right ;
while( p2->left != NULL ){
p2 = p2->left ;
}
p2->left = r->left ;
delete r ;
return p1 ;
}
}
if( value <= r->value ){
r->left = _del( value , r->left);
return r ;
}else{
r->right =_del( value, r->right );
return r ;
}
return r ;
}
作业:修改链表程序,能够删除全部相同元素;在指定位置后插入数据
1、算法
脱离具体的语言
有穷性 --- 在保证执行有限步骤之后确定能够结束
确切性 --- 每条语句具体干什么
输入输出 --- 所有的算法都有输出,打印屏幕,写文件,写DB
2、快速排序法
数据个数超过一个,任选其中一个数据作为分界值,把其他数据按大小关系分为2组,分界值在中间
对两组数据实行递归重组
//快速排序算法,效率最高的排序算法。第一个参数表示数组首地址,第二个参数表示数组起始位置,第三个参数表示结束位置
void mysort( int * p , int left , int right ){
int l = left ; //从左侧开始走
int r = right ; //从右侧开始走
int povit = p[(left + right)/2]; //把数组中间的一个数据作为分界点
do{
while( p[l]<povit && l < right ){ //循环退出,则是l找到一个比自己大的
l++ ;
}
while( p[r]>povit && r > left ){
r--;
}
if( l <= r ){
int t = p[l];
p[l] = p[r];
p[r] = t ;
l++;
r--;
}
}while( l <= r ); //条件就是左右的两个人还没有碰面
if( r > left ){ //只要右边的仍比左边的大,就要继续循环
mysort( p , left , r );
}
if( l < right ){ //只要左边的仍比右边的小,也要继续循环
mysort( p , l , right );
}
}
3、直接使用系统的qsort()函数
要自己定义一个排序规则
4、模版
(1)模版的参数至少出现一次,才能确定类型
(2)只能在紧跟的函数中使用,函数声明紧跟在后面
声明多个模版类型 template<class T1 , class T2>
class关键字不能省略
(3)对于模版类型的要求,要能重载">","<","="
建议:在编码时能用一种运算符完成的操作,就不要使用多个运算符,避免多个重载
(4)用模版写的函数叫函数模版
函数模版在调用的时候确定类型的
用模版写的类叫类模版
数据类型,参数类型,函数返回类型都可以使用模版
类模版不是类,是不完整的类
类模版要在声明时用类名<int>指定,确定类型
(5)C++的泛型(模版)是编译时确定类型的 --- 效率
Java的泛型是运行时的
(6)模版类的声明和定义(多文件结构)是不能分开的
模版函数的声明和定义是可以分开的 template<class T> 在头文件和实现文件中都要出现
5、STL包含三大类,容器类(可以存储其他对象的对象),算法(一系列封装好的函数),迭代器(用于遍历操作的类)
容器可以直接存储对象,也可以存储对象的指针。成熟的程序员喜欢使用间接存储。
容器主要包括两种类型:序列类(一般是线形存储)和关联类(一般是非线性存储)。
vector ---- 数组 可变长 不提供pop_front()删除头元素的函数
list ----- 链表
(1)Vector v[1000]当越界的时候,会出现段错误
v.at(1000) 越界的时候,会抛出out_of_range的异常,在程序中捕获
v.size() 返回长度,可利用这个循环迭代
v.empty()判断容器是否为空
Iterator迭代器 : 可以做取*操作 *iterator
iter->name <=> (*iter).name
iter++
v.begin() 指向数组的开始
v.end() 指向数组最后一个元素的后面,是一个结束标志
vector<int> v1;
vector<int>::iterator it; //iterator是vector的一个内部类
for( it = v1.begin(); it != v1.end(); it++ )
cout << *it << endl;
v.insert(iter,5); //在iter所指的元素前面插入5
v.insert(iter,5,100); //在iter所指的元素前插入5个100
这样的插入操作,会造成原来的iterator失效,对起重新赋值,可以保证继续使用
(2)list
不能做at()
多了push_front(),pop_front()
iter不能做加n操作
使用于做频繁的插入删除操作
6、关联式容器
(1)map
适合根据键查找值的操作
存储上按照键值排序 ,并且key值唯一
map<int,Student> m;
Student s( 1 ,"liucy" );
m.insert( map<int,Student>::value_type(
s.getId() , s ) ) ; //创建一个pair,并存到map的第一个位置中 value_type是map的静态函数
Student s2( 4, "tangliang" );
m.insert( map<int,Student>::value_type(
s2.getId() , s ) ) ;
map<int,Student>::iterator it ;
for(it=m.begin();it!=m.end();it++ ){
cout<< it->first << " "<<it->second;
cout<<endl ;
}
在map中用[]查询,并不安全
m.find(1); // 查询key为1的value
返回一个iter,指向找到的那个键值对,如果没找到,iter会与iter.end()的值相等
(2)multimap
其中的key允许重复
查找:multimap<int ,Student>::iterator it ;
multimap<int ,Student>::iterator lt ;
multimap<int ,Student>::iterator ut ;
lt = m.lower_bound( 1 );
ut = m.upper_bound( 1 );
for( it=lt ; it != ut ; it++ ){
cout<<it->first <<" " ;
cout<<it->second <<endl;
}
(3)set
set中不能插入重复数据,相当于map中的key
插入数据的时候不必指定位置
因为与map中的key一致,仍保留着排序的特性
(4) multiset
与vector类似,唯一不同的就是保留着排序的特性
7、模版的声明和实现都写在头文件中
/usr/local/include/c++/3.2/