【面向对象编程】知识点总结

多继承,虚基类,模板编程不考。考试范围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){...}

构造函数可以重载,一般成员函数也可以重载。

构造函数和对象成员

  1. 已定义的类的对象可以作新类的成员
  2. 产生新定义类的对象时,需对它的对象成员进行初始化,通过其他对象成员的构造函数实现;
  3. 调用对象成员的构造函数,载调用对象自身的构造函数,析构函数顺序相反。

即,若有类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函数可以随意使用这个类的私有成员。

友元函数相关说明

  1. 友元函数不是成员函数;所以可以把友元函数声明在类的任何一个地方(public,private,protected)效果一样;
  2. 目的:提高效率
  3. 慎用。破坏类的隐蔽性。
  4. 普通函数,别的类的成员函数也可以作为友元函数。

友元类

类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有可能

总结

  1. public派生,基类的特性全不变;
  2. private派生,protected—>private,无法被直接访问。因此,在派生链中,一旦出现private继承,父类成员的类内直接访问特性无法在后面的派生中传递下去。
  3. 类中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运算符重载

多态:静态多态和动态多态。
静态:函数重载,运算符重载;
动态多态:程序执行过程确定关系,如动态确定函数的调用关系。通过类的继承和虚函数实现的。

重载运算符的限制

  1. 对已有的运算符重载
  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)

其他总结:

  1. 只能用友元重载的:<< >>
  2. 赋值运算符=重载函数不能被派生类继承!(唯一不能继承的)
  3. 类型转换函数:只能成员

13.3静态联编

静态联编:联编出现在编译连接阶段,即函数调用关系确定是在程序执行之前
也成为早期联编

13.4动态联编和虚函数

  1. 运行阶段才确定函数的调用关系
  2. 滞后联编。实现动态多态。
  3. 必须将类的成员函数定义为虚函数,才能实现动态联编。

成员函数定义为虚函数格式

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;
}

同名的虚函数会覆盖、或称为重写,把基类的函数换掉

虚函数总结

  1. 派生类虚函数必须与基类虚函数同名,且参数的类型,个数,顺序必须一致,否则属于函数重载
  2. 基类虚函数virtual不可缺。
  3. 通过基类对象的指针或引用调用虚函数。
  4. 虚函数必须是类的成员函数
  5. 构造函数不可虚函数,析构可。

虚析构函数
如果类的构造函数中有动态申请的存储空间,在析构函数中应释放该空间。
此时,建议将析构函数定义为虚函数,以便实现通过基类的指针或引用撤消派生类对象时的多态性。

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中的容器就是类模板。

优点:

  1. 克服大量不同函数,相似代码的问题
  2. 克服函数重载重写几个函数的繁琐
  3. 克服宏定义不能进行参数类型检查的弊端

缺点:调试困难

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

karshey

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

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

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

打赏作者

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

抵扣说明:

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

余额充值