面向对象程序设计学习笔记

C++面向对象编程

针对C的补充

头文件"stdafx.h"

预编译头文件,是把一个工程中的MFC标准头文件进行预编译,在工程编译时不再需要编译这部分头文件而是使用它的编译效果。
一些大型程序使用这个效率比较好。
#include"stdafx.h"一定要放在最开头。

数值变量的初始化也可以在括号内进行。

stringstream类

在标准头文件中,使用这个类可以对基于字符串的对象进行数值互换操作。
stringstream( string ) >> int;(即将一个字符串数值转换成了int型数值)

stringstream类的拓展性应用:
https://blog.csdn.net/sunshineacm/article/details/78068987

类型转换函数库

①string转换成int: atoi
②string转换成long(长整型): atol
③string转换成double:atof

C++中的类与对象

面向过程的程序设计中代码和数据是分离的,可维护性较差;
面向对象的程序设计则把数据及处理这些数据的函数都封装在一个类中,类 是一种数据类型,而使用类的变量则成为对象。
面向对象编程有三个优点:
1.可维护性好;
2.易修改性好;
3.可重用性好。

类的定义

类 是进行封装和数据隐藏的工具,通过 类 将逻辑上相关的实体联系起来并可从外部对这些实体进行访问。

用关键字 class 定义 类 。
其功能与struct 相类似,但 class还可以包含函数。

类的主要功能不是执行而是提供必要的资源。
语法:
class 类名
{
private:
私有成员数据和函数;

protected:
保护成员数据和函数;

public:
共有成员数据和函数;
} 类的对象声明;

一个类含有私有、保护、共有三部分,当缺省时则默认私有。

私有部分:只能被该类本身声明的其他成员或该类的“friend”类访问;
保护部分:还能被本类派生的类的成员函数访问,可用于类的继承;
共有部分:可以被本类以外的函数访问,是类与外部的接口。

class、struct、union都可以定义一个类:
class 缺省说明时默认所有成员都是私有的;
struct 若不特别指出,则所有成员都是共有的;
union 所有成员都是共有的且不能更改。

类的使用

用户可以通过“对象名.成员名”的方式来引用对象的任何public成员,就像一般的函数或变量。

如果类的方法的定义是在类的外部实现,则在定义方法时将类名放在方法名前面,用::隔开。例:
返回值类型 类名::函数名( 形参表 )
{
}
方法的具体实现和类的定义可以放在不同的源文件中。

在class内部直接定义完整的函数时编译器自动将其作为inline(内联函数)处理;
在class外部定义其具体方法时,函数只是一般的class成员函数。(也可以在定义函数具体方法时在前面加上inline变成内联函数)
(内联函数:
优点:提高程序运行效率;
缺点:无法递归调用)

定义 类 时,类的内部也可以嵌套。

this指针

同一类的各个对象都有自己数据成员的存储空间,但是系统不会给每个类的对象建立成员函数副本。
我们调用成员函数时并不知道正在哪个对象上运行,实际上,C++提供了一个this隐含指针参数,他不能显式说明,但可以在成员函数中显式使用。
运行原理:当一个对象调用类的成员函数时,对象的地址被传给了this指针。

构造函数和析构函数

C++中有几类函数决定了如何建立、初始化、复制和删除对象,其中包括构造函数和析构函数,它们既可以在类内定义和声明,也可以在外面定义。
若一个类包含构造函数,则建立该类的对象时就要调用它;
若一个类包含析构函数,则销毁该类的对象时就要调用它。

构造函数:
特殊的函数,主要用于为对象分配内存空间、对数据成员进行初始化和其它内部管理操作。
构造函数的特点是名称与它所在的类相同,因此当定义该类的对象时就已经自动调用构造函数进行初始化了。
构造函数可以接收参数并允许重载,当一个类有多个构造函数时编译程序的运行和函数重载一样:比对参数表。

1.构造函数的原型和实现都没有返回值,也没有void声明,这是规则。
2.实际应用中若没有给类定义构造函数,则编译系统自动生成一个没有参数的构造函数,把对象中的每个实例变量初始化为0;
3.当定义构造函数时在形参表给变量赋值了,那么可以在调用时缺省参数。
4.构造函数也可以没有参数。

