C++学习之旅 -类和对象(重点)

news2024/11/18 15:52:03

文章目录

      • 封装
        • 封装的意义
        • 案例1
        • 案例2
      • 访问权限
      • C++中class和struct的区别
      • 成员属性私有化
      • 构造函数和析构函数
        • 构造函数
        • 析构函数
        • 构造函数的分类以及调用
          • 构造&调用
      • 拷贝构造函数调用时机
      • 深拷贝&浅拷贝
      • 初始化列表
      • 类对象作为类成员
      • 静态成员
      • C++对象模型&this指针
        • 成员变量和成员函数分开存储
        • this指针
      • 空指针访问成员函数
      • const修饰成员函数
        • 常函数
        • 常对象
      • 友元
        • 1. 全局函数做友元
        • 2. 类做友元 (中有说明`class::名称`的说明)
        • 3. 成员函数做友元
      • 运算符重载
        • 加号运算符重载
        • 左移运算符重载
        • 递增运算符重载
        • 赋值运算符重载
        • 关系运算符重载
        • 函数调用运算符重载
      • 继承(重点)
        • 继承方式
        • 继承中的对象模型
        • 构造和析构顺序(继承中)
        • 同名成员处理
        • 同名静态成员处理(继承中)
        • 多继承语法
        • 棱形继承
          • 问题
      • 多态
        • 案例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 c1;
	//给圆类型对象进行赋值
	c1.m_r = 10;
	cout << "圆的周长为:" << c1.calculateZC() << endl;
	return 0;
}

在这里插入图片描述

案例2

设计一个学生类,属性有名字学号

#include <iostream>

using namespace std;

class Student {
public:
	int sid;
	string name;

	void showStudent() {
		cout << "学号:" << sid <<",姓名:" << name << endl;
	}
};

int main() {
	Student s1;
	s1.sid = 1;
	s1.name = "张三";
	s1.showStudent();
	return 0;
}

访问权限

  1. public: 公共
  2. protected: -保护权限
  3. private: 私有
#include <iostream>

using namespace std;

//public: 类内和类外都可以使用
//protected:类内可以访问,类外不可以访问;子类可以访问父类中的保护权限
//private: 类内可以访问,类外不可以访问;子类不可以访问父类中的私有内容
class Person {
	//公共权限
public:
	string m_Name;
	//保护权限
protected:
	string m_Car;
//私有权限
private:
	int m_PassWord;
public:
	void func() {
		m_Name = "张三";
		m_Car = "比亚迪";
		m_PassWord = 123456;
	}
};


int main() {
	Person p1;
	p1.m_Name = "李四";
	//p1.m_Car = "奔驰";//保护访问权限内容,在类外访问不到
	//p1.m_PassWord = 11223344;//私有权限内容,在类外访问不到
	return 0;
}

保护访问权限内容,在类外访问不到
在这里插入图片描述

C++中class和struct的区别

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

  • struct默认权限是公共
  • class默认权限是私有

成员属性私有化

在实际开发中都会将我们的成员属性私有化

  • 优点1: 将所有成员设置为私有,可以自己控制读写权限
  • 有点2: 对于写权限,可以检测数据的有效性

示例

#include <iostream>

using namespace std;

class Person {
private:
	int sid;
	string name;
	int age;
public:
	int getSid() {
		return this->sid;
	}
	void setSid(int sid) {
		this->sid = sid;
	}
	string getName() {
		return this->name;
	}
	void setName(string name) {
		this->name = name;
	}
	int getAge() {
		return this->age;
	}
	void setAge(int age) {
		this->age = age;
	}
};


int main() {
	Person p1;
	p1.setSid(1);
	p1.setName("张三");
	p1.setAge(18);
	cout << p1.getSid() << p1.getName() << p1.getAge() << endl;
	return 0;
}

构造函数和析构函数

构造函数

构造函数(对象的初始化状态)
语法类名(){}

  1. 构造函数,没有返回值也不写void
  2. 函数名与类名相同
  3. 构造函数可以有参数,所以可以发生重载
  4. 程序调用对象的时候会自动调用构造函数,无须手动调用,而且只会调用一次

析构函数

在对象销毁前编译器也会调用它
语法~类名(){}

  1. 析构函数,没有返回值不写void
  2. 函数名与类名相同,在前面加上符号~
  3. 析构函数不可以有参数,因此不可发生重载
  4. 程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次
#include <iostream>

using namespace std;

class Person {
private:
	string name;
public:  //注意要写作用域,目的是让类外访问
	Person() { 
      //不写会自己给你一个空实现,写了就用你自己的
    cout << "无参构造函数Person调用了" << endl; }
	Person(string name) {
		this->name = name;
	}
};


int main() {
	Person p1;
	return 0;
}

在这里插入图片描述

注意点: 没有写构造函数的时候默认调用空实现的无参构造函数,一旦写了构造函数,之后的构造函数也不存在执行(原意:自己写了个有参构造函数自己没写无参,无参构造函数就是没有的)

#include <iostream>

