全文目录
引言
通过前面的三篇文章,相信大家对类和对象已经有了一个基本的认识。
类和对象1(初识)
类和对象2(默认成员函数)
类和对象3(初始化列表)
但是在前面举的一些例子中不难发现一些漏洞以及许多可以改进的点:
比如一些时候我们需要一些全局的变量来管理许多对象的属性,但是创建全局变量又影响了类封装;
我们知道this指针的类型是T* const
有时我们需要对const修饰的类对象进行例如赋值给别的对象,打印等操作时。this指针的传递就会出现权限放大的问题;
我们有时也需要在类外访问私有成员,正常情况下显然是不允许的。
在本篇文章中就将继续补充类和对象的知识:
static成员
我们知道,类对象是存储在栈区中的,我们可以计算一个简单的类的大小:
class A
{
private:
char _a;
int _b;
int _c;
};
int main()
{
A a;
cout << sizeof(a) << endl;
return 0;
}
根据内存对齐,不难计算出这个A类的大小为12字节。
这三个成员变量_a、_b、_c都是属于某个对象的属性,类实例化时被定义。当然不能在类外访问,也不具有全局的属性。
static成员变量
但是static变量是存储在静态区的,它虽然封装在类中(只在类类型中声明),但不属于某个对象。
对于某个类类型而言,static成员只是在类中声明,在类外定义,定义时不添加static关键字,不占用该类型对象的空间:
class A
{
private:
char _a;
int _b;
int _c;
static int a;
};
int A::a = 10;
int main()
{
A a;
cout << sizeof(a) << endl;
return 0;
}
这个static成员变量就类似于封装在类中的全局变量。虽然不能在类外面访问,但是可以在类中起到全局变量类似的作用来反映这个类类型的许多对象的属性。
static成员函数
用static修饰的成员函数,称之为静态成员函数:
static成员函数与一般成员函数不同,它没有隐含的this指针的传参,因此不能访问私有的成员变量。但是它可以正常访问static成员变量:
class A
{
public:
static void fun()
{
//_a++; 错误代码:非静态成员引用必须与特定对象相对
cout << a++ << endl;
}
private:
char _a;
int _b;
static int a;
};
int A::a = 10;
访问static成员函数时,可以通过对象名.函数调用
访问,也可以通过类类型名::函数调
用访问:
class A
{
public:
static void fun()
{
//_a++; 错误代码:非静态成员引用必须与特定对象相对
cout << a++ << endl;
}
private:
char _a;
int _b;
static int a;
};
int A::a = 10;
int main()
{
A a;
a.fun();
A::fun();
return 0;
}
之前提过,调用成员函数其实是代替某个对象调用成员该成员函数。这里就有一个前提,即在调用成员函数时,一定有一个对象主体是已经定义过的,所以一般的成员函数隐式传this指针访问私有成员变量当然是可以的;
但是对于static成员函数而言,我们调用static成员函数时却没有已经实例化对象的前提(可以通过类型名调用)。所以不能隐式的this传参,自然也就不能访问私有的成员变量。而static成员变量虽然在类中声明,但是不随实例化定义,所以自然可以当其在外部定义后在函数中访问。
const对象
在前面我们简单的实现过一些成员函数,例如日期类加日期的函数、打印函数。对于普通的类对象当然是适合的:
class Date
{
public:
Date(int year = 2023, int month = 2, int day = 10)
: _year(year)
, _month(month)
, _day(day)
{
}
int GetMonthDay(int year, int month);
Date& AddDate(int day);
void print();
private:
int _year;
int _month;
int _day;
};
void Date::print()
{
cout << _year << " " << _month << " " << _day << endl;
}
int main()
{
Date d1;
d1.print();
return 0;
}
但当我们实例化一个const Date型的类对象时,这个const对象就不能调用print函数来打印日期了:
int main()
{
Date d1;
d1.print();
const Date d2;
//d2.print(); 错误代码:不能将“this”指针从“const Date”转换为“Date&“
return 0;
}
因为this指针的类型是Date* const
,当对这个可更改指向的之的指针传const对象时,就会发生权限放大的问题,这当然是不允许的。
面对这种问题,我们就需要给this指针加上const修饰,使其的类型为const Date* const
。但是this指针是隐式传参的,所以我们不能按照以前的方式传const型指针。
C++提供了用const修饰this指针的方法,即在参数列表后加上const:
class Date
{
public:
Date(int year = 2023, int month = 2, int day = 10)
: _year(year)
, _month(month)
, _day(day)
{
}
int GetMonthDay(int year, int month);
Date& AddDate(int day);
void print() const;
private:
int _year;
int _month;
int _day;
};
void Date::print() const
{
cout << _year << " " << _month << " " << _day << endl;
}
int main()
{
Date d1;
d1.print(); //权限缩小
const Date d2(2023, 5, 19);
d2.print(); //权限平移
return 0;
}
这样就可以实现const对象的this传参了。
友元
我们有时会需要在类外访问私有成员函数友元提供了一种突破封装的方式,提供了便利。
但是友元的过多使用一定会破坏封装,所以不宜多用!
友元函数
友元函数即可以使用friend关键字使一个在外部声明的函数作为类的友元函数。这样,这个友元函数就可以访问类的私有成员:
class A
{
friend void func1(A&);
private:
int _a;
};
void func1(A& a)
{
cout << a._a << endl;
}
需要注意的是:
- 友元函数可访问类的私有和保护成员,但不是类的成员函数(没有this指针传参);
- 友元函数不能用const修饰;
- 一个函数可以是多个类的友元函数;
- 友元函数的调用与普通函数的调用原理相同;
- 友元函数可以在类定义的任何地方声明,不受类访问限定符限制;
- 需要在类内部的任何位置,加上friend修饰声明该外部函数(friend在函数声明的返回值前)。
(在下一篇文章中将介绍一个友元函数的应用)
友元类
与友元函数类似,friend关键字也可以声明一个外部的类作为某类的友元类,友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
class A
{
friend void func1(A&);
friend class B;
private:
int _a;
};
void func1(A& a)
{
cout << a._a << endl;
}
class B
{
public:
void func1(A& a)
{
cout << a._a << endl;
}
private:
int _b;
};
需要注意的是:
- 友元关系不具有交换性:
比如上述A类和B类,在A类中声明B类为其友元类,那么可以在B类中直接访问A类的私有成员变量,但想在A类中访问B类中私有的成员变量则不行。 - 友元关系不能传递:
如果C是B的友元, B是A的友元,则不能说明C时A的友元。 - 友元关系不能继承:后面再详细介绍。
总结
到此,关于类和对象的所有基础知识就介绍完了
接下来将会使用前面的知识来举例实现一个日期类。相信大家会对类和对象有更好的理解,欢迎持续关注哦
如果大家认为我对某一部分没有介绍清楚或者某一部分出了问题,欢迎大家在评论区提出
如果本文对你有帮助,希望一键三连哦
希望与大家共同进步哦