析构函数:
也是特殊函数,与定义它的类具有相同的名字,但要在前面加上一个波浪号“~”。
析构函数没有参数也没有返回值,因此一个类中只有一个析构函数,用于释放分配给对象的内存空间。
当程序超出类对象的作用域或对类指针用delete时系统将自动调用析构函数。
若不定义析构函那么编译系统会自主生成一个缺省的析构函数,一般可以满足需求;但是若对象在撤销前需要一些内部操作那么应定义析构函数。
析构函数也可以在类的外部定义:
类名 ::~类名 ( )
{
操作
}

注意:
对象的构造次序和析构次序是相反的,最先被构造的对象会最后析构。

重载

包含函数重载和操作符重载。

函数重载:
声明多个名称相同,但带有不同类型、不同数目的参数和返回值的函数,可以实现多种不同的功能。

操作符重载
对已有的操作符赋予新的功能,但与原含义不冲突,使用时根据操作符出现的位置来判断具体实现哪一种操作。
(注:单目运算操作中只有一个参数,因此不可能分清是前置操作还是后置操作)

使用操作符重载时要先以以下方式声明成员函数:
函数类型 operator # ( 形参表 );
operator是关键字;
#表示欲重载的运算符;
函数类型指明返回值类型,通常与类的类型一致或为void。

C++中约定,在增量运算操作符中,若放上一个整数形参,就是后置运算符。

拷贝构造函数

用一个已有的同类型对象的数据对一个新创建的对象进行初始化。
类名::类名 ( const 类名 & 引用名,… );
为了保证引用的对象不被修改,通常把引用参数说明为const参数。

使用时机:
①创建对象时调用拷贝构造函数进行数据成员初始化;
②当函数具有类类型传值参数或函数返回类型为类类型时,系统自动调用拷贝构造函数进行局部对象的初始化工作。
(销毁形参也需要运行析构函数)
(函数返回值为类类型时需要拷贝构造函数建立临时对象)
(question:拷贝构造函数的传值类型为什么必须是传引用?
answer:事实上,除了传引用,传值和传指针的方式本质上都是传值,若是传值则会无穷递归地调用拷贝构造函数,因此必须是传引用。)

浅复制和深复制
若数据成员资源是由指针指示的堆,那么系统复制时只会进行指针地址的复制而不会重新分配内存空间。这时就需要自定义拷贝构造函数。

拷贝构造函数避免了不同对象共享内存的错误,是“深复制”;
系统做的简单的数据复制是“浅复制”。

常成员

定义常成员用const约束。

常数据成员:数据成员在实例化被初始化后约束为只读;
常成员函数:成员函数的this指针被约束为指向常量的常指针,在函数体内不能修改数据成员的值。

常数据成员:
因为类对象要通过执行构造函数才能建立存储空间,所以用构造函数实现常数据成员值的初始化是必须的。

①直接在构造函数的定义中用常量进行初始化,这样每个对象相应的常数据成员都是具有相同的值;
const int M;
Mclass(): M(5)
{
} //在定义类的语句块里面;

②在调用构造函数创建对象时用实参和参数初始式配合,对常数据成员赋值,这样每个对象相应的常数据成员可以有不同的值;
·public:
Student(int num);
private:
const int code; //在定义类的语句块里面;

void Student::Student(int num):code(num)
{
} //定义构造函数的语句块;

int main()
{
Student(5);
} //将5赋给了num,然后将num付给了常数据成员code;

常对象
在定义对象的说明语句前面用const作为前缀。
该对象的·全部数据成员在作用域中限定为只读。

常成员函数
常成员函数的this指针被约束为指向常量的常指针;
const X * const this
由于this指针隐含定义,所以在函数首部以const为后缀。
不能修改this所指对象的成员。

静态成员

当类成员被冠以static声明时是静态成员;
“静态”是指它的作用域局限于类,提供了一种同类对象共享机制;

静态数据成员
在类中声明,在类外定义。
static数据成员从存储性质上是全局变量,但作用域是类;根据public、private或protected的状态决定是否可以在类外可以用“类名::”作为限定词或通过对象访问它。

特别的是,static数据成员在声明和创建对象时不会建立存储空间,因此在类声明之外还要有一个说明语句,让它在编译时建立存储空间和进行一次文件范围的初始化,若不指定初值则默认为0 。、

class Counter
{
static int num; //声明私有静态数据成员;
};
int Counter::num=0; //在类外定义静态数据成员,置初值;
int main()
{
}

静态成员函数
一个成员函数冠以static声明。
静态成员函数没有this指针,是不依赖于类的共同操作,只能访问类的静态数据成员。

在类外可以用“类名::”作为限定词或通过对象调用它。

友元