using namespace std;

class Person {
private:
	string name;
public:  //注意要写作用域,目的是让类外访问
	//构造函数
	Person() { cout << "无参构造函数Person调用了" << endl; }
	Person(string name) {
		this->name = name;
	}
	//析构函数
	~Person() {
		cout << "析构函数~Person调用了" << endl;
	}
};


int main() {
	Person p1; //这是在栈上的数据,main执行完后释放这个对象
	return 0;
}

在这里插入图片描述

构造和析构都是必须要有的,如果自己不写,那么编译器会提供一个空实现的构造和析构

构造函数的分类以及调用

俩种分类方法:

  • 按参数
    • 无参构造
    • 有参构造
  • 按类型
    • 普通构造
    • 拷贝构造
构造&调用
class Person{
  //无参构造函数
  Person(){/*自行添加*/}
  //有参构造函数
  Person(int a){/*自行添加*/}
  //拷贝构造函数
  Person(const int &p){/*自行添加*/}
  //析构函数
  ~Person(){/*自行添加*/}
};
//调用
int main(){
  //1.括号法
  Person p1; //无参,不需要学Person p1();有问题(这样会认为是一个函数声明)
  Person p2(10); //有参
  Person p3(p2);//拷贝
  //2. 显示法
  Person p4;//无参
  Person p5 = Person(10);//有参
  Person p6 = Person(p5);//拷贝
  Person(10);//匿名对象,特点:当前行执行结束后,系统会立即回收掉匿名对象
  
  //3.隐式转换法
  Person p7 = 10; //相当于写了Person p7 = Person(10)
  Person p8 = p7; //拷贝构造
}

注意: 不要利用拷贝构造函数初始化匿名对象,Person(p3)因为编译器会认为是一个对象的声明

拷贝构造函数调用时机

  • 使用一个已经创建完毕的对象来初始化一个新对象
#include <iostream>

using namespace std;

class Person {
public:
	int age;
public:
	Person() {//无参构造
		cout << "无参构造函数调用" << endl;
	}
	Person(int age) { //有参构造
		this->age = age;
		cout << "有参构造函数调用" << endl;
	}
	Person(const Person& p) {//拷贝构造
		this->age = p.age;
		cout << "拷贝构造函数调用" << endl;
	}
	~Person() { //析构函数
		cout << "析构函数调用" << endl;
	}
};


int main() {
	Person p1(20); 
	Person p2(p1);
	cout << "p2的年龄:" << p2.age << endl;//查看是否拷贝出来
	return 0;
}

在这里插入图片描述

  • 值传递的方式给函数传值
#include <iostream>

using namespace std;

class Person {
public :
	int age;
public:
	Person() {//无参构造
		cout << "无参构造函数调用" << endl;
	}
	Person(int age) { //有参构造
		this->age = age;
		cout << "有参构造函数调用" << endl;
	}
	Person(const Person& p) {//拷贝构造
		this->age = p.age;
		cout << "拷贝构造函数调用" << endl;
	}
	~Person() { //析构函数
		cout << "析构函数调用" << endl;
	}
};

void doWork(Person p) {
  
}

int main() {
	Person p;
	doWork(p);
	return 0;
}

在这里插入图片描述

  • 以值方式返回局部对象
#include <iostream>

using namespace std;

class Person {
public :
	int age;
public:
	Person() {//无参构造
		cout << "无参构造函数调用" << endl;
	}
	Person(int age) { //有参构造
		this->age = age;
		cout << "有参构造函数调用" << endl;
	}
	Person(const Person& p) {//拷贝构造
		this->age = p.age;
		cout << "拷贝构造函数调用" << endl;
	}
	~Person() { //析构函数
		cout << "析构函数调用" << endl;
	}
};

Person doWork() {
	Person p1;
	cout << (int*)&p1 << endl;
	return p1;
}

int main() {
	Person p = doWork();
	cout << (int*)&p << endl;
	return 0;
}

在这里插入图片描述

其实应该是不一样的地址,这里编译器优化了

深拷贝&浅拷贝

  • 浅拷贝: 简单的赋值拷贝操作
  • 深拷贝: 在堆区重新申请空间,进行拷贝操作
#include <iostream>

using namespace std;

class Person {
public :
	int age;
	int* m_Height;
public:
	Person() {//无参构造
		cout << "无参构造函数调用" << endl;
	}
	Person(int age,int height) { //有参构造
		this->age = age;
		m_Height = new int(height); //指针接收堆区的数据
		cout << "有参构造函数调用" << endl;
	}
	Person(const Person& p) {//拷贝构造
		this->age = p.age;
		cout << "拷贝构造函数调用" << endl;
	}
	~Person() { //析构函数
		if (m_Height == NULL) {
			delete m_Height;
			m_Height = NULL;
		}
		cout << "析构函数调用" << endl;
	}
};


int main() {
	Person p1(18,160);
	cout << "p1的年龄:" << p1.age << "p1身高:" << *p1.m_Height << endl;
	Person p2(p1);
	cout << "p2的年龄:" << p2.age << "p2身高:" << *p2.m_Height << endl;
	return 0;
}

