本篇详细学习类和对象相关的知识,类是C++的核心属性,主要用于指定对象的形式,封装了数据和函数的组合。

C++类的定义

定义类需要使用关键字class,主体在{}中,包含了成员变量和成员函数:

1
2
3
4
5
6
7
class classname{
Access specifiers: //访问修饰符public、private、protected
Date members/variables;
Member functions(){

}
};

访问修饰符:确定类成员的访问属性,在类对象作用域内,公共成员在类外部是可以访问的;

1
2
3
4
5
6
class Box{
public:
double length;
double breadth;
double height;
};

C++定义对象

1
2
Box Box1;
Box Box2;

对象Box1和Box2都有各自的数据成员,类的对象的公共数据成员可以直接使用成员访问运算符.来访问。

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
#include <iostream>
#include <windows.h>
using namespace std;
class Box
{
public:
double length; // 长度
double breadth; // 宽度
double height; // 高度
double get(void);
void set(double len, double bre, double hei);
};

double Box::get(void)
{
return length * breadth * height;
}

void Box::set(double len, double bre, double hei)
{
length = len;
breadth = bre;
height = hei;
}

int main(void){
SetConsoleOutputCP(CP_UTF8); //保证输出不是乱码
Box Box1; // 声明 Box1,类型为 Box
Box Box2;
Box Box3;
double volume = 0.0;

Box1.length = 5.0;
Box1.breadth = 6.0;
Box1.height = 7.0;
volume = Box1.length * Box1.breadth * Box1.height;
cout << "Box1 的体积:" << volume <<endl;

Box2.length = 10.0;
Box2.breadth = 12.0;
Box2.height = 13.0;
volume = Box2.length * Box2.breadth * Box2.height;
cout << "Box2 的体积:" << volume <<endl;

Box3.set(15.0, 16.0, 17.0);
volume = Box3.get();
cout << "Box3 的体积:" << volume <<endl;

return 0;
}

Tip:私有的成员和受保护的成员不能使用直接访问运算符进行访问。

类和对象详解

1.类成员函数:

指的是把定义和原型写在类内的函数,就像类定义的其他变量一样,类成员函数是类的一个成员,可以操作类的任意对象。

成员函数可以定义在类定义内部,或者单独使用范围解析运算符::来定义,在类定义中定义的成员函数把函数声明为内联,即使没有使用inline标识符:

1
2
3
4
5
6
7
8
9
10
11
12
class Box
{
public:
double length; // 长度
double breadth; // 宽度
double height; // 高度

double getVolume(void)
{
return length * breadth * height;
}
};

同时也可以在类外部使用范围解析运算符::定义该函数,如下所示:

1
2
3
4
double Box::getVolume(void)
{
return length * breadth * height;
}

Tip::: 运算符之前必须使用类名,调用成员函数是在对象上使用点运算符,这样就可以操作与该对象有关的数据。

1
2
3
Box myBox;          // 创建一个对象

myBox.getVolume(); // 调用该对象的成员函数

2.访问修饰符:

数据封装是面向对象的一个重要特点,可以防止函数直接访问类的内部成员,类成员的访问限制主要通过在类内各个部分标记public、private、protected来指定。三个关键字就是访问修饰符;一个类可以有多个public、private、protected区域,每个区域在下一个标记区域开始之前或者类结束之前都是有效的,默认的修饰符都是private。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Base {

public:

// 公有成员

protected:

// 受保护成员

private:

// 私有成员

};

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

class Line{
public:
double length;
void setlength(double len);
double getlength(void);
}

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

double Line::getlength(void){
return length;
}

int main(void){
Line line;
line.setlength(2.0);
cout << line.length << endl;

line.length = 3.0;
cout << line.length << endl;
return 0;
}

private成员:

私有成员变量和函数在类的外部是不可访问的,只有类和友元函数可以访问私有成员,在实际操作中一般会在私有区域定义数据,在公有区域定义相关函数,以便在类的外部可以调用这些函数。

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

class Box{
public:
double length;
void setwidth(double wid);
double getwidth(void);
private:
double width;
};

void Box::setwidth(double wid){
width = wid;
}