友元可以是一个普通函数、成员函数或者另一个类。
类的主要特征是数据隐藏,为了使类外部的函数或类能访问该类的私有部分,通过关键字friend声明一个类的友元。在类的内部,友元被看作该类的成员。

友元函数
在一个类A中将关键字friend冠于一个函数原型或类名之前,那么该函数或类成为类A的友元,不受声明位置(private、public、protected)的影响。

调用方法的差别:
Friendfun ( &A ,5 ); //友元函数必须在参数表显式地指出要访问的对象;
A.Menberfun ( 5 ) ; //成员函数直接在对象上操作;

友元类
若F类是A类的友元类,那么F类的所有成员函数都是A类的友元函数。
程序中,友元类通常设计成一种对数据操作或类之间传递消息的辅助类。

注意:组合类成员的访问受成员特性的制约;
例:B是A的友元类,若A是B的公有成员,那么可以用B.A.x在主函数中访问A的私有成员;若A不是B的公有成员则不行。

在程序编译的角度看,类、对象都是一个模块、一个运行实体而已,所以可以只有数据成员或只有成员函数。

类的包含

即类的数据成员是另一个已经定义的类对象,(has-a),是一种软件重用技术。

若B类中包含了A类,那么要想在创建B类对象时完成对A类的初始化,需要:首先在B类的构造函数中以参数初始化的形式调用A类的构造函数(赋值形式不能启动A类的构造函数),然后A类的构造函数(定义在B中,声明在A中)完成对A类对象地初始化。
如果没有写参数初始化的式子,也会自动调用不带参数的A类默认的构造函数,此时若A类声明中没有明确写不带参数的构造函数(完全没写构造函数的话就可以调动默认的构造函数),则编译器不通过。

参数初始式列表:在构造函数的参数表后面加一个冒号,冒号后面以逗号分隔,以括号指定每个数据成员对应的初始化变元。
构造函数名 (变元表): 数据成员 ( 变元表 ) , 数据成员 ( 变元表 )…
{
}

运算符重载

注意以下运算符不能重载:
. .* :: ?: sizeof

运算符重载后不能改变优先级、结合性和操作数的个数。
不能创建新的运算符,只能重载系统预定义的运算符。

重载运算符的语法

运算符函数是一种特殊的成员函数或友元函数。
类型 类名::operator op (参数表)
{
}

重载运算符后,可以通过间接方式调用重载版本,系统根据参数和类型来寻找匹配版本,也可以通过函数名的方式来调用。

用于类运算的运算符通常需要重载但是有两个运算符会提供默认版本:
赋值运算符: = :系统默认为对象数据成员的复制;
地址运算符: & :系统默认为返回任何类对象的地址。
当然这两个也可以重载。

利用成员或友元函数重载运算符

运算符重载函数既可以重载为成员函数、友元函数或普通函数。
普通函数:访问private和protected的数据时要通过public接口,加大了开销。
成员函数:具有this指针;
友元函数:没有this指针。

一元运算符:
不论前置后置都要有一个操作数。
成员函数: Oblect.operator op()
友元函数: operator op(Object)

二元运算符:
任何二元运算符都要有左右运算符。
成员函数: ObjectL.operator op(ObjectR)
友元函数: operator op(ObjectL,ObjectR)

不管是成员函数还是友元函数重载,运算符的使用方法都相同,但由于传递参数的方法不同(体现在编译器的解释),应用的场景也有所不同。

用成员函数重载运算符
当一元运算符的操作数或二元运算符的左操作数是该类的一个对象时,一般定义为成员函数
例:

class TriCoor
{
//省略;
};

TriCoor TriCoor::operator+(TriCoor t)
{
TriCoor temp;
temp.x=x+t.x;
return temp;
}  //左操作数由this指针隐含传递,右操作数放在参数表中传递;
//相当于: temp.x=this->x + t.x;
//由左操作数激活函数;

TriCoor & TriCoor::operator++()
{
x++;
return *this;
}  //“++”和“=”运算符函数返回类型为 类 类型的引用,
//主要目的是减少了返回时对匿名对象数据复制的开销。

用友元函数重载运算符
若用成员函数重载运算符“+”:
x=x+25; //正确,因为左操作数是类类型,可以驱动重载函数;右操作数通过构造函数实现数据类型隐式转换
x=25+x; //错误,因为左操作数不是类类型,无法驱动重载函数。
这种情况就需要通过友元函数来定义运算符重载函数。

整型常量通过参数调用构造函数实现类型转换。

用const约束参数可以保证对实参的只读性。

