当前位置: 首页 > article >正文

C++之OOP

感谢哔哩哔哩UP“开发者LaoJ”,以下是学习记录~

1、类是属性、行为的封装,将数据的表示和数据的处理集中到一起

2、类内成员的访问权限为:public、protected、private

  • public,可以被外界访问
  • protected,不可被外界访问,可以被派生类访问
  • private,不可以被外界访问,也不可以被派生类访问

3、类的相关成员函数的定义

/*类的相关成员函数的定义*/
//1、成员函数内联定义

//2、成员函数在类内声明,在类外定义

//3、构造函数,允许重载
//3.1使用默认构造函数(=default)
myclass() = default;
//3.2、带参数
myclass(int val) : data(val) {……}
//3.3、动态内存分配(会调用默认构造函数)
myclass *objptr = new myclass();
……
delete objptr;
//3.4、复制构造函数
myclass obj1;
myclass obj2 = obj1;

//4、析构函数
//没有参数,没有返回值,不可以重载
//自定义的析构函数,一般用来释放动态内存、关闭文件、释放资源等
//当对象超出作用域时,析构函数会被调用

//5、成员初始化列表
myclass(string str, int val) : name(str), data(val) {……}

浅拷贝:

  • 浅拷贝是默认的拷贝行为,它只是简单地复制对象的成员变量的值。如果对象中有指针成员,浅拷贝只会复制指针的值,而不会复制指针指向的内容
  • 浅拷贝会导致多个对象的指针成员指向同一块内存。当其中一个对象释放了这块内存后,其他对象的指针就会变成悬空指针

深拷贝:

  • 深拷贝会复制对象的所有成员变量,包括指针指向的内容。对于动态分配的资源,深拷贝会为新对象分配新的内存,并将原对象的内容复制到新内存中
  • 需要手动实现拷贝构造函数和赋值运算符重载,以确保深拷贝的正确性

4、this指针

this指针指向类的实例化对象,是一个顶层const,即不可以修改指向,但是可以借助this指针来修改类内的成员

5、静态数据成员

  • 静态成员是类级别的,不是对象级别的,被类的所有对象共享
  • 静态成员变量在类内声明,类外定义(必须在类外定义),在编译时,由编译器分配内存
  • 静态成员函数只能访问静态变量
  • 可以通过类名和类的对象访问静态成员

6、虚函数指针和虚函数表

当一个类中有一个或多个虚函数时,编译时会为其生成一张虚函数表(vftable),在类的对象中,有一个虚函数指针(vfptr),指向该虚函数表

如果父类中有虚函数,无论子类是否有虚函数,均会有一张虚函数表

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

class Device {
public:
	virtual void fetchData() {
		cout << "device fetchData" << endl;
	}
	virtual void performAction() {
		cout << "device performAction" << endl;
	}
	virtual ~Device() = default;
};

class LightController : public Device {
public:
	void fetchData() override {
		cout << "LightController fetchData" << endl;
	}
	/*void performAction() {
	*	cout << "LightController performAction" << endl;
	}*/
};

int main(void)
{
	Device objD, objD2;
	LightController objL;
	objL.fetchData();
	objL.performAction();
	return 0;
}

根据上图可知:

  • 父类对象和子类对象的虚函数指针的指向不同。其中,因为子类的performAction函数没有实现,所以在虚函数表中,子类的performAction函数的地址与父类中同名函数的地址一样
  • 定义了两个基类的对象,对象内均有一个vfptr指针,指向的地址相同,说明一个基类的多个对象共享一份虚函数表

一、封装

封装简化了代码的复杂性,隐藏类的内部的实现细节,只是向外提供接口以供调用,起到了保护其内部成员的作用

二、继承

继承允许创建一个新的类,从一个或多个现有的类中继承属性和行为。新类称为派生类,被继承类称为基类

一般而言,共性的东西会被设计在基类中,个性化的东西被设计在派生类中

在构造派生类对象时,会先调用基类的构造函数,然后调用派生类的构造函数;在派生类对象作用域结束时,先调用派生类的析构函数,然后调用基类的析构函数

2.1、三种继承形式

继承的三种形式:public、protected和private

  • public:

