《C++新经典对象模型》之第6章 对象构造语义学

《C++新经典对象模型》之第6章 对象构造语义

  • 6.1 继承体系下的对象构造
    • 6.1.1 对象的构造顺序
    • 6.1.2 虚函数
    • 6.1.3 构造函数中对虚函数的调用
        • 06.01.cpp
  • 6.2 对象复制语义学与析构函数语义学
    • 6.2.1 对象的默认复制行为
    • 6.2.2 拷贝赋值运算符与拷贝构造函数
    • 6.2.3 禁止对象的拷贝构造和赋值
    • 6.2.4 析构函数语义
        • 06.02.cpp
  • 6.3 局部对象、全局对象的构造和析构
    • 6.3.1 局部对象的构造和析构
    • 6.3.2 全局对象的构造和析构
        • 06.03.cpp
    • 6.4 局部静态对象、对象数组构造析构和内存分配
      • 6.4.1 局部静态对象的构造和析构
      • 6.4.2 局部静态对象数组的内存分配
        • 06.04.cpp
    • 6.5 new、delete运算符与内存高级话题
        • 06.05.cpp
    • 6.6 临时性对象的详细探讨
      • 6.6.1 拷贝构造函数相关的临时性对象
      • 6.6.2 拷贝赋值运算符相关的临时性对象
      • 6.6.3 直接运算产生的临时性对象
        • 06.06.cpp

6.1 继承体系下的对象构造

6.1.1 对象的构造顺序

从父类到子类,从根源到末端。
先成员变量(定义顺序,从上到下),后构造函数(先初始化列表,后函数体)。

析构顺序:与构造顺序相反。
从子类到父类,先析构函数,后成员变量(从下到上)。

C::成员变量1构造函数	//7
C::成员变量2构造函数	//8
C::C()	//9B::成员变量1构造函数	//4B::成员变量2构造函数	//5B::B()	//6A::成员变量1构造函数	//1A::成员变量2构造函数	//2A::A()	//3构造函数初始化列表构造函数函数体A::~A()	//7A::成员变量2析构函数	//8A::成员变量1析构函数	//9B::~B()	//4B::成员变量2析构函数	//5B::成员变量1析构函数	//6
C::~C()	//1
C::成员变量2析构函数	//2
C::成员变量1析构函数	//3

6.1.2 虚函数

给虚函数表指针赋值的语句,编译器会插入到构造函数的函数体之前。

C::C()B::B()	A::A()vptr = A::vftable;	//虚函数表指针赋值A构造函数初始化列表A构造函数函数体vptr = B::vftable;B构造函数初始化列表B构造函数函数体vptr = C::vftable;C构造函数初始化列表C构造函数函数体

6.1.3 构造函数中对虚函数的调用

(1)构造函数中调用虚函数,并不通过虚函数表来调用,而是直接调用(虚函数有真实地址)。
(2)在构造函数中调用的虚函数从所在类往根类回溯,逐次找这个虚函数,找到哪个就直接调用(静态方式)。
(3)构造函数中调用虚函数时,对象未构造完整,不宜采用虚函数表机制调用虚函数。
(4)不要在类的构造函数和析构函数中调用虚函数。

