面向对象程序设计

目录

第二章 类和对象之初体验

类的定义

类的格式定义

//说明部分
class<类名>{
	public:
	<成员函数和数据成员的说明或实现>
	private:
	<数据成员和成员函数的说明或实现>
};

//实现部分
<函数类型><类名>::<成员函数名>(<参数表>){
	<函数体>
}

例子:
定义时钟类:

class Clock{
	public:
	void SetTime(int NewH,int NewM,int NewS);
	void ShowTime();
	private:
	int Hour,Minute,Second;//在类体中不允许对数据成员初始化
};

对象的定义

<类名><对象名表>;

//事例
Clock myClock,*pc,clocks[30];
Clock &cl=myClock;
对象成员的表示方法
  1. 一般对象的成员表示用运算符“.”
<对象名>.<数据成员名>
<对象名>.<成员函数名>(<参数表>)

Clock myClock;
myClock.Hour
myClock.SetTime(10,12,14)
  1. 指向对象指针的成员表示用运算“->”
<对象指针名>-><数据成员名>
<对象指针名>-><成员函数名>(<参数表>)

Clock *pC=&myClock;
pC->Hour
  1. 对象引用的成员表示用运算符“.”
<对象引用名>.<数据成员名>
<对象引用名>.<成员函数名>(<参数表>)

Clock &cl=myClock;
cl.Hour
cl.SetTime(10,12,14)
  1. 对象数组元素的成员表示同一般对象
<数组名>[<下标>].<成员名>

Clock cl[3];
cl[0].Hour
cl[0].SetTime(10,12,14)

对象的初始化

构造函数和析构函数
  • 构造函数:在创建对象时,用给定的值对对象进行初始化
  • 析构函数:释放一个对象
class Clock{
	public:
	Clock(int NewH,int NewM,int NewS);//构造函数
	~Clock();//析构函数
	void SetTime(int NewH,int NewM,int NewS);
	void ShowTime();
	private:
	int Hour,Minute,Second;
};
//构造函数的实现:
Clock::Clock(int NewH,int NewM,int NewS){
	Hour=NewH;
	Minute=NewM;
	Second=NewS;
	cout<<"Constructor called.\n";
}
Clock::~Clock(){
	cout<<"Destructor called.\n";
}
拷贝构造函数

用已知对象初始化创建另一对象时所用的构造函数。

class Point{
	public:
	Point(int xx=0,int yy=0){X=xx;Y=yy;}
	Point(Point& p);
	int GetX(){return X;}
	int GetY(){return Y;}
	private:
	int X,Y;
};
Point::Point(Point& p){
	X=p.X;Y=p.Y;
	cout<<"拷贝构造函数被调用"<<endl;
}
int main(){
	Point A(1,2);
	Point B(A);//拷贝构造函数被调用
	cout<<B.GetX()<<endl;
}

成员函数的特性

内联函数
  • 内联函数是一种函数体被替换,而不是被调用的函数。(为了提高运行效率)
  • 内联函数内不要有复杂结构(循环、switch)
  • 使用inline关键字
#include<iostream.h>
class M{
	public:
	M(int i,int j){
		a=i;b=j;
	}
	int fun1(){return a;}
	int fun2(){return b;}
	int fun3(),fun4();
	private:
		int a,b;
};
inline int M::fun3(){return fun1()+fun2();}
inline int M::fun4(){return fun3();}
void main(){
	M m(5,8);
	int n=m.fun4();
	cout<<n<<endl;
}
重载性

成员函数可以重载,重载时应遵循参数可以区别的规则。

  • 参数的类型
  • 参数的个数
设置参数的默认值
  • 成员函数的参数可以设置默认值
  • 指定了默认参数的右边,不能出现没有指定默认值的参数

静态成员

静态成员不是属于某个对象的,而是属于整个类的,即所有对象的

静态数据成员
  1. 说明方法:
class M{
	int a,b,c;
	static int s;
	...
};
  1. 静态数据成员的初始化及访问方式
在类体外初始化:
<数据类型><类名>::<数据成员名>=<初值>;
int M::s=0;

//访问方式:
<类名>::<静态数据成员名>
M::s
  1. 静态数据成员被存放在静态存储区,必须初始化
静态成员函数

定义:

static <类型><成员函数名>(<参数表>)

引用:

<类名>::<静态成员函数名>(<参数表>)
<对象名>.<静态成员函数>(<参数表>)

在静态成员函数中可以直接引用其静态成员,而引用非静态成员时需用对象名引用。

class A{
	public:
		static void f(A,a);//静态成员函数
	private:
		int x;//非静态成员变量
};
void A::f(A,a){
count<<x;//对x的引用是错误的
cout<<a.x;//正确
}

友元

定义:友元是C++提供的一种破坏数据封装和数据隐藏的机制。

  • 通过将一个模块声明为另一个模块的友元,一个模块能够引用到另一个模块中本是隐藏的信息。
友元函数

友元函数可以访问类中的私有成员和其他成员

friend <类型><函数名>(<参数表>)
友元类

若一个类为另一个类的友元,则此类的所有成员都能访问对方类的私有成员。

friend class <类名>;

注意事项

  • 友元关系是不可逆的
  • 友元关系是不可传递的

第三章 类和对象再讨论

对象指针和对象引用

指向类的成员的指针

通过指向成员的指针只能访问公有成员

类型说明符 类名::*指针名;
类型说明符 (类名::*指针名)(参数表);

1. 指向数据成员的指针
初始化:

int A::*pc=&A::c;

通过对象名(或对象指针)与成员指针结合来访问数据成员

A a;
a.*pc=8;
A *p;
p->*pc=8;

2. 指向函数成员的指针
初始化:

int (A::*pfun)(int)=A::fun;

通过对象名(或对象指针)与成员指针结合来访问函数成员

A a;a.*pfun(9);
A *p;p->*pfun(9);
对象指针和对象引用作函数参数
  • 对象指针作为函数参数: 实现传址调用
  • 对象引用作函数参数: 实现传址调用,比指针更简单、更直接
class M{
	public:
		M(){x=y=0;}
		M(int i,int j){x=i;y=j;}
		void copy(M &m);
		void setxy(int i,int j){x=i;y=j;}
		void print(){cout<<x<<","<<y<<endl;}
	private:
		int x,y;
};
void M::copy(M &m){x=m.x;y=m.y;}
void fun(M m1,M &m2){m1.setxy(12,15);m2.setxy(22,25);}
void main(){
	M p(5,7),q;
	q.copy(p);
	fun(p,q);
	p.print();
	q.print();
}
this指针

隐含于每一个类的成员函数中的特殊指针
当通过一个对象调用成员函数时,系统先将该对象的地址赋值给this指针,然后调用成员函数。

