C++类和对象

C++面向对象的三大属性:封装、继承、多态。对象包括属性和行为。

1、封装

1.1封装的意义

封装是C++面对对象三大特性之一。

封装的意义:

  • 将属性和形为作为一个整体,表现生活中的事物。
  • 将属性和形为加以权限控制。

语法:class 类名{访问权限:属性/ 行为};

封装意义一:

将属性和行为作为一个整体

示例:

1、创建一个圆类,求圆的周长

#include<iostream>
using namespace std;
const double PI = 3.14;
class circle
{
	//访问权限
	// 公共权限
public:
		//属性:半径
		int m_r;
	// 行为
		//获取圆的周长
		double calculateZC()
		{
			return 2 * PI * m_r;
		}

};

int main()
{
	//通过circle类创建一个具体的circle对象
	circle c1;
	c1.m_r = 10;
	cout << "圆的周长为:" << c1.calculateZC()<<endl;
	return 0;
}

2、设计一个学生类,属性有姓名和学号,可以给姓名和学号赋值,可以显示学生的姓名和学号。

#include<iostream>
#include<string>
using namespace std;
class student {
public://公共权限
	//类中的属性和形为,统一称为成员
	//属性:成员属性,成员变量
	//行为:成员函数,成员方法
	//
	string name;
	string student_id;
	void showiinfo()
	{
		cout<<"学生姓名为:" << name << endl;
		cout<<"学生学号是:" << student_id << endl;
	}

};
int main()
{
	//创建一个具体的学生,实例化
	student a;
	a.name = "张三";
	a.student_id = "TS20060190P31";
	a.showiinfo();
	return 0;
}

封装意义二:

类在设计时,可以把属性和行为放在不同的权限下,加以控制

访问权限有三种:

  1. 公共权限 public (类内类外都可以访问)
  2. 保护权限 protected (类内可以访问,类外不可以访问),在继承的时候,父亲的保护权限儿子可以访问。
  3. 私有权限 private  (类内可以访问,类外不可以访问),继承的时候,父亲的私有权限不可以被儿子访问。
#include<iostream>
#include<string>
using namespace std;
class person {
// 公共权限
public:
	string name;
//保护权限
protected:
	string m_car;
// 私有权限
private:
	int password;
public:
	void func()
	{
		name = "张三";
		m_car = "拖拉机";
		password = 123457;
	}
};
int main()
{
	person p1;
	p1.name = "李四";
	//不可以被访问
	//p1.m_car = "车";
	return 0;
}

 struct 和class的区别

在c++中,struct和class的唯一区别是默认的访问权限不同

  • struct的没有访问权限的设定,因此所有的属性都是公开的。
  • class的默认访问权限是私有的。

1.2 类的构造函数和析构函数

为什么需要构造函数?

c++的目标之一是想让使用类对象的时候和使用标准型一样,但是从以上代码看,我们没法像初始化int或者结构体一样初始化类对象。没法初始化的原因在于:数据的访问权限是私有的,有的人说设置成公共权限就好了,但是这又违背了类的一个主要初衷:数据隐藏。最好的办法是在创建对象的时就进行初始化,因此C++提供了一个特殊的成员函数--类构造函数,用于创建新对象,将值传给他们的成员。

构造函数有三种形式:

  • 根据有参数和无参数分为:有参构造函数,无参函数(默认构造函数)
  • 按照类型分:普通构造函数,拷贝构造函数

值得注意的是:编译器在默认的情况下会提供默认构造函数、有参构造函数和拷贝构造函数。

但是以下两种特殊情况:

  1. 当用户自定义了有参构造函数后,编译器不再提供默认构造函数,但是会提供拷贝构造函数
  2. 当用户自定义了拷贝构造函数后,编译器不再提供默认构造函数和有参构造函数。

1.2.1 定义构造函数

#include<iostream>
using namespace std;
//构造函数的分类及调用
//分类
//按照参数分类:有参构造,无参构造(默认构造)
//按照类型分:普通构造函数,拷贝构造函数
//构造函数的名称和类名一样
class person {
public:
	person()
	{
		cout<<"无参构造函数的调用" << endl;
	}
	person(int a)
	{
		age = a;
		cout << "有参构造函数的调用" << endl;
	}
	//拷贝构造函数
	person(const person &p)
	{
		//将传入的人身上的所有属性传到我身上
		age=p.age; 
		cout << "拷贝构造函数的调用" << endl;
	}
	~person()
	{
		cout<<"析构函数的调用" << endl;
	}
	int age;
};

构造参数--拷贝函数

拷贝函数是一种特殊的构造函数,使用同一类中已经创建过的对象来初始化新的对象。拷贝函数通常出现在以下几种情况:

  • 使用一个创建好的对象初始化一个新对象
  • 值传递的方式给函数传值
  • 值方式返回函数局部对象
//第一种: 使用创建好的对象创建一个新对象
void test1()
{
	
	Person p1(10);
	//使用已经创建好的p1对象创建p2
	Person p2(p1);
}
//第二种:将对象作为参数传递给函数
void test2()
{
	Person p1(10);
	//将p1作为函数参数传递给dowork函数
	dowork(p1);
}
// 第三种:返回局部对象
Person _dowork()
{
	Person p1(10);
	return p1;//将局部对象当做返回值返回
}
void test3()
{
	Person p = _dowork();
}