#include <iostream>
using namespace std;namespace _n1
{class TA{public:TA(){cout << "TA::TA(), this = " << this << endl;}~TA(){cout << "TA::~TA(), this = " << this << endl;}};class TB{public:TB(){cout << "TB::TB(), this = " << this << endl;}~TB(){cout << "TB::~TB(), this = " << this << endl;}};class T1{public:T1(){cout << "T1::T1(), this = " << this << endl;}~T1(){cout << "T1::~T1(), this = " << this << endl;}};class T2 : public T1{public:T2(){cout << "T2::T2(), this = " << this << endl;}~T2(){cout << "T2::~T2(), this = " << this << endl;}};class T3 : public T2{public:T3(){cout << "T3::T3(), this = " << this << endl;}~T3(){cout << "T3::~T3(), this = " << this << endl;}private:TA ta;TB tb;};
}namespace _n2
{class A{public:A(){myvirfunc();cout << "A::A(), this = " << this << endl;}virtual ~A(){myvirfunc();cout << "A::~A(), this = " << this << endl;}public:virtual void myvirfunc(){myvirfunc2();cout << "A::myvirfunc(), this = " << this << endl;}virtual void myvirfunc2(){cout << "A::myvirfunc2(), this = " << this << endl;}};class B : public A{public:B(){myvirfunc();cout << "B::B(), this = " << this << endl;}virtual ~B(){myvirfunc();cout << "B::~B(), this = " << this << endl;}public:virtual void myvirfunc(){myvirfunc2();cout << "B::myvirfunc(), this = " << this << endl;}virtual void myvirfunc2(){cout << "B::myvirfunc2(), this = " << this << endl;}};class C : public B{public:C() : m_c(11){myvirfunc(); // 调用一个虚函数cout << "C::C(), this = " << this << endl;}virtual ~C(){myvirfunc();cout << "C::~C(), this = " << this << endl;}public:int m_c;public:virtual void myvirfunc(){myvirfunc2();cout << "C::myvirfunc(), this = " << this << endl;}virtual void myvirfunc2(){cout << "C::myvirfunc2(), this = " << this << endl;}};
}int main()
{if (0){_n1::T3 t3;}if (0){_n2::C c;}if (1){_n2::A *mycobj = new _n2::C();mycobj->myvirfunc();delete mycobj;}return 0;
}
06.01.cpp
#include <cstdio>
#include <iostream>
using namespace std;struct A
{A() { printf("A::A(), this = %p\n", this); }virtual ~A() {}virtual void myvirfunc2(){printf("A::myvirfunc2()\n");}virtual void myvirfunc(){myvirfunc2();printf("A::myvirfunc()\n");}
};
struct B : A
{B(){myvirfunc();printf("B::B(), this = %p\n", this);}virtual ~B() {}virtual void myvirfunc2(){printf("B::myvirfunc2()\n");}virtual void myvirfunc(){myvirfunc2();printf("B::myvirfunc()\n");}
};
struct C : B
{int m_c;C(): m_c(11){myvirfunc(); // 调用一个虚函数printf("C::C(), this = %p\n", this);}virtual ~C(){myvirfunc();}virtual void myvirfunc2(){printf("C::myvirfunc2()\n");}virtual void myvirfunc(){myvirfunc2();printf("C::myvirfunc()\n");}
};int main()
{// C c;C *mycobj = new C();// mycobj->myvirfunc();delete mycobj;cout << "Over!\n";return 0;
}

6.2 对象复制语义学与析构函数语义学

6.2.1 对象的默认复制行为

无拷贝构造函数和拷贝赋值运算符时,默认的对象复制行为会发挥作用。

6.2.2 拷贝赋值运算符与拷贝构造函数

提供拷贝赋值运算符或拷贝构造函数时,需提供默认构造函数(此时编译器不会生成)。


struct JI
{JI() { cout << "JI::JI()" << endl; }virtual ~JI() { cout << "JI::~JI()" << endl; }
};struct A : JI
{int m_i, m_j;A() { cout << "A::A()" << endl; }~A() { cout << "A::~A()" << endl; }A &operator=(const A &tmp){if (&tmp == this)return *this;// static_cast<JI&>(*this) = tmp; // 调用父类的拷贝赋值运算符// JI::operator=(tmp);// 调用父类的拷贝赋值运算符m_i = tmp.m_i;m_j = tmp.m_j;cout << "A::operator=(const A&)" << endl;return *this;}A(const A &tmp)//: JI(tmp) // 显式调用父类拷贝构造函数{// 编译器会插入代码,父类按位复制,调用父类的构造函数或者拷贝构造函数?m_i = tmp.m_i;m_j = tmp.m_j;cout << "A::A(const A&)" << endl;}
};

6.2.3 禁止对象的拷贝构造和赋值

(1)类中声明私有的拷贝构造函数和拷贝赋值运算符,无函数体。

class A {private:A& operator=(const A&);A(const A&);
};

(2)c++11,=delete,将拷贝构造函数和拷贝赋值运算符标记为禁用。

class A {public:A& operator=(const A&) =delete;A(const A&) =delete;
};

6.2.4 析构函数语义

  1. 析构函数被合成
    编译器合成析构函数的情况:
    (1)类继承的父类具有析构函数,会合成析构函数并调用父类的析构函数。
    (2)类中类类型成员变量具有析构函数,会合成析构函数并调用该成员变量所属类的析构函数。

  2. 析构函数被扩展
    已写析构函数时,向其中增加代码的情况:
    (1)类继承的父类具有析构函数,会在析构函数后面插入代码,调用父类的析构函数。
    (2)类中类类型成员变量具有析构函数,会在析构函数后面插入代码,调用该成员变量所属类的析构函数。

06.02.cpp
#include <iostream>
using namespace std;struct JI
{JI() { cout << "JI::JI()" << endl; }virtual ~JI() { cout << "JI::~JI()" << endl; }
};struct A : JI
{int m_i, m_j;// private://	A& operator=(const A& tmp);//	A(const A& tmptime);// public://	A& operator=(const A& tmp) = delete;//	A(const A& tmptime) = delete;A() { cout << "A::A()" << endl; }~A() { cout << "A::~A()" << endl; }A &operator=(const A &tmp){if (&tmp == this)return *this;// static_cast<JI&>(*this) = tmp; // 调用父类的拷贝赋值运算符// JI::operator=(tmp);// 调用父类的拷贝赋值运算符m_i = tmp.m_i;m_j = tmp.m_j;cout << "A::operator=(const A&)" << endl;return *this;}A(const A &tmp)//: JI(tmp) // 显式调用父类拷贝构造函数{// 编译器会插入代码,调用父类的构造函数或者拷贝构造函数?m_i = tmp.m_i;m_j = tmp.m_j;cout << "A::A(const A&)" << endl;}
};struct ParC
{virtual ~ParC() { cout << "ParC::~ParC()" << endl; }
};
struct MemC
{ParC m_j;~MemC() { cout << "MemC::~MemC()" << endl; }
};int main()
{{A aobj;aobj.m_i = 15;aobj.m_j = 20;A aobj2 = aobj; // 执行拷贝构造A aobj3;aobj3.m_i = 13;aobj3.m_j = 16;aobj2 = aobj3; // 执行拷贝复制运算符}{MemC mobj;}cout << "Over!\n";return 0;
}

6.3 局部对象、全局对象的构造和析构

6.3.1 局部对象的构造和析构

只要超出了对象的作用域,编译器总会在适当的地方插入调用对象析构函数的代码。
尽量把对象定义在需要立即用到它的代码段的附件。

6.3.2 全局对象的构造和析构

全局对象的初始化和释放过程:
(1)全局对象获得地址(可执行文件中确定的,和堆、栈中分配内存不一样,程序运行期间一直存在)。
(2)全局对象内存清零(静态初始化)。
(3)调用全局对象对应类的构造函数。
(4)执行main函数。
(5)main函数执行完毕后,调用全局对象对应类的析构造函数。
(6)整个可执行程序执行完毕。

06.03.cpp
#include <iostream>
using namespace std;struct A
{int m_i;A() { cout << "A::A()" << endl; }~A() { cout << "A::~A()" << endl; }
};void myfunc()
{// A obja; // 这里定义不合适if (1 == 1){// 这里会被编译器插入调用obja对象析构函数的代码,影响执行效率完全没必要return;}A obja; // 这里定义合适obja.m_i = 10;cout << "obja.m_i = " << obja.m_i << endl;return;
}A g_aobj;int main()
{{A obja;int mytest = 1;if (mytest == 0)return 0;myfunc();}g_aobj.m_i = 6; //cout << "Over!\n";return 0;
}

6.4 局部静态对象、对象数组构造析构和内存分配

6.4.1 局部静态对象的构造和析构

只有当函数调用时,局部静态对象的构造函数才会执行。
多次调用,只有第一次才构造。
编译时内存开始地址和大小已确定(BSS段),运行到对应代码时,才从事先约定好的地址分配出来。
构造实现方式:局部静态对象地址旁增加字节做标记(是否已构造),第一次构造,第二次标记存在,不构造,跳过static A s_aobj;代码行。
析构实现方式:第一次构造时,增加代码_atexit登记信息,main函数执行完后执行析构。

6.4.2 局部静态对象数组的内存分配

同局部静态对象类似。
但编译器优化,大数组不分配实际的物理地址,对象数组做有用事情时,才实际分配。

06.04.cpp
#include <cstdio>
#include <iostream>
using namespace std;struct A
{int m_i;// A() { cout << "A::A()" << endl; }//~A() { cout << "A::~A()" << endl; }
};void myfunc1()
{static A s_aobj1;static A s_aobj2;printf("&s_aobj1=%p\n", &s_aobj1);printf("&s_aobj2=%p\n", &s_aobj2);
}
const A &myfunc2()
{static A s_aobj1;printf("&s_aobj1=%p\n", &s_aobj1);return s_aobj1;
}void myfunc()
{static A s_aobj[1000'0000]; // '是数字分隔符, C++14for (int i = 0; i < 1000'0000; i++)s_aobj[i].m_i = i;//大数组做有用事情时,才实际分配内存printf("s_aobj = %p\n", s_aobj);
}int main()
{myfunc1();myfunc1();printf("&A=%p\n", &myfunc2());printf("&A=%p\n", &myfunc2());myfunc();myfunc();cout << "Over!\n";return 0;
}

6.5 new、delete运算符与内存高级话题

void *p = malloc(0);
char *q = new char[0];

new内部调用malloc,两行代码几乎等价。
代码诡异,可能报错,别使用。

06.05.cpp
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;int main()
{void *p = malloc(0);char *q = new char[0];strcpy_s((char *)p, 100, "This is a test1!");strcpy_s(q, 100, "This is a test2!");printf("p=%s\n", p);printf("q=%s\n", q);delete[] q;free(p);cout << "Over!\n";return 0;
}

6.6 临时性对象的详细探讨

6.6.1 拷贝构造函数相关的临时性对象

struct A
{int m_i;A() { cout << "A::A()" << endl; }~A() { cout << "A::~A()" << endl; }A(const A &tmp){m_i = tmp.m_i;cout << "A::A(const A&)" << endl;}A &operator=(const A &tmp){m_i = tmp.m_i;cout << "A::operaotor=(const A&)" << endl;//printf("&tmp=%p\n", &tmp);return *this;}
};
A operator+(const A &obj1, const A &obj2)
{// cout << "A operaotor+(const A&, const A &)" << endl;A tmp; // 一次构造函数tmp.m_i = obj1.m_i + obj2.m_i;printf("&tmp=%p\n", &tmp);return tmp; // 一次拷贝构造函数+一次析构函数(编译器优化后可能无临时对象)
}if (1){A myobj1;printf("&myobj1=%p\n", &myobj1);A myobj2;printf("&myobj2=%p\n", &myobj2);A resultobj1 = myobj1 + myobj2;//编译器优化后直接将tmp的地址用作resultobj1的地址,节省一次复制构造+一次析构cout << "------------------------\n";printf("&resultobj1=%p\n", &resultobj1);}

6.6.2 拷贝赋值运算符相关的临时性对象

struct A
{int m_i;A() { cout << "A::A()" << endl; }~A() { cout << "A::~A()" << endl; }A(const A &tmp){m_i = tmp.m_i;cout << "A::A(const A&)" << endl;}A &operator=(const A &tmp){m_i = tmp.m_i;cout << "A::operaotor=(const A&)" << endl;//printf("&tmp=%p\n", &tmp);return *this;}
};
A operator+(const A &obj1, const A &obj2)
{// cout << "A operaotor+(const A&, const A &)" << endl;A tmp; // 一次构造函数tmp.m_i = obj1.m_i + obj2.m_i;printf("&tmp=%p\n", &tmp);return tmp; // 一次拷贝构造函数+一次析构函数(编译器优化后可能无临时对象)
}if (1){A myobj1;printf("&myobj1=%p\n", &myobj1);A myobj2;printf("&myobj2=%p\n", &myobj2);A resultobj2;resultobj2 = myobj1 + myobj2;//一次构造函数+一次拷贝赋值运算符+一次析构cout << "------------------------\n";printf("&resultobj2=%p\n", &resultobj2);}

6.6.3 直接运算产生的临时性对象

if (1){A myobj1;A myobj2;myobj1 + myobj2;//一次构造+一次析构}if (1){A myobj1;myobj1.m_i = 1;A myobj2;myobj2.m_i = 2;printf("(myobj1 + myobj2).m_i = %d\n", (myobj1 + myobj2).m_i); // 临时对象(operator+,一次构造+一次析构,且析构在printf后执行)}if (1){A myobj1;myobj1.m_i = 1;A myobj2;myobj2.m_i = 2;if ((myobj1 + myobj1).m_i > 3 || (myobj1 + myobj2).m_i > 5) // 两个临时对象cout << "Condition established" << endl;elsecout << "Condition not established" << endl;}if (1){A myobj1;myobj1.m_i = 1;A myobj2;myobj2.m_i = 2;if ((myobj1 + myobj1).m_i > 1 || (myobj1 + myobj2).m_i > 5) // 一个临时对象cout << "Condition established" << endl;}

编译器会在必要的地方插入代码产生临时对象,完成要实现的意图。

  1. 临时对象被摧毁
if (0){const char *p = (string("123") + string("456")).c_str(); // error,临时对象被摧毁了printf("p = %s\n", p);string aaa = string("123") + string("456");const char *q = aaa.c_str(); // okprintf("q = %s\n", q);}
  1. 临时对象因绑定到引用而被保留
if (0){const string &aaa2 = string("123") + string("456");printf("aaa2 = %s\n", aaa2.c_str());}
06.06.cpp
#include <cstdio>
#include <iostream>
using namespace std;struct A
{int m_i;A() { cout << "A::A()" << endl; }~A() { cout << "A::~A()" << endl; }A(const A &tmp){m_i = tmp.m_i;cout << "A::A(const A&)" << endl;}A &operator=(const A &tmp){m_i = tmp.m_i;cout << "A::operaotor=(const A&)" << endl;// printf("&tmp=%p\n", &tmp);return *this;}
};
A operator+(const A &obj1, const A &obj2)
{// cout << "A operaotor+(const A&, const A &)" << endl;A tmp; // 一次构造函数tmp.m_i = obj1.m_i + obj2.m_i;printf("&tmp=%p\n", &tmp);return tmp; // 一次拷贝构造函数+一次析构函数(编译器优化后可能无临时对象)
}int main()
{if (0){A myobj1;printf("&myobj1=%p\n", &myobj1);A myobj2;printf("&myobj2=%p\n", &myobj2);A resultobj1 = myobj1 + myobj2; // 编译器优化后直接将tmp的地址用作resultobj1的地址,节省一次复制构造+一次析构cout << "------------------------\n";printf("&resultobj1=%p\n", &resultobj1);}if (0){A myobj1;printf("&myobj1=%p\n", &myobj1);A myobj2;printf("&myobj2=%p\n", &myobj2);A resultobj2;resultobj2 = myobj1 + myobj2; // 一次构造函数+一次拷贝赋值运算符+一次析构cout << "------------------------\n";printf("&resultobj2=%p\n", &resultobj2);}if (0){A myobj1;A myobj2;myobj1 + myobj2;}if (0){A myobj1;myobj1.m_i = 1;A myobj2;myobj2.m_i = 2;printf("(myobj1 + myobj2).m_i = %d\n", (myobj1 + myobj2).m_i); // 临时对象(operator+,一次构造+一次析构,且析构在printf后执行)}if (0){A myobj1;myobj1.m_i = 1;A myobj2;myobj2.m_i = 2;if ((myobj1 + myobj1).m_i > 3 || (myobj1 + myobj2).m_i > 5) // 两个临时对象cout << "Condition established" << endl;elsecout << "Condition not established" << endl;}if (1){A myobj1;myobj1.m_i = 1;A myobj2;myobj2.m_i = 2;if ((myobj1 + myobj1).m_i > 1 || (myobj1 + myobj2).m_i > 5) // 一个临时对象cout << "Condition established" << endl;}if (0){const char *p = (string("123") + string("456")).c_str(); // error,临时对象被摧毁了printf("p = %s\n", p);string aaa = string("123") + string("456");const char *q = aaa.c_str(); // okprintf("q = %s\n", q);}if (0){const string &aaa2 = string("123") + string("456");printf("aaa2 = %s\n", aaa2.c_str());}cout << "Over!\n";return 0;
}

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

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

相关文章

一维小波包的分解与重构程序深入学习——Matlab

绘制上述图的matlab程序为&#xff1a; clear all; close all; load noisdopp; xnoisdopp; wptwpdec(x,3,db1,shannon) %返回小波包树&#xff0c;设置采用的熵为shannon plot(wpt); %% 学习目标&#xff1a;一维小波包的分解和重构深入学习 %% 获取小波树上某个节点的小…

代码随想录day21(1)二叉树:平衡二叉树(leetcode110)

题目要求&#xff1a;判断一棵树是否为平衡二叉树 思路&#xff1a;递归地比较左右子树&#xff0c;只要有一棵子树不满足条件就说明这棵树不是平衡二叉树。本题采用迭代法较为复杂。 leetcode实战&#xff1a; 代码实现&#xff1a; 递归&#xff1a; 迭代&#xff1a;

python失物招领系统-安卓-flask-django-nodejs-php

对于本失物招领 的设计来说&#xff0c; 它是应用mysql数据库、安卓等技术动态编程以及数据库进行努力学习和大量实践&#xff0c;并运用到了 建设中在整个系统的设计当中&#xff0c;具体根据网上失物招领的现状来进行开发的&#xff0c;具体根据用户需求实现网上失物招领网络…

Java 设计模式系列:行为型-状态模式

简介 状态模式&#xff08;State Pattern&#xff09;是一种行为型设计模式&#xff0c;允许一个对象在其内部状态改变时改变其行为。状态模式中类的行为是由状态决定的&#xff0c;在不同的状态下有不同的行为。 状态模式主要解决的是当控制一个对象状态的条件表达式过于复杂…

ES进程除了kill之外,有什么优雅关闭的方式吗?

问题 Linux环境中&#xff0c;Elasticsearch 8的进程除了kill之外&#xff0c;有什么优雅关闭的方式吗&#xff1f; 具体方式 在Linux环境中&#xff0c;Elasticsearch&#xff08;ES&#xff09;进程可以通过多种方式实现优雅关闭&#xff0c;这种方式允许它完成必要的清理…

[Labtools 27-1429] XML parser encountered a problem in file

平台&#xff1a;Vivado2108.3 最近在使用vivado的debug过程中发现&#xff0c;编译好工程后打开open hardware manager出现如下错误。 [Labtools 27-1429] XML parser encountered a problem in file E:/githome/xxxx/hw_1/hw.xml at line 1 : XML character encoding not su…

逐笔成交、委托数据的因子计算

3.2 逐笔成交数据的因子计算 逐笔成交数据包含的信息很丰富&#xff0c;可以构建很多中高频因子。以下为逐笔成交的样例数据&#xff1a;利用逐笔成交数据中的买卖订单号&#xff0c;可以将其合并为单笔订单成交数据&#xff0c;并从单笔订单的角度区分大小单和主买卖方向等。本…

智能合约语言(eDSL)—— 使用rust实现eDSL的原理

为理解rust变成eDSL的实现原理&#xff0c;我们需要简单了解元编程与宏的概念,元编程被描述成一种计算机程序可以将代码看待成数据的能力&#xff0c;使用元编程技术编写的程序能够像普通程序在运行时更新、替换变量那样操作更新、替换代码。宏在 Rust 语言中是一种功能&#x…

机器人离散化阻抗控制

机器人离散化阻抗控制是一种控制策略&#xff0c;它结合了阻抗控制的思想与离散化方法&#xff0c;以实现对机器人运动与外力之间动态关系的精细调节。这种控制方法旨在使机器人在与环境交互时能够表现出期望的阻抗特性&#xff0c;从而实现对接触力和位置的精确控制。 在离散…

opencv逐帧获取视频图片

背景 由于我要做一个深度学习方向的计算机视觉项目&#xff0c;需要一些数据集来进行训练&#xff0c;我便想尝试捕获视频中的图片用来标注。 注意事项 如果视频中的场景单一&#xff0c;那么使用该视频获取的数据集训练出的模型面对其他场景的泛化能力就会受到限制。为了提…

自动化单元测试 Automatic Test Generation

自动化单元测试 Automatic Test Generation Parasoft C/Ctest / wing / VectorCast https://patricegodefroid.github.io/public_psfiles/talk-issta2010.pdf 一、自动化测试技术 软件研发生命周期的各个阶段都有自动化测试技术的存在&#xff0c;并且对提升测试效率有着至…

STM32—控制蜂鸣器(定时器)

目录 1 、 电路构成及原理图 2 、编写实现代码 main.c tim_irq.c 3、代码讲解 4、烧录到开发板调试、验证代码 5、检验效果 此笔记基于朗峰 STM32F103 系列全集成开发板的记录。 1 、 电路构成及原理图 定时器中断是利用定时器的计数功能&#xff08;向上计数或向下计…

虚拟DOM是什么以及React 和Vue中有何区别

虚拟 DOM&#xff08;Virtual DOM&#xff09;是一种编程概念&#xff0c;其中 UI 的状态被保存在内存中&#xff0c;作为一个虚拟的节点树&#xff08;或者说是 JavaScript 对象&#xff09;&#xff0c;然后通过一个叫做 “对比&#xff08;diffing&#xff09;” 的过程来更…

算法---二分查找练习-3(山脉数组的顶峰索引)

山脉数组的顶峰索引 1. 题目解析2. 讲解算法原理3. 编写代码 1. 题目解析 题目地址&#xff1a;点这里 2. 讲解算法原理 初始化两个指针 left 和 right&#xff0c;分别指向数组的起始位置和结束位置。 进入循环&#xff0c;循环条件为 left < right。 在每次循环中&…

Visual Studio - 添加快捷键图标

Visual Studio - 添加快捷键图标 1. Text Editor Toolbar Options -> Add or Remove Buttons -> Customize2. Toolbars3. Commands -> Debug4. Add Command...References 1. Text Editor Toolbar Options -> Add or Remove Buttons -> Customize 2. Toolbars B…

机器学习-05-特征工程

总结 本系列是机器学习课程的系列课程&#xff0c;主要介绍机器学习中特征工程部分。 参考 机器学习之特征工程详解 特征工程&#xff08;Feature Engineering&#xff09; 特征工程是指使用专业的背景知识和技巧处理数据&#xff0c;使得特征能在机器学习算法上发生更好的…

Python数据分析与可视化笔记 三 了解数据 数据分类 集中趋势 离散程度 相关性测量 数据缺失 噪声 离群点

数据分为定性数据和定量数据。 定性数据包括两个基本层次&#xff0c;即定序(ordinal)和定义(nominal)层次。定序变量是指该变量只是对某些特性的“多少”进行排序&#xff0c;但各等级之间的差别不确定。例如评价一个事物有“好”、“一般”、“不好”三个等级&#xff0c;但各…

深入剖析Apache Kafka Partition:结构、策略与影响

引言 Apache Kafka作为一款高性能、分布式的消息系统&#xff0c;其出色的吞吐量和持久化能力在大数据领域备受青睐。而Partition作为Kafka架构中的重要基石&#xff0c;不仅决定了系统的可扩展性和并行处理能力&#xff0c;而且对消息的有序性、可用性和容错性起到关键作用。…

认识DDR3

DDR&#xff1a;双倍速率同步动态随机存储器&#xff0c;特点为掉电无法保持数据&#xff0c;时钟上升沿和下降沿都会传输数据&#xff0c;突发长度伪8&#xff0c; 它的存储方式可以通过行地址&#xff0c;列地址&#xff0c;和bank数来确定&#xff0c; DDR的容量为&#x…

Lvs+keepalived+nginx搭建高可用负载均衡集群

环境配置 master主机192.168.199.149&#xff0c;虚拟IP192.168.199.148 back备机192.168.199.150 真实服务器1 192.168.199.155 真实服务器2 192.168.199.156 关闭防火墙和selinux master配置&#xff08;149&#xff09; 添加虚拟IP ip addr add 192.168.199.148/24 …