构造函数不能是虚函数,而析构函数可以是虚函数。原因如下:
智能指针是一种封装了原始指针的类对象,可以自动管理指针所指向的资源的生命周期,避免内存泄漏和空悬指针等问题。C++ 标准库提供了三种智能指针:std::unique_ptr,std::shared_ptr 和 std::weak_ptr。另外,C++98 中还有一种智能指针 std::auto_ptr,但是在 C++11 中已经被废弃,不建议使用。
指针和引用都是 C++ 中表示内存地址的概念,但是它们有很多不同的特点和用法。
例如:
int a = 10; // 定义一个整型变量 a
int *p = &a; // 定义一个指针 p,它的值是 a 的地址
int &r = a; // 定义一个引用 r,它是 a 的别名
int *p; // 合法,p 是一个未初始化的指针
int &r; // 不合法,r 必须初始化
int *q = NULL; // 合法,q 是一个空指针
int &s = NULL; // 不合法,s 不能为 NULL
int a = 10;
int b = 20;
int *p = &a; // p 指向 a
int &r = a; // r 引用 a
p = &b; // 合法,p 改变为指向 b
r = b; // 不合法,r 不能改变为引用 b,这里相当于给 a 赋值为 b
int a = 10;
int *p = &a;
int &r = a;
*p = 20; // 合法,通过解引用修改 p 所指对象的值为 20
r = 30; // 合法,直接修改 r 所引用对象的值为 30
int a = 10;
int *p = &a;
int **q = &p; // 合法,q 是一个二级指针,指向 p
int &r = a;
int &&s = r; // 不合法,s 不能是 r 的引用
int a = 10;
double b = 3.14;
char c = 'A';
int *p1 = &a;
double *p2 = &b;
char *p3 = &c;
int &r1 = a;
double &r2 = b;
char &r3 = c;
cout << sizeof(p1) << endl; // 输出 8(在64位机器上),表示 p1 占8个字节
cout << sizeof(p2) << endl; // 输出 8(在64位机器上),表示 p2 占8个字节
cout << sizeof(p3) << endl; // 输出 8(在64位机器上),表示 p3 占8个字节
cout << sizeof(r1) << endl; // 输出 4,表示 r1 所引用的对象 a 占4个字节
cout << sizeof(r2) << endl; // 输出 8,表示 r2 所引用的对象 b 占8个字节
cout << sizeof(r3) << endl; // 输出 1,表示 r3 所引用的对象 c 占1个字节
int a[3] = {10, 20, 30};
int *p = a; // p 指向数组的第一个元素
int &r = a[0]; // r 引用数组的第一个元素
p++; // 合法,p 移动到数组的第二个元素
r++; // 合法,r 所引用的元素值增加 1,即 a[0] 变为 11
void swap1(int *a, int *b) { // 使用指针作为参数
if (a == NULL || b == NULL) return; // 需要检查指针是否为空
int temp = *a;
*a = *b;
*b = temp;
}
void swap2(int &a, int &b) { // 使用引用作为参数
// 不需要检查引用是否为空,因为引用不能为空
int temp = a;
a = b;
b = temp;
}
,它的定义如下:template <class T, class Allocator = allocator<T> >
class vector;
其中,T 是元素的类型,Allocator 是分配器的类型,默认为 std::allocator。
vector 的特性有:
vector 的用法有:
vector<int> v1; // 创建一个空的 vector
vector<int> v2(10); // 创建一个包含 10 个 int 类型元素的 vector,默认值为 0
vector<int> v3(10, 1); // 创建一个包含 10 个 int 类型元素的 vector,初始值为 1
vector<int> v4(v3); // 创建一个和 v3 相同的 vector
vector<int> v5(v3.begin(), v3.end()); // 创建一个包含 v3 中所有元素的 vector
vector<int> v6{1, 2, 3, 4, 5}; // 创建一个包含初始化列表中元素的 vector
push_back
和 pop_back
方法在尾部插入或删除元素。v1.push_back(10); // 在 v1 的尾部插入一个值为 10 的元素
v1.pop_back(); // 删除 v1 的尾部元素
insert
和 erase
方法在任意位置插入或删除一个或多个元素。v1.insert(v1.begin(), 20); // 在 v1 的头部插入一个值为 20 的元素
v1.insert(v1.begin() + 2, 3, 30); // 在 v1 的第三个位置插入三个值为 30 的元素
v1.erase(v1.begin()); // 删除 v1 的头部元素
v1.erase(v1.begin(), v1.begin() + 3); // 删除 v1 的前三个元素
resize
和 reserve
方法来改变 vector 的大小或容量。v1.resize(5); // 改变 v1 的大小为 5,如果原来小于 5,则在尾部添加默认值;如果原来大于 5,则删除多余的元素
v1.resize(10, -1); // 改变 v1 的大小为 10,如果原来小于 10,则在尾部添加值为 -1 的元素;如果原来大于 10,则删除多余的元素
v1.reserve(20); // 改变 v1 的容量为 20,如果原来小于 20,则分配更大的内存空间,但不改变大小;如果原来大于等于 20,则不做任何操作
[]
或 at
方法来访问或修改 vector 中的元素。cout << v1[0] << endl; // 输出 v1 的第一个元素,不检查越界
cout << v1.at(0) << endl; // 输出 v1 的第一个元素,如果越界则抛出异常
v1[0] = 100; // 修改 v1 的第一个元素为 100,不检查越界
v1.at(0) = 200; // 修改 v1 的第一个元素为 200,如果越界则抛出异常
front
和 back
方法来访问或修改 vector 的头部或尾部元素。cout << v1.front() << endl; // 输出 v1 的头部元素
cout << v1.back() << endl; // 输出 v1 的尾部元素
v1.front() = 300; // 修改 v1 的头部元素为 300
v1.back() = 400; // 修改 v1 的尾部元素为 400
begin
和 end
方法来获取 vector 的迭代器,用于遍历或操作 vector 中的元素。¹²vector<int>::iterator it; // 定义一个迭代器
for (it = v1.begin(); it != v1.end(); it++) { // 遍历 v1 中的所有元素
cout << *it << " "; // 输出当前元素的值
*it += 10; // 将当前元素的值增加 10
}
cout << endl;
empty
和 size
方法来判断 vector 是否为空或获取 vector 中的元素个数。¹²if (v1.empty()) { // 判断 v1 是否为空
cout << "v1 is empty" << endl;
} else {
cout << "v1 is not empty" << endl;
}
cout << "v1 size: " << v1.size() << endl; // 输出 v1 中的元素个数
clear
方法来清空 vector 中的所有元素。v1.clear(); // 清空 v1 中的所有元素
,它的定义如下:template <class T, class Compare = less<T>, class Allocator = allocator<T> >
class set;
其中,T 是元素的类型,Compare 是比较函数对象的类型,默认为 std::less,表示按照升序排序;Allocator 是分配器的类型,默认为 std::allocator。
set 的特性有:
set 的用法有:
set<int> s1; // 创建一个空的 set
set<int> s2{1, 2, 3, 4, 5}; // 创建一个包含初始化列表中元素的 set
set<int> s3(s2); // 创建一个和 s2 相同的 set
set<int> s4(s2.begin(), s2.end()); // 创建一个包含 s2 中所有元素的 set
set<int, greater<int>> s5; // 创建一个按照降序排序的 set
insert
方法在 set 中插入一个或多个元素。s1.insert(10); // 在 s1 中插入一个值为 10 的元素
s1.insert({20, 30, 40}); // 在 s1 中插入多个值为 20, 30, 40 的元素
s1.insert(s2.begin(), s2.end()); // 在 s1 中插入 s2 中的所有元素
erase
方法在 set 中删除一个或多个元素。s1.erase(10); // 在 s1 中删除值为 10 的元素
s1.erase(s1.begin()); // 在 s1 中删除第一个元素
s1.erase(s1.begin(), s1.end()); // 在 s1 中删除所有元素
find
方法在 set 中查找一个元素,返回一个指向该元素的迭代器,如果没有找到则返回 end()。auto it = s1.find(10); // 在 s1 中查找值为 10 的元素
if (it != s1.end()) {
cout << "Found " << *it << endl; // 输出 Found 10
} else {
cout << "Not found" << endl;
}
count
方法在 set 中统计一个元素出现的次数,返回一个整数值,由于 set 不允许重复元素,所以结果只能是 0 或 1。int n = s1.count(10); // 在 s1 中统计值为 10 的元素出现的次数
cout << n << endl; // 输出 0 或 1
lower_bound
和 upper_bound
方法在有序的 set 中查找第一个大于等于或大于目标值的元素的位置,返回一个迭代器。auto it1 = s1.lower_bound(15); // 在 s1 中查找第一个大于等于 15 的元素的位置
auto it2 = s1.upper_bound(15); // 在 s1 中查找第一个大于 15 的元素的位置
if (it1 != s1.end()) {
cout << *it1 << endl; // 输出找到的元素的值
} else {
cout << "Not found" << endl;
}
if (it2 != s1.end()) {
cout << *it2 << endl; // 输出找到的元素的值
} else {
cout << "Not found" << endl;
}
begin
和 end
方法来获取 set 的迭代器,用于遍历或操作 set 中的元素。set<int>::iterator it; // 定义一个迭代器
for (it = s1.begin(); it != s1.end(); it++) { // 遍历 s1 中的所有元素
cout << *it << " "; // 输出当前元素的值
}
cout << endl;
empty
和 size
方法来判断 set 是否为空或获取 set 中的元素个数。if (s1.empty()) { // 判断 s1 是否为空
cout << "s1 is empty" << endl;
} else {
cout << "s1 is not empty" << endl;
}
cout << "s1 size: " << s1.size() << endl; // 输出 s1 中的元素个数
clear
方法来清空 set 中的所有元素。s1.clear(); // 清空 s1 中的所有元素
,它的定义如下:template <class Key, class T, class Compare = less<Key>, class Allocator = allocator<pair<const Key, T> > >
class map;
其中,Key 是键的类型,T 是值的类型,Compare 是比较函数对象的类型,默认为 std::less,表示按照升序排序;Allocator 是分配器的类型,默认为 std::allocator
map 的特性有:
map 的用法有:
map<int, string> m1; // 创建一个空的 map
map<int, string> m2{{1, "one"}, {2, "two"}, {3, "three"}}; // 创建一个包含初始化列表中元素的 map
map<int, string> m3(m2); // 创建一个和 m2 相同的 map
map<int, string> m4(m2.begin(), m2.end()); // 创建一个包含 m2 中所有元素的 map
map<int, string, greater<int>> m5; // 创建一个按照降序排序的 map
insert
方法在 map 中插入一个或多个键值对。m1.insert({10, "ten"}); // 在 m1 中插入一个键值对 {10, "ten"}
m1.insert({{20, "twenty"}, {30, "thirty"}}); // 在 m1 中插入多个键值对 {{20, "twenty"}, {30, "thirty"}}
m1.insert(m2.begin(), m2.end()); // 在 m1 中插入 m2 中的所有键值对
erase
方法在 map 中删除一个或多个键值对。m1.erase(10); // 在 m1 中删除键为 10 的键值对
m1.erase(m1.begin()); // 在 m1 中删除第一个键值对
m1.erase(m1.begin(), m1.end()); // 在 m1 中删除所有键值对
find
方法在 map 中查找一个键对应的值,返回一个指向该键值对的迭代器,如果没有找到则返回 end()。auto it = m1.find(10); // 在 m1 中查找键为 10 的键值对
if (it != m1.end()) {
cout << "Found " << it->first << ": " << it->second << endl; // 输出 Found 10: ten
} else {
cout << "Not found" << endl;
}
count
方法在 map 中统计一个键出现的次数,返回一个整数值,由于 map 不允许重复键,所以结果只能是 0 或 1。int n = m1.count(10); // 在 m1 中统计键为 10 的键值对出现的次数
cout << n << endl; // 输出 0 或 1
lower_bound
和 upper_bound
方法在有序的 map 中查找第一个大于等于或大于目标键的键值对的位置,返回一个迭代器。auto it1 = m1.lower_bound(15); // 在 m1 中查找第一个大于等于 15 的键值对的位置
auto it2 = m1.upper_bound(15); // 在 m1 中查找第一个大于 15 的键值对的位置
if (it1 != m1.end()) {
cout << it1->first << ": " << it1->second << endl; // 输出找到的键值对
} else {
cout << "Not found" << endl;
}
if (it2 != m1.end()) {
cout << it2->first << ": " << it2->second << endl; // 输出找到的键值对
} else {
cout << "Not found" << endl;
}
begin
和 end
方法来获取 map 的迭代器,用于遍历或操作 map 中的键值对。map<int, string>::iterator it; // 定义一个迭代器
for (it = m1.begin(); it != m1.end(); it++) { // 遍历 m1 中的所有键值对
cout << it->first << ": " << it->second << " "; // 输出当前键值对
}
cout << endl;
empty
和 size
方法来判断 map 是否为空或获取 map 中的键值对个数。if (m1.empty()) { // 判断 m1 是否为空
cout << "m1 is empty" << endl;
} else {
cout << "m1 is not empty" << endl;
}
cout << "m1 size: " << m1.size() << endl; // 输出 m1 中的键值对个数
clear
方法来清空 map 中的所有键值对。m1.clear(); // 清空 m1 中的所有键值对
,它的定义如下:template <class T, class Allocator = allocator<T> >
class deque;
其中,T 是元素的类型,Allocator 是分配器的类型,默认为 std::allocator。
deque 的特性有:
deque 的用法有:
deque<int> d1; // 创建一个空的 deque
deque<int> d2(10); // 创建一个包含 10 个 int 类型元素的 deque,默认值为 0
deque<int> d3(10, 1); // 创建一个包含 10 个 int 类型元素的 deque,初始值为 1
deque<int> d4(d3); // 创建一个和 d3 相同的 deque
deque<int> d5(d3.begin(), d3.end()); // 创建一个包含 d3 中所有元素的 deque
deque<int> d6{1, 2, 3, 4, 5}; // 创建一个包含初始化列表中元素的 deque
push_front
和 push_back
方法在 deque 的头部或尾部插入元素。d1.push_front(10); // 在 d1 的头部插入一个值为 10 的元素
d1.push_back(20); // 在 d1 的尾部插入一个值为 20 的元素
pop_front
和 pop_back
方法在 deque 的头部或尾部删除元素。d1.pop_front(); // 删除 d1 的头部元素
d1.pop_back(); // 删除 d1 的尾部元素
insert
和 erase
方法在任意位置插入或删除一个或多个元素。d1.insert(d1.begin(), 30); // 在 d1 的头部插入一个值为 30 的元素
d1.insert(d1.begin() + 2, 3, 40); // 在 d1 的第三个位置插入三个值为 40 的元素
d1.erase(d1.begin()); // 删除 d1 的头部元素
d1.erase(d1.begin(), d1.begin() + 3); // 删除 d1 的前三个元素
resize
方法来改变 deque 的大小。d1.resize(5); // 改变 d1 的大小为 5,如果原来小于 5,则在尾部添加默认值;如果原来大于 5,则删除多余的元素
d1.resize(10, -1); // 改变 d1 的大小为 10,如果原来小于 10,则在尾部添加值为 -1 的元素;如果原来大于 10,则删除多余的元素
[]
或 at
方法来访问或修改 deque 中的元素。cout << d1[0] << endl; // 输出 d1 的第一个元素,不检查越界
cout << d1.at(0) << endl; // 输出 d1 的第一个元素,如果越界则抛出异常
d1[0] = 100; // 修改 d1 的第一个元素为 100,不检查越界
d1.at(0) = 200; // 修改 d1 的第一个元素为 200,如果越界则抛出异常
front
和 back
方法来访问或修改 deque 的头部或尾部元素。cout << d1.front() << endl; // 输出 d1 的头部元素
cout << d1.back() << endl; // 输出 d1 的尾部元素
d1.front() = 300; // 修改 d1 的头部元素为 300
d1.back() = 400; // 修改 d1 的尾部元素为 400
begin
和 end
方法来获取 deque 的迭代器,用于遍历或操作 deque 中的元素。deque<int>::iterator it; // 定义一个迭代器
for (it = d1.begin(); it != d1.end(); it++) { // 遍历 d1 中的所有元素
cout << *it << " "; // 输出当前元素的值
*it += 10; // 将当前元素的值增加 10
}
cout << endl;
empty
和 size
方法来判断 deque 是否为空或获取 deque 中的元素个数。if (d1.empty()) { // 判断 d1 是否为空
cout << "d1 is empty" << endl;
} else {
cout << "d1 is not empty" << endl;
}
cout << "d1 size: " << d1.size() << endl; // 输出 d1 中的元素个数
clear
方法来清空 deque 中的所有元素。d1.clear(); // 清空 d1 中的所有元素
,它的定义如下:template <class T, class Container = deque<T> >
class queue;
其中,T 是元素的类型,Container 是底层容器的类型,默认为 std::deque。
queue 的特性有:
queue 的用法有:
queue<int> q1; // 创建一个空的 queue
deque<int> d{1, 2, 3, 4, 5}; // 创建一个 deque 对象
queue<int> q2(d); // 创建一个包含 deque 中所有元素的 queue
push
方法在 queue 的尾部插入一个元素。q1.push(10); // 在 q1 的尾部插入一个值为 10 的元素
pop
方法在 queue 的头部删除一个元素。q1.pop(); // 删除 q1 的头部元素
front
和 back
方法来访问 queue 的头部或尾部元素。cout << q1.front() << endl; // 输出 q1 的头部元素
cout << q1.back() << endl; // 输出 q1 的尾部元素
empty
和 size
方法来判断 queue 是否为空或获取 queue 中的元素个数。if (q1.empty()) { // 判断 q1 是否为空
cout << "q1 is empty" << endl;
} else {
cout << "q1 is not empty" << endl;
}
cout << "q1 size: " << q1.size() << endl; // 输出 q1 中的元素个数
,它的定义如下:template <class T, class Hash = hash<T>, class KeyEqual = equal_to<T>, class Allocator = allocator<T> >
class unordered_set;
其中,T 是元素的类型,Hash 是哈希函数对象的类型,默认为 std::hash;KeyEqual 是判断两个元素是否相等的函数对象的类型,默认为 std::equal_to;Allocator 是分配器的类型,默认为 std::allocator。
unordered_set 的特性有:
unordered_set 的用法有:
unordered_set<int> s1; // 创建一个空的 unordered_set
unordered_set<int> s2{1, 2, 3, 4, 5}; // 创建一个包含初始化列表中元素的 unordered_set
unordered_set<int> s3(s2); // 创建一个和 s2 相同的 unordered_set
unordered_set<int> s4(s2.begin(), s2.end()); // 创建一个包含 s2 中所有元素的 unordered_set
insert
方法在 unordered_set 中插入一个或多个元素。s1.insert(10); // 在 s1 中插入一个值为 10 的元素
s1.insert({20, 30, 40}); // 在 s1 中插入多个值为 20, 30, 40 的元素
s1.insert(s2.begin(), s2.end()); // 在 s1 中插入 s2 中的所有元素
erase
方法在 unordered_set 中删除一个或多个元素。s1.erase(10); // 在 s1 中删除值为 10 的元素
s1.erase(s1.begin()); // 在 s1 中删除第一个元素
s1.erase(s1.begin(), s1.end()); // 在 s1 中删除所有元素
find
方法在 unordered_set 中查找一个元素,返回一个指向该元素的迭代器,如果没有找到则返回 end()。auto it = s1.find(10); // 在 s1 中查找值为 10 的元素
if (it != s1.end()) {
cout << "Found " << *it << endl; // 输出 Found 10
} else {
cout << "Not found" << endl;
}
count
方法在 unordered_set 中统计一个元素出现的次数,返回一个整数值,由于 unordered_set 不允许重复元素,所以结果只能是 0 或 1。int n = s1.count(10); // 在 s1 中统计值为 10 的元素出现的次数
cout << n << endl; // 输出 0 或 1
begin
和 end
方法来获取 unordered_set 的迭代器,用于遍历或操作 unordered_set 中的元素。unordered_set<int>::iterator it; // 定义一个迭代器
for (it = s1.begin(); it != s1.end(); it++) { // 遍历 s1 中的所有元素
cout << *it << " "; // 输出当前元素的值
}
cout << endl;
empty
和 size
方法来判断 unordered_set 是否为空或获取 unordered_set 中的元素个数。if (s1.empty()) { // 判断 s1 是否为空
cout << "s1 is empty" << endl;
} else {
cout << "s1 is not empty" << endl;
}
cout << "s1 size: " << s1.size() << endl; // 输出 s1 中的元素个数
clear
方法来清空 unordered_set 中的所有元素。s1.clear(); // 清空 s1 中的所有元素
,它的定义如下:template <class Key, class T, class Hash = hash<Key>, class KeyEqual = equal_to<Key>, class Allocator = allocator<pair<const Key, T> > >
class unordered_map;
其中,Key 是键的类型,T 是值的类型,Hash 是哈希函数对象的类型,默认为 std::hash;KeyEqual 是判断两个键是否相等的函数对象的类型,默认为 std::equal_to;Allocator 是分配器的类型,默认为 std::allocator
unordered_map 的特性有:
unordered_map 的用法有:
unordered_map<int, string> m1; // 创建一个空的 unordered_map
unordered_map<int, string> m2{{1, "one"}, {2, "two"}, {3, "three"}}; // 创建一个包含初始化列表中元素的 unordered_map
unordered_map<int, string> m3(m2); // 创建一个和 m2 相同的 unordered_map
unordered_map<int, string> m4(m2.begin(), m2.end()); // 创建一个包含 m2 中所有元素的 unordered_map
insert
方法在 unordered_map 中插入一个或多个键值对。m1.insert({10, "ten"}); // 在 m1 中插入一个键值对 {10, "ten"}
m1.insert({{20, "twenty"}, {30, "thirty"}}); // 在 m1 中插入多个键值对 {{20, "twenty"}, {30, "thirty"}}
m1.insert(m2.begin(), m2.end()); // 在 m1 中插入 m2 中的所有键值对
erase
方法在 unordered_map 中删除一个或多个键值对。m1.erase(10); // 在 m1 中删除键为 10 的键值对
m1.erase(m1.begin()); // 在 m1 中删除第一个键值对
m1.erase(m1.begin(), m1.end()); // 在 m1 中删除所有键值对
find
方法在 unordered_map 中查找一个键对应的值,返回一个指向该键值对的迭代器,如果没有找到则返回 end()。auto it = m1.find(10); // 在 m1 中查找键为 10 的键值对
if (it != m1.end()) {
cout << "Found " << it->first << ": " << it->second << endl; // 输出 Found 10: ten
} else {
cout << "Not found" << endl;
}
count
方法在 unordered_map 中统计一个键出现的次数,返回一个整数值,由于 unordered_map 不允许重复键,所以结果只能是 0 或 1。int n = m1.count(10); // 在 m1 中统计键为 10 的键值对出现的次数
cout << n << endl; // 输出 0 或 1
begin
和 end
方法来获取 unordered_map 的迭代器,用于遍历或操作 unordered_map 中的键值对。unordered_map<int, string>::iterator it; // 定义一个迭代器
for (it = m1.begin(); it != m1.end(); it++) { // 遍历 m1 中的所有键值对
cout << it->first << ": " << it->second << " "; // 输出当前键值对
}
cout << endl;
empty
和 size
方法来判断 unordered_map 是否为空或获取 unordered_map 中的键值对个数。if (m1.empty()) { // 判断 m1 是否为空
cout << "m1 is empty" << endl;
} else {
cout << "m1 is not empty" << endl;
}
cout << "m1 size: " << m1.size() << endl; // 输出 m1 中的键值对个数
clear
方法来清空 unordered_map 中的所有键值对。m1.clear(); // 清空 m1 中的所有键值对
,它的定义如下:template <class T, class Container = deque<T> >
class stack;
其中,T 是元素的类型,Container 是底层容器的类型,默认为 std::deque。
stack 的特性有:
stack 的用法有:
stack<int> s1; // 创建一个空的 stack
deque<int> d{1, 2, 3, 4, 5}; // 创建一个 deque 对象
stack<int> s2(d); // 创建一个包含 deque 中所有元素的 stack
push
方法在 stack 的顶部插入一个元素。s1.push(10); // 在 s1 的顶部插入一个值为 10 的元素
pop
方法在 stack 的顶部删除一个元素。s1.pop(); // 删除 s1 的顶部元素
top
方法来访问或修改 stack 的顶部元素。cout << s1.top() << endl; // 输出 s1 的顶部元素
s1.top() = 20; // 修改 s1 的顶部元素为 20
empty
和 size
方法来判断 stack 是否为空或获取 stack 中的元素个数。if (s1.empty()) { // 判断 s1 是否为空
cout << "s1 is empty" << endl;
} else {
cout << "s1 is not empty" << endl;
}
cout << "s1 size: " << s1.size() << endl; // 输出 s1 中的元素个数
动态多态的实现原理如下:
下面是一个简单的例子:
// 基类
class Animal {
public:
// 虚析构函数
virtual ~Animal() {}
// 虚函数
virtual void speak() {
cout << "Animal speak" << endl;
}
};
// 子类
class Dog : public Animal {
public:
// 重写虚函数
virtual void speak() {
cout << "Dog speak" << endl;
}
};
// 子类
class Cat : public Animal {
public:
// 重写虚函数
virtual void speak() {
cout << "Cat speak" << endl;
}
};
// 测试代码
int main() {
// 基类指针
Animal* p = nullptr;
// 指向子类对象
p = new Dog();
// 调用虚函数
p->speak(); // 输出 Dog speak
// 释放内存
delete p;
// 指向另一个子类对象
p = new Cat();
// 调用虚函数
p->speak(); // 输出 Cat speak
// 释放内存
delete p;
return 0;
}
虚函数:虚函数是一种特殊的成员函数,它可以在基类中声明为 virtual,并在派生类中重写,从而实现动态多态。虚函数的调用是通过虚函数表(vtable)和虚指针(vptr)来实现的,每个有虚函数的类都有一个虚函数表,存储该类的所有虚函数的地址,每个有虚函数的对象都有一个虚指针,指向该对象所属类的虚函数表。当通过基类指针或引用调用一个虚函数时,会根据该指针或引用所指向的对象的类型,找到对应的虚函数表,并根据虚函数在表中的偏移量,找到正确的虚函数地址,并调用之。
不能是虚函数的函数:C++ 中有一些特殊的成员函数,它们不能被声明为虚函数,主要有以下几种:
构造函数:构造函数是用于创建对象并初始化对象状态的成员函数,它不能被声明为虚函数,因为在创建对象时,还没有分配内存空间,也没有生成虚指针和虚函数表,因此无法调用虚函数。如果将构造函数声明为虚函数,编译器会报错。但是,构造函数可以调用其他的虚函数,只是这时候调用的是自己类中定义或继承的版本,而不是子类重写的版本。
析构函数:析构函数是用于销毁对象并释放资源的成员函数,它可以被声明为虚函数,以实现多态的析构。如果一个基类指针或引用指向一个派生类对象,并且通过该指针或引用来删除该对象,那么如果基类的析构函数不是虚函数,就只会调用基类的析构函数,而不会调用派生类的析构函数,从而导致资源泄漏或其他错误。但是,析构函数不能被声明为纯虚函数(pure virtual function),也就是在声明时后面加上 = 0
的那种。因为纯虚函数表示没有实现,需要子类来提供实现,而每个类都需要有自己的析构函数来执行清理工作。如果将析构函数声明为纯虚函数,编译器会报错。
静态成员函数:静态成员函数是属于类本身而不属于对象的成员函数,它可以通过类名或对象来调用,但是它不包含 this 指针,也不受访问控制符的限制。静态成员函数不能被声明为虚函数,因为静态成员函数不依赖于对象的存在,也就无法使用虚指针和虚函数表来实现多态。如果将静态成员函数声明为虚函数,编译器会报错。
友元函数:友元函数是一种特殊的非成员函数,它可以访问某个类或对象的私有或保护成员,但是它不属于该类或对象。友元关系不能被继承或传递。友元函数不能被声明为虚函数。
内联(inline)成员函数:内联成员函数是一种优化技术,它可以在编译期将内联成员函数的代码直接嵌入到调用处,从而避免了普通成员函数调用时产生的额外开销(如参数传递、栈帧分配等)。内联成员函数可以在定义时加上 inline 关键字来显式声明,也可以在类内部定义时隐式声明。内联成员函数不能被声明为虚函数,因为虚函数的调用是通过虚函数表和虚指针来实现的,这与内联成员函数的优化目的相矛盾。如果将内联成员函数声明为虚函数,编译器会忽略 inline 关键字,将其当作普通的虚函数处理。
,它的定义如下:template <class T, class Allocator = allocator<T> >
class vector;
其中,T 是元素的类型,Allocator 是分配器的类型,默认为 std::allocator。
vector 的特性有:
vector 支持随机访问,可以通过下标运算符 [] 或 at 方法来访问或修改任意位置的元素,时间复杂度为 O(1)。
vector 支持在尾部插入或删除元素,可以使用 push_back 或 pop_back 方法来实现,时间复杂度为 O(1)(摊还)。
vector 不支持在头部或中间插入或删除元素,如果要实现,可以使用 insert 或 erase 方法,但是时间复杂度为 O(n),因为需要移动后面的元素。
vector 有一个容量(capacity)和一个大小(size)的概念,容量表示已经分配的内存空间能够容纳的元素个数,大小表示实际存储的元素个数。当大小超过容量时,vector 会重新分配一块更大的内存空间,并将原来的元素复制过去,这会导致效率降低和迭代器失效。可以使用 reserve 方法来预先分配足够的内存空间,避免频繁的重新分配。
list:list 是一种顺序容器,它使用双向链表来存储元素,每个节点包含一个元素和两个指针,分别指向前一个节点和后一个节点。list 的头文件是
,它的定义如下:
template <class T, class Allocator = allocator<T> >
class list;
其中,T 是元素的类型,Allocator 是分配器的类型,默认为 std::allocator。
list 的特性有:
list 不支持随机访问,只能通过迭代器或指针来访问或修改任意位置的元素,时间复杂度为 O(n)。
list 支持在头部或尾部插入或删除元素,可以使用 push_front, pop_front, push_back 或 pop_back 方法来实现,时间复杂度为 O(1)。
list 支持在中间插入或删除元素,可以使用 insert 或 erase 方法来实现,时间复杂度为 O(1),因为不需要移动其他元素。
list 没有容量和大小的概念,它只有一个大小(size)的概念,表示实际存储的元素个数。list 不需要重新分配内存空间,因为它是动态地分配每个节点的内存空间。
区别和适用场景:vector 和 list 容器的区别主要在于它们使用的数据结构和内存管理方式不同,这导致了它们在性能和功能上有各自的优缺点。一般来说: