编程语言
首页 > 编程语言> > 175-C++重要知识点6

175-C++重要知识点6

作者:互联网

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