#include<iostream.h>
class A{
	public:
		A(int i,int j){a=i;b=j;}//相当于this->a=i;this->b=j;
		A(){a=b=0;}
		void Copy(A &a);
		void Print(){cout<<a<<','<<b<<endl;}
	private:
		int a,b;
};
void A::Copy(A &a){
	if(this==&a)return;
	*this=a;
}
int main(){
	A a1,a2(1,5);
	a1.Copy(a2);
	a1.Print();
	return 0;
}

对象数组和对象指针数组

对象数组

声明:类名 数组名[元素个数];
访问方法:下标 数组名[下标].成员名
初始化:调用构造函数 Point A[2]={Point(1,2),Point(3,4)}; (删除时调用析构函数)

  • 各元素对象的初值要求为相同的值时,可以声明具有默认形参值的构造函数。
#include <string.h>
class Student{
    char name[20];
    long int stuno;
    int score;
    public:
        Student(char name1[]="",long int no=0,int sco=0)
        { // 默认形参值的构造函数
            strcpy(name,name1);
            stuno=no;
            score=sco;
        }
        void Setscore(int n){score=n;}
        void Print(){cout<<stuno<<'\t'<<name<<'\t'<<score<<endl;}
};
 void main(){
    Student stu[5]={
	    Student("Ma",5019001,94),
		Student("Hu",5019002,95),
		Student("Li",5019003,88)
	};
    stu[3] = Student("Zhu",5019004,85);
    stu[4] = Student("Lu",5019005,90);
    stu[1].Setscore(98);
    for(int i(0);i<5;i++)
        stu[i].Print();
}
指向数组的指针和对象指针数组
  1. 指向数组的指针
    声明:类名 (*指针名)[大小]
  2. 指针数组
  • 数组的元素是指向对象的指针,并要求所有数组元素都是指向相同类的对象的指针
    声明类名 *对象指针数组名[大小]
#include <iostream>
using namespace std;

class A {
	public:
		A(int i = 0, int j = 0) {
			x = i;
			y = j;
		}

		void Print() {
			cout << x << ',' << y << endl;
		}

	private:
		int x, y;
};

void main() {
	A a1, a2(5, 8), a3(2, 5), a4(8, 4);
	A *array1[4] = {&a4, &a3, &a2, &a1};
	for (int i(0); i < 4; i++)
		array1[i]->Print();
	cout << endl;
	A *array2[4];
	array2[0] = &a1;
	array2[1] = &a2;
	array2[2] = &a3;
	array2[3] = &a4;
	for (i = 0; i < 4; i++)
		array2[i]->Print();
}

常类型

  1. 常对象
const <类名><对象名>(<初值>)
<类名> const<对象名>(<初值>)
  1. 常指针
  • 地址值为常量的指针
    <类型> *const <指针名>=<初值>
  • 所指向的值为常量的指针
    const <类型> *<指针名>=<常量>
  1. 常引用
    const <类型> &<引用名>=<初值>
#include<iostream.h>
void main(  )  {
	int a=0,  b=0;
 	int  &d = a; // 引用
	const  int  &c = a;  // 常引用
	c = 1; // 非法
	a = 1; // 合法
	d = 1; // 合法

	int  *const  p1 = &a;  // 地址值为常量的指针
	const  int  *p2 = &a;  // 所指向值为常量的指针
	p1 = &c; // 非法
	p2 = &b;
	*p2 = 1; // 非法
	*p1 = 1; // 合法

	cout << a << b << c << d << *p2 <<endl;
}
  1. 常成员函数
    <类型><成员函数名>(<参数表>) const{<函数体>}
  • 常对象只能调用常成员函数,不能调用非常成员函数
#include <iostream>
using namespace std;

class B {
	public:
		B(int i,  int j) {
			b1 = i;
			b2 = j;
		}
		void Print( ) {
			cout << b1 << ';' << b2 << endl;
		}
		void Print( ) const {
			cout << b2 << ':' << b1 << endl;
		}
	private:
		int b1, b2;
};

int main(  )  {
	B b1(5, 10);
	b1.Print();
	const B b2(2, 8);
	b2.Print();
	return 0;
}
  1. 常数据成员
    const <类型> <常数据成员名>
  • 常数据成员初始化是通过构造函数的成员初始列表来实现的
  • 构造函数的成员初始列表的格式:
    <构造函数名>(<参数表>):<成员初始化列表>{<函数体>}
#include <iostream>
using namespace std;

class A {
	public:
		A(int i);
		void Print() {
			cout << a << ',' << b
			     << ',' << r << endl;
		}
		const  int  &r;
	private:
		const  int  a;
		static const  int  b;
};
const  int  A::b = 15;
//静态成员初始化

A::A( int i ) : a( i ), r( a ) {   }
//常数据成员初始化

int main(  )  {
	A a1(10), a2(20);
	a1.Print();
	a2.Print();
	return 0;
}

子对象和堆对象

子对象
  • 定义:在一个类中可以使用另一个类的对象作其数据成员,这种对象的数据成员称为子对象。
  • 初始化:子对象初始化应放在构造函数的成员初始化列表中。
#include <iostream>
using namespace std;

class B {
	public:
		B(int i, int j) {
			b1 = i;
			b2 = j;
		}
		void Print( ) {
			cout << b1 << ',' << b2 << endl;
		}
	private:
		int b1, b2;
};

class A {
	public:
		A(int i, int j, int k) : b( i, j ) {
			a = k;     //子对象初始化
		}
		void Print( ) {
			b.Print();
			cout << a << endl;
		}
	private:
		B b;
		int a;
};

int main(  )  {
	B b(7, 8);
	b.Print();
	A a(4, 5, 6);
	a.Print();
	return 0;
}

堆对象(动态对象)
  1. 使用new运算符创建堆对象
  • 创建一个对象或其他类型变量
    new <类名>或者<类型说明符>(<初值>);
A  * pa;
pa = new A(3,5);
int * p;
p = new int(8);
  • 创建一个对象数组或其他类型数组
    new <类名>或者<类型说明符>[<大小>];
A * parray;
parray = new A[10];
//对象数组创建后可使用如下语句,判断创建是否成功:
if( parray= =NULL ){
    cout<<"数组创建失败!/n";
    exit(1)}
  1. 使用delete运算符释放对象
  • 对象或变量:delete <指针名>
  • 对象数组或其他类型数组:delete []<指针名>
#include <iostream>
using namespace std;

class B {
	public:
		B( ) {
			strcpy(name, " ");
			b = 0;
			cout << "Default constructor called.\n";
		}
		B(char *s,  double d)  {
			strcpy(name, s);
			b = d;
			cout << "Constructor called.\n";
		}
		~B( ) {
			cout << "Destructor called." << name << endl;
		}
		void GetB( char *s, double &d)  {
			strcpy(s, name);
			d = b;
		}
	private:
		char name[20];
		double b;
};


