《常见关键字知识整理》
extern
- extern 是一个存储类说明符,用于声明一个变量或函数是在其他文件中定义的。
- 当在一个文件中使用 extern声明一个变量或函数时,意味着该变量或函数是在其他文件中定义的,并且可以在当前文件中使用。
- 使用 extern声明的变量或函数的定义可以在其他文件中找到,编译器在链接阶段会将所有外部定义的变量和函数链接在一起,形成最终的可执行文件。
static
- 修饰局部变量:当static用于修饰局部变量时,这个变量的存储位置会在程序执行期间保持不变,且只在程序执行到该变量的声明处时初始化一次。即使函数被多次调用,static局部变量也只在第一次调用时初始化,之后的调用将不会重新初始化它。
- 修饰全局变量或函数:当static用于修饰全局变量或函数时,限制了这些变量或函数的作用域,它们只能在定义它们的文件内部访问。有助于避免在不同文件之间的命名冲突。
- 修饰类的成员变量或函数:在类内部,static成员变量或函数属于类本身,而不是类的任何特定对象。这意味着所有对象共享同一个(static)成员变量,无需每个对象都存储一份拷贝。static成员函数可以在没有类实例的情况下调用。
const
定义普通常量:当修饰基本数据类型的变量时,表示常量含义,对应的值不能被修改。
const int MAX_SIZE = 100; // MAX_SIZE是一个
修饰指针:这里分多种情况,比如指针本身是常量,指针指向的数据是常量,或者指针本身和其指向的数据都是常量。
修饰引用:const 修饰引用时,一般用作函数参数,表示函数不会修改传递的参数值。
void func(const int& a) { // a是一个对常量的// ...
}
修饰类成员函数:const 修饰成员函数,表示函数不会修改类的任何成员变量,除非这些成员变量被声明为 mutable。
class MyClass {
public:void myFunc() const { // myFunc是一个co// ...}
};
修饰类成员变量:const 修饰成员变量,表示生命期内不可改动此值。
class MyClass {
public:const int a = 5;
};
inline
- inline的作用是建议编译器将函数调用替换为函数体,以减少函数调用的开销,和宏比较类似。
- 使用inline函数的目的一定是希望可以提高程序的运行效率,特别是那些频繁调用的小函数。
优点:
- 降低函数调用的开销,原理就是因为省去了调用和返回的指令开销。
- 如果函数体较小,可以提高代码执行的效率。
缺点:
- 容易导致代码膨胀,整个可执行程序体积变大,特别是当inline函数体较大且被多次调用时。
- 内联是一种建议,编译器可以选择忽略inline关键字。
explicit
关键字explicit的主要作用是防止构造函数或转换函数在不合适的情况下被隐式调用。
例如,如果有一个只有一个参数的构造函数,加上explicit关键字后,编译器就不会自动用该构造函数进行隐式转换。这可以避免由于意外的隐式转换导致的难以调试的行为。
class Foo {
public:explicit Foo(int x): value(x) {}
private:int value;
};
void func(Foo f) {//....
}
int main() {Foo foo = 10;//错误,必须使用Foo foo(10) 或 Foo foo = Foo(10)func(10);//错误,必须使用func(Foo(10))
}
如果没有explicit关键字,Foofoo=10; 以及func(10);这样的代码是可以通过编译的,这会导致一些意想不到的隐式转换。
final
final关键字在C++11中引|入,它主要用于防止类被继承或防止虚函数被覆盖。
- 防止类被继承:当一个类被声明为final,这个类不能被进一步继承。
- 防止虚函数被覆盖:当一个虚函数被声明为final,这个虚函数在派生类中不能被重新定义。
- override关键字:final通常和override关键字一起使用,可以显式指出该函数是覆盖基类中的某个虚函数,并且不允许再被派生类覆盖。
volatile
在C++中,volatile关键字的主要作用是告知编译器某个变量可能会在程序的其他地方被改变,防止编译器对这样的变量进行优化。保证对该变量进行的读写操作不会被编译器优化掉,每一次访问都是实际发生的。具体来说,volatile关键字会影响以下几个方面:
- 防止编译器对代码进行优化,确保每次读写都是直接从内存中操作,而不是使用寄存器中的值。
- 通常用于多线程编程中,表示变量可以被其他线程修改。
- 硬件编程中,如处理器寄存器,内存映射I/O等场景,常与硬件交互的变量声明为volatile。
delete
delete 关键字用来禁用某些默认的成员函数。主要的作用就是禁用拷贝构造函数和拷贝赋值运算符,如下例:
class MyClass {
public:MyClass() = default;MyClass(const MyClass&) = delete;MyClass& operator=(const MyClass&) = delete;
};
default
default 关键字用于显式地指示编译器为某个成员函数生成默认的实现。它经常用在构造函数、析构函数,以及拷贝构造函数上。
class MyClass {
public:MyClass() = default;~MyClass() = default;MyClass(const MyClass&) = default;MyClass& operator=(const MyClass&) = default;
};
静态转换(static_cast):
- 用于显式转换类型,通常用于较为安全的转换,如基本数据类型之间的转换,以及具有继承关系的类指针或引用之间的转换。
- 在编译时进行类型检查,因此不能用于转换不相关的指针类型。
- 不能用于动态类型转换(如基类到派生类的转换)。
动态转换(dynamic_cast):
- 主要用于具有继承关系的类指针或引用之间的转换,特别是将基类指针或引用转换为派生类指针或引用。
- 运行时进行类型检查,如果转换失败,会返回空指针(对于指针)或引发 std::bad_cast 异常(对于引用)。
- 只能用于转换类的指针或引用,并且必须存在虚函数。
常量转换(const_cast):
- 用于去除对象的 const 属性或 volatile 属性。
- 主要用于修正传递给函数的参数类型,以便在函数内部修改它们。
- 通常被认为是不安全的,因为它可以绕过 const 限定符的安全性。
重新解释转换(reinterpret_cast):
- 用法:主要用于在几乎无关的类型之间进行转换,比如将指针类型转换为整型,或相反。这通常用于底层操作,比如硬件编程,或某些预期内的操作。
- 非常危险,通常情况下应该尽量避免使用,因为它不提供任何类型安全性保证。
- 通常用于处理底层的内存操作,如将指针转换为整数类型以进行位操作。
堆内存和栈内存的区别
序列式容器(Sequence Containers)
按元素插入顺序存储,元素位置与插入顺序相关,不自动排序。
1. vector(动态数组)
- 特点:连续内存空间,支持随机访问,尾部插入 / 删除效率高(O (1)),中间 / 头部插入 / 删除效率低(O (n))。
- 适用场景:需要频繁随机访问,且插入 / 删除主要在尾部的场景(如存储动态列表)。
#include <vector>
vector<int> vec = {1, 2, 3};
vec.push_back(4); // 尾部插入
int x = vec[2]; // 随机访问(O(1))
vec.insert(vec.begin(), 0); // 头部插入(效率低)
2. list(双向链表)
- 特点:非连续内存,通过指针连接节点,支持双向遍历,任意位置插入 / 删除效率高(O (1)),不支持随机访问(O (n))。
- 适用场景:需要频繁在中间插入 / 删除元素的场景(如实现队列、链表)。
#include <list>
list<int> lst = {1, 2, 3};
lst.push_front(0); // 头部插入(O(1))
lst.insert(++lst.begin(), 5); // 中间插入
3. deque(双端队列)
- 特点:分段连续内存,支持首尾高效插入 / 删除(O (1)),随机访问效率较高(O (1))。
- 适用场景:需要在两端频繁操作的场景(如实现队列、栈)。
#include <deque>
deque<int> dq;
dq.push_back(1); // 尾部插入
dq.push_front(0); // 头部插入
int x = dq[1]; // 随机访问
4. array(固定大小数组)
- 特点:编译时固定大小的连续内存,效率与原生数组接近,支持随机访问。
- 适用场景:需要固定大小且高效访问的数组(比原生数组更安全,支持迭代器)。
#include <array>
array<int, 3> arr = {1, 2, 3}; // 大小固定为3
int x = arr[1];
5. forward_list(单向链表)
- 特点:单链表结构,仅支持单向遍历,内存占用比 list 更小,插入 / 删除效率高。
- 适用场景:内存受限且只需单向遍历的场景。
关联式容器(Associative Containers)
元素按关键字(Key)排序存储,支持快速查找(通常 O (log n)),分为 “有序” 和 “无序” 两类。
1. 有序关联容器(基于红黑树实现)
set:存储唯一关键字,元素即关键字,自动排序(默认升序)。
#include <set>
set<int> s = {3, 1, 2}; // 自动排序为 {1, 2, 3}
s.insert(4); // 插入后仍有序
bool exists = s.count(2); // 查找元素是否存在(O(log n))
multiset:与 set 类似,但允许关键字重复。
multiset<int> ms = {2, 1, 2}; // 存储 {1, 2, 2}
map:存储键值对(Key-Value),关键字唯一,按 Key 排序。
#include <map>
map<string, int> mp;
mp["apple"] = 5; // 插入键值对
int price = mp["apple"]; // 查找(O(log n))
multimap:与 map 类似,但允许关键字重复(一个 Key 可对应多个 Value)。
2. 无序关联容器(基于哈希表实现)
unordered_set:存储唯一关键字,无序,查找、插入、删除效率平均为 O (1)(哈希冲突时可能退化)。
#include <unordered_set>
unordered_set<int> us = {3, 1, 2}; // 无序存储
unordered_multiset:允许关键字重复的无序集合。
unordered_map:存储键值对,Key 唯一,无序,查找效率高(平均 O (1))。
#include <unordered_map>
unordered_map<string, int> ump;
ump["banana"] = 3; // 插入键值对
unordered_multimap:允许 Key 重复的无序映射。
容器适配器(Container Adapters)
基于其他容器实现,提供特定接口,不直接支持迭代器。
1. stack(栈)
特点:后进先出(LIFO),基于 deque 或 list 实现,仅支持栈顶操作。
#include <stack>
stack<int> st;
st.push(1); // 入栈
st.pop(); // 出栈(不返回元素)
int top = st.top(); // 获取栈顶元素
2. queue(队列)
特点:先进先出(FIFO),基于 deque 或 list 实现,支持队尾入、队头出。
#include <queue>
queue<int> q;
q.push(1); // 入队
q.pop(); // 出队
int front = q.front(); // 获取队头元素
3. priority_queue(优先队列)
特点:元素按优先级排序(默认最大元素在队头),基于堆结构实现。
#include <queue>
priority_queue<int> pq;
pq.push(3);
pq.push(1);
pq.push(2); // 内部按降序排序,队头为3
int max = pq.top(); // 获取优先级最高的元素
容器选择建议
- 随机访问频繁:优先 vector、deque、array。
- 中间插入 / 删除频繁:优先 list、forward_list。
- 快速查找(按 Key):有序场景用 set/map,无序场景用 unordered_set / unordered_map。
- 特定数据结构需求:栈用 stack,队列用 queue,优先级队列用 priority_queue。