C++关键字总结
一、const
该用const的位置就应该带上const,一出手就是高手风范
const 在函数不同位置代表不同的作用:https://blog.youkuaiyun.com/weixin_44039187/article/details/105182211
const 在变量不同位置代表不同的作用:https://www.cnblogs.com/scyq/p/12419267.html
1.1 修饰对象
-
变量:不可以被改变
-
指针:主要区别在于指针本身是否可以修改和指针指向的内容是否可以修改。
- 指向常量的指针(pointer to const):指向的内容是常量,
const char* p2
- 无法通过指针修改内存数据,但是可以改变指针的指向。
- 指针常量(const pointer):指针的地址是常量,
char* const p3
- 无法改变指针的指向,但是可以修改指向的值
- 指向常量的指针(pointer to const):指向的内容是常量,
-
引用:指向常量的引用(reference to const)
const A &q
-
成员函数:说明该成员函数内不能修改成员变量。
1.2 #define 和 const 常量
宏定义 #define | const 常量 |
---|---|
宏定义,相当于字符替换 | 常量声明 |
预处理器处理 | 编译器处理 |
无类型安全检查 | 有类型安全检查 |
不分配内存 | 要分配内存 |
存储在代码段 | 存储在数据段 |
可通过 #undef 取消 | 不可取消 |
二、static
2.1 非static成员 与 static成员对比
staic data member:只保存一份在内存中
static member func:无this指针,只能用于处理static数据
non-static data member:每个对象都保存一份各自的数据
不同对象调用成员函数 等价于 通过传入不同对象的this指针来作为成员函数的参数,去访问各自对象的数据。
2.2 静态数据必须在类外部定义
静态成员变量的声明在类内部,但是其定义必须在类外部定义来获得内存
类类型::变量名
2.3 修饰对象
- 修饰普通变量,修改变量的存储区域和生命周期,使变量存储在静态存储区,在 main 函数运行前就分配了空间,如果有初始值就用初始值初始化它。
- 修饰普通函数,表明函数的作用范围,仅在定义该函数的文件内才能使用。防止与他人命名空间里的函数重名,可以将函数定位为 static。
- 修饰成员变量,修饰成员变量使所有的对象只保存一个该变量,静态存储区,而非对象实例的内存中。。
- 修饰成员函数,不需要生成对象就可以访问该函数,存储在程序的代码段中,但是在 static 函数内不能访问非静态成员。
2.4 总结对比
使用位置 | 存储位置 | 作用域 | 生命周期 |
---|---|---|---|
静态全局变量 | 静态存储区 | 当前文件 | 程序开始到程序结束 |
全局变量 | 静态存储区 | 全局作用域,跨文件可访问 | 程序开始到程序结束 |
静态局部变量 | 静态存储区 | 定义它的块或函数内 | 程序开始到程序结束 |
局部变量 | 栈区 | 函数或块内有效 | 函数或块开始到结束 |
静态类成员变量 | 静态存储区 | 类内或通过类名访问 | 程序开始到程序结束 |
类成员变量 | 对象内存(堆区或栈区,取决于对象的存储位置) | 对象作用域 | 随对象生命周期而定 |
静态类成员函数 | 代码段 | 类内或通过类名访问 | 程序开始到程序结束 |
静态函数(普通) | 代码段 | 当前文件 | 程序开始到程序结束 |
普通函数 | 代码段 | 全局作用域 | 程序开始到程序结束 |
三、inline内联函数
3.1 虚函数可以是内联函数嘛
可以,但没必要。
虚函数通过虚函数表(vtable)机制实现动态绑定(运行期)。
而内联函数要求函数地址在编译时已知(编译器),而动态绑定的虚函数无法满足这一要求。
而当虚函数**静态绑定时(非多态调用)**可以内联——(但是这样虚函数的意义在哪?)
3.2 内联函数和宏定义区别
特性 | 内联函数(inline ) | 宏定义(#define ) |
---|---|---|
替换时机 | 编译时 | 预处理时(编译之前) |
类型安全 | 类型安全,参数具有明确类型 | 无类型检查,可能导致隐式类型转换问题 |
作用域 | 按照函数的作用域来解析,支持作用域控制 | 没有作用域,可能引发意外的命名冲突 |
返回值 | 可以返回值 | 不能直接返回值,宏展开时只做文本替换 |
性能 | 性能与宏相当,实际优化取决于编译器 | 性能上通常与内联函数相似,但存在额外风险 |
四、volatile
用来修饰变量(指针),它告诉编译器该变量的值可能会在程序执行的过程中被外部因素(如硬件、操作系统、其他线程等)改变,从而影响编译器的优化行为。
因此在每次访问该变量时,必须从内存中重新读取它,而不是使用缓存。
4.1 什么时候使用volatile
硬件寄存器:在嵌入式编程中,通常会使用
volatile
来表示硬件寄存器的值(硬件可能会直接修改这些寄存器的值)多线程编程:在多线程编程中,多个线程可能会同时访问同一个共享变量。此时,如果没有使用
volatile
,编译器可能会对该变量进行优化
特性 | volatile | const | atomic |
---|---|---|---|
用途 | 防止编译器优化,确保每次访问内存值 | 确保值不可修改 | 确保线程间的原子性操作 |
作用 | 保证每次访问变量时都从内存读取值 | 不能修改变量的值 | 保证操作的原子性,防止竞争条件 |
线程安全 | 不提供线程安全支持 | 不涉及线程安全 | 提供线程安全支持,适用于并发编程 |
常见用途 | 嵌入式编程、多线程间共享变量 | 用于常量数据 | 多线程共享数据的原子操作 |
五、explicit
主要作用:用于修饰构造函数和转换运算符,防止它们在隐式类型转换过程中被错误调用
5.1 隐式调用构造函数
class MyClass {
public:
MyClass(int x) {
std::cout << "Constructor called with value " << x << std::endl;
}
};
void function(MyClass obj) {
std::cout << "Function called" << std::endl;
}
int main() {
function(10); // 隐式调用 MyClass(10),会调用构造函数
return 0;
}
//explicit
class MyClass {
public:
explicit MyClass(int x) { // `explicit` 防止隐式转换
std::cout << "Constructor called with value " << x << std::endl;
}
};
void function(MyClass obj) {
std::cout << "Function called" << std::endl;
}
int main() {
// function(10); // 编译错误,因为 `MyClass(10)` 需要显式调用
function(MyClass(10)); // 显式调用构造函数
return 0;
}
5.2 隐式调用操作符
class MyClass {
public:
/*explicit*/operator int() const { // 转换为 int 类型
return 42;
}
};
void function(int x) {
std::cout << "Function called with value " << x << std::endl;
}
int main() {
MyClass obj;
function(obj); // 隐式调用 `operator int()`,会转换为 int 类型
//function(static_cast<int>(obj)); // 显式调用转换
return 0;
}
特性 | explicit 修饰构造函数 | explicit 修饰转换运算符 |
---|---|---|
功能 | 防止构造函数的隐式类型转换 | 防止类型转换运算符的隐式转换 |
典型用途 | 避免自动调用构造函数进行隐式转换 | 避免自动调用转换运算符进行隐式转换 |
要求显式调用 | 必须显式地创建对象 | 必须显式地进行类型转换 |
避免的情况 | 误用构造函数进行隐式转换 | 误用类型转换进行隐式转换 |
六、friend
指定某些类或函数访问另一个类的私有(
private
)和保护(protected
)成员,即使它们并不是该类的成员(破坏封装性)
6.1 友元函数
友元函数是一个被声明为类的友元的函数,虽然它不是该类的成员函数,但它能够访问该类的私有和保护成员。
class MyClass {
private:
int x;
public:
MyClass(int val) : x(val) {}
// 声明友元函数
friend void display(const MyClass& obj);
};
// 友元函数定义
void display(const MyClass& obj) {
std::cout << "Value of x: " << obj.x << std::endl; //可访问MyClass的私有变量 x
}
注意点:声明友元时必须把函数的声明放在类定义内部,但其定义可以在类外部。
6.2 友元类
友元类是一个被声明为另一个类的友元的类,允许该类访问目标类的私有和保护成员。
class MyClass {
private:
int x;
public:
MyClass(int val) : x(val) {}
// 声明 FriendClass 为友元类
friend class FriendClass;
};
class FriendClass {
public:
void display(const MyClass& obj) {
std::cout << "Value of x: " << obj.x << std::endl; //可直接访问MyClass的私有变量 x
}
};
特性 | 友元函数 | 友元类 |
---|---|---|
定义位置 | 声明在目标类内部,定义可以在类外部 | 声明在目标类内部 |
访问范围 | 允许单个函数访问类的私有成员 | 允许整个类及其成员访问目标类的私有成员 |
作用范围 | 只限于声明为友元的函数 | 允许友元类的所有成员访问目标类的私有成员 |
类型 | 可以是全局函数或类的成员函数等 | 只能是类 |
七、using
7.1 using声明
一次只引入命名空间的一个成员。它使得我们可以清楚知道程序中所引用的到底是哪个名字
using namespace_name::name;
7.2 using指示
using 指示
使得某个特定命名空间中所有名字都可见,这样我们就无需再为它们添加任何前缀限定符了。
using namespace_name name;
7.3 尽量少使用using指示
使用 using 命令比使用 using 编译命令更安全,这是由于它只导入了指定的名称。如果该名称与局部名称发生冲突,编译器将发出指示。
using编译命令导入所有的名称,包括可能并不需要的名称。如果与局部名称发生冲突,则局部名称将覆盖名称空间版本,而编译器并不会发出警告。
八、::范围解析运算符
帮助开发者区分全局变量、类成员、命名空间成员等,避免命名冲突或不明确的问题。
8.1 全局作用域符(::name
)
用于在全局命名空间中查找标识符(变量、函数、类型等)。它常用于解决命名冲突或者在嵌套作用域中访问全局变量或全局函数。
-
访问全局变量或全局函数:即使局部作用域中有与全局命名相同的变量或函数,也可以通过
::
来显式访问全局的标识符。-
#include <iostream> int value = 10; // 全局变量 int main() { int value = 20; // 局部变量 std::cout << "Local value: " << value << std::endl; // 输出局部变量 std::cout << "Global value: " << ::value << std::endl; // 使用全局作用域符访问全局变量 return 0; }
-
8.2 命名空间作用域符(namespace::name
)
用于表示指定类型的作用域范围是具体某个命名空间的
-
访问全局命名空间中的标识符:当命名空间或类中定义的成员名称与全局名称冲突时,
::
可用来明确区分。-
namespace MyNamespace { int value = 100; } int value = 200; // 全局变量 int main() { std::cout << "Global value: " << ::value << std::endl; // 访问全局变量 std::cout << "Namespace value: " << MyNamespace::value << std::endl; // 访问命名空间的变量 return 0; }
-
8.3 类作用域符(class::name
)
用于表示指定类型的作用域范围是具体某个类的
-
定义类的静态成员变量
-
class MyClass { public: static int value; // 静态成员变量声明 }; // 静态成员变量的定义 int MyClass::value = 100;
-
-
定义类成员函数
-
class MyClass { public: void display(); // 成员函数声明 }; // 成员函数定义 void MyClass::display() { std::cout << "Member function defined outside the class" << std::endl; }
-
九、enum
9.1 不限定作用域的枚举类型(传统枚举)
枚举常量在全局作用域中。容易引发命名冲突。
enum Color {
Red, Green, Blue
};
enum TrafficLight {
Red, Yellow, Green // 与 Color 枚举冲突
};
int main() {
std::cout << "Red: " << Red << std::endl; // 冲突:不清楚是 Color::Red 还是 TrafficLight::Red
return 0;
}
9.2 限定作用域的枚举类型(强类型枚举(C++11 引入))
使用
enum class
定义枚举类型,可以避免命名冲突,并增加类型安全性。优点:
枚举常量受限于枚举类型作用域,避免命名冲突。
类型安全:不能将不同枚举类型的值混用。
需要显式类型转换才能输出数值。
#include <iostream>
enum class Color {
Red, Green, Blue
};
enum class TrafficLight {
Red, Yellow, Green
};
int main() {
Color color = Color::Red;
TrafficLight light = TrafficLight::Red;
// std::cout << "Red: " << Red << std::endl; // 错误,Red 不在全局作用域
std::cout << "Color::Red: " << static_cast<int>(color) << std::endl; // 显式转换
std::cout << "TrafficLight::Red: " << static_cast<int>(light) << std::endl;
return 0;
}
9.3 总结
特点 | 传统枚举 (enum ) | 强类型枚举 (enum class ) |
---|---|---|
作用域 | 全局作用域 | 定义在类或命名空间作用域内 |
类型安全 | 不安全,可以隐式转换为整数或其他枚举 | 类型安全,不能隐式转换 |
命名冲突 | 易发生冲突 | 避免冲突 |
底层类型 | 隐式为 int | 可以显式指定底层类型 |
使用方式 | EnumName | EnumName::Constant |
十、decltype
用于检查实体的声明类型或表达式的类型;语法
decltype ( expression )
C++11 引入,与
auto
一起极大地增强了类型推导的能力。总结:
decltype
是一种编译时类型推导工具,可以获取表达式的类型。与
auto
不同,decltype
不需要初始化变量,并且直接基于表达式的类型推导。常用于泛型编程、函数返回类型推导和复杂类型推导。
通过
decltype
,开发者可以在模板和复杂代码中减少显式声明类型的负担,提升代码的灵活性和可读性。
10.1 使用场景
泛型编程:在模板中,通过 decltype
推导函数或表达式的返回类型。
template <typename T, typename U>
auto add(T a, U b) -> decltype(a + b) { // 使用 decltype 推导返回值类型
return a + b;
}
int main() {
std::cout << add(1, 2.5) << std::endl; // 返回 double 类型
return 0;
}