类
类(class)是结构体的拓展,不仅能够拥有成员元素,还拥有成员函数.
在面向对象编程(OOP)中,对象就是类的实例,也就是变量.
C++ 中 struct 关键字定义的也是类,上文中的 结构体 的定义来自 C.因为某些历史原因,C++ 保留并拓展了 struct.
定义类
类使用关键字 class 或者 struct 定义,下文以 class 举例.
---|---
与使用 `struct` 大同小异.该例定义了一个名为 `Object` 的类.该类拥有两个成员元素,分别为 `weight,value`;并在 `}` 后使用该类型定义了一个数组 `e`.
定义类的指针形同 [`struct`](../struct/).
### 访问说明符
不同于 [`struct`](../struct/) 中的举例,本例中出现了 `public`,这属于访问说明符.
* `public`:该访问说明符之后的各个成员都可以被公开访问,简单来说就是无论 **类内** 还是 **类外** 都可以访问.
* `protected`:该访问说明符之后的各个成员可以被 **类内** 、派生类或者友元的成员访问,但类外 **不能访问** .
* `private`:该访问说明符之后的各个成员 **只能** 被 **类内** 成员或者友元的成员访问,**不能** 被从类外或者派生类中访问.
对于 `struct`,它的所有成员都是默认 `public`.对于 `class`,它的所有成员都是默认 `private`.
关于友元以及派生类的基本概念
友元(`friend`):使用 `friend` 关键字修饰某个函数或者类.可以使得在 **被修饰者** 在不成为成员函数或者成员类的情况下,访问该类的私有(`private`)或者受保护(`protected`)成员.简单来说就是只要带有这个类的 `friend` 标记,就可以访问私有或受保护的成员元素.
派生类(`derived class`):C++ 允许使用一个类作为 **基类** ,并通过基类 **派生** 出 **派生类** .其中派生类(根据特定规则)继承基类中的成员变量和成员函数.可以提高代码的复用率.
派生类似 "is" 的关系.如猫(派生类)"is" 哺乳动物(基类).
对于上面 `private` 和 `protected` 的区别,可以看做派生类可以访问基类的 `protected` 的元素(`public` 同),但不能访问 `private` 元素.
## 访问与修改成员元素的值
方法形同 [`struct`](../struct/)
* 对于变量,使用 `.` 符号.
* 对于指针,使用 `->` 符号.
## 成员函数
成员函数,顾名思义.就是类中所包含的函数.
常见成员函数举例
---|---
---|---
该类有一个打印 `Object` 成员元素的函数,以及更改成员元素 `weight` 的函数.
和函数类似,对于成员函数,也可以先声明,在定义,如第十四行(声明处)以及十七行后(定义处).
如果想要调用 `var` 的 `print` 成员函数,可以使用 `var.print()` 进行调用.
### 重载运算符
何为重载
C++ 允许编写者为名称相同的函数或者运算符指定不同的定义.这称为 **重载** (overload).
如果同名函数的参数种类、数量中的一者或多者两两不相同,则这些同名函数被看做是不同的.
需要注意的是:如果两个同名函数的区别仅仅是返回值的类型不同则无法进行重载,此时编译器会拒绝编译!
如果在调用时不会出现混淆(指调用某些同名函数时,无法根据所填参数种类和数量唯一地判断出被调用函数.常发生在具有默认参数的函数中),则编译器会根据调用时所填参数判断应调用函数.
而上述过程被称作重载解析.
重载运算符,可以部分程度上代替函数,简化代码.
下面给出重载运算符的例子.
---|---
该例定义了一个向量类,并重载了 * + - 运算符,并分别代表向量内积,向量加,向量减.
重载运算符的模板大致可分为下面几部分.
---|---
对于自定义的类,如果重载了某些运算符(一般来说只需要重载 `<` 这个比较运算符),便可以使用相应的 STL 容器或算法,如 [`sort`](../../basic/stl-sort/).
如要了解更多,可参见「参考资料」第四条.
可以被重载的运算符
---|---
在实例化变量时设定初始值
为完成这种操作,需要定义 默认构造函数(Default constructor).
---|---
该例定义了 `Object` 的默认构造函数,该函数能够在我们实例化 `Object` 类型变量时,将所有的成员元素初始化为 `0`.
若无显式的构造函数,则编译器认为该类有隐式的默认构造函数.换言之,若无定义任何构造函数,则编译器会自动生成一个默认构造函数,并会根据成员元素的类型进行初始化(与定义 内置类型 变量相同).
在这种情况下,成员元素都是未初始化的,访问未初始化的变量的结果是未定义的(也就是说并不知道会返回何值).
如果需要自定义初始化的值,可以再定义(或重载)构造函数.
关于定义(或重载)构造函数
一般来说,默认构造函数是不带参数的,这区别于构造函数.构造函数和默认构造函数的定义大同小异,只是参数数量上的不同.
构造函数可以被重载(当然首次被叫做定义).需要注意的是,如果已经定义了构造函数,那么编译器便不会再生成无参数的默认构造函数.这会可能会使试图以默认方法构造变量的行为编译失败(指不填入初始化参数).
使用 C++11 或以上时,可以使用 `{}` 进行变量的初始化.
关于 `{}`
使用 `{}` 进行初始化,会用到 std::initializer_list 这一个轻量代理对象进行初始化.
初始化步骤大概如下
1. 尝试寻找参数中有 `std::initializer_list` 的默认构造函数,如果有则调用(调用完后不再进行下面的查找,下同).
2. 尝试将 `{}` 中的元素填入其他构造参数,如果能将参数按照顺序填满(默认参数也算在内),则调用该默认构造函数.
3. 若无 `private` 成员元素,则尝试在 **类外** 按照元素定义顺序或者下标顺序依次赋值.
_上述过程只是完整过程的简化版本,详细内容参见 "参考资料九"_
---|---
关于隐式类型转换
有时候会写出如下的代码
---|---
看上去十分不符合逻辑,一个 `int` 类型不可能转化为 `node` 类型.但是编译器不会进行 `error` 提示.
原因是在进行赋值时,首先会将 `1` 作为参数调用 `node::node(int)`,然后调用默认的复制函数进行赋值.
但大多数情况下,编写者会希望编译器进行报错.这时便可以在构造函数前追加 `explicit` 关键字.这会告诉编译器必须显式进行调用.
---|---
也就是说 node a=1 将会报错,但 node a=node(1) 不会.因为后者显式调用了构造函数.当然大多数人不会写出后者的代码,但此例足以说明 explicit 的作用.
_不过在算法竞赛中,为了避免此类情况常用的是 "加强对代码的规范程度",从源头上避免_
销毁
这是不可避免的问题.每一个变量都将在作用范围结束走向销毁.
但对于已经指向了动态申请的内存的指针来说,该指针在销毁时不会自动释放所指向的内存,需要手动释放动态内存.
如果结构体的成员元素包含指针,同样会遇到这种问题.需要用到析构函数来手动释放动态内存.
析构 函数(Destructor)将会在该变量被销毁时被调用.重载的方法形同构造函数,但需要在前加 ~
_默认定义的析构函数通常对于算法竞赛已经足够使用,通常我们只有在成员元素包含指针时才会重载析构函数._
---|---
### 为类变量赋值
默认情况下,赋值时会按照对应成员元素赋值的规则进行.也可以使用 `类名称()` 或 `类名称{}` 作为临时变量来进行赋值.
前者只是调用了复制构造函数(copy constructor),而后者在调用复制构造函数前会调用默认构造函数.
另外默认情况下,进行的赋值都是对应元素间进行 **浅拷贝** ,如果成员元素中有指针,则在赋值完成后,两个变量的成员指针具有相同的地址.
---|---
如需解决指针问题或更多操作,需要重载相应的构造函数.
_更多 构造函数(constructor)内容,参见「参考资料」第六条._
参考资料
- cppreference class
- cppreference access
- cppreference default_constructor
- cppreference operator
- cplusplus Data structures
- cplusplus Special members
- C++11 FAQ
- cppreference Friendship and inheritance
- cppreference value initialization
本页面最近更新: 2026/1/7 08:56:54,更新历史 发现错误?想一起完善?在 GitHub 上编辑此页! 本页面贡献者:Lans1ot, Ir1d, Tiphereth-A, Enter-tainer, JasonkayZK, ksyx, Lewy Zeng, c-forrest, CCXXXI, Chrogeek, cjsoft, HeRaNO, kernel_panic, YiJiugg 本页面的全部内容在CC BY-SA 4.0 和 SATA 协议之条款下提供,附加条款亦可能应用