主题
C++关键字
const
定义常量
- const修饰的变量不可修改,且必须初始化。
- const修饰的常量可以是编译时常量,也可以是运行时常量,具体取决于其初始化方式。
- 非const变量默认是extern。要使const变量能够在其他文件中访问,必须在文件中显式地指定它为extern。
c++
// 不指定extern,则只能在当前文件内访问
extern const int BUF_SIZE = 100; // 编译时常量
int getSize()
{
return sizeof(int);
}
int main() {
const int n = 10; // 编译时常量
cosnt int n1 = 5 * n; // 常量表达式也是编译时常量
int arr[n]{};
const int size = getSize(); // 运行时常量:需要运行时才能确定
}const与指针
const位于*左侧,修饰的是* 变量,修饰的是值,即不可以通过指针来修改其指向的值,但可以修改指针的指向。
c++
const char* s1 = "Tom";
// *s1 = "Lucy"; // 错误,不能通过指针来修改其指向的值
s1 = "Lucy"; // 修改指针的指向这里并不是修改了Tom,而是在新的内存中存放Lucy,把指针s1的指向由Tom的内存改为指向Lucy的内存。
const位于*右侧,修饰的是指针变量,指针的值是不可以改变的,即指向不能变,必须初始化。可以通过指针来修改其指向的值。
c++
char* const s2 = "Tom";
// *s2 = "Lucy"; // 错误
// s2 = "Lucy"; // 错误,因为指向不可改变为什么高亮的行也会错误呢?在C++中,字符串字面量的类型是const char[],是不可以修改的。事实上,这相当于上面两者的结合体,既不能修改指针指向,也不能通过指针修改其指向的值。
c++
const char* const s2 = "Tom";如果要修改,应该使用string或字符数组。
const与函数
const与类
- const修饰成员变量,必须使用初始化列表初始化,不能在构造函数内初始化。
- const修饰成员函数,不能修改成员变量的值,且只能调用const成员函数。
- const修饰类的实例对象,该对象的所有成员变量都不能修改。
c++
class Person {
public:
const int age_ = 0;
Person(int age) : age_(age) {
// this->age_ = age;
}
void display() const;
}
const Person p1(10);
// p1.age = 20;constexpr
- constexpr修饰的变量必须能在编译阶段确定或计算出,即编译时常量。
- constexpr修饰的函数的参数和返回值(返回值可以用非常量的变量存储)必须满足常量表达式的要求。
- constexpr结合模板使用可以实现复杂的编译时计算,提高代码的灵活性和性能。
c++
template <typename T>
T getA(T a)
{
// std::is_same 编译时检查两个类型是否相同
if constexpr (std::is_same<T, int>::value)
return a * 10;
else if constexpr (std::is_same<T, float>::value)
return a * 1000;
else
return a;
}static
- 静态函数:静态函数的作用域为当前文件,即只能在当前文件内访问。普通函数默认是extern,可以在其他文件中访问。
- 静态变量:空间分配只初始化一次,存在于程序运行的整个生命周期。
- static与类:静态成员变量和静态成员方法都由类所拥有,内存空间只有一份,类的所有实例对象都共享类的静态成员变量和静态成员方法。
- 静态成员变量:不能使用构造函数初始化,除非静态成员变量是const整数类型或枚举型,否则不能在声明时初始化。
- 静态成员方法:只能访问静态成员变量和静态成员方法。
- 可直接通过类名访问public的静态成员变量和静态成员方法。
c++
static void test2() {
static int age = 1; // age变量在静态存储区,再次运行函数也不会重新初始化age
cout << "age = " << age << endl;
age++;
cout << "age = " << age << endl;
}c++
class Person
{
public:
static int age;
static void addAge() {
age++;
}
}
int Person::age = 1;
int main() {
Person p1 = Person();
p1.addAge();
Person::addAge();
cout << "age = " << Person::age << endl; // 3
Person p2 = Person();
p2.addAge();
cout << "age = " << p2.age << endl; // 4
}typedef
用于定义类型的别名,便于代码的可读性和维护性。
c++
typedef unsigned int Item; // 定义unsigned int类型的别名为Item
Item x = 10;extern
- 用于声明全局变量。通常用于在多个源文件中共享全局变量。
- 用于解决 C++ 和 C 代码之间的兼容性问题。由于C++支持函数重载,C++编译器会将函数名与参数类型、返回值等信息一起编译到函数符号中,而C编译器不会这样做。因此,直接在C程序中调用C++函数会导致链接错误。为了解决这个问题,可以使用
extern "C"关键字来指示编译器按照C语言的方式来处理特定的代码。c++//xx.h int add(...) //xx.c int add(){} //xx.cpp extern "C" { #include "xx.h" }c++//xx.hpp #include <iostream> extern "C" { // 可同时声明多个函数 int add(int, int); void increment(); } //xx.cpp int add(int x, int y) { std::cout << "x=" << x << ", y=" <<y << std::endl; return x + y; } //xx.c 先声明函数,再调用 extern int add(int, int); // extern可省略,但推荐加上 int main() { add(1, 2); }
inline
- 在类中声明函数并定义,则该函数是隐式内联函数。在声明之后想要成为内联函数,必须在定义处加inline关键字。(推荐后者写法)
- 虚函数可以是内联函数,但当虚函数表现多态性的时候不会内联,因为虚函数的多态性在运行期,而编译器无法知道运行期调用哪块代码。编译器需要知道所调用的对象是哪个类,这只有在编译器具有实际对象而不是对象的指针或引用时才会发生。
内联函数仅仅省去了函数调用的开销,从而提高函数的执行效率,但每一处的内联函数都要复制代码,会导致程序代码量增加。以下情况不适合使用内联:
- 函数体内的代码较长,内联将导致内存消耗代价较高。
- 执行函数的时间比调用函数的开销大。
final
- 将类声明为final,将禁止此类被继承。
- 普通函数不能声明为final,只有虚函数声明为final,可防止派生类覆盖该虚函数。
override
显式声明派生类中的成员函数覆盖了基类中的虚函数。
- 增强可读性并进行编译器检查:如果派生类中的函数没有正确覆盖基类中的虚函数(例如函数签名不匹配),编译器会报错。
- 与final结合使用表示该函数覆写了基类的虚函数,且不允许进一步覆写。
volatile
- 变量可能被某些编译器未知因素更改,使用volatile修饰变量告知编译器不要对该变量进行优化。
- volatile声明的变量,每次访问都必须从内存中取值(没有被volatile修饰的变量可能由于编译器的优化,从CPU寄存器中取值)。
- const可以是volatile(如只读的状态寄存器)
- 指针可以是volatile
explicit
- 当类的构造函数只有一个输入参数时(其它参数有默认值也算只有一个输入),此构造函数可隐式将参数值转换为类对象。explicit修饰此构造函数后,将关闭此特性,但仍可以显式强制类型转换。
- 修饰转换函数时,可以防止隐式转换,但按语境(通常是在与布尔相关的条件判断或布尔逻辑运算符中的语境)转换除外。
c++
class Person
{
public:
// 构造函数
explicit Person(std::string name, int age=0): name_(name) {}
// 转换函数1:将Person对象转换为string类型
explicit operator std::string() const { return name_; }
// 转换函数2:将Person对象转换为int类型
operator int() const { return age_; }
explicit operator bool() const { return !name_.empty(); }
private:
std::string name_;
int age_ = 0;
};
int main() {
Person p1 = Person("Tom");
// Person p1 = "Tom"; // 隐式转换,构造函数使用explicit后会报错
Person p2 = (Person) "Tom"; // 显式强制转换
std::string name = (std::string) p2; // 强制类型转换
int age = p2; // 隐式调用转换函数2,转换函数使用explicit后会报错
if (p2) {}
}提示
谨慎使用隐式转换函数。通常,最好选择仅在被显式地调用时才会执行的函数。