c++远征03封装篇(上)

C++远征之封装篇(上)-慕课网

简介:本课程将代领小伙伴们真正迈入C++语言的面向对象大门,在课程中,将会深入讲解类的定义方法,属性的封装方法,构造函数和析构函数等内容,并且所有知识均会通过编码实践的方式讲解到操作层面,力求帮助小伙伴们即学即会!

1 课程介绍(略)

2 类与对象初体验

2.1 c++ 类和对象

类的定义

  • 关键字(class)
  • 类名
  • 属性(数据成员)
  • 方法(成员函数)
  • 访问限定符

访问限定符|说明
—|—
public|公共的(希望暴露)
protected|受保护的
private|私有的(希望隐藏)

2.2 c++ 类对象

1
2
3
4
5
6
7
8
9
class TV
{
public:
char name[20];
int type;

void changeVol;
void power();
}
  • 从栈实例化对象
1
2
3
4
5
TV tv;
TV tv[20];

tv.type = 0;
tv.changeVol();
  • 从堆实例化对象
1
2
3
4
5
6
7
8
9
10
TV *p = new TV();
TV *q = new TV[20];

p -> type = 0;
p -> changeVol();

delete p;
delete []q;
p = NULL;
q = NULL;

3 初始字符串类型

说明: string 类型,<string>,命名空间为 std

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 初始化方式
string s1; // 空串
string s2("ABC"); // 用字符串字面亮初始化 s2
string s3(s2); // 将 s3 初始化为 s2 的一个副本
string s4(n, 'c'); // 将 s4 初始化为字符 'c' 的 n 个副本

// 常用操作
s1.empty(); // 判空:true, s1 为空串 ; false , s1 不是空串
s2.size(); // 返回 s2 中字符的个数
s2[2]; // 返回下标 2 位置的字符
s1 + s2; // 返回拼接后的新串
s1 = s2; // 把 s1 的内容替换为 s2 的内容
s1 == s2; // 判定相等:true, 相等;false, 不相等
s1 != s2; // 判定不等:true, 不想等; false, 相等

限制: 使用 “+” 拼接字符串时,可以用 ”hello” ,但不能连续两个都是这种形式!

1
2
3
4
5
6
string s1 = "hello";
string s2("world");
strnig s3 = s1 + s2;
string s4 = "hello" + s2;
string s5 = "hello" + s2 + "world";
string s6 = "hello" + "world"; // 错误
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
#include <iostream>
#include <stdlib.h>
#include <string>

using namespace std;

int main(void)
{

string name;
cout << "Please input your name:";
getline(cin, name);
if(name.empty())
{
cout << "input is null." << endl;
return 1;
}
if(name == "imooc")
{
cout << "you are a administractor" << endl;
}
cout << "hello " + name << endl;
cout << "your name length:" << name.size() << endl;
cout << "your name first letter is:" << name[0] << endl;

return 0;
}

4 属性封装的艺术

4.1 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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include <iostream>
#include <stdlib.h>
#include <string>
using namespace std;

/*********************
* 数据的封装
* 定义一个 Student 类,含有以下信息:
* 1. 姓名:name
* 2. 性别:gender
* 3. 学分(只读):score
* 4. 学习:study
***********************/


class Student
{
public:
// m_strName
void setName(string _name)
{

m_strName = _name;
}
string getName()
{

return m_strName;
}

// m_strGender
void setGender(string _gender)
{

m_strGender = _gender;
}
string getGender()
{

return m_strGender;
}

// m_iScore
int getScore()
{

return m_iScore;
}


void initScore()
{

m_iScore = 0;
}

void study(int _score)
{

m_iScore += _score;
}
private:
string m_strName;
string m_strGender;
int m_iScore;
};

int main(void)
{

Student stu;
stu.initScore();

stu.setName("mengxiang");
stu.setGender("boy");
stu.study(5);
stu.study(3);

cout << stu.getName() << " " << stu.getGender() << endl;

return 0;
}

5 精彩的类外定义

5.1 类外定义

说明: 成员函数有两种定义方式

  1. 类內定义:直接在类中编写函数,这是函数会尽可能编译为 inline 函数。
  2. 类外定义:只在类中编写函数的声明,函数的定义(实现)放在其它位置。

