第一节 封装的破坏——友元
在C++类中,类是数据和函数的封装体,类外不能够直接访问类的私有和保护成员。C++正是提供了一种机制——友元,用于突破访问限制(偷家)。
在一个类中,可以利用关键字friend将别的模块:普通函数、其他类的成员函数、其他类声明为该类的友元!
1.友元函数
友元函数:友元函数是普通函数/或/类外和类内的函数,友元类的所有成员函数也被称为友元函数。
①友元函数的声明可以出现在类的任何地方;
②友元关系不能够传递,并且友元是单向的。
(1)类内友元函数——注意:类内友元函数不是该类的成员函数
class Clock
{
private:
int hour, minute, second;
public:
Clock(int, int, int);//构造函数的原型声明
//声明只做类型检查 不会检查形参
friend void Display(Clock&);//声明Display函数为Clock的友元函数
};
Clock::Clock(int h, int m, int s) { hour = h; minute = h; second = s; }
void Display(Clock& t) { cout << t.hour << t.minute << t.second << endl; }
int main()
{
Clock t(13, 22, 20);
Display(t);
return 0;
}
启示:
①声明只做类型检查,并不会检查形参;
②在类内声明的友元函数,在类外定义的时候不用加上作用域符号,因为他不是该类的成员函数;
③友元函数一般带有一个与其有好友关系的类类型的入口参数。因为友元函数不是类的成员函数,这也就意味着它没有this指针,不能直接调用类成员的名字,因此必须通过入口参数传递进来的对象名或对象指针来引用该对象的成员;
④friend在类型的前面。
(2)类内公用友元函数(同时定义为两个类的友元函数)
class Date
{
private:
int year, month, day;
public:
Date(int y, int m, int d) :year(y), month(m), day(d){}
friend void Show(Clock2&, Date&);//声明函数为Show的友元函数 在哪个类的声明就是哪个类的友元
};
void Show(Clock2& t, Date& d)
{
cout << d.year << "年" << d.month << "月" << d.day << "日"<<endl;
cout << t.hour << "时" << t.minute << "分" << t.second << "秒" << endl;
}
int main()
{
Date d(2015, 1, 1);
Clock2 t(13, 22, 20);
Show(t,d);
return 0;
}
启示:
①为了避免编译错误,必须通过向前引用告诉编译器类Date将在后面定义。在向前引用的类声明之后,可以使用该类作为函数参数;
②友元函数声明在哪个类就是哪个类的友元(在未加上限定符也就是下面第三种的情况下)。
(3)一个类的成员函数作为另一个类的友元
class Date3
{
private:
int year, month, day;
public:
Date3(int y, int m, int d) :year(y), month(m), day(d) {}
void Show3(Clock3&);
};
class Clock3
{
private:
int hour, minute, second;
public:
Clock3(int h, int m, int s) :hour(h), minute(m), second(s) {}
friend void Date3::Show3(Clock3&);//声明函数为Clock3的友元函数 在哪个类的声明就是哪个类的友元
};
void Date3::Show3(Clock3& t)
{
cout << year << "年" << month << "月" << day << "日" << endl;
cout << t.hour << "时" << t.minute << "分" << t.second << "秒" << endl;
}
int main()
{
Date3 d(2015, 1, 1);
Clock3 t(13, 22, 20);
d.Show3(t);
return 0;
}
启示:
①一个类的成员函数作为另一个类的友元函数时,必须先定义这个类
②一个类的成员函数作为另一个类的友元函数,在友元函数中声明时需加上作用域限定符
③在类外定义函数不需要加上friend
2.友元类
友元类:友元是一个类。B类是A类的一个友元类,则B中的所有成员函数都是A的友元函数,可以访问A中的任何成员。
格式:
class B
{
...
};
class A
{
...
friend B;
...
};
实例:
class Date4;
class Clock4
{
public:
Clock4(int h, int m, int s):hour(h),minute(m),second(s){}
void Display4(Date4&);
private:
int hour, minute, second;
};
class Date4
{
public:
Date4(int y,int m,int d):year(y),month(m),day(d){}
friend Clock4;//定义Clock4为Date4的友元类 Clock4的对象可以任意访问Date4内所有成员
private:
int year, month, day;
};
void Clock4::Display4(Date4& d)
{
cout << d.year << d.month << d.day << endl;
cout << hour << minute << second << endl;
}
int main()
{
Date4 d(2015, 1, 1);
Clock4 t(13, 22, 20);
t.Display4(d);
return 0;
}
第二节 对象机制的破坏——静态成员
如果把类内的某一数据成员(类内除函数成员以外的都是数据成员)声明为静态数据成员,则它在内存中只占用一份空间,而不是每个对象都分别为他开辟一个新空间保存,即被类的所有对象所共享。
友元与静态成员称之为面向对象的妥协。
即使没有定义对象,也可以通过类名引用静态数据成员!
比如在A类中有数据成员:
static int number =80;
如果要在类外修改可以写为:
int A::number = 180;
1.静态数据成员
一个类的对象具有相同的属性是指属性值可以不同,其他都相同。
还有一个概念是类属性,指属性值也相同,而这正是依靠静态数据成员实现。
类的普通数据成员在类的每一个对象中都拥有一个拷贝,也就是说每个对象的同名数据成员可以分别存储不同的数值。但是静态数据成员则不同,它由类的所有对象共同维护和使用,从而实现了同一类的不同对象之间的数据共享。
说明:
①如果只声明了类而未定义对象,则类的一般数据成员是不占内存空间的,只有在定义对象时,才为对象的数据成员分配空间。但是静态数据成员不是,只要在类中定义了静态数据成员,即使不定义对象,也会立即开辟一个空间用以存储静态数据成员。
②静态成员在程序编译时被分配空间,到程序结束时才释放空间
③静态数据成员可以初始化,但是只能在类外进行初始化
如果未对静态数据成员赋值,则编译系统会自动赋予初始值0
int Student::stu_count=0;//表示在类外对Student类中的stu_count进行初始化
格式: 数据类型 类名::数据成员名=初始化值;
注意!类外初始化不必加上static
④静态数据成员既可以通过类名访问,也可以通过对象访问
int main()
{
Student s1;
cout<<Student::stu_count<<endl;
cout<<s1.stu_count<<endl;
}
⑤若静态数据成员被定义为是私有的,则需要通过成员函数才能访问!!!
辨析:全局变量与静态成员数据的不同
①全局变量的作用域是全局
②静态成员数据的作用域仅仅限定于定义该数据的类的作用域内(就是它归属的那个类)
2.静态成员函数
与静态数据成员不同,静态成员函数的作用不是为了对象成员之间的沟通,而是为了能够处理静态数据成员。
静态成员函数不能够默认访问非静态成员数据,因为静态成员函数是全局的,属于整个类,而/非静态成员变量/的访问/是在成员函数的this指针的基础上进行的。(因此一定要访问的话传参的时候传个指针过去就可以访问了)
方法二是:如果一定要访问本类的非静态成员,应该加上对象名和成员运算符.。
假设s已定义为Student对象,那么在静态成员函数中要访问s的score数据,可以出现:
cout<<s.socre<<endl;//合法
cout<<score<<endl;//非法
也可以这么理解:静态成员函数所需内存在程序执行前(编译阶段)就分配好了,非静态成员必须要等到这个类在堆或者栈上分了内存才能用,所以如果静态成员函数访问非静态,可能非静态成员还没有内存。
在c++程序中最好养成这样的习惯:只用静态成员函数访问静态数据成员,而不访问非静态数据成员。