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; 四、频率与概率 五、练习

Elman 神经网络算法详解

Elman 神经网络算法详解 一、引言 Elman 神经网络作为一种经典的递归神经网络&#xff08;RNN&#xff09;&#xff0c;在处理动态系统和时间序列数据方面具有独特的优势。它通过特殊的结构设计&#xff0c;能够有效地捕捉数据中的时间依赖关系&#xff0c;在语音识别、自然语…

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…

沃德校园跑腿助手:助力校园团队数字化管理

随着互联网技术的不断发展&#xff0c;校园生活也逐渐进入了智能化、数字化的新时代。从日常学习到生活服务&#xff0c;线上化已成为学生和校园团队的普遍需求。而沃德校园跑腿助手&#xff0c;正是基于FastAdminThinkPHPUniapp技术栈&#xff0c;为校园团队提供的一款高效、便…

深入探讨 MySQL 配置与优化:从零到生产环境的最佳实践20241112

深入探讨 MySQL 配置与优化&#xff1a;从零到生产环境的最佳实践 引言 MySQL 是全球最受欢迎的开源关系型数据库之一&#xff0c;其高性能、灵活性和广泛的社区支持使其成为无数开发者的首选。然而&#xff0c;部署一台高效、稳定的 MySQL 实例并非易事。本文将结合一个实际…

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

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

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

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

【JS】异步编程

我是目录 Promisenew Promise(..) 构造器如何确定promise的类型如何信任Promise链式流Promise的局限性async与awaitasyncawaitawait 等到之后,做了一件什么事情?异步编程的四种方式: 1,回调函数:先执行程序的主要逻辑,将耗时的操作推迟执行。简单、易理解,但不利于代码维…

软件设计师-软件工程

软件生存周期 可行性分析&#xff0c;可行性分析报告和项目开发计划需求分析&#xff0c;软件需求说明书&#xff0c;确定软件的综合要求&#xff1a;系统界面&#xff0c;系统功能&#xff0c;系统性能&#xff0c;安全性&#xff0c;保密性和可靠性方面的要求&#xff0c;系…

算法——移除链表元素(leetcode203)

移除链表元素首先我们要理解链表这种数据结构&#xff0c;它跟数组不一样它可以分布在内存中的各个位置链表节点由值和指针组成&#xff0c;指针指向下一个链表节点。 移除链表元素简而言之就是将符合条件的链表节点给移除出去我们不能直接将其移除而是通过将被移除节点的上一…

数字字符串格式化

小M在工作时遇到了一个问题&#xff0c;他需要将用户输入的不带千分位逗号的数字字符串转换为带千分位逗号的格式&#xff0c;并且保留小数部分。小M还发现&#xff0c;有时候输入的数字字符串前面会有无用的 0&#xff0c;这些也需要精简掉。请你帮助小M编写程序&#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…

C++设计模式精选面试题及参考答案

目录 什么是单例模式?它的应用场景是什么?如何保证单例模式线程安全? 什么是工厂方法模式?如何与简单工厂模式进行比较? 抽象工厂模式和工厂方法模式有什么区别?请给出实际应用场景。 什么是建造者模式?它和工厂模式有什么不同? 在什么情况下使用单例模式?如何在…

ios swift开发--ios远程推送通知配置

远程推送通知&#xff08;Push Notifications&#xff09;在 iOS 平台上是免费提供的&#xff0c;但需要一些准备工作。以下是开通和使用远程推送通知的基本步骤&#xff1a; 开通远程推送通知 注册 Apple Developer Program&#xff1a; 访问 Apple Developer 并注册一个开发…

关于QUERY_ALL_PACKAGES权限导致Google下架apk

谷歌商店被下架,原因是第三方使用了 QUERY_ALL_PACKAGES 权限&#xff1b; Google在高版本上限制了此权限的使用。当然&#xff0c;并不是 QUERY_ALL_PACKAGES 这个权限没有了&#xff0c;而是被列为敏感权限&#xff0c;必须有充分的理由说明&#xff0c;才允许上架 GP&#…

【征稿倒计时!华南理工大学主办 | 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…