类外:对于继承的成员,保持与父类的对外可见性一致

类内:可以处理父类的public、protected成员,不可以处理父类的private成员

  • protected:

类外:对于继承的成员,除了private成员外,其它成员对外均为protected

类内:可以处理父类的public、protected成员,不可以处理父类的private成员

  • private:

类外:对于继承的成员,对外均为private

类内:可以处理父类的public、protected成员,不可以处理父类的private成员

  • 总结:

派生类内是否可以访问父类的成员,需要看父类成员的访问权限。类外是否可访问父类成员,需要看继承形式

不管什么继承方式,也不管父类成员本身的访问权限修饰符是什么,子类对象已完全继承父类的所有属性和行为,只是对外不一定可见,在类内不一定可以处理

2.2、同名问题

对于普通成员,只能通过类的对象进行访问

对于静态成员,可以通过类的对象和类访问,静态成员属于类,类的所有对象共享静态变量。故而,静态变量必须在类内声明类外定义

2.2.1、子类与父类普通成员名称相同

#include<iostream>
using namespace std;

class Base
{
public:
	int a = 0;
	void myfun() {
		cout << "I am parent class's function" << endl;
	}
};

class A : public Base
{
public:
	int a = 1;
	void myfun() {
		cout << "I am child class's function" << endl;
	}
};

int main(void)
{
	/*使用对象访问静态成员*/
	A obj;
	cout << obj.a << endl;
	cout << obj.Base::a << endl;
	obj.myfun();
	obj.Base::myfun();
}

2.2.2、子类与父类静态成员名称相同

#include<iostream>
using namespace std;

class Base 
{
	public:
		static int a;
		static void myfun() {
			cout << "I am parent class's function" << endl;
		}
};
int Base::a = 0;

class A : public Base
{
        public:
                static int a;
                static void myfun() {
                        cout << "I am child class's function" << endl;
                }       
};               
int A::a = 1; 

int main(void)
{
	/*使用对象访问静态成员*/
	A obj;
	cout << obj.a << endl;
	cout << obj.Base::a << endl;
	obj.myfun();
	obj.Base::myfun();

	/*使用类访问静态成员*/
	cout << A::a << endl;
	cout << A::Base::a << endl;
	A::myfun();
	A::Base::myfun();
	return 0;
}

2.2.3、派生类的多个父类中有同名成员

#include <iostream>
using namespace std;

class parent1
{
public:
	int a = 6;
};

class parent2
{
public:
	int a = 9;
};

class child : public parent1, public parent2
{};

int main(void)
{
	child obj;
	//cout << obj.a << endl;            //错误,需要指明是哪个父类中的成员
    /*当多个父类中有同名成员时
     *需要指明该同名成员是来自哪个父类*/
	cout << obj.parent1::a << endl;
	cout << obj.parent2::a << endl;
}

2.2.4、钻石继承问题--虚继承

#include<iostream>
using namespace std;

class base
{
public:
	base() {
		cout << "create base" << endl;
		basea = 0;
	}
	~base() {
		cout << "delete base" << endl;
	}
	int basea;
};

class A : virtual public base
{
public:
	A() {
		cout << "create A" << endl;
		a = 1;
	}
	~A() {
		cout << "delete A" << endl;
	}
	int a;
};

class B : virtual public base
{
public:
	B() {
		cout << "create B" << endl;
		b = 2;
	}
	~B() {
		cout << "delete B" << endl;
	}
	int b;
};

class C : public A, public B
{
public:
	C() {
		cout << "create	C" << endl;
		c = 3;
	}
	~C() {
		cout << "delete C" << endl;
	}
	int c;
};

/*通过使用virtual,可以使得A和B中的basea指向同一块内存
 *如果没有使用该关键字,那么A和B会有basea各自的副本*/

