c++笔记:类与对象

类和对象

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

1.封装

1.1 封装的意义

  • 将属性与行为作为一个整体,表现事物

  • 将属性行为加以权限控制

封装意义一:

在设计类的时候,属性与行为写在一起表现事物

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

示例:设计一个圆类,求圆周长

#include <iostream>
using namespace std;

#define pi 3.14

class Circle{
	//访问权限
	//公共权限
public:

	//属性
	int m_r;  //半径

	//行为
	double calculateZC(){
		return 2*pi*m_r;
	}
};
 
int main(){

	//通过圆类 创建具体的圆(对象)
	Circle c1;
	//给圆对象的属性进行赋值
	c1.m_r=10;

	cout<<"圆的周长为:"<<c1.calculateZC()<<endl;

	return 0;
}

封装意义二:

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

访问权限有三种:

  1. public 公共权限 成员 类内、类外均可访问

  1. protected 保护权限 成员 类内可访问,类外不可访问 子类可访问父类的保护内容

  1. private 私有权限 成员 类内可访问,类外不可访问 子类不可访问父类的私有内容

#include <iostream>
#include<string>
using namespace std;

class Person{

public:    //公共权限
	string Name;  //姓名

protected:    //保护权限
	string Car;  //汽车

private:    //私有权限
	int Password;

public:
	void func(){
		Name="张三";
		Car="拖拉机";
		Password=123456;
	}
};
 
int main(){

    Person p1;
	p1.Name="李四";
	p1.Car="奔驰";  //非法语句,保护权限内容类外不可访问
	p1.Password=123;  //非法语句,私有权限内容类外不可访问

	return 0;
}

1.2 class 和 struct 区别

在于默认的访问权限不同:

  • struct 默认权限为公有

  • class 默认权限为私有

1.3 成员属性设置为私有

优点一:将所有成员属性设为私有,可自己控制读写权限

优点二:对于写权限,我们可以检测数据有效性

实例:创建圆和点类,判断点和圆的关系:

#include <iostream>
using namespace std;

class Point{
public:
	//设置x坐标
	void set_X(int x){
		m_X=x;
	}

	//设置Y坐标
	void set_Y(int y){
		m_Y=y;
	}

	//获取x坐标
	int get_X(){
		return m_X;
	}

	//获取Y坐标
	int get_Y(){
		return m_Y;
	}

private:
	int m_X;
	int m_Y;

};

class Circle{
public:
	//设置半径
	void setm_R(int r){
		m_R=r;
	}

	//获取半径
    int getm_R(){
		return m_R;
	}

	//设置圆心
	void setm_Center(Point center){
		m_Center=center;
	}

	//获取圆心
    Point getm_Center(){
		return m_Center;
	}

private:
	int m_R;  //长
	Point m_Center;  //圆心点 在类中可以让另一类作本类成员
   
};

//判断点与圆的关系
void judge(Point &point, Circle &circle){
	//计算两点间距离平方
	int delta_s2=
	(circle.getm_Center().get_X()-point.get_X())*(circle.getm_Center().get_X()-point.get_X())+
	(circle.getm_Center().get_Y()-point.get_Y())*(circle.getm_Center().get_Y()-point.get_Y());

	//计算半径平方
	int r2=circle.getm_R()*circle.getm_R();

	//判断关系
	if(delta_s2>r2)cout<<"点在圆外!"<<endl;
	else if(delta_s2<r2)cout<<"点在圆内!"<<endl;
	else cout<<"点在圆上!"<<endl;
}

	int main(){
		Point point;
		point.set_X(10);
		point.set_Y(2);

		Point center;
		center.set_X(10);
		center.set_Y(0);

		Circle circle;
		circle.setm_R(10);
		circle.setm_Center(center);

		judge(point,circle);
	
	return 0;
}

ATTENTION! 这样的代码在工程上是不适合的——所有的类都在一个文件中,应分文件编写,具体步骤如下:

创建头文件:

创建源文件

同理创建circle的头文件与源文件

主文件仅需保留主函数与判断函数

2.对象的初始化和清理

2.1构造函数与析构函数

由编译器自动调用,完成对象初始化与清理。如果我们不构造与析构,编译器会提供,但编译器提供的构造与析构是空实现

  • 构造函数:用于创建对象时为对象属性赋值

  • 析构函数:对象销毁前系统自动调用,执行清理