double Box::getwidth(void){
return width;
}

int main(void){
Box box;

box.length = 10.0; //不使用成员函数设置,因为是公有的。
cout << "length of box:" << box.length << endl;

//box.width = 10.0; 因为width是私有的
box.setwidth(10.0); //可以,通过成员进行设置。
cout << "width of box:" << box.getwidth() << endl;

return 0;
}

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 Box{
protected:
double width;
}

class smallBox:Box{
public:
void setsmallwidth(double wid);
double getsmallwidth(void);
};

void smallBox::setsmallwidth(double wid){
width = wid;
}

double smallBox::getsmallwidth(void){
return width;
}

int main(void){
smallBox box;

box.setsmallwidth(15.0);
cout << "width of box:" << getsmallwidth << endl;

return 0;
}

继承中的特点

访问权限有三种,继承也有三种,相应的改变基类成员的属性。

1.public继承:基类的成员在派生类的访问属性还是保持public、private、protected。

2.protected继承:基类的成员在派生类的访问属性变为protected、protected、private。

3.private继承:基类的成员在派生类的访问属性变为private、private、private。

无论是上述的哪一种继承方式,下面两点都没有改变:

1.private成员只能被本类成员和友元访问,不能被派生类访问。

2.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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include<iostream>
#include<assert.h>
using namespace std;

class A{
public:
int a;
A(){
a1 = 1;
a2 = 2;
a3 = 3;
a = 4;
} //构造函数
void fun(){
cout << a << endl; //正确
cout << a1 << endl; //正确
cout << a2 << endl; //正确
cout << a3 << endl; //正确
}
public:
int a1;
protected:
int a2;
private:
int a3;
};
class B : public A{
public:
int a;
B(int i){
A();
a = i;
} //构造函数
void fun(){
cout << a << endl; //正确,public成员
cout << a1 << endl; //正确,基类的public成员,在派生类中仍是public成员。
cout << a2 << endl; //正确,基类的protected成员,在派生类中仍是protected可以被派生类访问。
cout << a3 << endl; //错误,基类的private成员不能被派生类访问。
}
};
int main(){
B b(10);
cout << b.a << endl;
cout << b.a1 << endl; //正确
cout << b.a2 << endl; //错误,类外不能访问protected成员
cout << b.a3 << endl; //错误,类外不能访问private成员
system("pause");
return 0;
}

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

class Line{
public:
void setlength(double len);
double getlength(void);
Line();
private:
double length;
};

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

double Line::getlength(void){
return length;
}

Line::Line(void){
cout << "object have been created!" << endl;
} //总是忘记加Line::

int main(void){
Line line;
line.setlength(6.0);
cout << "Line's length is " << line.getlength() << endl;
return 0;
}

默认的构造函数没有任何参数,若是有需要,构造函数也可以带上参数,这样在创建对象时会给对象赋初值。

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

class Line{
public:
void setlength(double len);
double getlength(void);
Line(double len);
private:
double length;
};

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

double Line::getlength(void){
return length;
}

Line::Line(double len){
cout << "object has been created, length = " << len << endl;
length = len;
}

int main(void){
Line line(10.0);
cout << "length of line : " << line.getlength() << endl;
line.setlength(6.0);
cout << "length of line : " << line.getlength() << endl;

return 0;
}

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

1
2
3
Line::line(double len):length(len){
cout << "object is being created, length = " << len << endl;
} //初始化列表的语法等同于上面的常规初始化

若是有一个类C,具有多个字段需要初始化,同理使用上述语法,不同字段使用逗号进行分隔即可。

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

class Line{
public:
void setlength(double len);
double getlength(void);
Line();
~Line();
private:
double length;
};

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

double Line::getlength(void){
return length;
}

Line::Line(void){
cout << "object is been created" << endl;
}

Line::~Line(void){
cout << "object is been deleted" << endl;
}

int main(void){
Line line;
line.setlength(10.0);
cout << "length of line is" << line.getlength() <<endl;
return 0;
}

拷贝构造函数

拷贝构造是一种特殊的构造函数,通过使用另一个同类型的对象来初始化新建的对象,若是类中没有定义拷贝构造函数,编译器会自行定义,若是类带有指针变量且有动态内存分配,则必须有一个拷贝构造函数:

1
2
3
classname (const classname &obj){
//构造函数主体
} //obj是对象引用,用于初始化另外一个对象
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
#include <iostream>
using namespace std;

class Line{
public:
int getlength(void);
Line(int len);
Line(const Line &obj);
~Line();

private:
int *ptr;
};

Line::Line(int len){
cout << "调用构造函数" << endl;
ptr = new int;
*ptr = len;
}

Line::Line(const Line &obj){
cout << "调用拷贝构造函数并为指针ptr分配内存" << endl;
ptr = new int;
*ptr = *obj.ptr;
}

Line::~Line(){
cout << "释放内存" << endl;
delete ptr;
}

int Line::getlength(void){
return *ptr;
}

void display(Line obj){
cout << "line 大小:" << obj.getlength() <<endl;
}

int main(void){
Line Line(10);
display(Line);
return 0;
}

上述实例输出如下:

1
2
3
4
5
调用构造函数
调用拷贝构造函数并为指针 ptr 分配内存
line 大小 : 10
释放内存
释放内存

出现调用拷贝构造函数的情况是因为在调用display()用的是值传递而不是引用传递就会导致会有拷贝副本的出现就会触发拷贝构造函数。

上述实例中稍作修改,通过使用已有对象来初始化新建对象:

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
56
57
58
59
#include <iostream>

using namespace std;

class Line
{
public:
int getLength( void );
Line( int len ); // 简单的构造函数
Line( const Line &obj); // 拷贝构造函数
~Line(); // 析构函数

private:
int *ptr;
};

// 成员函数定义,包括构造函数
Line::Line(int len)
{
cout << "调用构造函数" << endl;
// 为指针分配内存
ptr = new int;
*ptr = len;
}

Line::Line(const Line &obj)
{
cout << "调用拷贝构造函数并为指针 ptr 分配内存" << endl;
ptr = new int;
*ptr = *obj.ptr; // 拷贝值
}

Line::~Line(void)
{
cout << "释放内存" << endl;
delete ptr;
}
int Line::getLength( void )
{
return *ptr;
}

void display(Line obj)
{
cout << "line 大小 : " << obj.getLength() <<endl;
}

// 程序的主函数
int main( )
{
Line line1(10);

Line line2 = line1; // 这里也调用了拷贝构造函数

display(line1);
display(line2);

return 0;
}

display()函数是值拷贝,所以这段程序一共是调用了三次拷贝构造函数。

C++友元函数

类的友元函数定义在类的外部,但是有权利去访问私有成员和受保护成员,友元函数不是成员函数,友元可以是一个函数,也可以是一个类,被称为友元类,在该情况下,整个类及其所有成员都是友元。

声明一个函数是一个类的友元,需要在前面加上关键字friend。

1
2
3
4
5
6
7
class Box{
double width;
public:
double length;
friend void printwidth(Box box);
void setwidth(double wid);
};
1
friend class classtwo;

声明类classtwo的所有成员函数作为类classone的友元。

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

class Line{
double width;
public:
double length;
friend void printwidth(Line line);
void setwidth(double wid);
};

void setwidth(double wid){
width = wid;
}

void printwidth(Line line){
cout << line.width << endl;
}

int main(void){
Line line;
line.setwidth(10.0);
printwidth(line);
return 0;
}

C++内联函数

C++内联函数通常是和类一起使用,若是一个函数是内联的,那么在编译的时候,编译器会把函数的代码副本放置在每个调用该函数的地方,内联函数若是修改则需要重新编译所有的客户端,若是想将函数定义为内联函数,需要在函数名前加上inline关键字,且在调用函数之前就要对函数进行定义,定义函数多于一行(过于复杂),编译器会忽略inline限定符。

在类定义中的定义的函数都是内联函数,即使 没有关键字。

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
using namespace std;

inline int max(int a, int b){
return (a > b) ? a : b;
}

int main(){
cout << "Max (20,10): " << Max(20,10) << endl;
return 0;
}

C++this指针

在C++中,this指针是一个特殊的指针,指向当前对象的实例。