深拷贝与浅拷贝

浅拷贝指的是拷贝函数在进行对象初始化时进行的一个简单的赋值拷贝操作,复制的指向对象的指针,比如说我们用拷贝构造的方式创建了一个对象p2 ,Peroson p2(p1),虽然p2和p1的对象名不一样,但是他们指向同一块内存。

当我们成员变量的内存空间在堆区时,也就是我们用new创建成员时,进行浅拷贝创建对象会造成堆区的重读释放,编译器会报错,如下所示:

#include<iostream>
using namespace std;
class Person {
public:
	int m_age;
	int* m_height;

	Person(int age, int height) {
		cout << "Person类的有参数构造函数" << endl;
		m_age = age;
		m_height = new int(height);// 用new在堆区开辟了一块新的变量空间,需要在析构函数中释放
	}
	~Person()
	{
			if (m_height != NULL)
			{
				delete m_height;//释放掉
				m_height = NULL;//预防野指针出现
			}
	}
	
};
void test()
{
	Person p1(18, 180);
	Person p2(p1);//利用拷贝函数创建新对象
}

解决浅拷贝出现问题的办法时利用深拷贝,深拷贝就是重新在堆区重新申请空间,进行拷贝操作

#include<iostream>
using namespace std;
class Person {
public:
	int m_age;
	int* m_height;

	Person(int age, int height) {
		cout << "Person类的有参数构造函数" << endl;
		m_age = age;
		m_height = new int(height);// 用new在堆区开辟了一块新的变量空间,需要在析构函数中释放
	}
	Person(const Person &p)
	{
		m_age = p.m_age;
		m_height = new int(*p.m_height); //在堆区重新申请空间
	}
	~Person()
	{
			if (m_height != NULL)
			{
				delete m_height;//释放掉
				m_height = NULL;//预防野指针出现
			}
	}
	
};
void test()
{
	Person p1(18, 180);
	Person p2(p1);//利用拷贝函数创建新对象
}
int main()
{
	test();
	return 0;
}

1.2.2 调用构造函数

构造函数的调用也有两种方式:

  • 显示调用
  • 隐式调用
// 显示法
	person p2 = person(10);
	 显示法拷贝构造
	person p3 = person(p2);
	//隐式转换法
	person p4 = 10;//person p4 = person(10);
	//显示拷贝构造
	person p5 = p4;
//隐式拷贝构造
person p5(p4);

1.3 类对象作为类成员

c++中类的成员可以另一个类的对象,称之为对象成员。当其他类作为成员,先构造其他类,再构造自身,析构和构造相反

构造函数初始化列表

语法:

类名():变量1(初始值),变量2(初始值) {}

异同:

  • 两种函数的效果都是一样的,都是实现类成员变量的初始化
  • 初始化列表构造函数显式的初始化类的成员,而没有使用初始化列表的构造函数是对类成员的赋值。

参考链接:初始化列表构造函数

class Example {
public:
	int a;
	int b;
	//构造函数内部赋值
	Example()
	{
		a = 0;
		b = 1;
	}
	//构造函数初始化列表
	Example():a(0),b(1)
	{  }
};

什么时候必须使用初始化列表构造函数?

  1. 成员类型是没有默认构造函数的类。
  2. const成员或引用类型的成员。
class Phone {
public:
	
	Phone(string name)
	{
		m_pname = name;
	}
	string m_pname;
};
class Person {
public:
    //Phone类没有默认构造函数,只能用初始化列表构造函数
	Person(string name, string pname):m_name(name),m_phone(pname)
	{
		
	}
	string m_name;
	Phone m_phone;
};

1.3 静态成员

静态成员就是在成员变量和成员函数前面加上static关键字。

静态成员分为:

  • 静态成员变量
  1. 所有对象共享一份数据
  2. 在编译阶段分配内存
  3. 类内声明,类外初始化
#include<iostream>
#include<string>
using namespace std;
class Person {
public:
	static string name;// 类内声明
};
string Person::name = "张三"; //类外初始化
int main()
{
	Person p;
	cout << p.name << endl;
	Person p2;
	p2.name = "李四"; //当p2改变了变量的时候,所有的类对象相应的数据都被改了
	cout << p.name << endl;
	cout << p2.name << endl;

}
  • 静态成员函数
  1. 所有对象共享同一个函数
  2. 静态函数只能访问静态成员变量
#include<iostream>
#include<string>
using namespace std;
class Person {
public:
	static string name;// 类内声明
	int age;
	static void func()
	{
		name = "张三";
		//age = 100; 不可以访问非静态成员变量
		cout << "静态成员函数的调用" << endl;
	}
};
string Person::name = "张三"; //类外初始化
int main()
{
	Person p;
	//两种静态成员函数的访问方式
	//第一种根据类名访问
	Person::func();
	//第二种根据对象访问
	p.func();
}

1.4 this指针

