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

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;
}

相关文章:

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

类和对象 1. 静态对象的探讨与全局对象的构造顺序 静态对象的探讨 类中的静态成员变量(类类型静态成员) 类中静态变量的声明与定义&#xff08;类中声明类外定义&#xff09; #include<iostream> using namespace std;namespace _nmspl {class A{public:A():m_i(5){…...

AutoHotKey自动热键AHK-正则表达式

在这个软件的操作中,基本都是需要即时的解决一些问题,所以对字符串的操作是比较多的,所以正则的使用还是比较重要的,接下来我们用一个例子来了解正则表达式的使用 str "7654321" RegExMatch(str, "65(43)(21)", SubPat)str ( str %str% SubPat %SubPa…...

【3D Slicer】的小白入门使用指南四

开源解剖影像浏览工具Open Anatomy Browser使用及介绍 和3D slicer米有太大关系,该工具是网页版影像数据的浏览工具(可以简单理解为网页版的3D slicer) 介绍 ● 开放解剖(OA)浏览器是由神经影像分析中心开发的,基于网络浏览器技术构建的图谱查看器。 ● OA浏览器将解剖模…...

flink同步mysql数据表到pg库

1.关闭防火墙和selinux systemctl stop firewalld systemctl disable firewalld systemctl status firewalldvi /etc/selinux/config 修改为disabled2.安装java8 yum list java-1.8* yum install java-1.8.0-openjdk* -yjava -version3.下载和部署postgresql 下载地址&#…...

AndroidStudio-常用布局

一、线性布局LinearLayout 线性布局内部的各视图有两种排列方式: 1.orientation属性值为horizontal时&#xff0c;内部视图在水平方向从左往右排列。 2.orientation属性值为vertical时&#xff0c;内部视图在垂直方向从上往下排列。 如果不指定orientation属性&#xff0c;…...

Vue全栈开发旅游网项目(10)-用户管理后端接口开发

1.异步用户登录\登出接口开发 1.设计公共响应数据类型 文件地址&#xff1a;utils/response404.py from django.http import JsonResponseclass BadRequestJsonResponse(JsonResponse):status_code 400def __init__(self, err_list, *args, **kwargs):data {"error_c…...

[Android]查找java类中声明为native方法的具体实现方法

在android代码中&#xff0c;经常可以看到native方法&#xff0c;需要查看其对应的C方法&#xff0c;这些方法是一一对应的&#xff0c;对应关系是在jni注册里关联起来的。 比较直观的是这样的例子&#xff0c; Parcel.java //Java层的方法里调用了native方法nativeWriteInt…...

Exploring Defeasible Reasoning in Large Language Models: A Chain-of-Thought A

文章目录 题目摘要简介准备工作数据集生成方法实验结论 题目 探索大型语言模型中的可废止推理&#xff1a;思路链 论文地址&#xff1a;http://collegepublications.co.uk/downloads/LNGAI00004.pdf#page136 摘要 许多大型语言模型 (LLM) 经过大量高质量数据语料库的训练&…...

uniapp在app模式下组件传值

在 UniApp 编译成 App 后&#xff0c;传递参数可以通过多种方式实现&#xff0c;常见的方式有以下几种&#xff1a; 1. 通过 URL 参数传递&#xff08;适用于 WebView&#xff09; 如果你的 App 页面通过 WebView 渲染&#xff0c;可以像在 Web 端一样通过 URL 传递参数。例如…...

Docker解决暴露2375端口引发的安全漏洞

docker的暴露api端口2375&#xff0c;没有任何安全防护&#xff0c;我们通过linux系统防火墙&#xff08;iptables&#xff09;来进行ip访问限制 # 查看iptables所有规则 iptables -L -nv # 只允许某个ip访问2375端口 iptables -I INPUT -s 127.0.0.1 -p tcp --dport 2375 -j A…...

HTML5+CSS前端开发【保姆级教学】+新闻文章初体验

Hello&#xff0c;各位编程猿们&#xff01;上一篇文章介绍了前端以及软件的安装&#xff0c;这一篇我们要继续讲解页面更多知识点&#xff0c;教大家做一篇新闻题材的文章 新闻文章 当我们点开浏览器经常看到各种各样的文章&#xff0c;今天我们就来看看大家最喜欢关注的体育…...

『VUE』26. props实现子组件传递数据给父组件(详细图文注释)

目录 本节内容示例代码总结 欢迎关注 『VUE』 专栏&#xff0c;持续更新中 欢迎关注 『VUE』 专栏&#xff0c;持续更新中 本节内容 父组件传子组件–props 子组件传父组件–自定义事件 本节讲子组件传父组件–通过props里的方法传递,就是父亲写了一个函数,给子组件调用,然后…...

RHCE-DNS域名解析服务器

一、DNS简介 DNS &#xff08; Domain Name System &#xff09;是互联网上的一项服务&#xff0c;它作为将域名和 IP 地址相互映射的一个分布式 数据库&#xff0c;能够使人更方便的访问互联网。 DNS 系统使用的是网络的查询&#xff0c;那么自然需要有监听的 port 。 DNS 使…...

移民统计年鉴(1996-2021年)

年鉴中包含了以下几个方面的数据&#xff1a; 移民数量&#xff1a;记录了每年全球移民的总数&#xff0c;以及不同国家和地区的移民流入和流出情况。 移民类型&#xff1a;区分了经济移民、难民、家庭团聚等不同类型的移民。 移民原因&#xff1a;分析了推动移民的各种因素&…...

MFC1(note)

引言 在学习SDK后我们发现&#xff0c;写消息好麻烦&#xff0c;处理消息更麻烦 处理消息效率低发送消息效率低 所以把SDK中这些消息全部封装好 MFC封装了windows 的大部分API 这里说一下QT架构跨平台 MFC用得如何取决于你SDK的水平 创建 如果打开没有MFC 一般勾选以下…...

1.1 关于游戏编程

1.1.1、游戏中客户端和服务器的交互 游戏通常采用客户端-服务器模式‌。在这种模式下&#xff0c;服务器负责处理游戏的核心逻辑、数据存储和玩家间的交互&#xff0c;而客户端则负责呈现游戏画面、接收玩家输入并与服务器通信‌。 客户端和服务器的作用和功能 ‌客户端‌&a…...

光流法与直接法在SLAM中的应用

本文总结视觉SLAM中常用的光流法与直接法 1、Lucas-Kanade光流法 相机所拍摄到的图像随相机视角的变化而变化&#xff0c;这种变化也可以理解为图像中像素的反向移动。“光流”&#xff08;Optical Flow&#xff09;是指通过分析连续图像帧来估计场景中像素或特征点的运动的技…...

C++模板特化实战:在使用开源库boost::geometry::index::rtree时,用特化来让其支持自己的数据类型

用自己定义的数据结构作为rtree的key。 // rTree的key struct OverlapKey {using BDPoint boost::geometry::model::point<double, 3, boost::geometry::cs::cartesian>; //双精度的点using MyRTree boost::geometry::index::rtree<OverlapKey, boost::geometry::in…...

让空间计算触手可及,VR手套何以点石成金?

引言 如何让一位母亲与她去世的小女儿“重逢”&#xff1f;韩国MBC电视台《I Met You》节目实现了一个“不可能”心愿。 在空旷的绿幕中&#xff0c;母亲Jang Ji-sung透过VR头显&#xff0c;看到了三年前因白血病去世的女儿Nayeon。当她伸出双手&#xff0c;居然能摸到女儿的…...

穿越数据迷宫:C++哈希表的奇幻旅程

文章目录 前言&#x1f4d4;一、unordered系列关联式容器&#x1f4d5;1.1 unordered 容器概述&#x1f4d5;1.2 哈希表在 unordered 容器中的实现原理&#x1f4d5;1.3 unordered 容器的特点 &#x1f4d4;二、unordered_set 和 unordered_map 的基本操作&#x1f4d5;2.1 un…...

SMT32 智能环境监测系统 嵌入式初学者课堂小组作业

一、应用知识 1&#xff0c;开发语言&#xff1a;C语言 2&#xff0c;开发工具&#xff1a;Keil uVision5、Setup_JLinkARM_V415e 3&#xff0c;开发平台&#xff1a;XCOM V2.0 4&#xff0c;开发知识&#xff1a;温湿度传感DHT11、STM32F4xx中文参考手册 5&#xff0c;文…...

20241114给荣品PRO-RK3566开发板刷Rockchip原厂的Android13下适配RJ45以太网卡

20241114给荣品PRO-RK3566开发板刷Rockchip原厂的Android13下适配RJ45以太网卡 2024/11/14 15:44 缘起&#xff1a;使用EVB2的方案&#xff0c;RJ45加进去怎么也不通。 实在没有办法&#xff0c;只能将荣品的SDK&#xff1a;rk-android13-20240713.tgz 解压缩&#xff0c;编译之…...

JVM这个工具的使用方法

JVM&#xff08;Java虚拟机&#xff09;是Java程序运行的基础环境&#xff0c;它提供了内存管理、线程管理和性能监控等功能。吃透JVM诊断方法&#xff0c;可以帮助开发者更有效地解决Java应用在运行时遇到的问题。以下是一些常见的JVM诊断方法&#xff1a; 使用JConsole: JCon…...

创建型设计模式与面向接口编程

创建型设计模式&#xff08;Creational Patterns&#xff09;的主要目的之一就是帮助实现面向接口编程&#xff0c;避免直接创建实现类的实例。通过这些模式&#xff0c;可以将对象的创建过程封装起来&#xff0c;使得客户端代码不需要知道具体的实现类&#xff0c;从而提高代码…...

算法每日双题精讲——滑动窗口(长度最小的子数组,无重复字符的最长子串)

&#x1f31f;快来参与讨论&#x1f4ac;&#xff0c;点赞&#x1f44d;、收藏⭐、分享&#x1f4e4;&#xff0c;共创活力社区。 &#x1f31f; 别再犹豫了&#xff01;快来订阅我们的算法每日双题精讲专栏&#xff0c;一起踏上算法学习的精彩之旅吧&#xff01;&#x1f4aa;…...

1.7 JS性能优化

从输入url到页面加载完成都做了些什么 输入 URL - 资源定位符 http://www.zhaowa.com - http 协议 域名解析 https://www.zhaowa.com > ip 1. 切HOST&#xff1f; > 浏览器缓存映射、系统、路由、运营商、根服务器 2. 实际的静态文件存放&#xff1f; 大流量 > 多个…...

STL - vector的使用和模拟实现

目录 一&#xff1a;vector的构造函数 二&#xff1a;vector的迭代器 三vector的空间增长问题 四&#xff1a;vector 增删查改接口 五&#xff1a;vector的模拟实现 &#xff08;一&#xff09;一些简单接口的实现&#xff1a; &#xff08;二&#xff09;一些复杂接口的…...

《鸿蒙生态:开发者的机遇与挑战》

一、引言 在当今科技飞速发展的时代&#xff0c;操作系统作为连接硬件与软件的核心枢纽&#xff0c;其重要性不言而喻。鸿蒙系统的出现&#xff0c;为开发者带来了新的机遇与挑战。本文将从开发者的角度出发&#xff0c;阐述对鸿蒙生态的认知和了解&#xff0c;分析鸿蒙生态的…...

【C++融会贯通】二叉树进阶

目录 一、内容说明 二、二叉搜索树 2.1 二叉搜索树概念 2.2 二叉搜索树操作 2.2.1 二叉搜索树的查找 2.2.2 二叉搜索树的插入 2.2.3 二叉搜索树的删除 2.3 二叉搜索树的实现 2.3.1 二叉搜索树的节点设置 2.3.2 二叉搜索树的查找函数 2.3.2.1 非递归实现 2.3.2.2 递…...

使用python-Spark使用的场景案例具体代码分析

使用场景 1. 数据批处理 • 日志分析&#xff1a;互联网公司每天会产生海量的服务器日志&#xff0c;如访问日志、应用程序日志等。Spark可以高效地读取这些日志文件&#xff0c;对数据进行清洗&#xff08;例如去除无效记录、解析日志格式&#xff09;、转换&#xff08;例如…...