0
点赞
收藏
分享

微信扫一扫

C++学习笔记----类和对象

NicoalsNC 2022-03-16 阅读 73

目录

6.1 封装

6.1.1 封装的意义

6.1.2 struct和class的区别

6.1.3 将成员属性设为私有

设计案例1:立方体类

设计案例2:点和圆的关系

6.2 对象的初始化和清理

6.2.1 构造函数

6.2.2 析构函数

6.2.3 拷贝构造函数调用时机

6.2.4 构造函数调用规则

6.2.5 深拷贝与浅拷贝

6.2.6 初始化列表

6.2.7 类对象作为类的成员

6.2.8 静态成员

6.3 C++对象模型和this指针

6.3.1 成员变量和成员函数分开存储

6.3.2  this指针的概念

6.3.3  空指针访问成员函数

6.3.4  const修饰成员函数

6.4  友元

6.4.1  全局函数做友元

6.4.2  类做友元

6.4.3  成员函数做友元 

6.5  运算符重载

6.5.1  加号运算符重载

6.5.2  左移运算符(<<)的重载

6.5.3  递增运算符的重载

6.5.4  赋值运算符重载

6.5.5  关系运算符的重载

6.5.6  函数调用运算符()重载

6.6  继承

6.6.1  继承的基本语法

6.6.2  继承的方式

6.6.3  继承中的对象模型

6.6.4  继承中构造和析构顺序

6.6.5  继承中同名成员的处理方式

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

6.6.7  多继承语法

6.6.8  菱形继承

6.7  多态

6.7.1  多态的基本概念

6.7.2  多态案例----计算器类

6.7.3  纯虚函数和抽象类

6.7.4  多态案例二----制作饮品

6.7.5  虚析构和纯虚析构

6.7.6  多态案例三----电脑组装


 c++面向对象三大特性:封装、继承、多态。

6.1 封装

6.1.1 封装的意义

         1. 将属性和行为作为一个整体。(放在一个class里面)
         2. 将属性和行为加以权限控制。
              public公共权限:类内外都可以访问
              protected保护权限: 类外不可以访问
              private私有权限: 类外不可以访问

//定义一个圆类
#define PI 3.14
//class 定义一个类 circle是类的名字
class circle
{
	//访问权限:公共权限
public:
	//属性
	int r;
	//行为
	double circumference( )
	{
		return r * PI * 2;
	}
};
int main()
{
	circle c1;//创建具体的圆(对象)(实例化)
	c1.r = 10;//给具体的圆的属性赋值
	cout << "圆的周长为:" << c1.circumference() << endl;
	return 0;
}

6.1.2 struct和class的区别

struct默认权限为: 共有  public
class默认权限为:   私有  private

6.1.3 将成员属性设为私有

优点:

         ①将成员属性设为私有,可以自己控制读写的权限。
         ②对于写权限,可以检测数据的有效性。

class person
{
private:
	string name;//可读写
	int age;//只读
	string lover;//只写
public:
	void SetName(string s)
	{
		name = s;
	}
	string GetName()
	{
		return name;
	}
	int GetAge()
	{
		age = 18;
		return age;
	}
	void SetLover(string s)
	{
		lover = s;
	}
};
int main()
{
	person p1;
	p1.SetName("xiyang");
	p1.SetLover("2A");
	cout << "姓名为:" << p1.GetName() << endl;
	cout << "年龄为:" << p1.GetAge() << endl;
	return 0;
}

 设计案例1:立方体类

/*
要求:
1.设计一个立方体类
2.求出立方体的面积和体积
3.分别用全局函数和成员函数判断两个立方体是否相等
*/

class cube
{
private:
	//属性
	int L;
	int W;
	int H;
public:
	//行为
	//设置 获取长,宽,高
	void SetL(int a)
	{
		L = a;
	}
	int GetL()
	{
		return L;
	}
	void SetW(int a)
	{
		W = a;
	}
	int GetW()
	{
		return W;
	}
	void SetH(int a)
	{
		H = a;
	}
	int GetH()
	{
		return H;
	}
	//获得面积
	int S()
	{
		return 2 * ((L * W) + (L * H) + (W * H));
	}
	//获得体积
	int V()
	{
		return L * W * H;
	}
	//成员函数判断
	bool isSameByClass(cube& c)
	{
		if (c.GetL() == L && c.GetW() ==W && c.GetH() == H)
			return true;
		else
			return false;
	}

};
//全局函数判断
bool isSame(cube& c1, cube& c2)
{
	if (c1.GetL() == c2.GetL() && c1.GetW() == c2.GetW() && c1.GetH() == c2.GetH())
		return true;
	else
		return false;
}
int main()
{
	cube c1,c2;
	c1.SetL(10);
	c1.SetW(10);
	c1.SetH(10);
	c2.SetL(10);
	c2.SetW(10);
	c2.SetH(10);
	cout << "第一个立方体的面积为:" << c1.S() << endl;
	cout << "第一个立方体的体积为:" << c1.V() << endl;
	bool ret1 = isSame(c1, c2);
	if (ret1)
	{
		cout << "全局函数判断c1 c2相等" << endl;
	}
	else
		cout << "全局函数判断c1 c2不相等" << endl;

	bool ret2 = c1.isSameByClass(c2);
	if (ret2)
	{
		cout << "成员函数判断c1 c2相等" << endl;
	}
	else
		cout << "成员函数判断c1 c2不相等" << endl;
	return 0;
}

设计案例2:点和圆的关系

