在C++中,静态成员是类的一个特性,它们与类本身关联,而不是与类的任何特定对象关联;静态成员可以是数据成员(变量)或成员函数;静态成员在类的所有对象之间共享一个副本,这意味着无认创建多少个类实例,都只有一个静态成员副本存在。如果想在同类的多个对象之间实现数据共享,也不要用全局对象,可以用静态的数据成员。
友元(friend)是一种特殊的声明,它允许一个函数或类访问另一个类的私有(private)和保护(protected)成员;友元不是类的成员,但它可以访问类的所有成员,包括私有和保护成员。使用友元的目的是为了允许在类外部的函数或类能够访问类的私有或保护成员,友元有破坏了类的封装性,所以应尽量避免使用友元;如果确实需要使用友元,应慎重考虑其不会破坏类的封装性和安全性。
一、静态成员
1.1 静态数据成员
静态数据成员在类的所有对象之间共享存储空间。它们是类的所有对象共有的。静态数据成员必须在类的外部进行定义和初始化。示例代码如下:
#include <iostream>
using namespace std;
class Student{
public:
Student(){}
Student(string n, int s): name(n), score(s){}
void display(){
cout <<"name:" <<name <<", score:" <<score <<endl;
}
private:
string name; // 姓名
int score; //分数
static int total; //总分
};
// 初始化Student类中静态数据成员
int Student::total = 0;
这里需要注意的是,静态数据成员必须在类的外部进行定义和初始化,如果在类体内初始化静态数据成员,编译系统会报错【[Error] ISO C++ forbids in-class initialization of non-const static member 'Student::total'】 - C++禁止内部初始化非常量静态成员'Student::total'。
1.2 静态成员函数
静态成员函数只能访问静态数据成员和其他静态成员函数,它们不能访问类中非静态数据成员或非静态成员函数,因为非静态成员是与类的特定对象关联的,静态成员函数通常用于执行与类相关但不依赖于任何特定对象的操作。示例代码如下:
#include <iostream>
using namespace std;
class Student{
public:
Student(){}
Student(string n, int s): name(n), score(s){}
// 一般成员函数 - 计算总分
void calc_total(){
total += score;
}
// 静态成员函数 - 获取总分
static int get_total(){
return total;
}
private:
string name; // 姓名
int score; //分数
static int total; //总分
};
// 初始化Student类中静态数据成员
int Student::total = 0;
// 求数组中所有Student对象之和
void assemblyArrayTotal(Student all[], size_t size){
for(size_t i = 0; i < size; i++){
all[i].calc_total();
}
}
int main(){
Student all[] = {
Student("Tom", 90),
Student("John", 95),
Student("Lily", 98)
};
// 累计总分
assemblyArrayTotal(all, sizeof(all)/sizeof(all[0]));
// 显示总分
cout <<"All Student score:" <<Student::get_total() <<endl;
return 0;
}
运行结果如下:
这里需要注意几点:
(1)静态成员函数只能访问静态数据成员,不能访问类的非静态数据成员。示例如下:
class Student{
public:
Student(){}
Student(string n, int s): name(n), score(s){}
// 一般成员函数 - 计算总分
void calc_total(){
total += score;
}
// 静态成员函数 - 获取总分
static int get_total(){
score++; // 错误,静态成员函数不能调用非静态数据成员
return total;
}
private:
string name; // 姓名
int score; //分数
static int total; //总分
};
如上在静态成员函数get_total()中调用非静态数据成员score,编译系统会报错【[Error] invalid use of member 'Student::score' in static member function】- 在静态成员函数中无效地使用成员'Student::score'。
(2)静态成员函数只能访问其他静态成员函数,不能访问非静态成员函数。示例代码如下:
class Student{
public:
Student(){}
Student(string n, int s): name(n), score(s){}
// 一般成员函数 - 计算总分
void calc_total(){
total += score;
}
// 静态成员函数 - 获取总分
static int get_total(){
calc_total(); //错误,静态成员函数不能调用非静态成员函数
return total;
}
private:
string name; // 姓名
int score; //分数
static int total; //总分
};
如上在静态成员函数get_total()中调用非静态成员函数calc_total(),编译系统会报错【[Error] cannot call member function 'void Student::calc_total()' without object】- 没有对象不能调用成员函数void Student::calc_total()。
二、友元
2.1 友元函数
友元函数是可以访问类的私有和保护成员的普通函数,它需要在类内部进行声明,并在类外部进行定义。声明时使用friend关键字。
2.1.1 普通函数声明为友元函数
普通函数声明为友元函数首先在类体中声明,再在类体外进行定义,示例代码如下:
#include <iostream>
using namespace std;
class Animal{
public:
Animal(string name){
this->name = name;
}
// 声明友元函数
friend void display(Animal &);
private:
string name;
};
// 定义友元函数
void display(Animal &a){
cout <<"Animal name:" <<a.name <<endl;
}
int main(){
Animal a1("Dog");
display(a1);
return 0;
}
运行结果可以看出,普通友元函数display()可以直接读取类Animal中的私有数据成员name的值,如下图:
2.1.2 友元成员函数
友元函数不仅可以是一般函数(非成员函数),也可以是另一个类中的成员函数。示例如下:
#include <iostream>
using namespace std;
class Animal; // 声明Animal类的提前引用声明
class Dog{
public:
// 声明display成员函数,形参是Animal类对象的引用
void display(Animal &a);
};
class Animal{
public:
Animal(string name){
this->name = name;
}
// 声明Dog类中的display函数为本类的友元成员函数
friend void Dog::display(Animal &);
private:
string name;
};
// Dog类体外定义成员函数
void Dog::display(Animal &a){
cout <<"Dog name is" <<a.name;
}
int main(){
Animal a1("Angle");
Dog d1;
// 显示名称
d1.display(a1);
return 0;
}
运行结果如下图:
这里需要注意几个细节:
(1)在Dog类中声明成员函数中形参引用类型是Animal,所以必须在Dog类前面事先声明Animal类,否则编译系统会报错【[Error] 'Animal' has not been declared】- "Animal"类未被声明。
(2)在Animal中要先声明友元函数display(),不能在Animal类体中定义,否则编译系统会报错【[Error] cannot define member function 'Dog::display' within 'Animal'】 - 无法在"Animal"中定义成员函数"Dog::display"。
(3)需要注意Dog类和Animal类的顺序,如果这里Dog类放在Animal类下面,编译系统也会报错【[Error] invalid use of incomplete type 'class Dog'】- 无效使用不完整类型"class Dog";这是因为只是事先声明了Dog类,而内部成员还未定义,而Animal类中引用Dog::display成员还未定义。
2.2 友元类
一个类也可以声明另一个类为其友元,这意味着友元类可以访问该类的所有私有和保护成员。所以把上面示例稍作修改即可,代码如下:
#include <iostream>
using namespace std;
class Animal;
class Dog{
public:
// 声明成员函数,形参为Animal类的常引用
void display(const Animal &);
};
class Animal{
public:
Animal(string name){
this->name = name;
}
// 声明Dog类为友元
friend class Dog;
private:
string name;
};
// Dog类体外定义成员函数
void Dog::display(const Animal &a){
cout <<"Dog name is" <<a.name;
}
int main(){
Animal a1("Angle");
Dog d1;
// 显示名称
d1.display(a1);
return 0;
}
在”2.1.2 友元成员函数“中注意一些细节部分,此处也需要注意,否则会出现一系列问题。在Animal类中将Dog声明为友元后,在Dog类中即可通过Animal类引用,获取其所有私有和保护成员。
使用友元的注意事项:
- 友元破坏了类的封装性,因为它允许成员函数或非成员类访问类的私有和保护成员。因此,应该慎重使用友元。
- 友元关系不具有传递性。如果类B是类A的友元,类C是类B的友元,这并不意味着类C是类A的友元。
- 友元关系不具有交换性。如果类B是类A的友元,这并不意味着类A自动成为类B的友元。
- 友元关系不能继承。如果类B是类A的友元,类C从类B继承面来,这并不意味着类C是类A的友元。