在这里插入图片描述

浅拷贝带来的问题,堆区资源重复释放

#include <iostream>

using namespace std;

class Person {
public :
	int age;
	int* m_Height;
public:
	Person() {//无参构造
		cout << "无参构造函数调用" << endl;
	}
	Person(int age,int height) { //有参构造
		this->age = age;
		m_Height = new int(height); //指针接收堆区的数据
		cout << "有参构造函数调用" << endl;
	}
	Person(const Person& p) {//拷贝构造
		this->age = p.age;
		m_Height = new int(*p.m_Height);
		cout << "拷贝构造函数调用" << endl;
	}
	~Person() { //析构函数
		if (m_Height == NULL) {
			delete m_Height;
			m_Height = NULL;
		}
		cout << "析构函数调用" << endl;
	}
};


int main() {
	Person p1(18,160);
	cout << "p1的年龄:" << p1.age << "p1身高:" << *p1.m_Height << endl;
	Person p2(p1);
	cout << "p2的年龄:" << p2.age << "p2身高:" << *p2.m_Height << endl;
	return 0;
}

初始化列表

作用: 用来初始化属性
语法: 构造函数(): 属性1(值1),属性2(值2),...{}

#include <iostream>

using namespace std;

class Person {
public:
	int m_A;
	int m_B;
	int m_C;
	Person():m_A(10),m_B(20),m_C(30) {}
	//不是写死的
	//Person(int a,int b,int c):m_A(a),m_B(b),m_C(c) {}
};


int main() {
	Person p;
	cout << p.m_A << endl;
}

类对象作为类成员

#include <iostream>

using namespace std;
class Phone {
public:
	string p_name;
	Phone(string p_name) :p_name(p_name) {}
};
class Person {
public:
	string name;
	Phone phone;
	//Phone phone = phoneName;
	Person(string name, string phoneName):name(name),phone(phoneName) {
	}
};


int main() {
	Person p("张三", "苹果XR");
	cout << p.name << "拿着" << p.phone.p_name << endl;
}

静态成员

  • 静态成员变量
    • 所有对象共享一份数据
    • 在编译阶段分配内存
    • 类内声明;类外初始化
      在这里插入图片描述
#include <iostream>

using namespace std;

class Person {
public:
	//所有对象都共享一份
	//类内声明
	static int m_A;
};
//类外初始化
int Person::m_A = 100;

int main() {
	Person p;
	cout << p.m_A << endl;
}

静态成员不属于某个对象上,所有对象都共享一份数据
静态成员有俩种访问方式

  1. 通过对象进行访问
Person p;
cout << p.m_A << endl;
  1. 通过类名进行访问
//类名::静态变量名
cout << Person::m_A << endl;

静态成员变量也是有访问权限的

class 类名{
  private:
    static int m_B;
}
//int Person::m_B = 200;//不可以访问,原因是private私有的
  • 静态成员函数
    • 所有对象共享同一函数
#include <iostream>

using namespace std;

class Person {
public:
	static void func() {
		cout << "static void func 调用" << endl;
	}
};


int main() {
	//俩种方法方式
	//1.通过对象访问
	Person p;
	p.func();
	//2.通过类名进行访问
	Person::func();
}
  • 静态成员函数只能访问静态成员变量
#include <iostream>

using namespace std;

class Person {
public:
	static int m_A;
	static void func() {
		m_A = 300;//静态成员函数可以访问静态成员变量
		cout << "static void func 调用" << endl;
	}
};

int Person::m_A = 100;

int main() {
	//俩种方法方式
	//1.通过对象访问
	Person p;
	p.func();
	//2.通过类名进行访问
	Person::func();
}

非静态成员变量是通过实例来创建对象,所以静态函数不清楚非静态成员变量的变化

C++对象模型&this指针

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

空对象占用内存空间为: 1。C++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置

#include <iostream>

using namespace std;

class Person {
public:
	int m_A; //非静态成员变量 (属于类的对象上)
	static int m_B; //静态成员变量(不属于类的对象上)

	void func() {} //非静态成员函数(不属于类的对象上)
	static void func2() {} //静态成员函数(不属于类的对象上)

};

int Person::m_B = 0;



int main() {
	Person p;
	cout << sizeof(p) << endl;
}

this指针

我们知道了成员变量和成员函数是分开存储的
每一个非静态成员只会诞生一份函数实例,也就是多个同类型的对象公用一块代码
那么问题是: 这一块代码是如何区分那个对象调用自己

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

class Person{
  private:
    int money;
  public:
    Person(int money){
      this->money = money;
    }
}

空指针访问成员函数

#include <iostream>

using namespace std;

class Person {
public:
	int m_Age;
	void showClassName() {
		cout << "this is Person class" << endl;
	}
	void showPersonAge() {
		cout << "age = " << m_Age << endl;
	}
};




int main() {
	Person* p = NULL;
	p->showClassName();  //正常输出
	p->showPersonAge();
}

