9、类和对象

9.1 封装

9.1.1 封装的例子

class Student {
public:
	string name;
	int age;
public:
	void setName(string name_) {
		name = name_;
	}
};
int main() {
	
	Student s1;
	s1.setName("zhangsan");
	return 0;
}

类中的行为都叫做成员,例如成员属性,成员变量,成员方法

9.1.2 访问权限

名称权限范围
public类内、类外都可以访问
protected类内可以访问,类外不可以访问,子类可以访问
private类内可以访问,类外不可以访问,子类不可以访问

strcut 和class 区别

  • 默认权限不同,struct 默认权限为public,class默认权限为private

9.2 对象的初始化和清理

9.2.1 构造函数与析构函数

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

  • 没有返回值也不需要用void修饰
  • 函数名与类名相同
  • 可以有参数,可以重载

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

  • 没有返回值也不需要用void修饰
  • 函数名与类名相同
  • 可以有参数,不可以重载
  • 在对象销毁前会自动调用析构函数

9.2.2 构造函数的分类及调用

分类:

  • 按照参数:有参构造函数和无参构造函数
  • 按照类型:拷贝构造函数和普通构造函数
// 拷贝构造函数
Person(const Person &p){
	age=p.age;
	name=p.name;
}

调用:

  • 括号法:
    • 无参构造函数调用:Person p1;
    • 有参构造函数调用:Person p2(10);
    • 拷贝构造函数调用:Persiong p2(p1);
  • 显示法:
    • Person p1;
    • Person p2 = Person(10);
    • Person p3 = Person(p2);
  • 隐式法
    • Person p1=10;// 相当远Person p1=Person(10);
    • Persion p5=p4;

c++ 默认提供无参构造函数,无参析构函数以及拷贝函数

9.2.3 初始化列表

class Student {
public:
	string name;
	int age;
	Person():name("zhangsan"),age(10)
	{

	}
	Person(string a,int b):name(a),age(b)
	{

	}
public:
	void setName(string name_) {
		name = name_;
	}
};

9.2.4 类对象作为类成员

  • 当类对象作为类成员的时候,类成员的构造方法先执行,即内对象先构造,在构造外对象
  • 析构方法与构造方法相反

9.2.5 静态成员

使用static修饰的成员变量和成员函数称之为静态成员变量、静态成员函数

  • 静态成员变量:

    • 所有对象共享同一份数据
    • 在编译阶段分配内存
    • 类内声明,类外初始化
  • 静态成员函数:

    • 所有对象共享同一个函数
    • 静态成员函数只能访问静态成员变量

静态成员变量

  1. 初始化
class Student {
	
public:
	string name;
	int age;
	static string school;
public:
	void setName(string name_) {
		name = name_;
	}
};
int deliverValue(Student s);
int deliverPlace(Student* s);
string Student::school = "黑龙江";// 静态成员变量初始化
int main() {
	
	Student s1;
	s1.setName("zhangsan");
	s1.school = "哈尔滨";// 或者直接通过某个对象初始化也可以
	return 0;
}
  1. 访问方式
  • 通过对象访问 s1.school
  • 通过类名访问 Student::school

静态成员函数

  1. 访问方式
  • 通过对象访问 s1.func();
  • 通过类名访问 Student::func();

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

9.3.1 this指针

成员变量和成员函数式分开存储的,静态成员变量,静态成员函数,以及成员函数都是共享的,只有非静态成员变量是对属于对象的。
那么对于非静态成员函数是怎么区分是哪个对象调用自己的?C++通过提供特殊的对象指针this指针解决上述问题。this指针指向被调用的成员函数所属的对象。

  • this 指针隐含在每个非静态成员函数内部,不需要定义,直接可以使用

this指针的作用

  • 当形参与成员变量同名时,可以用this指针来区分
  • 在类的非静态成员函数中返回对象本身,可以使用 return *this
class Student {
	
public:
	string name;
	int age;
	static string school;
public:
	void setName(string name) {
		this->name = name;
	}
};

在VS2022中键盘输入this.age= 会自动修正为this->age=,对于VS2022 指针的"."相当于“->”的快捷键。

9.3.2 const修饰成员函数

  • 常函数
    • 成员函数添加const后我们称这个函数为常函数
    • 常函数内不可以修改成员属性
    • 成员属性声明时加关键字multable后,在常函数中依然可以修改
  • 常对象
    • 声明对象前加const称该对象为常对象
    • 常对象只能调用常函数

常函数如下:

void show(string name) const {
	cout<<this->name;
}

const 本质上是修饰的this指针

9.3 友元

  • 全局函数做友元
  • 类做友元
  • 成员函数做友元

9.3.1 全局函数做友元

在类中加入friend修饰的函数声明,就可以将该函数定义为当前类的友元

class Student {
	friend void visit(Student* s);
public:
	string name;
	int age;
	static string school;
private:
	string score;
public:
	void setName(string name) {
		this->name = name;
	}
	void show(string name) const {
		cout<<this->name;
	}
};
void visit(Student* s) {
	cout << s->score;
}

9.3.2 类做友元

在类中加入friend修饰的类声明,就可以将该类定义为当前类的友元

9.3.2 类做友元

在类中加入friend修饰的类声明,就可以将该类定义为当前类的友元

friend Person p;

9.3.2 成员函数做友元

在类中加入friend修饰的成员函数声明,就可以将成员函数定义为当前类的友元

	friend void GoodGay::visit();

9.4 运算符重载

9.4.1 对于加号进行重载

  • 成员函数重载