5.2 代码演示

说明: 以分文件方式为例。
Teacher.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <string>
using namespace std;
class Teacher
{
public:
// m_strName
void setName(string _name);
string getName();

// m_strGender
void setGender(string _gender);
string getGender();

// m_iAge
void setAge(int _age);
int getAge();

void teach();
private:
string m_strName;
string m_strGender;
int m_iAge;
};

Teacher.cpp

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
#include <iostream>
#include <stdlib.h>
#include "Teacher.h"
using namespace std;

// m_strName
void Teacher::setName(string _name)
{
m_strName = _name;
}
string Teacher::getName()
{
return m_strName;
}

// m_strGender
void Teacher::setGender(string _gender)
{
m_strGender = _gender;
}
string Teacher::getGender()
{
return m_strGender;
}

// m_iAge
void Teacher::setAge(int _age)
{
m_iAge = _age;
}

int Teacher::getAge()
{
return m_iAge;
}

void Teacher::teach()
{
cout << "having class..." << endl;
}

int main(void)
{

Teacher t;
t.setName("keating");
t.setGender("male");
t.setAge(42);

cout << t.getName() << " " << t.getAge() << " " << t.getGender() << endl; // keating 42 male
t.teach(); // having class...

return 0;
}

6 对象的生离死别

6.1 构造函数讲解

问题
实例化的对象是如何在内存中存储的?
类中的代码是如何存储的?
代码和数据关系是怎样的?

内存分区

内存分区|说明|例如
—|—|—
栈区|通过普通方式创建的变量,内存分配和回收由系统控制| int x = 0;p = NULL;
堆区|动态分配内存的变量| int
p = new int[20];
全局区|存储全局变量及静态变量|
常量区|存储代码中的常量和字符串字面量|string str = “hello”;
代码区|存储逻辑代码的二进制|

构造函数

  • 在对象实例化时被自动调用
  • 构造函数名和类名相同
  • 构造函数可以有多个重载形式
  • 实例化对象时仅用到一个构造函数
  • 当用户没有定义构造函数时,编译器自动生成一个构造函数
  • 和其它函数一样,也支持默认值
1
2
3
4
5
6
7
8
9
10
11
12
class Student
{
public:
// 和类名同名
// 没有返回值
Student(string _name)
{
m_sreName = _name;
}
private;
string m_strName;
}

注意: 使用默认值要慎重,如果导致创建实例时计算机无法确定使用哪一个构造函数,程序会出错!

6.2 代码演示

Teacher.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <string>
using namespace std;
class Teacher
{
public:
// 构造函数
Teacher();
Teacher(string name, int age = 20);

// m_strName
void setName(string _name);
string getName();

// m_iAge
void setAge(int _age);
int getAge();

void teach();
private:
string m_strName;
int m_iAge;
};

Teacher.m

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
#include <iostream>
#include <stdlib.h>
#include "Teacher.h"
using namespace std;

Teacher::Teacher()
{
m_strName = "Jim";
m_iAge = 5;
cout << "Teacher()" << endl;
}

Teacher::Teacher(string name, int age)
{
m_strName = name;
m_iAge = age;
cout << "Teacher(string name, int age)" << endl;
}

// m_strName
void Teacher::setName(string _name)
{
m_strName = _name;
}
string Teacher::getName()
{
return m_strName;
}

// m_iAge
void Teacher::setAge(int _age)
{
m_iAge = _age;
}

int Teacher::getAge()
{
return m_iAge;
}

void Teacher::teach()
{
cout << "having class..." << endl;
}

int main(void)
{

Teacher t1;
Teacher t2("Merry", 15);
Teacher t3("Sean");

cout << t1.getName() << " " << t1.getAge() << endl;
cout << t2.getName() << " " << t2.getAge() << endl;

return 0;
}

6.3 练习(略)

6.4 构造函数初始化列表

说明: 可以在构造函数后面添加初始化列表 完成一些属性成员初始化的工作,特点如下

  • 初始化列表先于构造函数执行
  • 初始化列表只能用于构造函数
  • 初始化列表可以同事初始化多个数据成员

价值:

  • 相比在构造函数中初始化属性成员,初始化列表语义更明确
  • const 修饰的属性成员不能在构造函数中初始化,但初始化列表可以