在这里插入图片描述

原因是this是空指针。在m_Age这段中,其实默认添加了this->m_Age
我们可以

class Person {
public:
	int m_Age;
	void showClassName() {
		cout << "this is Person class" << endl;
	}
	void showPersonAge() {
		if (this == NULL) {
			return;
		}
		cout << "age = " << m_Age << endl;
	}
};

const修饰成员函数

常函数

  • 成员函数后加const后我们称这个函数为常函数
  • 常函数内不可以修改成员属性
  • 成员属性声明时加关键字mutable后,在常函数中依然可以修改
#include <iostream>

using namespace std;

class Person {
public:
	int m_A;
	mutable int m_B; //特殊变量,即使在常函数中,也可以修改这个值
	//this指针的本质 是指针常量 指针的指向是不可以修改的
	void showPerson() const{ //这个const就相当于时const Person *const this;
		//this->m_A = 100; //Person *const this;表达式是必须可修改的左值
		//this = NULL;//this指针不能修改指针指向的
		this->m_B = 100;
	}
};
int main() {
	Person p;
	//p->showPersonAge();
}

常函数(void showPerson const)本质是const 对象名 *const this。要修改就要加上mutable关键字

常对象

  • 声明对象加const称该对象为常对象
  • 常对象只能调用常函数
#include <iostream>

using namespace std;

class Person {
public:
	int m_A;
	mutable int m_B; //特殊变量,即使在常函数中,也可以修改这个值
	//this指针的本质 是指针常量 指针的指向是不可以修改的
	void showPerson() const{ //这个const就相当于时const Person *const this;
		//this->m_A = 100; //Person *const this;表达式是必须可修改的左值
		//this = NULL;//this指针不能修改指针指向的
		this->m_B = 100;
	}
};




int main() {
	const Person p;
	//p.m_A = 10;//不能修改
	p.m_B = 20;//可以修改
	//p->showPersonAge();
}
//常对象只能调用常函数

友元

声明一些特殊的函数或者特殊的类来作为另一个类的朋友访问到这类的私有成员
友元关键字friend

友元的三种实现

1. 全局函数做友元

//没有友元
#include <iostream>

using namespace std;

//建筑物类
class Building {
public:
	string m_SittingRoom;//客厅
private:
	string m_BedRoom;//卧室
public:
	Building() {
		m_SittingRoom = "客厅";
		m_BedRoom = "卧室";
	}
};
//全局函数
void goodFriend(Building *building) {
	cout << "好朋友的全局函数:" << building->m_SittingRoom << endl;  //公共属性
	//cout << "好朋友的自己的函数:" << building->m_BedRoom << endl; //报错
}




int main() {
	Building building;
	goodFriend(&building);
}
#include <iostream>

using namespace std;

//建筑物类
class Building {
	//goodFriend是这个Building的好朋友,可以访问Building中私有属性
	friend void goodFriend(Building* building);
public:
	string m_SittingRoom;//客厅
private:
	string m_BedRoom;//卧室
public:
	Building() {
		m_SittingRoom = "客厅";
		m_BedRoom = "卧室";
	}
};
//全局函数
void goodFriend(Building *building) {
	cout << "好朋友的全局函数:" << building->m_SittingRoom << endl;  //公共属性
	cout << "好朋友的自己的函数:" << building->m_BedRoom << endl;
}




int main() {
	Building building;
	goodFriend(&building);
}

在这里插入图片描述

只要写在类的最上面就可以了,不需要什么public
核心: friend 返回值数据类型 函数名(形参);

2. 类做友元 (中有说明class::名称的说明)

//class访问公共属性
#include <iostream>

using namespace std;

class Building;//类声明
class GoodFriend {
public:
	Building* building;
	void visit(); //参观函数访问Building中的属性
	GoodFriend();
};

//建筑物类
class Building {
public:
	string m_SittingRoom;//客厅
private:
	string m_BedRoom;//卧室
public:
	Building();
};

//类外去写成员函数
Building::Building() {
	m_SittingRoom = "客厅";
	m_BedRoom = "卧室";
}
GoodFriend::GoodFriend() {
  //创建建筑物对象
	building = new Building;
}
void GoodFriend::visit() {
	cout << "好朋友正在访问" << building->m_SittingRoom << endl;
}

int main() {
	Building building;
	GoodFriend goodFriend;
	goodFriend.visit();
	
}
#include <iostream>

using namespace std;

class Building;//类声明
class GoodFriend {
public:
	Building* building;
	void visit(); //参观函数访问Building中的属性
	GoodFriend();
};

//建筑物类
class Building {
	friend class GoodFriend; //GoodFriend是本类的好朋友,可以访问本类中私有成员(主要)
public:
	string m_SittingRoom;//客厅
private:
	string m_BedRoom;//卧室
public:
	Building();
};

