Skip to content
0

C++关键字

const

定义常量

  1. const修饰的变量不可修改,且必须初始化。
  2. const修饰的常量可以是编译时常量,也可以是运行时常量,具体取决于其初始化方式。
  3. 非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与类

  1. const修饰成员变量,必须使用初始化列表初始化,不能在构造函数内初始化。
  2. const修饰成员函数,不能修改成员变量的值,且只能调用const成员函数。
  3. 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

  1. 静态函数:静态函数的作用域为当前文件,即只能在当前文件内访问。普通函数默认是extern,可以在其他文件中访问。
  2. 静态变量:空间分配只初始化一次,存在于程序运行的整个生命周期。
  3. 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

  1. 用于声明全局变量。通常用于在多个源文件中共享全局变量。
  2. 用于解决 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关键字。(推荐后者写法)
  • 虚函数可以是内联函数,但当虚函数表现多态性的时候不会内联,因为虚函数的多态性在运行期,而编译器无法知道运行期调用哪块代码。编译器需要知道所调用的对象是哪个类,这只有在编译器具有实际对象而不是对象的指针或引用时才会发生。

内联函数仅仅省去了函数调用的开销,从而提高函数的执行效率,但每一处的内联函数都要复制代码,会导致程序代码量增加。以下情况不适合使用内联:

  1. 函数体内的代码较长,内联将导致内存消耗代价较高。
  2. 执行函数的时间比调用函数的开销大。

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) {}
}

提示

谨慎使用隐式转换函数。通常,最好选择仅在被显式地调用时才会执行的函数。