int main(void)
{
	C c1;
	cout << "通过C访问A类的a:" << c1.a << endl;
	cout << "通过C访问B类的b:" << c1.b << endl;

	/*第一个问题:不明确的问题
	 *如果不使用virtual关键字,需要指明访问哪个父类的同名成员
	 *使用virtual关键字后,同名成员指向同一内存,不确定来源亦可
	 *以下三个语句均正确*/
	cout << "通过C访问base类的basea:" << c1.basea << endl;
	cout << "通过C访问base类的basea:" << c1.A::basea << endl;
	cout << "通过C访问base类的basea:" << c1.B::basea << endl;

	/*第二个问题:多份同名,不同值的问题*/
	c1.A::basea = 5;
	c1.B::basea = 6;
	cout << "c的basea的值:" << c1.basea << endl;
	cout << "sizeof(C) is: " << sizeof(C) << endl;
	return 0;
}

三、多态

多态允许一个类的对象被视为其他类的对象

多态允许以一致的方式使用不同的类型的对象

多态通过虚函数、指针/引用来实现

多态分为静态多态(函数重载、运算符重载)和动态多态(虚函数、继承对象间的指针/引用)

3.1、静态多态

3.1.1、函数重载

函数名必须相同,参数列表必须不同,返回值可以相同也可以不同

3.1.2、运算符重载

对于我们自定义的类,系统的内置运算符并不适用。因此需要重载运算符

重载运算符:

  • 返回值类型 operator 运算符 (操作数列表) {相关操作}

重载运算符的形式:

  • 使用内联函数:此时,函数为类的成员函数
  • 类内声明类外定义:此时,函数仅可以访问类的公有成员
  • 友元函数实现:此方式比较通用
#include<iostream>
#include<string>
using namespace std;

class student {
public:
	/*使用成员函数实现 加号运算符 重载*/
	student& operator+ (student& st) {
		this->age += st.age;
		return *this;
	}
	/*使用友元函数实现 输出运算符 重载
	 *必须返回一个输出流对象的引用*/
	friend ostream& operator<< (ostream& os, student& st);
	/*使用类内声明类外定义的方式实现 前置++和后置++运算符 重载
	 *此时,仅可访问类的公有成员*/
	student& operator++ ();
	student operator++ (int);
	void print() {
		cout << "age is: " << age << endl;
	}
	int score = 60;
private:
	string name = "lisi";
	int age = 23;
};
ostream& operator<< (ostream& os, student& st) 
{
	os << "name:" << st.name << " " << "age:" << st.age << "score:" << st.score << endl;
	return os;
}

student& student::operator++ ()
{
	++score;
	return *this;
}
student student::operator++ (int) 
{
	student tmp(*this);
	++score;
	return tmp;
}

int main(void)
{
	student st1, st2;
	st1.print();
	st1 + st2;
	st1.print();
	cout << st1;
	st2 = st1++;
	cout << st1.score << endl;
	cout << st2.score << endl;
	cout << st1 << endl;
	cout << st2 << endl;
	return 0;
}

3.2、动态多态

动态多态支持向上转型而不支持向下转型。即一个派生类对象可以赋给一个基类的指针,但一个派生类指针不可以指向一个派生类对象

重写是派生类重新实现(覆盖)基类中已有的虚函数(若不是虚函数,派生类无法覆盖基类的实现),发生在继承关系中。重写会带来一定的内存开销和寻址延迟,但是相对于现在的计算机来说,并不是多大问题(虚函数本身是一个指针)

当基类指针指向派生类对象时,如果基类的析构函数不是虚函数,编译器会根据指针的类型(基类)调用析构函数,而不会根据实际对象的类型(派生类)调用析构函数

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

/*fetchData和performAction是纯虚函数,所以Device是抽象类,该类不可以实例化对象
 *纯虚函数必须在派生类中实现
 *Device应该提供虚析构函数,否则,不会调用子类的析构函数*/
class Device {
public:
	virtual void fetchData() const = 0;
	virtual void performAction() const = 0;
	virtual ~Device() = default;
};

class TemperatureSensor : public Device {
public:
	void fetchData() const override {
		cout << "Fetching temperature data." << endl;
	}
	void performAction() const override {
	}
	~TemperatureSensor() {
		cout << "delete T" << endl;
	}
};

class LightController : public Device {
public:
	void fetchData() const override {
		cout << "Fetching light data." << endl;
	}
	void performAction() const override {
		cout << "Adjusting light intensity" << endl;
	}
	~LightController() {
		cout << "delete L" << endl;
	}
};