//类外去写成员函数
Building::Building() {
	m_SittingRoom = "客厅";
	m_BedRoom = "卧室";
}
GoodFriend::GoodFriend() {
  //创建建筑物对象
	building = new Building;
}
void GoodFriend::visit() {
	cout << "好朋友正在访问" << building->m_SittingRoom << endl;
	cout << "好朋友正在访问" << building->m_BedRoom << endl;
}

int main() {
	Building building;
	GoodFriend goodFriend;
	goodFriend.visit();
	
}

核心: friend class 类名

这里提到的一个写法知识点

class 类名{
public:
  数据类型 成员变量名;
  类名();
  返回值数据类型 成员函数名();
}
//初始化(构造函数)
类名::类名(){/*初始化*/}
//初始化(成员函数)
返回值数据类型 类名::成员函数(/*形参*/){/*内容*/}

3. 成员函数做友元

class 类名1{
  void 成员函数();
}
class 类名2{
  friend 类名1::成员函数(); //类1下的成员函数作为本类的好朋友,可以访问私有成员
private:
   int test;
}

运算符重载

加号运算符重载

//通过成员函数重载+运算符
#include <iostream>

using namespace std;

class Person {
public:
	int m_A;
	int m_B;
	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 main() {
	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 << "p2.m_A = " << p3.m_A << endl;
	cout << "p2.m_B = " << p3.m_B << endl;
	
}

成员函数的本质:Person p3 = p1.operator+(p1);
全局函数重载本质调用: Person p3 = operator+(p1,p2)

左移运算符重载

ostream operator<<(ostream &cout,对象数据类型 &名字){
  cout << "自定义" <<"自定义";
}
ostream &operator<<(ostream &cout,对象数据类型 &名字);

递增运算符重载

void operator++(){
  //先做运算
  //再返回
  return ...;
}

赋值运算符重载

c++编译器至少给一个类添加4个函数

  • 默认构造函数(无参,函数体为空)
  • 默认析构函数(无参,函数体为空)
  • 默认拷贝构造函数,对属性进行值拷贝
  • 赋值运算符operator=对属性进行值拷贝
void operator=(){
  if(m_Age != NULL){
    delete m_Age;
    m_Age = NULL;
  }
}

关系运算符重载

void operator==(){}
void operator!=(){}

函数调用运算符重载

//仿函数没有固定的写法
void operator()(string test){
  cout << test <<endl;
}
int operator()(string test,int age){}

继承(重点)

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

继承方式

  1. 公共继承
class A{
  //父类
public:
  int m_A;
protected:
  int m_B;
private:
  int m_C;
};
class B:public A{
  void func(){
    m_A = 10;//父类中的公共权限成员到子类依旧是公共权限
    m_B = 10;//父类中的保护权限成员到子类依旧是保护权限
    //m_C = 10;//父类中的隐私权限成员到子类中是拿不到私有权限
  }
};
  1. 保护继承
class A{
  //父类
public:
  int m_A;
protected:
  int m_B;
private:
  int m_C;
};
class B:protected A{
public:
  void func(){
    m_A = 10;//父类中的公共权限成员到子类中变为了保护权限
    m_B = 10;//父类中的保护权限成员到子类依旧是保护权限
    //m_C = 10;//父类中的隐私权限成员到子类中是拿不到私有权限
  }
};
  1. 私有继承
class A{
  //父类
public:
  int m_A;
protected:
  int m_B;
private:
  int m_C;
};
class B:private A{
public:
  void func(){
    m_A = 10;//父类中的公共权限成员到子类中变为了私有成员
    m_B = 10;//父类中的保护权限成员到子类中变为了私有成员
    //m_C = 10;//父类中的隐私权限成员到子类中是拿不到私有权限
  }
};

继承中的对象模型

class A{
  //父类
public:
  int m_A;
protected:
  int m_B;
private:
  int m_C;
};
class B:public A{
public:
  int m_D;
};
void test(){
  cout << "sizeof B=" << sizeof(B) << endl;
}

在这里插入图片描述

父类中非静态是私有还是公共权限的数据我们子类都会被继承下来保留一份(自己中的属性也包括)

验证:打开图中高亮的文件

在这里插入图片描述

注意: 文件路径不能有&

#cl /d1 报告单个类的布局(reportSingleClassLayout类名) 类在的.cpp文件
cl /d1 reportSingleClassLayoutB main.cpp

在这里插入图片描述

构造和析构顺序(继承中)

#include <iostream>

using namespace std;

class A {
	//父类
public:
	int m_A;
	A() {
		cout << "父类A构造函数" << endl;
	}
	~A() {
		cout << "父类A析构函数" << endl;
	}
protected:
	int m_B;
private:
	int m_C;
};
class B :public A {
public:
	int m_D;
	B() {
		cout << "子类B构造函数" << endl;
	}
	~B() {
		cout << "子类B析构函数" << endl;
	}
};
void test() {
	cout << "sizeof B=" << sizeof(B) << endl;
}

int main() {
	B b;
	return 0;
}

在这里插入图片描述

同名成员处理

问题: 父类和子类出现了同样名称的成员

