多继承,虚基类,模板编程不考。考试范围10-15章。
第10章:类和对象
10.1 类和对象的定义
类是一种类型,该类型的变量成为对象。
一个类的定义:
private可缺省。类定义后的大括号要有分号;
class Person
{
private:
string name;
int age;
public:
Person(string a,int b)
{
name=a;
age=b;
}
string GetName()
{
return name;
}
int GetAge()
{
return age;
}
};
关于类内外访问权限:类内都可访问,类外只有public的可访问。
若想在类内声明,类外定义:
格式是:
返回类型 类::函数名(参数列表)
class Person
{
private:
string name;
int age;
public:
Person(string a,int b)
{
name=a;
age=b;
}
string GetName();
int GetAge();
};
string Person::GetName()
{
return name;
}
int Person::GetAge()
{
return age;
}
关于对象的存储空间:
定义对象后,对象数据成员占用不同的存储空间;该类的所有对象的同一成员函数共享同一代码空间。
定义类和对象的相关说明:
1、类中数据成员的类型:自身类的对象不能做数据成员。
2、类的使用在前,定义在后,则需要声明。如:
class B;
3、定义对象有三种方法:
定义完成后定义对象;(常用)
定义类的同时定义对象:(如代码有三个对象了)
class A
{
...
}a1,a2,a3;
定义无类名对象(只能定义一次,没啥用)
class
{
...
}a1,a2,a3;
4、结构体与类的区别:
结构体缺省是public;类中缺省是private;
结构体是类的特例。
10.2 初始化对象,撤销对象
对类公有成员的初始化:
可以直接这样。
Person p={"aaa",12};
构造函数
调用时机:产生新对象时自动调用。
//类内定义:
类名(参数列表)
{...}
//类外定义
类名::类名(参数列表)
{...}
//eg:
//类内:
Person(string a,int b){name=a;age=b;}
//类外:
Person::Person(string a,int b){name=a;age=b;}
构造函数特点:
1、构造函数是成员函数,可在类内/外。
2、构造函数:函数名与类名相同,无返回值。
3、构造函数可重载。
4、一般是public的。
5、创建对象时系统自动调用函数。
析构函数
撤销对象时,自动调用。
//类内:
~ClassName(){...}
//类外:
ClassName::~ClassName(){...}
析构函数特点
1、是成员函数,可在类内/外 定义
2、一般是public
3、是特殊函数,函数的名字是在类名前加~;无返回值,无参数
4、不可重载。
5、可被显式调用,也可被系统自动调用,其中:
对象是系统自动创建的,在对象作用域结束时系统自动调用析构函数。
对象是new创建的,在使用delete释放对象时,delete会自动调用析构函数。
会自动调用析构函数。
缺省构造函数
定义类时若没定义构造函数,则系统自动生成缺省构造函数,此构造函数不进行任何操作。
缺省构造函数有两种形式:没有参数/有缺省值。缺省构造函数只能有一个。否则会矛盾。
如下,编译系统不知道调用哪个。
Date::Date()
{year=2021;month=6;day=16;}
//
Date::Date(int y=2017,int m=6;int d=16)
{year=y;month=m;day=d;}
若已经定义了构造函数,系统不会自动生成缺省构造函数。
缺省析构函数
要注意的就是:若类中有动态申请的数据空间就要自己定义析构函数(delete)。
拷贝构造函数
若用户不定义拷贝构造函数,系统会自动生成一个缺省的拷贝构造函数;
当类中有动态申请的数据空间时必须自己定义拷贝构造函数,即显式地定义(也要显式地定义相应地析构函数)。
内联函数,外联函数
类内定义的是内联,类外定义的是外联。
在类外也可以定义内联函数:类内声明,类外定义,要有inline;
如:
inline Complex::Complex(double x){...}
构造函数可以重载,一般成员函数也可以重载。
构造函数和对象成员
- 已定义的类的对象可以作新类的成员
- 产生新定义类的对象时,需对它的对象成员进行初始化,通过其他对象成员的构造函数实现;
- 先调用对象成员的构造函数,载调用对象自身的构造函数,析构函数顺序相反。
即,若有类A,类B,A为B的成员,构造B时先调用A的构造函数,再B。
成员构造好了才能构造自己,析构相反
this指针
this指针是隐含于每一个类的成员函数中的特殊指针,指向调用该函数的对象。
当对象调用成员函数时,this指针自动将对象自身的地址传给成员函数。
第11章:类和对象的其他属性
11.1静态成员
一个类的不同对象的数据成员的存储空间独立,但如果类的一个成员定义成静态型,则所有对象的该成员公用统一存储空间。
静态数据成员是属于类的。
静态数据成员的定义和初始化
class A
{
int a;
static int b;//类内定义
public:....
}
int A::b=10;//类外初始化,不用再写static
必须类外初始化。
静态成员函数
- 属于类;
- 静态成员函数只能直接访问静态数据成员。
如:
class A
{
int a;
static int b;
public:
A(int aa=1){a=aa;}
void fc(A a1)
{
cout<<"a=="<<a1.a<<" b=="<<b<<endl;//这里因为b是静态数据成员,所以直接b而不用“那个对象的”b;
}
};
int A::b=10;
int main()
{
int x;cin>>x;
A a(x);
a.fc(a);
}
//输入3
//输出
a==3 b==10
11.2友元
- 类具有封装型和隐蔽性
- 只有类内可以访问私有成员,类外,用公有函数接口访问私有成员
- 用友元函数可以使函数访问一个类所有对象的私有成员
如:
class A
{
friend void fc(...);//fc函数是类A的友元函数
};
void fc()
{
class A a;
...
}
函数fc是类A的“朋友”,所以类A所有的对象中的私有成员可以被fc访问
友元会破坏类的数据成员的隐蔽性
ps:若将主函数设置为一个类的友元函数:
friend int main();
则整个main函数可以随意使用这个类的私有成员。
友元函数相关说明
- 友元函数不是成员函数;所以可以把友元函数声明在类的任何一个地方(public,private,protected)效果一样;
- 目的:提高效率
- 慎用。破坏类的隐蔽性。
- 普通函数,别的类的成员函数也可以作为友元函数。
友元类
类A可做类B的友元。这样A的所有成员函数是B的友元函数。
如:
A是B的友元,所以A的函数可以访问B的私有成员
就像A在class B这里认证了朋友,然后A就可以任用B的私有成员
注意引用性说明
class A;//对类A的声明;因为定义在下面。
class B
{
friend class A;
...
};
class A
{
public:
void f1(){
}
};
第12章:继承和派生
12.1 继承的基本概念和12.2单一继承
类A加以拓展得到了B,称为类B继承了A或A派生了B。
则:A是父类,基类;B是子类,派生类。
单一继承的一般格式:
B公有继承A如:
class B:public A
继承方式有:public,private,protected;
public:继承后基类只有public的成员可以访问,其他的保持原类型,且不能访问;
private:继承后A中的public和protected变成了private,但不可直接访问。
protected:继承后A中的public和protected变成protected,但不可直接访问。
总结:只有public继承是不改变A中成员原来特性且public的可以访问。
private和protected成员区别
- private特性的数据成员不管如何继承都无法直接访问。
- 根据不同的派生方式protected成员地直接访问特性可能被传递到派生类中去。
private一定不能,protected有可能
总结
- public派生,基类的特性全不变;
- private派生,protected—>private,无法被直接访问。因此,在派生链中,一旦出现private继承,父类成员的类内直接访问特性无法在后面的派生中传递下去。
- 类中protected成员的优点,可隐藏数据,又可将类内直接访问特性传递到派生类去。但private后者不行。
12.4 基类成员的初始化
派生类构造函数的一般格式:
ClassName::ClassName(arges):Base1(arg1),Base2(arg2)
Base1,Base2是基类的名称,arg1,arg2是调用基类构造函数的实参列表。
调用顺序:Base1——Base2——…(冒号后面的顺序)
最后执行派生类自身的构造函数体。
即,先构造基类再构造派生类
若派生类中包含对象成员,则在派生类的构造函数初始化列表中要有:基类和对象成员的构造函数,如:
12.5 二义性和支配规则
二义性
若两个基类AB中都有变量x,则,在使用的时候,A的x用A::x,B的用B::x;
解决访问二义性。
::是一种限定作用域的运算符,不可嵌套使用。如:A::B::C::x,是错的!
C++规定任意基类在派生类中只能被继承一次。
即B只能继承一次A。
支配规则
基类和派生类出现同名成员时,派生类成员优先。
后来居上
12.7 访问基类成员和对象成员的成员
成员函数中使用:基类和对象成员 的成员情况不同。
基类的成员直接使用,对象的成员要用如:a.x 的形式(访问对象a的x成员)
12.8赋值兼容
可以将公有派生类对象赋值给基类对象,反之不可。
会把派生类对象中从基类继承来的成员赋给基类对象。
第13章:多态性
13.1函数重载和13.2运算符重载
多态:静态多态和动态多态。
静态:函数重载,运算符重载;
动态多态:程序执行过程确定关系,如动态确定函数的调用关系。通过类的继承和虚函数实现的。
重载运算符的限制
- 对已有的运算符重载
- 不可改变优先级,结合性,语法结构。
两种函数重载
重载为类的成员函数:
类内:
<函数返回值类型>operator<重载运算符>(参数列表)
eg:
A operator +(const A&a) //返回一个类
类外:
<函数返回值类型><类名>::operator<重载运算符>(参数列表)
eg:
A A::operator +(const A&a)
重载为友元函数:friend要放在前面~
类内:
friend <函数返回值类型>operator<重载运算符>(参数列表)
eg:
friend Complex operator +(const Complex &c1)
类外:
<函数返回值类型>operator<重载运算符>(参数列表)
两种重载方法比较:
成员函数实现,则前面的是对象,如a1+a2,即a1.operator+(a2);因此,用成员函数重载二元运算符,若数字放前面有风险。
友元函数,两个都是参与计算的,如a1+a2,即operator+(a1,a2),因此无所谓谁在前后。
即,二元——友元,一元——成员。一元里最典型的是赋值运算符,用成员函数重载!
两个才有朋友
若运算符重载返回对象值,有两种写法,*this和自己在函数内定义的同类型对象。
//1
Complex Complex::operator+=(const Complex &c)
{
r+=c.r;
i+=c.i;
return *this;//!!返回我自己
}
//2
Complex Complex::operator+=(const Complex &c)
{
r+=c.r;
i+=c.i;
Complex temp=*this;
return temp;//!!也是返回我自己
}
结论:
1、返回本类对象时,可以用对象自身和局部对象做为返回值(有时需要定义拷贝构造函数)。
2、返回对象的引用时,不能用局部对象做为返回值。
其他运算符的重载
前置++,后置++之类,是一元,用成员函数重载。
前置
<函数返回值><类名>::operator++()
后置 有int是后置++;
<函数返回值><类名>::operator++(int)
其他总结:
- 只能用友元重载的:<< >>
- 赋值运算符=重载函数不能被派生类继承!(唯一不能继承的)
- 类型转换函数:只能成员
13.3静态联编
静态联编:联编出现在编译连接阶段,即函数调用关系确定是在程序执行之前
也成为早期联编。
13.4动态联编和虚函数
- 运行阶段才确定函数的调用关系
- 滞后联编。实现动态多态。
- 必须将类的成员函数定义为虚函数,才能实现动态联编。
将成员函数定义为虚函数格式
virtual <函数返回值类型><函数名>(参数列表)
书上13.22的例子:每一个求面积的成员函数都是虚函数。在运行的时候调用哪个函数取决于是哪个类型的指针,如调用正方形的Area函数,那就要用正方形类的指针p进行p.Area()操作。
两种调用方式:
&.
*->且形参是基类对象指针
//1
double CalcArea(Point &p)
{
return(p.Area());
}
//2
double CalcArea(Point *p) // 形参是基类对象的指针
{
return(p->Area()); //通过指针调用成员函数
}
整体代码,可加强理解:
#include <iostream>
using namespace std;
class Point
{
protected:
double x, y; // 点的坐标值
public:
Point(double a = 0, double b = 0){ x = a; y = b; }
virtual double Area(){ return 0.0; } // 虚函数1
};
class Rectangle :public Point // 定义长方形类,继承点类
{
protected:
double x1, y1; // 长方形右下角点的坐标值,基类中x, y为左上角坐标点
public:
Rectangle(double a = 0, double b = 0, double c = 0, double d = 0) :Point(a, b)
{
x1 = c; y1 = d;
}
virtual double Area(){ return (x - x1)*(y - y1); } // 虚函数2
};
class Circle :public Point
{
protected:
double r; // 半径,基类中x, y为圆心坐标点
public:
Circle(double a = 0, double b = 0, double c = 0) :Point(a, b)
{
r = c;
}
virtual double Area(){ return 3.14*r*r; } // 虚函数3
};
double CalcArea(Point &p)
{
return(p.Area());
} // A
int main()
{
Point p(1, 2);
Rectangle r(0, 0, 1, 1);
Circle c(0, 0, 1);
cout << CalcArea(p) << '\t' << CalcArea(r) << '\t' << CalcArea(c) << '\n';
return 0;
}
------------------------------------------------------
// …… // 类的定义部分一样,在此省略
double CalcArea(Point *p) // 形参是基类对象的指针
{
return(p->Area()); //通过指针调用成员函数
}
int main()
{
Point p(1, 2);
Rectangle r(0, 0, 1, 1);
Circle c(0, 0, 1);
cout << CalcArea(&p) << '\t' << CalcArea(&r) << '\t' << CalcArea(&c) << '\n'; // 实参是指针
return 0;
}
同名的虚函数会覆盖、或称为重写,把基类的函数换掉。
虚函数总结
- 派生类虚函数必须与基类虚函数同名,且参数的类型,个数,顺序必须一致,否则属于函数重载。
- 基类虚函数virtual不可缺。
- 通过基类对象的指针或引用调用虚函数。
- 虚函数必须是类的成员函数
- 构造函数不可虚函数,析构可。
虚析构函数
如果类的构造函数中有动态申请的存储空间,在析构函数中应释放该空间。
此时,建议将析构函数定义为虚函数,以便实现通过基类的指针或引用撤消派生类对象时的多态性。
13.5纯虚函数和抽象类
Li1322.Shape即纯虚函数。
实现依赖于不同的派生类,就可以把基类中的虚函数定义为纯虚函数。至少包含一个纯虚函数的类称为抽象类。
抽象类的派生类中,应写出纯虚函数的实现,否则派生类依然是抽象类。
抽象类只能做基类。
定义纯虚函数的一般格式:(参数列表)=0
virtual <函数返回值类型><函数名>(参数列表)=0
关于纯虚函数和抽象类的说明:
抽象类只能做派生类的基类,不能定义抽象类的抽象
正常使用的角度:抽象类没有函数体。
语法角度:可以有。
语法上没错,但是没用
第14章:输入输出流
14.1流类和流对象
数据之间的传输操作即流。
程序中,对数据的输入输出以字节流实现。
数据流分为文本流和二进制流:
文本文件:数据是ASCII码;
二进制流:内存映像。
缓冲区:内存中的临时存储区,匹配不同部件数据传输的差异。
流类:标准流,文件流和字符串流;
14.2标准IO流和流对象
好多啊,都不知道要考哪里
友元函数重载流运算符:
注意引用和别名out,in;
friend ostream & operator <<(ostream & out, T & data);//输出的运算符
//对应cout<<a; cout是第一个参数,a是第二个参数
friend istream & operator >> (istream & in,T & data);//输入
//同理
函数部分:
return 别名;
14.4文件处理
三个类:
ifstream/ofstream/fstream
文件看成有序的字节流。编码方式有文本文件,二进制文件;
存取方式:顺序读写文件,随机读写文件;
文件操作的基本步骤:
streamclass fileObj(filename,openMode);
fileObj.open(filename,openMode);
...
fileObj.close();
打开方式:
//1 建立对象的同时连接文件
istream infile("data.dat",ios::in);//输入
ofstream outfile("d:\\data.dat",ios::out);//输出
//2 先建立流对象,再调用open函数连接外部文件——我就会这种
ofstream outfile;
outfile.open("data",ios::out);
既能输入又能输出:
fstream file("data",ios::in|ios::out);
测试文件是否打开,关闭:
//开
outfile.open("data");
if(!outfile) cerr<<"error:unable to open!";
//关
outfile.close();
if(outfile.fail()) cerr<<"error to close!";
文本文件
每一个字节都是ASCII码,是顺序存储文件,默认打开方式是文本文件。
打开文本文件并输入——写文件
void main()
{
ofstream out("text");
if(!out) {cout<<"error"<<endl;return;}
out<<"1 1 1";
out.close();
}
从文件读入——到程序
遇到空格会停止的。
void main()
{
instream in("text");
if(!in) {cout<<"error!"<<endl;return;}
int i;in>>i;//把文本第一个字符当作int型读入;
float a;in>>a;
in.close();
}
二进制文件:可保密。
用二进制方式打开文件:
ios::binary;
文件追加操作:ios::app
file.open("data2",ios::app);
在file的那个文件后追加data2;
第15章:C++模板
模板:使用无类型参数来产生一系列函数或类的机制。
模板:函数模板,类模板;
函数模板定义:
template T
返回值类型 函数名(参数)
{...}
如:
类模板
stl中的容器就是类模板。
优点:
- 克服大量不同函数,相似代码的问题
- 克服函数重载重写几个函数的繁琐
- 克服宏定义不能进行参数类型检查的弊端
缺点:调试困难