友元、嵌套、异常

在其他类中声明的类:友元类、友元成员函数、嵌套类。
之前我们学习过友元函数(普通函数),一个类包含友元函数,该友元函数相当是扩展了该类的接口。
类并非只能包含一个友元函数,还能包含一个友元类,使得该类中的所有方法都可以访问修改该类的成员。稍稍严格一些呢。我们可以限制某个类的某个成员方法为另一个类的友元成员函数,只有该方法可以访问修改该类的成员,而类中的其它方法无此权限。一个类能够对谁开放权限,这是这个类来指定的(在类中声明的),这个类只会对需要的类开放权限,因此依旧是保护了数据成员且提高灵活性。

友元声明可在私有、保护、公有部分,位置无所谓。
什么时候使用友元类呢?
一个类的最终的实现就是修改另一个类的数据成员。(一个类为另一个类服务(修改),不同于类嵌套(也是服务于另一个类))。

举个例子:遥控器和电视机,遥控器所实现的所有功能都是围绕电视机状态的改变,需要访问电视机所有的数据成员。为此,将遥控器的声明放入到电视机类中,作为电视机的友元类便可顺利获取电视机的数据访问权限。而在定义实现电视机类时,函数参数必然离不开电视机的,但此时作为电视机的友元类,它所有的方法在利用电视机对象时,不仅仅只能是利用接口,更是可以获取到私有成员。

#ifndef REMOTE_H
#define ROMOTE_H
#include<iostream>

class Tv
{
public:
	friend class Romote;//声明类
	enum{ON,OFF};
	enum{DV,TV};
	enum{maxv=20};
	enum{maxc=100};
private:
	int state;
	int channel;
	int mode;
	int volume;
public:
	//Tv(int s= ON,int m=TV):state(s),channel(2),mode(TV),volume(1){}
	Tv(int s = ON, int m = TV)  {
		state = s;
		channel = 1;
		mode = m;
		volume = 1;
	}
	bool onoff() {
		return state == ON ?  OFF:ON;
	}
	bool ison() const
	{
		return state;
	}
	bool tvdvd()
	{
		return mode == TV ? DV : TV;
	}
	bool chanup();
	bool chandown();
	bool volumup();
	bool volumdowm();
	void show();
	
};

class Romote
{
	int state;
public:
	Romote(int s=Tv::ON):state(s){}
	bool chanup(Tv & t) {
		return t.chanup();
	}
	bool chandown(Tv & t) {
		return t.chandown();
	}
	bool volumup(Tv & t)
	{
		return t.volumup();
	}
	bool volumdowm(Tv & t)
	{
		return t.volumdowm();
	}
	bool onoff(Tv &t)
	{
		return t.onoff();
	}
	bool tvdvd(Tv &t)
	{
		return t.tvdvd();
	}
	bool setchan(Tv &t, int c)
	{
		if(t.channel+c< Tv::maxc)
		{ 
			t.channel += c;
		
		}
		else
		{
			t.channel = (t.channel + c)%Tv::maxc;

		}
		return true;
		 
	}

};


bool Tv::chanup()
{
	if (channel == maxc)
		channel = 1;
	else
		channel++;
	return true;
	

	
}
bool Tv::chandown()
{
	if (channel == 0)
		channel = maxc;
	else
		channel--;
	return true;
}
bool Tv::volumup()
{
	if (volume > maxv)
		return false;
	else
	{
		volume++;
		return true;
	}

}
bool Tv::volumdowm()
{
	if (volume <= 0)
	{
		return false;
	}
	else
	{
		volume--;
		return true;
	}
}
void Tv::show()
{
	using std::cout;
	using std::cin;
	using std::endl;
	cout << state << " " << mode << " " << channel << " " << volume << endl;

}

#endif
#include"remote.h"

int main()
{
	Tv t;
	t.show();
	//t.chanup();
	t.volumup();
	t.show();
	Romote r;
	r.setchan(t, 100);
	//r.chandown(t);
	r.volumup(t);
	t.show();
	std::cin.get();
    return 0;
}

成员友元函数:
仅仅使得类的某几个成员函数成为友元函数,此时在类声明中指定成员函数,由于此时要先声明类和该成员函数,且此类可能需要使用到类,因而有个次序的问题,为解决这个问题,c++支持前向声明。
成员友元函数声明语法:
在这里插入图片描述
成员友元函数和类友元的差别:
类所有方法都可修改另一个类的私有成员,指定的成员方法才可修改类的私有成员。权限开放范围不一样。