C++中成员变量和成员函数是分开储存的只有非静态成员变量属于类对象上,静态成员变量、非静态成员函数、静态成员函数都不属于类对象。(这里值得一提的是,即使是空对象,也会占用一个内存,供编译器区分。)也就是说多个对象会共用一份非静态成员函数代码,那么非静态成员函数如何区分是哪个对象在调用自己呢?

c++提供特殊的对象指针--this指针。this指针指向被调用的成员函数所属类对象。不需要定义,直接使用。

this指针的用途:

  • 当形参和成员变量重名时,用this指针来区分
  • 在类的非静态成员函数中返回对象本身,用return *this
#include<iostream>
using namespace std;
class Perosn {
public:
	int age;
	Perosn(int age)
	{
		age = age;
	}
};
void test01()
{
	Perosn p1(18);
	cout << "p1的年龄:" << p1.age << endl;
}
int main()
{
	test01();
	return 0;
}

p1的年龄:-858993460

当我们使用this指针后:

#include<iostream>
using namespace std;
class Perosn {
public:
	int age;
	Perosn(int age)
	{
		this->age = age;
	}
};
void test01()
{
	Perosn p1(18);
	cout << "p1的年龄:" << p1.age << endl;
}
int main()
{
	test01();
	return 0;
}

p1的年龄:18

#include<iostream>
using namespace std;
class Perosn {
public:
	int age;
	Perosn(int age)
	{
		this->age = age;
	}
	Perosn& addage(const Perosn& p)
	{
		this->age += p.age;
		return *this; //这里返回的是一个类对象
	}

};
void test01()
{
	Perosn p1(18);
	
	Perosn p2(10);
	//由于函数返回的是对象,因此我们可以链式编程
	p2.addage(p1).addage(p1).addage(p1);
	cout << "p1的年龄:" << p2.age << endl;
}
int main()
{
	test01();
	return 0;
}

p1的年龄:64

const修饰成员函数

常函数:

  • 成员函数后加const后称这个函数为常函数
  • 常函数内不可以修改成员属性
  • 成员属性声明时加关键字mutable后,在常函数中依旧可以修改

常对象:

  • 声明对象前加const称该对象为常对象
  • 常对象只能用常函数,因为普通函数会修改对象的属性

加上mutable后,

 1.5 友元

友元既可以访问公有属性,又可以访问私有属性。

定义:

1、全局函数做友元

#include<iostream>
#include<string>
using namespace std;
class Building{
	//友元函数的声明 Goodfriend是全局部函数
	friend void Goodfriend(Building& p);
public:
	string m_sittingroom;//公有属性
	//构造函数初始化
	Building(string sittingroom, string bedroom)
	{
		m_sittingroom = sittingroom;
		m_bedroom = bedroom;
	}
private:
	string m_bedroom; //私有属性
};
//引用传递
void Goodfriend(Building &p)
{
	cout << "有人正在访问你的" << p.m_sittingroom << endl;
	cout << "有人正在访问你的" << p.m_bedroom << endl;
}
void test()
{
	Building b("客厅","卧室");
	Goodfriend(b);
}
int main()
{
	test();
	return 0;
}

2、类做友元

#include<iostream>
#include<string>
using namespace std;
class Building {
	friend class Goodfriend; //声明友元类
public:
	string m_sittingroom;//公有属性
	//构造函数初始化
	Building(string sittingroom, string bedroom);
	
private:
	string m_bedroom; //私有属性
};
class Goodfriend {
public:
	void visit();
	Goodfriend(Building p);//这是拷贝构造函数
	Building building;//指针形式:可能是new或者是指针传递
};
//类外实现成员函数
Building::Building(string sittingroom, string bedroom)
{
	m_bedroom = bedroom;
	m_sittingroom = sittingroom;
}
Goodfriend::Goodfriend(Building p):building(p)
{
	
}
void Goodfriend::visit()
{
	cout << "有人正在访问你的" << building.m_sittingroom << endl;
	cout << "有人正在访问你的" << building.m_bedroom << endl;
}
void test()
{
	Building p("客厅", "卧室");
	Goodfriend gg(p);
	gg.visit();
}
int main()
{
	test();
	return 0;
}

3、成员函数作为友元

#include<iostream>
using namespace std;
class Building;
class Goodfriend {
public:
	void visit();//可以访问buliding的私有属性
	void visit2();//不可以访问building的私有属性
	Goodfriend(Building& p);//这是拷贝构造函数
	Building* building;//指针形式:可能是new或者是指针传递
};
class Building {
	friend void Goodfriend::visit();//Goodfriend下的成员函数visit作为友元
public:
	string m_sittingroom;//公有属性
	//构造函数初始化
	Building(string sittingroom, string bedroom);

private:
	string m_bedroom; //私有属性
};