不能用友元函数重载的运算符有:
= () [] ->

当一个运算符的操作需要修改类对象的状态时,应用成员函数重载;
若硬要以友元函数来重载,则可以对左操作数以引用参数的形式出现在形参表,并且不加上const。

几个经典的运算符的重载

重载++和–

C++规定,前置形式重载为一元运算符函数,后置形式重载为二元运算符函数。

例子:类A的对象Aobject
前置自增表达式
成员函数: A & A::Aobject.operator++();
友元函数: friend A & operator++(A&);
后置自增表达式
成员函数: A & A::operator++(int);
友元函数: friend A & operator++(A&,int);
(int为参数0,是一个伪值,只用于与前置形式相区别,在函数体中不能使用,否则会引起二义性)

前置重载直接传对象的引用进来,自增后返回对象;
后置重载传对象的引用,拷贝复制一个临时对象保存对象的原始值,对象自增自减,返回临时变量(原始值)。

重载赋值运算符

用于对象数据的复制,只能用成员函数重载,且不能被继承。
类名 & 类名::operator =( 类名);

例子:

class Name 
{
public:
  Name & operator=(Name);
  ...
private:
  char* pName;
  int size;
}  //在类中声明;

Nmae & Name::operator=(Name Obj)
{
delete[] pName;   //无论有没有,先释放空间上保险;
size = Obj.size;
pName = new char[size+1];
if(pName!=0) strcpy_s(pName,size+1,Obj.pName);
return *this;  //与拷贝构造函数的区别;
}

重载赋值运算符函数与拷贝构造函数相似,区别在于返回值类型不同和执行时机不同(前者用于程序运行时修改对象的数据,后者用于对象的初始化)

系统提供的重载赋值运算是浅复制。

重载[]和()运算符

只能用成员函数进行重载。

重载下标运算符
二元运算符,用于访问数据对象的元素。
例:
int & X::operator[] (int); //返回值是否需要引用取决于是否要修改左值。
那么当调用x[k]时,编译为: x.operator[] (k)

重载函数调用运算符()
二元运算符。
例:
int A::operator() (int,int);
那么当调用a(x,y)时,编译为:a.operator(x,y)

重载流插入<<和流提取>>运算符

可以通过重载 << 和 >> 来实现传输用户自定义类型的数据。
例:

//输出向量
ostream & operator << (ostream & output, const Vector & A)
{
	for(int i=0;i<A.len;i++)
	{
		output<<A.v[i]<<" ";
	}
	return output;
}

//输入向量
istream & operator>>(istream & input, Vector & A)
{
	for(int i=0;i<A.len;i++)
	{
		input>>A.v[i];
	}
	return input;
}

cout是C++输出流ostream的预定义对象,用于连接显示器;
cin是C++输入流istream的与定义对象,用于连接键盘;
“流”是C++基本类库中定义的类。

主函数中调用:cin>>a; 编译器翻译为:operator>>(cin,a);
函数返回一个对istream的引用是以便于流运算符的连续调用。
流插入运算符与之同理。

operator<<函数和operator>>函数之所以要使用非成员函数是因为触发函数的是cin和cout,不是类的对象。

类类型转换

数据类型转换是在程序运行或编译时,数据的某种类型转换到另一种类型。实现类与类之间、类与基本数据类型之间的转换可以用构造函数和类型转换函数。
有隐式调用和显式调用两种方式。

使用构造函数

具有一个非默认参数的构造函数 实现从参数类型到该类类型的转换。
ClassX:: ClassX (arg, arg1=E1,… argn=En);
ClassX为用户定义的类名;
arg为基本类型或类类型参数,是将被转换成ClassX类的参数;
其他的形参是默认参数和默认值;

当程序运行发现参数类型不匹配时会隐式调用构造函数,用整型常量建立临时对象来完成操作。
可以发生在算术运算、赋值运算和函数参数传递中。

使用类型转换函数

具有一个非默认参数的构造函数能够把某种类型对象转换成指定类对象,但不能将一个类对象转换为基本数据类型。因此引入类型转换函数。
ClassX::operator Type()
{
//…
return Type_Value;
}
其中ClassX是类类型标识符;
Type是类型标识符,可以是基本数据类型、复合数据类型或类类型;
Type_Value为Type类型的表达式;
函数的功能是把ClassX类型的对象转换成Type类型的对象,函数没有返回值、没有参数,但必须要有一个返回Type类型的语句。
只能定义为一个类的成员函数,不能定义为友元函数。