在这里插入图片描述

#ifndef TV_H
#define TV_H

#include<iostream>
class Tv;
class Remote
{
	int state;
public:
	enum{ON,OFF};
	Remote(int s=ON):state(s){}
	bool onoff(Tv & t);//由于不知道Tv的具体函数 ,无法在此直接定义Remote类中需要使用类Tv方法的方法
	bool volup(Tv & t);
	bool setchannel(Tv &t, int n);
	bool channelup(Tv &t);
	

};
class Tv
{
	int state;
	int channel;
	int volume;

public:
	friend bool Remote::setchannel(Tv &t, int n);
	enum{ON,OFF};
	enum{ maxchannel = 100 };
	enum{maxvolume=30};
	Tv(int s = ON)
	{
		state = s;
		channel = 2;
		volume = 1;
	}
	bool onoff()
	{
		return state == ON ? OFF : ON;
	}
	bool channelup()
	{
		if (channel == maxchannel)
			channel = 1;
		else
			channel++;
		return true;
	}
	bool volumeup()
	{
		if (volume == maxvolume)
			return false;
		else
		{
			volume++;
			return true;
		}
	}
	void show();

};

void Tv::show()
{
	std::cout << state << " " << channel << " " << volume << std::endl;
}
bool Remote::onoff(Tv & t)//由于不知道Tv的具体函数 ,无法在此直接定义Remote类中需要使用类Tv方法的方法
{
	return t.onoff();
}
bool Remote:: volup(Tv & t)
{
	return t.volumeup();
}
bool Remote:: setchannel(Tv &t, int n)//只有在这里用到了Tv类私有成员 
{
	t.channel = n;
	return true;
}
bool Remote::channelup(Tv &t)

{
	return t.channelup();
}

#endif
#include"tv.h"

int main()
{
	Tv t;
	t.show();
	Remote r;
	r.setchannel(t, 20);
	t.show();
	std::cin.get();

    return 0;
}

想想看一个类可能需要改变另一个类的数据成员,但另一个类也有可能改变这个类的数据成员,即互相影响。声明类友元的方法和前面的一样,但是得注意声明的次序。利用前向声明和仅仅声明方法,不做定义。
在这里插入图片描述
使用友元函数的另一种情况,函数同时需要获得两个类的私有数据访问权限,此时则在两个类中均将该方法声明为友元函数,当在类外定义函数时,则可以使用这两个类的私有成员。但此时必然会出现一个类还未声明,需要使用向前声明解决。

总结:友元函数、友元类、成员友元函数,均是在一个类中将某个(类所有)函数声明为友元,使得该普通的函数不再是类外人,而是类的自己人,可以访问修改类数据成员。在使用成员友元函数时,两个类互为友元类时,友元函数需要访问多个类时,在声明友元时,需要使用前向声明(站在编译器的角度看,让他知道所声明的东西是存在的,而不是未声明时就使用)

嵌套类
在一个类中声明的类被称作是嵌套类。相对的外层类可称为包含类。包含类成员函数可创建、使用嵌套类的对象。嵌套类声明在私有、保护、公有部分会影响其作用域,这决定了哪部分可以创建创建其对象。嵌套类的私有、保护、公有声明决定了对类成员的访问。统称为作用域和访问控制,即在哪里能使用以及如何使用嵌套类。
嵌套类声明在私有部分,则只有包含类的成员函数能创建使用该类对象;声明在保护部分,则派生类和包含类均可创建使用该嵌套类的对象;声明在公有部分,在派生类、外部、包含类均可使用该嵌套类。
在这里插入图片描述了解了嵌套类的可见性后,需要决定在可见的部分,能访问到嵌套类的哪些数据成员,比如说,嵌套类声明在私有部分,那么只有包含类的成员函数能使用到该类对象,即便数据成员声明为公有的,也只是对包含类可见可用,以达到帮助包含类实现,而类外部压根就看不见嵌套类,又何谈嵌套类的数据成员呢?
嵌套类目的帮助实现包含类,并避免名称冲突。
模板类的嵌套类,当然优势在于嵌套类可使用模板的类型参数。在具体化模板时,生成多个类定义,嵌套类在不同具体类中,因而不会出现名称冲突的问题。

#pragma once
#ifndef QUEUE_H
#define QUEUE_H