构造函数语法:类名(){}

  1. 构造函数,无返回值也不写void

  1. 函数名与类名相同

  1. 可有参数,可重载

  1. 程序调用对象时自动调用构造,无需手动且只调一次

析构函数语法:~类名(){}

  1. 析构函数,无返回值也不写void

  1. 函数名与类名相同,名称前加~

  1. 不可有参数,不可重载

  1. 程序对象销毁时自动调用析构,无需手动且只调一次

2.2构造函数的分类及调用

按参数分类:有参构造、无参构造

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

三种调用方式:括号法、显示法、隐式转换法

#include<iostream>
using namespace std;

//构造函数 初始化操作
class Person{
public:
	Person(){
	    cout<<"Person无参构造函数的调用"<<endl;
	}

	Person(int a){
		age=a;
	    cout<<"Person(int a)有参构造函数的调用"<<endl;
	}

	//拷贝构造
	Person(const Person &p){
		cout<<"Person(const Person &p)拷贝构造函数的调用"<<endl;
		age=p.age;
	}

	~Person(){
	cout<<"Person析构函数的调用"<<endl;
	}


	int age;

};

void test01(){
//    1.括号法
//	Person p1;  //默认构造的调用,注意不要用括号,否则编译器认为是函数声明而非对象创建
//	Person p2(10);  //有参构造调用
//	Person p3(p2);  //拷贝构造的调用

//	cout<<"年龄为:"<<p2.age<<endl;
//	cout<<"年龄为:"<<p3.age<<endl;

	//2.显示法
	Person p1;
	Person p2=Person(10);
	Person p3=Person(p2);
	//Person(p3);  不要用拷贝构造函数初始化匿名对象

	//3.隐式转换法
	Person p4=10;  //相当于Person p4=Person(10);
	Person p5=p4;  //相当于Person p5=Person(p4);
     
}

int main(){
	test01();

	system("pause");
	return 0;
}

2.3 拷贝构造函数调用时机

通常三种情况:

  • 使用一个已经创建完毕的对象初始化一个新对象

  • 值传递的方式给函数参数传值

  • 以值方式返回局部对象

#include<iostream>
using namespace std;

//构造函数 初始化操作
class Person{
public:
	Person(){
	    cout<<"Person无参构造函数的调用"<<endl;
	}

	Person(int age){
		m_age=age;
	    cout<<"Person(int age)有参构造函数的调用"<<endl;
	}

	//拷贝构造
	Person(const Person &p){
		cout<<"Person(const Person &p)拷贝构造函数的调用"<<endl;
		m_age=p.m_age;
	}

	~Person(){
	cout<<"Person析构函数的调用"<<endl;
	}

	int m_age;

};

     //1.用一个已经创建完毕的对象初始化一个新对象
void test01(){
	Person p1(20);
	Person p2(p1);
	cout<<"p2的年龄为:"<<p2.m_age<<endl;
}

	//2.值传递的方式给函数参数传值
void doo(Person pp){
}

void test02(){
	Person p;
	doo(p);
}

	//3.值方式返回局部对象
Person dooo(){
	Person p3;
	return p3;
}

void test03(){
	Person P=dooo();
}
	
int main(){
	//test01();
	//test02();
	test03();

	system("pause");
	return 0;
}

2.4 构造函数调用规则

默认情况下,c++编译器至少给一个类添加3个函数:

  1. 默认构造(无参,函数体为空)

  1. 默认析构(无参,函数体为空)

  1. 默认拷贝

调用规则:

  • 若用户定义有参构造函数,c++不再提供默认无参,但会提供默认拷贝

  • 若用户定义拷贝,c++不会提供其他构造函数

2.5 深拷贝与浅拷贝

浅拷贝:简单赋值拷贝操作 带来的问题是堆区内存重复释放,非法操作

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

#include<iostream>
using namespace std;

//构造函数 初始化操作
class Person{
public:
	Person(){
	    cout<<"Person无参构造函数的调用"<<endl;
	}

	Person(int age,int height){
		m_age=age;
		m_height=new int(height);
	    cout<<"Person(int age)有参构造函数的调用"<<endl;
	}

	//拷贝构造
	Person(const Person &p){
		cout<<"Person(const Person &p)拷贝构造函数的调用"<<endl;
		m_age=p.m_age;
		//m_height=p.m_height; 编译器自动调用的拷贝构造函数语句

		//深拷贝操作,重新在堆区申请内存空间
		m_height=new int(*p.m_height);
	}

