OOP课程笔记:第四章 类的其他特性
第四章 类的其他特性
1.友元函数
友元函数是一种定义在类外部的普通函数,能够访问类中私有成员和保护成员。
友元函数需要在类体内进行说明,在前面加上关键字 friend
friend <type> FuncName(<args>);友元函数不是成员函数,用法也与普通的函数完全一致
友元函数破坏了类的封装性和隐蔽性,使得非成员函数可以访问类的私有成员
class A{
float x, y;
public:
A(float a, float b){
x = a;
y = b;
}
float Sum() { return x + y; }
friend float Sum(A &a) { return a.x + a.y; } // 必须使用类名引用来访问,因为友元函数不是类成员
};
void main(void){
A t1(4, 5), t2(10, 20);
cout << t1.Sum() << endl;
cout << Sum(t2) << endl;
}类与类的沟通
大多数情况是友元函数是某个类的成员函数,即A类中的某个成员函数是B类中的友元函数,这个成员函数可以直接访问B类中的私有数据。这就实现了类与类之间的沟通。
下例中,在 B 中声明友元,在 A 中声明成员, 在类外定义函数 A::fun()
class B; // 先定义类A,则首先对类B作引用性说明
class A{
// 类A的成员定义
public:
void fun(B &); // 函数的原型说明
};
class B{
friend void A::fun(B &); // 定义友元函数
}
void A::fun(B &b) {} // 函数的完整定义2.友元类
可以在一个类中声明另一个类是其的友元类
const float PI = 3.1415926;
class A{
float r;
float h;
public:
A(float a, float b){
r = a;
h = b;
}
float Getr() { return r; }
float Geth() { return h; }
friend class B; // 定义类B为类A的友元
};
class B{
int number;
public:
B(int n = 1) { number = n; }
void Show(A &a){ // 需要使用A的对象名来访问
cout << PI * a.r * a.r * a.h * number << endl;
} // 求类A的某个对象*n的体积
};
void main(void){
A a1(25, 40), a2(10, 40);
B b1(2);
b1.Show(a1);
b1.Show(a2);
}3.派生类访问基类私有成员
不管是按哪一种方式派生,基类的私有成员在派生类中都是不可见的。
如果在一个派生类中要访问基类中的私有成员,可以将这个派生类声明为基类的友元。
class M{
friend class N; // N为M的友元,可以直接使用M中的私有成员
private:
int i, j;
void show(void) { cout << "i=" << i << '\t' << "j=" << j << '\t'; }
public:
M(int a = 0, int b = 0){
i = a;
j = b;
}
};
class N : public M{ // N为M的派生类
public:
N(int a = 0, int b = 0) : M(a, b) {}
void Print(void){
show();
cout << "i+j=" << i + j << endl;
}
};
void main(void){
N n1(10, 20);
M m1(100, 200);
// m1.show(); //私有成员函数,在类外不可调用
n1.Print();
}4.静态成员
类所产生的所有对象中的静态成员均共享一个存储空间,这个空间是在编译的时候分配的。
静态数据成员
在类定义中,用关键字 static 修饰的数据成员称为静态数据成员。
必须在文件作用域中,对静态数据成员作一次且只能作一次定义性说明
可以通过静态数据成员名前加上类名和作用域运算符,可直接引用静态数据成员
class A{
int x,y; static int z;
public:
void Setxy(int a, int b){ x=a; y=b;}
};
A a1, a2; // 共用一个z
int A::z = 0; // 必须对静态成员作一次定义性说明 静态成员函数
可以将类的成员函数定义为静态的成员函数。即使用关键字 static 来修饰成员函数
1.与静态数据成员一样,在类外的程序代码中,通过类名加上作用域操作符,可直接调用静态成员函数。
2.静态成员函数只能直接使用本类的静态数据成员或静态成员函数,但不能直接使用非静态的数据成员(可以通过对象名引用使用)。这是因为静态成员函数可被其它程序代码直接调用,所以,它不包含对象地址的 this 指针。
3.静态成员函数的实现部分在类定义之外定义时,其前面不能加修饰词 static 。这是由于关键字 static 不是数据类型的组成部分,因此,在类外定义静态成员函数的实现部分时,不能使用这个关键字
4.不能把静态成员函数定义为虚函数。静态成员函数也是在编译时分配存储空间,所以在程序的执行过程中不能提供多态性。
5.可将静态成员函数定义为内联的(inline),其定义方法与非静态成员函数完全相同。
class Tc{
private:
int A;
static int B; // 静态数据成员
public:
Tc(int a){
A = a;
B += a;
}
static void display(Tc c); // Tc的对象为形参
};
void TC::display(Tc c){
cout << "A=" << c.A << ",B=" << B << endl;
}
int Tc::B = 2;
void main(void){
Tc a(2), b(4);
Tc::display(a);
Tc::display(b);
}静态成员的继承
如果基类定义了一个 static 成员,则在整个继承层次中只存在该成员的唯一定义,不论从基类派生出来多少个派生类,对于每个静态成员来说都只存在唯一的实例。
静态成员遵循一般的访问控制规则,如果基类中的成员是 private 的,则派生类无权访问
静态成员函数无法被覆盖
static 成员函数没有 this 指针,只有非 static 成员函数可以声明为虚函数,虚函数调用的绑定是根据对象类型确定的
派生类中定义的同名静态函数会隐藏基类的静态函数,而非覆盖。
class Base{
public:
static void func() { cout << "Base\n"; }
};
class Derived : public Base{
public:
static void func() { cout << "Derived\n"; } // 隐藏 Base::func()
};
int main(){
Derived::func();
Derived::Base::func();
}5.常对象
如果在说明对象时用 const 限定,则被说明的对象为常对象,常对象的数据成员的值在整个生存期内不能被修改。
由于常对象的值(包括所有的数据成员的值)不能被改变,因此,通过常对象只能调用类中那些不改变数据成员值的成员函数(即常成员函数),而不能调用类中的其他普通成员函数。
class ClassName{};
ClassName const a1;
const ClassName a2; // 两种方式都可以创建常对象常成员函数
const 说明的成员函数称为常成员函数
<typename> func(<args>) const; // const 在最后const 是函数类型的一个组成部分,在函数的实现部分也要使用关键字 const
常成员函数不能修改对象的数据成员,也不能调用该类中没有由关键字 const 修饰的成员函数,从而保证了在常成员函数中不会修改数据成员的值。
const 参与区分函数重载
如果一个对象被说明为常对象,则通过该对象只能调用它的常成员函数
常数据成员
类的数据成员中被 const 说明的数据成员称为常数据成员
类的数据成员也可以是常量或常引用
如果类中说明了常数据成员,构造函数只能通过初始化列表对该数据成员进行初始化,而任何其他函数都不能对该成员赋值。
class Person{
private:
intage;
char *name;
public:
Person(int, char *);
~Person();
voidPrint(); // 重载函数,用于输出的普通成员函数
voidPrint() const; // 重载函数,用于输出的常成员函数,const参与重载
voidModifyAge();
};
Person::Person(intn, char *na) // 构造函数的定义
{
age = n;
name = newchar[strlen(na) + 1];
strcpy(name, na);
}
Person::~Person() // 析构函数的定义
{
delete[] name;
}6.指向类成员的指针
在C++中可以定义一种特殊的指针,它指向类中的成员函数或类中的数据成员。并可通过这样的指针来使用类中的数据成员或调用类中的成员函数。
指向类数据成员的指针
定义一个指向类中数据成员的指针变量的一般格式为:
<typename> ClassName::*PointName;其中 typename 是指针 PointName 所指向数据的类型,它必须是类 ClassName 中某一数据成员的类型
1.指向类中数据成员的指针变量不是类中的成员,这种指针变量应在类外定义
2.与指向类中数据成员的指针变量同类型的任一数据成员,可将其地址赋给这种指针变量,赋值的一般格式为:
PointName = &ClassName::member;
mptr = &S::y; //示例:表示将对象S的数据成员y的地址赋给指针变量mptr3.用这种指针访问数据成员时,必须指明是使用哪一个对象的数据成员。当与对象结合使用时,其用法为:
ObjectName.* PointName4.由于这种指针变量并不是类的成员,所以使用它只能访问对象的公有数据成员
指向类中成员函数的指针变量
定义一个指向类中成员函数的指针变量的一般格式为:
<typename> (ClassName::*PointName)(<args>);其中 PointName 是指向类中成员函数的指针变量,ClassName 是已定义的类名;typename 是通过函数指针 PointName 调用类中的成员函数时所返回值的数据类型,它必须与类 ClassName 中某一成员函数的返回值的类型相一致,<args> 是函数的形式参数表
在使用这种指向成员函数的指针前,应先对其赋值
PointName = ClassName::FuncName;1.指向类中成员函数的指针变量不是类中的成员,这种指针变量应在类外定义
2.不能将任一成员函数的地址赋给指向成员函数的指针变量,只有成员函数的参数个数、参数类型、参数的顺序和函数的类型均与这种指针变量相同时,才能将成员函数的地址赋给这种指针变量
3.使用这种指针变量来调用成员函数时,必须指明调用哪一个对象的成员函数,这种指针变量是不能单独使用,必须用对象名引用
4.由于这种指针变量不是类的成员,所以用它只能调用公有的成员函数
评论已关闭