#include<string>
template<class T>
class Queue
{
	class Node{//嵌套类使用了类型参数 具体化时 嵌套类的成员类型依参数来确定 
	public:
		T item;
		Node * next;
		Node(T &i):item(i),next(nullptr){}  
	};
	Node *begin;
	Node *end;
	int count;
	int size;
public:
	enum{SIZE=3};
	Queue(int s=SIZE): size(s),begin(nullptr),end(nullptr),count(0){}
	bool isfull() const{
		return count == size;

	}
	bool isempty() const
	{
		return count == 0;
	}
	bool enqueue(T & c);
	bool dequeue(T &c);
};

template<class T>
bool Queue<T>::enqueue(T & c)

{
	if (size == count)
		return false;
	else
	{
		Node * p = new Node(c);
		if (count == 0)
			begin = p;
		else
			end->next = p;
		end = p;
		count++;
		return true;
	}

}

template <class T>
bool Queue<T>::dequeue(T & c)
{
	if (count == 0)
		return false;
	else
	{
		Node * temp = begin;
		c = begin->item;
		begin = begin->next;
		delete temp;
		count--;
		if (count == 0)
			end = nullptr;
		return true;
	}
}


#endif
#include"queue.h"

#include<iostream>

int main()
{
	using std::cout;
	using std::cin;
	using std::endl;
	std::string s;
	Queue<std::string> q;
	while (!q.isfull())
	{
		cout << " a string:";
		cin >> s;
		//从逻辑到实现的过程
		q.enqueue(s);//向栈中添加数据  如何添加数据呢  得根据最底层存储数据的方法 (直接存数据方法是离散块)
		//队列是什么 其实什么都不是  认为定义类型 我们赋予它先入后出规则 ,为了实现这种规则 (数据满足先入后出) 就需要约束数据进入和出去方法(核心,有了入和出的规则)其实我们的队列也就实现了  
		//我们知道了只要约束了入和出的规则 但是我们还没有想好该如何存数据  从入和出方法出发 使用数组存需要挪动元素 也能做 复杂效率低
		//而采用离散的数据块 我们要是他们不离散(线性表),为此需要存储额外的数据(指针,找到下一个元素)空间换时间 如何表达这样一个数据块(不也是一个类型嘛?万物皆为类,还可为其添加方法,直接调用封装好的方法减少了代码的冗余)
		//有了存储结构,我们才能说该如何实现先入后出!严格把控好整个队列的秩序 (每一个入和出都不得违反先入后出的规则)
	}
	while (!q.isempty())
	{
		q.dequeue(s);
		cout << s << endl;

	}
	cin.get();
	cin.get();
    return 0;
}

异常:
运行阶段产生的错误(利用输入的值做除法运算,当被除数为0时,有的编译器能接收被除数为0的情况,但有的直接崩了,这就使得实现效果不一样,我们不要这类随实现而带来的不确定的结果)

为了解决上面的问题,我们程序猿在编程的时候就要考虑到哪些在运行时可能产生的错误,对这些错误采取一定的措施,即便产生了如此的问题,我们也知道是哪里有问题。

1、最粗暴简单的就是终止程序:在可能产生错误的地方调用abort方法(位于cstdlib库),该方法使得程序直接结束且返回错误提示,而不会返回主函数中继续执行。利用abort方法,管他编译器是如何处理错误的,我们统一用终止程序进行就好。

#include "stdafx.h"
#include<cstdlib>
#include<iostream>

double hmean(double x,double y)
{
	if (x == -y)
		std::abort();
	return x + y / x - y;

}

int main()
{
	using std::cout;
	using std::cin;
	using std::endl;
	int x;
	int y;
	cout << " input x ,y:";
	cin >> x;
	cin >> y;
	cout << hmean(x, y) << endl;
	cin.get();
	cin.get();
    return 0;
}

在这里插入图片描述

2、返回错误码(调用程序通过错误码(返回值)知道函数调用正常or异常)
我们并不想让程序结束,输入数据即使有问题,我们是否能以特殊统一的方法来处理错误数据,比如将其返回值设定为统一的值,并控制函数返回值为false。通常的一个做法,就是改变可能处理异常数据的函数参数个数(多加入一个类型为指针或是引用的参数,存入计算结果)和返回类型为布尔型,指定一个指针或是引用来存就算结果,对于错误的数据,存一个明确的值,返回false。而在调用程序中根据返回的布尔值,有选择的处理计算结果(使用还是舍弃)。

#include "stdafx.h"
#include<iostream>
#include<cfloat>
bool hmean(double x, double y, double *result);