int main(  )  {
	B *pb;
	double d;
	char s[20];
	pb = new B[4];
	pb[0] = B("Ma", 3.5);
	pb[1] = B("Hu", 5.8);
	pb[2] = B("Gao", 7.2);
	pb[3] = B("Li", 9.4);
	for (int i = 0; i < 4; i++) {
		pb[i].GetB(s, d);
		cout << s << ',' << d << endl;
	}
	delete [ ]pb ;
	return 0;
}

类型转换

类型的隐含转换
  • 算术运算:低类型->高类型
  • 赋值表达式:等号右边类型->等号左边类型
构造函数具有类型转换功能
#include <iostream>
using namespace std;

class D {
	public:
		D( ) {
			d = 0;
		}
		D(double i) {
			d = i;
		}

		void Print( ) {
			cout << d << endl;
		}

	private:
		double d;
};

int main(  )  {
	D d;
	d = 20;
	d.Print( ) ;
	return 0;
}

类型转换函数

operator <数据类型说明符>(){<函数体>}

#include <iostream>
using namespace std;

class E {
	public:
		E(int i, int j ) {
			den = i;
			num = j;
		}
		operator double( );
	private:
		double den, num;
};

E::operator double( ) {
	return double(den) / double(num);
}

int main(  )  {
	E e(6, 10);
	double a(3.5);
	a += e - 2;
	cout << a << endl;
	return 0;
}

第四章 运算符重载

4.1 运算符重载的基本语法

  1. 运算符函数
    operator@,@表示要重载的运算符
#include<iostream>

class MinInt{
	char b;
	public:
	MinInt(char ch=0):b(ch){}
	MinInt operator-()const{//一元运算符
		cout<<"MinInt::operator-"<<endl;
		return MinInt(-b);
	}
	MinInt operator+(const MinInt& rv)const{//二元运算符
		cout<<"MinInt::operator+"<<endl;
		return MinInt(b+rv.b);
	}
	MinInt& operator+=(const MinInt& rv){//复合赋值运算符
		cout<<"MinInt::operator+="<<endl;
		b+=rv.b;
		return *this;
	}
};
  1. 运算符重载的限制
  • ::(作用域解析符)、.(成员选择符)、.*(成员指针间接引用符)、?:(条件运算符) 不能被重载
  • 不能定义c++中没有的运算符
  • 重载运算符不能改变优先级和结合性
  • 重载运算符不能改变操作数个数
  • 可以重载的运算符
+-*/%^&
|~=<>
<=>=++<<>>==
!=&&||+=-=/=*=
%=^=&=|=>>=<<=[]
()->->*newdeletenew[]delete[]

4.2 常用运算符的重载

(1)一元运算符
class Byte{
	unsigned char b;
	public:
	Byte(unsigned char bb=0):b(bb){}
	//无副作用的运算符定义为const成员函数
	const Byte& operator+()const{//正号
		return *this;
	}
	const Byte operator-()const{//负号
		return Byte(-b);
	}
	Byte operator!()const{//逻辑非
		return Byte(!b);
	}
	Byte* operator&()const{//取地址
		return this;
	}
	//有副作用的运算符定义为非const成员函数
	const Byte& operator++()const{//前缀++
		b++;
		return *this;
	}
	const Byte operator++(int)const{//后缀++
		Byte before(b);
		b++;
		return before;
	}
};

用全局友元函数重载一元运算符

class Integer{
	long i;
	Integer* This(){
		return this;
	}
	public:
	Integer(long ll=0):i(ll){}
	friend Integer* operator&(const Integer& a);
	friend const Integer& operator++(const Integer& a);
	friend const Integer operator++(const Integer& a,int);
};

//全局运算符函数的定义
Integer* operator&(Integer& a){
	return a.This();
}
const Integer& operator++(Integer& a){//前缀++
		a.i++;
		return a;
}
const Integer& operator++(Integer& a,int){//后缀++
		Integer before(a.i);
		a.i++;
		return before;
}

自增自减运算符

  • 前缀形式:返回改变后的对象*this
  • 后缀形式:返回改变之前的值。编译器看到++a.i会调用Integer::operator++(int),而b++会调用Integer::operator++()
(2)二元运算符
class Byte {
		unsigned char b;
	public:
		Byte(unsigned char bb = 0): b(bb) {}
		const Byte operator+(const Byte &right)const { //正号
			return Byte(b + right.b);
		}
		const Byte operator%(const Byte &right)const {
			assert(right.b != 0);
			return Byte(b % right.b);
		}
		//位运算符 ^,&,|,<<,>>
		const Byte operator^(const Byte &right) const {
			return Byte(b ^ right.b);
		}
		Byte &operator=(const Byte &right) { // 只能用成员函数重载
			if (this == &right)
				return *this; // 自赋值检测
			b = right.b;
			return *this;
		}
		//复合赋值运算符有:+=,-=,*=,/=,%=,^=,&=,|=,<<=,>>=
		Byte &operator/=(const Byte &right) {
			assert(right.b != 0);
			b /= right.b;
			return *this;
		}
		//关系运算符有 ==, !=, <,<=,>,>=
		bool operator==(const Byte &right) const {
			return b == right.b;
		}
		//二元逻辑运算符&&和||
		bool operator&&(const Byte &right) const {
			return b && right.b;
		}
}; //end of class Byte

用全局友元函数重载二元运算符

  • 全局函数重载二元运算符时,要带两个参数,其中至少有一个是类类型的。
  • 第一个参数作为左操作数;第二个参数是右操作数。
    注意,赋值运算符operator= 只能用成员函数重载。
class Integer {
		long i;
	public:
		Integer(long ll = 0) : i(ll) {}
		friend const Integer operator+(const Integer &left, const Integer &right);

		friend const Integer operator^(const Integer &left, const Integer &right);
		// 修改并返回左值的复合赋值运算符,第一个参数是非const引用,即左值
		friend Integer &operator+=(Integer &left, const Integer &right);
		// 逻辑运算符和关系运算符返回bool值,不改变操作数
		friend bool operator==(const Integer &left, const Integer &right);
};

// 复合赋值运算符,此处只给出了+=的实现,其余实现类似,略
Integer &operator+=(Integer &left, const Integer &right) {
	if (&left == &right) {/* self-assignment */}
	left.i += right.i;
	return left;
}

(3)运算符函数参数/返回类型
  • 返回值的类型取决于运算符的具体含义。如果使用运算符的结果是产生一个新值,就需要产生一个作为返回值的新对象,这个对象作为一个常量通过传值方式返回。如果函数返回的是原有对象,则通常以引用方式返回,根据是否希望对返回的值进行运算来决定是否返回const引用
  • 所有赋值运算符均改变左值。为了使赋值结果能用于链式表达式,如a=b=c,应该返回一个改变了的左值的引用。一般赋值运算符的返回值是非const引用,以便能够对刚刚赋值的对象进行运算。
    返回值优化
    临时对象语法return Integer(left.i+right.i);
  • 当编译器看到这种语法时,会明白创建这个对象的目的只是返回它,所以编译器直接把这个对象创建在外部返回值的存储单元中,所以只需要调用一次构造函数,不需要拷贝构造函数和析构函数的调用。因此,使用临时对象语法的效率非常高,这被称为返回值优化。