类型转换函数一般写在类的public域,在主函数中可以显式地用int(Count)或(int)Count形式调用。

用户定义的类型转换函数只能在无二义性的情况下隐式使用。

类型转换函数没有参数,所以不能够重载。

类型转换函数可以被继承,它是虚的。

继承

继承是面向对象程序设计中软件重用的关键技术,使用已经定义的类作为基础建立新的类。

has-a 表示类的包含关系;
uses-a 表示一个类部分使用另一个类;
is-a表示一种分类方式,描述类的抽象和层次关系,具有传递性;

类的包含和继承都是软件重用技术,二者可以相互修改转换。

有向无环图/“类格”
结点:类定义
前驱结点:基类/父类/超类
后继结点:派生类/子类

语句格式

class 派生类名 : 基类名表
{
数据成员和成员函数说明;
};

其中:基类名表的格式:
访问控制 基类名1, 访问控制 基类名2,……

“访问控制”是表示继承权限的关键字,成为访问描述符,决定了对基类成员的不同访问权限:
public 公有继承
private 私有继承
protected 保护继承

若省略了访问描述符则默认为私有继承;
若以struct定义类则默认为公有继承;

访问控制

公有继承:基类中的公有成员成为派生类的公有成员,基类的保护成员成为派生类的保护成员;

保护继承: 基类的所有公有成员和保护成员同时成为派生类的保护成员;

私有继承:基类的所有公有成员和保护成员同时成为派生类的私有成员;

不论以何种方式继承都不能直接调用基类的私有成员。

保护成员是专门为继承机制而设置的,使其屏蔽在类层次体系中,在派生类中可见,在类外不可见;

基类的私有成员在派生类中不可见,但在创建派生类时也要创建从基类继承过来的私有数据成员。
建立一个派生类时会从最高层的基类开始进行初始化,建立所有的数据成员。

因为继承的目的是实现软件重用,所以一般都是使用公有继承而不是保护继承和私有继承。

访问声明:
在派生类的公有段中声明私有继承的基类函数,可以使该函数成为派生类的公有成员。

语句格式:
基类名::成员

注意:
①访问声明仅调整名字的访问权限:1、数据成员不带数据类型,仅显示名称;2、成员函数不带参数和返回值类型,仅是函数名本身;

②访问声明不允许在派生类中降低或提升基类成员的可访问性;

③对重载函数名的访问声明将调整基类所有同名函数的访问域;
注意:1、基类中不同访问域的重载函数名不能用于访问声明;
2、派生类中与基类名字相同的成员不可调整访问权限;

重名成员

C++允许派生类的成员与基类成员重名,在派生类中访问重名成员时会屏蔽基类的同名成员。

若要在派生类中使用基类的同名成员,可以显式地使用作用域运算符::指定。

静态成员

若在基类中定义了静态成员,那么这些静态成员将在整个类体系中被共享,根据静态成员自身的访问特性和派生类的继承方式在类层次中具有不同的访问特性。

基类的初始化

为了初始化派生类从基类继承的数据成员,可以在派生类的构造函数使用冒号语法的参数初始式。
构造函数的执行顺序由系统决定,与参数初始式的顺序无关。

多继承

一个类有多个直接基类。
多继承的说明只需要在冒号后面加上以逗号分隔的基类名列表即可。

构造顺序:
也是先执行基类构造在构造派生类,因为有多个基类,所以基类的构造顺序取决于定义派生类时指定的各个基类的顺序,而与构造函数中参数初始式表的顺序无关。

同名函数:
若有两个基类有同名函数,则在派生类调用时要使用作用域运算符指定调用哪个基类继承的函数。

虚继承:
①非虚基类:
一个类不能被多次说明为一个派生类的直接基类,但可以多次成为间接基类:
基类A,
派生类B1继承了A,派生类B继承了A,
派生类C继承了B1和B2,那么相当于C生成了两份不同的基类A内存。
因为类C中有两个继承A的副本,所以称A为非虚基类。

②虚基类:
如果不想要两个相同的基类副本,避免产生二义性,那么可以用虚基类。
在上面的例子中,只需要在类B1和B2的定义中,对基类A的继承关键字前面加上virtual:
例: class B1: virtual public A
{
};
那么当C开始生成对象时不再分别生成两个A,而是由virtual指引直接生成一个A;

注意:调用C的构造函数时用参数初始式表驱动了虚基类A的构造函数,但是会忽略直接基类B1和B2的构造函数,也就是说B1和B2的构造函数不受C的参数初始式表影响。