int main()
{
	using std::cin;
	using std::cout;
	using std::endl;
	cout << " input x,y:";
	double x, y;
	double z;
	while (cin >> x&&cin >> y)//当输入字符条件为false 
	{
		if (hmean(x, y, &z))
		{
			cout << z << endl;
		}
		cout << " input x,y:";
	}

    return 0;
}

bool hmean(double x, double y, double *result)
{
	if (x == -y)
	{
		*result = DBL_MAX;
		return false;
	}
	else
	{
		*result = x + y / x - y;
		return true;
	}
}

3、异常机制:
异常机制提供了将程序控制权从程序的一个部分传递到程序的另一个部分。

程序运行过程中,输入的数据不满足计算条件的情况,我们首先想到的是停止代码的继续执行,如果是上两种方法,可能直接终止程序。但我们现在所探索的是处理错误而不是简单地否定错误。为此我们希望当出现类似的错误时,统一跳转(何为跳转?出现错误代码之后的部分不会被执行)到指定的程序块进行处理(throw 类型匹配catch类型)。问题来了:如何指定错误类型?到哪里去寻找异常处理代码?为此我们可以声明类用作错误类型;而异常处理代码则是根据特定的错误类型来匹配(类似于调用函数,匹配参数的过程)且其位置固定就是在try之后。为了达到此类效果,我们使用try和catch,将可能出现的异常的代码放在try内,try内可能包含函数调用,被调用的函数产生的异常将在随后的catch中匹配。

异常处理的三个部分:
1.引发异常:检测到错误数据后,利用throw(throw跳转语句,命令程序进行跳转回溯)+类型对象(表示了异常的特征)来引发异常
理解throw语句:结束当前被调用函数的执行,返回控制权给上一个调用函数直到找到包含try的函数,在try之后紧跟的异常处理块中去匹配异常类型。
栈解退(类似于关键词return,但return仅仅返回到被调用的地方,而throw会持续获取栈顶元素直到找到第一个位于try块中的地址且参数类型得到了匹配):
直接调用的函数没有引发异常,但是所调用的函数内部又调用了函数而引发了异常,这时便需要使用栈解退技术来保证跳转至合适的地方和释放掉自动变量所占用的空间。
复习一下正常的函数调用过程 :
一个函数内调用另一个函数:
借助栈,假设从main()开始,其中调用one(),则将当前调用函数前的内存地址存入到栈顶空间中并保存变量,此时跳转到one()所在的内存块中,若在one()中调用了two(),则保存one内执行到的地址和one 中的自动变量到同一栈顶空间中,此时two()执行完毕通过return获得当前的栈顶元素的中地址值和自动变量值进行处理,再遇到return获取到当前栈顶元素地址值和自动变量(main中没执行的代码块和自动变量)。

其实懂了函数调用,throw 的栈解退也是同样地思路,不一样的是,会持续获取栈顶元素直到找到第一个位于try块中的地址(若第一个try后的catch类型参数不匹配,则接着去栈顶元素 ,查找是否还存在地址位于try块内,如果一直不匹配,出现了所谓的意外异常,也许程序就终止了 ),将控制权转移到try的末尾异常控制块。同样在解退的过程中,会释放栈中自动变量的内存空间。
throw 重声异常,通过栈再去查找是否有位于try内的地址,有则转移到该try后的异常处理程序中,有点类似上面的一直查找直到找到匹配的catch类型。

#ifndef MEAN_H
#define MEAN_H
#include<iostream>



class Bad1
{
	double x;
	double y;
public:
	Bad1(double xx,double yy):x(xx),y(yy){}
	void show() {
		std::cout << x<<" " <<y<<" dismatch function type"<< std::endl;
		
	}
};

class Bad2
{
	double x;
	double y;

public:
	Bad2(double xx, double yy)
	{
		x = xx;
		y = yy;
	}
	char * show() {
		return "x,y both need >0";
	}
};

#endif
#include"mean.h"


double mean(double x, double y);
double mean2(double x, double y);
double mean3(double x, double y);

class Test {
public:
	Test() {
		std::cout << "create" << std::endl;

	}
	~Test()
	{
		std::cout << "delete" << std::endl;
	}
	void show()
	{
		std::cout << "live" << std::endl;
	}
};