(4)全局运算符和成员运算符

使用成员运算符的限制是左操作数必须是当前类的对象,左操作数不能进行自动类型转换,而全局运算符为两个操作数都提供了转换的可能性。因此,如果左操作数是其他类的对象,或是希望运算符的两个操作数都能进行类型转换,则使用全局函数重载运算符。

class Number {
  int i;
public:
  Number(int ii = 0) : i(ii) {}
  const Number operator+(const Number& n) const { //成员
    return Number(i + n.i);} 
friend const Number operator-(const Number&, const Number&);
};
const Number operator-(const Number& n1, const Number& n2){ //友元
    return Number(n1.i - n2.i);
}
int main() {
  Number a(47), b(11);
  a + b; // OK
  a + 1; // 右操作数转换为Number
  1 + a; // 错误:左操作数不是Number类型
  a - b; // OK
  a - 1; //右操作数转换为Number
  1 - a; //左操作数转换为Number
} 
(5)重载输入/输出运算符
  • operator>> 带两个操作数,左操作数cin是istream 类型的对象,而右操作数是接收输入数据的变量。输入操作会引起两个操作数的改变,因而,这两个参数需要传递非const引用。
  • operator>> 可以用于链式表达式,如 cin>>b>>c。这等价于: cin>>b; cin>>c;
#include <iostream.h>
class complex{
	private:
	  	double real, image;
	public:
		complex(double r = 0, double i = 0){
    		real = r; image = i; 
    	} 
		//成员函数重载运算符+
		const complex operator+(const complex& right)  const{
    			return complex (real+right.real,image+right.image);  }
		//全局函数重载输入输出运算符
		friend ostream& operator<<(ostream& os, const complex& c);
		friend istream& operator>>(istream& is,complex& c);
};
//输入输出运算符函数的定义
ostream& operator<<(ostream& os, const complex& c){
   	if(c.real==0 && c.image==0){ os << "0"; }
   	if(c.real!=0){ os << c.real; }
   	if(c.image!=0){
		if(c.image>0 && c.real!=0)
				os << "+";
		os << c.image << "i" ;
	 }
   	return os; //返回ostream对象便于链式表达式
}

istream& operator>>(istream& is, complex& c){
	cout<<"please input a complex:";
	return is>>c.real>>c.image;
}

int main() {
	complex c1,c2;
	cin>>c1;
	cin>>c2;
	cout<<c1+c2<<endl; //调用全局函数operator<<
}

4.3 重载赋值运算符operator=

  • 没有定义赋值运算符函数时,编译器会为生成一个默认的赋值运算符函数。
  • 类型的对象在初始化时调用构造函数,而赋值时调用operator=
  • 赋值运算符的左侧操作数是已经存在的对象时,才会调用operator=
  • 赋值运算符必须作为成员函数重载,不能使用全局函数重载operator=
void f( ){
  	int m = 10;//初始化
  	int n;	 //定义变量,但没有初始化
  	n = 5;//赋值,虽是首次赋值,但不是初始化
  	n = m;	 //赋值
  	
	MyType b;	 //调用缺省构造函数
  	MyType a = b;//创建并初始化a,等价MyTypea(b);
  	//调用拷贝构造函数,而不是operator=
	a = b;//a是已经存在的对象,调用operator=
}

赋值检测

在对象赋值之前应该进行自赋值检测:检验对象是否在给自身赋值。

//一个简单的字符串类
class my_string{
	char* str;
	int len;
public:
	my_string(const char* s = ""){
    	len = strlen(s);
    	str = new char[len + 1];
    	strcpy(str, s);
	}
	~my_string(){delete[]str;}
	//…其他构造函数和成员函数略
	my_string& operator=(const my_string& s);
};
 
my_string a("abcde"),  b("hijk");
a = b;		//如何赋值?

直接赋值方式:

my_string& my_string::operator= (const my_string& s) {
    len = s.len;
    str = s.str;
    return *this;
}

导致的问题:

  1. 原来的a.str指向的动态存储空间没有释放,造成内存泄漏。
  2. a和b的str指向了同一段存储空间,破坏了对象的完整性,即对a和b中一个字符串操作,另一个将受到影响。
    正确操作:
my_string& my_string::operator= (const my_string& s) {  
   	//先释放当前对象中的动态存储空间
    	delete[] str;
   	//再重新分配空间
   	len = s.len;
    str = new char[len + 1];
   	//最后进行字符串的拷贝
    strcpy(str, s.str);
    return *this;
}

若对正确操作执行a=a语句,则会出现错误,这是因为没有在operator=中进行自赋值检测。

my_string& my_string::operator= (const my_string& s) {    
	//赋值之前先进行自赋值检测
    if(this == &s)
	    return *this;
    delete[] str;
    len = s.len;
    str = new char[len + 1];
    strcpy(str, s.str);
    return *this;
}

4.4 重载下标运算符operator[]

  • 下标运算符operator[]必须是成员函数,它只接收一个参数,通常是整值类型。
  • 下标运算符作用的对象应该能像数组一样操作,所以经常用该运算符返回一个元素的引用,以便用作左值。
class vect {
public:
	//构造函数和析构函数
    	explicit vect(int n = 10);  
    	vect(const vect& v); 
    	vect(const int a[], int n); 
    	~vect() { delete []p; }

	//其他成员函数
    	int& operator[](int i);	// 重载下标运算
    	int ub() const { return (size - 1); }
    	vect& operator=(const vect& v);

private:
    	int* p;
    	int  size;
};
//成员函数类外定义
vect::vect(int n) : size(n){
    		assert(size > 0);
    		p = new int[size];
 }
vect::vect(const int a[], int n) : size(n){
    		assert(size > 0);
    		p = new int[size];
    		for(int i=0; i<size; ++i)
        		p[i] = a[i];
}
vect::vect(const vect& v) : size(v.size){
    		p = new int[size];
    		for(int i=0; i<size; ++i)
        		p[i] = v.p[i];
}
int& vect::operator[](int i){
    assert( i>=0 && i<size);
    return p[i];	//返回的是左值
} 
vect& vect::operator=(const vect& v){
    if (this != &v) {
    	assert(v.size == size);//只允许相同大小的数组赋值
        for(int i =0; i<size; ++i)
            p[i] = v.p[i];
    }
    return *this;
}
int main() { //测试程序
	int a[5]={1,2,3,4,5};
	vect v1(a,5);
	v1[2]=9;//调用operator[]
	for(int i=0; i<=v1.ub(); ++i)
		cout<<v1[i]<<"\t";
}

