类与对象(下)
首先,我们再次提及构造函数
一、构造函数
构造函数体赋值
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
在这段代码中,我们写的构造函数,实际是对对象的私有变量_year,_month,_day进行赋初始值,这不能达到初始化的效果。我们要知道,初始化只有一次,而赋值可以有多次。
初始化列表
什么是初始化列表?
构造函数的初始化列表是一种用于在创建对象时对成员变量进行初始化的特殊语法。
形式:它出现在构造函数的函数体之前,用冒号开始,初始化列表里的语句用逗号“,”分隔。
具体就如下面的代码一样:
class Date
{
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
这里再提及一个点,有几种情况是需要我们去进行初始化处理的:
①首先是引用成员变量
②const修饰的成员变量
③无默认构造函数的自定义类型成员
譬如引用成员变量,我们之前引用有学过,定义时就一定得初始化,而不能不初始化等到后面再去赋值。 const修饰的变量,我们无法对其进行赋值,但我们可以对其进行一次初始化。
对于自定义类型成员变量,会先使用初始化列表进行初始化。
初始化列表的作用
首先,使用了初始化列表就可以在创建对象时直接对成员变量进行初始化处理,而不需要进入构造函数的函数体中再次进行赋值操作,因此可以提高代码效率。
并且,在上面列举的三种情况下,是很有必要去进行初始化的!
成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后 次序无关
class A
{
public:
A(int a)
:_a1(a)
,_a2(_a1)
{}
void Print() {
cout<<_a1<<" "<<_a2<<endl;
}
private:
int _a2;
int _a1;
};
int main() {
A aa(1);
aa.Print();
}
//A. 输出1 1
//B.程序崩溃
//C.编译不通过
//D.输出1 随机值
在这个题目中,按平常思维,或许是这样做的:传值a=1,把a赋值给a1,再又将a1赋值给a2,或许答案是A,但实则不然。
上面说了成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后 次序无关。该题目先定义的a2,再定义的a1。因此在初始化时,会先进行a2的初始化,由于此时a1还没给定初始值是个随机值,则a2会被赋值一个随机值。接着才进行a1的初始化,把a赋值给a1为1。所以这道题的最终答案是D。
好,接着还给大家看一个易错题:
//有一个类A,其数据成员如下: 则构造函数中,成员变量一定要通过
//初始化列表来初始化的是:
class A {
...
private:
int a;
public:
const int b;
float* &c;
static const char* d;
static double* e;
};
前面说了有三种情况需要进行初始化处理。这道题中或许会选择b,c,d。 但这里需要注意d是不能进行初始化处理的,即使它是const成员变量,但由于它被static修饰,它被储存在静态区里了。static修饰的成员变量只能在类外进行初始化。所以通过初始化列表进行初始化的只有b和c。
二、static成员
声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用
static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化
特性:
① 静态成员变量必须在类外定义,类外定义时不添加static关键字,类中只是声明
② 静态成员也是类的成员,受public、protected、private 访问限定符的限制
③静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区(这个有些类似于我们写的成员函数)
④类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
⑤静态成员函数没有隐藏的this指针,不能访问任何非静态成员
class A
{
public:
A() { ++_scount; }
A(const A& t) { ++_scount; }
~A() { --_scount; }
static int GetACount() { return _scount; }
private:
static int _scount; //类中声明
};
int A::_scount = 0; //在类外定义,并且不需要添加static关键词
void TestA()
{
cout << A::GetACount() << endl; //类名::静态成员
A a1;
cout << a1.GetACount() << endl; //对象.静态成员
}
三、友元
友元是一个突破限制的东西。利用友元可以实现一些权限的突破。接下来详细介绍友元:
友元被分为友元函数和友元类
友元函数:
class Date
{
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
ostream& operator<<(ostream& _cout)
{
_cout << _year << "-" << _month << "-" << _day << endl;
return _cout;
}
private:
int _year;
int _month;
int _day;
};
我们在使用operate<<(ostream& _cout)的时候会发现。第一个参数是cout,this指针抢占了第一个位置,也就是说我们使用 cout<<“_year” 的时候,this指向的是cout而不是Date类的对象,因为_year是私有的,就导致这里的_year就无法被访问。因此,为了解决此类问题,我们得学习友元的概念。
当然不用友元也有一种解决办法:
d1 << cout; -> d1.operator<<(&d1, cout);
因为成员函数第一个参数一定是隐藏的this,所以d1必须放在<<的左侧。但明显,这样不符合常规调用,如果代码都以这样的形式写,就会很难看。
友元函数可以直接访问类的私有成员, 它是定义在类外部的普通函数,不属于任何类, 但需要在类的内部声明,声明时需要加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修饰
③友元函数可以在类定义的任何地方声明,不受类访问限定符限制
④一个函数可以是多个类的友元函数(可以在多个类中被定义使用)
⑤友元函数的调用与普通函数的调用原理相同
⑥友元函数不具备this指针
友元类:
友元类是一个很有意思的东西。但是友元类具有单向性,用下面的代码举个例子:
class Time
{
friend class Date;// 声明Date类为Time类的友元类,则在Date类中就直接访问Time类
// 中的私有成员变量。但是!Time类不能去访问随意Date类中的私有成员变量
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)
{
//直接访问Time类私有的成员变量
_t._hour = hour;
_t._minute = minute;
_t._second = second;
}
private:
int _year;
int _month;
int _day;
Time _t;
};
定义过后,Date类就可以随意得进行对Time类私有变量的访问了!
与此同时,友元关系不能传递。如果C是B的友元, B是A的友元,则不能说明C时A的友元。
四、内部类
概念:如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。
注意:内部类就是外部类的友元类,参见友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员, 但是外部类不是内部类的友元,因此外部类不能随意访问内部类的私有成员。
class A
{
private:
static int k;
int h;
public:
class B // B天生就是A的友元,但A不是B的友元
{
public:
void foo(const A& a)
{
cout << k << endl;//OK
cout << a.h << endl;//OK
}
};
};
int A::k = 1;
int main()
{
A::B b;
b.foo(A());
return 0;
}
五、 匿名对象
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A(int a)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
class Solution {
public:
int Sum_Solution(int n) {
//...
return n;
}
int main()
{
A aa1; //原先,我们定义对象都是以这样的形式
A aa1(); //但不能这么定义对象,因为编译器无法识别下面是一个函数声明,还是对象定义
// 但是我们可以这么定义匿名对象,匿名对象的特点不用取名字,
A();
// 但是他的生命周期只有这一行,我们可以看到下一行他就会自动调用析构函数
// 匿名对象在这样场景下就很好用
Solution().Sum_Solution(10); //譬如,我们只需通过该类去访问一个函数时,这个对象
//我们就没有必要具体的写出来,就可以采用匿名对象的方式
return 0;
}
};
六、拷贝对象时的一些编译器优化
在传参和传返回值的过程中,一般编译器会做一些优化,减少对象的拷贝,这个在一些场景下还是非常有用的。
class A
{
public:
A(int a = 0)
:_a(a)
{}
A(const A& aa)
:_a(aa._a)
{}
~A()
{}
A& operator=(const A& aa)
{
if (this != &aa)
{
_a = aa._a;
}
return *this;
}
private:
int _a;
};
void f1(A aa)
{}
A f2()
{
A aa;
return aa;
}
int main()
{
// 传值传参,这里先构造出aa1,然后将aa1传给f1函数处的形参
// 相当于进行了一次构造和一次拷贝构造
A aa1;
f1(aa1);
// 传值返回,先构造出aa,再将aa作为返回值传回来,
// 传回来的过程就是一次拷贝构造
f2();
// 隐式类型,连续的(构造+拷贝构造)->优化为直接构造
f1(1);
// 因为传参1和f1处的形参A类型不同,因此1会有一个隐式类型的转换
// 调用构造函数,再接着将实参传给形参进行一次拷贝构造
// 像这样连续在一起的构造就拷贝构造就直接优化为直接构造了
//一个表达式中,连续的(构造+拷贝构造)->优化为一个构造,类比上面
f1(A(2));
// 一个表达式中,连续的(拷贝构造+拷贝构造)->优化一个拷贝构造
A aa2 = f2();
//原本应该是构造aa后,将aa拷贝给f2,f2再拷贝构造给aa2
//这里直接优化,连续的拷贝构造+拷贝构造就被优化成了一个拷贝构造
//甚至部分编译器可以进一步优化,构造+拷贝构造又可以优化成构造
// 一个表达式中,连续拷贝构造+赋值重载->无法优化
aa1 = f2();
//要注意的是这里是进行赋值运算符,而不是进行拷贝构造
return 0;
}
好的,终于结束类与对象的相关内容。支持一下,点个赞吧!