类的深入剖析二
本章大纲
- 常对象、常成员函数和常数据成员
- 对象常引用
- 类的组合
- 友元函数和友元类
- this指针
- static数据成员和static成员函数
常对象
- 数据成员值在对象的整个生存期间内不能被改变的对象。
- 常对象在定义时必须进行初始化,而且不能被更新
- 如果要修改常对象中某个数据成员的值,可以将该数据成员声明为**mutable**
C++ |
---|
| const 类名 对象名([参数列表]);
类名 const 对象名([参数列表]);
|
常成员函数
- const**关键字修饰的某个**成员函数,称为常成员函数
- 常对象只能调用常成员函数,保证常对象的数据成员不能被修改
- 普通对象可以调用常成员函数,常对象不能调用普通成员函数
- 常成员函数不能更新对象的数据成员,也不能调用该类中的非const成员函数
- const成员函数可以进行非const版本的重载
- 如果调用对象是const,使用const版本的重载函数,
- 如果调用对象是非const,使用非const版本的重载函数。
- 构造函数和析构函数不能进行const声明,否则不能初始化和扫尾
|
普通成员函数 |
const 成员函数 |
普通对象 |
可以调用 |
可以调用 |
const 对象 |
不可调用 |
可以调用 |
C++ |
---|
| int Time::getHour() const {
//...1.不能更新对象的数据成员
//...2.不能调用该类的非const成员函数
}
|
常数据成员
- 使用
const
修饰的类的数据成员
- 常数据成员不能通过构造函数赋值,只能通过初始化器初始化
C++ |
---|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 | class Time {
private:
const int hour;//const数据成员
const int second;//const数据成员
int minute;
public:
Time(int h, int s, int m);
};
//通过初始化器初始化类的const数据成员
Time::Time(int h, int s, int m) : hour(h), second(s) {
minute = m;
}
//Time::Time(int h, int s, int m) : hour(h), second(s), minute(m) {
//
//}
|
对象的常引用
- 一个对象的引用就是该对象的别名,对象名和引用名都指向同一段内存单元,即通过引用名可以访问对象中的公有成员
- 如果希望通过引用名只能调用对象中的数据成员,而不能修改它们,可以将对象的引用声明为常引用
编程建议
- 在C++面向对象程序设计中,经常用
const
指针和const
引用作函数参数。这样既能保证数据安全,使数据不能被随意修改,在调用函数时又不必建立实参的拷贝。
- 用
const
指针和const
引用作函数参数,还可以提高程序运行效率
小总结
形式 |
含义 |
Time const t1;const Time t1; |
t1是常对象,其值在任何情况下都不能改变 |
void Time∷fun( )const {} |
fun是Time类中的常成员函数,可以引用,但不能修改本类中的数据成员 |
Time * const p; |
p是指向Time对象的常指针,p的值(即p的指向)不能改变 |
const Time *p; |
p是指向Time类常对象的指针,其指向的类对象的值不能通过指针来改变 |
const Time &t1=t; |
t1是Time类对象t的引用,二者指向同一段内存空间 |
类的组合
C++ |
---|
| 类名::类名(形参表):内嵌对象1(形参表), 内嵌对象2(形参表),…
{
//类的初始化
}
//其中,“内嵌对象1(形参表), 内嵌对象2(形参表),…”为初始化列表,用来完成对内嵌对象的初始化。
|
组合类的构造函数的执行顺序
- 如果有多个内嵌对象,按照**内嵌对象**在组合类的声明中**出现的次序**,依次调用其内嵌对象的构造函数。(注意:并不是按照初始化列表中给出的顺序
- 再执行本类的构造函数的函数体。
注意: 内嵌对象调用的如果是被隐式定义的拷贝构造函数,则不包含任何被其调用时要显示的输出语句。
友元
- 在一个类中,可以利用关键字
friend
将别的模块(一般**函数**、其它类的**成员函数**或其它**类**)声明为本类的友元,这样类中本来隐藏的信息(私有和保护成员)就可以被友元访问。
- 友元提供了不同类或对象的成员函数之间、类的成员函数与一般函数之间进行数据共享的机制。
- 友元并不是类的成员
友元函数
- 在类体内说明,在函数的类型说明符前加关键字
friend
;
- 在类体外定义,定义格式与普通函数相同;
- 友元函数是非成员函数,在调用上与普通函数相同;
- 友元函数可以直接访问该类中的私有成员、保护成员
定义函数func是类A的友元函数:
C++ |
---|
| class A {
friend int func(int a,double b);
//...
};
int func(int a,double b){//该函数可直接访问类A中的私有成员和保护成员
return a+b;
}
|
C++ |
---|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | class Car {
private:
int passwd;
friend void func(Car &, int); // 声明友元函数
public:
void member_func(int);
};
void func(Car &car, int pwd) // 定义友元函数
{
car.member_func;
}
void X::member_func(int key) // 成员函数定义
{
this->passwd = key;
}
|
友元类
实例:
C++ |
---|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52 | #include <iostream>
using namespace std;
class Teacher { // 教师类
friend class Stu; // Stu类是Teacher类的友元类
// Stu类的所有成员函数是Teacher类的友元函数
private:
double salary;
string phone;
public:
Teacher();
Teacher(double, string);
};
Teacher::Teacher() {}
Teacher::Teacher(double _salary, string _phone) {
this->salary = _salary;
this->phone = _phone;
}
class Stu { // 学生类
private:
int age;
double weight;
public:
Stu();
Stu(int, double);
void see_teacher(Teacher&);
};
Stu::Stu() {}
Stu::Stu(int param_age, double param_weight) {
this->age = param_age;
this->weight = param_weight;
}
void Stu::see_teacher(Teacher& teacher) {
cout << "教师工资:" << teacher.salary << " 手机:" << teacher.phone << endl;
}
int main(int argc, char const* argv[]) {
Teacher thr(3800, "18539268715");
Stu sdt(25, 160);
sdt.see_teacher(thr);
return 0;
}
//===output===
教师工资:3800 手机:18539268715
|
如果去掉friend class Stu;
,将会编译报错:
C++ |
---|
1
2
3
4
5
6
7
8
9
10
11
12 | demo.cpp: In member function ‘void Stu::see_teacher(Teacher&)’:
demo.cpp:42:35: error: ‘double Teacher::salary’ is private within this context
42 | cout << "教师工资:" << teacher.salary << " 手机:" << teacher.phone << endl;
| ^~~~~~
demo.cpp:8:10: note: declared private here
8 | double salary;
| ^~~~~~
demo.cpp:42:66: error: ‘std::string Teacher::phone’ is private within this context
42 | cout << "教师工资:" << teacher.salary << " 手机:" << teacher.phone << endl;
| ^~~~~
demo.cpp:9:10: note: declared private here
9 | string phone;
|
注意:
- 友元关系是不能传递的。B类是A类的友元,C类是B类的友元,C类和A类之间,如果没有声明,就没有任何友元关系,不能进行数据共享。
- 友元关系是单向的(不是对称的)。如果声明B类是A类的友元,B类的成员函数就可以访问A类的私有和保护数据,但A类的成员函数却不能访问B类的私有和保护数据。
- 友元的作用主要是为了**提高程序的运行效率和方便编程**。但随着硬件性能的提高,友元的作用也不明显,相反,友元破坏了类的封装性,所以在使用时,应**权衡利弊**。
this指针
- 不同对象都调用同一个函数代码段
- 成员函数必须使用对象来调用。一个类的所有对象调用的成员函数都是同一个代码段
- 在每个类的成员函数中,都隐含了一个**this 指针**。该指针指向正在调用该成员函数的对象
- 等同于执行
A *this=&obj;//A为具体的类
- this指针的作用是使成员函数记住当前访问成员函数的对象,以便对对象中的数据成员进行访问。
static数据成员
- 引入原因:某些数据成员是某个类的所有对象共享的,属于整个类
- 所有对象的**static数据成员**的值都是一样的
- static数据成员不随对象的建立而分配空间,不随对象的撤销而释放
- static数据成员是在程序**编译时**被分配空间的,到**程序结束**时释放空间
- static数据成员初始化:
- **不能用构造函数**初始化赋值;
- 不能放在main函数中初始化;
- 在程序定义该类的任何对象之前,对类中的static数据成员单独初始化
- 只能在类体外初始化
- static数据成员可通过**类名**来引用,可通过**对象名**引用
- 私有static数据成员不能在类外直接引用(类名、对象名),而必须通过公有成员函数引用
具体的初始化规则:
C++ |
---|
| 文件范围内使用下边语句格式进行初始化:
<数据类型> <类名>::<静态数据成员名 >= <初始化值>;
在初始化语句中不必加static。如果未对静态数据成员赋初值,则编译系统会自动赋予初值0。
int Box∷height=10; //对静态数据成员height初始化
cout<<Box∷height<<endl; //通过类名引用静态数据成员
|
const static
数据成员既可以在类内又可以在类外初始化!!!
C++ |
---|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 | #include <iostream>
using namespace std;
class Demo {
private:
const static int attr;//类内未初始化必须在类外初始化,否则编译不通过
//undefined reference to 'Demo::attr'
const static int attr = 10;//类内初始化后不能再在类外初始化
public:
Demo() {}
void show() { cout << attr << endl; } //'Demo::attr'
};
const int Demo::attr = 10;
int const Demo::attr = 10;
int Demo::attr const = 10;//编译报错
int Demo::attr = 10 const;//编译报错
int main(int argc, char const* argv[]) {
Demo demo;
demo.show();
return 0;
}
|
static成员函数
静态数据成员与全局变量:
- 静态数据成员的作用域只限于定义该类的作用域内
- 在类作用域内,可以通过类名和域运算符"∷"引用静态数据成员,而不论类对象是否存在。
static成员函数
- 引入原因
- 对static数据成员的访问。
- 定义
- 函数定义前加static关键字
- 一般为公有,也可以定义为私有
- 调用
<类名>::<static 成员函数名>(<参数表>)
本章总结
- 使用关键字const可以指定对象是不可以修改的。
- 对象可以作为其它类的数据成员
- 使用友元函数和友元类可以提高性能
- 每个对象都可以通过this指针访问自己的地址
- 使用静态数据成员可以表示类的实例共享的属性。