源.cpp

#include<iostream>
using namespace std;
#include"point.h"
#include"circle.h"
void IsInCircle(Circle &c,Point &p)
{
	int distance =
		(c.getCenter().getX() - p.getX()) * (c.getCenter().getX() - p.getX()) +
		(c.getCenter().getY() - p.getY()) * (c.getCenter().getY() - p.getY());
	int rdistance = c.getR() * c.getR();
	if (distance == rdistance)
		cout << "点在圆上"<<endl;
	else if (distance > rdistance)
		cout << "点在圆外"<<endl;
	else
		cout << "点在圆内"<<endl;
}
int main()
{
	Circle c;
	Point center;
	Point q;
	center.setX(10);
	center.setY(10);
	q.setX(10);
	q.setY(10);
	c.setCenter(center);
	c.setR(10);
	IsInCircle(c, q);
	return 0;
}

circle.h

#pragma once
#include<iostream>
using namespace std;
#include"point.h"

class Circle {
public:
	void setR(int r);
	int getR();
	void setCenter(Point center);
	Point getCenter();
private:
	int m_R;
	Point m_Center;
};

circle.cpp

#include"circle.h"

void Circle::setR(int r) {
	m_R = r;
	}
int Circle::getR() {
	return m_R;
	}
void Circle::setCenter(Point center)
{
	m_Center = center;
	}
Point Circle::getCenter() {
	return m_Center;
	}

point.h

#pragma once
#include<iostream>
using namespace std;

class Point {
public:
	void setX(int x);
	int getX();
	void setY(int y);
	int getY();
private:
	int m_X;//д
	int m_Y;//д
};

point.cpp

#include"point.h"

void Point::setX(int x)
{
	m_X = x;
}
int Point::getX() {
	return m_X;
}
void Point::setY(int y) {
	m_Y = y;
}
int Point::getY() {
	return m_Y;
}

6.2 对象的初始化和清理

C++利用构造函数析构函数解决了对象的初始化和清理。对象的初始化和清理工作是编译器强制要求我们做的事情,因此就算我们不提供构造和析构,编译器也会提供,只不过编译器提供的是构造函数和析构函数的空实现。

6.2.1 构造函数

定义:主要作用在创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
语法类名 () { }

注意:①没有返回值,不用写void。

           ②函数名与类名相同。

           ③构造函数可以有参数,可以发生重载。

           ④创建对象时,构造函数会自动调用,并且只调用一次。

分类:

        按参数分类:有参构造和无参构造(默认构造)

        按类型分类:普通构造和拷贝构造

调用方式:

        括号法、显示法、隐式转换法

//构造的分类和调用
class person
{
public:
	//无参(普通构造)(默认构造)
	person()
	{
		cout << "无参构造函数调用" << endl;
	}
	//有参(普通构造)
	person(int a)
	{
		age = a;
		cout << "有参构造函数调用" << endl;
	}
	//拷贝构造函数
	person(const person& p)   //加const作用,拷贝之后防止本体被修改
	{
		age = p.age;//克隆数据
		cout << "拷贝构造函数的调用" << endl;
	}
	int age;
};
int main()
{
	//括号法
	person p1;//不能加(),加了()编译器会认为是一个函数声明,在一个函数体里可以声明另一个函数
	person p2(10); //p2的年龄初始化为10
	person p3(p2);

	//显示法
	person p4 = person();
	person p5 = person(10);
	person p6 = person(p5);
	//person()为匿名对象,没有名字,但创建了对象,特点:当前行执行结束后系统就会回收掉匿名对象
	//不要用拷贝构造函数初始化匿名对象,如person(p3),等价于person p3

	//隐式转换法
	person p7 = 10;//转换为:person p7=person(10)
}

6.2.2 析构函数

定义:主要作用在于对象销毁前系统自动调用,执行一些清理工作。
语法~类名 () { }

注意:①没有返回值,不写void

          ②函数名和类名相同,在名称前加~

          ③析构函数不可以有参数,不可以发生重载

          ④对象在销毁前会自动调用析构函数,并且只会调用一次

//构造函数和析构函数例子
class person
{
public:
	person()
	{
		cout << "构造函数的调用" << endl;
	}
	~person()
	{
		cout << "析构函数的调用" << endl;
	}
};
void test()
{
	person P;
}
int main()
{
	test();
	return 0;
}

 6.2.3 拷贝构造函数调用时机

C++中拷贝函数调用一般有三种情况:
                 ①使用一个已创建完毕的对象来初始化一个新对象。
                 ②值传递的方式给函数参数传值。
                 ③以值的方式返回局部对象。

class person
{
public:
	person()
	{
		cout << "默认构造函数的调用" << endl;
	}
	person(int a)
	{
		age = a;
		cout << "默认构造函数的调用" << endl;
	}
	person(const person& p)
	{
		age = p.age;//克隆数据
		cout << "拷贝构造函数的调用" << endl;
	}
	int  show()
	{
		return age;
	}
private:
	int age;
};
void test1(person p)
{
}
person test2()
{
	person p;
	return p;
}
int main()
{
	//使用一个已创建完毕的对象来初始化一个新对象
	person p1(20);
	person p2(p1);
	cout << "p2的年龄为:" << p2.show() << endl;

	//值传递的方式给函数参数传值,拷贝一份临时副本作为形参,改变临时副本的值不影响实参
	person p3;
	test1(p3);
	
	//以值的方式返回局部对象,return返回时返回的不是局部对象,而是拷贝一个新对象返回
	test2();
	return 0;
}