虚函数与多态性

多态性是指一个名字多种语义或界面相同多种实现。
(函数重载是多态性的一种简单形式)

虚函数:冠以关键字virtual的成员函数,C++为类体系提供的一种灵活的多态机制;
动态联编:虚函数允许函数调用与函数体的联系在程序进行时才进行。

联编
联编是指一个程序模块、代码之间互相关联的过程。
根据联编的时机可以分为静态联编和动态联编:

①静态联编:
程序之间的匹配、链接在编译阶段、运行之前完成,即早期匹配。
②动态联编:
程序联编推迟到运行时才进行,即晚期联编。

类指针的关系

有四种关系:
①基类指针引用基类成员;(安全)
②派生类指针引用派生类成员;(安全)
③基类指针引用派生类成员;
④派生类指针引用基类成员;

基类指针引用派生类对象:
基类指针引用派生类中继承基类的成员是安全的,
但要引用派生类独有的成员时需要将基类指针强制类型转换成派生类指针。

派生类指针引用基类对象:
派生类指针只有通过强制类型转换后才能引用基类对象。

既可以对指针进行类型转换也可以直接对对象进行类型转换。

虚函数与基类指针

实现运行时的多态必须要用基类指针调用派生类的不同实现版本,只有用同一个基类指针访问虚函数才能称为运行时的多态。

当用基类指针调用基类和派生类的同名函数时,需要显示表达或类型转换才能调用到派生类的同名函数。
为了实现只用一种形式的语句就能让基类指针随着所指对象改变而调用当前版本的同名函数。实际上这表达了一种运行时的动态性质。

基类的同名函数冠以关键字virtual被说明为虚函数,之后派生类相同界面的同名成员函数默认拥有虚特性,可以省略virtual说明符。

原理:
①虚函数的函数原型定义了一种接口,这种接口在派生类中重载了不同的实现版本;
②虚函数和基类指针的解释机制实现了程序运行时的单界面、多实现版本;
③虚函数调用的解释依赖于调用它的对象类型,使基类指针指向不同派生类时自动完成this指针的转换。

注意:
①一旦一个成员函数被说明为虚函数,那么在整个类体系中所有界面相同的重载函数都保持虚特性。
②虚函数必须是成员函数,不能是全局函数或静态函数,因为虚函数的动态联编必须在类体系中依赖this指针来实现。
③不能将友元说明为虚函数,但虚函数可以是另一个类的友元;
④析构函数可以是虚函数,但是构造函数不能是虚函数。

当重载一般的函数时,只要求函数名相同;
重载虚函数时,要求函数名、返回类型、参数个数、参数类型和顺序完全相同。

虚析构函数

构造函数不能是虚函数,因为建立一个派生类必须从类层次的根开始一层层建立;
析构函数可以是虚函数,用于动态建立类对象时指引delete运算符选择正确的析构调用。若不用虚函数则当用基类指针动态生成派生类对象,析构时只会调用基类的析构函数而不会调用派生类的析构函数。

纯虚函数与抽象类

基类往往用于表示一些抽象的概念,因此可以在基类存在一些没有定义的虚函数,仅用于说明一个公共界面,由派生类重定义这些虚函数,提供各自的实现版本。

纯虚函数:
virtual 类型 函数名 (参数表) =0;

抽象类:
抽象类至少有一个纯虚函数。
若抽象类的一个派生类没有为继承的纯虚函数定义实现版本,那么它依然是抽象类。
对应的称为具体类。

注意:
①抽象类只能用作其他类的基类;
②抽象类不能建立对象;
③抽象类不能用作参数类型、函数返回类型或显式类型转换。

但是可以说明抽象类的指针和引用。
抽象类指针p通过获取不同的派生类对象地址来改变指向,利用多态性调用纯虚函数在派生类中的不同实现版本。

异质链表

为了把不同类的对象统一组织在一个数据结构中,可以定义抽象类指针数组或链表,其中的不同类类型元素都有共同的基类。

模板

在面向对象技术中,模板把函数或类要处理的数据类型参数化,表现为参数的多态性,这种机制成为类属

模板是表达逻辑结构相同但是具体元素数据类型不同的数据对象的通用行为,是开发大型软件、建立通用函数库和类库的工具。

类属类型: 运算对象的类型不是实际的数据类型而是参数化的类型;
函数模板: 带类属参数的函数;
类模板: 带类属参数的类;