//类外实现成员函数
Building::Building(string sittingroom, string bedroom)
{
	m_bedroom = bedroom;
	m_sittingroom = sittingroom;
}
Goodfriend::Goodfriend(Building& p) :building(&p)
{

}
void Goodfriend::visit()
{
	cout << "有人正在访问你的" << building->m_sittingroom << endl;
	cout << "有人正在访问你的" << building->m_bedroom << endl;
}
void Goodfriend::visit2()
{
	cout << "有人正在访问你的" << building->m_sittingroom << endl;
	//cout << "有人正在访问你的" << building->m_bedroom << endl;// 此时作为非友元的visit2不能访问私有属性
}
void test()
{
	Building p("客厅", "卧室");
	Goodfriend gg(p);
	gg.visit();
	gg.visit2();
}
int main()
{
	test();
	return 0;
}

1.6 运算符重载

运算符重载:对已有的运算符重新进行定义,赋予其不同的功能

加号运算符重载

  • 成员函数实现重载
  • #include<iostream>
    using namespace std;
    class person
    {
    public:
    	person();
    	person(int a,int b);
    	person operator+(person& p);
    	~person();
    	int m_age;
    	int m_number;
    
    private:
    
    };
    person::person()
    {
    
    }
    person::person(int a,int b)
    {
    	m_age = a;
    	m_number = b;
    }
    
    person::~person()
    {
    }
    //成员函数重载
    person person::operator+(person& p)
    {
    	person temp;
    	//这里的this指针指的是调用成员函数的对象
    	this->m_age = this->m_age + p.m_age;
    	this->m_number = this->m_number + p.m_number;
    	return *this;
    }
    
    
    void test()
    {
    	person p1(10, 20);
    	person p2(20, 30);
    	person p3 = p1 + p2;
    	cout << p3.m_age << "," << p3.m_number;
    }
    int main()
    {
    	test();
    	return 0;
    }
  • 全局函数实现重载
  • #include<iostream>
    using namespace std;
    class person
    {
    public:
    	person();
    	person(int a,int b);
    	person operator+(person& p);
    	~person();
    	int m_age;
    	int m_number;
    
    private:
    
    };
    person::person()
    {
    
    }
    person::person(int a,int b)
    {
    	m_age = a;
    	m_number = b;
    }
    
    person::~person()
    {
    }
    
    
    //全局函数实现重载
    person operator+(person& p1, person& p2)
    {
    	person temp;
    	temp.m_age = p1.m_age + p2.m_age;
    	temp.m_number = p1.m_number + p2.m_number;
    	return temp;
    }
    void test()
    {
    	person p1(10, 20);
    	person p2(20, 30);
    	person p3 = p1 + p2;
    	cout << p3.m_age << "," << p3.m_number;
    }
    int main()
    {
    	test();
    	return 0;
    }
    

    左移运算符重载

在C++中,”<<“记作左移运算符,在ostream类中左移运算符重载为输出运算符,cout就是ostream类对象,即便如此,cout也不能直接输出我们自定义的类对象,因次在定义类的时候需要对其进行重载。

在这里我们用全局函数的方式实现重载,因为用成员函数的方式实现的话,我们还需要创建一个新的对象。

#include<iostream>
using namespace std;
#include<string>
class Person
{
public:
	Person();
	~Person();
	int m_age;
	string m_name;
private:

};

Person::Person()
{
	m_name = "张三";
	m_age = 26;
}

Person::~Person()
{

}
//怎么解释这段代码 ostream是个类,cout是ostream类对象
ostream& operator<<(ostream& out, Person& p)
{
	out << p.m_age << " " << p.m_name;
	return out;
}
int main()
{
	Person p;
	cout << p<<endl;
}

通常自定义类下的属性都设置为私有属性,这时候需要配合友元来使用成员操作符。

#include<iostream>
using namespace std;
#include<string>
class Person
{
	friend ostream& operator<<(ostream& out, Person& p);
public:
	Person();
	~Person();
private:
	int m_age;
	string m_name;

};

Person::Person()
{
	m_name = "张三";
	m_age = 26;
}

Person::~Person()
{

}
//怎么解释这段代码 ostream是个类,cout是ostream类对象
ostream& operator<<(ostream& out, Person& p)
{
	out << p.m_age << " " << p.m_name;
	return out;
}
int main()
{
	Person p;
	cout << p<<endl;
}

2、继承

继承是面向对象的三大特性之一,那么什么时候用到继承呢?继承有什么好处呢?

我们以猫来举例子,自然界中有很多猫科动物,我们举几个简单的例子,有橘猫、狸花猫、山东狮子猫等,这些猫既有共性,又有特性,都是猫,但是毛色不一样。既有共性,又有特性。

在程序中也一样,类和类之间既有联系又有区别,对于共性的那一部分,我们可以选择提取出来,这样的话,在创造其他类的时候,直接调用这个公共部分就可以,不需要再重新编写,这个部分就叫做继承,继承能够减小代码的重复性。

语法:class 子类(派生类):继承方式 父类(基类)

通过继承,派生类对象获得了基类的数据成员,派生类对象可以使用基类的方法。那么派生对象需要干些什么呢?派生对象需要添加自己的构造函数,可以根据自己的需要添加额外的成员数据和成员函数。

代码:

#include<iostream>
using namespace std;
class BasePage {
public:
	void header()
	{
		cout << "首页、公开课、登录、注册...(公共头部)" << endl;
	}

	void Bottom()
	{
		cout << "帮助、交流合作、站内地图...(公共底部)" << endl;
	}
	void left()
	{
		cout << "Java、Python、C++、...(公共分类列表)" << endl;
	}
};
//类继承的写法
//语法:class 子类(派生类):继承方式 父类(基类)
class CPP :public BasePage
{
public:
	void content()
	{
		cout << "C++学科视频" << endl;
	}
};
class JAVA :public BasePage
{
public:
	void content()
	{
		cout << "JAVA学科视频" << endl;
	}
};
class Python:public BasePage
{
public:
	void content()
	{
		cout << "Python学科视频" << endl;
	}
};
void test()
{
	CPP cpp;
	cpp.content();
	cpp.left();
	cpp.Bottom();
	cpp.header();
	Python py;
	py.content();
	py.left();
	py.Bottom();
	py.header();
	JAVA ja;
	ja.Bottom();
	ja.content();
	ja.header();
	ja.left();
}
int main()
{
	test();
}

继承方式

  • 公共继承
  • 私有继承
  • 保护继承
#include<iostream>
using namespace std;
class Base1 {
public:
	int m_a;
protected:
	int m_b;
private:
	int m_c;
};
//公共继承
class Son1 :public Base1 {
public:
	void func()
	{
		m_a = 100; //父类中公共权限成员 在子类中依然是公共的
		m_b = 10;
		//m_c = 30; //父类中的私有权限成员,不可以访问
	}
};
void test01()
{
	Son1 s;
	//s.m_b = 100;//这行代码会报错,因为父类中的保护权限仍然是保护的 类外不可以访问
	s.m_a = 10;
}
// 私有继承
class Son2 :private Base1 {
public:
	void func()
	{
		m_a = 100;
		m_b = 10;
}
};
//可以创建一个子类来验证Son2中的成员属性
class grandson :public Son2 {

public:
	void func()
	{
		/*m_a = 100; 
		m_b = 10;*/
	}
};
void test02()
{
	Son2 s2;
	//cout << s2.m_a<<endl;//在Son2 中,m_a是私有权限,不能访问
	//cout << s2.m_b << endl;//在Son2 中,m_b是私有权限,不能访问
}
//保护继承
class Son3 :protected Base1 {
public:
	void func()
	{
		m_a = 100;
		m_b = 10;
	}
};

void test02()
{
	Son3 s2;
	//cout << s2.m_a<<endl;//在Son2 中,m_a是保护权限,不能访问
	//cout << s2.m_b << endl;//在Son2 中,m_b是保护权限,不能访问
}

总结:

所有继承方式下,父类的私有属性都是不能访问的

公共继承下,父类的公共属性和保护属性不变

私有继承下,父类的公共属性和私有属性都是私有的

保护继承下,父类的公共属性和私有属性都是保护的。

重点:我们以为在子类中不能访问父类中的私有属性,那么私有属性就不会继承,其实是错误的,私有属性也会被继承下来,但是被编译器雪藏了,所以才不能访问。

继承中构造和析构顺序

在继承中子类和父类的构造和析构顺序是怎样的?

先构造父类,再构造子类,先析构子类,再析构父类。

#include<iostream>
using namespace std;
class Base {
public:
	Base()
	{
		cout << "这是Base类的构造函数" << endl;
	}
	~Base()
	{
		cout << "这是Base类的析构函数" << endl;
	}
};
class Son :public Base {
public:
	Son()
	{
		cout << "这是Son类的构造函数" << endl;
	}
	~Son()
	{
		cout<<"这是Son类的析构函数" << endl;
	}
};
void test()
{
	Son s1;
}
int main()
{
	test();
}

 为了加深对继承的理解,参考了书籍“C++ Primer Plus”,照着书敲了一遍代码,主要是在构造函数这块儿加深了一下理解。那么就按照书中的顺序来捋一遍。

我们首先创建一个TablePlayer基类,

程序清单 tablepayer.h

#pragma once
#ifndef TABLEPLAYER_H_
#define TABLEPLAYER_H_
#include<string>
#include<iostream>
using namespace std;
class TablePlayer
{
public:
	TablePlayer(const string &fn,const string& ln,bool ht);
	void name() const;//只读函数
	bool HasTable() const { return hastable; };
	void ResetTable(bool v) { hastable = v; };

private:
	string  firstname;
	string  lastname;
	bool    hastable;
};


#endif // !

程序清单 tableplayer.cpp

#include"tableplayer.h"
TablePlayer::TablePlayer(const string& fn, const string& ln, bool ht) :firstname(fn), lastname(ln), hastable(ht) 
{
	
}
void TablePlayer::name() const
{
	cout << lastname << "," << firstname;
}

在tableplayer类记录了运动员一些基本信息的基础上,我们想添加一项功能,就是记录运动员的比分,因此我们创建一个RatePlayer类,为了不重复编写关于基本信息的代码,可以直接从TablePlayer类继承下来。

程序清单 RatePlayer.h