  • 访问子类同名成员,直接访问即可
#include <iostream>

using namespace std;

class A {
	//父类
public:
	int m_A;
	A() {
		m_A = 100;
		cout << "父类A构造函数" << endl;
	}
};
class B :public A {
public:
	int m_A;
	B() {
		m_A = 200;
		cout << "子类B构造函数" << endl;
	}
	void func(){}
};
void test() {
	B b;
	cout << "B类下的m_A=" << b.m_A << endl;
}

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

在这里插入图片描述

  • 访问父类同名成员,需要加作用域
//子类实例对象名.父类名::同名成员变量/同名成员函数
void test() {
	B b;
	cout << "A类下的m_A=" << b.A::m_A << endl;
}

在这里插入图片描述

如果想访问到父类中被隐藏的成员函数,需要加作用域

子类实例名.类名::函数名(/*形参*/);

同名静态成员处理(继承中)

#include <iostream>

using namespace std;

class Base {
public:
	static int m_A;
	static void func() {
		cout << "Base func" << endl;
	}
};
int Base::m_A = 100;

class Son:public Base{
public:
	static int m_A;
	static void func() {
		cout << "Son func" << endl;
	}
};
int Son::m_A = 200;

int main() {
	//同名静态成员属性
	Son s;
	//1.通过对象的方式访问
	cout << "通过对象的方式访问" << endl;
	cout << "子类s的静态变量=" << s.m_A << endl;
	cout << "父类Base下的静态变量=" << s.Base::m_A << endl;
	//2.通过类名的方式访问
	cout << "通过类名的方式访问" << endl;
	cout << "子类s的静态变量=" << Son::m_A << endl;
	cout << "父类Base下的静态变量=" << Son::Base::m_A << endl;

	//同名静态成员函数
	cout << "通过对象的方式访问" << endl;
	s.func();
	s.Base::func();
	cout << "通过类名的方式访问" << endl;
	Son::func();
	Son::Base::func();
	return 0;
}

多继承语法

在C++中可以继承多个类
语法: class 子类:继承方式 父类1,继承方式 父类2...

因为可能存在俩个父类中存在成员重名问题,所以实际开发中不建议使用多继承

#include <iostream>

using namespace std;

class Base1 {
public:
	int m_A;
	int m_D;
	Base1() {
		m_A = 100;
	}
};
class Base2 {
public:
	int m_B;
	int m_D;
	Base2() {
		m_B = 200;
	}
};

class Son :public Base1, public Base2 {
public:
	int m_C;
};

int main() {
	Son s;
	cout << "sizeof Son=" << sizeof(s) << endl;
	cout << "Base1 m_D=" << s.Base1::m_D << endl;
	cout << "Base2 m_D=" << s.Base2::m_D << endl;
	return 0;
}

棱形继承

俩个派生类继承一个基类(人话:俩个子类一个父类);之后一个类继承了这俩个派生类

问题
  1. B类继承了A类,C类也继承了A类,D类继承了B和C类;当D类使用数据时,就会产生二义性
  2. D类继承了B和C类,其实我们都清楚我们只需要一份就可以了
#include <iostream>

using namespace std;

class Anime {
public:
	int m_Age;
};
class Sheep :public Anime {
};
class Tuo :public Anime {
};
class SheepTuo :public Sheep, public Tuo {};

int main() {
	SheepTuo st;
	st.Sheep::m_Age = 18;
	st.Tuo::m_Age = 20;
	//当出现菱形继承,俩个父类拥有相同的数据,需要加以作用域区分
	cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;
	cout << "st.Tuo::m_Age = " << st.Tuo::m_Age << endl;
	return 0;
}

解决: 继承之前加上关键字virtual变成虚继承

#include <iostream>

using namespace std;

class Anime {
public:
	int m_Age;
};
class Sheep :virtual public Anime {
};
class Tuo :virtual public Anime {
};
class SheepTuo :public Sheep, public Tuo {};

int main() {
	SheepTuo st;
	st.Sheep::m_Age = 18;
	st.Tuo::m_Age = 20;
	//当出现菱形继承,俩个父类拥有相同的数据,需要加以作用域区分
	cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;
	cout << "st.Tuo::m_Age = " << st.Tuo::m_Age << endl;
	return 0;
}

多态

  • 静态多态
    • 函数重载
    • 运算符重载
    • 服用函数名
  • 动态多态
    • 派生类
    • 虚函数实现运行时多态
#include <iostream>

using namespace std;

class Anime {
public:
	void speak() {
		cout << "动物在说话" << endl;
	}
};

class Cat :public Anime {
public:
	void speak() {
		cout << "猫在说话" << endl;
	}
};

//执行说话函数
void doSpeak(Anime& anime) { //父类的引用指向子类对象Anime &anime = cat
	anime.speak();
}


int main() {
	Cat cat;
	doSpeak(cat);
	return 0;
}

输出的结果是动物在说话,这个时候就要使用虚函数

class Anime {
public:
	//虚函数
	virtual void speak() {
		cout << "动物在说话" << endl;
	}
};
//动态多态的满足条件
//1.有继承关系
//2.子类要重写父类的虚函数
  • 动态多态使用