4.5 重载类型转换函数

  1. 类型转换运算符
  • 重载operator type运算符可以将当前类型转换为type指定的类型。这个运算符只能用成员函数重载,而且不带参数。
#include <iostream.h> 
#include <cassert> 
class MinInt{ 
	char m; 
	public:
	MinInt(int val = 0){ //int类型转换为MinInt
			assert(val>=0 && val<=100); //要求取值范围在0~100之间 
			m = static_cast<char>(val); 
	} 
	operator int( ){//MinInt对象转换为int 类型 
			return static_cast<int>(m); }
}; 
int main( )  { 
	MinInt mi(10), num;  
	num = mi + 20; /*首先将mi转换为int类型,再执行加法运算; 
			再将int类型的计算结果30 转换为赋值左边的MinInt 类型 */ 
	int val = num;   //将num自动转换为int,并赋值给val 
	cout<< mi << '\t' << num << '\t' << val; 
	//num 和mi 转换为int输出 
} 
// 用户自定义的类型转换
class One {
public:
  	One() {}
};

class Two {
public:
  	Two(const One&) {}	//类型转换构造函数 One => Two
};

class Three {
  	int i;
public:
  	Three(int ii = 0, int = 0) : i(ii) {}
  	//int => Three
};
class Four {
  	int x;
public:
  	Four(int xx) : x(xx) {}	// int => Four
  	operator Three() const 	//Four => Three
   	{ return Three(x); }	
};
void f(Two) {}
void g(Three) {}
int main() {
  	One one;
  	f(one); // OK:自动类型转换Two(const One&)
  	Four four(1);
  	g(four);//OK: 自动类型转换operator Three()
  	g(1);  // Three(1,0)
} 

  1. 可能引起的二义性问题
  • 如果程序中定义了多种用于从X类到Y类自动转换的方法,那么在实际需要进行类型转换时将会产生二义性错误。
class Y; // 类声明
class X {
public:
  		operator Y() const; // X到Y的转换
};
class Y {
public:
  		Y(X); // X到Y的转换
};
void f(Y) {}
int main() {
  		X a;
		f(a); // Error: 二义性
} 
  • 如果程序中定义了从一种类型到其他多种类型的自动转换方法,那么在实际需要进行类型转换时也可能导致二义性。
class Y{ }; 
class Z{ }; 
class X {
public:
	operator Y() const; // X到Y的转换
	operator Z() const; // X到Z的转换
};
void f(Y) {}
void f(Z) {}
int main() {
  	X a;
	f(a); // Error: 二义性
} 

小结

运算符建议重载方式
一元运算符成员函数
=,[],(),->,->* 类型转换必须是成员函数
复合赋值运算符成员函数
其他二元运算符非成员函数
输入输出运算符<<和>>非成员函数(友元)

第五章 类之继承性与派生类

基类和派生类

  • 继承:保持已有类的特性而构造新类的过程
  • 派生:在已有类的基础上新增自己的特性而产生新类的过程
  • 基类:被继承的已有类
  • 派生类:派生出的新类
    继承与派生的目的
  • 继承的目的:实现代码的重用
  • 派生的目的:当新的问题出现,原有程序无法解决(或不能完全解决)时,需要对原有程序进行改造
    类与类之间的关系
  • 继承关系:描述了派生类与基类之间的“是”关系,派生类是基类中的一种,是它的具体化。
  • 组合关系:又称包含关系,一个类中有另一个类中的对象,表现两者之间的“有”的关系。
    继承的类型
  • 单重继承:指生成的派生类只有一个基类
  • 多重继承:指生成的派生类有两个或两个以上的基类
    派生类的定义
class <派生类名><继承方式> <基类名>{
	<派生类新增成员说明>
};

派生类的三种继承方式
不同继承方式的影响主要体现在:派生类成员对基类成员的访问权限

  • 继承方式:public(公有)、private(私有)、protected(保护)
  • 默认方式:对class来说是private,对struct来讲是public
    访问权限
  1. 基类的私有成员在派生类中不能直接访问
  2. 公有继承方式:基类中公有和保护成员在派生类中仍然是公有和保护成员
  3. 私有继承方式:基类中公有和保护成员在派生类中都为私有成员
  4. 保护继承方式:基类中公有和保护成员在派生类中都为保护成员
基类中成员公有继承私有继承保护继承
私成员不可访问不可访问不可访问
公成员公有私有保护
保护成员保护私有保护
成员访问权限的控制
  1. 公有继承方法
#include<iostream>
#include<cmath>
using namecpace std;

class Point	//基类Point类的声明
{
	public:	//公有函数成员
		void InitP(float xx=0, float yy=0)    {X=xx;Y=yy;}
		void Move(float xOff, float yOff)    {X+=xOff;Y+=yOff;}
		float GetX() {return X;}
		float GetY() {return Y;}
	private://私有数据成员
		float X,Y;
};

class Rectangle: public Point  //派生类声明
{
	public:	//新增公有函数成员
		void InitR(float x, float y, float w, float h){
			InitP(x,y);W=w;H=h;
		}//调用基类公有成员函数
		float GetH() {return H;}
		float GetW() {return W;}
	private://新增私有数据成员
		float W,H;
};

int main( )
{  Rectangle rect;
	rect.InitR(2,3,20,10);
    //通过派生类对象访问基类公有成员
	rect.Move(3,2);  
	cout<<rect.GetX()<<',' <<rect.GetY()<<','
		<<rect.GetH()<<','<<rect.GetW()<<endl;
	return 0;
}
  1. 私有继承方式
#include <iostream.h>
class A
{
	public:
        void f(int i)
        {  cout<<i<<endl;  }
        void g()
        {  cout<<"A\n";  }
};
class B:A //默认私有继承
{
	public:
        void h()
        {  cout<<"B\n";  }
        A::f;   //引入本作用域
};
void main()
{
    B b;
    b.f(10);
    b.g( );   //错
    b.h( );
}
  1. 保护继承方式
    对于建立其所在类对象的模块来说,它与private成员的性质相同;对于其派生类来说,它与public成员的性质相同
#include <iostream.h>
#include <string.h>
class A
{
    public:
	    A(const char *str)
	    {  strcpy(name,str);  }
    protected:
        char name[80];
};
class B:protected A
{
    public:
	    B(const char *str):A(str)  {}
	    void Print()
	    {  cout<<name<<endl;  }//在派生类中直接访问基类protected成员
};
void main()
{
    B b("Zhang");
    b.Print();
}

单继承

派生类的构造函数和析构函数

构造函数和析构函数不能继承

  1. 构造函数
<派生类构造函数名> (<总参数表>):<基类构造函数名> (<参数表>)<其他初始化项>
{
    <派生类自身数据成员初始化>
}