	~Person(){  //析构代码,将堆区开辟的数据手动释放

		if(m_height!=NULL){
		delete m_height;
		m_height=NULL;
		}

	    cout<<"Person析构函数的调用"<<endl;
	}

	int m_age;  //年龄
	int *m_height;  //身高

};

void test01(){
	Person p1(20,172);
	cout<<"p1年龄为:"<<p1.m_age<<endl;
	cout<<"p1身高为:"<<*p1.m_height<<endl;

	Person p2(p1);
	cout<<"p2的年龄为:"<<p2.m_age<<endl;
	cout<<"p2身高为:"<<*p2.m_height<<endl;
}


int main(){
	test01();

	system("pause");
	return 0;
}

SUMMARY: 如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝问题

2.6 初始化列表

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

eg.

class Person{
public:
    Person(int a,int b,int c):m_A(a),m_B(b),m_C(c){   
    }
       
    int m_A;
    int m_B;
    int m_C;
};

2.7 类对象作为类成员

当其他类的对象作为本类成员,先构造其他类对象,再构造自身;先析构自身,再析构其他类对象

#include<iostream>
#include<string>
using namespace std;

class Phone{
public:
	Phone(string Brand,int num){
		P_Brand=Brand;
		P_num=num;
		cout<<"Phone构造函数调用"<<endl;
	}

	string P_Brand;  //手机品牌
	int P_num;  //手机号

};

class Person{
public:
	Person(string Name,string Brand,int num):P_Name(Name),P_Phone(Brand,num){
		cout<<"Person的构造函数调用"<<endl;
	}


	string P_Name;  //姓名
	Phone P_Phone;  //手机
};

void test01(){
	Person p1("昊京","苹果14",1232343);
	cout<<p1.P_Name<<"拿着一个"<<p1.P_Phone.P_Brand<<"牌子的手机,手机号是:"
		<<p1.P_Phone.P_num<<endl;
}


int main(){
	test01();

	system("pause");
	return 0;
}

2.8 静态成员

即在成员变量和成员函数前加关键字static,成为静态成员

  • 静态成员变量

所有对象共享一份数据

在编译阶段分配内存(全局区)

类内声明,类外初始化

  • 静态成员函数

所有对象共享一个函数

静态成员函数只能访问静态成员变量

#include <iostream>
using namespace std;

class Person{
public:
	static int m_A;
	int m_C;

	static void func(){
		m_A=0;
		//m_C=1;  静态成员函数仅能访问静态成员变量
	cout<<"static void func的调用"<<endl;
	}
protected:
	static int m_B;
};

int Person::m_A=100;

void test01(){

	Person p;
	cout<<p.m_A<<endl;

	//Person p;
	p.func();

	cout<<Person::m_A<<endl;  //可通过类名访问静态成员变量
	//cout<<Person::m_B<<endl;  //类外访问不到私有与保护
	Person::func();
}

void main(){
	test01();
}

3. c++对象模型和this指针

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

非静态成员变量 属于类的对象

静态成员变量 不属于类对象

非静态成员函数 不属于类对象

静态成员函数 不属于类对象

空类所占内存空间为1,编译器为空对象分1字节空间以区分空对象占内存位置

3.2 this指针

this指针指向被调用的成员函数所属的对象,本质是指针常量

用途:

  • 当形参与成员变量同名时,可用其区分

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

#include <iostream>
using namespace std;

class Person{
public:
    Person(int age){
        this->age=age;  //this指向被调用的成员函数所属对象
    }

    Person &PersonADDage(Person p){  
//返回值类型为引用才能返回自己,若去掉引用,则为值返回局部对象,将调用拷贝构造函数产生新对象
        this->age+=p.age;
        return *this;
    }

    int age;
};

void test01(){

    Person p1(18);
    cout<<"p1年龄为:"<<p1.age<<endl;  

}

void test02(){
    Person p2(10);
    Person p3(10); 

    p3.PersonADDage(p2).PersonADDage(p2);

    cout<<"p3的年龄为"<<p3.age<<endl;

}

void main(){
    test02();
}

3.3 空指针访问成员函数

#include <iostream>
using namespace std;

