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

news2025/1/24 2:27:06

类和对象

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

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

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

相关文章

golang中的init函数

程序的初始化和执行都起始于 main 包。如果 main 包还导入了其它的包&#xff0c;那么就会在编译时将它们依次 导入。有时一个包会被多个包同时导入&#xff0c;那么它只会被导入一次&#xff08;例如很多包可能都会用到 fmt 包&#xff0c;但 它只会被导入一次&#x…

【大数据学习 | flume】flume之常见的sink组件

Flume Sink取出Channel中的数据&#xff0c;进行相应的存储文件系统&#xff0c;数据库&#xff0c;或者提交到远程服务器。Flume也提供了各种sink的实现&#xff0c;包括HDFS sink、Logger sink、Avro sink、File Roll sink、HBase sink&#xff0c;。 ​ Flume Sink在设置存…

数学分组求偶数和

问题描述 小M面对一组从 1 到 9 的数字&#xff0c;这些数字被分成多个小组&#xff0c;并从每个小组中选择一个数字组成一个新的数。目标是使得这个新数的各位数字之和为偶数。任务是计算出有多少种不同的分组和选择方法可以达到这一目标。 numbers: 一个由多个整数字符串组…

构建安全护盾:HarmonyOS 应用的数据安全审计与日志管理实战

文章目录 前言数据安全审计与日志管理的重要性什么是数据安全审计&#xff1f;为什么需要日志管理&#xff1f; 数据安全审计与日志管理的基本原则实现数据安全审计与日志管理的技术方案1. 数据安全审计的实现2. 日志管理的实现 ArkUI 与 ArkTS 的代码示例1. 审计日志记录2. 实…

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

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

HarmonyOS 开发环境搭建

HarmonyOS&#xff08;鸿蒙操作系统&#xff09;作为一种面向全场景多设备的智能操作系统&#xff0c;正逐渐在市场上崭露头角。为了进入HarmonyOS生态&#xff0c;开发者需要搭建一个高效的开发环境。本文将详细介绍如何搭建HarmonyOS开发环境&#xff0c;特别是如何安装和配置…

基于VUE实现语音通话:边录边转发送语言消息、 播放pcm 音频

文章目录 引言I 音频协议音频格式:音频协议:II 实现协议创建ws对象初始化边录边转发送语言消息 setupPCM按下通话按钮时开始讲话,松开后停止讲话播放pcm 音频III 第三库recorderplayer调试引言 需求:电台通讯网(电台远程遥控软件-超短波)该系统通过网络、超短波终端等无线…

【提高篇】3.3 GPIO(三,工作模式详解 上)

目录 一,工作模式介绍 二,输入浮空 2.1 输入浮空简介 2.2 输入浮空特点 2.3 按键检测示例 2.4 高阻态 三,输入上拉 3.1 输入上拉简介 3.2 输入上拉的特点 3.3 按键检测示例 四,输入下拉 4.1 输入下拉简介 4.2 输入下拉特点 4.3 按键检测示例 一,工作模式介绍…

微服务瞎写

1.微服务解决的问题 1、如何发现新节点以及检查各节点的运行状态&#xff1f; 2、如何发现服务及负载均衡如何实现&#xff1f; 3、服务间如何进行消息通信&#xff1f; 4、如何对使用者暴露服务API&#xff1f; 5、如何集中管理各节点配置文件&#xff1f; 6、如何收集各…

Python Tornado框架教程:高性能Web框架的全面解析

Python Tornado框架教程&#xff1a;高性能Web框架的全面解析 引言 在现代Web开发中&#xff0c;选择合适的框架至关重要。Python的Tornado框架因其高性能和非阻塞I/O特性而备受青睐。它特别适合处理大量并发连接的应用&#xff0c;比如聊天应用、实时数据处理和WebSocket服务…

基于图像分类的对抗攻击算法研究