执行顺序:基类->(子对象)->派生类
2. 析构函数
执行顺序:派生类->(子对象)->基类
例:多层派生类构造函数和析构函数

#include <iostream.h>
class A {
    public:
        A( )
        {  a=0;  }
        A(int i)
        {  a=i;  }
        ~A()
        {  cout<<"In A.\n";  }
        void Print()
        {  cout<<a<<',';  }
    private:
        int a;
};
class B:public A {
	public:
        B( )
        {  b1=b2=0;  }
        B(int i)
        {  b1=0;b2=i;  }
        B(int i,int j,int k):A(i),b1(j),b2(k)
        {  }
        ~B( ) {  cout<<"In B.\n";  }
        void Print( ) {
            A::Print();
            cout<<b1<<','<<b2<<',';
        }
	private:
        int b1,b2;
};
class C:public B {
	public:
        C( ) {  c=0;  }
        C(int i) {  c=i;  }
        C(int i,int j,int k,int l ):B(i,j,k),c( l )
        {  }
        ~C()
        {  cout<<"In C.\n";  }
        void Print() {
            B::Print();
            cout<<c<<endl;
        }
	private:
        int c;
};
void main( ) {
    C c1;
    C c2(10);
    C c3(10,20,30,40);
    c1.Print();
    c2.Print();
    c3.Print();
}

子类型

当一个类型至少包含了另一个类型的所有行为,则称该类型是另一个类型的子类型。
如果类型B是类型A的子类型,则称类型B适应于类型A(类型适应关系)。

赋值兼容规则
  1. B类的对象可以赋值给A类的对象
  2. B类的对象可以给A类对象引用赋值
  3. B类的对象地址值可以给A类对象指针赋值
#include <iostream.h>
class A {
    public:
        A( )
        {  a=0;  }
        A(int i)
        {  a=i;  }
        void Print( )
        {  cout<<a<<endl;  }
        int Geta()
        {  return a;  }
    private:
        int a;
};
class B:public A {
	public:
        B( ) {  b=0;  }
        B(int i,int j):A(i),b(j) {  }
        void Print( ) { 
            cout<<b<<',';
            A::Print();
        }
    private:
        int b;
};
void fun(A &a) {
    cout<<a.Geta( )+2<<',';
    a.Print();
}
void main( ) {
    A a1(10),a2;
    B b(10,20);
    b.Print();
    a2=b;
    a2.Print( );
    A *pa=new A(15);
    B *pb=new B(15,25);
    pa=pb;
    pa->Print();
    fun(*pb);
    delete pa;
}

多继承

多继承的构造函数和析构函数
<派生类构造函数名> (<总参数表>): <基类名1> (<参数表1>), <基类名2> (<参数表2>),{
  	<派生类构造函数体>
}

先执行基类的构造函数。多个基类构造函数的执行顺序取决于定义派生类时规定的先后顺序。

#include <iostream>
using namespace std;

class A {
	public:
		A(int i) {
			a = i;
			cout << "Constructor called.A\n";
		}
		~A() {
			cout << "Destructor called.A\n";
		}
		void Print( ) {
			cout << a << endl;
		}
	private:
		int a;
};

class B {
	public:
		B(int i) {
			b = i;
			cout << "Constructor called.B\n";
		}
		~B() {
			cout << "Destructor called.B\n";
		}
		void Print() {
			cout << b << endl;
		}
	private:
		int b;
};

class C {
	public:
		C(int i) {
			c = i;
			cout << "Constructor called.C\n";
		}
		~C() {
			cout << "Destructor called.C\n";
		}
		int Getc( ) {
			return c;
		}
	private:
		int c;
};

class D : public A, public B {
	public:
		D(int i, int j, int k, int l): B(i), A(j), c(l) {
			d = k;
			cout << "Constructor called.D\n";
		}
		~D( ) {
			cout << "Destructor called.D\n";
		}
		void Print() {
			A::Print();
			B::Print();
			cout << d << ',' << c.Getc() << endl;
		}
	private:
		int d;
		C c;
};

int main( ) {
	D d(5, 6, 7, 8);
	d.Print();
	B b(2);
	b = d;
	b.Print();
	return 0;
}
多继承的二义性
  1. 调用不同基类中的相同成员
#include <iostream>
using namespace std;

class A {
	public:
		void f() {
			cout << "A.\n";
		}
};

class B {
	public:
		void f() {
			cout << "B.\n";
		}
		void g() {
			cout << "BB.\n";
		}
};

class D: public A, public B {
	public:
		void g() {
			cout << "DD.\n";
		}
		void h() {
			B::f();
		}
};

int main( ) {
	D d;
	d.A::f();//声明是哪个类中的函数
	d.g();
	d.h();
	return 0;
}
  1. 当类层次结构如下时:
    –>B–>
    D—| |---->A
    –>C–>
class B
{         public:
            int b;
}
class B1 : public B
{
       private:
            int b1;
}
class B2 : public B
{
       private:
            int b2;
};
class C : public B1,public B2
{
       public:
           int f();
       private:
           int d;
}
//C中的b变量存在二义性:是B1中的b,还是B2中的b
//正确的: c.B1::b
//    	 c.B2::b

虚基类

用于有共同基类的场合。主要用来解决多继承时可能发生的对同一基类继承多次而产生的二义性问题。
声明:class B1:virtual public B

class B{ private: int b;};
class B1 : virtual public B { private: int b1;};
class B2 : virtual public B { private: int b2;};
class C : public B1, public B2{ private: float d;}
C  cobj;
cobj.b;//正确
#include <iostream>
using namespace std;
class B0	//声明基类B0
{ public:	//外部接口
	int nV;
	void fun(){cout<<"Member of B0"<<endl;}
};
class B1: virtual public B0  //B0为虚基类,派生B1类
{ public:	//新增外部接口
	int nV1;
};
class B2: virtual public B0  //B0为虚基类,派生B2类
{  public:	//新增外部接口
	int nV2;
};
class D1: public B1, public B2	//派生类D1声明
{  public:	//新增外部接口
	int nVd;
	void fund(){cout<<"Member of D1"<<endl;}
};
int main( )	//程序主函数
{        D1 d1;	//声明D1类对象d1
	d1.nV=2;	//使用最远基类成员
	d1.fun();
}
含有虚基类的派生类构造函数
#include <iostream>
using namespace std;

class B0 {	//声明基类B0
	public:	//外部接口
		B0(int n) {
			nV = n;
		}
		int nV;
		void fun() {
			cout << "Member of B0" << endl;
		}
};

class B1: virtual public B0 {
	public:
		B1(int a) : B0(a) {}
		int nV1;
};

class B2: virtual public B0 {
	public:
		B2(int a) : B0(a) {}
		int nV2;
};

class D1: public B1, public B2 {
	public:
		D1(int a) : B0(a), B1(a), B2(a) {}
		int nVd;
		void fund() {
			cout << "Member of D1" << endl;
		}
};

