ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

175-C++重要知识点6

2022-01-26 17:58:23  阅读:169  来源: 互联网

标签:知识点 Int Object ++ C++ int 对象 operator 175


1.下面的结果各是什么?

int i = 0;
i = i++ + 1;

Int a = 0;
a = a++ + 1;

和编译器有关,有的编译器上面输出的结果为2,++是后置++,所以i还是0,然后加1,把结果1赋值给i,i变为1,然后再执行++,所以最终i的结果为2

下面的输出的结果为1,++是我们重载的运算符,返回的值是a的旧值,也就是0,所以结果为1

2.请问下面程序输出d.value的值是多少?

Int& operator++(int)
{
	static Int old = * this;
	old = * this;
	++* this;
	return old;
}
int main()
{
	Int a(10);
	Int b = a++;
	Int& c = a++;
	c = c + 100;
	Int d = a++;
}

static Int old = * this;语句只执行一次,执行完Int a(10);后a.value的值为10,执行完Int b = a++;以后,b.value的值为10,a.value的值为11,执行完Int& c = a++;以后c.value的值为11,a.value的值为12,此时c是old的引用,old的值为11,执行完c = c + 100;以后old的值为111,但是在执行Int d = a++;时,用a.value的值去赋值old,old的值变成了12,最终a.value的值为12

一般情况下不要在函数里面轻易定义静态变量,也不要轻易使用银行用返回,如果当一个函数作为引用返回时,用引用接收必须要有原因,否则不要使用引用接收,最好使用值接收

3.a = a++ + 1的解析过程

  Int a = 0;
  a = a++ + 1;
//a = a.operator++(0) + 1;//0用于区分调用后置++,不一定是0,只要是int类型就可以
//a = operator++(&a,0) + 1;
//a = operator++(&a,0).operator+(1);
//a = operator+(&operator++(&a,0),1);
//a.operator=(operator+(&operator++(&a,0),1));
//operator=(&a,operator+(&operator++(&a,0),1));