class Person{
public:
	void showClassName(){
		cout<<"this is Person class!"<<endl;
	}

	void showPersonAge(){
/*   此段可提升代码健壮性,即使空指针访问有属性成员函数也不会报错
		if(this==NULL){
			return;
		}
*/
		cout<<"age="<<m_Age<<endl;  //实际上此句为cout<<"age="<<this->m_Age<<endl;
	}

	int m_Age;
};

void test01(){

	Person *p=NULL;
	p->showClassName();
	p->showPersonAge();  //非法操作,因为p为空指针,并无实际对象,加上/*...*/后才正常

}


void main(){
	test01
		();

}

3.4 const修饰成员函数

常函数:

  • 成员函数后加const

  • 常函数内不可修改成员属性

  • 成员属性声明加关键字mutable后在常函数中可修改

常对象:

  • 声明对象前加const为常对象

  • 常对象只能调用常函数

#include <iostream>
using namespace std;

class Person{
public:

	void showPerson()const{  //成员函数后加const,修饰的是this指针指向值,令其不可改
		this->m_A=100;  //非法操作
		this->m_B=100;  //合法操作	
	}

	void func(){
	}

	int m_A;
	mutable int m_B;  //加mutable关键字,使属性可以在常函数中修改
};

void test01(){
	Person p;
	p.showPerson();
}

void test02(){
	const Person pp;
	pp.m_A=1;  //非法操作,常对象属性不可改
	pp.m_B=1;
	
	pp.showPerson();
	pp.func();  //非法操作,常对象只能调常函数,因为普通函数可修改属性
}


void main(){
	test01();
	test02();

}

4. 友元

目的:让一个函数或类访问另一个类中私有成员

关键字:friend

实现:

  • 全局函数作友元

  • 类作友元

  • 成员函数作友元

#include <iostream>
#include<string>
using namespace std;

class Building;

class GoodGay1{
public:
	Building *m_building;

	GoodGay1();
    
	void visit();
};

class GoodGay2{
public:
	Building *mm_building;

	GoodGay2();
	
	void vvisit();
};


class Building{

    friend void goodgay(Building &build);
	friend class GoodGay1;
	friend void GoodGay2::vvisit();
	
public:

	Building();

	string m_SittingRoom;

private:
	string m_BedRoom;
};

Building::Building(){
        m_SittingRoom="客厅";
		m_BedRoom="卧室";
}


GoodGay1::GoodGay1(){
	m_building=new Building;
}

GoodGay2::GoodGay2(){
	mm_building=new Building;
	}


void GoodGay1::visit(){
	cout<<"GoodGay类正在访问:"<<m_building->m_SittingRoom<<endl;
	cout<<"GoodGay类正在访问:"<<m_building->m_BedRoom<<endl;
}

void GoodGay2::vvisit(){
	cout<<"GoodGay2类中vvsit()函数正在访问"<<mm_building->m_SittingRoom<<endl;
	cout<<"GoodGay2类中vvsit()函数正在访问"<<mm_building->m_BedRoom;
}

void test01(){
	GoodGay1 GG;
	GG.visit();
}

void test02(){
	GoodGay2 Gg;
	Gg.vvisit();
}

void goodgay(Building &build){
	cout<<"全局函数goodgay正在访问"<<build.m_SittingRoom<<endl; 
	cout<<"全局函数goodgay正在访问"<<build.m_BedRoom<<endl;
}

void main(){

	Building building;
	goodgay(building);

	test01();

	test02();

}

5. 运算符重载

5.1 加号运算符重载

实现自定义数据相加

#include <iostream>
#include<string>
using namespace std;

class Person{
public:
	int m_A;
	int m_B;

	/*成员函数重载
	Person operator+(Person &pers){
		Person temp;
		temp.m_A=this->m_A+pers.m_A;
        temp.m_B=this->m_B+pers.m_B;

		return temp;
	}*/

};

/*全局函数重载*/
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;
}

Person operator+(Person &p,int a){
	Person temp;
	temp.m_A=p.m_A+a;
	temp.m_B=p.m_B+a;
	return temp;
}

void test01(){
	Person p1;
	p1.m_A=10;
	p1.m_B=10;

	Person p2;
	p2.m_A=20;
	p2.m_B=20;
	
	Person p3=p1+p2;
	cout<<p3.m_A<<'\t'<<p3.m_B<<endl;
    
    Person p4=p2+100;
	cout<<p4.m_A<<'\t'<<p4.m_B<<endl;
}