int main()
{
	using std::cin;
	using std::cout;
	using std::endl;
	double x, y;
	{
		Test t;
		while (cin >> x >> y)
		{
			try {
				cout << mean3(x, y) << endl;
			}
			catch (Bad1 &b)
			{
				b.show();
				continue;
			}
			catch (Bad2 &b)
			{
				cout << b.show();
				break;
			}
		}
	}
	return 0;
}
double mean(double x, double y)
{
	if (x == -y)
		throw Bad1(x, y);
	return x - y / x + y;
}
double mean2(double x, double y)
{
	if (x < 0 || y < 0)
		throw Bad2(x, y);
	return x*y;
}
double mean3(double xx, double yy)
{
	Test t;
	double result;

	try {
		result = mean(xx, yy) + mean2(xx, yy);

	}
	catch (Bad1 & b) {
		throw;
	}
	catch (Bad2 &b)
	{
		throw;
	}
	t.show();
	return result;
}


2.使用处理程序捕获异常(通常在调用程序中,用于应对函数调用时发生了异常该怎么办)
利用catch+类型声明(处理的异常特征),利用catch + 异常类型的组合使得编译器得知到哪里去找异常处理程序。
catch类似于函数定义,参数间发生值传递,并执行catch代码块的内容。
无论catch的参数类型是否为引用,都采用值传递构建副本,throw的对象在被调用函数运行结束后生命周期结束,所以需要副本 。那为什么还要使用引用呢?在继承关系中,可以根据所指向对象的类型调用成员函数。而不使用引用下,只能调用基类的方法。当异常类型存在继承关系,要格外注意在catch中类型声明顺序,一旦第一个是基类,那么派生类对象和基类对象都匹配到这个catch,catch的匹配顺序是从第一个一次往后,匹配成功后不再进行其余catch的匹配。

3.使用try块,表示此块内的代码可能引发异常,需要注意。
try内的代码没有引发异常时则跳过异常代码程序(catch块),执行第一条语句;引发异常,但没有匹配到相应的类型,默认调用abort()。

#include "stdafx.h"

#include<iostream>
double mean(double,double);

int main()
{
	using std::cout;
	using std::cin;
	using std::endl;
	double x, y,result;
	
	while (cin >> x >> y)
	{
		try {
			result=mean(x, y);//出现error 不会正常返回值 直接跳转到catch语句

		}
		catch(char *s){
			cout << s << endl;
			continue;
		}
		cout << result << endl;//只有正常调用返回的情况下,才能执行到。错误处里中使用了continue 跳过了该部分,否则从catch 执行完成后还是能执行该语句的 ,但如果是第一次出错 ,且result 无初值,程序产生错误
	}
    return 0;
}

double mean(double x,double y)
{
	if (x == -y)
	{
		throw "x==-y";

	}
	return x - y / x + y;

}

上一个例子中,引发异常的函数传递的参数类型是char *,这个方法简单,但是对于不同类型的错误,区分性不高,而且没能把错误信息或是原因进行传递。为了存储错误信息我们可采用类设计,即通过类对象存储有错误的数据,有个问题,那么这个对象的生命周期是多少,至少是到第一个包含try的函数了(进行值传递后,在catch中使用的是副本),若再次引发异常在传递的是副本。

#ifndef MEAN_H
#define MEAN_H
#include<iostream>
class Bad1
{
	double x;
	double y;
public:
	Bad1(double xx,double yy):x(xx),y(yy){}
	void show() {
		std::cout << x<<" " <<y<<" dismatch function type"<< std::endl;
		
	}
};

class Bad2
{
	double x;
	double y;

public:
	Bad2(double xx, double yy)
	{
		x = xx;
		y = yy;
	}
	char * show() {
		return "x,y both need >0";
	}
};

#endif
#include "stdafx.h"
#include"mean.h"


double mean(double x ,double y);
double mean2(double x,double y);
int main()
{
	using std::cin;
	using std::cout;
	using std::endl;
	double xx, yy, result;
	while (cin >> xx >> yy)
	{
		try
		{
			//result = mean(xx, yy);
			cout << mean(xx, yy) << endl;
			cout << mean2(xx, yy) << endl;
		}
		catch (Bad1 &b) //失误了 一定要注意catch的逻辑 继续执行 跳出循环 进行下一次循环
		{
			b.show();
			continue;
		}
		catch (Bad2 &b)
		{
			cout << b.show() << endl;
			break;
		}
	}
	
    return 0;
}