#pragma once
#ifndef RATEPLAYER_H_
#define RATEPLAYER_H_
#include "tableplayer.h"
class RatePlayer:public TablePlayer
{
public:
	//构造函数
	RatePlayer(const int& r,const string&fn,const string&ln,bool ht);
	RatePlayer(const int& r, const TablePlayer& tp);
	unsigned int Rating() const { return rating; };
	void resetrating(unsigned int r) { rating = r; };
private:
	unsigned int rating;
};

#endif // !RATEPLAYER_H_

程序清单 RatePlayer.cpp

#include "RatePlayer.h"
//为什么要把TablePlayer构造函数放在前面 因为先构造基类,首选的是默认构造函数
RatePlayer::RatePlayer(const int& r, const string& fn, const string& ln, bool ht) :TablePlayer(fn, ln, ht), rating(r)
{

}
//调用了基类的拷贝构造函数
RatePlayer::RatePlayer(const int& r, const TablePlayer& tp) : TablePlayer(tp),rating(r)
{
	
}

程序清单 main.cpp

#include"RatePlayer.h"
//RatePlayer类继承tableplayer类,但是多了一个打分的1功能
int main()
{
	TablePlayer player1("Chunk", "Blizzard", true);
	//TablePlayer player2("Tara", "Boomdea", false);
	RatePlayer rateplayer2(1140, "Tara", "Boomdea", false);
	player1.name();
	if (player1.HasTable())
		cout << ":  has a table.\n";
	else
		cout << ":  hasn't a table.\n";

	rateplayer2.name();
	if (rateplayer2.HasTable())
		cout << ":  has a table.\n";
	else
		cout << ":  hasn't a table.\n";
	//用拷贝构造函数的方式构造一个类对象
	cout << "Name:";
	rateplayer2.name();
	cout << "Rating: " << rateplayer2.Rating() << endl;
	RatePlayer rateplayer3(1212, player1);
	cout << "Name:";
	rateplayer3.name();
	cout << "  Rating: " << rateplayer3.Rating() << endl;


}

派生类和基类之间的特殊关系

  • 在基类的方法不是私有的情况下,派生类可以使用基类的方法。(RatePlayer就调用了TablePlayer的name()方法)
  • 基类指针可以在不进行显式类型转换的情况下指向派生类对象;
  • 基类引用可以在不进行显式类型转换的情况下指向派生类对象。
RatePlayer rp1={1124,"Chunk","Boomdea",false};
TablePlayer* rt1=&rp1;  //创建一个基类指针指向派生类对象
TablePlayer& rt2=rp1;   //创建一个基类引用指向派生类对象

 值得注意的是,基类指针或者基类引用只能调用基类的方法,不能用来调用派生类的方法,并且派生类指针和派生类引用不能指向基类对象。

继承中同名成员处理方式

问题:当子类中出现与父类同名的成员时,如何通过子类对象访问到父类中的同名数据。

  • 访问子类成员的时候,直接访问就可以
  • 访问父类同名成员的时候,需要加上作用域。
  • 子类成员函数和父类成员函数重名时,子类成员函数会隐藏所有的父类同名成员函数(包括重载函数),因此想要访问父类中同名成员函数,需要加作用域
#include<iostream>
using namespace std;
class Base {
public:
	int m_age;
	Base()
	{
		m_age = 10;
	}
	void func()
	{
		cout<<"这是父类的同名成员函数" << endl;
	}
	void func(int  a)
	{
		cout << "这是子类的同名成员有参函数" << endl;
	}
};
class Son :public Base {
public:
	int  m_age;
	Son()
	{
		m_age = 20;
	}
	void func()
	{
		cout<<"这是子类的同名成员函数" << endl;
	}
	
};
//同名成员属性的处理方式
void test()
{
	Son s1;
	cout << s1.m_age << endl;//此时输出的是20,访问的是子类中的成员
	cout << s1.Base::m_age << endl;//此时输出的是10,访问的是父类中的成员
}
//同名成员函数的处理方式
/*
如果子类中出现和父类中同名的成员函数,子类中的同名成员函数会隐藏掉父类中的所有同名成员函数
包括重载函数,如果想访问的话,需要加上作用域。
*/
void test01()
{
	Son s2;
	s2.func();//子类成员函数的调用
	//s2.func(10);//这种访问方式是不允许的
    s2.Base::func();//加上父类作用域后,访问父类作用域下的同名成员函数
	s2.Base::func(10);
}
int main()
{
	//test();
	test01();
}

继承中同名静态成员处理方式

静态成员函数和普通的成员函数的访问方式并没有什么太大的区别,唯一一个比较大的区别就是:静态成员有两种访问方式:通过类名访问和通过对象访问。