void main(){
	test01();
	
}

5.2 左移运算符重载

输出自定义数据类型

#include<iostream>
using namespace std;

// 左移运算符重载
// 作用:可以输出自定义数据类型
class Person {
// 友元,目的是让重载<<运算符的函数也可打印Person中的私有属性
	friend ostream& operator<<(ostream& cout, Person& p);
	friend void test01();
    
private:
	int m_A;
	int m_B;
	// 通常不会用成员函数重载左移运算符,
    //若void operator<<(Person &p),则p.operator<<(p),是错误的
	// 而void operator<<(cout),会变成p<<cout,cout在右边,实际应该是cout<<P,在左边
};
// 本质 operator<<(cout,p),简化cout<<p;
//链式规则,返回cout,可以无限追加
// 如何知道cout属于什么数据类型呢?点击cout,右键:转到定义(或者按F12)
ostream& operator<<(ostream &cout,Person &p) {
	cout << "p.m_A=" << p.m_A << "   " << "p.m_B=" << p.m_B;
	return cout;
}

void test01() {
	Person p; 
	p.m_A = 10;
	p.m_B = 10;
	cout << "这是普通输出:" << endl;
	cout << "p.m_A=" << p.m_A << "    " << "p.m_B=" << p.m_B <<endl;
	//链式规则,返回cout,可以无限追加
	cout << endl << "链式规则:" << endl;
	cout << p <<endl<<"hello,world"<<endl;

}
int main() {
	test01();
	system("pause");
	return 0;
}

5.3 递增运算符重载

#include<iostream>
using namespace std;

class MyInteger{
    friend ostream &operator<<(ostream &out,MyTnteger &myint);
public:
    MyInteger(){
        m_Num=0;
    }
    
    //重载前置++运算符
    MyInteger& operator++(){  
 /*为什么返回引用不返回值?如果返回值,实际上返回了一个新对象而非本来的对象。返回引用是为了对一个对象进行操作*/
        m_Num++;  //先++
        return *this; //再返回 
 /*若无return,则返回类型为void,错误;应返回对象自身,因此用this指针(返回被调用的成员函数的对象)*/
    }
    
    //重载后置++运算符
    MyInteger operator++(int){  //注意参数列表要加int(一个占位参数,区分前置和后置递增),否则函数无法重载
/*为什么返回值不返回引用?若返回引用,返回结果实际是局部对象的引用,函数执行后该局部对象立即被释放,不可作返回值*/        
        //注意后置递增是先执行表达式后++操作
        MyInteger temp=*this;  //先 记录结果
        m_Num++;  //后递增
        return temp;  //最后返回记录的结果
    }
    
private:
    int m_Num;
};

//重载左移运算符
ostream &operator<<(ostream &out,MyTnteger &myint){
    out<<myint.m_Num;
    return out;
}

void test01(){
    MyInteger myint;
    cout<<++myint<<endl;
}

void test02(){
    MyInteger myint;
    cout<<myint++<<endl;
    cout<<myint<<endl;
}

void main(){
    test01();
    test02();
}

5.4 赋值运算符重载

#include <iostream>
using namespace std;

class Person{
public:
	
	Person(int age){
		m_Age=new int(age);
	}

	//重载赋值运算符
	Person &operator=( Person &p){
/*为什么返回引用不返回值?如果返回值,实际上返回了一个新对象而非本来的对象。返回引用是为了对一个对象进行操作*/
        
		// m_Age=p.m_Age;  //编译器提供的浅拷贝,但析构时会发生堆区数据重复释放

		//1.先判断是否有属性在堆区,如果有,先释放干净
		if(m_Age!=NULL){
			delete m_Age;
			m_Age=NULL;
		}

		//2.再进行深拷贝操作
		m_Age=new int(*p.m_Age);

		return *this;
	}

	~Person(){
		if(m_Age!=NULL){
			delete m_Age;
			m_Age=NULL;
		}
	}

	int *m_Age;
};


void test01(){
	Person p1(23);
	cout<<"p1年龄为:"<<*p1.m_Age<<endl;

	Person p2(18);
	cout<<"p2年龄为:"<<*p2.m_Age<<endl;

	p2=p1;  
	cout<<"p1年龄为:"<<*p1.m_Age<<"p2年龄为:"<<*p2.m_Age<<endl;

}