class SmartSocket : public Device {
public:
	void fetchData() const override {
		cout << "Fetching power consumption data." << endl;
	}
	void performAction() const override {
		cout << "Turning on/off the smart socket" << endl;
	}
	~SmartSocket() {
		cout << "delete S" << endl;
	}
};

int main(void)
{
	vector<Device*> devices;
	/*devices中有多个基类Device的指针,分别指向不同的派生类
	 *当使用基类的指针指向派生类时 并且基类中的函数使用virtual进行修饰时
	 *使用该指针调用函数时,派生类中的同名函数可以覆盖掉基类中的该函数*/
	devices.push_back(new TemperatureSensor());
	devices.push_back(new LightController());
	devices.push_back(new SmartSocket());

	for (const auto& device : devices) {
		device->fetchData();
		device->performAction();
	}

	/*如果基类Device不定义虚析构函数,并且子类对象是通过new(动态申请内存)出来的
	 *清空Device类型的指针,并不会调用子类的析构函数
     *如果派生类中有动态分配的内存或其他资源(如文件句柄、网络连接等),将会导致内存泄漏
     *此处,将调用deveces.size()次基类的析构函数*/
	for (const auto& device : devices) {
		delete device;
	}
	return 0;
}

四、友元

友元会破坏封装性,不能滥用

友元不受类内访问权限的限制

友元关系不具有传递性,并且是单向的。即,A是B的友元,B是C的友元,A不一定是C的友元,B不一定是A的友元

4.1、友元函数

友元函数通常声明在类中,定义在类外(也可以定义在类内,此时就是隐式的内联函数)

友元函数不是类的成员函数,但是可以访问类的私有成员

友元函数可以是全局函数,也可以是其他类的成员函数

#include <iostream>
using namespace std;

class MyClass {
private:
    int secretValue;

public:
    MyClass(int value) : secretValue(value) {}
    friend void showSecret(const MyClass& obj);
};

void showSecret(const MyClass& obj) {
    cout << "Secret value: " << obj.secretValue << endl;
}

int main() {
    MyClass obj(42);
    showSecret(obj);
    return 0;
}

4.2、友元类

#include <iostream>
using namespace std;

class MyClass {
private:
    int secretValue;

public:
    MyClass(int value) : secretValue(value) {}
    friend class FriendClass;
};

class FriendClass {
public:
    void showSecret(const MyClass& obj) {
        cout << "Secret value: " << obj.secretValue << endl;
    }
};

int main() {
    MyClass obj(42);
    FriendClass friendObj;
    friendObj.showSecret(obj);
    return 0;
}

 


http://www.kler.cn/a/586716.html

相关文章:

  • 蓝桥杯嵌入式赛道复习笔记1(led点亮)
  • 【Python机器学习】2.4. K均值聚类(KMeans Analysis)实战(进阶)
  • SOME/IP:用Python实现协议订阅、Offer、订阅ACK与报文接收
  • 【Unity网络同步框架 - Nakama研究(二)】
  • Spark 中创建 DataFrame 的2种方式对比
  • 【最后203篇系列】015 几种消息队列的思考
  • docker后台运行,便于后期用命令行进入它的终端
  • 【vscode-03】AUTOSAR CP 插件配置
  • DataWhale 速通AI编程开发:(进阶篇)第3章 提示词(Prompts)配置项
  • AI与人的智能,改变一生的思维模型【7】易得性偏差
  • 空调acwing二进制差分
  • C++移动语义与右值引用:从理论到实践的深度解析引言
  • python:数据类构建器
  • 《DeepSeek深度使用教程:开启智能交互新体验》Deepseek深度使用教程
  • 【大模型基础_毛玉仁】2.4 基于 Encoder-Decoder 架构的大语言模型
  • AtCoder Beginner Contest 003(A - 社の給料、B -トランプ、C -プログラミング講座、D - 社の冬 )题目讲解
  • 【PHP】新版本特性记录(持续更新)
  • java 的标记接口RandomAccess使用方法
  • vulnhub靶场之stapler靶机
  • MIDI,AI 3D场景生成技术