Skip to content

类的深入剖析二

本章大纲

  • 常对象、常成员函数和常数据成员
  • 对象常引用
  • 类的组合
  • 友元函数和友元类
  • this指针
  • static数据成员和static成员函数

常对象

  • 数据成员值在对象的整个生存期间内不能被改变的对象。
  • 常对象在定义时必须进行初始化,而且不能被更新
  • 如果要修改常对象中某个数据成员的值,可以将该数据成员声明为**mutable**
C++
1
2
const 类名 对象名[参数列表]);
类名 const 对象名[参数列表]);

常成员函数

  • const**关键字修饰的某个**成员函数,称为常成员函数
  • 常对象只能调用常成员函数,保证常对象的数据成员不能被修改
  • 普通对象可以调用常成员函数,常对象不能调用普通成员函数
  • 常成员函数不能更新对象的数据成员,也不能调用该类中的非const成员函数
  • const成员函数可以进行非const版本的重载
  • 如果调用对象是const,使用const版本的重载函数,
  • 如果调用对象是非const,使用非const版本的重载函数。
  • 构造函数和析构函数不能进行const声明,否则不能初始化和扫尾
普通成员函数 const 成员函数
普通对象 可以调用 可以调用
const 对象 不可调用 可以调用
C++
1
2
3
4
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++
1
const 类名 &引用名 = 对象名;

编程建议

  • 在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
//cosnt 和&、*在一起的用法辨析

类的组合

C++
1
2
3
4
5
类名::类名(形参表):内嵌对象1(形参表), 内嵌对象2(形参表),
{
  //类的初始化
}
//其中,“内嵌对象1(形参表), 内嵌对象2(形参表),…”为初始化列表,用来完成对内嵌对象的初始化。

组合类的构造函数的执行顺序

  1. 如果有多个内嵌对象,按照**内嵌对象**在组合类的声明中**出现的次序**,依次调用其内嵌对象的构造函数。(注意:并不是按照初始化列表中给出的顺序
  2. 再执行本类的构造函数的函数体。

注意: 内嵌对象调用的如果是被隐式定义的拷贝构造函数,则不包含任何被其调用时要显示的输出语句。

友元

  • 在一个类中,可以利用关键字friend将别的模块(一般**函数**、其它类的**成员函数**或其它**类**)声明为本类的友元,这样类中本来隐藏的信息(私有和保护成员)就可以被友元访问。
  • 友元提供了不同类或对象的成员函数之间、类的成员函数与一般函数之间进行数据共享的机制。
  • 友元并不是类的成员

友元函数

  1. 在类体内说明,在函数的类型说明符前加关键字friend
  2. 在类体外定义,定义格式与普通函数相同;
  3. 友元函数是非成员函数,在调用上与普通函数相同;
  4. 友元函数可以直接访问该类中的私有成员、保护成员

定义函数func是类A的友元函数:

C++
1
2
3
4
5
6
7
class A {
  friend int func(int a,double b)
  //...
};
int func(int a,double b){//该函数可直接访问类A中的私有成员和保护成员
    return a+b;
}
  • 函数func(int, double)是定义在类A外的一个函数

  • 如果**函数频繁访问类A的私有成员和保护成员**,可以将该函数定义成类A的友元函数

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;
}

友元类

  • 当声明类A为类B的友元时,友元类A中的所有成员函数都是类B的友元函数

  • 可以放在类B的任意位置,通常放在类定义的首部

  • 要求类A在程序中有定义,并未要求声明顺序

  • cpp class B { friend class A; //定义友元类A(类A的常规定义) };

实例:

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++
1
2
3
4
5
6
文件范围内使用下边语句格式进行初始化
<数据类型>  <类名>::<静态数据成员名 >= <初始化值>
在初始化语句中不必加static如果未对静态数据成员赋初值则编译系统会自动赋予初值0

int Boxheight=10;              //对静态数据成员height初始化
cout<<Boxheight<<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指针访问自己的地址
  • 使用静态数据成员可以表示类的实例共享的属性。

Comments