6.2.4 构造函数调用规则

创建一个类,C++至少给每一个类添加4个函数:

        1、默认构造(空实现)

        2、析构函数(空实现)

        3、拷贝构造(值拷贝)

        4、赋值运算符Operator=对属性进行值拷贝

①如果用户定义一个有参构造函数,C++不会提供默认构造函数,但是会提供拷贝构造函数。
②如果用户定义一个拷贝构造函数,C++不会提供别的构造函数。

//eg.①如果用户定义一个有参构造函数,C++不会提供默认构造函数,但是会提供拷贝构造函数
class person
{
public:
	person(int a)
	{
		age = a;
		cout << "默认构造函数的调用" << endl;
	}
	int  show()
	{
		return age;
	}
private:
	int age;
};
int main()
{
	person p1;//err
	person p2(18);
	person p3(p2);//拷贝构造函数
	cout << "p2的年龄为:" << p2.show() << endl;
	return 0;
}
//②如果用户定义一个拷贝构造函数,C++不会提供别的构造函数
class person
{
public:
	person(const person& p)
	{
		age = p.age;//克隆数据
		cout << "拷贝构造函数的调用" << endl;
	}
	int  show()
	{
		return age;
	}
private:
	int age;
};
int main()
{
	person p1;//err
	person p2(18);//err
	person p3(p1);
	cout << "p2的年龄为:" << p2.show() << endl;
	return 0;
}

6.2.5 深拷贝与浅拷贝

        浅拷贝:简单的赋值拷贝。

        深拷贝:在堆区重新申请空间,进行拷贝。

浅拷贝存在的问题:属性有在堆区开辟的,拷贝之后两个类成员堆区属性都指向同一个地址,新拷贝成员执行析构操作之后,堆区内存被释放,旧成员执行析构操作就会使堆区内容重复释放。

解决方法:使两个成员堆区内容指向不同的地址,即进行深拷贝。

class person
{
public:
	person(int a, int h)
	{
		age = a;
		height = new int(h);
		cout << "有参构造函数的调用" << endl;
	}
	person(const person& p)
	{
		age = p.age;
		//height=p.height;//编译器默认实现
		height = new int(*(p.height));
		cout << "拷贝构造函数的调用" << endl;
	}
	~person()
	{
		if (height != NULL)
		{
			delete height;
			height = NULL;
		}
		cout << "析构函数的调用" << endl;
	}
	int  GetAge()
	{
		return age;
	}
	int GetHeight()
	{
		return *height;
	}
private:
	int age;
	int* height;
};
int main()
{
	person p1(18, 160);
	cout << "p1的年龄为:" << p1.GetAge() << " p1的身高为:" << p1.GetHeight() << endl;
	person p2(p1);
	cout << "p2的年龄为:" << p2.GetAge() << " p2的身高为:" << p2.GetHeight() << endl;
	return 0;
}

6.2.6 初始化列表

作用:用来初始化属性。

语法:   构造函数():属性1(值1),属性2(值2)...{}

class person
{
public:
	//初始化列表初始化属性
	person() :age(10), weigh(123), height(160)
	{

	}
	person(int a, int b, int c) :age(a), weigh(b), height(c)
	{

	}
	int age;
	int weigh;
	int height;
};
int main()
{
	person p1;
	person p2(18, 456, 180);
	cout << "p1的年龄,体重,身高为:\n" << p1.age << p1.weigh << p1.height<<endl;
	cout << "p2的年龄,体重,身高为:\n" << p2.age << p2.weigh << p2.height<<endl;
	return 0;
}

6.2.7 类对象作为类的成员

注意:当其他类对象作为本类成员,构造时先构造类对象,再构造自身。析构顺序和构造顺序相反。

class Phone {
public:
	Phone(string name) :PhoneName(name)
	{

	}
	string PhoneName;
};
class Person {
public:
	Person(string s, string p) :PersonName(s),PhoneName(p)
	{

	}
	string PersonName;
	string PhoneName;
};
int main()
{
	Person p("gua", "iphone");
	cout << p.PersonName << endl<<p.PhoneName << endl;
	return 0;
}

6.2.8 静态成员

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

静态成员变量:

        所有对象共享一份数据。
        在编译阶段分配内存(运行之前就分配了)。
        类内声明,类外初始化。

class Person
{
public:
	static int a;//类内声明
};
int Person::a = 100;//类外初始化
int main()
{
	Person p1;
	cout << "a的值为:" << p1.a << endl;
	Person p2;
	p2.a = 200;
	cout << "a的值为:" << p1.a << endl;
	return 0;
}

 注意:静态成员变量不属于某一个对象。因此有两种访问方式:①类名访问,②对象访问。

class Person
{
public:
	static int a;
};
int Person::a = 100;
int main()
{
	//对象访问
	Person p1;
	cout << p1.a << endl;
	//类名访问
	cout  << Person::a << endl;
	return 0;
}

        静态成员变量也有访问权限(类外访问不到私有静态成员变量)。

静态成员函数:

        所有对象共享一个函数。

        静态成员函数只能访问静态成员变量(访问成员变量,函数无法区分是哪个对象的成员变量)。

静态成员函数也有两种访问方式:

//静态成员函数的访问
class Person
{
public:
	static void test()
	{
		cout << "static void test()调用" << endl;
	}
};
int main()
{
	//对象访问
	Person p;
	p.test();
	//成员访问
	Person::test();
	return 0;
}
//静态成员函数只能访问静态成员函数
class Person
{
public:
	static void test()
	{
		a = 200;
		b = 200;//err,对象不明
		cout << "static void test()调用" << endl;
	}
	static int a;//静态成员函数访问静态成员变量
	int b;//非静态成员函数
};
int Person::a = 100;
int main()
{
	Person p;
	p.test();
	return 0;
}

        静态成员函数也有访问权限(类外访问不到私有静态成员函数)。

6.3 C++对象模型和this指针

6.3.1 成员变量和成员函数分开存储

        在C++中,类的成员变量和成员函数分开存储,只有非静态成员变量才属于类的对象上。

class person1
{

};
class person2
{
	int a;//非静态成员变量
};
class person3
{
	int a;
	static int b;//静态成员变量
};
class person4
{
	int a;
	static int b;//静态成员变量
	void test()//非静态成员函数
	{

	}
};
int main()
{
	//空对象占用内存是 1
	//C++会给每一个空对象分配一个字节的内存空间,为了区分空对象占内存的位置
	//每一个空对象也应该有一个独一无二的内存地址
	person1 p1;
	cout << "sizeof(p)=" << sizeof(p1) << endl;//1
	
	//虽然空对象有一个字节,但是一旦类里面不为空就跟着类中字节走
	person2 p2;
	cout << "sizeof(p)=" << sizeof(p2) << endl;//4
	
	//静态成员变量不属于类对象上的
	person3 p3;
	cout << "sizeof(p)=" << sizeof(p3) << endl;//4

	//类的成员变量和成员函数分开存储
	person4 p4;
	cout << "sizeof(p)=" << sizeof(p4) << endl;//4
	return 0;
}

 6.3.2  this指针的概念

        this指针指向被调用的成员函数所属的对象。

        this指针隐含在每一个非静态成员函数内的一种指针。

        this指针不需要定义,可直接使用。

this指针的用途:

        当形参和成员变量同名时,可以用this指针来区分。(解决名称冲突)

        在类的非静态成员函数中返回对象本身,可以用retrun *this(返回对象本身用 *this)

形参与成员变量名称相同时,无法进行赋值,利用this指针解决

 

         在类的非静态成员函数中返回对象本身,可以用retrun *this

class person
{
public:
	person(int age)
	{
		this->age = age;
	}
	person& test(person p)//person一定要加&,使用本体
	{
		this->age += p.age;
		return *this;
	}
	int age;
};
int main()
{
	person p1(18);
	person p2(18);
	//链式编程
	p2.test(p1).test(p1);
	cout << "p2的年龄为:" << p2.age << endl;
	return 0;
}

        如果不加引用,每次返回的都是拷贝构造函数构造的新的对象,和原来不一样,要多次连续对一个对象操作,需要加上引用。

 6.3.3  空指针访问成员函数

        C++中空指针可以调用成员函数,但要注意有没有用到this指针。如果用到this指针,需要加以判断保证代码的健壮性。

class person
{
public:
	void test1()
	{
		cout << "test1" << endl;
	}
	void test2()
	{
		cout << "age="<<age<< endl; //age相当于this->age
	}
	int age;
};
int main()
{
	person *p=NULL;
	p->test1();
	p->test2();  //err,传入的指针为空
	return 0;
}

        改正方法:

void test2()
{
	if(this->age==NULL)
	return;//提高代码的健壮性
	cout << "age="<<this->age<< endl;//err,传入的指针为空
}

6.3.4  const修饰成员函数

        const,限制为只读状态。