double mean(double x, double y)
{
	if (x == -y)
		throw Bad1(x, y);
	return x - y / x + y;
}
double mean2(double x, double y)
{
	if (x < 0 || y < 0)
		throw Bad2(x, y);
	return x*y;
}

被C++11所摒弃的异常规范():
即在声明、定义函数时在参数列表后加throw (参数类型)。
目的:提示使用者使用该函数需要被包含于try代码块内。被摒弃的语法,但是关键词 noexpect 依旧有用,表示这个函数不会引发任何异常。

我们如果知道调用的函数会引发异常,但不知具体的类型,可以使用catch(…)捕获未知类型的异常。
在这里插入图片描述

Exception类:
异常基类,可被继承生成新的异常类,内含虚函数 const char * what()。
从Exception类派生得到 logic_error和runtime_ error,他们分别作为基类,分别派生可得到更多的异常类。如果库中已有的异常类型不能满足实际开发需要,则可根据logic_error或是runtime_error派生出所需的异常类。
使用从Exception类派生得到的bad_alloc类(定义在new头文件中),当new分配内存失败时,将引发bad_alloc异常。此外,对于new分配内存失败时,我们不希望以异常机制来处理而是使得其返回空指针的标记,我们使用这个标记来得知内存分配失败,具体语法如下:
在这里插入图片描述

未处理异常:
(没有异常规范的函数)所调用的引发异常的函数未包含在try代码块内,或是异常类型没有匹配到合适的catch。(通过栈解退,完成了所有的try catch匹配),默认将调用terminate函数,该函数内部默认调用abort()终止程序。
但是我们可以调用set_terminate()修改内部所调用的函数。但是需要包含头文件Exception
在这里插入图片描述
模板类由于具体的类型不确定,所以不适用异常处理机制。
当函数中包含动态分配的空间时,若该函数本身将引发异常,在栈解退的时候,throw 保证了指针空间的释放,但是动态分配的空间仍在,为此需要手动释放动态内存空间。可在引发异常的代码中再加入try catch,使得能在解退前先释放掉空间(本层次中先释放内存空间),再返回到上一级(重声throw),让上一级处理真正的异常。
在这里插入图片描述
RTTI(运行阶段类型识别):
在运行阶段确定对象的类型,当基类指针指向派生类对象时,由于虚函数机制(类对象有个指向虚函数表的指针,虚函数表中记录着函数的地址,新定义的,重写的,原有的都有),所以其实不需要知道对象的类型(有对象就能保证调用到合适的方法,当使用基类指针调用方法时到存储该对象的地址处找到虚函数表调用该最新版本方法)。
但是可能存在:一个基类指针可能指向派生类对象,也可能指向基类对象的情况,不能直接拿这个基类指针去调用派生类中新增的方法(基类指针就不包含此方法,压根不能用,但我们知道基类指针所指向的部分对象是派生类对象,我们想调用此派生类对象的新增方法),为了解决这类问题,RRTI就很有用了,他有可能将基类指针转化为派生类指针(能不能还是得看指向对象的类型是否属于派生类型,这样就可安全根据实际的对象类型调用方法)。
RTTI工作原理:
1.dynamic_cast运算符:有可能的话,使用一个指向基类的指针来生成指向派生类的指针;否则,返回空指针
2.typeid运算符:返回对象的类型值
3.typeinfo结构:存储的是类型的信息
RTTI仅能用于包含虚函数的类继承.

dynamic_cast运算符:判断类型能否转化!能否将对象的地址值赋给特定类型的指针!通常派生类对象或是基类对象的地址可以赋给基类指针。这样便可得知我们能对这个对象进行哪些操作!!!虽然我们还是不知道对象的实际类型,但我们可以通过dynamic _cast来确定对象所在的派生层次结构进而调用该层次下的方法。(若指针能进行类型转化,刚好可利用转化后的指针调用函数)
在这里插入图片描述
dynamic_cast用于引用时,与指针不同的地方:不能进行指针类型的转化时将抛出异常类型bad_cast而不是空指针。
在这里插入图片描述

#pragma once
#ifndef DYNAMIC_H
#define DYNAMIC_H
#include<string>
#include<iostream>
class One {
	int hold;
public:
	One(int i):hold(i){}
	virtual ~One(){}
	int  get_value() const {
		return hold;
	}
	virtual void speak() {
		std::cout << " first class speak()" << std::endl;

	}
};


class Two :public One {
public:
	Two(int i):One(i){}
	virtual ~Two() {
	}
	virtual void speak() {
		std::cout << "second class speak()" << std::endl;
	}
	virtual void say() {
		std::cout << "secontd class say" << get_value()<<std::endl;
	}
};


