上篇我们对空类进行了介绍,这篇对类与对象进行结尾.
内容介绍:
1.再看构造函数
2.static成员
3.C++11 的成员初始化新玩法
4.友元
5.内部类(了解)
目录
一.再看构造函数
1.构造函数:
在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值,我们在类与对象中阶的时候已经全面的了解过构造函数了,而接下来我们将打破上一章对构造函数的认知.
以date类为例:(只看构造函数)
一次赋初值:
Date(int year, int month, int day) {
_year = year;
_month = month;
_day = day;
}
多次赋初值:
Date(int year, int month, int day) {//对成员变量多次赋初值,并不会发生错误.
_year = year;
_month = month;
_day = day;
_year = 1;
_day = 2;
_month = 3;
}
在上一章我们进行构造函数的讲解时,都是以上面这种代码来对实例化对象进行初始化的,但实际这种方式并不是初始化,更应该称其为赋初值,因为初始化只能初始化一次,而构造函数体内可以多次赋值,那么到底什么是初始化?赋初值和初始化的区别是什么?我们继续看后面.
2.初始化和赋初值:
(1)初始化列表:
以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式,c++规则中给出了这种方式让我们进行真正的初始化,在此初始化列表中每个类成员变量只能进行一次赋值,也就做到真正的初始化.
以Date类为例,处在函数名和函数体中间的这块,称为初始化列表:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{ }
(2)初始化列表的四个特性:
①.每个成员变量在初始化列表中只能出现一次.(验证了上面的初始化只能初始化一次)
②.类中包含以下成员,必须放在初始化列表的位置进行初始化.
a.引用成员变量: 我们在学习引用的时候,就明白必须初始化是引用的特性.
b.const成员变量: const类型的成员变量的值在初始化后就无法再改变了,因此必须初始化.
c.自定义类型成员.(该类没有默认构造函数): 在实例化没有默认构造函数的对象成员时,编译器无法自动调用该类的构造函数对其进行初始化,因此则需要我们再初始化列表中手动调用其构造函数完成该成员对象的初始化.
③.尽量使用初始化列表进行初始化,因为不管你是否使用初始化列表,对于自定义类型的成员变量,一定会先使用初始化列表来进行初始化.
④.成员变量在类中的声明次序,就是其在初始化列表中的初始化顺序.与其在初始化列表中的先后次序无关.
例如: 对于下面这个类,其成员变量的声明次序为:_a,_b,_c,因此无论这些成员变量在初始化列表中是什么顺序,都是先初始化_a,再是_b,最后_c.
class A {
public:
A(int a = 1, int b = 2, int c = 3)
:_a(a)
,_c(c)
,_b(b)
{}
private:
int _a;
int _b;
int _c;
};
3.explicit关键字
来源:
构造函数不仅可以构造和初始化对象,对于单个参数或全缺省的构造函数,还具有类型转换的作用.
介绍:
以Date类为例: 我们会发现明明没有对数字和对象类型的赋值运算符进行重载,那么这段代码为什么还能运行成功呢?(默认生成的赋值运算符是对象对对象进行赋值的)
class Date {
public:
Date(int a)//单参构造函数
:_a(a)
{}
void Print() {
cout << _a << " " << _b << " " << _c << endl;
}
private:
int _a;
int _b;
int _c;
};
int main() {
Date d1(1);
d1 = 4;
d1.Print();
return 0;
}
运行结果:
原因:
第一步是创建一个d1类型的对象,初始值为1.
第二步我们是直接将4赋值给d1对象,因为类中有默认的赋值运算符重载的原因,在编译器中会先将其转化为: d1.operator=(4),然后我们再来看编译器自动生成的默认赋值运算符重载是怎样定义的: Date operator=(Date b),现在我们在进行对应,4是实参被传进来之后,作为临时对象b创建时的初始值--->Date b=4,以这种形式存在,刚好此时类中存在只有一个参数的构造函数,发生隐式转换该转换成立,最后完成赋值,那么问题就来了,我们明明没有对数字和对象的赋值运算符进行重载,为什么这个等式可以完成呢?因此为了防止这类事情的发生,引入了explicit关键字,只要在单个参数的构造函数前面加上此关键字,类似的隐式转化就不会发生.(全缺省构造函数也相同)
二.static成员
1.概念
声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数,静态的成员变量一定要在类外进行初始化.
2.特性
1. 静态成员为所有类对象所共享,不属于某个具体的实例.
2. 静态成员变量必须在类外定义,定义时不添加static关键字.
3. 静态成员可用类名::静态成员或者对象.静态成员来访问.(推荐使用类名::静态成员,因为对象.静态成员最终也会被转化成类名的方式)
4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员.
5. 静态成员和类的普通成员一样,也有public、protected、private3种访问级别,也可以具有返回值.
3.实现一个类,计算中程序中创建出了多少个类对象
class A{
public:
A() {++_scount;}
A(const A& t) {++_scount;}
static int GetACount() { return _scount;}
private:
static int _scount;
};
int A::_scount = 0;
void TestA(){
cout<<A::GetACount()<<endl;
A a1, a2;
A a3(a1);
cout<<A::GetACount()<<endl;
}
我们对其进行分析,首先创建了两个对象a1,a2,此时_scount成员变量++两次,值为2,然后创建a3,调用拷贝构造函数,使_scount又进行了一次++,此时_scount的值为3,我们也正好创建了3个对象.
运行结果:
4.静态成员函数可以调用非静态成员函数吗?
不可以,非静态成员函数中有隐藏的this指针,只能由对象来调用,静态成员函数不能给其this指针传参.
5.非静态成员函数可以调用类的静态成员函数吗?
可以,类的静态成员函数是所有对象公有的可以直接访问,因此可以被非静态成员函数直接调用.
三.C++11 的成员初始化新玩法.
介绍:
C++11支持非静态成员变量在声明时进行初始化赋值,但是要注意这里不是初始化,这里是给声明的成员变量缺省值.
例子:
class B{
public:
B(int b)
:_b(b)
{}
int _b;
};
class A{
public:
void Print(){
cout << a << endl;
cout << b._b << endl;
cout << sizeof(p) << endl;
}
private:
// 非静态成员变量,可以在成员声明时给缺省值。
int a = 10;
B b = 20;
int* p = (int*)malloc(4);
static int n;
};
int A::n = 10;
int main(){
A a;
a.Print();
return 0;
}
运行结果:
四.友元
1.作用
友元提供了一种突破封装的方式,有时提供了便利,但是友元会增加耦合度,破坏封装,所以友元不宜多用.
2.友元函数
(1).为什么需要友元函数.
如果我们需要对<<进行重载,在类中直接使用: ostream& operator<<(ostream& _cout),进行重载,重载完之后我们会发现在调用的时候为: 对象<<cout,因为非静态的类成员函数都有隐藏的this指针,所以必然会发生这种情况,与实际的cout形式不同,所以我们不能将其放在类中进行重载,因此在类外试试将其重载成全局函数: ostream& operator<<(ostream& _cout,类类型 对象),这时候我们再次调用就为: cout<<对象,但是因为该重载函数是一个类外的全局函数,因此它不能访问类中的私有成员变量,为了解决这个问题我们引入了友元函数,我们只需在类中对该重载函数进行友元声明,那么他就不受访问限定符的影响,从而完成我们的需求了.
(2).声明方式.
友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但是需要在类的内部声明,声明时需要加friend关键字.
例子:
class Date {
friend ostream& operator<<(ostream& _cout, const Date& d);
friend istream& operator>>(istream& _cin, Date& d);
public:
Date(int year = 1, int month = 3, int day = 3)
: _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;
}
(3)特性:
1.友元函数可以访问类的私有和保护成员,但它自己不是类的成员函数.
2.友元函数不能用const修饰.
3.友元函数可以在类中任何地方声明,不受类访问限定符的限制.
4.一个函数可以是多个类的友元函数.
5.友元函数的调用与普通函数的调用和原理相同.
3.友元类
(1).概念
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员.
(2).例子
在date类的成员函数中可以直接访问time类的私有成员变量.
class Date; // 前置声明
class Time {
friend class Date; // 声明日期类为时间类的友元类,则在日期类中就直接访问Time类中的私有成员变量
public:
Time(int hour = 1, int minute = 2, int second = 3)
: _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;
};
(3).特性
1.友元关系是单向的,不具有交换性.
eg: A是B的友元类,A中的成员函数可以访问B中的非公有成员,但B中的成员函数并不能访问A中的非公有成员.
2.友元关系不能传递.
eg: 如果B是A的友元,C是B的友元,并不能说明C时A的友元.
五.内部类(了解)
1.概念
如果一个类定义在另一个类的内部,这个内部类就叫做内部类,注意此时这个内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去调用内部类,外部类对内部类没有任何优越的访问权限.
2.注意
内部类就是外部类的友元类,注意友元类的定义,内部类的成员函数可以用外部类的对象作为参数来访问外部类中的所有成员.(外部类不是内部类的友元)
3.特性
1.内部类可以定义在外部类的public、protected、private都是可以的.
2.注意内部类可以直接访问外部类中的static、枚举成员,不需要外部类的对象/类名.
3.sizeof(外部类)=外部类,和内部类没有任何关系.
4.例子
class A{
private:
static int k;
int h;
public:
class B{
public:
void foo(const A& a){
cout << k << endl;//OK
cout << a.h << endl;//OK
}
};
};
int A::k = 3;
int main(){
A::B b;
b.foo(A());
return 0;
}