常函数:(成员函数后加const

        常函数内不可以修改成员属性。
        成员属性声明时加关键字mutable后,在常函数中依然可以修改。

        函数中this指针的定义相当于 Person * const this,指针的指向不可以修改,但指针指向的值可以修改

        常函数中this指针的定义相当于const Person * const this,指针的指向和指针指向的值均不可修改

class person
{
	void test2()
	{
		a = 100;
		b = 200; //err b是常量不可以修改
		c = 300;
	}
	//在成员函数后加const,修饰的是this指向,让指针指向的值不能改变
	void test1() const
	{
		a = 100;  //err 相当于this->a=100 
		b = 200;  //err b是常量不可修改
		c = 300;
	}
	int a;
	const int b;
	mutable int c;
};
int main()
{
	person p;
	p.test1();
	p.test2();
	return 0;
}

常对象:(声明对象前加const)

        常对象只能调用常函数。(常对象本身不允许修改对象属性,如果常对象调用的常函数对对象属性进行了修改,相当于间接的对常对象的属性进行了修改,所以常对象只能调用不能对对象属性进行修改的函数即常函数)

        常对象可以修改声明时加关键字mutable的成员属性。

class  person
{
public:
	void test1()
	{
	}
	void test2() const
	{
	}
	int a;
	const int b;
	mutable int c;
};
int main()
{
	const person p;//不能修改指针指向的值
	p.a = 100;//err
	p.b = 200;//err
	p.c = 300;
	p.test1();//err 常对象只能调用常函数
	p.test2();
}

6.4  友元

        友元的目的就是让一个函数或者类访问另一个类中的私有成员。

        在 private 和 public 中声明均可。

6.4.1  全局函数做友元

//全局函数做友元
class building {
private:
	string bedroom;
	friend void test2(building* b);//声明友元函数
public:	
	building()
	{
		bedroom = "卧室";
		livingroom = "客厅";
	}
	string livingroom;
};
void test1(building* b)
{
	cout << "访问" << b->livingroom << endl;
	//cout << "访问" << b->bedroom << endl;//err 不可访问私有成员
}
void test2(building* b)
{
	cout << "访问" << b->livingroom << endl;
	cout << "访问" << b->bedroom << endl;
}

6.4.2  类做友元

//类做友元
class Building {
	friend class GoodGay2;
public:
	Building();
	string Livingroom;
private:
	string Bedroom;
};
class GoodGay1 {
public:
	GoodGay1();
	void visit();
	Building* p;
};
class GoodGay2 {
public:
	GoodGay2();
	void visit();
	Building* p;
};
Building::Building()
{
	Bedroom = "卧室";
	Livingroom = "客厅";
}
//非友元
GoodGay1::GoodGay1()
{
	p = new Building;
}
void GoodGay1::visit()
{
	cout << "朋友在访问" << p->Livingroom << endl;
	cout << "朋友在访问" << p->Bedroom << endl;//err 没有访问权限
}
//友元
GoodGay2::GoodGay2()
{
	p = new Building;
}
void GoodGay2::visit()
{
	cout << "朋友在访问" << p->Livingroom << endl;
	cout << "朋友在访问" << p->Bedroom << endl;
}
int main()
{
	GoodGay1  g1;
	g1.visit();
	GoodGay2 g2;
	g2.visit();
	return 0;
}

6.4.3  成员函数做友元 

class Building;//当用到友元成员函数时,需注意友元声明与友元定义之间的互相依赖。
class GoodGay {
public:
	GoodGay();

	void visit1();
	void visit2();

	Building* building;
};
class Building {
public:
	friend void GoodGay::visit1();
	Building();
public:
	string LivingRoom;
private:
	string BedRoom;
};

Building::Building()
{
	LivingRoom = "客厅";
	BedRoom = "卧室";
}

GoodGay::GoodGay()
{
	building = new Building;
}
void GoodGay::visit1()
{
	cout << "visit函数正在访问:" << building->LivingRoom << endl;
	cout << "visit函数正在访问:" << building->BedRoom << endl;
}
void GoodGay::visit2()
{
	cout << "visit函数正在访问:" << building->LivingRoom << endl;
	cout << "visit函数正在访问:" << building->BedRoom << endl;
}
void test1()
{
	GoodGay gg;
	gg.visit1();
}
void test2()
{
	GoodGay gg;
	gg.visit2();
}
int main()
{
	test1();
	test2();
}

6.5  运算符重载

        运算符重载对已有的运算符重新定义,赋予另一种功能,以适应不同的数据类型

        对内置的数据类型,编译器知道如何进行运算,对非内置的数据类型就需要对运算符进行重载。

6.5.1  加号运算符重载

class Person {
public:
	//成员函数实现+重载
	Person operator+(Person& p)
	{
		Person temp;
		temp.m_A = this->m_A + p.m_A;
		temp.m_B = this->m_B + p.m_B;
		return temp;
	}
	int m_A;
	int m_B;
};
//全局函数实现+重载
Person operator+(Person& p1, Person& p2)
{
	Person temp;
	temp.m_A = p1.m_A + p2.m_A;
	temp.m_B = p1.m_B + p2.m_B;
	return temp;
}
void test01()
{
	Person p1;
	p1.m_A = 10;
	p1.m_B = 10;
	Person p2;
	p2.m_A = 10;
	p2.m_B = 10;
	Person p3 = p1 + p2;
	cout << p3.m_A << endl << p3.m_B << endl;
}

        运算符重载也可以发生函数重载。

        对于内置的数据类型的表达式的运算符是不可能改变的。

        不要滥用运算符重载。(加法写成减法,减法写成乘法,别人可能看不懂代码)

6.5.2  左移运算符(<<)的重载

        作用:可以输出自定义的数据类型。

        加&的原因:     

        输入 ostream& cout带&的原因是因为输出流对象cout全局只能有一个,不能创建一个新的传进来,要带引用。

        写成:ostream& operator<<(ostream& cout, Person& p),则:cout<<a<<b;正确,因为它等同于(cout<<a)<<b;(cout<<a)返回cout的引用,即就是它自己,它可以再次作为左值,因而能够连着写这个输出流 。如果不加& 第一个括号中返回的是cout的临时变量,它不可以作为左值(因为operator << (ostream& cout, Person& p)的第一个参数是ostream&,而不是ostream),因而错误。

class Person {
	friend ostream& operator<<(ostream& cout, Person& p);
public:
//舍弃成员函数重载,简化为p.operator<<cout,相当于p<<cout
	//void operator<<(cout)
	//{
	//}

	Person()
	{
		this->m_a = 10;
		this->m_b = 10;
	}
private:
	int m_a;
	int m_b;
};

//全局函数重载左移运算符
//返回ostream是链式编程思想,返回后cout<<p<<...后面可以再利用

ostream& operator<<(ostream& cout,Person& p)   
{
	cout << p.m_a << endl << p.m_b << endl;
	return cout;
}

6.5.3  递增运算符的重载

        作用:通过重载递增运算符,实现自己的整型数据。

        重载 << 时 Intege r加 & 后输出 myint++ 报错,不加 & 不会报错。

        原因:后置++不是一个可修改的左值,非常量引用必须是一个可修改的左值。

class Integer {
	friend ostream& operator<<(ostream& cout, Integer p);
private:
	int num;
public:
	Integer()
	{
		this->num = 0;
	}
	//前置++
	//&的作用是对同一个数据进行递增
	Integer& operator++()
	{
		//先++
		this->num++;
		//再返回
		return *this;
	}
	//后置++
	//int是占位参数
	Integer operator++(int)
	{
		//先暂存
		Integer temp=*this;
		//再++
		num++;
		//返回暂存的数据
		return temp;
	}
};
ostream& operator<<(ostream& cout, Integer p)
{
	cout << p.num;
	return cout;
}
void test01()
{
	Integer myint;
	cout << ++myint << endl;
	cout << myint << endl;
}
void test02()
{
	Integer myint;
	cout << myint++<<endl;
	cout << myint << endl;
}

6.5.4  赋值运算符重载

         创建一个类,C++ 至少给每一个类添加4个函数:默认构造(空实现),析构函数(空实现),拷贝构造(值拷贝),赋值运算符 operator= 对属性进行值拷贝。

        赋值运算符重载要注意数据存放在堆区时,浅拷贝带来的问题。

//赋值运算符的重载
class person
{
public:
	person(int a)
	{
		age=new int(a);
	}
	~person()
	{
		if (age != NULL)
		{
			delete age;
			age = NULL;
		}
	}
	//重载赋值运算符以避免浅拷贝带来的问题
	person& operator=(person& p)
	{
		//age=p.age;//编译器默认实现
		if (age != NULL)
		{
			delete age;
			age = NULL;
		}
		age = new int(*p.age);
		//返回自身
		return *this;
	}
	int *age;
};
int main()
{
	person p1(10);
	person p2(20);
	person p3(30);
	p3=p2 = p1;
	cout << *(p2.age) << endl;//10
	cout << *(p3.age) << endl;//10
	return 0;
}

6.5.5  关系运算符的重载

        作用:可以让两个自定义类型对象进行对比操作。

//关系运算符重载 == !=
class person
{
public:
	person(string s,int a):name(s),age(a)
	{

	}
	bool operator==(person& p)
	{
		if (this->name == p.name && this->age == p.age)
		{
			return true;
		}
		else
			return false;
	}
	string name;
	int age;
};
int main()
{
	person p1("Tom",10);
	person p2("Jerry",20);
	if (p1 == p2)
	{
		cout << "p1和p2相等" << endl;
	}
	else
		cout << "p1和p2不相等" << endl;
	return 0;
}

6.5.6  函数调用运算符()重载

        重载后的使用方式非常像函数调用,因此称为仿函数。

        仿函数没有固定写法,非常灵活。

class MyPrint {
public:
	void operator()(string test)
	{
		cout << test << endl;
	}
};
void MyPrint02(string test)
{
	cout << test << endl;
}
void test01()
{
	MyPrint myprint;
	myprint("Hello World!");  //使用起来非常类似函数调用,因此称为仿函数
	MyPrint02("Hello World!");
}
//仿函数非常灵活,没有固定的写法
//加法类
class MyAdd {
public:
	int operator()(int num1, int num2)
	{
		return num1 + num2;
	}
};
void test02()
{
	MyAdd myadd;
	cout<<myadd(10, 20)<<endl;
	//匿名函数对象
	cout<<MyAdd()(10, 20)<<endl;
}

6.6  继承

6.6.1  继承的基本语法

        继承的好处:减少重复代码。

        语法:class 子类 : 继承方式 父类{};

        派生类中的成员包含两部分:一类是从基类继承过来的(共性),一类是自己增加的成员(个性)。

6.6.2  继承的方式

        公共继承,public

        保护继承,protected

        私有继承,private

 6.6.3  继承中的对象模型

        父类中的所有非静态成员属性都会被子类继承

        父类的私有成员被编译器隐藏,访问不到但是被继承

class Base
{
public:
	int a;
	static int d;//不继承
protected:
	int b;
private:
	int c;
};
class son :public Base
{
public:
	int e;
};
int main()
{
	//父类中的所有非静态成员属性都会被子类继承
	//父类的私有成员被编译器隐藏,访问不到但是被继承
	cout << "size of(son)=" << sizeof(son) << endl;//16
	return 0;
}

        利用工具查看:

1.

 

 2. 跳转盘符  

3.跳转文件路径  cd 具体路径下

4.查看命名

cl /d1 reportSingleClassLayout查看的类名 所属文件名

 6.6.4  继承中构造和析构顺序

class Base
{
public:
	Base()
	{
		cout << "父类构造"<<endl;
	}
	~Base()
	{
		cout << "父类析构"<<endl;
	}
};
class Son :public Base {
public:
	Son()
	{
		cout << "子类构造"<<endl;
	}
	~Son()
	{
		cout << "子类析构"<<endl;
	}
};

 6.6.5  继承中同名成员的处理方式

        子类对象访问子类同名成员:直接访问
        子类对象访问父类同名成员:加作用域        

        如果子类中出现和父类同名的成员函数,子类同名成员函数会隐藏掉父类中所有的同名成员函数(父类中同名成员函数,和同名成员函数的重载函数)。        

class Base
{
public:
	Base()
	{
		m_A=100;
	}
	void func()
	{
		cout << "Base_func"<<endl;
	}
	int m_A;
};
class Son :public Base {
public:
	Son()
	{
		m_A = 200;
	}
	void func()
	{
		cout << "Son_func"<<endl;
	}
	int m_A;
};
void test01()    //同名成员属性处理
{
	Son son;
	cout << son.m_A << endl;
	cout << son.Base::m_A << endl;
}
void test02()    //同名成员函数处理
{
	Son son;
	son.func();
	son.Base::func();
}

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

        静态成员和非静态成员出现同名时,处理方式一致。

        注意通过对象访问和通过类名访问两种方式。

class Base
{
public:
	
	static void func()
	{
		cout << "Base_func" << endl;
	}
	static int m_A;
};
int Base::m_A = 100;
class Son :public Base {
public:
	static void func()
	{
		cout << "Son_func" << endl;
	}
	static int m_A;
};
int Son::m_A = 200;
void test01()    //同名成员属性处理
{
	//通过对象访问
	Son son;
	cout << son.m_A << endl;
	cout << son.Base::m_A << endl;
	//通过类名访问
	cout << Son::m_A << endl;
	cout << Son::Base::m_A << endl;
}
void test02()    //同名成员函数处理
{
	//通过对象访问
	Son son;
	son.func();
	son.Base::func();
	//通过类名访问
	Son::func();
	Son::Base::func();
}

6.6.7  多继承语法

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

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

class Base1{
public:
	Base1()
	{
		this->a = 100;
	}
	int a;
};

class Base2 {
public:
	Base2()
	{
		b = 200;
	}
	int b;
};

class Son :public Base1, public Base2 {
public:
	Son()
	{
		c = 300;
	}
	int c;
};

6.6.8  菱形继承

        两个派生类继承同一个基类,又有某个类同时继承者两个派生类。

        主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义,通过虚继承来解决。

class Animal {
public:
	int age;
};

class Sheep :virtual public Animal {

};
class Camel :virtual public Animal {

};
class SheepCamel :public Sheep, public Camel {

};

void test01()
{
	Sheep sheep;
	sheep.age = 18;
	Camel camel;
	camel.age = 28;
	SheepCamel sheepcamel;
	sheepcamel.age = 38;
	cout << sheep.age << endl;
	cout << camel.age << endl;
	cout << sheepcamel.age << endl;
}

6.7  多态

6.7.1  多态的基本概念

        多态可分为两类
                静态多态:函数重载 和 运算符重载        

                动态多态:派生类和虚函数实现运行时多态

        两者的区别:

                静态多态的函数地址早绑定 - 编译阶段确定函数地址(运行前就确定了走哪一段代码)

                动态多态的函数地址晚绑定 - 运行阶段确定函数地址(运行起来才确定走哪一段代码)

class Animal {
public:
	virtual void speak()
	{
		cout << "动物在说话?";
	}
	int age;
};
class Cat :public Animal {
	virtual void speak()
	{
		cout << "小猫在说话?"<<endl;
	}
};
class Dog :public Animal {
	void speak()
	{
		cout << "小狗在说话?"<<endl;
	}
};
void DoSpeak(Animal &animal)
{
	animal.speak();
}
void test01()
{
	Cat cat;
	Dog dog;
	DoSpeak(cat);
	DoSpeak(dog);
}

         重写后,子类的vfptable中内容会发生改写。

 6.7.2  多态案例----计算器类

        多态好处:

                组织结构清晰。

                可读性强。

                对前期和后期的扩展以及维护性高。

        开发中提倡开闭原则:对扩展进行开放,对修改进行关闭。

        C++开发提倡利用多态设计程序架构,因为多态优点很多。

//抽象计算机类
class AbstractCalculator {
public:
	virtual int getresult()
	{
		return 0;
	}
	int num1;
	int num2;
};
//加法计算机类
class AddCalculator :public AbstractCalculator {
public:
	int getresult()
	{
		return num1 + num2;
	}
};
//减法计算机类
class SubCalculator :public AbstractCalculator {
public:
	int getresult()
	{
		return num1 - num2;
	}
};
//乘法计算器类
class MulCalculator :public AbstractCalculator {
public:
	int getresult()
	{
		return num1 * num2;
	}
};
void Calculator()
{
	//父类指针执行子类对象
	AbstractCalculator* abc;
	//加法
	abc = new AddCalculator;
	abc->num1 = 10;
	abc->num2 = 20;
	cout<<abc->getresult()<<endl;
	//减法
	abc = new SubCalculator;
	abc->num1 = 20;
	abc->num2 = 10;
	cout << abc->getresult()<<endl;
	//乘法
	abc = new MulCalculator;
	abc->num1 = 10;
	abc->num2 = 20;
	cout << abc->getresult()<<endl;
	delete abc;
}

6.7.3  纯虚函数和抽象类

        在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容,因此可

以将虚函数改为纯虚函数。

        纯虚函数定义virtual 返回值类型 函数名 (参数列表)= 0 ;

        抽象类定义:当类中有了纯虚函数,这个类也称为抽象类

        抽象类的特点

                无法实例化对象

                子类必须重写抽象类中的纯虚函数,否则也属于抽象类

class Base
{
public:
	//纯虚函数
	virtual void func() = 0;
};
class Son1 :public Base
{
public:
};
class Son2 :public Base
{
public:
	void func() 
	{
    }
};
int main()
{
	//base b;//err 抽象类无法实例化对象
	//new base;//err 

	//son1 s;//子类必须重写抽象类中的纯虚函数,否则也属于抽象类
	son2 s;
}

6.7.4  多态案例二----制作饮品

class AbstructDrinking {
public:
	//煮水
	virtual void BoilWater()
	{
		cout << "煮水" << endl;
	}
	//冲泡
	virtual void Brew() = 0;
	//倒入杯中
	virtual void PourInCup()
	{
		cout << "倒入杯中" << endl;
	}
	//加入辅料
	virtual void PutSomething() = 0;
	void makeDrink()
	{
		BoilWater();
		Brew();
		PourInCup();
		PutSomething();
	}
};
class Coffee :public AbstructDrinking {
public:
	//冲泡
	virtual void Brew()
	{
		cout << "冲泡咖啡"<<endl;
	}
	//加入辅料
	virtual void PutSomething()
	{
		cout << "加入糖和牛奶" << endl;
	}
};
class Tea :public AbstructDrinking {
public:
	//冲泡
	virtual void Brew()
	{
		cout << "冲泡茶叶" << endl;
	}
	//加入辅料
	virtual void PutSomething()
	{
		cout << "加入柠檬" << endl;
	}
};
void DoWork(AbstructDrinking* drink)
{
	drink->makeDrink();
	delete drink;
}
void test01()
{
	AbstructDrinking* drink = new Coffee;
	DoWork(drink);
	cout << "----------------------" << endl;
	drink = new Tea;
	DoWork(drink);
}

6.7.5  虚析构和纯虚析构

        多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构

函数,出现内存泄漏。

        解决方式:将父类中的析构函数改为虚析构或者纯虚析构

        虚析构语法virtual ~类名(){}

        纯虚析构语法virtual ~类名() = 0; 

         类名::~类名(){}

                纯虚析构函数要有具体的函数实现

        两者共性:

                都可以解决父类指针释放子类对象。

                都需要具体的函数实现。

        两者区别:

                如果是纯虚析构,该类属于抽象类,无法实例化对象。

class Animal
{
public:
	Animal()
	{
		cout << "Animal构造函数的调用" << endl;
	}
	~Animal()
	{
		cout << "Animal析构函数的调用" << endl;
	}

	//虚析构可以解决 父类指针释放子类对象时不干净的问题
	//virtual ~Animal()
	//{
	//	cout << "Animal虚析构函数的调用" << endl;
	//}

	//纯虚析构
	//virtual ~Animal() = 0;
	virtual void speak() = 0;

};
//纯虚析构函数要有具体的函数实现
//Animal::~Animal()
//{
//	cout << "Animal纯虚析构函数的调用" << endl;
//}
class Cat:public Animal
{
public:
	Cat(string n)
	{
		cout << "Cat的构造函数的调用" << endl;
		name = new string(n);
	}
	void speak()
	{
		cout << *name<<"miao~" << endl;
	}
	~Cat()
	{
		cout << "Cat的析构函数的调用" << endl;
		if (name != NULL)
		{
			delete name;
			name = NULL;
		}
	}
	string *name;
};
int main()
{
	Animal* a = new Cat("mimi");
	a->speak();
	//父类指针在析构时候,不会调用子类中的析构函数,导致子类如果有堆区的属性,会出现内存的泄露
	delete a;
}

         总结:

                虚析构或纯虚析构就是用来解决通过父类指针释放子类对象。

                如果子类中没有堆区数据,可以不写为虚析构或纯虚析构。

                用于纯虚析构函数的类也属于抽象类

6.7.6  多态案例三----电脑组装

//抽象不同零件的类
class CPU {
public:
	virtual void calculate() = 0;
};

class VideoCard {
public:
	virtual void display() = 0;
};

class Memory {
public:
	virtual void storage() = 0;
};

//电脑类
class Computer {
public:
	Computer(CPU* cpu, VideoCard* vc, Memory* mem)
	{
		this->cpu = cpu;
		this->vc = vc;
		this->mem = mem;
	}
	void Work()
	{
		cpu->calculate();
		vc->display();
		mem->storage();
	}
	~Computer()
	{
		if (cpu != NULL)
		{
			cout << "cpu释放" << endl;
			delete cpu;
			cpu = NULL;
		}
		if (vc != NULL)
		{
			cout << "显卡释放" << endl;
			delete vc;
			vc = NULL;
		}
		if (mem != NULL)
		{
			cout << "内存条释放" << endl;
			delete mem;
			mem = NULL;
		}
		cout << "电脑释放" << endl;
	}
	void Cpu()
	{
		cout << cpu << endl;
	}
private:
	CPU* cpu;
	VideoCard* vc;
	Memory* mem;
};

//Intel
class IntelCpu :public CPU {
public:
	virtual void calculate()
	{
		cout << "Intel的CPU开始计算了?"<<endl;
	}
};
class IntelVc :public VideoCard {
public:
	virtual void display()
	{
		cout << "Intel的显卡开始显示了?"<<endl;
	}
};
class IntelMem :public Memory {
public:
	virtual void storage()
	{
		cout << "Intel的内存条开始存储了?"<<endl;
	}
};

//Lenovo
class LenovoCpu :public CPU {
public:
	virtual void calculate()
	{
		cout << "Lenovo的CPU开始计算了?"<<endl;
	}
};
class LenovoVc :public VideoCard {
public:
	virtual void display()
	{
		cout << "Lenovo的显卡开始显示了?"<<endl;
	}
};
class LenovoMem :public Memory {
public:
	virtual void storage()
	{
		cout << "Lenovo的内存条开始存储了?"<<endl;
	}
};

void test01()
{
	//第一台电脑零件
	CPU* cpu = new IntelCpu;
	VideoCard* vc = new IntelVc;
	Memory* mem = new IntelMem;

	//创建第一台电脑
	Computer *computer = new Computer(cpu, vc, mem);
	computer->Work();
	delete computer;
}
举报

相关推荐

0 条评论