void main(){
	test01();
}

5.5 关系运算符重载

#include <iostream>
#include<string>
using namespace std;

class Person{
public:

	Person(string name,int age){
		m_Name=name;
		m_Age=age;
	}

	//重载==
	bool operator==(Person &p){
		if(this->m_Name==p.m_Name&&this->m_Age==p.m_Age){
			return true;
		}
		else return false;
	}

	//重载!=
	bool operator!=(Person &p){
		if(this->m_Name!=p.m_Name||this->m_Age!=p.m_Age){
			return true;
		}
		else return false;
	}

	string m_Name;
	int m_Age;
};


void test01(){
	Person p1("Tom",23);
	
	Person p2("Jerry",23);
	
	if(p2==p1)cout<<"p1=p2"<<endl;
	if(p2!=p1)cout<<"p1!=p2"<<endl;
	
}

void main(){
	test01();
}

5.6 函数调用运算符重载

#include <iostream>
#include<string>
using namespace std;

class MyPrint{
public:
	void operator()(string str){
		cout<<str<<endl;
	}

	int operator()(int num1,int num2){
		return num1+num2;
	}

};

int main(){
	MyPrint myprint;
	myprint("Hello SJTU!");
	myprint("Hello SJTU!");
	int a=myprint(1,2);
	int b=myprint(1,2);
	cout<<a<<'\t'<<b;
    
    cout<<MyPrint()(100,100)<<endl;  //匿名对象调用
	
	return 0;
}

6. 继承

6.1 语法:class 子类:继承方式 父类

#include <iostream>
#include<string>
using namespace std;

class BasePage{
public:
	void header(){
		cout<<"首页 公开课 登录 注册 ···"<<endl;
	}
	void footer(){
		cout<<"帮助 交流 地图···"<<endl;
	}
	void left(){
		cout<<"Java c++ Python"<<endl;
	}
};

class Java:public BasePage{
public:
	void content(){
		cout<<"Java course"<<endl;
	}
};

class Cpp:public BasePage{
public:
	void content(){
		cout<<"c++ course"<<endl;
	}

};

void test01(){
	cout<<"Java page"<<endl;
	Java ja;
	ja.header();
	ja.footer();
	ja.left();
	ja.content();

	cout<<"c++ page"<<endl;
	Cpp c;
	c.header();
	c.footer();
	c.left();
	c.content();

}

int main(){
	test01();
	return 0;
}

6.2 继承方式

  • 公共继承

  • 保护继承

  • 私有继承

class Base{
public:
	int m_A;
protected:
	int m_B;
private:
	int m_C;
};

class Son1:public Base{
public:
	void func(){
		m_A=10;  //父类中public成员,到子类中依然public
		m_B=20;  //父类中protected成员,到子类中依然protected
		//m_C=30;  //父类中private成员,到子类中private,子类无法访问
	}

};
void test01(){
	Son1 s1;
	s1.m_A=100;
	//s1.m_B=200;  //非法操作,保护权限类外不可访问
	//s1.m_C=300;  //非法操作,私有权限类外不可访问  
}

class Son2:protected Base{
public:
	void func(){
		m_A=40;  //父类中public成员,到子类中protected
		m_B=50;  //父类中protected成员,到子类中依然protected
		//m_C=60;  //父类中private成员,到子类中private,子类无法访问
	}
};

class Son3:private:Base{
public:
	void func{}{
		m_A=70;  //父类中public成员,到子类中private
	    m_B=80;  //父类中protected成员,到子类中private
	    //m_C=90;  //父类中private成员,到子类中private,子类无法访问
	}
};

class GrandSon:public Son3{
public:
	void func(){
		//m_A=12;  //非法,在Son3中已是private
		//m_B=23;  //非法,在Son3中已是private
		//m_C=34;  //非法,在Son3中已是private
	}

};

6.3 继承中对象模型

子类会全部继承父类的属性与行为,只是根据继承方式不同编译器会隐藏某些属性行为

6.4 继承中构造和析构顺序

#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 test01(){
	//Base base;
	Son son;
}

int main(){	
	test01();
	return 0;
}

6.5 继承中同名成员处理

  • 访问子类同名成员 直接访问

  • 访问父类同名成员 加作用域