实例化: 模板的类属参数被调用的实参的具体数据类型替换,由编译器生成一段真正可以运行的代码;
模板函数: 函数模板被实例化后;
模板类: 类模板被实例化后;

使用模板抽象了数据类型的程序设计技术被称为泛型程序设计

函数模板说明

定义函数模板和类模板前要进行模板说明,其作用是说明模板中使用的类属参数。
template < class T1,class T2,…,class Tn>
因为关键字class已经用于说明类定义,因此C++新标准支持使用新的关键字typename进行类属参数说明:
template < typename T1,typename T2,…,typename Tn>

函数模板和模板函数

函数模板定义由模板说明和函数定义组成。

所有在模板中说明的类属参数必须在函数定义中出现至少一次。

函数参数表中可以使用类属参数也可以使用一般数据类型。

重载函数模板

重载函数模板既可以用函数模板也可以用普通函数。

类属参数不能识别出数据类型隐形转换。

编译器的匹配顺序:
①函数名和参数类型最符合的函数;
②寻找一个匹配的函数模板进行实例化;
③通过类型转换进行参数匹配的重载函数;
以上都未能找到就报错,若有多于一个的匹配选择也报错。

类模板说明

一个类模板是类定义的一种模式,用于实现数据类型参数化的类。
类模板由模板说明和类说明构成:
template< typename Type>
class Tclass
{
//Tclass的成员函数
private:
Type DateMenber;
//…
};

类属参数必须在类说明中出现一次;

类模板的成员函数都是函数模板,实现语法和函数模板类似。

若函数模板在类中作为内联inline函数则不需要特殊说明;
若在类外定义,则每个成员函数都需要冠以模板参数说明,并在指定类名时要后跟类属参数:
template < typename T > Array < T >:: Array()
{
}

类模板和模板类

当类模板实例化时成员函数同时被实例化为模板函数。

说明一个对象时必须用实际类型参数替换类属参数,把类模板实例化为模板类,实际参数用尖括号相括。
例: Array< int > IntArray;

类模板作为函数参数

函数的形参可以是类模板或类模板的引用,调用时对应的实参是该类模板实例化的模板类对象。

当一个函数拥有类模板参数时,这个函数必定是函数模板。

类模板可以继承或派生

类模板和友元

①模板类的友元函数:

函数成为每个实例化的类模板的友元函数:
template< typename T >
class X
{
//…
friend void f();
}

对特定数据类型T,函数成为每个实例化的类模板的友元函数:
template < typename T >
class X
{
//…;
template < typename T > friend void f();
}

②模板类的友元类:
与上面声明友元函数类似,当前面加上 template < typename T>时则限定了对特定的数据类型T才会成为友元类。

类模板和静态成员

若类模板中定义了静态成员,则实例化后,各个模板类的所有对象共享该类的静态成员。

输入输出流

流类与流对象

在程序中,对数据的输入和输出都是以字节流实现的。
低级I/O是无格式的数据传输,高级I/O是格式化的数据传输,把字节序列解释为各种自定义或预定义的类型数据。
程序中可以建立或删除流类对象,从流类中获取数据是流对象的输入操作,称为“提取”,向流中添加数据是流对象的输出操作,称为“插入”。

流类库

是用继承方法建立起来的一个输入/输出类库,主要有两个平行的基类:streambuf类和ios类,这是两个低级的类,是所有流类的基类。
还有一个独立的类:iostream_init,主要用于流类的初始化操作。

缓冲区是有一个字节序列和两个指针组成,其中的输入缓冲区指针和输出缓冲区指针指向缓冲区当前插入或提取的位置。

streambuf:
提供对缓冲区的低级操作:设置缓冲区,操作缓冲区指针,从缓冲区提取或插入字节等。
filebuf(提供文件缓冲区的管理)、strstreambuf(使用字符串保存字节序列,提供在内存中提取和插入操作的缓冲区管理)、stdiobuf(提供标准的I/O文件的缓冲区管理)都是streambuf的派生类。

ios类:
提供流的高级I/O操作。
ios类是抽象基类,提供输入、输出所需的公共操作。
①包含了一个指向streambuf的指针,提供格式化标志用于格式化I/O处理,对I/O的错误进行处理,设置文件模式,以及提供建立相关流的方法。
②派生了两个类:输入流类istream、输出流类ostream

istream:提供了流的大部分输入操作,主要对streambuf类进行插入时格式化和非格式化的转换,并对所有系统预定义的输入流重载流提取运算符">>",它有三个派生类:1、ifstream文件输入流类;2.istrstream是字符串输入流类;3、istream_withassign是重载了赋值运算符的输入流类。

