C++ 多态 最详细图文+代码讲解

news2024/10/7 7:29:35

                                                              

                                               感谢各位 点赞 收藏 评论 三连支持

                                                   本文章收录于专栏【C++进阶】   

                                                     ❀希望能对大家有所帮助❀

                                                       本文章由 风君子吖 原创

                                       

回顾 

上篇文章,我们学习了继承的相关知识,详细解刨了继承中的各种细节,而本章内容将在继承的基础上学习多态

多态的概念

多态的概念:从字面意思是上来看,就是多种形态。具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态(结果)。

先来举一个日常生活中的例子:我们在路上走着走着,突然手机一响,收到你的好朋友发来的信息,你欣喜地打开跟他的聊天,却发现他竟然给你发了一个拼多多的砍价链接,这时候,你对他回了一句,我没有下载拼多多,然后他却更加开心地回了你一句,新用户砍得更多!

这就是一种多态,明明都是手机用户,为什么新用户能砍的更多,这是不是就是不同的用户进行同样的操作但是却有不同的(状态)结果。

class User {
public:
	virtual void chop()
	{
		cout << "只能砍一点点" << endl;
	}
};

class NewUser : public User {
public:
	virtual void chop()
	{
		cout << "砍得更多!" << endl;
	}
};

class ReturnUser : public User {
public:
	virtual void chop()
	{
		cout << "砍得比较多" << endl;
	}
};

void func(User& user)
{
	user.chop();
}

int main()
{
	User us;
	NewUser nus;
	ReturnUser rus;
	func(us);
	func(nus);
	func(rus);
	return 0;
}

多态的构成条件

想要实现多态,需要满足以下条件

1. 必须通过基类的指针或者引用调用虚函数
2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写

虚函数和虚函数重写

在类中被关键字virtual修饰的函数就叫做虚函数,而虚函数在继承体系中是可以构成重写的!

那么什么是虚函数的重写?

虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的
返回值类型、函数名字、参数列表完全相同【三同】),则称子类的虚函数重写了基类的虚函数。

这与普通函数的重载不同,函数的重载是需要参数的不同,且在同一作用域,而重写是针对虚函数的。

那么总结下来,虚函数要完成重写需要满足以下条件

1.这个函数得在基类和派生类中是虚函数(在派生类中可以省略掉virtural关键字,可以理解为是从基类继承下来的,但是推荐写上)。

2.函数要满足三同(返回值类型、函数名字、参数列表完全相同)。

可是有两个例外

1.协变(基类与派生类虚函数返回值类型不同)
派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指
针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。

这种是C++的一种例外情况,但是很少会用到,仅做了解即可。

class A {
public:
	virtual A& func()
	{
		cout << "A::func()" << endl;
	}
};

class B : public A
{
	virtual B& func()
	{
		cout << "B::func()" << endl;
	}
};

也能构成虚函数的重写

2.析构函数的多态

class A {
public:
	virtual A& func()
	{
		cout << "A::func()" << endl;
	}
	virtual ~A(){}    //析构函数可以构成多态,这是因为底层会将类的析构函数统一命名为destructor
};

class B : public A
{
	virtual B& func()
	{
		cout << "B::func()" << endl;
	}
	virtual ~B() {}
};

这是因为编译器就会在底层将类的析构函数都统一命名为destructor,所以才能构成多态,毕竟要实现三同!


那么再来看多态的构成条件

1. 必须通过基类的指针或者引用调用虚函数
2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写

为什么必须是基类的指针或者是引用调用虚函数?

先来看看如果不是指针和引用来调用函数的情况。

 注意这里的func函数的参数去掉了& 

class User {
public:
	virtual void chop()
	{
		cout << "只能砍一点点" << endl;
	}
};

class NewUser : public User {
public:
	virtual void chop()
	{
		cout << "砍得更多!" << endl;
	}
};

class ReturnUser : public User {
public:
	virtual void chop()
	{
		cout << "砍得比较多" << endl;
	}
};

void func(User user)
{
	user.chop();
}

int main()
{
	User us;
	NewUser nus;
	ReturnUser rus;
	func(us);
	func(nus);
	func(rus);
	return 0;
}

 从结果来看,是没有构成多态的。

那么这是为什么?大家其实可以运用我们之前学习过的继承的知识来解答,因为派生类在转换为基类时,会发生切片 ,这种切片行为如果不是引用或者指针的话,会把派生类的那一部分完完全全地切下来,而如果是引用或者指针,只是限制了你的访问范围。

