C++类与对象

C++ 类与对象·

C++ 在 C 语言的基础上增加了面向对象编程,C++ 支持面向对象程序设计。类是 C++ 的核心特性,通常被称为用户定义的类型。

类用于指定对象的形式,它包含了数据表示法和用于处理数据的方法。类中的数据和方法称为类的成员。函数在一个类中被称为类的成员。

C++ 类定义·

定义一个类,本质上是定义一个数据类型的蓝图。这实际上并没有定义任何数据,但它定义了类的名称意味着它定义了类的对象包括了什么,以及可以在这个对象上执行哪些操作。

类定义是以关键字 class 开头,后跟类的名称。类的主体是包含在一对花括号中。类定义后必须跟着一个分号或一个声明列表。并且其中的成员数据(变量)不能赋初始值。例如,我们使用关键字 class 定义 Box 数据类型,如下所示:

1
2
3
4
5
6
7
8
9
10
class Box
{
public:
void V(){v=length*breadth*height;return v;} //计算体积
void C(){c=4*(length+breadth+height);return c;} //计算周长
private:
double length; // 盒子的长度
double breadth; // 盒子的宽度
double height; // 盒子的高度
};

类成员的访问限制(权限)·

数据封装是面向对象编程的一个重要特点,它防止函数直接访问类类型的内部成员。类成员的访问限制是通过在类主体内部对各个区域标记 public、private、protected 来指定的。关键字 public、private、protected 称为访问修饰符。

一个类可以有多个 public、protected 或 private 标记区域。每个标记区域在下一个标记区域开始之前或者在遇到类主体结束右括号之前都是有效的。成员和类的默认访问修饰符是 private

1
2
3
4
5
6
7
8
9
class Base
{
public:
// 公有成员
protected:
// 受保护成员
private:
// 私有成员
};

公有(public)成员·

公有成员在程序中类的外部是可访问的。您可以不使用任何成员函数来设置和获取公有变量的值。

私有(private)成员·

私有成员变量或函数在类的外部是不可访问的,甚至是不可查看的。只有类和友元函数可以访问私有成员。

默认情况下,如果您没有使用任何访问修饰符,类的成员将被假定为私有成员。

保护(protected)成员·

保护成员变量或函数与私有成员十分相似,但有一点不同,保护成员在派生类(即子类)中是可访问的。

代码演示:·

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
#include <iostream>
using namespace std;
class text
{
public:
int a;
private:
int b;
protected:
int c;
public:
void setb(int Bb){b=Bb;}
};
class t::text // t 是 text 的派生类
{
public:
void setc(int Cc);
};
void t::setc(int Cc) {c=Cc;}

int main()
{
text text1;
text1.a = 5; //这是允许的,因为 a 是公有的
text1.b = 10; //这是不允许的,因为 b 是私有的
text1.setb(10); //这是允许的,因为 b 的赋值是通过外部接口去给 b 赋值

t text2;
t.setc(15); //这里使用了成员函数给 text 中的 c 赋值了
}

定义 类 的对象·

类提供了对象的蓝图,所以基本上,对象是根据类来创建的。声明类的对象,就像声明基本类型的变量一样。下面的语句声明了类 Box 的两个对象:

Box Box1; // 声明 Box1,类型为 Box
Box Box2; // 声明 Box2,类型为 Box

对象 Box1 和 Box2 都有它们各自的数据成员。

访问 对象 里的数据成员·

**在 类 外**访问 类的对象里 的公共数据成员可以使用直接成员访问运算符 . 来访问。
如使用 “对象名.成员名” 的方式访问public属性的成员

为了更好地理解这些概念,让我们尝试一下下面的实例:

实例·

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
#include <iostream>
using namespace std;
class Box
{
public:
double length; // 长度
double breadth; // 宽度
double height; // 高度
};
int main( )
{
Box Box1; // 声明 Box1,类型为 Box
Box Box2; // 声明 Box2,类型为 Box
double volume = 0.0; // 用于存储体积

// box 1 详述
Box1.height = 5.0;
Box1.length = 6.0;
Box1.breadth = 7.0;

// box 2 详述
Box2.height = 10.0;
Box2.length = 12.0;
Box2.breadth = 13.0;

// box 1 的体积
volume = Box1.height * Box1.length * Box1.breadth;
cout << "Box1 的体积:" << volume <<endl;

// box 2 的体积
volume = Box2.height * Box2.length * Box2.breadth;
cout << "Box2 的体积:" << volume <<endl;
return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:

1
2
Box1 的体积:210
Box2 的体积:1560

需要注意的是,私有的成员和受保护的成员不能使用直接成员访问运算符 . 来直接访问。我们将在后续的教程中学习如何访问私有成员和受保护的成员。

类的构造函数·

类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。

特点:

  1. 构造函数的名称与类的名称是完全相同的,
  2. 并且不会返回任何类型,
  3. 也不会返回 void。

**作用:**构造函数可用于为某些成员变量设置初始值。

在创建派生类对象时,构造函数的执行顺序:
基类构造函数 → 对象成员构造函数 → 派生类本身的构造函数

下面的实例有助于更好地理解构造函数的概念:

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
#include <iostream>
using namespace std;

class Line
{
public:
void setLength( double len );
double getLength( void );
Line(); // 这是不带参数的构造函数,可以用于旁白。
Line(double len); // 这是带参数的构造函数,在创建对象时就会给对象赋初始值
//这里的两个Line是使用了重载函数

private:
double length;
};

// 成员函数定义,包括构造函数
Line::Line(void)
{
cout << "Object is being created" << endl;
length = 1;
}
Line::Line( double len)
{
cout << "Object is being created, length = " << len << endl;
length = len;
}

void Line::setLength( double len )
{
length = len;
}

double Line::getLength( void )
{
return length;
}
// 程序的主函数
int main( )
{
Line line1; //自动指向不带参数的
Line line2(10.0); //自动指向带参数的

// 设置长度
line.setLength(6.0);
cout << "Length of line : " << line.getLength() <<endl;

return 0;
}

使用初始化列表来初始化字段·

主要是用于 较多参数 时的值的初始化

使用初始化列表来初始化字段:

1
2
3
4
Line::Line( double len): length(len)
{
cout << "Object is being created, length = " << len << endl;
}

上面的语法等同于如下语法:

1
2
3
4
5
Line::Line( double len)
{
length = len;
cout << "Object is being created, length = " << len << endl;
}

假设有一个类 C,具有多个字段 X、Y、Z 等需要进行初始化,同理地,您可以使用上面的语法,只需要在不同的字段使用逗号进行分隔,如下所示:

1
2
3
4
C::C( double a, double b, double c): X(a), Y(b), Z(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
#include <iostream>
using namespace std;

class Line
{
public:
void setLength( double len );
double getLength( void );
Line(); // 这是构造函数声明
~Line(); // 这是析构函数声明

private:
double length;
};
// 成员函数定义,包括构造函数
Line::Line(void)
{
cout << "对象已被创建" << endl;
}
Line::~Line(void)
{
cout << "对象已被删除" << endl;
}

void Line::setLength( double len )
{
length = len;
}

double Line::getLength( void )
{
return length;
}
// 程序的主函数
int main( )
{
Line line;
// 设置长度
line.setLength(6.0);
cout << "Length of line : " << line.getLength() <<endl;
return 0;
}

执行后的结果为:

对象已被创建
Length of line : 6
对象已被删除

类的拷贝构造函数·

类的友元函数·

类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。
友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。
如果要声明函数为一个类的友元,需要在类定义中该函数原型前使用关键字 friend

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
53
54
55
#include <iostream>

using namespace std;

class Box
{
double width;
public:
friend void printWidth(Box box1);//也可以是(Box &box1),在于是否需要形参改变实参。
friend class BigBox;
void setWidth(double wid);
};

class BigBox
{
public :
void Print(int width, Box &box2)
{
// BigBox是Box的友元类,它可以直接访问Box类的任何成员
box2.setWidth(width);
cout << "Width of box : " << box2.width << endl;
}
};

// 成员函数定义
void Box::setWidth(double wid)
{
width = wid;
}

// 请注意:printWidth() 不是任何类的成员函数
void printWidth(Box box1)
{
/* 因为 printWidth() 是 Box 的友元,它可以直接访问该类的任何成员 */
cout << "Width of box : " << box1.width << endl;
}

// 程序的主函数
int main()
{
Box box;
BigBox big;

// 使用成员函数设置宽度
box.setWidth(10.0);

// 使用友元函数输出宽度
printWidth(box);

// 使用友元类中的方法设置宽度
big.Print(20, box);

getchar();
return 0;
}

类 与 对象详解·

到目前为止,我们已经对 C++ 的类和对象有了基本的了解。下面的列表中还列出了其他一些 C++ 类和对象相关的概念,可以点击相应的链接进行学习。

概念 描述
类成员函数 类的成员函数是指那些把定义和原型写在类定义内部的函数,就像类定义中的其他变量一样。
类访问修饰符 类成员可以被定义为 public、private 或 protected。默认情况下是定义为 private。
构造函数 与 析构函数 类的构造函数是一种特殊的函数,在创建一个新的对象时调用。类的析构函数也是一种特殊的函数,在删除所创建的对象时调用。
C++ 拷贝构造函数 拷贝构造函数,是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。
C++ 友元函数 友元函数可以访问类的 private 和 protected 成员。
C++ 内联函数 通过内联函数,编译器试图在调用函数的地方扩展函数体中的代码。
C++ 中的 this 指针 每个对象都有一个特殊的指针 this,它指向对象本身。
C++ 中指向类的指针 指向类的指针方式如同指向结构的指针。实际上,类可以看成是一个带有函数的结构。
C++ 类的静态成员 类的数据成员和函数成员都可以被声明为静态的。

继承 与 派生·

单继承·

一个类可以派生自多个类,这意味着,它可以从多个基类继承数据和函数。定义一个派生类,我们使用一个类派生列表来指定基类。类派生列表以一个或多个基类命名,形式如下:

1
class derived_class: access_specifier base_class

其中,访问修饰符 access_specifier 是 publicprotectedprivate 其中的一个,base_class 是之前定义过的某个类(基类)的名称。如果未使用访问修饰符 access_specifier,则默认为 private

多继承·

多继承即一个子类可以有多个父类,它继承了多个父类的特性。

C++ 类可以从多个类 继承成员,语法如下:

1
2
class <派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>,...
{<派生类类体>};

其中,访问修饰符 继承方式 是 publicprotectedprivate 其中的一个,用来修饰每个基类,各个基类之间用逗号分隔。

按访问范围分·

总结一下三种继承方式:

继承方式 基类的public成员 基类的protected成员 基类的private成员 继承引起的访问控制关系变化概括
public继承 仍为public成员 仍为protected成员 不可访问 基类的非私有成员在子类的访问属性不变
protected继承 变为protected成员 仍为protected成员 不可访问 基类的非私有成员都为子类的保护成员
private继承 变为private成员 变为private成员 不可访问 基类中的非私有成员都称为子类的私有成员

公有继承 pubilc·

  • 基类的public和protected成员的访问属性在派生类中保持不变,但基类的private成员不可直接访问。
  • 派生类中的成员函数可以直接访问基类中的public和protected成员,但不能直接访问基类的private成员。
  • 通过派生类的对象只能访问基类的public成员。

保护继承 protected·

  • 基类的public和protected成员都以protected权限出现在派生类中,但基类的private成员不可直接访问。
  • 派生类中的成员函数可以直接访问基类中的public和protected成员,但不能直接访问基类的private成员。
  • 通过派生类的对象不能直接访问基类中的任何成员

私有继承 private·

  • 基类的public和protected成员都以private权限出现在派生类中,但基类的private成员不可直接访问,且以 该派生类基类 的派生类不能访问 该基类任何成员
  • 派生类中的成员函数可以直接访问基类中的public和protected成员,但不能直接访问基类的private成员。
  • 通过派生类的对象不能直接访问基类中的任何成员

派生后 继承 到的 权限 所对应的:

派生类的成员对基类成员的访问

派生类的对象对基类成员的访问

一个派生类继承了所有的基类方法,但下列情况除外:

  • 基类的构造函数、析构函数和拷贝构造函数。
  • 基类的重载运算符。
  • 基类的友元函数。

二义性问题·

虚基类·