一.const成员函数
概念:将const修饰的成员函数称之为const成员函数,const修饰成员函数放到成员函数参数列表的后面。
在使用成员函数的过程中,我们在函数参数位置是无法显示调用this指针的,但很多函数的使用又不会改变this指针指向的内容(比如说打印成员函数的值),这时候我们就可以直接在成员函数后加上一个const即可:
//比如我们要实现一个日期类打印成员函数:
void Print()//这时候传入的this指针类型为Date const* this
{
//.......
};
//如果这样写:
void Print()const//传入的this类型就变为const Date const* this
{
//.......
}
二.(补充构造函数内容)初始化列表:
我们之前说过,C++中一共有三种默认构造函数,我们通常需要自己去写我们的构造函数,而对成员的初始化我们通常以给定数值或使用缺省参数去完成初始化。但实际上,C++中的初始化都会去使用构造函数中的初始化列表进行初始化:
//本文的参考类均为普通日期类
class Date
{
public:
Date(int year = 2024, int month = 8, int day = 13)
:_year(year),
_month(month),
_day(day)
{
}
private:
int _year;
int _month;
int _day;
};
这就是初始化列表的一般形式。
初始化列表的特点:
1.每个成员变量在初始化列表中只能出现一次,语法理解上初始化列表可以认为是每个成员变量定义初始化的地方。
2.引用成员变量,const成员变量,没有默认构造的类类型变量,必须放在初始化列表位置进行初始化,否则会编译报错。引用与const都是在定义的时候直接初始化,后面无法改变,所以必须要在初始化列表进行初始化。
3.C++11支持在成员变量声明的位置给缺省值,这个缺省值主要是给没有显示在初始化列表初始化的成员使用的。
比如说这样也可以达到对日期类的初始化目的:
class Date
{
public:
Date(int year = 2024, int month = 8, int day = 13)
:_year(year),
_month(month),
{
}
private:
int _year;
int _month;
int _day = 0;
};
4.尽量使用初始化列表初始化,因为那些你不在初始化列表初始化的成员也会走初始化列表,如果这个成员在声明位置给了缺省值,初始化列表会用这个缺省值初始化。如果你没有给缺省值,对于没有显示在初始化列表初始化的内置类型成员是否初始化取决于编译器,C++并没有规定。对于没有显示在初始化列表初始化的自定义类型成员会调用这个成员类型的默认构造函数,如果没有默认构造会编译错误。
5.初始化列表中按照成员变量在类中声明顺序进行初始化,跟成员在初始化列表出现的的先后顺序无关。建议声明顺序和初始化列表顺序保持一致。
总结图表:
三.static成员与友元
2.1Static成员
1.用static修饰的成员变量,称之为静态成员变量,静态成员变量一定要在类外进行初始化。
2.静态成员变量为所有类对象所共享,不属于某个具体的对象,不存在对象中,存放在静态区。
3.用static修饰的成员函数,称之为静态成员函数,静态成员函数没有this指针。
4.静态成员函数中可以访问其他的静态成员,但是不能访问非静态的,因为没有this指针。
5.非静态的成员函数,可以访问任意的静态成员变量和静态成员函数。
6.突破类域就可以访问静态成员,可以通过类名::静态成员 或者 对象.静态成员 来访问静态成员变量和静态成员函数。
7.静态成员也是类的成员,受public、protected、private 访问限定符的限制。
8.静态成员变量不能在声明位置给缺省值初始化,因为缺省值是个构造函数初始化列表的,静态成员变量不属于某个对象,不走构造函数初始化列表。
这里我们可以用牛客网的一道题目去介绍下这些特点 求1+2+3+...+n_牛客题霸_牛客网 (nowcoder.com):
class Sum{
public:
Sum()
{
_ret += _i;
++_i;
}
static int GetResult()
{
return _ret;
}
private:
static int _ret;
static int _i;
};
int Sum:: _i = 1;
int Sum:: _ret = 0;
class Solution {
public:
int Sum_Solution(int n) {
Sum arr[n];
return Sum::GetResult();
}
};
这是一种利用static特性解决该问题的解法,因为开始的时候我们创建了一个Sum类型的数组,编译器这时就会去调用n次的Sum的构造函数,每次调用后静态成员值保持不变,就达到了我们预期的等差数列之和。其次,最终返回值的时候,由于静态成员函数突破类域即可使用,所以我们使用一个静态成员函数去获取我们的累加值,完成题目。
2.2友元
简单来说,由于我们类成员一般是private或protect,所以外部函数无法直接访问类对象的成员,虽然我们可以直接写一个成员函数来返回成员的值,但远不如直接将其设置为友元更加方便:
class Date
{
friend void Print(const Date& d);
public:
Date(int year = 2024, int month = 9, int day = 13)
:_year(year),
_month(month),
_day(day)
{
}
private:
int _year;
int _month;
int _day;
};
void Print(const Date& d)
{
cout << d._year << '-' << d._month << '-' << d._day << endl;
}
int main()
{
Date d;
Print(d);
return 0;
}
这样就可以轻松调用类的成员。当然你的friend放的位置不影响其发挥作用,但我一般习惯放到开头。
TIps:
1.友元提供了一种突破类访问限定符封装的方式,友元分为:友元函数和友元类,在函数声明或者类声明的前面加friend,并且把友元声明放到一个类的里面。
2.外部友元函数可访问类的私有和保护成员,友元函数仅仅是一种声明,他不是类的成员函数。
3.友元函数可以在类定义的任何地方声明,不受类访问限定符限制。
4.一个函数可以是多个类的友元函数。
5.友元类中的成员函数都可以是另一个类的友元函数,都可以访问另一个类中的私有和保护成员。
6.友元类的关系是单向的,不具有交换性,比如A类是B类的友元,但是B类不是A类的友元。
7.友元类关系不能传递,如果A是B的友元, B是C的友元,但是A不是C的友元。
8.有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。