int main() {
	D1 d1(1);
	d1.nV = 2;
	d1.fun();
}

第六章 类的多态性与虚函数

面向对象程序设计的特征:抽象性、封装性、多态性

多态性概述

多态性是指相同的动词作用到不同类型的对象上。
多态性(OOP): 当不同对象接受到相同的消息产生不同的动作,这种性质称为多态性。
C++实现的多态性:函数重载、运算符重载、模板(编译时多态性)、虚函数(运行时多态性)

静态联编和动态联编

联编: 一个源程序需要经过编译、连接,才能成为可执行代码。上述过程中需要将一个函数调用链接上相应的函数代码,这一过程称为联编。
静态联编: 在程序被编译时进行联编(早联编)。程序执行快,但灵活性较小。
动态联编: 编译时无法确定要调用的函数,在程序运行时联编。(晚联编,滞后联编)灵活性高,程序执行慢。

虚函数

virtual float area( ){ return -1;}
virtual 关键字的作用:指示C++编译器对该函数的调用进行动态联编。
区别示例:

#include <iostream>
using namespace std;

//静态联编
class shape {
	public:
		float  area( ) {
			return -1;
		}
};

//动态联编
class shape {
	public:
		virtual float  area( ) {
			return -1;
		}
};

class circle : public shape {
		float radius;
	public:
		circle(float r) {
			radius = r;
		}
		float  area( ) {
			return 3.14159 * radius * radius;
		}
};

int main( ) {
	shape obj, *ptr;
	circle c(3.6);
	ptr = &obj;
	cout << ptr->area( ) << endl;//-1

	ptr = &c;
	cout << ptr->area( ) << endl;//-1 静态联编
	return 0;
}

声明:virtual <类型说明符> <函数名> ( <参数表> )
包含虚函数的类被称为多态类。
函数覆盖: 在派生类中,虚函数被重新定义以实现不同的操作。 这种方式称为函数超越(overriding),又称为函数覆盖。

管可以用对象名和点算符的方式调用虚函数:tri.area( ) 或者 rect.area( )。这时是静态联编方式。

只有当访问虚函数是通过基类指针s时才可获得运行时的多态性。

虚函数与重载函数的比较
虚函数重载函数
虚函数要求具有完全相同的函数原型要求函数有相同的函数名称,并有不同的参数序列
函数只能是成员函数重载函数可以是成员函数和非成员函数

纯虚函数和抽象类

纯虚函数

纯虚函数是指在基类中声明但是没有定义的虚函数,而且设置函数值等于零。
virtual type func_name(parameter list) =0;

class shape{
public:
	virtual float area( )=0;
	//A PURE VIRTUAL FUNCTION 
};
class triangle : public shape{
	float H,W;
public:
	triangle(float h,float w){H=h;W=w;}
	float area( ){return H*W*0.5;}
};
抽象类

抽象类:包含有纯虚函数的类称为抽象类

  • 不能说明抽象类的对象,但能说明指向抽象类的指针
  • 一个抽象类只能作为基类来派生其他的类
void main(  ) { 
	shape *s;
	triangle  tri(3,4);   
	rectangle rect(3,4);
	s=&tri;
	cout<<“The area of triangle:<<s->area()<<endl;
	s=&rect;
	cout<< “The area of rectangle:<<s->area()<<endl;
}
虚析构函数

作用:通过基类指针删除派生类对象

#include <iostream.h>
 class A {
	 public: 
        virtual ~A()
        {  cout<<"A::~A() called.\n";  }
 };
 class B : public A {
    public:
        B(int i)
        {  buffer=new char[i];  }
        ~B( ) {  
            delete [] buffer;
            cout<<"B::~B() called.\n";
        }
 private:
        char *buffer;
 };
void fun(A *a) {
    delete a;
}
void main( ) {
    B *b=new B(5);
    fun(b);
}

模板与STL类库

模板

C++中的模板提供了重用源代码的方法,C++的库是基于模板的技术
声明:

函数模板
template <class T>
T <函数名称>( T <参数名>,) 
{
    <函数操作>
}

举例:

#include <iostream.h>

template <class T>
T max( T a, T b) {
    return a > b ? a : b;
}

void main( ) {
    cout << "max(20, 30) = " << max(20, 30) << endl;
    cout << "max('t', 'v') = " << max('t', 'v') << endl;
    cout << "max(10.1, 15.2) = " << max(10.1, 15.2) << endl;
}

和普通函数一样,函数模板之间也可以重载。
而且函数模板也可以与普通函数之间构成重载关系。

类模板
template <class T1,class T2...class Tn> 
class 类模板名 {
	//类模板定义
};

类模板的成员函数可以在类模板的外面定义,类模板成员函数也可以重载。

template<class T> 
void TStack<T>::Push(T t) {
	*pLast++=a;
}
template<class T>
 T TStack<T>::Pop(int i) {
	return *(pLast-i);
}

示例:

#include <iostream>
using namespace std;

template<class T>
class Array {
	public:
		int getlength() {
			return length;
		}
		T  operator[](int i) {
			return array[i];   //运算符[]重载
		}
		void setarray(T t, int i) {
			this->array[i] = t;
		}
		Array(int l) {    //构造函数
			length = l;
			array = new T[length];
		}
		~Array () {
			delete [] array;
		}
	private:
		int length;
		T *array;
};

int main( ) {
	Array<int> a(5);   //int代表T,类模板的实例化产生类,类的实例化产生对象
	//Array  a(5);  会出错
	cout << a.getlength() << endl;//5

	a.setarray(3, 2);
	cout << a.operator[](2) << endl;//3
	cout << a[2] << endl;//3
	return 0;
}
STL

容器类(类模板)、算法(函数模板)、迭代器(指向对象的指针)
容器类:vector、list、deque、set、mutilset、map、multimap、hash set、hash multiset、hash map和hash multimap

第七章 输入输出流库

流的基本概念

在计算机内存中,数据从内存的一个地址移动到另一个地址称为数据流动——流操作。

输出流

在C++中,将“<<”(即左移运算符)重载为输出运算符。
输出运算符“<<”有二个运算分量,左边(左分量)为输出流ostream对象(cout),右边(右分量)为一个基本类型数据

输入流

在C++中,将“>>”(即右移运算符)重载为输入运算符。
输入运算符“>>”有二个运算分量,左边(左分量)为输入流istream对象(cin),右边(右分量)为一个基本类型数据。

输入输出格式控制

用于输入输出格式控制的成员函数:

