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

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

http://www.kler.cn/a/396570.html

相关文章:

  • awk(常用)
  • 插入排序——希尔排序
  • 一种时间戳对齐的方法(离线)
  • 直接映射缓存配置
  • 使用 Prompt API 与您的对象聊天
  • Android setTheme设置透明主题无效
  • conda 和 pip 的比较
  • 嵌入式面试题练习 - 2024/11/15
  • NVR小程序接入平台/设备EasyNVR多个NVR同时管理设备接入:海康NVR 3.0提示不在线如何处理?
  • C++- 基于多设计模式下的同步异步日志系统
  • 力扣 LeetCode 150. 逆波兰表达式求值(Day5:栈与队列)
  • 第 6 章 - Go 语言 运算符
  • MacOS下,如何在Safari浏览器中打开或关闭页面中的图片文字翻译功能
  • 【Python爬虫实战】轻量级爬虫利器:DrissionPage之SessionPage与WebPage模块详解
  • c++中,头文件包含iostream.h和`<iostream>`的差别
  • 【Flink】-- flink新版本发布:v2.0-preview1
  • Ubuntu24.04 network:0 unclaimed wireless adapter no found
  • DAY110代码审计-PHP框架开发篇ThinkPHP版本缺陷不安全写法路由访问利用链
  • 鸿蒙next 应用重启方案
  • fast-api后端 + fetch 前端流式文字响应
  • Spring Cloud 使用 Nacos 注册中心
  • 从基础到进阶,Dockerfile 如何使用环境变量
  • stm32在linux环境下的开发与调试
  • nacos-operator在k8s集群上部署nacos-server2.4.3版本踩坑实录
  • 数据结构 -- 二叉搜索树
  • 十一:HTTP 状态码详解:解读每一个响应背后的意义