C++编程技巧与规范-类和对象

类和对象

1. 静态对象的探讨与全局对象的构造顺序

静态对象的探讨

类中的静态成员变量(类类型静态成员)
  • 类中静态变量的声明与定义(类中声明类外定义
#include<iostream>
using namespace std;namespace _nmspl
{class A{public:A():m_i(5){cout << "A:A()缺省构造函数执行了" << endl;}~A(){cout << "A:~A()缺省析构函数执行了" << endl;}int m_i;};class B{public:static A m_sa; // 静态成员变量的声明};A B::m_sa; // 这是对类B静态成员变量m_sa的定义
}int main()
{_nmspl::B bobj;cout << bobj.m_sa.m_i;return 0;
}
  • 没有创建类B时
    • 类中静态成员变量即使没有被调用,也会被构造和析构 在这里插入图片描述
  • inline
    • inline关键字最初用于建议编译器尝试将一个函数体直接插入到调用该函数的地方,以减少函数调用的开销。这并不意味着编译器一定会内联这个函数,它只是对编译器的一个提示。

    • 在C++17增加新用法,

      • 内联变量
      • 在这里插入图片描述
      • 在这里插入图片描述
      • 在这里插入图片描述
    • 内联静态成员变量

      • 在这里插入图片描述
    • visual studio中改变C++标准

      • 在这里插入图片描述
        #include<iostream>
        using namespace std;namespace _nmspl
        {class A{public:A():m_i(5){cout << "A:A()缺省构造函数执行了" << endl;}~A(){cout << "A:~A()缺省析构函数执行了" << endl;}int m_i;};//class B//{//public://	static A m_sa; // 静态成员变量的声明//};//A B::m_sa; // 这是对类B静态成员变量m_sa的定义class B{public:inline static A m_sa; // 静态成员即声明又定义};
        }
        
函数中的静态对象(类类型静态对象)
  • 如果函数没有被调用过,那么这个静态对象不会被构造,即使函数被调用多次,静态对象也只会被创建依次

区别于:类中静态成员变量即使没有被调用,也会被构造和析构

全局对象的构造顺序问题

  • 全局对象的构造顺序不确定的
    • 在这里插入图片描述
  • 注意不要出现构造一个全局对象,需要另外一个全局对象,因为无法确定谁先被构造
    • 出现错误在这里插入图片描述
// class1.h
#ifndef __CLASS1_H__
#define __CLASS1_H__class Class1 {
public:Class1();~Class1();
};
#endif// class2.h
#ifndef __CLASS2_H__
#define __CLASS2_H__class Class2 {
public:Class2();~Class2();
public:int m_i;
};
#endif// class1.cpp
#include<iostream>
using namespace std;
#include"Class1.h"
#include"Class2.h"extern Class2 gclass2;
Class1::Class1() 
{cout << "调用Class2中的m_i=" << gclass2.m_i << endl;cout << "Class1:构造函数()" << endl;
}
Class1::~Class1()
{cout << "Class1:析构函数()" << endl;
}// class2.cpp
#include<iostream>
using namespace std;
#include"Class2.h"Class2::Class2():m_i(5)
{cout << "Class2:构造函数()" << endl;
}
Class2::~Class2()
{cout << "Class2:析构函数()" << endl;
}// main.cpp
#include<iostream>
using namespace std;
#include"Class1.h"
#include"Class2.h"
Class1 gclass1;
Class2 gclass2;
int main()
{return 0;
}
  • 如果需要可以使用函数进行构造返回
    • 在这里插入图片描述
// class1.h
#ifndef __CLASS1_H__
#define __CLASS1_H__class Class1 {
public:Class1();~Class1();
};
#endif// class2.h
#ifndef __CLASS2_H__
#define __CLASS2_H__class Class2 {
public:Class2();~Class2();
public:int m_i;
};
#endif// class1.cpp
#include<iostream>
using namespace std;
#include"Class1.h"
#include"Class2.h"#include"func.h"extern Class2 gclass2;
Class1::Class1() 
{cout << getClass2().m_i << endl;cout << "Class1:构造函数()" << endl;
}
Class1::~Class1()
{cout << "Class1:析构函数()" << endl;
}// class2.cpp
#include<iostream>
using namespace std;
#include"Class2.h"Class2& getClass2()
{static Class2 gclass2;   // 不要在多线程中调用return gclass2;
}
Class2::Class2():m_i(5)
{cout << "Class2:构造函数()" << endl;
}
Class2::~Class2()
{cout << "Class2:析构函数()" << endl;
}
// func.h
#ifndef __FUNC_H__
#define __FUNC_H__class Class2; // 类的前置声明
Class2& getClass2();
#endif
// main.cpp
#include<iostream>
using namespace std;
#include"Class1.h"
#include"Class2.h"
Class1 gclass1;
//Class2 gclass2;
int main()
{return 0;
}

