作者简介:努力的clz ,一个努力编程的菜鸟
文章专栏:C++ Primer 学习笔记
专栏简介: 本专栏是博主学习 C++ Primer 的学习笔记,因为这本书内容超级多,所以博主将其中的 重点 概括提炼出来,于是有了这个专栏的诞生。
C++ Primer 学习笔记 第7章 类
笔记导航
- C++ Primer 第7章 类 - 上
- C++ Primer 第7章 类 - 中
⇦当前位置
- C++ Primer 第7章 类 - 下
(加班中)
- C++ Primer 总目录 传送门
如果本篇文章对大家起到帮助的话,跪求各位帅哥美女们,
求赞 、求收藏 、求关注!
本节将会创建两个新类:
Screen、Window_mgr 类
来展示类的其它特性。
1. 定义一个类型成员
Screen 类
表示显示器中的一个窗口。
#include
using std::string;
class Screen
{
public:
typedef string::size_type pos;
private:
pos cursor = 0; // 光标的位置
pos height = 0, width = 0; // 屏幕的高和宽
string contents; // 保持 Screen 的内容
};
关于 pos 的声明有两点需要注意:
- 可以等价第使用类型别名 代替上方代码写法;
- 用来定义类型的成员必须先定义后使用。
class Screen
{
public:
typedef string::size_type pos;
// 上方代码等价于
using pos = string::size_type; // 使用类型别名等价地声明一个类型名字
private:
// 略
};
2. Screen 类的成员函数
#include
using std::string;
class Screen
{
public:
typedef string::size_type pos; // 定义一个类型成员
Screen() = default; // 因为 Screen 有另一个构造函数,所以必须显式声明默认构造函数
// 初始值列表未对 cursor 赋初值,所以会隐式地使用类内初始值
Screen(pos ht, pos wd, char c) : height(ht), width(wd), contents(ht * wd, c) {}
char get() const // 读取光标处的字符
{
return contents[cursor]; // 隐式内联
}
inline char get(pos ht, pos wd) const; // 显式内联
Screen &move(pos r, pos c); // 能在之后被设为内联
private:
pos cursor = 0; // 光标的位置
pos height = 0, width = 0; // 屏幕的高和宽
string contents; // 保持 Screen 的内容
};
3. 令成员作为内联函数
定义在类内部的成员函数是
自动 inline
的,例如Screen 类
的构造函数和 get 函数。
- 在类的 内部,可以把
inline 关键字
作为声明的一部分显式地声明成员函数;- 在类的 外部,可以用
inline 关键字
修饰函数的定义.
/**
* @brief 移动光标到指定的列
*
* @param r
* @param c
* @return Screen&
*/
inline Screen &Screen::move(pos r, pos c) // 可以在函数的定义处指定inline
{
pos row = r * width; // 计算行的位置
cursor = row + c; // 在行内将光标移动到指定的列
return *this; // 以左值的形式返回对象
}
/**
* @brief 返回光标所指字符
*
* @param r
* @param c
* @return char
*/
char Screen::get(pos r, pos c) const // 在类的内部声明成inline
{
pos row = r * width; // 计算行的位置
return contents[row + c]; // 返回给定列的字符
}
4. 重载成员函数
成员函数也可以和非成员函数一样进行 函数重载 ,只要函数之间 参数数量、类型 有区别即可。
5. 可变数据成员
当在变量声明前增加一个
mutable 关键字
,就成为了 可变数据成员 ,其永远不会是const
。
即使在const成员函数
内,也可以对 可变数据成员 进行修改。
/**
* @brief 保持一个计数值,用于记录成员函数被调用的次数
*
*/
void Screen::some_member() const
{
++access_ctr; // mutable 可变数据成员
}
6. 类数据成员的初始值
下方代码中,使用一个单独的元素值对
vector 成员
执行了列表初始化,这个Screen
的值被传递给vector
的构造函数,从而创建一个单元素的vector对象
。
// todo 窗口管理类
class Window_mgr
{
private:
// 这个 Window_mgr 追踪的 Screen
// 默认情况下,一个 Window_mgr 包含一个标准尺寸
vector<Screen> screens{Screen(24, 80, ' ')};
};
添加两个重名的
set 函数
,set 成员返回值是调用 set 的对象的引用,也就是函数返回的是对象本身而非对象的副本。
/**
* @brief 设置光标所在位置的字符
*
* @param c
* @return Screen&
*/
inline Screen &Screen::set(char c)
{
contents[cursor] = c; // 设置当前光标所在位置的新值
return *this; // 将this对象作为左值返回
}
/**
* @brief 设置给定位置的字符
*
* @param r
* @param col
* @param ch
* @return Screen&
*/
inline Screen &Screen::set(pos r, pos col, char ch)
{
contents[r * width + col] = ch; // 设置给定位置的新值
return *this; // 将this对象作为左值返回
}
1. 从const 成员函数返回 * this
接下来会添加一个
display 操作
,负责打印 Screen 的内容,令其为一个const 成员
。
一个 const 成员函数如果以引用的形式返回*this
,那么它的返回类型将是常量引用。
2. 基于 const 的重载
/**
* @brief 负责显示 Screen 的内容
*
* @param os
*/
void do_display(ostream &os) const
{
os << contents;
}
// 根据对象是否是 const 重载了 display 函数
Screen &display(ostream &os)
{
do_display(os);
return *this;
}
const Screen &display(ostream &os) const
{
do_display(os);
return *this;
}
每个类定义了唯一的类型。即使两个类的成员列表完全一致,也是不同的类型。
struct First
{
int memi;
int getMem();
};
struct Second
{
int memi;
int getMem();
};
First obj1;
Second obj2= boj1; // 错误: obj1 和 obj2 的类型不同
类的声明
类的声明和定义可以分开进行,先声明后定义。
这种声明称为前向声明
,类型 Screen 在声明之后、定义之前处于不完全类型
。
不完全类型
只能知道 Screen 是一个类类型,但不清楚类内包含哪些成员(因为还没定义)。
class Screen; // Screen 类的声明
除了将非成员函数定义成 友元 ,还有其它应用场景:
- 把其它类定义成友元;
- 把其它类的成员函数定义成友元;
1. 类之间的友元关系
在 Screen 类中设置 友元类 ,友元类的成员函数可以访问包括非公有成员在内的所有成员。
注意!友元关系不具备传递性 。
Screen 设置 友元类Window_mgr,如果 Window_mgr 设置 友元类 Student,但 Screen 与 Student 并不是友元关系。
// todo 窗口管理类
class Window_mgr
{
public:
// 窗口中每个屏幕的编号
using ScreenIndex = vector<Screen>::size_type;
// 按照编号将指定的Screen重置为空白
void clear(ScreenIndex);
private:
// 这个 Window_mgr 追踪的 Screen
// 默认情况下,一个 Window_mgr 包含一个标准尺寸
vector<Screen> screens{Screen(24, 80, ' ')};
};
/**
* @brief
*
* @param i
*/
void Window_mgr::clear(ScreenIndex i)
{
// s是一个Screen的引用,指向我们想清空的那个屏幕
Screen &s = screens[i];
// 将那个选的ing的Screen重置为空白
s.contents = string(s.height * s.width, ' ');
}
2. 令成员函数作为友元
设置友元类,Window_mgr 所有的成员函数都可以访问 Screen的成员变量,但可以只单独设置一个函数为友元。
令某个成员函数作为友元,需要按照一定的先后顺序规则:
- 先定义 Window_mgr 类,声明 clear 函数,但不能定义;
- 定义 Screen 类,包括对 clear 的友元声明;
- 定义clear,此时才可以 Screen 的成员。
class Screen{
// Window_mgr::clear 必须在 Screen 类之前被声明
friend void Window_mgr::clear(ScreenIndex);
// Screen类的剩余部分
}
3. 函数重载和友元
如果一个类想把一组重载函数声明成它的友元,需要对其一个个分别声明。
4. 友元声明和作用域
类和非成员函数的声明不是必须在它们的友元声明之前。
分析下方代码,要理解友元声明的作用是影响访问权限,并非普通意义上的声明。
struct X
{
friend void f() { /* 友元函数可以定义在类的内部 */}
X() {
f(); // 错误: f 还没被声明
}
void g();
void h();
};
void X::g() {
return f(); // 错误: f 还没有被声明
}
void f(); // 声明那个定义在 X 中的函数
void X::h() {
return f(); // 正确: 现在 f 的声明在作用域内了
}
C++ Primer 学习笔记 第7章 类
笔记导航
- C++ Primer 第7章 类 - 上
- C++ Primer 第7章 类 - 中
⇦当前位置
- C++ Primer 第7章 类 - 下
(加班中)
- C++ Primer 总目录 传送门
如果本篇文章对大家起到帮助的话,跪求各位帅哥美女们,
求赞 、求收藏 、求关注!