1
2
3
4
5
6
7
8
class Circle
{
public:
// 初始化常量属性成员 m_dPi
Circle():m_dPi(3.14) {}
private:
const double m_dPi;
}

6.5 初始化列表代码演示

Teacher.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <string>
using namespace std;
class Teacher
{
public:
Teacher(string name = "Jim", int age = 1, int m = 100);
void setName(string _name);
string getName();
void setAge(int _age);
int getAge();
int getMax();
private:
string m_strName;
int m_iAge;
const int m_iMax;
};

Teacher.cpp

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>
#include <stdlib.h>
#include "Teacher.h"
using namespace std;

// 构造函数(使用了初始化列表)
Teacher::Teacher(string name, int age, int m):m_strName(name), m_iAge(age), m_iMax(m)
{
cout << "Teacher(string name, int age)" << endl;
}

void Teacher::setName(string _name)
{
m_strName = _name;
}
string Teacher::getName()
{
return m_strName;
}

void Teacher::setAge(int _age)
{
m_iAge = _age;
}

int Teacher::getAge()
{
return m_iAge;
}

int Teacher::getMax()
{
return m_iMax;
}

int main(void)
{

Teacher t1("Xiaoli", 26, 100);
cout << t1.getName() << " " << t1.getAge() << " " << t1.getMax() << endl; // Xiaoli 26 100

return 0;
}

6.6 练习(略)

6.7 c++ 拷贝构造函数

说明: 拷贝构造函数会在对象被拷贝时调用,定义格式 类名(const 类名&变量名)

  • 如果没有自定义的拷贝构造函数,则系统会自动生成一个拷贝构造函数。
  • 拷贝构造函数的参数是确定的,因此不能重载。
  • 当采用直接初始化或复制对象时,系统会自动调用拷贝构造函数。
  • 如果对象有 const 修饰的成员,则需要在拷贝构造函数后面通过初始化列表对其进行重新初始化。

拷贝构造函数被调用的场景

  • 直接初始化:Student stu3(stu1);
  • 将对象赋值给其它变量:Student stu2 = stu1;
  • 作为函数参数传递(传递的是副本): fun(stu1);
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
#include <iostream>
using namespace std;

class Student
{
public:
Student()
{
cout << "Student" << endl;
}
// 拷贝构造函数
Student(const Student &stu)
{
cout << "Student(const Student &stu)" << endl;
}
private:
string m_strName;
};


int main(void)
{

// 调用构造函数 Student()
Student stu1;

// 调用拷贝构造函数
Student stu2 = stu1;
Student stu3(stu1);
}

6.8 c++ 构造函数代码演示(略)

6.9 练习(略)

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

class Student
{
public:
Student()
{
cout << "Student()" << endl;
m_pName = new char[20]; // 动态分配内存
}

//析构函数
~Student()
{
cout << "~Student()" << endl;
delete []m_pName;
}

private:
char *m_pName;
};


int main(void)
{

Student stu1;
}

6.11 c++ 析构函数代码演示

6.12 练习(略)

7 课程总结

7.1 总结

7.2 综合练习

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
#include <iostream>
#include <string>
using namespace std;
/**
* 定义类:Student
* 数据成员:m_strName
* 无参构造函数:Student()
* 有参构造函数:Student(string _name)
* 拷贝构造函数:Student(const Student& stu)
* 析构函数:~Student()
* 数据成员函数:setName(string _name)、getName()
*/

class Student
{
public:
Student():m_strName("neo")
{
cout << "Student()" << endl;
}
Student(string _name):m_strName(_name)
{
cout << "Student(string _name)" << endl;
}
Student(const Student& stu)
{
cout << "Student(const Student& stu)" << endl;
}
~Student()
{
cout << "~Student()" << endl;
}
void setName(string _name)
{

m_strName = _name;
}
string getName()
{

return m_strName;
}
private:
string m_strName;
}

int main(void)
{

// 通过new方式实例化对象*stu
Student *stu = new Student()
// 更改对象的数据成员为“慕课网”
stu->setName("慕课网");
// 打印对象的数据成员
cout << stu->getName() << endl;
return 0;
}