    • 父类的指针或者引用 执行子类的对象
  • 多态的优点

    • 代码组织结构清晰
    • 可读性强
    • 利于前期和后期的扩展以及维护

案例1: 开闭原则

class AbstractCalculator{
  public:
    virtual int getResult(){
      return 0;
    }
    int num1;
    int num2;
};
//加法计算器类
class AddCalulator:public AbstractCalculator{
public:
  int getResult(){
    return num1+num2;
  }
};
//乘法计算器类
class MulCalulator:public AbstractCalculator{
public:
  int getResult(){
    return num1 * num2;
  }
};

int main(){
  AbstractCalculator *add = new AddCalulator();
  add->num1 = 10;
  add->num2 = 20;
  
}

纯虚函数&抽象类

以案例1来看AbstractCalculator中的虚函数getResultreturn 0一直都没使用过,所以我们可以改为纯虚函数
语法: virtual 返回值类型 函数名(参数列表) = 0;

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

抽象类特点
  • 无法实例化对象
  • 子类必须重写抽象类中的纯虚函数,否则也属于是抽象类
#include <iostream>

using namespace std;

//Base是抽象类
class Base {
public:
	//只要有一个纯虚函数,这个类就是抽象类
	//抽象类无法实例化对象
	virtual void func() = 0;
};

class Son :public Base {
public:
	void func() {
		cout << "Son func" << endl;
	}
};



int main() {
	Base* base = new Son;
	base->func();
	return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/633941.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Mybatis Generator源码修改

文章目录 报java.net.MalformedURLException错误解决问题原因&#xff1a;编译的时候没有把下面的dtd文件打进去解决方法 XML文件判空优化-增加空字符串修改InsertSelectiveElementGenerator修改UpdateByPrimaryKeySelectiveElementGenerator XML文件判空优化-最佳解决方案 报j…

一文详解!接口测试 API 自动化测试框架

目录 前言 框架定位 框架架构图 框架介绍 技术栈 Case 展示 执行展示 框架优势&#xff1a; 前言 接口测试 API 自动化测试框架可以提高测试效率和自动化程度&#xff0c;通常包括 HTTP 客户端、测试数据管理、测试报告生成、测试用例管理和调度等功能。下面是一个常用…

【论文】attention is all you need

重点在第三节 attention is all you need摘要1. 绪论2. 背景3. 模型架构3.1 编码器和解码器堆叠 3.2 注意力3.2.1 缩放点积注意力&#xff08;Scaled Dot-Product Attention&#xff09;3.2.2 多头注意力机制3.2.3 模型中注意力的应用 3.3 职位感知前馈网络&#xff08;Positio…

单链表OJ题:LeetCode--142.环形链表Ⅱ(判断第一次入环的节点)

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;本期来给大家解读一下LeetCode中第142道单链表OJ题&#xff0c;如果看完之后对你有一定的启发&#xff0c;那么请留下你的三连&#xff0c;祝大家心想事成&#xff01; 数据结构与算法专栏&#xff1a;数据结构与算法 个 人…

【网页设计】第 1 课 - 了解网页设计

欢迎来到博主 Apeiron 的博客&#xff0c;祝您旅程愉快 &#xff01; 时止则止&#xff0c;时行则行。动静不失其时&#xff0c;其道光明。 目录 1、缘起 2、了解网页设计 2.1、网页设计 2.2、网站结构 2.3、网站分类 2.4、页面鉴赏 3、总结 1、缘起 前段时间学习完了前…

OpenCV 图像与视频的基础操作

文章目录 引言创建和显示窗口如何通过 OpenCV 加载图片问题加载图片存在的问题如何通过 openCV 保存图片&#xff08;保存图片&#xff09;如何通过 OpenCV 保存图片如何利用 OpenCV 从摄像头采集视频&#xff08;读取视频文件&#xff09;如何从多媒体文件中读取视频帧&#x…

52、基于51单片机脉搏心率报警LCD 1602显示系统设计(程序+原理图+PCB图+Proteus仿真+参考论文+开题报告+任务书+外文文献+元器件清单等)

摘 要 脉搏心率测量仪在我们的日常生活中已经得到了非常广泛的应用,通过观测脉搏信号&#xff0c;可以对人体的健康进行检查&#xff0c;通常被用于保健中心和医院。为了提高脉搏测量仪的简便性和精确度&#xff0c;本课题设计了一种基于51单片机的脉搏测量仪。系统以51单片机…

C++设计模式 - 创建型模式之工厂模式

文章目录 C设计模式 - 创建型模式之工厂模式接口和针对接口编程 1. 简单工厂模式适用场合UML代码示例 2. 工厂方法模式适用场合UML代码示例 3. 抽象工厂模式适用场合UML代码示例 总结 C设计模式 - 创建型模式之工厂模式 工厂模式属于创建型模式&#xff0c;大致可以分为三类&a…

【Linux】信号量(基于环形队列的生产消费模型)

文章目录 POSIX信号量一、什么是信号量二、信号量接口1.初始化信号量2.销毁信号量3.申请信号量&#xff08;等待信号量&#xff09;4.释放信号量&#xff08;发布信号量&#xff09; 基于环形队列的生产消费模型一、结构介绍二、理论讲解三、代码实现 总结 POSIX信号量 POSIX信…

C语言之操作符详解

本章重点 1. 各种操作符的介绍 2. 表达式求值 给大家提到一些操作符&#xff0c;下面我们来给大家详细介绍 首先看算术操作符&#xff0c;其他几个都没什么可讲的我们来重点看一下/&#xff08;除法&#xff09; 整数除法&#xff08;除号的两端都是整数&#xff09; 浮点…

【漏洞复现】Apache RocketMQ 命令注入漏洞(CVE-2023-33246)

文章目录 前言声明一、漏洞描述二、漏洞危害三、影响版本四、环境搭建五、漏洞复现六、修复建议 前言 RocketMQ 是阿里巴巴在2012年开发的分布式消息中间件&#xff0c;专为万亿级超大规模的消息处理而设计&#xff0c;具有高吞吐量、低延迟、海量堆积、顺序收发等特点。同时它…

Shell脚本查询进程并kill进程(集群版)

记录&#xff1a;454 场景&#xff1a;使用Shell脚本查询进程并kill进程。使用Shell脚本远程执行脚本查询进程并kill进程。 版本&#xff1a;CentOS Linux release 7.9.2009。 1.使用Shell脚本查询进程并kill进程 1.1脚本 脚本名称&#xff1a;zk-kill_pid.sh 脚本内容&a…

从ROS1到ROS2无人机编程实战指南

亲爱的读者们&#xff0c;我今天非常荣幸地向大家推荐一本本人的全新力作——《从ROS1到ROS2无人机编程实战指南》。这本书站在初学者的角度&#xff0c;从入门到进阶&#xff0c;再到实战&#xff0c;循序渐进&#xff0c;是学习ROS1和ROS2的最佳选择。如今已在全国范围内上市…

Java spring boot 全解Camunda 7,从 0 到 1 构建工作流平台——第一节:各个开源框架对比

目录 1. Camunda 介绍2. Camunda 选型说明2.1 osworkflow2.2 jbpm2.3 ActivitiActiviti介绍各个版本的优缺点 2.4 flowable2.5 camundacamunda介绍主流版本介绍 2.6 n8n.io2.7 为什么选 camunda ? camunda7.x 还是 camunda 8.x &#xff1f;为什么选 camunda&#xff1f;camun…

碳排放预测模型 | Python实现基于机器学习回归分析的碳排放预测模型——数据可视化和探索

文章目录 效果一览文章概述研究内容环境准备源码设计学习总结参考资料效果一览 文章概述 碳排放预测模型 | Python实现基于机器回归分析的碳排放预测模型——数据可视化和探索 目标是测试所选特征对分析的重要性,检测异常值的存在并准备数据以供进一步分析。 </

Netty实战(十二)

预置的ChannelHandler和编解码器&#xff08;二&#xff09;HTTPS、WebSocket的添加使用和大型数据写入以及几种常见的序列化 一、基于Netty的HTTPS程序1.2 使用HTTPS2.3 WebSocket 二、空闲连接和超时三、 解码基于分隔符的协议和基于长度的协议3.1 基于分割符的协议3.2 基于长…

策 略 模 式「指 鼠 为 鸭」

前言 大家好&#xff0c;我是 god23bin&#xff0c;今天我们来介绍下设计模式中的一个重要的设计模式——策略模式。 当涉及到某个行为或算法有多个变体时&#xff0c;策略模式是一种常见的设计模式。它允许在运行时选择使用不同的策略&#xff0c;而无需修改现有代码。 现在…

OneFormer:规则通用图像分割的一个Transformer

文章目录 OneFormer: One Transformer to Rule Universal Image Segmentation摘要本文方法实验结果 OneFormer: One Transformer to Rule Universal Image Segmentation 摘要 通用图像分割并不是一个新概念。过去统一图像分割的尝试包括场景解析、全景分割&#xff0c;以及最…

【工具】SecureCR-8.5下载、安装激活和使用教程(包含常用设置)

目录 一、安装包下载 二、安装教程 三、激活操作 四、使用教程 五、常用设置 一、安装包下载 SecureCRT8.5安装包&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1yy677I99ln_3evoHc5dMXg 提取码&#xff1a;9tyj 二、安装教程 1. 解压、双击进行安装 2. 安装进…

【LeetCode】136. 只出现一次的数 python

目录 题目描述 第一次刷题 第二次刷题 异或运算的规则 题目描述 给你一个 非空 整数数组 nums &#xff0c;除了某个元素只出现一次以外&#xff0c;其余每个元素均出现两次。找出那个只出现了一次的元素。 你必须设计并实现线性时间复杂度的算法来解决此问题&#xff0c;…