C++复试笔记(三)
1.友元函数和友元类
1.1友元函数
友元函数的经典实例是重载 "<<" 和 ">>" ,去重载operator<<,然后发现没办法将operator<<重载成成员函数。因为cout的 输出流对象和隐含的this指针在抢占第一个参数的位置。this指针默认是第一个参数也就是左操作 数了。但是实际使用中cout需要是第一个形参对象,才能正常使用。所以要将operator<<重载成 全局函数。但又会导致类外没办法访问成员,此时就需要友元来解决。operator>>同理。
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修饰
- 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
- 一个函数可以是多个类的友元函数
- 友元函数的调用与普通函数的调用原理相同
1.2友元类
- 友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
- 友元关系是单向的,不具有交换性。
比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接
访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。
- 友元关系不能传递
如果C是B的友元, B是A的友元,则不能说明C时A的友元。
- 友元关系不能继承。
class Time
{
friend class Date; // 声明日期类为时间类的友元类,则在日期类中就直接访问Time类
中的私有成员变量
public:
Time(int hour = 0, int minute = 0, int second = 0)
: _hour(hour)
, _minute(minute)
, _second(second)
{}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
void SetTimeOfDate(int hour, int minute, int second)
{
// 直接访问时间类私有的成员变量
_t._hour = hour;
_t._minute = minute;
_t._second = second;
}
private:
int _year;
int _month;
int _day;
Time _t;
};
2.解引用和后置++
int main()
{
int a[10] = { 0,1,-2,3,-4,5,-6,7,-8,9 }, * p = a + 1;
cout << *p++ << endl;
return 0;
}
-
然后,定义了一个指向整型的指针
p
并将其初始化为a + 1
。这里,a
是数组名,也可以作为指向数组第一个元素的指针使用。所以,a + 1
就是指向数组第二个元素的指针(即指向值为1
的那个元素)。 -
cout << *p++ << endl;
这行代码执行了以下操作:*p
:首先解引用指针p
,得到它当前指向的值,这里是1
。p++
:然后,执行p
的自增操作,使p
指向下一个元素。但是需要注意的是,根据C++运算符优先级,p++
在此上下文中是在*p
获取值之后才进行的,这意味着这次自增不会影响本次解引用的结果。
因此,最终输出的是 1
,这是 p
初始指向位置的值。在输出之后,p
将会指向数组中的下一个元 素 -2
,但这个变化不影响已经输出的结果。
3.数组指针
int main()
{
int a[][3] = { 1,-2,3,-4,5,-6 }, (*p)[3] = a + 1;
cout << **p << endl;
return 0;
}
(*p)[3] = a + 1;
首先要明确的是,p是一个指针,因为加上了()。这里定义了一个指针 p
,它指向一个包含3个整数的数组。a + 1
指向数组 a
的第二行(即 a[1]
),因此 p
直接指向了 a
的第二行。
4.new对象要显示调用析构函数
#include<iostream>
#include<string>
using namespace std;
class Person {
private:
int age;
public:
string name;
Person(int a, string n = "Hqu") {
age = a;
name = n;
cout << "constructing..." << n << endl;
}
~Person() {
cout << name << "being destructed!" << endl;
}
};
int main() {
Person* p1 = new Person(19, "BUAA");
Person p2(50, "dong");
return 0;
}
当你使用
new
来分配内存时,需要显式地使用 delete
来释放这块内存,否则析构函数不会被调用,导致可能的内存泄漏。
而普通构造函数,在程序结束后会自动调用析构函数。
5.前置++、后置++和短路
int main()
{
int i = 1, j = 0;
if (--i && j++)
//if(j++ && --i)
cout << i << "," << j << endl;
else
cout << i << "," << j << endl;
return 0;
}
关键在于理解if (--i && j++)
这一行。这里使用了逻辑与(&&
)运算符,它具有短路特性。具体来说,当且仅当左侧表达式为真(非零)时,才会计算右侧表达式。在这个例子中:
--i
:首先执行,将i
从1减到0。此时--i
的结果是0。- 因为
--i
的结果是0(即假),根据&&
运算符的短路特性,程序不会计算&&
后面的j++
部分,因为无论j++
的结果是什么,整个表达式的最终结果都已经是假了(只要&&
左边的操作数为假,整个表达式就为假)。
因此,在这个特定情况下,尽管if
语句的条件判断导致执行了else
分支,但由于j++
并未被执行(由于短路),j
的值并不会增加。所以,输出结果仍然是:0,0
int main()
{
int i = 1, j = 0;
//if (--i && j++)
if(j++ && --i)
cout << i << "," << j << endl;
else
cout << i << "," << j << endl;
return 0;
}
调整了条件判断的顺序,现在逻辑表达式是 if(j++ && --i)
。这个变化很重要,因为它影响了短路求值的行为和操作符的执行情况。
- 首先定义两个整数变量
i
和j
,并分别初始化为1
和0
。 - 在
if
语句中,首先计算j++
。这里需要注意的是,j++
是后置递增操作,意味着它会先使用j
的当前值(这里是0
)进行逻辑与运算,然后再将j
增加1。 - 因为
j++
使用的是j
的原始值0
,所以j++ && --i
这个表达式的左部分已经是false
(即0)。根据逻辑与运算的短路特性,一旦确定左边为假,则不会评估右边的表达式。因此,--i
不会被执行。 - 尽管
--i
没有被执行,但由于j++
的存在,j
的值会在这一行结束后增加到1
。
因此,程序将会输出 i
和 j
的值,其中 i
保持不变,因为 --i
没有被执行;而 j
则由于 j++
而从 0
变成了 1
。最终输出结果将是:1,1