class Third :public Two {
	std::string str;
public:
	Third(int i, std::string s) :Two(i), str(s){}
	virtual ~Third(){}
	virtual void speak() {
		std::cout << "third class speak()" << std::endl;
	}
	virtual void say() {
		std::cout << "third class say " << get_value()<<" "<<str<<std::endl;//继承不可直接访问私有成员 通过调用继承来的方法  
		//能直接访问数据的,不调用函数去访问,效率多慢啊
	}
	const std::string & get_str() const {
		return str;
}

};
#endif
#include"dynamic.h"
#include<ctime>
#include<cstdlib>
One * getone();


int main()
{
	One *p;
	Two *pp;
	srand(time(0));
	for (int i = 0; i < 5; i++)
	{
		p = getone();
		p->speak();
		if (pp = dynamic_cast<Two *>(p))
		{
			pp->say();
		}
	}
    return 0;
}

One * getone( )
{
	One *temp=nullptr;
	int x;
	std::string s;
	switch (rand() % 3) {
	case 0:
		std::cout << "input a number:";
		std::cin >> x;
		temp = new One(x);
		break;
	case 1:
		std::cout << "input a number:";
		std::cin >> x;
		temp = new Two(x);
		break;

	case 2:
		std::cout << "input a number and string:";
		std::cin >> x;
		std::cin >> s;
		temp = new Third(x,s);
		break;
		
	}
	return temp;
}

2、typeid运算符
参数是类型或是对象的表达式,返回type_info对象的引用,类type_info的name()可返回类型说明。

std::cout << typeid(*p).name() <<std:: endl;//果然出错了  落了* 比较的是类型而不是指针
if (typeid(Third) == typeid(*p))
		{
			std::cout << "succeed!" << std::endl;
		}

类型转化运算符:

C语言的强制类型转化太松散,无论是否合理,都可以进行。C++设计者觉得这并不合理,为此对可进行转化的类型做出了更加严格的限制。dynamic_cast、const_cast、static_cast、reinterpret_cast指出了类型转化的原因,让编译器可以为我们检查程序的行为是否和我们设计的思路一致。这四个运算符更加适用于自定义定义类(尤其是具有继承关系的类)。基本类型可以通过简单的类型转化就能实现。

dynamic_cast向上类型转化,会检查是否安全,但耗时较高。
常见的应用场景:(只发生在基类、派生类指针和引用之间的转化)
与static_cast的差别:只用于具有继承关系的指针、引用类型,会检查是否安全。
在这里插入图片描述
const_cast丢弃const修饰,适用新的别名(无const的)来修改值。但是对于常量,即便使用无const修饰的新的别名来修改其值时,值也不会改变(本身就是个常量)。
static_cast无论是现有类型还是待转化类型,只要有一个方向的转化是隐式进行的,那么类型转化都是成功的。但无法保证安全性。
常见的应用场景:
1、父类和子类指针或是引用的转化。
2、基本类型的转化
3、任何类型转化为void 类型