ostream基本与上同理。ofstream、ostrstream、ostream_withassign。

iostream由istream和ostream派生出,有三个派生类:fstream、strstream、stdiostream。

头文件

①iostream:基本信息;
②iomanip:格式化I/O的带参数操纵算子;
③fstream:处理文件的相关信息,提供建立文件、读、写文件的各种操作接口。

标准流

对于C++标准流连接的外部设备都是文本形式的设备,标准流的主要工作是对内存中基本数据类型对象与文本进行翻译和传输,能够处理基本数据类型对象与文本之间I/O的流类称为文本流。

格式控制

1、设置标志字,格式控制函数。
2、格式控制符。

串流

字符串流:链接内存的流对象,用文本流I/O的方式操纵字符串,即流对象可以连接字符串。

串流在提取数据时对字符串按变量类型进行解释,插入数据时把类型数据转换成字符串。
stringstream、istringstrea、ostringstream类库中定义的istringstream和ostringstream类支持在string对象输入输出数据,需要包含sstream头文件。

strngstream这个类对于字符串和数值互相转换非常有用:
int a; //也可以是double等等;
string s;
stringstream(s)>>a;

拓展:字符串和其他类型的转换:
函数库cstdlib
atoi() , atol() ,atof() ;

文件与流

顺序存取文件、随机存取文件。

C++中要进行文件的读写需要建立一个文件流对象,然后把这个流对象和实际文件相关联(称为打开文件),连接后文件就可以通过流类的各种功能对文件进行操作,一个文件用完后需要关闭文件。

C++有三种文件流:
ifstream头文件包含文件输入流类ifstream;
ofstream、fstream。

打开文件:
建立文件流对象、与外部文件关联、指定文件的打开方式。

流类 对象名
对象名.open( 文件名 ,方式 )

流类 对象名 (文件名 ,方式 )
对象名是用户定义标识符,流对象名。
文件名是用字符串表示的外部文件的名字,可以是已经赋值的串变量或用双引号相括的串变量,要求用文件全名,若文件不在当前目录中则要写出路径。

关闭文件:
把缓冲区数据完整地写入文件,添加文件结束符,切断流对象和外部文件的链接。
关闭文件用fstream类的成员函数close。
当一个流对象的生存期结束,系统也会自动关闭文件。

文本文件:
文本文件是顺序存取文件。
描述一个对象的信息称为一个记录,一个记录通常放在一个用换行符分隔的逻辑行中;
记录的每个数据项之间可以用空白符、换行符、制表符等作为分隔符。

没有指定文件代码性质的默认为文本方式。

一般的,流对象打开文件时指针会自动指向文件头,但如果文件是重用的则必须要重新确定指针的位置。

流指针经过对文件的操作后,流对象的错误状态字被修改有可能会影响新的操作,那么可以用clear清除错误状态字,使其恢复正常。

一个文本文件一旦建立后便不能随意插入数据,但可以在文件尾部追加数据,应以app方式打开文件。

二进制数据文件:
二进制数据文件是以基本数据类型数据的二进制代码形式存放,二进制数据流不对写入或读出的数据进行格式转换。数据的解释由内存对象的类型决定。

因为二进制数据文件的存储格式和内存格式一致,存储长度仅与数据类型相关,所以二进制数据文件中的数据易于修改而不损坏其他数据。因此二进制数据文件又称为类型文件,由数据类型解释的一个单元通常包含若干字节,称为一个文件的”记录“,或文件的”元素“。

二进制数据文件的读写完全由程序控制,一般的字处理工具不能参与编辑,因为无需转换格式所以可以高速处理数据。

C++用binary方式打开二进制数据文件,读取数据用istream类的read函数,写入数据用ostream类的write函数。

①istream类操作读指针的函数:
istream & seekg (long pos );
istream & seekg (long off , ios:: seek_dir dir );
long tellg();
②ostream类操作写指针的函数:
以上类似, 换成 seekp tellp

二进制数据文件指针指向文件尾ios::end不一定能表示最后一个完整记录的结束,处理方法:
①用文件长度(记录个数)控制读写操作;
②在文件尾添加一个特殊的记录作为文件结束的标志。

异常处理

C++异常处理通过三个关键字实现:
被调用函数直接检测到异常条件的存在,并用throw语句抛出一个异常;
在上层调用函数中使用try语句检测函数调用是否引发异常;
被检测到的各种异常由catch语句捕获并作出相应的处理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值