类的使用
大约 10 分钟
类的使用
类的使用
通用格式
class className
{
private:
// data member declarations
public:
// member function prototypes
}
使用demo
代码较多,此处不再赘述,详见配套源码
使用
创建对象的n种方式
方式 | 写法 | 区别补充 |
---|---|---|
默认 | A a; | 调用默认构造函数 【使用限制】仅当没定义构造函数 / 定义了无参数或全有默认值参数的构造函数时可用 |
—————— | ———————— | ———————— |
单变量简写 | A a = val; | 调用对应的单参数构造函数 ,本质是隐式类型转换【使用限制】仅当定义了单参数的构造函数,并且没有用 explicit 关闭该特性时可用 |
显式 | A a = A(...); | 显式调用构造函数 ,可能调用赋值运算符? 【缺点】该写法可能会创建临时变量 |
隐式 | A a(...); | 隐式调用构造函数 【注意点】当不传参数时使用该写法有歧义,应不写后面的括号 【注意点】否则可能会理解为返回值为该类的函数的原型 |
new方式 | A *a = new A(...); | 调用构造函数 【注意点】动态分配内存(注意该写法需要手动delete来间接调用析构函数) |
定位new | A *a = new(add)A(...); | 调用构造函数 【注意点】动态分配内存(注意该写法可能不能delete且要直接调用析构函数) |
—————— | —————— | ———————— |
类引用创建 x 单变量 | A a = a2; | (同上,把构造函数 具体化为(默认)复制构造函数 ) |
类引用创建 x显示 | A a = A(a2); | (同上,把构造函数 具体化为(默认)复制构造函数 ) |
类引用创建 x 隐式 | A a(a2); | (同上,把构造函数 具体化为(默认)复制构造函数 ) |
类引用创建 x new | A *a = new A(a2); | (同上,把构造函数 具体化为(默认)复制构造函数 ) |
类引用创建 x 定位 | A *a = new(add)A(a2); | (同上,把构造函数 具体化为(默认)复制构造函数 ) |
—————— | —————— | ———————— |
列表初始化 x 显示 | A a = {...}; | 【使用限制】C++11新增 |
列表初始化 x 隐式 | A a {...}; | 【使用限制】C++11新增 |
列表初始化 x new | A *a = new A{...}; | 【使用限制】C++11新增 |
列表初始化 x 定位 | A *a = new(add)A{...}; | 【使用限制】C++11新增 |
—————— | —————— | ———————— |
(Java) | A a = new A(...); | (Java隐藏起了指针,这里的a 实质是一个智能引用) |
(Python) | a = A(...) |
补充:当...
中的内容为A的引用时
补充:关于A a = A(...);
的写法底层
几种写法的底层区别:(下面这几种写法都分别显示/隐式调用了构造函数,但有所区别)
A a(...);
- 行为:调用一次构造函数
A a = A(...);
- 行为:根据编译器不同而行为不同
A a; a = A(...);
- 行为:第一条表达式调用一次构造函数;第二条表达式行为同
A a = A(...);
?
- 行为:第一条表达式调用一次构造函数;第二条表达式行为同
对
A a = A(...);
编译器有两种方式来执行该语法- 第一种:与
A a(...);
的行为完全相同- 行为:调用一次构造函数
- 第二种:允许等号右侧创建一个临时变量,然后将该临时对象赋值到左侧的变量并丢弃它
- 补充:这里所说的丢弃:编译器可能让立刻删除临时对象,但也可能会等一段时间
- 行为:调用一次构造函数,一次析构函数,一次复制构造函数==(未清楚移动语义下的情况)==
- 第一种:与
选择建议
- 视有构造时有无参而交替使用
A a();
和A a;
,其效率更高
- 视有构造时有无参而交替使用
一些零散特性
this指针
- 使用场景
- 举例:
const Stock & topval(const Stock & s) const
中使用了两个对象,比较并返回结果大的那个
- 举例:
- 使用(类似于Python中的self)
- 举例:
return *this
- 举例:
- 本质原理
- 调用成员函数时,会通过
this
指针隐式传入一个参数,该参数为this
指针,指向调用成员函数的实例对象本身
- 调用成员函数时,会通过
类作用域
描述
- 在类中定义的名称,作用域都为整个类。作用域为整个类的名称只在类中是已知的,在类外是不可知的
- 在类中外,需要根据上下文使用直接成员运算符
.
、间接成员运算符->
、作用域解析运算符::
,如- 客户代码中,想要调用公有成员函数,需要通过对象
- 实现代码中,想要定义成员函数时,需要使用作用域解析运算符
作用域为类的常量
有几种实现方案
- 直接加
const
(不可行)- 举例:
class A{ private: const int Months = 12; ...
,不行 - 失败原因:声明类只是描述了对象的形式,并没有创建对象
- 举例:
- 使用枚举(
enum
)- 举例:
class A{ private: enum {Months = 12}; ...
或者也可以使用作用域内枚举(C++11) - 缺点:记得枚举只能定义整型,如果是浮点就不行了
- 实现原理:创建符号常量 注意这里是匿名枚举的用法,没有创建数据成员(只定义了枚举量,没有定义枚举变量,甚至连枚举名都没声明) 即所有对象实例都不包含枚举对象
- 举例:
- 创建静态变量的方法(
static
)- 举例:
class A{ private: static const int Months = 12; ...
- 实现原理:该变量将与其他静态变量存储在一起,而不是存储在对象中 因此只有一个常量副本,被所有该类的对象共享 (在C++98中,只能使用这种技术声明整数或枚举的静态常量而不能存储double常量。C++11消除了这种限制)
- 举例:
explicit
详见自动类型转换一节
初始化列表语法
详见构造函数的扩展
【功能扩展】类
类本身的扩展
【功能扩展】类 x 命名空间
- 一般形式:
#ifndef VECTOR_H_
#define VECTOR_H_
#include <iostream>
namespace VECTOR
{
class Vector
{
//...
}
}
#endif
- 或
/* 在QT中的使用 */
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
// ...
}
【功能扩展】类 x new
- 写法
- 创建与销毁
A *a = new A(...)
delete a
- 删除
- 不同于动态变量或静态变量的对象,当程序块或程序结束时自动调用析构函数
- new创建时,需要显示使用delete以间接调用析构函数
- 定位new非堆创建时,不能使用delete而应直接调用析构函数
- 创建与销毁
【功能扩展】静态类 = 类 x 静态(没这个概念)
Q
- C++有没有静态类这个概念?
其他
- C++类静态变量和类静态成员函数这两个我知道,Java中有静态类这个概念我也知道,虽然不知道是不是我想的那个。就问问C++后面的版本有没有这个概念或这样写行不行?
使用场景
- 开发的时候所有成员变量和成员函数都是static类型。想到这个整个类其实不需要实例,或者说只需要一份(其实用单例模式也可以,我就是实验心态弄着来试试)
网络资料与实验
- 搜了一下static静态类,有人说C++没这个概念,但也有人这样用了。而我自己实测,会有一个警告
- 英报错:
warning C4091: ‘static ’ : ignored on left of ‘ClassName’ when no variable is declared
- 中报错:
warning: C4091: “static ”: 没有声明变量时忽略“ClassName”的左侧
- 但是可以正常运行,没问题
参考写法1(静态类,没有这个概念)
static class View { private: static Student student; public: static int much ; static void addStudent(); };
参考写法2(直接在.h中创建静态实例,但没单例模式安全)
static class View { private: static Student student; public: static int much ; static void addStudent(); }; static View * view = new View();
参考写法3(单例模式,较安全的写法,可以与写法2相结合)
- 略
类成员的扩展
【功能扩展】类成员 x 内联函数
一般而言在头文件中不能定义函数,但可以定义内联函数
但在类声明(类作用域)中,可以不加关键字
inline
而直接定义函数。编译器会视作其为内联函数
【功能扩展】类成员 x new
类中创建的动态成员
- 注意要点和常规解决方法
- 注意不仅仅是
构造函数
,复制构造函数
[和赋值运算符
] 都要手动new
对象- 而且应使用相同的方式new(指针初始化为空除外)
- 注意不仅仅是
析构函数
[和赋值运算符
] 也要手动delete
对象(防止内存泄露)- 而且应使用与new对应的方式delete
- 注意
默认复制构造函数
和默认赋值运算符
的默认行为是浅赋值
,这会带来的问题,通常解决方案是转换为深拷贝 - 赋值运算符技巧
- 可以先判断一下是不是自我复制,如果是,则什么都不做。以避免不必要的性能损耗
- 赋值运算符中一般会先把旧的动态对象
delete掉,再new
一个新的动态对象 但若极个别情况,能确定大小相同时。其实也可以不delete再重新new,只需注意深拷贝的问题即可 但一般动态性对象大小都是随机的,如动态处理字符串长度,则需要先delete再new
- 注意不仅仅是
- 伪私有方法的解决方法
- 简概
- 如果觉得常规解决方法太麻烦,可以将方法定义为
伪私有方法
- 与其面对无法预料的运行故障,不如得到一个易于跟踪的编译错误
- 如果觉得常规解决方法太麻烦,可以将方法定义为
- 方法
- 即在
private
里定义复制构造函数和赋值运算符
- 即在
- 作用
- 避免本来自动生成的默认方法定义
- 这些方法是私有的,不能被广泛使用。即
A a1(a2);
、a1 = a2;
会产生编译错误
- 简概
delete
禁用方法的解决方法(C++11新增)- 略
【功能扩展】静态变量 = 类成员 x 静态变量
- 特点:静态变量的特点
- 访问:静态变量可以被静态类成员函数访问
- 作用:实现 “作用域为类的常量” 方法之一
类对象的扩展
【功能扩展】对象 x 函数返回
返回类型的选择(4种)
返回指向const对象的引用
- 优点:效率高、不修改
返回指向非const对象的引用
- 优点:效率高、可修改(连续使用)
- 场景
- 重载运算符
=
(效率高,可避免调用复制构造函数来创建一个新对象)(当等式右边是cosnt引用时才会使用复制构造函数) 可以用于连续赋值,如a1 = a2 = a3;
) - 重载与cout一起使用的<<运算符(效率高,连续修改cout对象) 可以用于串联输出,如
cout << a1 << a2;
)
- 重载运算符
返回对象
场景
如果被返回的对象是局部变量,则无法按引用方式返回,一般重载
算术运算符
属于这一类如
a = a1 + a2;
(不能修改a1、a2
)
返回const对象
场景
返回非const对象的话,有个奇异的属性,如可以这样写:
a1 + a2 = a3;
,虽然并没有什么意义如果不想产生这种行为,可以返回const对象,该语句是非法的