函数原型功能
setf(long flags);设置状态标志flags
unsetf(long flags);清楚状态标志并返回清楚前的标志
flags();测试状态标志
flags(long flags);设置标志flags并返回设置前标志
width();返回当前宽度设置值
width(int w);设置域宽w并返回以前的设置
precision(int p);设置小数位数,并返回以前的小数位数
fill();返回当前的填充字符
fill(char ch);设置填充字符ch,返回当前的填充字符
  1. C++预定义的操作符-iostream中的控制符
  • dec 以十进制输入输出整型数,用于输入输出
  • hex 以十六进制输入输出整型数,用于输入输出
  • oct 以八进制输入输出整型数,用于输入输出
  • ws 输入时跳过开头的空白符,用于输入
  • endl 插入一个换行符并刷新输出流,用于输出
  • ends 插入一个空字符,用以结束一个字符串,用于输出
  1. C++预定义的操作符-iomanip中的控制符
  • flush 刷新一个输入流,用于输入
  • setbase(int n) 把转换基数设置为n(n的取值为0,8,10,16),n的缺省值为0(以十进进制形式输出)。
  • resetiosflags(long f) 关闭由参数f指定的格式标志,用于输入输出
  • setiosflags(long f) 设置由参数f指定的格式标志,用于输入输出
  • setfill(char c) 设置填充字符
  • setprecision(int n) 设置数据小数位数,缺省时为6,用于输入输出
  • setw(int n) 设置域宽,用于输入输出
  • 其中:setiosflags(long f)、resetiosflags(long f) 中的long f为格式标志——同上。
cout.width(10);
cout.fill('*');
cout << values[i] <<'\n';
cout<<setiosflags(ios::left)<<setw(6)<<names[i]<<resetiosflags(ios::left)<<setw(10)<<setprecision(1)<<values[i]<<endl;

文件IO流

  • ifstream—文件输入流类,用于文件的输入(读文件)
  • ofstream—文件输出流类,用于文件的输出(写文件)
  • fstream—文件输入/输出流类,用于文件的输入/输出(读/写文件)
  • 当程序中进行文件操作时,应加上头文件“fstream.h”
文件的打开和关闭
  • 文件的打开
    void open( const char *s,ios_base::openmode ,mode=ios_base::out|ios_base::trunc)
    其中,第一个参数表示打开的文件,第二个参数表示文件打开方式,第三个参数表示访问方式。
    ![[Pasted image 20240525184429.png]]
    打开方式1:
ifstream  f1;  //定义文件输入流对象f1
f1.open( “d:\\vcprg\\7-3.cpp”); //打开D盘vcprg文件夹(目录)下的7-3.cpp文件,可进行文件读操作

打开方式2:

ifstream f1( “d:\\vcprg\\7-3.cpp”)

复制文件:

#include<iostream>
#include<fstream>

using namespace std;
void main(void)
{
	ifstream f1;        //定义文件输入流对象
	ofstream f2;
	f1.open("d:\\vcprg\\test.cpp"); //以读方式打开文件
	f2.open("d:\\vcprg\\text.txt"); //以写方式打开文件
	char c;
	while(f1) { //判断文件未结束则循环
		f1.get(c);
		f2<<c;
	}
	cout<<"文件复制成功"<<endl;
	f1.close();//关闭文件
	f2.close();
}
  • 文件异常处理:
  1. 用条件语句判断文件流标志位failbit是否为true。
if(!文件流对象){<异常处理程序>}
if(文件流对象){<正常处理程序>}
  1. 用成员fail()函数
if(文件流对象.fail()){<异常处理程序>}
if(!文件流对象.fail()){<正常处理程序>}
  • 文件的关闭
    文件流对象.close();
#include <fstream.h>

void main ( )
{
   ofstream  ost ; 
   ost . open ( “d:\\my1.dat ") ; 
   ost << 20 << endl ; 
   ost << 30.5 << endl ;
   ost . close ( ) ; 
   ifstream  ist ( “d:\\my1.dat" ) ; 
   int  n ;
   double  d ;
   ist >> n >> d ; 
   cout << n << endl << d << endl ;
}
文件的读写
  1. txt
    读文件格式:文件输入流对象.get(char);
    写文件格式:文件输出流对象.put(char ch);
  2. 二进制文件
    输入文件流对象.read((char*)&对象或&对象数组名[下标],sizeof(对象名或所属类名));
    输出文件流对象.write((char*)&对象或&对象数组名[下标],sizeof(对象名或所属类名));
二进制文件
  1. istream 类操作流读指针的成员函数
  • istream & istream :: seekg ( long pos) ;
    作用:读指针从流的起始位置向后移动由pos指定字节
  • istream & istream :: seekg ( long off, ios::seek_dir ) ;
    作用:读指针从流的seek_dir位置移动 off 指定字节
    seek_dir=cur\beg\end
  • istream & istream :: tellg () ;
    作用:返回读指针当前所指位置值
  1. ostream 类操作流写指针的成员函数
  • ostream & ostream :: seekp ( long pos) ;
    作用:写指针从流的起始位置向后移动由pos指定字节
  • ostream & ostream :: seekp ( long off, ios::seek_dir ) ;
    作用:写指针从流的seek_dir位置移动 off 指定字节
    seek_dir=cur\beg\end
  • ostream & ostream :: tellp () ;
    作用:返回写指针当前所指位置值
  1. istream 类中三个操作字节数据的成员函数
  • istream & istream :: get ( char & c ) ;
    作用:从流中提取一个字节数据,更新对象 c
  • int istream :: get ( ) ;
    作用:函数值返回流中一个字节数据
  • istream & istream :: read ( char * buf , int n ) ;
    作用:从流中提取 n 个字节数据,更新对象 buf
  1. ostream 类中两个操作字节数据的成员函数
  • ostream & ostream :: put ( char c ) ;
    作用:向流插入一个字节数据
  • ostream & ostream :: write ( char * buf , int n ) ;
    作用:向流插入 buf 对象的由第二个参数指定数目的字节数据

第八章 异常处理

异常使用三部曲

  1. 框定异常(try)
    在祖先函数处,框定可能产生错误的语句序列,它是异常的根据,若不框定异常,则没有异常。
  2. 定义异常处理(catch)
    将出现异常后的处理过程放在catch块中,以便当异常被抛出,因类型匹配而捕捉时,就处理之。
  3. 抛掷异常(throw)
    在可能产生异常的语句中进行错误检测,有错误就抛掷异常。
#include<iostream>
using namespace std;
int main(){
	try{
		cout<<"5/2="<<Div(5,2)<<endl;
		cout<<"8/0="<<Div(8,0)<<endl;
		cout<<"7/1="<<Div(7,1)<<endl;
	}
	catch(int){
		cout<<"except of deviding zero.\n";
	}
	cout<<"that is ok.\n";
}
int Div(int x,int y){
	if(y==0)throw y;
	return x/y;
}

申述异常

void f1()throw(A,B);//有A、B两种异常
void f2();//可能有任何异常
void f3()throw();//没有异常

异常并非一定是针对错误时刻处理

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Solen.&

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值