C++:类和对象(下)

发布于:2024-05-23 ⋅ 阅读:(132) ⋅ 点赞:(0)

一、隐式类型转换

1.单参数的隐式类型转换

       构造函数不仅可以构造与初始化对象,对于接收单个参数的构造函数,还具有类型转换的作用。例如下面这段代码:

int main()
{
	Date date;//调用无参构造函数
	Date date1 = date;//拷贝构造函数
	Date date2 = 3;//隐式类型转换

	return 0;
}

       第三种构造方式是隐式类型转换,将内置类型转换为自定义类型。单参数的构造函数支持隐式类型转换。编译器先生成为一个临时对象,再将这个临时对象拷贝构造给date2变量。但是不能创建一个引用类对象去引用这个3。如下面这段代码:

int main()
{
	Date date;//调用无参构造函数
	Date date1 = date;//拷贝构造函数
	Date date2 = 3;//隐式类型转换
	Date& date3 = 3;
	return 0;
}

       实际上引用的是3的临时变量。临时变量具有常性,是const修饰过后的临时变量,所以被date3引用相当于权限的放大。因此需要在类名前加const就可以。

       另外,在隐式类型转换这一条语句中,涉及到了构造和拷贝构造,编译器会直接优化成只调用一次构造函数。

2.隐式类型转换的应用

       当我们在一个数据结构中添加类类型的元素时,就可以使用隐式类型转换。例如我们想往栈中压入一个元素,就可以这样写:

class Date
{
public:
	Date()
	{
		_year = 0;
		_month = 1;
		_day = 2;
		cout << "Date()" << endl;
	}
	Date(int n)//带参数的构造函数
	{
		cout << "Date(int n)" << endl;
		_year = n;
		_month = n;
		_day = n;
		
	}
	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	int _year;
	int _month;
	int _day;
};

class Stack
{
public:
	
	void Push(Date d)//添加元素
	{
		// ...
	}
private:

};

int main()
{
	Stack st1;
	Date date1(5);
	st1.Push(date1);


	return 0;
}

       我们想将一个日期类对象date1压入栈中,就先使用栈类实例化出一个栈,然后再使用日期类实例化一个日期对象,将日期对象插入到栈中,栈具体实现细节先省略。这样就会调用两次构造函数,一次拷贝构造。

       但是当我们学习了隐式类型转换,就可以这样写:

class Date
{
public:
	Date()
	{
		_year = 0;
		_month = 1;
		_day = 2;
		cout << "Date()" << endl;
	}
	Date(int n)//带参数的构造函数
	{
		cout << "Date(int n)" << endl;
		_year = n;
		_month = n;
		_day = n;
		
	}
	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	int _year;
	int _month;
	int _day;
};

class Stack
{
public:
	
	void Push(const Date& d)//添加元素
	{
		// ...
	}
private:

};

int main()
{
	Stack st1;
	st1.push(5);
	return 0;
}

       将push函数的参数改为接受一个Date&类型的常量,然后将内置类型传进去,就会触发隐式类型转换,可以让我们少写一条语句。同时对于默认成员函数,由于接受参数时使用了引用,可以减少一次拷贝构造函数的调用。因此使用隐式类型转换效率更高,同时更方便。

3.explicit关键字

       如果不想让隐式类型转换发生,就在构造函数的前面加上explicit关键字即可。加上之后,就会提示报错:

error C2664: “void Stack::Push(const Date &)”: 无法将参数 1 从“int”转换为“const Date &”
1>class“Date”的构造函数声明为“explicit”

 

二、static成员

1.static修饰成员变量

       对象中只存成员变量,不在存在成员函数,也不存经过static修饰的成员变量。经过static修饰后的成员变量存于静态区中。初始化列表忽略static修饰过的成员,因此不能给缺省值初始化。只能在全局域中进行初始化。

class Date
{
public:
	Date()
	{
		_year = 0;
		_month = 1;
		_day = 2;
		cout << "Date()" << endl;
	}
	Date(int n1, int n2, int n3)//带参数的构造函数
	{
		cout << "Date(int n)" << endl;
		_year = n1;
		_month = n2;
		_day = n3;
	}
	Date(const Date& d)//拷贝构造函数
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
		cout << "Date(const Date& d)" << endl;
	}
	int _year;
	int _month;
	int _day;
	static int _scount;
};

//初始化
int Date:: _scount = 0;