2. 拷贝构造函数和拷贝赋值运算符

拷贝构造函数和拷贝赋值运算符的书写

#include<iostream>using namespace std;namespace _nmspl 
{class A{public:A() :m_caa(0), m_cab(0) {}//拷贝构造函数A(const A& tmpobj){m_caa = tmpobj.m_caa;m_cab = tmpobj.m_cab;}//拷贝赋值运算符重载A& operator+(const A& tmpobj){m_caa = tmpobj.m_caa;m_cab = tmpobj.m_cab;return *this;}public:int m_caa;int m_cab;};
}
int main()
{
}

对象自我赋值产生的问题

#include<iostream>
#include<cstring>
using namespace std;namespace _nmspl 
{class A{public:A() :m_caa(0), m_cab(0) {}//拷贝构造函数A(const A& tmpobj){m_caa = tmpobj.m_caa;m_cab = tmpobj.m_cab;}//拷贝赋值运算符重载A& operator+(const A& tmpobj){m_caa = tmpobj.m_caa;m_cab = tmpobj.m_cab;return *this;}public:int m_caa;int m_cab;};
}namespace _nmsp2
{class A{public:A() :m_caa(0), m_cab(0),m_cap(new char[100]) {}//拷贝构造函数A(const A& tmpobj){m_caa = tmpobj.m_caa;m_cab = tmpobj.m_cab;// 注意这个是错误的,拷贝构造函数时内存还未分配,直接new即可/*if (m_cap != NULL)  {delete[] m_cap;m_cap = NULL;}*/m_cap = new char[100];memcpy(m_cap,tmpobj.m_cap,100);}//拷贝赋值运算符重载A& operator+(const A& tmpobj){if (&tmpobj == this)return *this;// 注意这个是需要进行内存释放的,因为已经调用过构造函数了if (m_cap != NULL){delete[] m_cap;m_cap = NULL;}m_cap = new char[100];strcap(m_cap, tmpobj.m_cap);m_caa = tmpobj.m_caa;m_cab = tmpobj.m_cab;return *this;}~A(){delete[] m_cap;			}public:int m_caa;int m_cab;char* m_cap;};
}
int main()
{
}

继承关系下的拷贝构造函数和拷贝赋值运算符的书写

  • 关键点
    • 当又子类时,一定要将父类的析构函数设置为虚函数。不然在多态时,子类的析构函数不会被调用。
    • 当父类和子类同时都有拷贝构造函数和赋值运算符重载函数时,子类一定要主动去调用父类的这两个函数。不然父类的这两个函数不会被调用。
  • 在C++中,将基类(父类)的析构函数声明为虚函数是非常重要的,特别是在涉及到多态和继承的情况下。这样做的主要原因是确保当通过基类指针删除派生类对象时,能够正确地调用派生类的析构函数,从而释放派生类中可能分配的所有资源。这有助于避免内存泄漏或其他资源管理的问题。
#include <iostream>class Base {
public:~Base() { std::cout << "Base destructor\n"; }
};class Derived : public Base {int* data;
public:Derived() { data = new int[10]; }  // 分配一些资源~Derived() { delete[] data; std::cout << "Derived destructor\n"; }
};int main() {Base* basePtr = new Derived();delete basePtr;  // 这里只调用了Base的析构函数
}

在这个例子中,Base 类的析构函数不是虚函数。当我们通过 Base* 指针删除 Derived 对象时,只有 Base 的析构函数被调用。这意味着 Derived 类中的资源(即 data 数组)没有被释放,导致了内存泄漏。

  • 当子类B为空时
    #define _CRT_SECURE_NO_WARNINGS
    #include<iostream>
    #include<cstring>
    using namespace std;namespace _nmspl 
    {class A{public:A() :m_caa(0), m_cab(0) {}//拷贝构造函数A(const A& tmpobj){m_caa = tmpobj.m_caa;m_cab = tmpobj.m_cab;}//拷贝赋值运算符重载A& operator+(const A& tmpobj){m_caa = tmpobj.m_caa;m_cab = tmpobj.m_cab;return *this;}public:int m_caa;int m_cab;};
    }namespace _nmsp2
    {class A{public:A() :m_caa(0), m_cab(0),m_cap(new char[100]) {}//拷贝构造函数A(const A& tmpobj){m_caa = tmpobj.m_caa;m_cab = tmpobj.m_cab;// 注意这个是错误的,拷贝构造函数时内存还未分配,直接new即可/*if (m_cap != NULL)  {delete[] m_cap;m_cap = NULL;}*/m_cap = new char[100];memcpy(m_cap,tmpobj.m_cap,100);}//拷贝赋值运算符重载A& operator+(const A& tmpobj){if (&tmpobj == this)return *this;// 注意这个是需要进行内存释放的,因为已经调用过构造函数了if (m_cap != NULL){delete[] m_cap;m_cap = NULL;}m_cap = new char[100];memcpy(m_cap, tmpobj.m_cap,100);m_caa = tmpobj.m_caa;m_cab = tmpobj.m_cab;return *this;}virtual ~A(){delete[] m_cap;			}public:int m_caa;int m_cab;char* m_cap;};class B:public A{};
    }
    int main()
    {_nmsp2::B bobj1;bobj1.m_caa = 100;bobj1.m_cab = 200;strcpy(bobj1.m_cap,"new class");_nmsp2::B bobj2 = bobj1;  // 执行类A的拷贝构造函数bobj2 = bobj1; // 执行类A的拷贝赋值运算符
    }
    
  • 当子类B有自己的拷贝和赋值;
    #define _CRT_SECURE_NO_WARNINGS
    #include<iostream>
    #include<cstring>
    using namespace std;namespace _nmspl 
    {class A{public:A() :m_caa(0), m_cab(0) {}//拷贝构造函数A(const A& tmpobj){m_caa = tmpobj.m_caa;m_cab = tmpobj.m_cab;}//拷贝赋值运算符重载A& operator+(const A& tmpobj){m_caa = tmpobj.m_caa;m_cab = tmpobj.m_cab;return *this;}public:int m_caa;int m_cab;};
    }namespace _nmsp2
    {class A{public:A() :m_caa(0), m_cab(0),m_cap(new char[100]) {}//拷贝构造函数A(const A& tmpobj){m_caa = tmpobj.m_caa;m_cab = tmpobj.m_cab;// 注意这个是错误的,拷贝构造函数时内存还未分配,直接new即可/*if (m_cap != NULL)  {delete[] m_cap;m_cap = NULL;}*/m_cap = new char[100];memcpy(m_cap,tmpobj.m_cap,100);cout << "父类的拷贝构造函数" << endl;}//拷贝赋值运算符重载A& operator+(const A& tmpobj){if (&tmpobj == this)return *this;// 注意这个是需要进行内存释放的,因为已经调用过构造函数了if (m_cap != NULL){delete[] m_cap;m_cap = NULL;}m_cap = new char[100];memcpy(m_cap, tmpobj.m_cap,100);m_caa = tmpobj.m_caa;m_cab = tmpobj.m_cab;cout << "父类的拷贝赋值运算符" << endl;return *this;}virtual ~A(){delete[] m_cap;			}public:int m_caa;int m_cab;char* m_cap;};class B:public A{public:B() = default;B(const B& b){cout << "子类的拷贝构造函数" << endl;}void operator=(const B& b){cout << "子类的拷贝赋值运算符" << endl;//return B();}};
    }
    int main()
    {_nmsp2::B bobj1;bobj1.m_caa = 100;bobj1.m_cab = 200;strcpy(bobj1.m_cap,"new class");_nmsp2::B bobj2 = bobj1;  // 只调用子类的拷贝构造函数bobj2 = bobj1; // 只调用子类的拷贝赋值运算符
    }
    

只调用子类的函数

  • 需要程序自己主动去调用父类的拷贝构造函数与拷贝赋值运算符函数
    class B : public A{public:B() = default;B(const B& b) : A(b){cout << "子类的拷贝构造函数" << endl;}B& operator=(const B& b){A::operator=(b);cout << "子类的拷贝赋值运算符" << endl;return *this;}};

注意:调用父类的构造函数的错误写法
B(const B& b)
{
A(b);//存在二义性,创建对象或者调用函数
cout << “子类的拷贝构造函数” << endl;
}

  • 检查内存是否释放(只有在F5才起作用)
    _CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | _CRTDBG_LEAK_CHECK_DF);
    int* p = new int[10];
    

在这里插入图片描述

3. 类的public继承(is-a关系)及代码编写规则

  • 子类继承父类得方式-有三种:公有;受保护:私有继承
    • public代表得是一种is-a(是一种)的关系。通过这个子类产生的对象也一定是一个父类对象。
    • 人类(人类),人类(男人):父类表现的是一种更泛化的概念,而子类表现得是一种更特化的概念.
    • public继承关系的检验规则:能够在父类对象上做的行为也必然能在子类对象上做,每个子类对象同时也都是一个父类对象。
    • 里氏替换(利斯科夫替换)原则:任何基类出现的地方都应该可以无差别的使用子类替换.

子类遮蔽父类的普通成员函数

  • 对于public继承,不建议也不应该使用子类的普通成员函数遮蔽同名的父类的普通成员函数
  • 既然在父类中是普通成员函数,那么就代表在子类中不会有不同的行为,代表的是一种不变性
    #define _CRT_SECURE_NO_WARNINGS
    #include <iostream>
    #include <cstring>
    using namespace std;
    class Humain
    {
    public:void eat(){cout << "人类吃食物" << endl;}virtual ~Humain(){}
    };class Man :public Humain
    {
    public:void eat(){cout << "男人吃面试" << endl;}
    };
    int main()
    {Man man;man.eat(); // 调用子类的函数man.Humain::eat(); // 调用父类的成员函数
    }
    

父类的纯虚函数接口

  • 纯虚函数,让子类继承父类的纯虚函数接口。
  • 纯虚函数
    • 拥有此函数的类变成了抽象类,抽象类不能生成该类对象
    • 任何继承该类的类,必须实现这个纯虚函数。

父类的虚函数接口

  • 虚函数让子类继承父类的虚函数接口和实现,子类也可以提供实现

为纯虚函数指定实现体

  • 为纯虚函数指定实现体
    • 强制子类必须去实现该函数
    • 让一些确实不需要单独实现该接口的子类有机会直接调用父类的该实现体

类的public继承(is-a关系)综合范例

public继承关系下的代码编写规则

4. 类与类之间的组合关系和委托关系

组合关系(复合关系-Compositon)

  • 一个类中的定义中含有其他类类型变量
has-a关系(is-part-os)
is-implemented-in-terms-of关系
  • 根据…实现…

// multimap:键可以重复
// 我们现在先去实现一个键不可以重复的map
// 继承关系
//class MyMap :public multimap<T, U> {…};

template<typename T,typename U>
class MyMap
{
public:void insert(const T& key, const U & value){if (container.find(key) != container)return;container.insert(make_pair<T, U>(key, value));}size_t size(){return container.size();}
private:multimap<T, U> container;
};
组合关系的UML图

在这里插入图片描述
在这里插入图片描述

实心菱形框 - 组合关系中的Human与Info对象具有相同的生命周期

委托关系(聚合关系:Deletation)

  • 一个类中具有指向宁外一个类的指针
    • 在这里插入图片描述

空菱形框 - 生命周期不一样

5. 类的private继承探讨

  • public继承
    class Humain
    {
    public:
    };class Man :public Humain
    {
    public:
    };
    int main()
    {Man man;Humain & human = man;   // 父类引用绑定子类对象Humain* pHUman = &man;  //父类指针指向子类对象return 0;
    }
    
  • private继承:就不属于is-a关系了
    在这里插入图片描述
  • private继承是一种组合关系,是组合关系中的is-implemented-in-terms-of根据…实现…
  • 一般优先考虑使用组合关系,只有在一些比较特殊的情况和必要的情况下,比如牵扯一些保护的成员、私有成员、虚函数等 案例如下
  • 在这里插入图片描述

6. 不能被拷贝构造和拷贝赋值的类对象

  • 给构造函数写delete,编译器也不会自动生成默认的构造函数,需要程序员自己去写
    class A
    {
    public:A(const& A a) = delete;
    };
    int main()
    {A a;  //报错return 0;
    }
    

实现方案一:delete

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring>
using namespace std;class A
{
public:A(const& A a) = delete;A& operator=(const& A a)= delete;
};
int main()
{A a;return 0;
}

实现方案二:private

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring>
using namespace std;class A
{
private:A(const& A a) = delete;A& operator=(const& A a)= delete;
};
int main()
{A a;return 0;
}

但是类内还是可以访问这两个函数

实现方案三:不提供实现

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring>
using namespace std;class A
{
public:A() = default;A(const A& a) ;A& operator=(const A& a);
};
int main()
{A a;A a1(a);a1 = a;return 0;
}

调用会出现链接错误

实现法案四:继承Noncopyable成员

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring>
using namespace std;class Noncopyable
{
protected:Noncopyable() {};~Noncopyable() {};
private:Noncopyable(const Noncopyable& a) ;Noncopyable& operator=(const Noncopyable& a);
};
class A :private Noncopyable
{};
int main()
{A a;A a1(a);a1 = a;return 0;
}

7. 虚析构函数的内存泄露问题深谈

  • 一个类如果不是父类,建议此类的析构函数不要定义为虚析构函数。因为这样会因为虚函数表增一个虚函数表指针
  • 为什么会出现内存泄露问题
    #define _CRT_SECURE_NO_WARNINGS
    #include <iostream>
    #include <cstring>
    using namespace std;class ThirdPart
    {
    public:ThirdPart() = default;~ThirdPart() {cout << "~ThirdPart()被调用" << endl;};
    };
    class A :public ThirdPart
    {
    public:int* m_p;A() {m_p = new int[100];}~A(){cout << "~A()被调用" << endl;delete m_p;}
    };
    int main()
    {ThirdPart * ths = new A();delete ths;return 0;
    }
    

不要随便public继承一个类

  • 一个类不可以被继承:final
     class ThirdPart final
    {
    public:ThirdPart() = default;~ThirdPart() {cout << "~ThirdPart()被调用" << endl;};
    };
    

只有父类指针指向子类对象或者父类引用绑定到子类对象时,父类才需要虚析构函数
如果子类private或protected继承父类,那么父类指针不能指向子类对象,只能时public继承,需要父类提供虚析构函数

  • 一个函数的成员函数被声明为非public中,在main函数不能被调用

    class A 
    {	~A(){cout << "~A" << endl;}
    };
    int main()
    {A* p = new A();   delete p;  // erro:注意这里是去调用A的析构函数,而A的析构函数不能被调用return 0;
    }
    ``
  • 下面也就好理解了

    #define _CRT_SECURE_NO_WARNINGS
    #include <iostream>
    #include <cstring>
    using namespace std;class Noncopyable
    {
    protected:Noncopyable() {};~Noncopyable(){cout << "~Noncopyable" << endl;};
    private:Noncopyable(const Noncopyable& a);Noncopyable& operator=(const Noncopyable& a);
    };
    class A :public Noncopyable
    {	~A(){cout << "~Noncopyable" << endl;}
    };
    int main()
    {Noncopyable* p = new A();delete p;return 0;
    }
    

8. 类设计中的有些技巧

8.1 优先考虑为成员变量提供访问接口

class A
{
publicint m_a;
}class A
{
public :int getA(){return m_a;}
privateint m_a;
}

8.2 如何避免将父类的虚函数暴露给子类

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring>
using namespace std;class A
{
public:void fun(){func();}virtual ~A() {}
private:virtual void func(){cout << "A::func()" << endl;}
};class B:public A
{
public:B() = default;
private:virtual void func(){cout << "B::func()" << endl;}
};
int main()
{A* p = new B();p->fun(); //B::func()return 0;
}

fun函数是func虚函数的一行通道性质的代码。非虚拟接口(Nonvirtual Interface NVI)
如果能将虚函数设置为私有,则优先考虑将其设置为私有

8.3 不要在类的构造函数和析构函数中调用虚函数

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring>
using namespace std;class A
{
public:void fu(){func1_vir();}A(){func1_vir();}virtual ~A() {func2_vir();}virtual void func1_vir(){cout << "A::func1_vir()" << endl;}virtual void func2_vir(){cout << "A::func2_vir()" << endl;}
};class B:public A
{
public:B(){func1_vir();}virtual ~B(){func2_vir();}virtual void func1_vir(){cout << "B::func1_vir()" << endl;}virtual void func2_vir(){cout << "B::func2_vir()" << endl;}
};
int main()
{A* p = new B();cout << "begin_____" << endl;p->func1_vir();p->func2_vir();p->fu();cout << "end_____" << endl;delete p;return 0;
/* 输出结果
A::func1_vir()
B::func1_vir()
begin_____
B::func1_vir()
B::func2_vir()
B::func1_vir()
end_____
B::func2_vir()
A::func2_vir()*/
}

A::func1_vir() 父类中构造函数调用的虚函数是父类的虚函数
B::func1_vir() 子类中构造函数调用的虚函数是子类的虚函数
begin_____
B::func1_vir()
B::func2_vir()
B::func1_vir() 定义在父类中的非虚函数fu()中的虚函数调用的是子类的虚函数
end_____
B::func2_vir() 子类中析构函数调用的虚函数是子类的虚函数
A::func2_vir() 父类中析构函数调用的虚函数是父类的虚函数

如果在父类的构造函数中调用了一个子类的虚函数是无法做到的,因为执行到父类的构造函数时对象的子类部分还没有被构造出来
如果在父类的析构函数中调用一个子类的虚函数也是无法做到的,因为执行到父类的析构函数时对象的子类部分其实已经被销毁了
在构造函数或析构函数中,虚函数可能会失去虚函数的作用而被当作一个普通函数

8.4 析构函数的虚与非虚谈

  • 父类的析构函数不一定必须是虚函数,当父类指针指向子类或父类引用绑定子类时,父类需要写一个public修饰的析构函数,这样就可以通过父类的接口销毁子类对象,否则会导致内存泄漏
  • 用protect修饰析构函数
    • 无法创建子类对象
    • 无法让父类指针指向父类或者子类对象
  • 如果一个父类的析构函数不是虚函数,并且也不利用这个父类创建对象,也不会用到这个父类类型的指针,则应该考虑将父类的的析构函数使用protected修饰 ,增加代码安全性
  • 父类的析构函数不是虚函数,本身就暗示了不会通过父类的接口有销毁子类的对象

8.5 抽象类的模拟

  • 抽象类要求至少有一个纯虚函数
  • 抽象类:不能用来生成对象
  • 将模拟的抽象类的构造函数和拷贝构造函数都使用protected修饰
    class PVC
    {
    protected:PVC() {cout << "PVC()" << endl;}PVC(const PVC& pvc) {}
    };
    class SubPVC :public PVC
    {
    public:SubPVC(){cout << "SubPVC()" << endl;}
    };
    int main()
    {PVC* p = new SubPVC();  //  YesPVC* p = new PVC();  //  errorreturn 0;
    }
    
  • 将模拟的抽象类的析构函数设置为纯虚函数,并在类外提供实现体(大多数纯虚函数没有实现体,但是纯虚函数是个例外,为了释放资源,所以一般要有一个实现体)
    class PVC
    {
    protected:PVC() {}virtual ~PVC() = 0;
    };
    PVC::~PVC()
    {}
    class SubPVC :public PVC
    {
    public:~SubPVC() {}
    };
    
  • 将模拟的抽象类的析构函数使用protected修饰

8.6 尽量避免隐式类型转换

  • 类型转换构造函数
    class A
    {
    public:A(int i){cout << "A()" << endl;}
    };int main()
    {A a = 5.2; // 将5构造成一个临时对象Areturn 0;
    }
    
  • explicit
    class A
    {
    public:explicit A(int i){cout << "A()" << endl;}
    };int main()
    {//A a = 5;  // errorA a = A(5); return 0;
    }
    

8.7 强制类对象不可以或只可以在堆上分配内存

8.7.1 强制类对象不可以在堆上分配内存
  • 重载类中的operator newoperator delete,并使用private修饰
    
    class A
    {
    public:
    private:static void* operator new(size_t size);static void operator delete(void *p);};int main()
    {A* a = new A();  // errorA* a = new A[3];  // 但是却可以new数组return 0;
    }
    
  • 再次修改
    #define _CRT_SECURE_NO_WARNINGS
    #include <iostream>
    #include <cstring>
    using namespace std;class A
    {
    public:
    private:static void* operator new(size_t size);static void operator delete(void *p);static void* operator new[](size_t size);static void operator delete[](void* p);};int main()
    {A* a = new A[3];return 0;
    }
    
8.7.2 强制类对象只可以在堆上分配内存
  • 使用private修饰析构函数
class A
{
public:void destiry(){delete this;}
private:~A() {};  // 这样写也会导致创建在堆中的对象,不能delete。// 所以需要一个函数进行显示的调用};int main()
{A a;  // errorA* p = new A();p->destiry();return 0;
}

9. 命名空间使用的一些注意事项

  • 使用using声明命名空间的代码不要放在.h文件中 ,否则会造成命名空间污染
  • .cpp文件中,using声明语句放在include语句之后

10. 类定义的相互依赖与类的前向声明

  • 前向声明

    // a1.h
    #ifndef __A1_H__
    #define __A1_H__
    //#include"a2.h"
    class A2;
    class A1
    {
    public:A2* pm;
    };
    #endif // !__A1_H__// a2.h
    #ifndef __A2_H__
    #define __A2_H__
    //#include"a1.h"
    class A1;class A2
    {
    public:A1* pm;
    };
    #endif // !__A1_H__
  • 有些情况下需要类的完整定义而不是前向声明

    • 在类A1的定义中加入类A2类型的对象
    • 在类A1的定义中需要知道类A2对象的大小
    • 在类A1中需要调用A2的成员函数
    #ifndef __A2_H__
    #define __A2_H__
    //#include"a1.h"
    class A1;class A2
    {
    public:A1* pm;A1 pm;  // error
    };
    #endif // !__A1_H__
  • 类A1与类A2之间的直接1依赖.一般是避免这种设计。而是通过引入一个新类,让类A1和类A2都依赖这个新的类,从而打破两个类的之间的直接依赖

  • 解决1:

    • 引入中间层
    • 在这里插入图片描述
  • 解决2

    • 使用接口
    • 在这里插入图片描述

引用计数基础理论和实践

1. shared_ptr 实现及string存储简单说明

1.1 shared_ptr智能指针实现简单说明

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring>
#include<memory>
using namespace std;int main()
{shared_ptr<int> myp(new int(5));cout << "icount = " << myp.use_count() << endl;  // 1{shared_ptr<int> myp1(myp);cout << "icount = " << myp.use_count() << endl;// 2}shared_ptr<int> myp1(myp);cout << "icount = " << myp.use_count() << endl;// 2return 0;
}

在这里插入图片描述

1.2 string类型字符串存储方式的简单说明

  • string类型字符串存储方式的简单说明

  • 贪婪拷贝

  • 写时复制

  • 短字符优化

  • 在VS2022中(贪婪拷贝)

    int main()
    {std::string str1("123");std::string str2 = str1;printf("str1的地址:%p\n", str1.c_str());printf("str2的地址:%p\n", str2.c_str());/*
    str1的地址:0000000C7398F920
    str2的地址:0000000C7398F960*/return 0;
    }
    

    在这里插入图片描述

2. 通过copy-on-write方式实现的mystring类

在这里插入图片描述
在这里插入图片描述

2.1 骨架与计数设计

2.2 构造函数

2.3 拷贝构造函数

2.4 析构函数

2.5 拷贝赋值运算符

2.6 外部加锁,内部加锁,写时复制

  • 外部加锁:调用者负责,用调用者决定跨线程使用共享对象时的加锁时机
  • 内部加锁:对象将所有对自己的访问串行化,通过每个成员函数加锁的方法来实现,这样就不会在多线程中共享该对象时进行外部加锁了。

2.7 通过指针修改mystring所指字符串的内容


#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include<memory>class MyString
{
public:MyString(const char* tmp=""):pvalue(new stringvalue(tmp)){//point = tmp;}MyString(const MyString& tmp) :pvalue(tmp.pvalue)  // 拷贝构造函数{pvalue->refcount++;}MyString& operator=(const MyString& tmp)  // 拷贝赋值运算符重载{/*	if (&tmp == this)return *this;delete[] point;point = new char[strlen(tmp.point) + 1];strcpy(point,tmp.point);return *this;*/if (&tmp == this)return *this;//delete[] pvalue->point;--pvalue->refcount;//自己所指的引用计数减一if (pvalue->refcount == 0)delete pvalue; //把自己所指向的pvalue删除pvalue = tmp.pvalue;pvalue->refcount++;return *this;}//const char& operator[](int idx)const   // 非const 可以与const版本共存,但是都存在时都会调用非const版本的//{//	return pvalue->point[idx];//}char& operator[](int idx)  // const []{if (pvalue->refcount > 1){--pvalue->refcount;pvalue = new stringvalue(pvalue->point); // 写时复制}return pvalue->point[idx];}~MyString(){pvalue->refcount--;if (pvalue->refcount == 0)delete pvalue;}
private://char* point;struct stringvalue{size_t refcount; // 引用计数char* point;  stringvalue(const char* tmpstr){point = new char[strlen(tmpstr) + 1];strcpy(point,tmpstr);}~stringvalue(){delete[] point;}};
private:stringvalue* pvalue;
};
int main()
{return 0;
}

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

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

相关文章

如何在 Ubuntu 上安装 RStudio IDE(R语言集成开发环境) ?

RStudio 是一个功能强大的 R 语言集成开发环境(IDE)&#xff0c;R 是一种主要用于统计计算和数据分析的编程语言。任何从事数据科学项目或任何其他涉及 R 的类似任务的人&#xff0c;RStudio 都可以使您的工作更轻松。 本指南将引导您完成在 Ubuntu 系统上安装 RStudio 的过程…

高中数学:概率-相关运算性质

文章目录 一、概率定义二、运算性质三、事件相互独立四、频率与概率五、练习 一、概率定义 二、运算性质 基本性质 互斥事件的性质 对立事件性质 包含事件的性质 有交集但不包含的事件性质 三、事件相互独立 注意&#xff1a; 四、频率与概率 五、练习

VM安装Ubuntu详细配置

1、第一步修改阿里源&#xff1a;打开软件 与更新&#xff0c;更改下载自为&#xff1a;http://mirrors.aliyun.co/ubuntu 2、安装open-vm-tools: 安装两个文件&#xff1a;sudo api install open-vm-tools open-vm-tools-desktop 3、安装搜狗输入法&#xff1a;点击下载 a、…

乒乓球筐(多组输入模板)

乒乓球筐 import java.util.Scanner;// 注意类名必须为 Main, 不要有任何 package xxx 信息 public class Main {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNext()) {int[] hash new i…

AI 写作(五)核心技术之文本摘要:分类与应用(5/10)

一、文本摘要&#xff1a;AI 写作的关键技术 文本摘要在 AI 写作中扮演着至关重要的角色。在当今信息爆炸的时代&#xff0c;人们每天都被大量的文本信息所包围&#xff0c;如何快速有效地获取关键信息成为了一个迫切的需求。文本摘要技术正是为了解决这个问题而诞生的&#x…

TCP 三次握手意义及为什么是三次握手

✨✨✨励志成为超级技术宅 ✨✨✨ TCP的三次握手在笔试和面试中经常考察&#xff0c;非常重要&#xff0c;那么大家有没有思考过为什么是三次握手&#xff0c;俩次握手行不行呢&#xff1f;四次握手行不行呢&#xff1f;如果大家有疑问或者不是很理解&#xff0c;那么这篇博客…

初识算法 · 位运算(2)

目录 前言&#xff1a; 判定字符是否唯一 丢失的数字 比特位计数 只出现一次的数字III 前言&#xff1a; ​本文的主题是位运算&#xff0c;通过四道题目讲解&#xff0c;一道是判断字符是否唯一&#xff0c;一道是只出现一次的数字III&#xff0c;一道是比特位计数&…

Unity Assembly Definition Assembly Definition Reference

文章目录 1.Unity 预定义程序集2.Assembly definition3. Assembly definitions相关实验 1.Unity 预定义程序集 Unity 有4个预定义程序集&#xff1a; 阶段程序集名脚本文件1Assembly-CSharp-firstpassStandard Assets, Pro Standard Assets和Plugins文件夹下面的运行时脚本2A…

【征稿倒计时!华南理工大学主办 | IEEE出版 | EI检索稳定】2024智能机器人与自动控制国际学术会议 (IRAC 2024)

#华南理工大学主办&#xff01;#IEEE出版&#xff01;EI稳定检索&#xff01;#组委阵容强大&#xff01;IEEE Fellow、国家杰青等学术大咖领衔出席&#xff01;#会议设置“优秀论文”“优秀青年学者报告”“优秀海报”等评优奖项 2024智能机器人与自动控制国际学术会议 &#…

[CKS] Create/Read/Mount a Secret in K8S

最近准备花一周的时间准备CKS考试&#xff0c;在准备考试中发现有一个题目关于读取、创建以及挂载secret的题目。 ​ 专栏其他文章: [CKS] Create/Read/Mount a Secret in K8S-CSDN博客[CKS] Audit Log Policy-CSDN博客 -[CKS] 利用falco进行容器日志捕捉和安全监控-CSDN博客[C…

5个非LLM软件趋势

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

社会工程骗局席卷金融机构

2024 年北美金融机构收到的社交工程诈骗报告数量比一年前增加了 10 倍。数据显示&#xff0c;诈骗现在占所有数字银行欺诈的 23%。 深度伪造和 GenAI 诈骗的危险日益增加 BioCatch 在其 2024 年北美数字银行欺诈趋势报告中公布了这些发现&#xff0c;该报告还详细说明了报告的…

读数据质量管理:数据可靠性与数据质量问题解决之道03数据目录

1. 同步数据 1.1. 不同的数据仓库和数据湖通过数据集成层来进行桥接 1.2. AWS Glue、Fivetran和Matillion等数据集成工具从不同来源收集数据&#xff0c;统一这些数据&#xff0c;并将其转换为上游来源 1.3. 数据集成的一个典型用例是收集数据湖的数据并以结构化格式将其加载…

【数据库】数据库迁移的注意事项有哪些?

数据库迁移是一个复杂且关键的过程&#xff0c;需要谨慎处理以确保数据的完整性和应用程序的正常运行。以下是一些数据库迁移时需要注意的事项&#xff1a; 1. 充分的前期准备 1.1 评估迁移需求 明确目标&#xff1a;确定迁移的具体目标&#xff0c;例如添加新字段、修改现…

LabVIEW开发相机与显微镜自动对焦功能

自动对焦是显微成像系统中的关键功能&#xff0c;通常由显微镜的电动调焦模块或特定的镜头系统提供&#xff0c;而工业相机则主要用于高分辨率图像的采集&#xff0c;不具备独立的自动对焦功能。以下是自动对焦的工作原理、实现方式及实际应用案例。 1. 自动对焦的工作原理 &a…

ReactPress与WordPress:两大开源发布平台的对比与选择

ReactPress与WordPress&#xff1a;两大开源发布平台的对比与选择 在当今数字化时代&#xff0c;内容管理系统&#xff08;CMS&#xff09;已成为各类网站和应用的核心组成部分。两款备受欢迎的开源发布平台——ReactPress和WordPress&#xff0c;各自拥有独特的优势和特点&am…

京东商品详情,Python爬虫的“闪电战”

在这个数字化的时代&#xff0c;我们每天都在和数据打交道&#xff0c;尤其是电商数据。想象一下&#xff0c;你是一名侦探&#xff0c;需要快速获取京东上某个商品的详细信息&#xff0c;但是没有超能力&#xff0c;怎么办&#xff1f;别担心&#xff0c;Python爬虫来帮忙&…

np.zeros_like奇怪的bug

import numpy as np aa np.array([[1,2,3],[2,3,3]]) cc np.random.randn(2,3) print(aa) print(cc)bb np.zeros_like(aa) print(bb)for i in range(bb.shape[0]):for j in range(bb.shape[1]):bb[i,j] cc[i,j]print(bb)结果如下 这里发现这个bb的结果是没有赋值的 正确做…

【时间之外】IT人求职和创业应知【34】-人和机器人,机器人更可靠

目录 新闻一&#xff1a;人形机器人产业持续高速增长&#xff0c;2026年中国市场规模将突破200亿元 新闻二&#xff1a;AI技术驱动设备厂商格局变化&#xff0c;部分厂商市占率快速提升 新闻三&#xff1a;华为与江淮汽车携手打造超高端品牌“尊界”&#xff0c;计划于明年春…

连接实验室服务器并创建虚拟环境,从本地上传文件到linux服务器,使用requirement.txt安装环境需要的依赖的方法及下载缓慢的解决方法(Linux)

文章目录 一、连接实验室服务器并创建虚拟环境二、从本地上传文件到linux服务器三、使用requirement.txt安装环境需要的依赖的方法及下载缓慢的解决方法(Linux)四、查看虚拟环境中安装包位置五、Linux scp命令复制文件报错&#xff1a; not a regular file六、pycharm远程ssh连…