#include <iostream>
using namespace std;

class Base{
public:
	int m_A;

	Base(){
		m_A=100;
	}

	void func(){
		cout<<"Base中func调用"<<endl;
	}

	void func(int a){
		cout<<"Base中func(int a)调用"<<endl;
	}
};

class Son:public Base{
public:
	int m_A;

	Son(){
		m_A=200;
	}

	void func(){
		cout<<"Son中func调用"<<endl;
	}
};

void test01(){
	Son son;
	cout<<"son.m_A="<<son.m_A<<endl;
	cout<<"Base中 m_A="<<son.Base::m_A<<endl; //通过子类对象访问父类中同名属性需加作用域
}

void test02(){
	Son s;
	s.func();
    
/*若子类中出现与父类同名成员函数,子类同名成员会隐藏父类中所有同名成员函数,若想访问到父类中被隐藏的同名成员函数,需加作用域*/
	s.Base::func();
	s.Base::func(100);
}

int main(){	
	test01();
	test02();
	return 0;
}

6.6 继承同名静态成员处理

静态同名与非静态处理方式一致

还可通过类名直接访问:类名::静态属性、子类名::父类名::静态属性

6.7 多继承语法

c++允许一个类继承多个类

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

多继承可能引发同名成员,需加作用域区分。实际工程不建议使用

6.8 菱形继承

概念:两个派生类继承同一个基类,又有某个类同时继承这两个派生类

7.多态

7.1 多态概念

分类:

  • 静态多态:函数重载 运算符重载属于静态

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

区别:

  • 静态多态的函数地址早绑定——编译阶段确定函数地址

  • 动态多态的函数地址晚绑定——运行阶段确定函数地址

#include <iostream>
using namespace std;

class Animal{
public:
	 virtual void speak(){  //虚函数 若无virtual,地址早绑定,编译阶段确定函数地址         
		cout<<"animal is talking!"<<endl;
	}
/*此函数写完后类内产生一个指针——vfptr(virtual function pointer),指向虚函数表vftable(表内存有虚函数地址&Animal::speak)*/
};


class Cat: public Animal{
public:
	void speak(){    //virtual可加可不加
		cout<<"kitten is speaking!"<<endl;
	}
/*若无子类重写,则子类由父类继承而来指针——vfptr(virtual function pointer),指向虚函数表vftable(表内存有虚函数地址&Animal::speak);但虚函数重写后子类中虚函数表内容会替换为子类虚函数地址&Cat::speak(父类的不变)*/
};

//多态条件:
//1.有继承关系
//2.子类重写父类虚函数,重写时virtual关键字可加可不加

//动态多态使用:父类的指针或引用指向子类对象

void doSpeak(Animal &animal){  //父类的引用指向子类对象
	animal.speak();	
}

void test01(){
	Cat cat;
	doSpeak(cat);/*此句相当于Animal &animal=cat;当animal调用speak()函数,由于animal指向cat,所以在子类中虚函数表中找speak()*/
}


int main(){	
	test01();
	return 0;
}

案例:利多态实现加减乘计算

#include <iostream>
using namespace std;

class AbstractCalculater{
public:
	int m_Num1;
	int m_Num2;
	virtual int getResult()=0;
    /*{
		return 0;  父类虚函数实现无意义,故写为纯虚函数
	}*/
};

//加法类
class AddCalculater:public AbstractCalculater{
public:
	virtual int getResult(){
		return m_Num1+m_Num2;
	}
};

//减法类
class SubCalculater:public AbstractCalculater{
public:
	virtual int getResult(){
		return m_Num1-m_Num2;
	}
};

//乘法类
class MultiCalculater:public AbstractCalculater{
public:
	virtual int getResult(){
		return m_Num1*m_Num2;
	}
};

void test(){
	//加法
	AbstractCalculater *calculater=new AddCalculater;
	calculater->m_Num1=40;
	calculater->m_Num2=10;
	cout<<calculater->m_Num1<<"+"<<calculater->m_Num2<<"="<<calculater->getResult()<<endl;
	delete calculater;  // 堆区开辟的数据记得释放

	//减法
	calculater=new SubCalculater;
	calculater->m_Num1=40;
	calculater->m_Num2=10;
	cout<<calculater->m_Num1<<"-"<<calculater->m_Num2<<"="<<calculater->getResult()<<endl;
	delete calculater;  // 堆区开辟的数据记得释放

	//乘法
	calculater=new MultiCalculater;
	calculater->m_Num1=40;
	calculater->m_Num2=10;
	cout<<calculater->m_Num1<<"*"<<calculater->m_Num2<<"="<<calculater->getResult()<<endl;
	delete calculater;  // 堆区开辟的数据记得释放
}
int main(){	
	test();
	return 0;
}

