一、隐式类型转换
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;
}
匿名对象的生命周期只有它所在的这一行,即即用即销毁。