执行完operator=(&a,operator+(&operator++(&a,0),1));中(&operator++(&a,0)以后此时的a为1,然后拿返回的旧的对象(a为0)和1相加后的值为1,然后把相加后的结果赋值给a对象,所以a值从0变成1之后,又用1给a赋值,所以最终a的值为1

4.重载运算符什么时候需要用引用返回?什么时候需要用值返回?

如果需要返回运算符本身(如前置++,a+=b),就用引用返回,如果返回的是将亡值(如后置++,a=b+c),就用值返回

5.构造函数的三个作用
①创建对象
②初始化对象
③对于单参的构造函数可以类型转换

//单参参数
/* explicit */Int(int x):value(x)
{
	cout << "Create Int :" << this << endl;
}
//两个参数
Int(int x,int y = 0):value(x+y)
{
	cout << "Create Int :" << this << endl;
}
int main()
{
	Int a(10);
	int b = 100;
	a = b;
	return 0;
}

先构造Int类型的a,再构造 int 类型的b,然后把 int 类型的b的值赋值给 Int 类型的a,会有一个隐式转换,如果在 Int 的构造函数前面加上 explicit明确 关键字,就不允许隐式转换,必须显示转换,将 a = b; 改为 a = (Int)b; 或者 a = (Int)(200);也是正确的

如果不希望构造函数有隐式转换的能力,可以在构造函数前面加上 explicit 关键字

如果构造函数有两个参数是不可以的,所以构造函数的第三个作用只针对于构造函数只有一个参数,但是如果有两个参数的构造函数有一个默认值参数,也可以认为是一个参数,也是可以的,当然如果三个参数中有两个默认值参数也可以,依次类推

注意:
a = (Int)(b,100); 和 a = Int(b,100);是不一样的,第二个可以编译通过,是调用构造函数,创建一个无名对象,有一个默认值参数,所以可以进行隐式转换,第一个不能编译通过,它是以强转的方式构造了一个无名对象,必须是一个参数才可以

a = b;要想把内置类型(变量)赋值给自定义类型(对象),可以通过单参构造函数来实现

b = a;要想把自定义类型(对象)赋值给内置类型(变量),可以设计一个强转函数来实现,强转函数返回的类型就是强转后类型

operator int () const
{ return value; }

 b = a;//隐式转换
//b = a.operator int();
//b = operator int(&a);
 b = (int)a;//显示转换

定义了对象a和对象b,a<b对象和对象是不能相比的,所以有一个隐式转换的过程,用对象a的value值和对象b的value值作比较,a<x是用对象a的value值和x作比较,重载了类型强转运算符(把对象强转为某个内置类型,注意:强转后对象并不会变成内置类型,对象还是对象)

operator int() const
{ return value; }

int main()
{
	Int a = 10,b = 20;
	a < b;
	int x = 100;
	a < x;
	return 0;
}

6.mutable 易变关键字
mutable的中文意思是“可变的,易变的”,跟constant(既C++中的const)是反义词。在C++中,mutable是为了突破const的限制而设置的,被 mutable 修饰的变量,将永远处于可变的状态,即使在一个 const 函数中

我们知道,如果类的成员函数不会改变对象的状态,那么这个成员函数一般会声明成const的。但是,有些时候,我们需要在const的函数里面修改一些跟类状态无关的数据成员,那么这个数据成员就应该被mutalbe来修饰

下面举个例子可以通过加mutable关键字改变用const修饰的value的值:

class Add
{
	private:
		mutable int value;
	public:
		Add(int x = 0):value(x) {}
		int operator()(int a,int b) const
		{
			value = a + b;
			return value;
		}
};
int main()
{
	int a = 10,b = 20,c = 0;
	Add add;
	c = Add(a,b);
	return 0;
}

c = add(a,b);并不会去调动Add的构造函数,实际上编译器将它看待成c = add.operator()(a,b),add是一个对象,但是它的调动方式相当于函数,它实际上是对象,但是它的调动方式像函数一样,所以把它称之为仿函数,add相当于一个函数名,凡是重载了()运算符,就把它称之为仿函数,所以以后看到c = add(a,b);的时候,add可能是函数名也可能是对象,如果重载了()运算符就是对象

c = Add()(a,b)是什么意思?类型名+()调动构造函数产生一个临时对象(将亡值对象),将亡值对象调动自己的()运算符重载,把a和b的值相加

7.下面程序调动的过程是怎么样的?

class Int
{
	private:
		int value;
	public:
		Int(int x = 0):value(x) 
		{
			cout << "Create Int: " << this << endl;
		}
		Int(const Int& it):value(it.value)
		{
			cout << "Copy Create Int:" << this << endl;
		}
		Int& operator = (const Int& it)
		{
			if(this != &it)
			{
				value = it.value;
			}
			cout << this << " = " << &it << endl;
			return *this;
		}
		~Int()
		{
			cout << "Destroy Int : " << this << endl;
		}
};
class object 
{
private:
	int num;
	Int val;
public:
	Object(int x,int y)
	{
		cout << "Create Object:" << this << endl;
		num = x;
		val = y;
	}
	~Object()
	{
		cout << "Destroy Object:" << this << endl;
	}
};
int main()
{
	Object obj(1,2);
}

①程序从主函数开始执行,执行到Object obj(1,2);的时候系统给obj分配空间,但是此时还没有对象,然后先创建成员对象,调用Int的构造函数,打印出Create Int:xxxx xxxx
②然后调动obj的构造函数创建了对象obj,打印出Create Object:yyyy yyyy,注意在进入obj的构造函数函数体之前要创建出成员变量
②然后进入构造函数内部,先把x的值赋值给num,执行到val = y时因为我们不能直接把整型的值赋值给对象,所以会调动Int的构造函数创建一个临时对象(无名对象),打印出Create Int:zzzz zzzz之后
③再把临时对象赋值给val,会调动赋值运算符重载函数,打印出xxxx xxxx = zzzz zzzz
④当obj的构造函数结束时要调用Int的析构函数,析构临时对象(无名对象),打印出Destroy Int:zzzz zzzz
⑤然后main函数结束时,先析构对象本身,调用obj的析构函数,打印出Destroy Object:yyyy yyyy
⑥然后释放对象的成员对象,调用Int的析构函数,打印出Destroy Int:xxxx xxxx

如果将Object的构造函数改写成下面这种情况,那么函数的调动是什么样的呢?

Object(int x,int y) :num(x),val(y)
{
	cout << "Create Object:" << this << endl;
}

①先构造obj的成员val,调用Int的构造函数,打印出Create Int:xxxx xxxx
②然后调用obj的构造函数,打印出Create Object:yyyy yyyy
③然后调动obj的析构函数,打印出Destroy Object:yyyy yyyy
④然后调用Int的析构函数,析构obj的成员对象,打印出Destroy Object:xxxx xxxx

这时候构建就会非常简单,构建对象的次数会很少,所以对类的成员,尽可能的使用初始化列表的方式进行构建,可以使构建对象的次数减少,付出的代价减少

初始化列表变量的构建顺序和声明的顺序一致

8.可以实现自动管理,防止内存泄露

class Object
{
private:
	Int* ip;
public:
	Object(Int* s = NULL):ip(s)
	{
		
	}	
	~Object()
	{
		if(ip != NULL)
		{
			delete ip;
		}
		ip = NULL;
	}
};
int main()
{
	Object obj(new Int(10));
	return 0;
}

9.组合,一个对象里面包含另一个对象

①值类型组合,这样的组合称之为强关联,强关联是指当产生object对象时,object里面必然包含val对象

class Int {};

class Object
{
	int num;
	Int val;
};

Object中包含内置类型num和自定义类型val,val这个对象存在于Object中,是Object的一部分

②指针类型组合,这样的组合称之为弱关联,因为我现在指向的时堆区的对象,一会我可能指向堆区的另一个Int类型的对象,智能指针用的多

class Int {};

clas Object
{
	int num;
	Int *ip;
};

Object中包含内置类型num,自定义类型指针ip,ip指向的是堆区的Int类型的对象,不包含在Object对象中,不是Object的一部分

如何能够显示出指针所指的对象,然后进行相应的操作呢?就要重载两个重要的运算符,解引用运算符*和指向运算符->,解引用运算符直接返回堆区对象,指向运算符直接返回堆区对象的地址

class Object
{
private:
	Int* ip;
public:
	Object(Int* s = NULL):ip(s) {}	
	~Object()
	{
		if(ip != NULL)
		{ delete ip; }
		ip = NULL;
	}
	Int& operator*() //返回对象本身
	{ return *ip; }
	
	const Int& operator*() const
	{ return *ip;}
	
	Int* operator->() //返回对象的地址
	{ return &**this; }// *this就是对象obj,然后调用*重载返回对象本身,然后再取对象的地址
	
	const Int* operator->()
	{ return &**this; }
};
int main()
{
	Object obj(new Int(10));
	return 0;
}

operator&是取本对象的地址,operator->是取成员ip所指向的对象地址,是不同的概念

③引用类型组合,我们把这种组合称之为强引用,用的较少,前面两种用的较多

class Int {};

class Object
{
	int num;
	Int &val;
};

10.因为我们重载了解引用*运算符和指向->运算符,所以对象obj和裸指针具有相同的功能,所以用起来和指针很像,但是它比指针要好一些,原因是当主函数结束的时候,obj要调用析构函数,就把指向的对象释放了,但是对于裸指针,如果忘记delete,就会导致内存泄露,所以用对象的好处是我们只管申请,不需要管它什么时候释放,从而实现自动管理

class Object
{
private:
	Int* ip;
public:
	Object(Int* s = NULL):ip(s) {}	
	~Object()
	{
		if(ip != NULL)
		{ delete ip; }
		ip = NULL;
	}
	Int& operator*() //返回对象本身
	{ return *ip; }
	
	const Int& operator*() const
	{ return *ip;}
	
	Int* operator->() //返回对象的地址
	{ return &**this; }// *this就是对象obj,然后调用*重载返回对象本身,然后再取对象的地址
	
	const Int* operator->()
	{ return &**this; }// *this就是对象obj,然后调用*重载返回对象本身,然后再取对象的地址
};
int main()
{
	Object obj(new Int(10));
	Int *p = new Int(1);
	(*p).Value();
	(*obj).Value();
	p->Value();
	obj->Value();
	return 0;
}

标签:知识点,Int,Object,++,C++,int,对象,operator,175
来源: https://blog.csdn.net/weixin_45964837/article/details/122687421

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有