优点:

  1. 组织结构清晰

  1. 可读性强

  1. 对于后期扩展及维护性好

7.3 纯虚函数和抽象类

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

语法:virtual 返回值类型 函数名(参数列表)=0;

当类中有纯虚函数,类称抽象类。其无法实例化对象,且子类必须重写纯虚函数,否则也属于抽象类

7.4 虚析构与纯虚析构

多态使用时,如果子类中有属性开辟至堆区,那么父类纸质恩释放时无法调用子类析构代码

解决:将 父类 中析构改为虚析构或纯虚析构

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

纯虚析构语法:~virtual 类名()=0; 且要在类外完成其实现,类名::~类名(){}

#include <iostream>
#include<string>
using namespace std;

class Animal{
public:
	 virtual void speak()=0;
	 Animal(){
	    cout<<"Animal构造调用!"<<endl;
	 }

	 //virtual ~Animal()=0;  纯虚析构函数,若要用,须在类外给出纯虚析构函数体以防父类开辟到堆区数据
	 ~virtual Animal(){   //若无virtual,父类指针析构时不会调用子类析构,导致子类中若有堆区属性会发生内存泄漏
	    cout<<"Animal析构调用!"<<endl;
	 }
	 
};
//Animal:: ~Animal(){}  纯虚析构类外实现

class Cat: public Animal{
public:

	Cat(string name){
		cout<<"Cat构造调用!"<<endl;
		m_Name=new string(name);
	}

	void speak(){    
		cout<<*m_Name<<"_the kitten is speaking!"<<endl;
	}

	~Cat(){
		if(m_Name!=NULL){
			cout<<"Cat析构调用!"<<endl;
			delete m_Name;
			m_Name=NULL;
		}
	}

	string *m_Name;
};

void test(){
	Animal *animal=new Cat("Tom");
	animal->speak();
//父类指针析构时不会调用子类析构,导致子类中若有堆区属性会发生内存泄漏
	delete animal; 
}

int main(){
	test();
	return 0;
}

实例:电脑组装

电脑主要零件:CPU、显卡、内存条。将每个零件封装出抽象基类,并提供不同厂商的零件,创建电脑类让电脑工作的函数,并调用每个零件工作接口。测试组装三台电脑

#include <iostream>
using namespace std;

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){
		m_cpu=cpu;
		m_vc=vc;
		m_mem=mem;
	}

	void work(){
		m_cpu->calculate();
		m_vc->display();
		m_mem->storage();
	}

	~Computer(){
		delete m_cpu;
		delete m_vc;
		delete m_mem;
	}
private:
	CPU *m_cpu;
	VideoCard *m_vc;
	Memory *m_mem;
};

//Intel
class IntelCPU:public CPU{
public:
	virtual void calculate(){
		cout<<"Intel CPU is calculating!"<<endl;
	}
};

class IntelMemory:public Memory{
public:
	virtual void storage(){
		cout<<"Intel Memory is storagting!"<<endl;
	}
};

class IntelVideoCard:public VideoCard{
public:
	virtual void display(){
		cout<<"Intel VideoCard is displaying!"<<endl;
	}
};

//Lenovo
class LenovoCPU:public CPU{
public:
	virtual void calculate(){
		cout<<"Lenovo CPU is calculating!"<<endl;
	}
};

class LenovoMemory:public Memory{
public:
	virtual void storage(){
		cout<<"Lenovo Memory is storagting!"<<endl;
	}
};

class LenovoVideoCard:public VideoCard{
public:
	virtual void display(){
		cout<<"Lenovo VideoCard is displaying!"<<endl;
	}
};

void test(){
	CPU *intelcpu=new IntelCPU;
	VideoCard *intelvideocard=new IntelVideoCard;
    Memory *intelmemory=new IntelMemory;

	Computer *computer=new Computer(intelcpu,intelvideocard,intelmemory);
	computer->work();
	delete computer;
}

int main(){
	test();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值