#include<iostream>
using namespace std;
class Base {
public:
	static int m_a;//类内声明
	static void func()
	{
		cout << "父类下的同名静态成员函数" << endl;
	}
};
//类外初始化
int Base::m_a = 100;
class Son :public Base {
public:
	static int m_a;//类内声明
	static void func()
	{
		cout << "子类下的同名静态成员函数" << endl;
	}
	
};
int Son::m_a = 20;//类外初始化
//同名静态成员函数的访问方式
void test()
{
	//通过对象访问
	cout<<"通过对象访问" << endl;
	Son s1;
	cout << "Son   下   m_a= "<<s1.m_a<<endl;
	cout << "Base  下   m_a= " << s1.Base::m_a << endl;
	//通过类名访问
	cout<<"通过类名访问" << endl;
	cout << "Son   下   m_a= " << Son::m_a << endl;
	//这段代码的解释是:通过Son类名的方式访问Base作用域下的m_a
	cout << "Base  下   m_a= " << Son::Base::m_a<<endl;
}
//同名静态成员函数访问方式
void test1()
{
	//通过对象访问
	cout << "通过对象访问" << endl;
	Son s1;
	cout << "Son   下   的静态成员函数 ";
	s1.func();
	cout<<endl;
	cout << "Base  下   的静态成员函数 ";
	s1.Base::func();
	cout<< endl;
	//通过类名访问
	cout << "通过类名访问" << endl;
	cout << "Son   下   的静态成员函数 ";
	Son::func();
	cout<< endl;
	//这段代码的解释是:通过Son类名的方式访问Base作用域下的m_a
	cout << "Base  下  的静态成员函数 ";
	Son::Base::func();
	cout<< endl;

}
int main()
{
	test1();
}

多继承语法

C++允许继承多个父类

语法:class 父类:继承方式 父类1,继承方式:父类2

为了区分多个同名函数,需要加上作用域。

C++实际开发中不建议使用多继承。

菱形继承

什么叫菱形继承呢?如上图所示,父类下有两个派生类1和2,派生类3又多继承了派生类1和2。

菱形继承会产生一个问题?假如父类中有一个成员m_a,那么派生类1和2都会继承到这个m_a,导致派生类3多继承的时候,会继承多份m_a,举个例子看一下:

以下案例中,Father和Mother是从Person这个类派生而来的,而Son多继承于Father和Mother类,

#include<iostream>
using namespace std;
//虚继承解决菱形继承的问题
class Person {
public:
	int m_age=45;
};
class Father :public Person
{

};
class Mother :public Person
{

};
class Son :public Father,public Mother {


};
void test()
{
	Son s1;
	//s1.m_age = 10;
}
int main()
{
	test();
}

我们可以用vs的开发工具看看Son类的结构,可以看到Son类从Father基类和Mother基类各继承了一份m_age,造成了数据的二义性。

那么怎么解决这个问题呢?答案是虚继承

#include<iostream>
using namespace std;

class Person {
public:
	int m_age=45;
};
//虚继承解决菱形继承的问题,
//继承之前加上virtual 变成虚继承
//此时的Person类称为虚基类
class Father : virtual public Person {};
class Mother :virtual public Person {};
class Son :public Father,public Mother {};
void test()
{
	Son s1;
	s1.m_age = 10;//加上虚继承以后,这行代码不报错
}
int main()
{
	test();
}

这时候从Father和Mother类继承下来的是vbptr

 虚函数的工作原理

编译器是怎么处理虚函数的呢?编译器在处理虚函数的时候,给每个对象添加了一个隐藏成员vfptr,隐藏成员中保存了一个指向存储函数指针的数组的指针,这个指针数组叫做虚函数列表vtbl(virtual function table)。

 举个例子来看一下执行的过程:

多态

什么是多态?字面上解释就是多种形态,那么在类和对象中就可以解释为,同一个方法在基类和派生类中的行为是不同的,换句话说针对不同对象,同一个方法的行为是不同的。

现在看《C++ Primer Plus》中的例子,开发两个类,一个类用于表示基本支票账户--Brass Account,一个类用于Brass Plus支票账户,添加了透支保护性。

Brass账户有以下属性和行为:客户姓名、账号、当前结余;创建账户、存款、取款、显示账户信息。

Brass Plus账户包含了Brass账户的所有信息以及透支上限、透支贷款利率,当前透支总额,对于取款操作,考虑透支保护和显示操作必须显示plus账户的其他信息。

程序清单 Brass.h

#pragma once
#ifndef BRASS_H_
#define BRASS_H_
#include<iostream>
#include<string>
using namespace std;
class Brass
{
public:
	Brass(const string& s="Nullbody",long an=-1,double bal=0.0);
	void Deposit(double amt);
	virtual void Withdraw(double amt);
	double Balance() const;
	virtual void ViewAcct() const;
	~Brass() {};

private:
	string fullname; //客户姓名
	long acctNum; //账号
	double balance; //当前结余
};

class BrassPlus :public Brass
{
public:
	//两种构造函数
	BrassPlus(const string& s = "Nullbody", long an = -1, double bal = 0.0, double ml = 500, double r = 0.11125);
	BrassPlus(const Brass& ba, double ml = 500, double r = 0.11125);
	void Withdraw(double amt);
	void ViewAcct() const;
	void ResetMax(double m) { maxLoan = m; };
	void ResetRate(double r) { rate = r; };
	void ResetOwes() { owesbank = 0; };
	~BrassPlus() {};
private:
	double maxLoan; //透支上限
	double rate;  //透支贷款利率
	double owesbank; //当前的透支总额

};
#endif // !BRASS_H_

程序清单 Brass.cpp

#include "Brass.h"
Brass::Brass(const string& s, long an, double bal ) :fullname(s), acctNum(an), balance(bal)
{

}

