(12)C++联编、异常、单例、内部类、新式类型转换

联编

1.概念

将模块(每个文件单独的编译,就是单独的模块)或者函数合并到一起生成可执行代码的处理过程,(函数调用),按照联编所进行的阶段不同,可分为两种联编方法:静态联编和动态联编

理解

就比如说函数:

//函数实现
void print()
{
	cout << "联编" << endl;
}
//函数调用
print();

两种联编区分

(1)联编就是将函数实现和函数调用联系在一起,关联在一起。程序走到print()处,就知道要调用函数实现那一块,这就叫做联编。
(2)编译阶段不同:
a.编译阶段:编译阶段执行的叫做静态联编
b.运行阶段:运行阶段执行的叫做动态联编
(3)两种联编的区别:
a.执行阶段不同
b.静态联编在编译时关联好,动态联编在C++中针对多态,在运行时才将函数实现和函数调用关联好。但不是所有情况都需要动态联编,只是特殊的情况需要动态联编:

#include<iostream>
using namespace std;

class Cfarther
{
public:
	void print()
	{
		cout << "farther" << endl;
	}
};
class Cson :public Cfarther
{
public:
	void print()
	{
		cout << "son" << endl;
	}
};
int main()
{
	Cfarther *farther ;
	int b;
	cin >> b;
	switch (b)
	{
	case 1:farther = new Cfarther; break;
	case 2:farther = new Cson; break;
	}
	farther->print();
	system("pause");
	return 0;
}

像这样,只有在运行farther->print();的时候才知道调用了哪一个的对象,知道之后才将函数实现和函数调用联编,这就是动态联编。

单例模式

概念

最基本的设计模式

关键点

(1)一个类只能创建一个对象
(2)如果构造函数是私有的,类不能创建对象。
private的构造不能实例化对象(即创建对象)

单例创建步骤(4步走)

1.private不能创建对象,让构造成员函数成为私有的

class Cfarther
{
private:
	Cfarther()
	{
		cout << "单例" << endl;
	}
};

2.既然私有的构造成员不能从外部调用,那就从内部调用。
可以通过静态成员函数(因为非静态成员函数要在类外用对象调用,静态函数可以用对象调用,也可以用类名作用域调用)申请对象空间,并返回地址来调用成员。相当于在类内调用Cfarther

#include<iostream>
using namespace std;
class Cfarther
{
private:
	Cfarther()
	{
		cout << "单例" << endl;
	}
public:
	static Cfarther*farther()  //返回一个Cfarther*型的返回值
	{
		return (new Cfarther);
	}
};
int main()
{
//创建对象的形式   相当于连接了通道
	Cfarther *far = Cfarther::farther();
	system("pause");
	return 0;
}

第二步这种情况,也可以创建无数个对象。
在创建指针的时候都可以调用它,这就需要第三步

	Cfarther *far = Cfarther::farther();
	Cfarther *far1 = Cfarther::farther();

3.定义静态标记,记录对象个数,并控制

由于不能创建对象,所以类内的标记也是静态的
不过静态标记要在类外初始化

(1)

public:
	static int flog;  //定义静态标记
	static Cfarther*farther()  //返回一个Cfarther*型的返回值
	{
		return (new Cfarther);
	}
};
int Cfarther::flog = 1; //静态成员,类外初始化

(2)标记判断,flog == 1进到函数里,为0不进,返回一个NULL

	static Cfarther*farther()  //返回一个Cfarther*型的返回值
	{
		//标记判断
		if (1 == flog)
		{
		//执行完后flog就置成0,就不再执行对象创建了
			flog == 0; 			
			return (new Cfarther);	
		}
		else
		{
			return NULL; //这样就只执行一次了
		}
	}
//主函数调用