class Person(){
	Person operator+(Person &p){
	
	}
}
// 使用的时候
Person p3=p1.operator+(p2);
// 或者
Person p3=p1+p2;
  • 全局函数重载
Person operator+(Person &p1, Person &p2){
	
}
// 使用的时候
operator+(p1,p2)
// 或
Person p3=p1+p2;

9.4.2 对左移运算符进行重载

在输出的时候cout<< 无法对自定义类进行输出,所以可以对<<进行重载
通常情况下不会使用成员函数重载<<。因为无法简化为cout<<p。

  • 使用全局函数重载<<
ostream& operator<<(ostream &cout,Person &p){
	cout<<p.name;
	rerturn cout;
}

9.4.3 递增运算符重载

  • 前置递增重载(成员函数重载)
MyInteger& operator++(){
 m_num++;
 return *this;
}

这里必须要返回引用,当不返回引用的时候,两次连续的递增操作会出现问题

MyInteger a(0);// a.m_num=0;
cout<<++(++a);// 输出2  cout 重载过的函数,输出的是a.m_num的值
cout<<a;//输出1 

这是因为如果不返回引用,那么会第一次++a会返回一个a的副本,即匿名对象,然后第二次递增是对于这个副本进行递增,因此递增的结果是正确的,但是真正的a.m_num只递增了一次

  • 后置递增重载
MyInteger& operator++(int){
MyInteger tmp=*this;
 m_num++;
 return tmp;
}

这里需要注意,后置递增两次连加本身就是无法加2的,因为后置递增是先返回原值,在执行加法,即普通的int a=0;(a++)++也是等于1,在vs2022会直接报错,显示“表达式必须是可修改的左值”

9.4.4 赋值运算符重载

Person& operator=(Person &p){
	// 要先对原本对象存在堆区里的数据进行释放,否则就会存在一直不释放的数据
	if(m_Age!=NUll){
		delete m_Age;
		m_Age=NULL;
	}
	m_Age= new int*p.m_Age);
	return *this;
}

9.4.5 关系运算符重载

Person& operator==(Person &p){
	if(m_Age==p.m_Age){
		return true;
	}
	return false;
}

9.4.6 函数调用运算符重载(仿函数)

class MyAdd{
public:
	void operator()(String s){
		cout<<s<<endl;
	}
}


void test(){
	MyAdd myadd;
	int ret = myadd(100,100);
	cout<<ret<<endl;
}

9.5 继承

9.5.1 继承

class 子类:继承方式 父类
(子类也叫基类,父类也叫基类)

9.5.2 继承方式

  • public:权限不变,即父类中的私有还是私有不可访问,受保护的还是受保护的,可以访问
  • protected:父类中的公共权限和保护权限在子类中全部变为保护权限,私有不可访问
  • private:公共权限和保护权限都变为私有权限,私有不可访问

9.5.3 继承中的对象

在父类中的任何非静态属性都会在子类中存在一份,只不过是父类的私有属性,无法访问

9.5.4 继承中的构造和析构顺序

base构造
son构造
son析构
base析构

9.5.5 继承中同名成员访问

class Base{
	publicBase(){
		m_A=100;
	}
}

class Son :public Base{
	publicSon(){
		m_A=200;
	}
}

int main(){
	Son s;
	cout<<"son"<<s.m_A<<endl;
	cout<<"Base"<<s.Base::m_A<<endl;
}

成员函数的调用方法和成员属性的调用方法类似

注意:当子类和父类中出现同名成员函数时,父类中的所有同名成员函数被隐藏,即子类中出现change()函数,那么父类中的change(),change(int a)等等都会被隐藏,一定要调用的时候加父类作用域。

9.5.6 多继承

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

在实际开发中通常不建议使用多继承,父类属性出现重名情况是每次调用都需要明确作用域

9.5.6 菱形继承

在这里插入图片描述

  • 两个父类存在相同的属性,可以通过加作用域区分 s.Sheep:m_Age; s.Tuo:m_Age;
  • 存在重复属性,造成了数据的冗余,浪费内存。利用虚继承来解决:class Sheep:virtual public Animal;class Tuo:virtual public Animal; 这种情况下,s.Sheep:m_Age; s.Tuo:m_Age;是同一份数据

9.6 多态

9.6.1 基本概念

  • 静态多态:函数重载、运算符重载以及复用函数名都属于静态重载
  • 动态多态:派生类和虚函数实现运行时多态

静态多态和动态多态的根本区别:

  • 静态多态的函数地址早绑定,编译阶段确定函数地址
  • 动态多态的函数地址晚绑定,运行阶段确定函数地址
class Animal{
public:
	virtual void speack(){// 如果不使用虚函数,那么test函数执行的时候无论输入的是小猫,小狗都是“动物在说话”
		cout<<"动物在说话";
	}
}
class Catpublic Animal{
public:
	void speack(){
		cout<<"猫在说话";
	}
}
void test(Animal &animal){
	animal.speak();
}

动态多态满足条件:

  • 有继承关系
  • 子类重写父类的虚函数

重写:函数名与参数完全相同

在这里插入图片描述

9.6.2 纯虚函数和抽象类

  • 语法:virtual 返回值类型 函数名(参数列表)= 0;
  • 当类中存在纯虚函数那么这个类被称为抽象类
  • 抽象类特点:
    • 抽象类的子类必须重写纯虚函数。
    • 抽象类无法实例化对象

9.6.3 虚析构和纯虚析构

问题:多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码,会造成内存溢出
解决办法:将父类中的析构函数改成虚析构或者纯虚析构

父类的纯虚析构代码要在类外实现


Animal::~Animal(){

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值