详解C++编程中类的成员变量和成员函数的相关知识

时间:2022-06-09 07:55:19

C++类的成员变量成员函数
类是一种数据类型,它类似于普通的数据类型,但是又有别于普通的数据类型。类这种数据类型是一个包含成员变量和成员函数的一个集合。

类的成员变量和普通变量一样,也有数据类型和名称,占用固定长度的内存空间。但是,在定义类的时候不能对成员变量赋值,因为类只是一种数据类型,本身不占用内存空间,而变量的值则需要内存来存储。

类的成员函数也和普通函数一样,都有返回值和参数列表,它与一般函数的区别是:成员函数是一个类的成员,出现在类体中,它的作用范围由类来决定;而普通函数是独立的,作用范围是全局的,或位于某个命名空间内。

上节我们在最后的完整示例中给出了 Student 类的定义,如下所示:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Student{
public: //类包含的变量
  char *name;
  int age;
  float score;
public: //类包含的函数
  void say(){
    printf("%s的年龄是 %d,成绩是 %f\n", name, age, score);
  }
};
上面的代码在类体中定义了成员函数。你也可以只在类体中声明函数,而将函数定义放在类体外面,如下图所示:
class Student{
public:
  char *name;
  int age;
  float score;
public:
  void say(); //函数声明
};
//函数定义
void Student::say(){
  printf("%s的年龄是 %d,成绩是 %f\n", name, age, score);
}

在类体中直接定义函数时,不需要在函数名前面加上类名,因为函数属于哪一个类是不言而喻的。

但当成员函数定义在类外时,就必须在函数名前面加上类名予以限定。::被称为域解析符(也称作用域运算符或作用域限定符),用来连接类名和函数名,指明当前函数属于哪个类。

如果在域解析符“::”的前面没有类名,或者函数名前面既无类名又无域解析符“::”,如:

?
1
2
3
4
5
6
7
8
//无类名
::say( ){
  //TODO
}
//无类名也无域解析符
say( ){
  //TODO
}


则表示 say() 函数不属于任何类,这个函数不是成员函数,而是全局函数,即非成员函数的一般普通函数。

成员函数必须先在类体中作原型声明,然后在类外定义,也就是说类体的位置应在函数定义之前,否则编译时会出错。

虽然成员函数在类的外部定义,但在调用时会根据在类中声明的函数原型找到函数的定义(函数代码),从而执行该函数。
inline 成员函数

在类体中和类体外定义成员函数是有区别的:在类体中定义的成员函数为内联(inline)函数,在类体外定义的不是。

内联函数一般不是我们所期望的,它会将函数调用处用函数体替代,所以我建议在类体内部对成员函数作声明,而在类体外部进行定义,这是一种良好的编程习惯。

当然,如果你的函数比较短小,希望定义为内联函数,那也没有什么不妥的。

如果你既希望将函数定义在类体外部,又希望它是内联函数,那么可以在声明函数时加 inline 关键字,如下所示:

?
1
2
3
4
5
6
7
8
9
10
11
12
class Student{
public:
  char *name;
  int age;
  float score;
public:
  inline void say(); //声明为内联函数
};
//函数定义
void Student::say(){
  printf("%s的年龄是 %d,成绩是 %f\n", name, age, score);
}

这样,say() 就会变成内联函数。
在类体内部定义的函数也可以加 inline 关键字,但这是多余的,因为类体内部定义的函数默认就是内联函数。
值得注意的是,如果在类体外定义 inline 函数,则必须将类定义和成员函数的定义都放在同一个头文件中(或者写在同一个源文件中),否则编译时无法进行嵌入(将函数代码的嵌入到函数调用出)。这样做虽然提高了程序的执行效率,但从软件工程质量的角度来看,这样做并不是好的办法,因此实际开发中较少在类中使用内联函数。

C++提出内联函数的主要用意是:用内联函数取代带参宏定义(函数传参比宏更加方便易用),而不是提高程序运行效率,因为与执行函数花费的时间相比,调用函数花费的时间往往微乎其微。

C++成员函数的存储方式
用类去定义对象时,系统会为每一个对象分配存储空间。如果一个类包括了数据和函数,要分别为数据和函数的代码分配存储空间。

按理说,如果用同一个类定义了10个对象,那么就需要分别为10个对象的数据和函数代码分配存储单元,如下图所示。
详解C++编程中类的成员变量和成员函数的相关知识
能否只用一段空间来存放这个共同的函数代码段,在调用各对象的函数时,都去调用这个公用的函数代码。如图所示。
详解C++编程中类的成员变量和成员函数的相关知识
显然,这样做会大大节约存储空间。C++编译系统正是这样做的,因此每个对象所占用的存储空间只是该对象的数据部分所占用的存储空间,而不包括函数代码所占用的存储空间。如果声明了一个类:

?
1
2
3
4
5
6
7
8
9
10
11
class Time
{
  public:
  int hour;
  int minute;
  int sec;
  void set( )
  {
   cin>>a>>b>>c;
  }
};

可以用下面的语句来输出该类对象所占用的字节数:

?
1
cout<<sizeof(Time)<<endl;


输出的值是12。

这就证明了一个对象所占的空间大小只取决于该对象中数据成员所占的空间,而与成员函数无关。

函数代码是存储在对象空间之外的。如果对同一个类定义了10个对象,这些对象的成员函数对应的是同一个函数代码段,而不是10个不同的函数代码段。需要注意的是,虽然调用不同对象的成员函数时都是执行同一段函数代码,但是执行结果一般是不相同的。

不同的对象使用的是同一个函数代码段,它怎么能够分别对不同对象中的数据进行操作呢?

原来C++为此专门设立了一个名为this的指针,用来指向不同的对象。需要说明:
不论成员函数在类内定义还是在类外定义,成员函数的代码段都用同一种方式存储。
不要将成员函数的这种存储方式和inMne(内置)函数的概念混淆。不要误以为用inline声明(或默认为inline)的成员函数,其代码段占用对象的存储空间,而不用 inline声明的成员函数,其代码段不占用对象的存储空间。不论是否用inline声明,成员函数的代码段都不占用对象的存储空间。用inline声明的作用是在调用该函数时,将函数的代码段复制插人到函数调用点,而若不用inline声明,在调用该函数时,流程转去函数代码段的人口地址,在执行完该函数代码段后,流程返回函数调用点。inline与成员函数是否占用对象的存储空间无关,它们不属同一个问題,不应搞混。
应当说明,常说的“某某对象的成员函数”,是从逻辑的角度而言的,而成员函数的存储方式,是从物理的角度而言的,二者是不矛盾的。