```cpp
	Cfarther *far = Cfarther::farther();
	//释放空间
	delete far;
	Cfarther *far1 = Cfarther::farther();

理论上,far释放后,far1就能创建,但事实上还是失败了,原因是,标记是静态的标记,所以要进行改变才能让detele far后标记置成1

那么该如何改变呢?
这就用到析构函数了!
原来析构可以这么用!!长见识了!
(4)析构函数,将标记清空,以达到重复申请对象的目的

这样的意思就是,你可以重复申请,但每次只能申请一个

	~Cfarther()
	{
		1 == flog;
	}
//主函数调用
	Cfarther *far = Cfarther::farther();
	delete far;
	Cfarther *far1 = Cfarther::farther();
	delete far1;

这样就能成功创建far1了
总代码如下:

#include<iostream>
using namespace std;
class Cfarther
{
private:
	Cfarther()
	{
		cout << "单例" << endl;
	}
public:
	static int flog; //定义标记
	static Cfarther*farther()  //返回一个Cfarther*型的返回值
	{
		//标记判断
		if (1 == flog)
		{
			flog == 0;  //标记置成0
			return (new Cfarther);	//返回Cfarther堆区空间
		}
		else
		{
			return NULL;
		}
	}
	~Cfarther() //利用结束调用的性质
	{
		1 == flog;
	}
};
int Cfarther::flog = 1;
int main()
{
 //创建对象方式,调用farther函数
	Cfarther *far = Cfarther::farther(); 
	delete far;
	Cfarther *far1 = Cfarther::farther();
	delete far1;
	system("pause");
	return 0;
}

异常

概念

异常是人为定义的一种状况,比如说一个函数参数,当这个参数传递0的时候,我们就认为它是异常 。

异常终止函数

形式

abort()

功能

弹出异常框
测试

void text(int a)
{
	if (0 == a)
		abort();
}
//主函数调用
text(0);

这样就会弹出异常框

异常终止情况

关键字:try throw catch
try:测试异常
throw:遇到异常就扔出
catch:用参数接住扔出的异常,并处理

#include<iostream>
using namespace std;
void text(int a)
{
	while (a < 10)
	{
		if (5 == a) //判断异常是5
		{
			throw a;  //扔出a
		}
		a++;
	}
}
int main()
{
	try  //检测异常
	{
		text(5);
	}
	catch (int b)
	{
		cout << b << endl;
	}
	system("pause");
	return 0;
}

优势:比abort()函数灵活。
过程详解:try检测异常,若有异常,通过throw扔出,给catch用参数进行处理。也可以扔出其他类型:字符,浮点型,都行。

	catch (int b)
	{
		cout << b << endl;
	}
	catch (char c)
	{
		cout << c << endl;
	}

也可以并列,写多种处理情况,类比重载。若是整型打印b,字符型打印c。
可以在catch中再处理,让程序继续进行运行

	try  //检测异常
	{
		text(5);
	}
	catch (int b)
	{
		try
		{
			text(b+1);  //b是5,这就继续传6了
		}
		catch(int c)
		{
			cout << c << endl;
		}
		cout << b << endl;
	}

但如果catch扔出的类型不知道,可以:

catch(...)
{
	cout << "default" << endl;
}

注意:
catch扔出对象的时候,要用引用或者指针,不然会拷贝备份出来。
演示:

#include<iostream>
using namespace std;
class Cfarther
{
public:
	int b;
	int c;
	void print()
	{
		b = 12;
		c = 5;
		cout << "异常" << endl;
	}
};
void text(Cfarther &farther)
{
	while (farther.b  < 10)
	{
		if (5 == farther.b ) //判断异常是5
		{
			throw farther; //扔出对象,就是扔出的引用
			//throw &farther;  对象地址,用指针执行
		}
		farther.b ++;
	}
}
int main()
{
	Cfarther far;
		try  //测试
		{
			text(far);
		}
		catch (Cfarther&f)
		{
			f.c++; //上面是引用,所以在这里执行
		}
		catch (Cfarther*fa)
		{
			fa->c++; //上面仍的是地址,所以在这里执行
		}
}

内部类

概念

其实外部类和内部类是一个相对的定义。
类A中有类B,那么类A自然就是外部类了,类B就是内部类。
内部类可以使用外部类的任何变量和方法,但是外部类必须要实例化内部类才可以使用内部类。
而且在实例化之前必须先创建一个外部类的实例。
不过也可以使用this来调用外部类和内部类的元素。

类内的类,嵌套关系
重点:相互访问;
不过无论是内部访问外部的,还是外部访问内部的都要通过对象访问

外部类访问内部类成员

下面就说明内类不能直接使用外类,会报错:未声明标识符

class Cout
{
public:
	int a;
	Cout()
	{
		a = 12;
	}
public:
	class Cin   //内部类
	{
	public:
		int b;
		Cin()
		{
			b = 13;
		}
		void In()
		{
			cout  << a << endl;
		}
	};
};

纠正:在内类还要声明一个外类的对象,通过对象调用

		void In()
		{
			Cout out;
			cout  << out.a << endl;
		}

调用:
想要调用In()这个函数,就要在外类内创建一个内类的对象,
然后在主函数里,通过外类对象调用在外类中创建的内类的对象,然后,再用内类对象调用函数才可以,代码如下:

	Cout ou;  //创建一个外类对象
	ou.in.In (); //ou.in调用内类对象,in.In()调用函数

但如果

	Cout ou;  //创建一个外类对象
	ou.a = 15;
	ou.in.In (); //ou.in调用内类对象,in.In()调用函数

结果,a的值不会变!!为什么?

内类创建的外类对象是out,主函数创建的外类对象是ou。两个对象不同,各自有各自的空间,所以ou里的a值变了,但out里的还是没变的

那如何让ou传的a值改变呢??

传out的地址到内类里不就特别棒了,这里就无需创建新对象了
这里也就要用到指针来操作了

请看
在上面代码的基础上。
1)创建对象肯定行不通了,只能通过指针了,所以只能在内类中创建指针。

class Cin   //内部类
	{
	public:
		int b;
		Cout*p; //定义外类指针
	};

2)指针出来了,还没初始化呢!这是一个外类的指针,所以肯定指向外类的对象呀,那就装一个外类的对象ot吧!并且在函数里用指针调用。

class Cin   //内部类
	{
	public:
		int b;
		Cout*p; //定义外类指针,但还是在内类里的
		//通过构造函数把外部类的地址传进来,即吧this指针传进来 
		//这就是将外类指针初始化,装ot对象
		Cin(Cout*ot) :p(ot) //初始化指针 这里的构造就有参了
		{
			b = 13;
		}
		void In()  
		{
		//这里直接用p调用就可以了
			cout  << p->a  << endl;  
		}
	};

3)内类构造函数有参数了,就要在外类中声明的内类对象传参

Cin in(this);  //将this指针传进来,p就是当前外部类的指针了 

在外类中肯定不能这么写,因为成员不能直接赋值,所以要写在构造函数参数列表处进行传递

	//内类的构造函数有参数,所以要传参
	Cout() :in(this) //this指针,作为参数,传进内部类
	{
		a = 12;
	}

总代码如下:

#include<iostream>
using namespace std;
class Cout
{
public:
	int a;
	//内类的构造函数有参数,所以要传参
	Cout() :in(this) //this指针,作为参数,传进内部类
	{
		a = 12;
	}
public:
	class Cin   //内部类
	{
	public:
		int b;
		Cout*p; //定义外类指针
		//通过构造函数要把外部类的地址传进来,即吧this指针传进来 
		//将外类指针初始化,装ot对象,p即是外类的指针
		Cin(Cout*ot) :p(ot) 
		{
			b = 13;
		}
		void In()  
		{
			cout  << p->a  << endl;  //这里直接用p调用就可以了
		}
	};
public:

	Cin in; 
};
int main()
{
	Cout ou;
	ou.a = 15;
	ou.in.In ();
	system("pause");
	return 0;
}

内部类访问外部类成员

内部类可以使用外部类的任何变量和方法

类型转换

旧式强制类型转换(int)1.2 (1.2)int
新式类型转换
某种类型固定使用某种类型的新式转换
4种运算符:
static_cast const_cast dynameic_cast reinterpret_cast
无论新式还是旧式都是转换数据的,只不过新式吧数据分的更细了

static_cast

应用情况

当type和expression可以互相隐式转换的时候,这个方法执行起来才是合法的
即目标类型和当前表达式可以进行相互隐式类型转换
如:父类子类之间互相转换没问题,如果和一个不相干的类进行转换,就会报错

形式

static_cast<type>(expression)
例子:
父类和子类的两个指针进行相互转换

 //将son转换为Cfarther类型的然后再赋给farther
farther = static_cast<Cfarther*>(son);

如果用强制类型转换,将子类转换成其他的类

Cother *other;
other = (Cother*)son;

这样转换肯定会成功,但子类继承父类,子类又被转换成其他类,于是乎父类和子类的成员会被合成一个数据,转换之后的类就无法保证使用的安全性。但新式类型就会报错,所以更好一点

const_cast(不常用)

应用情况

类型转换表达式const属性(去掉const),仅当typ和expression一样的时候,才合法

注意

转指针与非转指针,非指针要传地址或者引用

形式

const_cast<type>(expression)
代码如下:目的是去掉const的属性

 //去掉const的属性
Cfarther *farther1 = const_cast<Cfarther*>(farther);

这是定义一个Cfarther型新对象接改后的对象,改的时候,是将const Cfarther farther转成Cfarther*farther
由旧式类型转换也可得

so = (Cson*)far; //将const去掉转成Cson*型

这里也是将const Cfarther类型的far强转成Cson*型的far

dynamic_cast(不常用)

应用情况

(1)expression的类型是目标type的共有派生类
(2)expression类型是目标type类型
(3)expression类型是目标type的共有父类,父类必须是多态的情况
因为,子类的可用空间比父类要大,所以只有多态的情况,扩大父类的可用空间,才能转换成子类
(1)和(3)即type和expression是父子类关系,就是合法转换
(2)的意思是,type和expression类型都是一样的

子类和父类的转换

父类转换成子类

	//父类转换成子类  父类是多态的情况
	son = dynamic_cast <Cson*>(farther);

子类转换成父类

//子类转换成父类
farther = dynamic_cast <Cfarther*>(son);

注意

不相干类即使用dynamic_cast有可能会通过,也最好不要用
因为dynamic_cast就是针对有继承关系类的转换而存在的(前提是,父类是多态)

reinterpret_cast(不常用)

应用情况

用于危险类型转换
例如:不同的不相干的类有不同的对象,都有不同的访问方式,类型不同,访问很容易崩溃的,这种运算符就是针对像这种类型不同之间转换的情况而存在的
例如::结构体和数据类型之间的转换

注意点

(1)这种转换适用于依赖实现底层编程技术,代码不可移植,例如:不同的系统可能以不同的顺序存储多字节数据中的字节
(2)互相转换时,空间一定够用,否则不行
(3)函数指针和数据指针不能相互转换

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
一个完整的C++类应该具备以下特点: 1. 使用私有的构造函数,禁止外部直接创建实; 2. 使用静态成员变量来存储的指针; 3. 使用静态成员函数来获取的指针,同时确保线程安全; 4. 使用静态成员函数来销毁,同时避免内存泄漏。 以下是一个完整的C++类的示代码: ```c++ #include <mutex> class Singleton { public: static Singleton* getInstance() { std::lock_guard<std::mutex> lock(m_mutex); if (instance == nullptr) { instance = new Singleton(); } return instance; } static void destroyInstance() { std::lock_guard<std::mutex> lock(m_mutex); if (instance != nullptr) { delete instance; instance = nullptr; } } // 添加其他方法和成员变量 private: Singleton() { /* 私有构造函数 */ } Singleton(const Singleton&) = delete; Singleton& operator=(const Singleton&) = delete; static Singleton* instance; static std::mutex m_mutex; }; Singleton* Singleton::instance = nullptr; std::mutex Singleton::m_mutex; ``` 在这个示中,我们使用了std::mutex来确保getInstance()和destroyInstance()方法的线程安全性。同时,为了确保类的完整性,我们可以添加其他方法和成员变量,以满足具体的业务需求。 在使用类时,可以通过Singleton::getInstance()方法来获取类实的指针。需要注意的是,由于类的构造函数是私有的,因此只能通过getInstance方法来创建类实。 在销毁类实时,可以调用Singleton::destroyInstance()方法来实现。需要注意的是,在销毁类实时,需要先判断类实是否为nullptr,以避免出现空指针引用的问题。同时,也需要显式地删除类实,并将其指针设置为nullptr,以防止出现悬挂指针的问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

沐鑫本鑫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值