所以,要想实现多态,是十分严格的,不满足任意一个条件都不行!

多态的原理(重点

不知道大家对于C++调用函数的方式有没有过了解

对于非类成员函数,C++会在代码区的全局中寻找对应的函数

对于被指定了命名空间的非类成员函数,需要指定命名空间才能调用到这个函数

对于类成员函数,它会根据对象类型在类域中寻找对应的函数!

而多态的方式与以上任何方式都不同,因为多态如果采用以上任意一种方式,都无法实现出多态的效果!  那么多态是如何实现准确调用函数的呢?

先说结论   --  是通过虚表指针(virtual function pointer)来找到调用的函数!

虚表指针(重点

先不说虚表指针是干嘛用的,先来看看这东西在哪里,我们再来顺藤摸瓜地了解它是干嘛用的。

class User {
public:
	virtual void chop()
	{
		cout << "只能砍一点点" << endl;
	}

	int _a;
};

int main()
{
	User u;
	cout << sizeof(u) << endl;
	return 0;
}

你认为User类的大小是多少?(在32位系统下)

 为什么是8呢?这里不是只有一个int 类型吗  而int类型是占4个字节

这是因为如果类中有虚函数,那么这个类就会有一个虚表指针,而指针在32位系统下占4个字节,在64位系统下占8个字节。

如果我们调用调试监控,就能发现虚表指针的存在。

 而虚表指针,它是一个什么呢? 先说结论 -- 虚表指针是一个函数指针数组,指向的是虚表(virtual function table)

虚表就是存放虚函数地址的地方。

而虚表指针就是一个指向虚表的函数指针数组,里面存放着这个类的所有虚函数的地址。

而多态,就是通过虚表指针来实现调用对应的函数的。

注意:虚表指针是对象构造的时候生成的,所以构造函数和拷贝构造不能实现多态(条件也不符合),虚表是编译就存在的,虚表存放在代码区

多态的调用(重点

那么多态的调用函数 跟其他普通函数到底有什么不同之处?

我们举以下例子

class User {
public:
	virtual void chop()
	{
		cout << "只能砍一点点" << endl;
	}

	int _a;
};

class NewUser : public User {
public:
	virtual void chop()
	{
		cout << "砍得更多!" << endl;
	}
};

int main()
{
	NewUser nu;

	User* ptr1 = &nu;
	ptr1->chop();       //构成多态,是多态的函数调用

	User user = nu;
	user.chop();        //不构成多态,是普通函数的调用

	return 0;
}

光看结果是没有用的,我们进入调试,然后切到反汇编来进行观察这两个函数的调用。(32位系统下,64位系统下稍有区别)

所以结论就是,多态的调用是根据eax寄存器中存放的指针来决定call的函数地址在哪里,这种调用需要程序在运行的过程中将虚表指针指向的对应的虚函数存入eax寄存器中,这种调用方式,我们称为“运行时决议”。

class A {
public:
	virtual void func1()
	{
		cout << "A::func()" << endl;
	}

	virtual void func2()
	{
		cout << "A::func()" << endl;
	}

	int _a = 1;
};

class B : public A
{
public:
	virtual void func1()
	{
		cout << "B::func()" << endl;
	}

	int _b = 2;
};

int main()
{
	A a;
	B b;
	A* ptr1 = &b;
	ptr1->func2();
}

 虚表指针的作用

附加知识

普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实
现。虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成
多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数。

C++11 新增的两个关键字 override 和 final

这是c++11 新增的两个关键字

override

作用:用于识别是否构成了虚函数的重写,如果构成了则无事发生,如果没有构成,则报错!

class A {
public:
	void func1()
	{
		cout << "A::func()" << endl;
	}

	int _a = 1;
};

class B : public A
{
public:
	virtual void func1() override
	{
		cout << "B::func()" << endl;
	}
	int _b = 2;
};
不构成重写
error C3668: “B::func1”: 包含重写说明符“override”的方法没有重写任何基类方法

final

作用:修饰虚函数,表示该虚函数不能再被重写

class A {
public:
	virtual void func1() final     //加了final ,该虚函数不可被重写
	{
		cout << "A::func()" << endl;
	}
};

class B : public A
{
public:
	virtual void func1()
	{
		cout << "B::func()" << endl;
	}
};
 error C3248: "A::func1": 声明为 "final" 的函数不能由 "B::func1" 重写

抽象类 

在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口
类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生
类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。

class A {
public:
	virtual void func1() = 0;
	int _a = 1;
};

class B : public A
{
public:
	virtual void func1()
	{
		cout << "B::func()" << endl;
	}
	int _b = 2;
};

int main()
{
	//A a;         抽象类无法实例化
	B b;
	A* prt1 = &b;
	prt1->func1();
	return 0;
}

单继承调试器的小缺陷

多态有多种情况,而有这样么一种情况,派生类有基类没有的虚函数,那么这个虚函数是否还会进入虚表之中呢?  答案是肯定的,只要是虚函数就一定会进入虚表! 但是在VS调试窗口中是看不见的,可以理解为是调试器的小缺陷。

class A {
public:
	virtual void func1()
	{
		cout << "A::func1()" << endl;
	}

	virtual void func2()
	{
		cout << "A::func2()" << endl;
	}

	int _a = 1;
};

class B : public A
{
public:
	virtual void func1()
	{
		cout << "B::func1()" << endl;
	}

	virtual void func2()
	{
		cout << "B::func2()" << endl;
	}

	virtual void func3()
	{
		cout << "B::func3()" << endl;
	}
	int _b = 2;
};

 

那么怎么证明这个虚函数是在虚表的? 空口无凭,我们直接通过内存观测窗口来证明! 

注意:如果观测结果有误,需要重新生成一下解决方案!

从内存窗口来看,是可以看到虚表中存在三个函数指针的,并且在VS编译器下,会在虚表的后面以nullptr作为结束条件。   (其他编译器处理方式不同,有的编译器没有对虚表结尾做处理)

用歪门邪道调用虚函数

既然虚表中存放了虚函数的地址,那么我们就可以用一些歪门邪道来调用虚函数。

class A {
public:
	virtual void func1()
	{
		cout << "A::func1()" << endl;
	}

	virtual void func2()
	{
		cout << "A::func2()" << endl;
	}

	int _a = 1;
};

class B : public A
{
public:
	virtual void func1()
	{
		cout << "B::func1()" << endl;
	}

	virtual void func2()
	{
		cout << "B::func2()" << endl;
	}

	virtual void func3()
	{
		cout << "B::func3()" << endl;
	}
	int _b = 2;
};

typedef void(*VFP)();         重定义void函数指针类型为 VFP

void Print(VFP* VFTable)
{
	for (int i = 0; VFTable[i] != nullptr; ++i)
	{
		printf("第%d个虚函数地址是%p:  ",i + 1,VFTable[i]);
		VFTable[i]();
	}
}

int main()
{
	B b;
	Print((VFP*)(*(int*)&b));
	return 0;
}

需要注意的是,因为在VS编译器下,虚表会以nullptr作为结束标志,其他编译器如果想实现这种效果就不能以 VFTable[i] != nullptr  作为终止条件   ,只能提前知晓他有几个虚函数,然后设置循环几次。

多继承中的虚函数表(重点)

那么对于多继承的多态,会有什么变化呢?

class A {
public:
	virtual void func1()
	{
		cout << "A::func1()" << endl;
	}

	virtual void func2()
	{
		cout << "A::func2()" << endl;
	}

	int _a = 1;
};

class B
{
public:
	virtual void func1()
	{
		cout << "B::func1()" << endl;
	}

	virtual void func2()
	{
		cout << "B::func2()" << endl;
	}

	int _b = 2;
};

class C : public A,public B
{
public:
	virtual void func1()
	{
		cout << "C::func()" << endl;
	}

	virtual void func3()
	{
		cout << "C::func3()" << endl;
	}
	int _c = 3;
};

typedef void(*VFP)();

void Print(VFP* VFTable)
{
	for (int i = 0; VFTable[i] != nullptr; ++i)
	{
		printf("第%d个虚函数地址是%p:  ",i + 1,VFTable[i]);
		VFTable[i]();
	}
}


int main()
{
	C c;
	VFP* vTable1((VFP*)(*(int*)&c));
	VFP* vTable2 = (VFP*)(*(int*)((char*)&c + sizeof(A)));
   	Print(vTable1);
	cout << endl;
	Print(vTable2);
	cout << endl;
	return 0;
}

从结果来看,多继承的多态,是会有多个虚表的,取决于你继承了几个类,而派生类的独有虚函数fun3是被存放在最先被继承的基类的虚表中的。

这里有一个疑问,为什么这里两个虚表中都被重写的func1的地址不同,但是都调用到了func1,这个涉及到了比较底层的问题,有一些编译器的行为,由于涉及较偏,这里不作解释,只用知道通过这个地址都能调用到同一个函数即可,如果感兴趣的话,可以通过反汇编来理解。

注意:如果打印不出来,需要重新生成一下解决方案!

对于多继承,它的构造函数我们也了解一下

class A {
public:
	virtual void func1()
	{
		cout << "A::func1()" << endl;
	}

	A(int a = 1)
		:_a(a)
	{}
protected:
	int _a;
};

class B
{
public:
	virtual void func1()
	{
		cout << "B::func1()" << endl;
	}
	B(int b = 2)
		:_b(b)
	{}

protected:
	int _b;
};

class C : public A,public B
{
public:
	virtual void func1()
	{
		cout << "C::func()" << endl;
	}
	C(int a = 1, int b = 2, int c = 3)
		:A(a)
		,B(b)
		,_c(c){}

protected:
	int _c;
};

int main()
{
	C c;
	return 0;
}

 

菱形继承、菱形虚拟继承 + 多态(了解)

菱形继承特别是菱形虚拟继承如果还要再加入多态,那就太复杂了,上个文章我就讲了,菱形继承实在不推荐用,会出现比较多的问题。

菱形继承与多继承类似,都会有两个虚表

class A {
public:
	virtual void func1()
	{
		cout << "A::func1()" << endl;
	}

	virtual void func2()
	{
		cout << "A::func2()" << endl;
	}

	int _a = 1;
};

class B : public A
{
public:
	virtual void func1()
	{
		cout << "B::func1()" << endl;
	}

	virtual void func2()
	{
		cout << "B::func2()" << endl;
	}

	int _b = 2;
};

class C : public A
{
public:
	virtual void func1()
	{
		cout << "C::func()" << endl;
	}

	virtual void func3()
	{
		cout << "C::func3()" << endl;
	}
	int _c = 3;
};

class D : public B, public C
{
public:
	virtual void func1()
	{
		cout << "D::func1()" << endl;
	}

	virtual void func4()
	{
		cout << "D::func4()" << endl;

	}
	int _d = 4;
};

int main()
{
	D d;
	VFP* vTable1((VFP*)(*(int*)&d));
	VFP* vTable2 = (VFP*)(*(int*)((char*)&d + sizeof(B)));
	Print(vTable1);
	cout << endl;
	Print(vTable2);
	cout << endl;
	return 0;
}

 注意:如果打印不出来需要重新生成一下解决方案!

菱形虚拟继承+多态(了解,难)

这里只做细微讲解,只看一下结构,和一些注意事项

因为菱形虚拟继承会改变原有的模型结构,这个时候调试窗口的结构已经不太方便我们观察,我们使用内存调试窗口来进行观察。

class A {
public:
	virtual void func1()
	{
		cout << "A::func1()" << endl;
	}

	int _a = 1;
};

class B : virtual public A
{
	virtual void func1()
	{
		cout << "B::func1()" << endl;
	}

	virtual void func2()
	{
		cout << "B::func2()" << endl;
	}

	int _b = 2;
};

class C : virtual public A
{
	virtual void func1()
	{
		cout << "C::func1()" << endl;
	}

	virtual void func2()
	{
		cout << "C::func2()" << endl;
	}
	int _c = 3;
};

class D : public B, public C
{
public:
	virtual void func1()
	{
		cout << "D::func1()" << endl;
	}

	virtual void func2()
	{
		cout << "D::func2()" << endl;
	}

	virtual void func3()
	{
		cout << "D::func3()" << endl;
	}
	int _d = 4;
};

typedef void(*VFP)();

void Print(VFP* VFTable)
{
	for (int i = 0; VFTable[i] != nullptr; ++i)
	{
		printf("第%d个虚函数地址是%p:  ",i + 1,VFTable[i]);
		VFTable[i]();
	}
}

int main()
{
	D d;
	cout << "sizeof(D):" << sizeof(D) << endl;
	cout << "sizeof(B):" << sizeof(B) << endl;
	cout << "sizeof(C):" << sizeof(C) << endl;
	VFP* vTable1 = ((VFP*)(*(int*)&d));
	VFP* vTable2 = (VFP*)(*(int*)((char*)&d + 12));
	VFP* vTable3 = (VFP*)(*(int*)((char*)&d + 28));
	Print(vTable1);
	cout << endl;
	Print(vTable2);
	cout << endl;
	Print(vTable3);
	return 0;
}

 

菱形虚拟继承的构造注意事项

class A {
public:
	virtual void func1()
	{
		cout << "A::func1()" << endl;
	}

	A(int a = 1)
		:_a(a){}

	int _a;
};

class B : virtual public A
{
public:
	virtual void func1()
	{
		cout << "B::func1()" << endl;
	}

	B(int a = 1,int b = 2)
		:A(a) 
		,_b(b) 
	{}

	int _b;
};

class C : virtual public A
{
public:
	virtual void func1()
	{
		cout << "C::func1()" << endl;
	}

	C(int a = 1, int c = 3)
		:A(a)
		, _c(c)
	{}

	int _c;
};

class D : public B, public C
{
public:
	virtual void func1()
	{
		cout << "D::func1()" << endl;
	}

	D(int a = 5 , int b = 2, int c = 3, int d = 4)
		:A(a)
		,B(b)
		,C(c)
		,_d(d) 
	{}

	int _d = 4;
};

int main()
{
	D d;
	cout << d._a << endl;
}

 

 

实际中我们不建议设计出菱形继承及菱形虚拟继承,一方面太复杂容易出问题,另一方面这样的
模型,访问基类成员有一定得性能损耗。

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

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

相关文章

Dreamweaver如何进行网页开发?

文章目录 0.引言1.安装Dreamweaver2.编写第一个网页 0.引言 笔者本科学习的编程语言主要是关于桌面开发的&#xff0c;对编程有一定的基础&#xff0c;而编程除了关于桌面软件开发&#xff08;VisualStudio如何进行桌面软件开发&#xff1f;&#xff09;&#xff0c;还有手机应…

网络安全自学笔记+学习路线(超详细)

01 什么是网络安全 网络安全可以基于攻击和防御视角来分类&#xff0c;我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术&#xff0c;而“蓝队”、“安全运营”、“安全运维”则研究防御技术。 无论网络、Web、移动、桌面、云等哪个领域&#xff0c;都有攻与防两面…

c语言编程练习题:7-193 两小时学完C语言

#include <stdio.h> int main(){int n,k,m;int sum;if (scanf("%d %d %d",&n,&k,&m)!EOF){sum n-k*m;}printf("%d",sum);return 0; }

(学习日记)2023.04.28

写在前面&#xff1a; 由于时间的不足与学习的碎片化&#xff0c;写博客变得有些奢侈。 但是对于记录学习&#xff08;忘了以后能快速复习&#xff09;的渴望一天天变得强烈。 既然如此 不如以天为单位&#xff0c;以时间为顺序&#xff0c;仅仅将博客当做一个知识学习的目录&a…

基于SSM的教务管理系统的设计与实现(论文+源码)_kaic

摘 要 学校教务管理信息化是提高办公效率的主要途径。随着中国高等教育的快速发展和学校规模的不断扩大&#xff0c;在校学生人数不断增加&#xff0c;办学层次出现多元化&#xff0c;由一地办学发展到多地多点办学&#xff0c;同时进一步推行学分制。这些变化使得教务部门的管…

【AI绘图】四、stable difusion提示词如何写?

上一篇&#xff1a;【AI绘图】三、stable diffussion操作界面介绍以及如何使用 如何写好提示词&#xff1f; 写出一份比较好的提示词是文生图技术的关键。但是&#xff0c;写出一份好的prompt并不容易&#xff0c;下面针对“如何写好提示词”这个问题&#xff0c;从提示词构成…

MySQL 数据操纵语言 DML

文章目录 数据操纵语言 DMLINSERT 语句UPDATE 语句DELETE 语句 数据操纵语言 DML 数据操纵语言&#xff08;Data Manipulation Language&#xff0c;DML&#xff09;是 SQL 语言的核心部分之一。在添加、更新或者删除表中的数据时&#xff0c;需要执行 DML 语句。很多时候我们提…

Unity导入Android aar包实现交互全流程

一.搭建Android项目 1.创建一个Android空项目 点击finish后,就等待编译,过程中会自动下载一些插件 等待... 等待... 编译完成: 2.创建Module 右键该工程的app,新建一个Module, Language:Kotlin语法和Java语法,语法不同,后续创建的代码文件有所差异,但不影响代码编…

地震勘探基础(六)之地震反褶积

地震反褶积 地震资料常规处理主要包括地震反褶积&#xff0c;水平叠加和偏移成像三大内容。水平叠加可以提高地震资料的信噪比&#xff0c;偏移成像可以提高地震资料的空间分辨率和保真度&#xff0c;地震反褶积可以提高地震资料的分辨率和压制干扰波。 1954年&#xff0c;Ro…

Python心经(5)

目录 python对于类和实例&#xff0c;&#xff0c;都能随时动态绑定 属性或者函数 可以通过__slots__去限定实例所能绑定属性的范围 python里面类很多定制函数__xx__ 下面给个python里面枚举类型 对type函数去实现 有关迭代器&#xff1a; 生成器&#xff1a; 可迭代对…

【Linux编译器gcc/g++】带你了解代码是如何变成可执行程序的!

Linux编辑器gcc/g的使用 背景知识gcc的使用预处理&#xff08;进行宏替换&#xff09;编译(生成汇编)汇编(生成及其可识别代码)链接(生成可执行文件或者库文件)库 动态库vs静态库 debug和release&#x1f340;小结&#x1f340; &#x1f389;博客主页&#xff1a;小智_x0___0x…

【FPGA零基础学习之旅#6】ip核基础知识之计数器

&#x1f389;欢迎来到FPGA专栏~ip核基础知识之计数器 ☆* o(≧▽≦)o *☆嗨~我是小夏与酒&#x1f379; ✨博客主页&#xff1a;小夏与酒的博客 &#x1f388;该系列文章专栏&#xff1a;FPGA学习之旅 文章作者技术和水平有限&#xff0c;如果文中出现错误&#xff0c;希望大家…

[网络工程]小型局域网组建的常用命令(ENSP)

⭐作者介绍&#xff1a;大二本科网络工程专业在读&#xff0c;持续学习Java&#xff0c;努力输出优质文章 ⭐作者主页&#xff1a;逐梦苍穹 目录 1、引言2、常用命令(ENSP)常规VTYConsole端口安全单臂路由DHCPNATACL基础ACL高级ACL GVRPSTP 1、引言 局域网&#xff08;Local A…

C++学习——第一节课-初识C++

大家好&#xff0c;我是涵子。今天我们来开始学习C。 目录 一、课前准备 二、C的第一个程序 2.1.C是个啥 2.2.C的第一个程序编写 2.2.1.头文件 2.2.2.命名空间 2.2.3.主程序函数 2.2.4.输出流 2.2.5.代码结束 三、其它的应用 3.1.输出三角形&#xff0c;矩形和勾 …

单例模式C++实现和观察者模式C++实现

目录 1、单例模式介绍 2、单例代码实现 2.1 static介绍 2.2 C中static的三种用法&#xff1a; &#xff08;1&#xff09;静态局部变量 &#xff08;2&#xff09;静态成员变量 &#xff08;3&#xff09;静态成员函数 3、观察者模式介绍 4、观察者代码实现 1、单例模…

.ini配置文件介绍与解析库使用

【前言】 ini 文件是英文"Initialization"的缩写&#xff0c;即初始化文件。它用来配置特定应用软件以实现对程序初始化或进行参数设置。.ini文件由节(section)、键(key)、值(value)三种模块构成。在windows系统/嵌入式软件中有很多XXX.ini文件&#xff0c;例如Syste…

IDC机房相电压与线电压的关系

380V电动机&#xff08;三相空调压缩机&#xff09;的电流计算公式为&#xff1a;Ⅰ&#xff1d;额定功率&#xff08;1.732额定电压功率因数效率&#xff09;。 功率因数是电力系统的一个重要的技术数据。功率因数是衡量电气设备效率高低的一个系数。功率因数低&#xff0c;说…

通过源码编译安装搭建 LNMP平台

搭建LNMP平台 一. 安装Nginx服务1.1 安装依赖包1.2 创建运行用户1.3 编译安装1.4 优化路径1.5 添加 Nginx 系统服务 二. 安装mysql服务2.1 安装Mysql环境依赖包2.2 创建运行用户2.3 编译安装2.4 修改mysql 配置文件2.5 更改mysql安装目录和配置文件的属主属组2.6 设置路径环境变…

VMware安装Windows11

VMware安装Windows11 嘚吧嘚准备工作VMware下载Windows11下载 VMware安装Windows11VMware配置安装Windows11 嘚吧嘚 最近在搞一些自己感兴趣的东西&#xff0c;需要(临时)安装一些软件来验证&#xff0c;考虑到用完还要卸载&#xff0c;不想把自己的电脑搞得乱七八糟&#x1f…