接着我们就继续学习我们C++当中的相关的知识。
一:初始化列表
还记得我们之前讲过的构造函数吗?我们在构造函数的函数体里面可以对对象当中的属性进行初始化。但是作为我们的构造函数来说,初始化的方式并不只是在构造函数体当中进行赋值。我们还可以构造函数列表当中进行对属性进行初始化。先举一个简单的例子:
#include<iostream>
//定义一个类,使用初始化列表进行初始化
class Date
{
public:
Date(int year = 2022, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{
//可以省略函数体当中的内容,直接在初始化列表当中初始化我们想要的内容
}
void print()
{
std::cout << _year << "年" << _month << "月" << _day << "日" << std::endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
//使用初始化列表初始化一个对象,检查初始化的效果
Date d1(2023, 7, 22);
d1.print();
return 0;
}
根据上面的例子当中,我们可以发现我们的构造函数当中的原本应该在函数体当中初始化的内容,我们却以一种奇奇怪怪的方式初始化出来。其实那种方式就是我们的初始化列表。我们在函数体之前先声明出要定义的对象。_year的部分表示我们类当中想要初始化的对象,后面括号里面的表示的是我们在形参当中传入的内容。并且初始化列表有一个要求,第一个初始化属性需要使用 : 进行引出,之后的都需要使用 , 进行分隔。观察初始化的结果:
我们会发现我们使用初始化列表所初始化完成的对象和我们之前使用函数体初始化完成的得到的效果完全相同。那我们又为什么要重新定义出一个初始化列表初始化对象呢?
这是因为,使用函数体初始化的内容并不是全部,有的几个成员变量必须使用初始化列表才可以初始化。我们就来一一举例。
1.使用引用的成员变量
我们之前说过引用并不能在初始化的时候定义一个空引用,也就是没有指向的对象。并且一旦定义完成之后就不能再继续进行修改。所以我们想要初始化起来就很麻烦,于是我们便有了初始化列表来解决这个问题,我们可以再初始化列表当中进行对引用的成员变量进行赋初值。
使用函数体尝试初始化引用变量,我们会发现我们的编译器会报错:目标必须是初始化引用,无法编译完成。之后我们再通过初始化列表进行初始化引用对象:
使用初始化列表进行初始化确实可以正常运行的。
2.const成员变量
使用函数体初始化会产生报错,所以使用初始化列表进行尝试:
使用初始化列表可以正常的运行。
3.自定义类型成员(且该类没有默认构造函数时)
同样的我们先使用函数体初始化不具有默认参数的成员变量:
我们会发现编译器依旧会产生报错,之后我们再来使用初始化列表进行初始化:
上面三种成员变量我们只可以使用初始化列表进行初始化,这也是初始化列表产生的意义。在我们正常的程序编写的过程当中,我们比较推荐使用初始化列表进行初始化我们对象的成员变量,这是因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使 用初始化列表初始化。所以为了减少我们的区分,我们可以直接使用初始化列表进行初始化操作。
还有一点极为重要的是:我们的初始化列表想要初始化成员变量,初始化的顺序是按照我们下方定义的私有的成员变量的顺序所进行的,所以假如我们想要使用之前定义好的变量继续初始化的时候就需要注意初始化顺序了,否则就会产生不必要的错误。
就比如我们上面的代码所展示的一样,我们想要传一个参数同时初始化两个成员变量,所以我们先使用参数初始化了一个变量,之后使用初始化的变量又去初始化另一个变量,我们想要的到的预期的结果是两个 1 。但是我们实际得到的结果却是:
就像我们上图所示的一个1一个随机值,但是为什么会出现这样的结果呢?这个时候就需要用到我们上面所说到的:其实我们初始化的顺序是按照我们在private当中定义的顺序来的。
我们会发现我们先进行的a2初始化的时候是调用的a1,但是我们这个时候a1还没有进行初始化,我们得到的结果还是一个随机值。所以我们在之后打印的时候就会得到一个1,一个随机值的结果。所以我们在以后编写程序的时候需要养成良好的编写代码的习惯。需要按照下面定义的列表的顺序书写初始化列表,这个时候就可以避免上面的错误产生了。修正过的代码运行的结果:
二:static修饰的成员变量
介绍完初始化列表我们接下来就来介绍一下static修饰的成员变量。
和我们C语言当中的使用方式是一样的,我们使用的static修饰的变量会被系统放在静态区,这个时候在调用完函数的时候我们的变量就不会被销毁,可以实现累加等操作。
同样的在类当中也可以定义静态成员变量,需要注意的是我们使用static修饰的静态成员变量在初始化的时候一定得在类的外面,也就是全局的范围内定义,否则就会产生报错。
如上面所示。为了解决我们上面产生的错误,我们需要将变量初始化到全局的范围内:
静态成员变量还具有五大特性:
1. 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
2. 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
3. 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
5. 静态成员也是类的成员,受public、protected、private 访问限定符的限制
所以当我们的类当中同时定义了静态成员函数和非静态成员函数的时候我们就需要仔细进行判断了,因为我们的静态成员函数是存放在静态区的,所以其中并不存在this指针,也正是因此,我们在调用的时候要想使用静态成员函数调用非静态成员函数就需要显示的传参使用,但是要是想要在非静态成员变量当中调用静态成员函数,那么我们就可以直接使用了。
三:友元
还记得我们之前使用到的友元函数吗?我们当时定义了一个全局范围内的函数,但是我们在函数当中又想调用我们类当中设立的私有的成员变量,在那个时候我们就是用到了友元。
但是我们在使用友元函数的时候需要慎重,不能过多的使用友元函数,因为友元函数增加了我们程序之间的耦合度,(也叫做关联度,使得每一个函数和函数之间可以相互访问,违反了封装性的原则,所以要慎用)
同样的我们也可以定义一个友元类,被设置成由友元类的对象我们同样可以访问其的私有成员变量。
#include<iostream>
//定义一个时间类
class Time
{
friend class Date;
public:
//定义一个构造函数
Time(int hour = 10, int minute = 18, int second = 23)
{
_hour = hour;
_minute = minute;
_second = second;
}
void print()
{
std::cout << _hour << "时" << _minute << "分" << _second << "秒" << std::endl;
}
private:
int _hour;
int _minute;
int _second;
};
//设置一个日期类,并将日期类设置成时间类的友元函数
class Date
{
private:
int _year;
int _month;
int _day;
Time _t; //尝试在Date当中访问Time类的私有变量
public:
//设置一个构造函数
Date(int year = 2023, int month = 12, int day = 12)
{
_year = year;
_month = month;
_day = day;
}
//定义一个成员函数,用于设置并访问Time类
void SetTime(int hour = 12, int minute = 12, int second = 12)
{
_t._hour = hour;
_t._minute = minute;
_t._second = second;
}
void print()
{
std::cout << _year << "年" << _month << "月" << _day << "日" << std::endl;
std::cout << _t._hour << "时" << _t._minute << "分" << _t._second << "秒" << std::endl;
}
};
int main()
{
//开始测试代码是否可以正常运行
Date d(2022, 12, 12);
d.SetTime(1, 1, 1);
d.print();
return 0;
}
就像是上图中的那样,我们将Date类设置成为Time类的友元类,所以我们可以访问Time当中的私有变量。我们需要记住的是:我们frined友元函数的作用就像是一个特权,所以我们在设置友元函数的时候只需要记住,我们想要拥有特权访问其他的私有成员变量的类才需要设置成友元类。运行结果:
和我们友元函数原理相同的还有我们的内部类。也就是说在一个类当中我们还可以设置另一个类,类似于一个嵌套的操作。内部类可以访问外部类的私有成员变量。我们可以将内部类看成是外部类的一个友元函数。代码如下:
#include<iostream>
//定义一个类,并在其中定义一个新的成员,作为其内部类
class Stu
{
private:
char _name[20];
int _age=12;
public:
//定义一个内部类,尝试在其中访问外部类的私有成员变量
class HomeWork
{
public:
void ChangeAndFind(const Stu& s)
{
std::cout << s._age << std::endl;
}
};
};
int main()
{
//使用一个HomeWork类,并访问其中的内容,并设置一个Stu类,用于传参
Stu s1;
Stu::HomeWork hw1;
hw1.ChangeAndFind(s1);
return 0;
}
我们需要注意的是:在访问内部类,并创建内部类的对象的时候,我们必须通过外部类的类名进行指定的使用才可以访问到我们的内部类。其他的在使用的时候一切正常。我们的内部类也可以成功的访问外部类的成员变量。运行结果如下:
——类和对象 (完)