//存款
void Brass::Deposit(double amt)
{
	if (amt < 0)
		cout << "Negative deposit is not allowed;"
		<< "deposit is canceled.\n";
	else
		balance += amt;
}
//取款
void Brass::Withdraw(double amt)
{
	if (amt < 0)
		cout << "Wtihdrawal amount must be positive; "
		<< "withdrawal canceled.\n";
	else if (amt > balance)
		cout << "Withdrawal amount of $" << amt
		<< "exceeds your balance.\n"
		<< "withdrawal is canceled.\n";
	else
		balance -= amt;
}
//查看余额
double Brass::Balance() const
{
	return balance;
}
void Brass::ViewAcct() const
{
	cout << "Client: " << fullname << endl;
	cout << "Account Number: " << acctNum << endl;
	cout << "Blance: $" << balance << endl;
}
BrassPlus::BrassPlus(const string& s , long an, double bal, double ml , double r) :Brass(s, an, bal)
{
	maxLoan = ml;
	rate = r;
	owesbank = 0.0;
}
BrassPlus::BrassPlus(const Brass& ba, double ml , double r ) :Brass(ba), maxLoan(ml), rate(r),owesbank(0)
{

}
void BrassPlus::ViewAcct() const
{
	Brass::ViewAcct();
	cout << "Maximum loan: $" << maxLoan << endl;
	cout << "Owed to bank: $" << owesbank << endl;
	cout << "Loan rate: " << rate << "%.\n";
}
void BrassPlus::Withdraw(double amt)
{
	double bal = Balance();
	if (amt <= bal)
		Brass::Withdraw(amt);
	else if (amt <= bal + maxLoan - owesbank)
	{
		double advance = amt - bal;
		owesbank += advance * (1 - rate);
		cout << "Bank advance: $" << advance * rate << endl;
		cout << "Finance charge: $" << advance * rate << endl;
		Deposit(advance);
		Brass::Withdraw(amt);
	}
	else
		cout << "Credit limit exceed. Transanction canceled.\n";


}

程序清单 main.cpp

#include"Brass.h"
int main()
{
	Brass dom("Domeic Banker", 11224, 4183.85);
	BrassPlus dot("Dorothy Banker", 12118, 2592.00);
	//Brass和BrassPlus中都有ViewAcct()函数,但是行为是不一样的,输出的结果也不一样
	//通过对象调用方法
	dom.ViewAcct();
	dot.ViewAcct();

}

程序运行结果:

Client: Domeic Banker
Account Number: 11224
Blance: $4183.85

Client: Dorothy Banker
Account Number: 12118
Blance: $2592
Maximum loan: $500
Owed to bank: $0
Loan rate: 0.11125%.

以上是通过对象调用方法的,如果是通过指针或者引用调用方法,它如何确定使用哪一种方法呢?如果没有使用关键字virtual,程序将根据指针类型或引用类型选择方法;如果加了virtual,程序将根据指针或引用指向的对象类型来选择方法。

#include"Brass.h"
int main()
{
	Brass dom("Domeic Banker", 11224, 4183.85);
	BrassPlus dot("Dorothy Banker", 12118, 2592.00);
	//Brass和BrassPlus中都有ViewAcct()函数,但是行为是不一样的,输出的结果也不一样
	//通过对象调用方法
	/*dom.ViewAcct();
	dot.ViewAcct();*/
	Brass& b1_ref = dom; //引用类型是Brass类,指向的Brass类对象
	Brass& b2_ref = dot; // 引用类型是Brass类,指向的是BrassPlus类对象
	b1_ref.ViewAcct();
	b2_ref.ViewAcct();

}

此时的ViewAcct()类型为非虚,根据分析是应该根据引用的类型来调用方法,因此上述代码调用的Brass::ViewAcct()。

那么代码执行的结果是:

Client: Domeic Banker
Account Number: 11224
Blance: $4183.85
Client: Dorothy Banker
Account Number: 12118
Blance: $2592

 如果加上virtual关键字后,那么执行结果有什么不一样呢?程序根据引用指向的对象类型调用了方法。

Client: Domeic Banker
Account Number: 11224
Blance: $4183.85
Client: Dorothy Banker
Account Number: 12118
Blance: $2592
Maximum loan: $500
Owed to bank: $0
Loan rate: 0.11125%.

多态满足条件

  • 有继承关系
  • 子类重写父类中的虚函数,注意是重写,不是重载。

静态联编和动态联编

什么是联编?

来看一下百度百科的解释:联编是指一个计算机程序自身彼此关联(使一个 源程序 经过编译、连接,成为一个可执行程序)的过程,在这个联编过程中,需要确定程序中的操作调用(函数调用)与执行该操作(函数)的代码段之间的映射关系。在C++中,由于函数的重载,编译器必须根据函数的参数列表和函数名才能确定使用哪个函数。

在编译阶段完成联编称为静态联编,然而加了虚函数以后,编译器在编译阶段不能完成联编,因为编译不知道选择哪种类型的对象。因此编译器在程序运行时选择正确的虚方法的代码,称为动态联编。                                                                                 

  • 12
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值