小古银的官方网站(完整教程):http://www.xiaoguyin.com/
C++入门教程视频:https://www.bilibili.com/video/av20868986/
目录
前言
在之前的教程中我们使用过无数次的类,尤其以字符串居多。在我们写程序的时候,代码会越写越多,这时候为了方便阅读和重复利用,会从一堆代码中找出可以单独成功能的代码,并将这些代码封装成函数;当代码量真的很多的时候,代码还是不容易理解,这时候还是为了方便理解和重复利用,将一些函数和这些函数需要的共同参数或者全局变量都打包在一起,封装成类,这样就会把整个程序代码的复杂度大幅度降低,这就是面向对象出现的理由。类是自定义的数据类型,类里面的函数叫做成员函数;类里面的变量叫做成员变量。用类创建的变量叫做对象。
类是为了降低代码的复杂度,使代码更容易理解和重用而出现的。如果过度封装则会导致代码复杂度大幅度上升并且后面代码越写越乱而且思维不清晰,最后导致写代码越来越慢而且容易出错和优化困难。如何合理封装类,就要靠你平时多学习并且积累经验,这是一个经验活。
面向对象是一种编程思想,通过良好的设计让程序代码的逻辑更加清晰,并且可以让其他程序员快速看懂。面向对象的三要素是:封装、继承和多态。
封装:将功能代码的细节隐藏起来,只提供易用易理解的成员函数等供其他代码调用,或者让使用这个类的程序员不必考虑其内部实现也能快速使用。经验越足封装效果越好,需要要多学和多练。
继承和多态:将简单部分封装成一个类后,让其他程序员可以基于这个类,扩展出具有更强大功能的类。
之前写了不少的例子,你应该也发现,成员函数只针对对象有效,某个对象的成员函数是不能操作其他对象的。例如两个字符串变量a
和b
,a.size()
输出的是a
保存的字符串的字符数量,b.size()
输出的是b
保存的字符串的字符数量,这两个size
是不能互相影响的。这也是面向对象思想的一部分。同样的还有std::pair
,用std::pair
创建的对象都有它们自己各自的成员变量first
和second
。
接下来讲的就是如何去设计一个类。如果一个类设计得很好,那么使用类的时候就会非常方便;相反就会非常麻烦。而要设计好一个类并不简单,需要努力积累编程经验。
类的简单声明
平面坐标系中有点(x, y)
,也就是两个值x
和y
组成一个点。之前使用了数据类型std::pair
,而现在,我们使用类来声明定义这样一个数据类型point_t
。
基础示例
// 用于表示二维坐标的数据类型
class point_t
{
};
int main(void)
{
point_t point; // 使用point_t类型声明一个对象
return 0;
}
基础讲解
以下代码声明了类point_t
,需要使用关键字class
,现在的它没有成员函数和成员变量。注意类声明后面是有分号;
的:
class point_t
{
};
而调用就是以下代码,用类名声明对象,换句话说就是,用数据类型声明变量:
point_t point;
声明成员变量
基础示例
接下来给数据类型添加两个成员变量x
和y
。
class point_t
{
public:
int x; // 成员变量x
int y; // 成员变量y
};
int main(void)
{
point_t point1;
point1.x = 100; // 给点1的横坐标赋值为100
point1.y = 150; // 给点1的纵坐标赋值为150
point_t point2;
point2.x = -250; // 给点1的横坐标赋值为-250
point2.y = -450; // 给点1的纵坐标赋值为-450
return 0;
}
基础讲解
在类中添加一个成员变量x
和成员变量y
,那么就可以通过对象来操作它们自身的x
和y
。
需要注意的是:在类中声明的成员变量,仅仅只是声明,这个时候是没有内存的。只有当类声明了对象之后,对象才会被分配到内存,而对象的内存就是由所有成员变量共同组成的。例如上面代码中的point_t point1;
,这个时候point1
才有内存,而它的内存就是由成员变量x
和y
内存的总和,int
类型占用4字节内存,因此point_t
声明的对象point1
占用8字节内存,而point2
也是占用8字节内存。
另一个需要注意的是:point1
中的x
和y
是属于point1
的,point2
中的x
和y
是属于point2
的,这些x
和y
并不是同一个变量。类point_t
里写的成员变量x
和y
,只是说明它声明的对象有各自的x
和y
。
细心的你应该发现有一个关键字public
,后面还带了一个冒号:
。在类里面,从public:
的位置开始的下面所有东西都是公开的。下一篇教程将详细讲解public
,现在只需要知道它是必须的。
声明定义成员函数
现在我们声明定义一个成员函数reset()
来设置x
和y
的值。
基础示例
#include <iostream> // std::cout std::endl
class point_t
{
public:
void reset(int a, int b);
public:
int x;
int y;
};
int main(void)
{
point_t point;
point.reset(111, 222);
std::cout << "横坐标的值是:" << point.x << std::endl;
std::cout << "纵坐标的值是:" << point.y << std::endl;
return 0;
}
void point_t::reset(int a, int b)
{
x = a;
y = b;
}
基础讲解
成员函数的声明是写在class
的{}
之间。成员函数的调用如下:
point.reset(111, 222);
成员函数的定义可以放在类声明里面,但是一般都不会这样做,声明和定义一般是分开的。成员函数的定义格式跟普通函数类似,不过成员函数需要在函数名称前面加上类名,以说明它是这个类的成员函数,如上面代码的point_t::
。
类的声明和定义就说到这里,其实也并不难。不过你看这个代码的时候,肯定发现了一个问题,我一直强调要初始化,但是这些成员变量没有初始化。如果你发现了,那就恭喜你,写代码是需要很细心的。这里的确没有初始化,因为初始化不能写在类声明里面(除了整数类型的成员变量,但是一般也不会写在类声明里面)。先不要着急,后面会慢慢说明。
巩固练习
在之前教程中简单讲解过std::vector
的使用。std::vector
在代码中是非常常用的,我相信你已经非常熟练std::vector
。现在,你可以尝试一下,设计一个简化版的std::vector<int>
。当你完成了这个简化版的std::vector<int>
,那么你将会得到以下几个方面的能力:
- 掌握如何设计一个完整的类。
- 对指针的使用更加熟练。
- 了解
std::vector
内部的实现原理,可以优化出更加好的代码。
接下来就是按照下面指示,完成练习:完成类的声明。
设计出一个类,实现一个保存int
类型的简化版的std::vector<int>
,这个类的类名叫做:simple_vector
。
这个类含有以下公共的成员函数,以下列出函数名称和函数功能:
empty
:
- 判断是否为空。
- 返回值:没有元素则返回true,否则返回false。
size
:
- 获取元素数量。
- 返回值:元素数量。
clear
:
- 清空所有元素。
at
:
- 查看指定位置的元素(不改变这个位置的值)。
- 参数:
pos
索引位置,从0开始。 - 返回值:指定位置的值。
push_back
:
- 在末尾添加一个元素。
- 参数:value 需要添加的值。
pop_back
:
- 删除最后一个元素。
insert
:
- 在指定的位置插入一个元素。
- 参数:
pos
指定要插入的位置。 - 参数:
value
需要添加的值。
erase
:
- 删除指定位置的一个元素。
- 参数: pos 指定要插入的位置。
成员函数就有了,现在需要保存数据,肯定需要使用数组,这些成员函数基本都是对这个数组进行操作,因此,这个数组肯定不能在这些成员函数内声明,只能声明为成员变量。这样就把这个数组的作用域限制在这个类里面,而且对这些成员函数而言,就等价于全局变量,不需要通过参数传递。那么现在就把这个数组命名为m_array
,但是你需要考虑:数组应该使用静态数组还是动态数组?
我们还需要一个变量来保存这个数组的元素数量,那么我们就把它叫做m_size
。
提示:数组的大小和索引一般用数据类型std::size_t
。
那么现在你就可以开始动手写simple_vector
的类声明了。