图像分类与对抗攻击 图像分类是计算机视觉的基础任务&#xff0c;旨在 将不同类别的图像准确归类 。随着深度学习发展&#xff0c;模型在大规模数据集上的表现已超越人类。然而&#xff0c;这一进步也引发了新的安全挑战—— 对抗攻击 。 对抗攻击通过向原始图像添加精心设计的…

【AI大模型】ELMo模型介绍:深度理解语言模型的嵌入艺术

学习目标 了解什么是ELMo.掌握ELMo的架构.掌握ELMo的预训练任务.了解ELMo的效果和成绩.了解ELMo的优缺点. 目录 &#x1f354; ELMo简介 &#x1f354; ELMo的架构 2.1 总体架构 2.2 Embedding模块 2.3 两部分的双层LSTM模块 2.4 词向量表征模块 &#x1f354; ELMo的预…

Linux Android 正点原子RK3568替换开机Logo完整教程

0.这CSDN是有BUG吗?大家注意:表示路径的2个点号全都变成3个点号啦! 接下来的后文中,应该是2个点都被CSDN变成了3个点: 1.将这两个 bmp 图片文件720x1280_8bit拷贝到内核源码目录下,替换内核源码中默认的 logo 图片。注意:此时还缺少电量显示图片 2.编译内核 make d…

【EasyExcel】复杂导出操作-自定义颜色样式等(版本3.1.x)

文章目录 前言一、自定义拦截器二、自定义操作1.自定义颜色2.合并单元格 三、复杂操作示例1.实体(使用了注解式样式)&#xff1a;2.自定义拦截器3.代码4.最终效果 前言 本文简单介绍阿里的EasyExcel的复杂导出操作&#xff0c;包括自定义样式&#xff0c;根据数据合并单元格等。…

Linux(CentOS)安装达梦数据库 dm8

CentOS版本&#xff1a;CentOS 7 达梦数据库版本&#xff1a;dm8 一、获取 dm8 安装文件 1、下载安装文件 打开达梦官网&#xff1a;https://www.dameng.com/ 下载的文件 解压后的文件 2、上传安装文件到 CentOS 使用FinalShell远程登录工具&#xff0c;并且使用 root 用户…

fastapi 调用ollama之下的sqlcoder模式进行对话操作数据库

from fastapi import FastAPI, HTTPException, Request from pydantic import BaseModel import ollama import mysql.connector from mysql.connector.cursor import MySQLCursor import jsonapp FastAPI()# 数据库连接配置 DB_CONFIG {"database": "web&quo…

如何监控Kafka消费者的性能指标?

要监控 Kafka 消费者性能指标&#xff0c;可以遵循以下最佳实践和策略&#xff1a; 关键性能指标监控&#xff1a; 消息吞吐量&#xff1a;监控消费者和生产者的吞吐量&#xff0c;以评估数据处理和消费的效率。延迟&#xff1a;监控端到端的延迟&#xff0c;例如通过比较消息产…

【LINUX相关】

一、Linux怎么进行查看日志&#xff1f; 首先得问问开发项目日志存放在哪里&#xff0c;可以使用多种命令来查看日志。常用的命令包括tail、cat、less和grep等。例如:1、使用tail命令可以实时查看日志文件的最新内容&#xff1a;tail -f log_file&#xff0c; 2、使用cat命令可…

IT运维的365天--019 用php做一个简单的文件上传工具

前情提要&#xff1a;朋友的工作室&#xff0c;有几个网站分布在不同的服务器上&#xff0c;要经常进行更新&#xff0c;之前是手动复制压缩包到各个服务器去更新&#xff08;有写了自动更新的Shell脚本&#xff09;。但还是觉得太麻烦&#xff0c;每次还要手动传输压缩包到各个…

计算机网络 (4)计算机网络体系结构

前言 计算机网络体系结构是指计算机网络层次结构模型&#xff0c;它是各层的协议以及层次之间的端口的集合。这一体系结构为计算机网络及其部件应完成的功能提供了精确定义&#xff0c;并规定了这些功能应由何种硬件或软件来实现。 一、主流模型 计算机网络体系结构存在多种模型…