int main()
{
	Date date1;
	Date date2;
	Date date3;
	Date date4;
	Date date5;
	//st1.Push(5);
	cout << Date::_scount << endl;

	return 0;
}

       static修饰成员变量的应用

       由于static修饰成员变量是属于所有对象的,每个对象都可以访问它,那么我们可以在类的构造函数中加上一条语句,实现统计创建多少个变量的代码:

class Date
{
public:
	Date()
	{
		_year = 0;
		_month = 1;
		_day = 2;
        //统计创建了多少个对象
		_scount++;
		cout << "Date()" << endl;
	}
	Date(int n1, int n2, int n3)//带参数的构造函数
	{
		cout << "Date(int n)" << endl;
		_year = n1;
		_month = n2;
		_day = n3;
	}
	Date(const Date& d)//拷贝构造函数
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
		cout << "Date(const Date& d)" << endl;
	}
	int _year;
	int _month;
	int _day;
	static int _scount;
};

//初始化
int Date:: _scount = 0;

int main()
{
	Date date1;
	Date date2;
	Date date3;
	Date date4;
	Date date5;
	//st1.Push(5);
	cout << Date::_scount << endl;

	return 0;
}

       即使定义了函数,在函数中创建对象,也可以统计:

Date func()
{
	Date date6;
	return date6;
}

int main()
{
	Date date1;
	Date date2;
	Date date3;
	Date date4;
	Date date5;
	func();
	//st1.Push(5);
	cout << Date::_scount << endl;

	return 0;
}

       另外,我们可以再析构函数中加上让_scount--的语句,来统计实时存在的对象数量。

       运行程序,可以看到结果如图所示:

       在函数func()调用结束,date6调用析构函数,_scount--,此时还存在的变量数为5,接着依次调用每个析构函数释放每个对象。

2.static修饰成员函数

       static修饰的成员函数没有this指针,只能访问静态成员。不能访问成员变量,只能通过公有静态函数访问。

三、友元

1.友元函数

       当我们想实现operator<<(重载流插入运算符)时,传递的第一个参数为cout,第二个为对象,但是实现在类里面时,会发现第一个参数被this指针占用了,而操作符运算顺序是与传参顺序相关的,因此我么想使用流插入运算符重载时就只能这样使用:

d1 << cout;

       这种使用方式跟我们日常使用方式相反,那想要实现顺序和日常使用一样,只能将运算符重载实现在类外面,这样就不会存在this传参的问题,我们可以自行决定传参的顺序。但是定义在类外面,那么类中私有的成员变量将无法访问,这个时候需要用友元来解决。

       友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。

class Date
{
	friend ostream& operator<<(ostream& _cout, const Date& d);
	friend istream& operator>>(istream& _cin, Date& d);
public:
	Date(int year = 1900, int month = 1, int day = 1)
		: _year(year)
		, _month(month)
		, _day(day)
	{}
private:
	int _year;
	int _month;
	int _day;
};
ostream& operator<<(ostream& _cout, const Date& d)
{
	_cout << d._year << "-" << d._month << "-" << d._day;
	return _cout;
}
istream& operator>>(istream& _cin, Date& d)
{
	_cin >> d._year;
	_cin >> d._month;
	_cin >> d._day;
	return _cin;
}
int main()
{
	Date d;
	cin >> d;
	cout << d << endl;
	return 0;
}

       说明:

  • 友元函数可访问类的私有和保护成员,但不是类的成员函数
  • 友元函数不能用const修饰
  • 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
  • 一个函数可以是多个类的友元函数
  • 友元函数的调用与普通函数的调用原理相同

2.友元类

       当一个类中的成员函数想访问另一个类中的成员变量,那么可以将那个类声明为这个类的友元类,与友元函数一样,只需在类中的任意位置加上声明即可。不过一般是加在类中的开头处。

friend class Date;
  • 友元关系是单向的,不具有交换性。比如Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。
  • 友元关系不能传递。如果B是A的友元,C是B的友元,则不能说明C时A的友元。

3.内部类

       类中不仅可以定义成员变量、成员函数,也可以定义类。在类中定义的类称为内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。内部类就是外部类的友元类。

  • 内部类可以定义在外部类的public、protected、private都是可以的。
  • 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。
  • sizeof(外部类)=外部类,和内部类没有任何关系。

四、匿名对象

       C++中还有匿名对象的概念,即没有名字的对象:

int main()
{
	Date date1(1, 2, 3);
	//匿名对象
	Date(1, 2, 3);

	return 0;
}

       匿名对象的生命周期只有它所在的这一行,即即用即销毁。