本节课要点:
- 由 dlist.h 经过基本优化得到 dlist2.h
- 构造函数
- 析构函数
- 静态成员属性
- 静态成员函数
- 面向对象设计全过程
基于对双向链表的实现。
新建value_type.h
#pragma once //头文件保护:确保该头文件在源文件里面只被包含一次
using value_type = int;
class dlist {
...
private:
struct node {
value_type data;
node *next = nullptr, *prior = nullptr;
};
...
};
class dlist {
...
private:
using callback = void (reference);
...
};
class dlist {
public: //类型隔离
//::作用域选择运算符,来自全局
using value_type = ::value_type;
using pointer = value_type*;
using reference = value_type&;
...
};
1. 将原函数放入private段
2. 重写函数
class dlist {
...
private:
...
//获取值 私有成员名字加下划线
//const node * p 两者区别:const修饰node,变量指针指向常量对象
//const修饰nodeptr_t,p是一个常量指针
//返回引用,避免复制
reference _get(const nodeptr_t p) try { //const约束p,让p只读
if (empty())
throw std::out_of_range("it's an empty list");
return p->data;
} catch (std::out_of_range& e) {
std::cout << e.what() << std::endl;
exit(1);
}
void _push(const nodeptr_t pre, const nodeptr_t p) {
pre->next->prior = p;
pre->next = p;
++ length;
}
void _pop(const nodeptr_t pre) try {
if (empty())
throw std::out_of_range("it's an empty list");
auto p = pre->next;
pre->next = p->next;
p->next->prior = pre;
delete p;
--length;
} catch (std::out_of_range& e) {
std::cout << e.what() << std::endl;
exit(1);
}
void _init() noexcept {
length = 0;
head.next = &tail;
tail.prior = &head;
//head.prior = tail.next = nullptr;
}
void _destroy() noexcept {
for (auto p = head.next; p != &tail; p = head.next) {
head.next = p->next;
delete p;
}
//_init();
}
public:
...
void push_front(value_type v) {
_push(&head, new node{v, head.next, &head});
}
void push_back(value_type v) {
_push(tail.prior, new node{v, &tail, tail.prior});
}
void pop_front() {
_pop(&head);
}
void pop_back() {
_pop(tail.prior->prior); //别把tail删了!
}
value_type front() {
return _get(head.next);
}
value_type back() {
return _get(tail.prior);
}
...
};
- 容易忘记初始化链表导致内存泄露——使用构造函数解决问题。
- 类的构造函数是类的一个特殊的成员。实际上,构造函数不是函数,语法中的 T(参数列表) 称为函数修饰符。
- 由于构造函数不是函数,所以它没有名字,也没有地址(函数的名字也是函数的指针),因此我们不能显式地调用它。
- 默认构造函数——不带任何参数。
- 实例化对象的时候,类的构造函数会被强制调用。
- 如果我们没有定义构造函数,那么编译器会为我们合成一个——合成默认构造函数——但它啥都不做。
class dlist {
...
public:
//构造函数
dlist() noexcept { //: head(0, &tail),初始化列表
std::cout << "in dlist()" << std::endl;
_init();
std::cout << ++count << std::endl;
}
//overloading
dlist(const std::initializer_list& l) noexcept : dlist() { //构造函数委托,先执行dlist(),再执行自己
for (auto & v : l) //基于范围的for语句
push_back(v);
}
...
};
构造函数委托:
//: dlist() —— 构造函数委托,先执行dlist(),再执行自己
dlist(const std::initializer_list& l) noexcept : dlist() {
for (auto & v : l) //基于范围的for语句
push_back(v);
}
- 类对象在构造或者运行时,可能会申请相应的资源。从原则上讲,当对象失效后,它占据的资源应该被释放,否则将可能导致资源不足或者其它类型的错诶发生。因此,在对象失效后立即释放资源是一项重要的操作——使用析构函数解决问题。
- 类的析构函数也是类的一个特殊的成员,没有函数名。
- 不能有参数——不能被重载。
- 由于构造函数和析构函数都不是函数,所以它们不能有返回值,但析构函数可以被显式地调用。
- 析构函数会在一个对象的生命期结束时被强制调用。
- 如果我们没有定义析构函数,那么编译器会为我们合成一个。
class dlist {
...
public:
...
~dlist() {
std::cout << "in ~dlist()" << std::endl;
_destroy();
std::cout << --count << std::endl;
}
...
};
试图记录构造函数的调用次数。
- 实例成员,依赖于对象而存在
- 静态成员,所有对象共享一份;静态成员属于类而不属于类对象
1. 独立地/单独地为静态成员属性分配存储空间 —— 必须在类外为其定义存储和初始化
2. 因为头文件可能被包含多次,所以我们选择.cpp进行声明,防止静态成员被多次初始化
3. .cpp是一个编译单元,而.h不是
新建dlist.cpp
#include "dlist2.h"
size_t dlist::count = 0;
每一个非静态的成员函数都使用一个 this 指针,而静态成员函数没有 this 指针,因此它“不知道”自己是有哪个对象发起的。
get_count(const dlist * const this) // 隐式地给出
因此,一般使用静态成员函数来访问静态数据成员。若要使静态成员函数中能够访问到实例成员,则需要给它提供一个对象参数。
class dlist {
...
public:
...
static size_t get_count() { //静态成员函数
return count;
}
...
};
找出的对象也许会比较多,需要剔除一些不必要的,仅保留那些与应用直接相关的对象。除此之外,另一项重要的工作就是必须标识出这些对象之间的关系,即谁为谁提供服务。
附上 dlist2.h
//dlist2.h
#pragma once
#include
#include
#include
#include "value_type.h"
class dlist {
public:
using value_type = ::value_type;
using pointer = value_type*;
using reference = value_type&;
private:
using callback = void (reference);
struct node {
value_type data;
node *next = nullptr, *prior = nullptr;
};
using nodeptr_t = node *;
node head, tail;
size_t length = 0;
static size_t count;
reference _get(const nodeptr_t p) try {
if (empty())
throw std::out_of_range("it's an empty list");
return p->data;
} catch (std::out_of_range& e) {
std::cout << e.what() << std::endl;
exit(1);
}
void _push(const nodeptr_t pre, const nodeptr_t p) {
pre->next->prior = p;
pre->next = p;
++ length;
}
void _pop(const nodeptr_t pre) try {
if (empty())
throw std::out_of_range("it's an empty list");
auto p = pre->next;
pre->next = p->next;
p->next->prior = pre;
delete p;
--length;
} catch (std::out_of_range& e) {
std::cout << e.what() << std::endl;
exit(1);
}
void _init() noexcept {
length = 0;
head.next = &tail;
tail.prior = &head;
}
void _destroy() noexcept {
for (auto p = head.next; p != &tail; p = head.next) {
head.next = p->next;
delete p;
}
}
public:
dlist() noexcept {
std::cout << "in dlist()" << std::endl;
_init();
std::cout << ++count << std::endl;
}
dlist(const std::initializer_list& l) noexcept : dlist() {
for (auto & v : l)
push_back(v);
}
~dlist() {
std::cout << "in ~dlist()" << std::endl;
_destroy();
std::cout << --count << std::endl;
}
void push_front(value_type v) {
_push(&head, new node{v, head.next, &head});
}
void push_back(value_type v) {
_push(tail.prior, new node{v, &tail, tail.prior});
}
void pop_front() {
_pop(&head);
}
void pop_back() {
_pop(tail.prior->prior);
}
value_type front() {
return _get(head.next);
}
value_type back() {
return _get(tail.prior);
}
bool empty() {
return length == 0;
}
size_t size() {
return this->length;
}
void traverse(callback f) {
for (auto p = head.next; p != &tail; p = p->next)
f(p->data);
}
static size_t get_count() {
return count;
}
};
别忘了链上 dlist.cpp!
# Makefile
sources = main.cpp dlist.cpp# 源文件
target = dlist # 生成的可执行代码的名称
CXX = g++
# W:warning -Wall开启全部警告
# -fsanitize=address启用内存消毒器
CXXFLAGS = -Wall -g -std=c++23 -fsanitize=address
LIB = # 链接额外的库(目前没有)
all:
# -o 指定名称 额外的库必须写在最后
$(CXX) $(sources) $(CXXFLAGS) -o $(target) $(LIB)
clean:
rm $(target)