每一个对象都可以通过this指针来访问自己的地址,this是一个隐藏的指针,可以在类的成员函数中使用,也可以用来指向调用对象;当一个对象的成员函数被调用,编译器会隐式传递地址作为this指针。

友元函数没有this指针,因为友元不是类的成员,只有成员函数才有this指针。

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>

class MyClass {
private:
int value;

public:
void setValue(int value) {
this->value = value;
}

void printValue() {
std::cout << "Value: " << this->value << std::endl;
}
};

int main() {
MyClass obj;
obj.setValue(42);
obj.printValue();

return 0;
}

下面这个实例用于比较长方体的体积:

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

class Box{
public:
Box(double l = 2.0, double b = 2.0, double h = 2.0){
cout << "调用构造函数" << endl;
length = l;
breadth = b;
height = h;
}

double volume(){
return length * breadth * height;
}

int compare(Box box){
return this->volume() > box.volume();
}

private:
double length;
double breadth;
double height;
};

int main(void){
Box box1(3.3, 1.2, 1.5);
Box box2(8.5, 6.0, 2.0);

if(box1.compare(box2)){
cout << "box2的体积比box1小" << endl;
}else{
cout << "box2的体积比box1大" << endl;
}

return 0;
}

C++指向类的指针

指向类的指针与指向结构的指针类似,访问指向类的指针的成员,需要使用成员访问运算符->,与所有指针一样,在使用之前,必须对其进行初始化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>

class myclass{
public:
int data;

void display(){
std::cout << "Data: " << data << std::endl;
}
};

int main(void){
myclass obj;
obj.data = 42;
myclass *ptr = &obj;
std::cout << "data via pointer: " << ptr->data <<endl;
ptr->display();
return 0;
}

动态分配内存:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
class myclass{
public:
int data;
void display(){
std::cout << “data: ” << data << endl;
}
};

int main(void){
myclass *ptr = new myclass;
ptr->data = 42;
ptr->display();
delete ptr;
return 0;
}

指向类的指针作为函数参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
class myclass{
public:
int data;
void display(){
std::cout << "data: " << data << endl;
}
};

void processobject(myclass *ptr){
ptr->display();
}

int main(){
myclass obj;
obj.data = 42;
processobject(&obj);
return 0;
}

C++类的静态成员

我们可以使用static关键字把类成员定义为静态的,若为静态,则无论创建多少个类的对象,静态成员都只有一个副本。

静态成员在类的所有对象中是共享的,静态成员的初始化不能放在类的定义中,可以通过外部使用范围解析运算符::来重新声明静态变量从而对它进行初始化。

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 Box{
public:
static int objectcount;
Box(double l, double b, double h){
cout << "Constructor called" << endl;
length = l;
breadth = b;
height = h;
objectcount++;
}
double volume(){
return length * breadth * height;
}

private:
double length;
double breadth;
double height;
};

int Box::objectcount = 0; //静态成员变量不能在类定义中初始化

int main(){
Box box1(3.3, 1.2, 1.5);
Box box2(8.5, 4.0, 3.2);
cout << "Total object number: " << Box::objectcount << endl;
return 0;
}

静态成员函数

若是将函数成员声明为静态,就可以将函数和任意特定对象独立开来,静态成员函数即使在类对象不存在的情况下也可以被调用,静态函数只需要使用类名加范围解析运算符::就可以访问。

静态成员函数只能访问静态成员数据、其他静态成员函数和类外部的函数。

静态成员函数有一个类范围,不能访问类的this指针,只能访问静态成员。

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
#include <iostream>
using namespace std;
class Box{
public:
static int objectcount;
Box(double l, double b, double h){
cout << "object create!" << endl;
length = l;
breadth = b;
height = h;
objectcount++;
}

double volume{
return length * breadth * height;
}

static getcount{
return objectcount;
}

private:
double length;
double breadth;
double height;
};

int Box::objectcount = 0;

int main(void){
cout << "Inital stage count: " << Box::getcount() << endl;
Box box1(3.0, 1.2, 1.5);
Box box2(7.1, 8.5, 7.2);
cout << "Final stage count: " << Box::getcount() << endl;
return 0;
}