<think>我们正在讨论C++构造函数初始化列表和函数体内操作的区别,以及嵌套的相关概念。用户的问题分为三个部分:1.构造函数使用初始化列表的好处2.构造函数体内可以执行哪些操作3.嵌套的定义、作用及使用场景根据用户提供的引用和之前的知识,我们来组织回答。第一部分:构造函数初始化列表的好处引用[1]和引用[2]提供了相关信息:-初始化列表可以初始化const成员和引用成员(必须使用初始化列表)-对于类型成员,使用初始化列表可以直接调用拷贝构造函数,避免先调用默认构造再赋值(效率更高)-在C++11之后,内初始化和初始化列表可以混合使用,但初始化列表会覆盖内初始化。第二部分:构造函数体内可以执行的操作-函数体内可以进行更复杂的逻辑,如条件判断、循环、函数调用等。-可以对成员进行修改(非const成员),包括对已经通过初始化列表初始化的成员进行二次赋值。-可以执行资源分配(如new)、文件打开等操作。第三部分:嵌套引用[3]提供了一个嵌套的例子(B中嵌套A的成员)。嵌套的定义、作用及使用场景:-嵌套是指在一个的内部定义的。-作用:1.封装:如果嵌套只被外部使用,可以将嵌套隐藏在外部的作用域内,避免污染全局命名空间。2.访问控制:嵌套可以访问外部的所有成员(包括私有成员),但外部不能直接访问嵌套的私有成员(除非嵌套将外部设为)。3.实现细节:嵌套通常用于实现外部的某些功能,作为实现细节的一部分,对外部隐藏。-使用场景:1.当某个只被另一个使用时,可以将其定义为嵌套。2.实现某些设计模式(如工厂模式)时,可以使用嵌套。3.例如,链表节点的定义可以嵌套在链表中。注意:嵌套与包含对象成员(如引用[3]中的B包含A的成员)是不同的概念。嵌套是定义在另一个内部的,而对象成员是另一个的实例作为成员。下面我们按照要求组织回答,注意LaTeX格式和引用标记。</think>### C++ 构造函数初始化列表与函数体内操作详解 #### 一、构造函数初始化列表的优势 1. **强制初始化特定成员**: - `const`成员和引用类型成员**必须**通过初始化列表初始化[^1] - 成员若无默认构造函数,也**必须**通过初始化列表初始化 ```cpp class Example { public: // const成员必须使用初始化列表 Example(int val) : constMember(val) {} private: const int constMember; }; ``` 2. **高效初始化对象成员**: - 避免"先默认构造再赋值"的双重开销[^4] - 直接调用成员的带参构造函数(性能更优) ```cpp // 高效方式:直接构造 MyClass() : memberObj(param) {} // 低效方式:先默认构造再赋值 MyClass() { memberObj = MyMember(param); } ``` 3. **初始化顺序控制**: - 初始化顺序严格按成员**声明顺序**执行[^5] - 与初始化列表中的书写顺序无关 ```cpp class MyClass { private: int i; // 先初始化 int j; // 后初始化 public: // 实际顺序:i(1) → j(i) MyClass() : j(i), i(1) {} }; ``` 4. **支持内初始化的覆盖**: - C++11的内初始化可被初始化列表覆盖[^2] ```cpp class Item { int a = 10; // 内初始化 public: Item() : a(20) {} // 覆盖为20 }; ``` #### 二、构造函数体内的操作能力 1. **执行复杂逻辑**: - 支持条件分支、循环等控制结构 ```cpp Matrix(int size) : dim(size) { if(size <= 0) throw invalid_argument("尺寸必须为正数"); data = new double[size*size]; // 动态分配 } ``` 2. **修改非const成员**: - 对已初始化的成员进行二次修改 ```cpp Counter() : value(0) { value++; // 修改初始化值 logCreation(); // 调用成员函数 } ``` 3. **资源管理操作**: - 动态内存分配(`new`/`delete`) - 文件操作/网络连接等资源初始化 ```cpp FileHandler() { file = fopen("data.bin", "rb"); if(!file) throw runtime_error("文件打开失败"); } ``` 4. **参数验证与错误处理**: - 实现运行时参数检查 - 抛出异常处理非法状态 #### 三、嵌套的定义与用途 1. **基本定义**: - 在一个内部定义另一个[^3] ```cpp class Outer { public: class Nested { // 嵌套定义 int nestedData; public: Nested(int d) : nestedData(d) {} }; Outer(int val) : nestedObj(val) {} private: Nested nestedObj; // 嵌套对象成员 }; ``` 2. **核心作用**: - **封装关联类型**:将仅在外部使用的类型隐藏在其作用域内 - **访问特权**:嵌套可直接访问外部的`private`/`protected`成员 - **实现隐藏**:对外隐藏内部实现细节(如迭代器实现) 3. **典型应用场景**: - **容器与迭代器**:为自定义容器实现专属迭代器 ```cpp class LinkedList { public: class Iterator { /*...*/ }; // 嵌套迭代器 }; ``` - **工厂模式**:嵌套实现具体产品 - **状态机**:用嵌套表示不同状态 - **工具**:仅被外部使用的辅助功能 4. **初始化注意事项**: - 通过外部构造函数初始化列表初始化嵌套对象[^3] ```cpp class B { A m_a; // 嵌套A的对象 public: B(int x, int y) : m_a(x, y) {} // 正确初始化方式 }; ``` #### 最佳实践总结 | **场景** | **推荐方式** | |------------------------|-------------------| | const/引用成员初始化 | 必须使用初始化列表 | | 对象成员初始化 | 优先初始化列表 | | 动态资源分配 | 函数体内操作 | | 运行时参数验证 | 函数体内操作 | | 嵌套对象初始化 | 通过初始化列表 |
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值