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();  //  Yes
    	PVC* p = new PVC();  //  error
    	return 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构造成一个临时对象A
    	return 0;
    }
    
  • explicit
    class A
    {
    public:
    	explicit A(int i)
    	{
    		cout << "A()" << endl;
    	}
    };
    
    int main()
    {
    	//A a = 5;  // error
    	A 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();  // error
    	A* 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;  // error
	A* 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;// 2
	return 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;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

python遇到问题

1&#xff0c;BeautifulSoup lxml 解析器安装 问 1&#xff0c;BeautifulSoup lxml 解析器安装2&#xff0c;BeautifulSoup 如何引入第三方库 BeautifulSoup lxml&#xff0c;默认是导入的是python内置的解析器答1 1. 安装 Python 和 pip 确保你已经安装了 Python 和 pip。你…

async 和 await的使用

一、需求 点击按钮处理重复提交&#xff0c;想要通过disabled的方式实现。 但是点击按钮调用的方法里有ajax、跳转、弹窗等一系列逻辑操作&#xff0c;需要等方法里流程都走完&#xff0c;再把disabled设为false&#xff0c;这样下次点击按钮时就可以继续走方法里的ajax等操作…

MacOS下,如何在Safari浏览器中打开或关闭页面中的图片文字翻译功能

MacOS下&#xff0c;如何在Safari浏览器中打开或关闭页面中的图片文字翻译功能 在Mac上的Safari浏览器中&#xff0c;可以通过实况文本功能来实现图片中的文本翻译。关闭步骤具体步骤如下&#xff1a; 在浏览器地址栏&#xff0c;鼠标右击翻译按钮&#xff0c;然后点击“首选…

31.2 DOD压缩和相关的prometheus源码解读

本节重点介绍 : 时序数据时间的特点DOD压缩原理讲解dod压缩过程讲解dod压缩 prometheus源码解读 时序数据时间的特点 持续采集采集间隔固定&#xff0c;如prometheus配置job中的scrape_interval参数每隔15秒采集一次 - job_name: node_exporterhonor_timestamps: truescrape…

推荐一款好用的ios传输设备管理工具:AnyTrans for iOS

AnyTrans for iOS是一款好用的ios传输设备管理工具&#xff0c;可以方便用户对iphone、ipad、ipod中的文件进行管理操作&#xff0c;可以方便用户在电脑上进行各类文件的管理操作&#xff0c;支持联系人、视频、音频、短信、图片等文件的导入&#xff0c;软件支持双向传输和浏览…

快速利用c语言实现线性表(lineList)

线性表是数据结构中最基本和简单的一个&#xff0c;它是n的相同类型数据的有序序列&#xff0c;我们也可以用c语言中的数组来理解线性表。 一、线性表声明 我们定义一个线性表的结构体&#xff0c;内部有三个元素&#xff1a;其中elem是一个指针&#xff0c;指向线性表的头&am…

计算机毕业设计Python+CNN卷积神经网络股票预测系统 股票推荐系统 股票可视化 股票数据分析 量化交易系统 股票爬虫 股票K线图 大数据毕业设计 AI

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

QT QLineEdit失去焦点事件问题与解决

本文介绍如何获得QLineEdit的失去焦点事件和获得焦点的输入框也会触发失去焦点事件的问题&#xff01; 目录 一、QLineEdit获得失去焦点事件 1.自定义类继承自QLineEdit 2.重写 focusOutEvent 3.使用 二、失去焦点事件问题 1.问题描述 2.问题解决 三、源码分享 lineed…

vscode执行npm install报错

npm install一直提示报错 以管理员身份运行vscode&#xff0c;如果每次觉得很麻烦可以做如下修改&#xff1a;

【算法】树状数组

前言 众所周知&#xff0c;通过前缀和&#xff0c;我们可以很快的在一个很大的数组中求出区间和&#xff0c;但是如果想要去修改数组中的一个数的值&#xff0c;前缀和就无法实现。所以来学习一个新的数据结构&#xff1a;树状数组 &#xff08;文章中关于树状数组的截图来自于…

Java项目实战II基于微信小程序的私家车位共享系统(开发文档+数据库+源码)

目录 一、前言 二、技术介绍 三、系统实现 四、文档参考 五、核心代码 六、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。获取源码联系方式请查看文末 一、前言 在城市化进…

ZeroSSL HTTPS SSL证书ACMESSL申请3个月证书

目录 一、引言 二、准备工作 三、申请 SSL 证书 四、证书选型 五、ssl重要性 一、引言 目前免费 Lets Encrypt、ZeroSSL、BuyPass、Google Public CA SSL 证书&#xff0c;一般免费3-6个月。从申请难易程度分析&#xff0c;zerossl申请相对快速和简单&#xff0c;亲测速度非…

pipx安装提示找不到包

执行&#xff1a; pipx install --include-deps --force "ansible6.*"WARNING: Retrying (Retry(total4, connectNone, readNone, redirectNone, statusNone)) after connection broken by NewConnectionError(<pip._vendor.urllib3.connection.HTTPSConnection …

react + ts定义接口类型写法

接口&#xff08;未进行ts定义&#xff09; export async function UserList(params: {// keyword?: string;current?: number;pageSize?: number;},// options?: { [key: string]: any }, ) {return request<API1.UserList>(http://geek.itheima.net/v1_0/mp/artic…

Golang超详细入门教程

Golang超详细入门教程 部分图片可能加载不出来&#xff0c;所以这里我上传到了自己的个人网站上也可以查看&#xff1a;http://dahua.bloggo.chat/testimonials/490.html 一、数据类型转换 C语言中数据可以隐式转换或显示转换, 但是Go语言中数据只能显示转换格式: 数据类型(…

Cannot resolve org.apache.tomcat.embed:tomcat-embed-core:9.0.60标红解决办法

解决方法是&#xff1a; MyBatis 会扫描这个包下的所有接口&#xff0c;并将这些接口注册为 MyBatis 的 Mapper。 把这个加上后&#xff0c;问题解决&#xff01;

游戏引擎学习第九天

视频参考:https://www.bilibili.com/video/BV1ouUPYAErK/ 修改之前的方波数据&#xff0c;改播放正弦波 下面主要讲关于浮点数 1. char&#xff08;字符类型&#xff09; 大小&#xff1a;1 字节&#xff08;8 位&#xff09;表示方式&#xff1a;char 存储的是一个字符的 A…

C#设计模式(12)——享元模式(Flyweight Pattern)

前言 享元模式通过共享对象来减少内存使用和提高性能。 代码 public abstract class Flyweight {public abstract void Control(); }public class ComputerFlyweight : Flyweight {private string _operator;public ComputerFlyweight(string name){_operator name;}public o…

upload-labs通关练习

目录 环境搭建 第一关 第二关 第三关 第四关 第五关 第六关 第七关 第八关 第九关 第十关 第十一关 第十二关 第十三关 第十四关 第十五关 第十六关 第十七关 第十八关 第十九关 第二十关 总结 环境搭建 upload-labs是一个使用php语言编写的&#xff0c…

【云原生系列--Longhorn的部署】

Longhorn部署手册 1.部署longhorn longhorn架构图&#xff1a; 1.1部署环境要求 kubernetes版本要大于v1.21 每个节点都必须装open-iscsi &#xff0c;Longhorn依赖于 iscsiadm主机为 Kubernetes 提供持久卷。 apt-get install -y open-iscsiRWX 支持要求每个节点都安装 N…