C++ 杂记之二从基础到进阶

大纲

结构体

结构体的概念

C 的结构体

在 C 语言中,结构体主要用于将多个不同类型的数据组合成一个整体,属于纯数据封装,不支持面向对象特性。

  • 基础语法
1
2
3
4
5
6
7
struct Student {
int age;
char name[20];
int score;
};

struct Student s1 = {"Tom", 18, 95};
  • 在 C 语言中,关键点是:
    • 结构体里只能包含成员变量,不能有函数。
    • 结构体定义出来后,声明变量时通常需要写 struct 关键字。
1
struct Student s1; // 声明变量时必须带 struct 关键字
  • 除非使用 typedef 关键字进行简化
1
2
3
4
5
6
7
typedef struct Student {
int age;
char name[20];
int score;
} Student;

Student s1; // 可以直接使用

C++ 的结构体

在 C++ 中,结构体进行了大幅增强,功能上与类(class)几乎完全等价,只是默认的访问控制权限不同而已。C++ 中的结构体支持面向对象特性,可以包含以下内容:

  • 成员变量
  • 成员函数
  • 构造函数 / 析构函数
  • 访问控制符(publicprotectedprivate
  • 继承、多态、虚函数
  • 模板

  • 基础语法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>

using namespace std;

struct Student {

private:
string name;
int age;

public:
// 构造函数
Student(string n, int a) : name(n), age(a) {

}

// 成员函数
void show() {
cout << "Name: " << name << ", Age: " << age << endl;
}
};

int main() {
Student s("Tom", 18);
s.show();
return 0;
}
  • C++ 中结构体与类的区别
特性 C++ 的 struct(结构体)C++ 的 class(类)
默认成员访问权限publicprivate
默认继承方式公有继承(public私有继承(private
其他特性几乎完全相同几乎完全相同

C 与 C++ 结构体的区别

对比项 C 语言的结构体 C++ 的结构体
功能定位仅数据集合(面向过程)完整对象(面向对象)
成员只能有变量,不能有函数可以有变量、函数、构造函数、析构函数、继承、虚函数、模板等
访问控制符不支持支持 public / protected / private
继承不支持支持继承
多态不支持支持(虚函数)
默认访问权限全部 public默认 public
typedef 简化定义后不可以直接使用,需要 typedef 才能省略 struct 关键字定义后可以直接使用,无需 typedef 就可以省略 struct 关键字
用途数据封装既能封装数据,也能封装行为

结构体的使用

本节将演示 C++ 中如何使用结果体,不再涉及 C 语言的结构体。

结构体作为值形参

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>
#include <cstring>

using namespace std;

struct student {
int id;
char name[50];

void show() {
cout << "id = " << id << ", name = " << name << endl;
}
};

// 结构体作为值形参(效率低,会发生数据拷贝)
void func(student stu) {
stu.id = 2;
strcpy(stu.name, "Jim");


}

int main() {
student student1;
student1.id = 1;
strcpy(student1.name, "Peter");
student1.show();

func(student1);
student1.show();
}

程序运行输出的结果如下:

1
2
id = 1, name = Peter
id = 1, name = Peter

结构体作为引用形参

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>
#include <cstring>

using namespace std;

struct student {
int id;
char name[50];

void show() {
cout << "id = " << id << ", name = " << name << endl;
}
};

// 结构体作为引用形参(效率高,不会发生数据拷贝)
void func(student &stu) {
stu.id = 2;
strcpy(stu.name, "Jim");
}

int main() {
student student1;
student1.id = 1;
strcpy(student1.name, "Peter");
student1.show();

func(student1);
student1.show();
}

程序运行输出的结果如下:

1
2
id = 1, name = Peter
id = 2, name = Jim

结果体作为指针形参

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>
#include <cstring>

using namespace std;

struct student {
int id;
char name[50];

void show() {
cout << "id = " << id << ", name = " << name << endl;
}
};

// 结构体作为指针形参(效率高,不会发生数据拷贝)
void func(student *stu) {
stu->id = 2;
strcpy(stu->name, "Jim");
}

int main() {
student student1;
student1.id = 1;
strcpy(student1.name, "Peter");
student1.show();

func(&student1);
student1.show();
}

程序运行输出的结果如下:

1
2
id = 1, name = Peter
id = 2, name = Jim

函数特性

函数特性 C++ 标准说明
内联函数(Inline Function)C++ 98inline 用于提示编译器将函数展开;类内定义的成员函数默认 inline
默认参数(Default Arguments)C++ 98 允许为函数参数指定默认值;默认参数只能在函数声明中指定,且必须从右向左连续出现。
未命名参数(Unnamed Parameter)C++ 98 参数可以只写类型不写名称,但函数体中无法使用该参数;常用于函数声明或不使用某参数的场景。
后置返回类型(Trailing Return Type)C++ 11 使用 auto func(...) -> type 语法,使返回类型可以依赖参数类型,尤其适用于模板和复杂类型。
返回 void 的表达式形式(return void_expression)C++ 17 允许 return func();,其中 func() 返回 void;C++ 17 才允许 return 语句中含 void 表达式。

内联函数

  • 内联函数的概述:

    • 定义:
      • 内联函数是提示编译器将函数调用直接展开到调用处的函数,而不是通过函数调用机制(压栈、跳转等)执行。
    • 作用:
      • 减少函数调用开销,提高性能(尤其是小函数、频繁调用的函数)。
      • 便于在头文件中定义函数,实现模板和类成员函数的编译。
    • 注意:
      • inline 是只是给编译器建议(不是强制内联),是否采用该建议由编译器决定,编译器可能会忽略建议。
      • inline 不适合大函数,否则可能会导致代码膨胀。
      • 在类内定义的成员函数,默认都是内联函数。
  • 内联函数的语法:

    1
    2
    3
    inline 返回类型 函数名(参数列表) {
    函数体
    }
  • 内联函数的使用示例:

    1
    2
    3
    4
    5
    6
    7
    inline int add(int a, int b) {
    return a + b;
    }

    int main() {
    int sum = add(3, 4); // 调用 add() 函数时,编译器可能会直接将 return a+b; 展开
    }
  • 内联函数的适用场景:

    • (1) 小型、简单的函数
      • 例如 Getter/Setter、数学小函数:
        1
        2
        3
        inline int square(int x) { 
        return x - x;
        }
    • (2) 类的成员函数
      • 在类内定义的成员函数,默认都是内联函数:
        1
        2
        3
        4
        5
        6
        class Point {
        int x, y;
        public:
        int getX() { return x; } // 默认 inline
        int getY() { return y; } // 默认 inline
        };
    • (3) 模板函数
      • 模板函数和或静态成员函数通常在头文件中定义,结合 inline 可以避免重复定义的链接错误:
        1
        2
        3
        4
        template <typename T>
        inline T max(T a, T b) {
        return (a > b) ? a : b;
        }
  • 内联函数的优缺点:

    优点缺点
    减少函数调用开销可能导致代码膨胀,增大可执行文件
    适合小函数、频繁调用大函数不适合使用 inline,会导致代码膨胀
    便于在头文件中定义函数不是强制内联,编译器可能忽略

C++ 11 的增强

在 C++ 11 中,增加了 inlineconstexpr、模板、类静态成员函数的结合用法,核心作用是减少函数调用开销、便于在头文件中定义小函数。值得一提的是,constexpr 可以看成是一种更严格的内联函数。

默认参数

  • 默认参数只能在函数声明中指定

    • 默认参数只能出现在函数的声明(通常在头文件中),不能在函数定义中再次指定。
    • 如果函数没有单独的声明,而是直接给出定义,则可以在该定义中指定默认参数。
  • 默认参数必须从右向左连续指定

    • 在具有多个参数的函数中,一旦某个参数被指定为默认参数,则该参数右边的所有参数都必须具有默认值。
    • 不允许在默认参数之后再出现非默认参数。
1
2
3
4
5
6
7
// 正确写法:默认参数在函数声明中指定
void func(int a, int b = 10, int c = 20);

// 函数定义中不可以再写默认参数
void func(int a, int b, int c) {

}
1
2
// 错误写法:默认参数后面还有非默认参数
void func(int a = 1, int b); // 编译出错
1
2
// 正确写法:从右向左连续指定默认参数
void func(int a, int b = 2, int c = 3);

未命名参数

  • 未命名参数(也称为省略参数名)的概述:

    • 函数声明时,可以只有形参类型,没有形参名称。
    • 比如:int func(int a, int);
    • 第一个参数 int a 有名称,可以在函数体中使用。
    • 第二个参数只有类型 int,没有名称,所以在函数体中无法访问这个参数。
    • 未命名参数不等于默认参数,因此在调用上面的函数时,必须传入两个参数。
  • 未命名参数的常见用途:

    • 回调或接口函数中,暂时不使用某个参数:
      1
      2
      3
      int func(int a, int) {
      return a * 2; // 第二个参数暂时不使用
      }
    • 声明函数而不定义时:通常在头文件里只给类型,名称可省略:
      1
      int func(int a, int); // 只声明,不需要第二个参数名
  • 未命名参数的注意事项:

    • 在函数声明中省略参数名是合法的。
    • 在函数定义中省略参数名也是合法的,但函数体内无法访问该参数。
    • 未命名参数不等于默认参数,没有默认值的参数即使未命名,也必须在调用时提供。

后置返回类型

  • 后置返回类型的语法:

    1
    auto func(参数列表) -> 返回类型;
  • 后置返回类型的使用示例:

    1
    2
    3
    4
    5
    6
    7
    // 函数声明
    auto func(int a, int b) -> int;

    // 函数定义
    auto func(int a, int b) -> int {
    return a + b;
    }
    • auto 出现在函数名前,表示编译器需要看 -> 后面的类型。
    • -> int 表示函数返回 int 类型。
    • 函数调用方式与普通函数一样:func(1, 2)
  • 后置返回类型的用途:

    • (1) 适合函数声明和函数定义分离
      • 传统写法有时类型太复杂:
        1
        std::map<std::string, std::vector<int>> func(int a, int b);
      • 用后置返回类型更清晰:
        1
        auto func(int a, int b) -> std::map<std::string, std::vector<int>>;
    • (2) 解决依赖于模板参数或复杂类型的问题
      • 在模板或泛型函数中,返回类型可能依赖于参数类型:
        1
        2
        3
        4
        template <typename T, typename U>
        auto add(T a, U b) -> decltype(a + b) {
        return a + b;
        }
      • decltype(a + b) 会根据输入类型自动推导返回类型。
      • 如果用传统方式定义函数:
        1
        T add(T a, U b); // 无法直接这样写,因为返回类型依赖参数类型
      • 编译会报错,因为返回类型在参数类型声明之前无法确定。
      • 后置返回类型解决了返回类型依赖参数类型的问题。
    • (3) 与 auto 结合,支持更灵活的类型推导
      • 后置返回类型和 auto 可以结合使用,让函数返回类型延迟到函数体或参数类型确定之后。
      • 这对模板和泛型编程非常有用。
  • 后置返回类型的注意事项:

    • 不是必须的,普通函数仍然可以用传统写法。
    • 不影响函数调用语法,调用时与普通函数一致。
    • 主要在返回类型依赖参数类型或者模板参数的情况下使用。

函数杂合用法

返回 void 的表达式

  • 概述和语法:

    • C++ 17 引入了允许 return void_expression; 的特性,也称为 void return expression返回 void 的表达式
    • 在 C++ 17 之前,return expr; 中的 expr 不能是 void
    • 从 C++ 17 开始,可以这样写:
      1
      2
      3
      4
      5
      6
      7
      void func1() {

      }

      void func2() {
      return func1(); // func1() 返回 void,可以直接 return
      }
  • 作用和意义:

    • 允许直接返回 void 表达式,写法更一致、简洁。
    • 等价于:
      1
      2
      3
      4
      5
      6
      7
      8
      void func1() {

      }

      void func2() {
      func1();
      return;
      }
    • 语义上没有额外效果,只是语法糖,方便模板编程或统一返回写法。
  • 注意事项:

    • 函数返回类型必须是 void
    • 不能用在有非 void 返回值的函数中,比如:
      1
      2
      3
      4
      5
      6
      7
      void func1() {

      }

      int func3() {
      return func1(); // 错误写法:因为 func1() 是 void
      }

其他常见的函数用法

  • (1) 没有形参的函数,可以使用以下两种等价写法进行声明:

    • int func();
    • int func(void);
    • 两者在 C++ 中的含义相同,均表示 “无参数函数”。
  • (2) 如果一个函数从未被调用,则它可以只有声明而没有定义,编译和链接都不会报错。但是,如果函数被调用,则必须提供函数定义,否则会产生链接错误。

  • (3) 普通函数可以被声明多次(通常放在 .h 头文件中),但函数定义只能出现一次(通常放在对应的 .cpp 文件中);多次定义同一个函数将导致链接阶段出现 multiple definition 错误。

类型转换

类型转换的分类

在 C++ 中,类型转换分为隐式类型转换和显式类型转换,其中显式类型转换有以下 4 种类型:

  • static_cast

    • 静态类型转换,用于如 int 转换成 char
    • static_cast 在编译期进行类型检查,只保证转换合法性,不保证运行时安全性。
    • static_cast 可以用于类层次结构中基类(父类)和派生类(子类)之间指针或者引用的转换。
      • 进行上行转换(将派生类的指针或者引用转换成基类表示)是安全的。
      • 进行下行转换(将基类的指针或者引用转换成派生类表示),由于没有运行时的动态类型检查,所以是不安全的。
    • static_cast 可以用于基本数据类型之间的转换,比如将 int 转化成 char,或者将 char 转换成 int,这种类型转换的安全性需要开发人员来保证。
    • static_cast 只能用于存在类型关系的指针转换(比如继承层次或 void *),不能用于无关类型指针之间的转换(比如 int * 转换为 dubbo *,或者 float * 转换为 dubbo *)。
  • dynamic_cast

    • 动态类型转换,用于如父类和子类之间的多态类型转换。
    • dynamic_cast 在运行期会基于 RTTI 进行类型检查,但只能用于多态类型(存在虚函数的继承体系中),能保证下行转换的安全性。
    • dynamic_cast 可以用于类层次结构中的上行转换和下行转换,但是不支持基本数据类型的转换。
    • 在类层次结构中进行上行转换(将派生类的指针或者引用转换成基类表示)时,dynamic_caststatic_cast 的效果一样。
    • 在类层次结构中进行下行转换(将基类的指针或者引用转换成派生类表示)时,dynamic_cast 具有运行时类型检查的功能,比 static_cast 更安全。
  • const_cast

    • 常量类型转换,用于赋予或者去除类型的 const 只读属性。
    • 常量指针被转换成非常量指针后,仍然指向原来的对象,反之亦然。
    • 常量引用被转换成非常量引用后,仍然指向原来的对象,反之亦然。
    • 不能直接对非指针和非引用的类型使用 const_cast 操作符去直接赋予或者去除它的 const 只读属性。
  • reinterpreter_cast

    • 重新解释类型(强制类型转换)
    • reinterpret_cast 用于不同类型之间进行强制类型转换,这是最不安全的一种类型转换机制,最可能出现问题,极少使用,极其危险。
    • reinterpret_cast 可以将一种数据类型强制转换成另一种数据类型,比如可以将一个指针转换成整数,也可以将整数转换成指针。

特别注意

在 C++ 的四种显式类型转换中,static_castconst_cast 会在编译期进行类型检查;reinterpret_cast 仅在编译期会做最基本的语法检查,不进行类型安全检查;dynamic_cast 会在运行期基于 RTTI 进行类型检查(仅适用于多态类型)。

类型转换的语法

  • C 语言风格的强制类型转换(Type Cast)很简单,不管什么类型的转换,语法都是:TYPE b = (TYPE) a

  • C++ 风格的显式类型转换,提供了 4 种显式类型转换操作符来应对不同的应用场景

    • TYPE b = const_cast<TYPE> (a)
    • TYPE b = static_cast<TYPE> (a)
    • TYPE b = dynamic_cast<TYPE> (a)
    • TYPE b = reinterpreter_cast<TYPE> (a)

类型转换的使用

  • 基类和派生类(包含虚函数)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <iostream>

using namespace std;

class Father {

public:
Father(string name, int age) {
this->m_Name = name;
this->m_Age = age;
}

// 虚函数
virtual void print() {
cout << endl << "name = " << this->m_Name << ", age = " << this->m_Age << endl;
}

protected:
string m_Name;
int m_Age;

};

class Son : public Father {

public:
Son(string name, int age, string hobby) : Father(name, age) {
this->m_Name = name;
this->m_Age = age;
this->m_hobby = hobby;
}

void print() override {
cout << endl << "name = " << this->m_Name << ", age = " << this->m_Age << ", hobby = " << m_hobby << endl;
}

private:
string m_hobby;

};
  • 隐式类型转换
1
2
3
4
void test1() {
int m = 3 + 35.63;
cout << "m = " << m << endl;
}
  • 显式类型转换 - static_cast(静态类型转换)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void test2() {
int k = 5 % static_cast<int>(3.2);
cout << "k = " << k << endl;

int i = 10;
int *p = &i;
void *q = p; // void * 可以指向任何指针类型(也称为万能指针)
int *n = static_cast<int *>(q);

Father father("Father", 60);
Son son("Son", 25, "Game");

// 上行转换(将派生类的指针转换成基类表示),是安全的
Son *son1 = new Son("Son", 25, "Game");
Father *father1 = static_cast<Father *>(son1);
father1->print(); // 发生多态,调用派生类的 print() 函数

// 下行转换(将基类的指针转换成派生类表示),是不安全的(编译通过,但运行可能出错)
Father *father2 = new Father("Father", 60);
Son *son2 = static_cast<Son *>(father2);
son2->print(); // 调用基类的 print() 函数
}
  • 显式类型转换 - dynamic_cast(动态类型转换)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void test3() {
Father father("Father", 60);
Son son("Son", 25, "Game");

// 上行转换(将派生类的引用转换成基类表示),是安全的
Father &father1 = dynamic_cast<Father &>(son);
father1.print(); // 发生多态,调用派生类的 print() 函数

// 下行转换(将基类的引用转换成派生类表示),是不安全的,无法进行类型转换(编译通过,但运行可能出错)
// Son &son2 = dynamic_cast<Son &>(father);
// son2.print();

// 上行转换(将派生类的指针转换成基类表示),是安全的
Son *son1 = new Son("Son", 25, "Game");
Father *father2 = dynamic_cast<Father *>(son1);
father2->print(); // 发生多态,调用派生类的 print() 函数

// 下行转换(将基类的指针转换成派生类表示),是不安全的,无法进行类型转换(编译通过,但运行可能出错)
Father *father3 = new Father("Father", 60);
// Son *son2 = dynamic_cast<Son *>(father2);
// son2->print();
}
  • 显式类型转换 - const_cast(常量类型转换)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void test4() {
const int *p = nullptr;
// 常量指针被转换成非常量指针
int *newP = const_cast<int *>(p);

int *p2 = nullptr;
// 非常量指针被转换成常量指针
const int *newP2 = const_cast<const int *> (p2);

int age = 20;
const int &ageRef = age;
// 常量引用被转换成非常量引用
int &ageRef2 = const_cast<int &>(ageRef);

int num = 10;
int &numRef = num;
// 非常量引用被转换成常量引用
const int &numRef2 = const_cast<const int &>(numRef);

const int a = 10;
// 不能直接对非指针和非引用的变量使用 const_cast 操作符去直接赋予或者去除它的 const 只读属性
// int a = const_cast<int>(a);
}
  • 显式类型转换 - reinterpret_cast(重新解释类型)
1
2
3
4
5
void test5() {
int a = 10;
// 将整数强制转换成指针(不安全,非常危险)
int *p = reinterpret_cast<int * >(a);
}

const 关键字

const 针对指针的不同写法

写法含义(读法)什么是常量类型分类
const char * p指向 const char 的指针指针所指向的对象是常量常量指针(pointer to const)
char const * p指向 const char 的指针指针所指向的对象是常量常量指针(pointer to const)
char * const p指针 p 本身是 const,但可以改指向的内容指针本身是常量指针常量(const pointer)
const char * const p指向 const char 的指针常量指针本身、指向的数据都是常量指向常量的指针常量(const pointer to const)
char const * const p指向 const char 的指针常量指针本身、指向的数据都是常量指向常量的指针常量(const pointer to const)
写法口诀判断(左定值,右定向)指针是否可变指向的数据是否可变结果
const char * pconst 在 * 左边(定值)常量指针(指针可变,指向的数据不可改)
char const * pconst 在 * 左边(定值)常量指针(指针可变,指向的数据不可改)
char * const pconst 在 * 右边(定向)指针常量(指针不可变,指向的数据可改)
const char * const p左定值 + 右定向 → 两者都受限指向常量的指针常量(指针和指向的数据都不可变)
char const * const p左定值 + 右定向 → 两者都受限指向常量的指针常量(指针和指向的数据都不可变)

  • (1) const char *

    • 含义:
      • 指向 const char 的指针(常量指针)
    • 特性:
      • 指针:可以改
      • 指向的数据:不能改
    • 示例:
      1
      2
      3
      4
      5
      6
      const char str[]   = "Hello";
      const char other[] = "World";

      const char *p = str;
      p = other; // 正确写法:指针可变
      *p = 'A'; // 错误写法:不能修改指针所指向的内容
    • 注意:
      • char const * 表示的意义完全一致const 放在类型前或后含义都相同),只是写法不同
  • (2) char const *

    • 含义:
      • 指向 const char 的指针(常量指针)
    • 特性:
      • 指针:可以改
      • 指向的数据:不能改
    • 示例:
      1
      2
      3
      4
      5
      6
      const char str[]   = "Hello";
      const char other[] = "World";

      char const *p = str;
      p = other; // 正确写法:指针可变
      *p = 'A'; // 错误写法:不能修改指针所指向的内容
    • 注意:
      • const char * 表示的意义完全一致const 放在类型前或后含义都相同),只是写法不同
  • (3) char * const

    • 含义:
      • const 指针(指针常量)指向 char
    • 特性:
      • 指针:不能改
      • 指向的数据:可以改
    • 示例:
      1
      2
      3
      4
      5
      6
      const char str[]   = "Hello";
      const char other[] = "World";

      char * const p = str;
      p = other; // 错误写法:指针本身不能变
      *p = 'A'; // 正确写法:可以修改指针所指向的内容
  • (4) const char * const p

    • 含义:
      • 指向 const charconst 指针(指向常量的指针常量)
      • 也就是:指针本身不能变,指向的数据也不能变
    • 特性:
      • 指针:不能改
      • 指向的数据:不能改
    • 示例:
      1
      2
      3
      4
      5
      6
      7
      const char str[]   = "Hello";
      const char other[] = "World";

      const char * const p = str;
      p = other; // 错误写法:指针 p 是 const,不可改变
      *p = 'A'; // 错误写法:指向的数据是 const,不可修改
      char c = p[0]; // 正确写法:可以读取数据
    • 注意:
      • char const * const p 表示的意义完全一致const 放在类型前或后含义都相同),只是写法不同
  • (5) char const * const p

    • 含义:
      • 指向 const charconst 指针(指向常量的指针常量)
      • 也就是:指针本身不能变,指向的数据也不能变
    • 特性:
      • 指针:不能改
      • 指向的数据:不能改
    • 示例:
      1
      2
      3
      4
      5
      6
      7
      const char str[]   = "Hello";
      const char other[] = "World";

      char const * const p = str;
      p = other; // 错误写法:指针 p 是 const,不可改变
      *p = 'A'; // 错误写法:指向的数据是 const,不可修改
      char c = p[1]; // 正确写法:可以读取数据
    • 注意:
      • const char * const p 表示的意义完全一致const 放在类型前或后含义都相同),只是写法不同

const 针对成员函数的使用

  • const 成员函数:

    • 不论是 const 对象,还是 非 const 对象,都可以调用 const 成员函数,因为该函数承诺不会修改对象的状态。
  • const 成员函数:

    • 只能被非 const 对象调用,不能被 const 对象调用,因为该函数可能修改对象的状态。
  • 非成员函数(普通函数):

    • 不能使用成员函数形式的 const 修饰符(即函数末尾的 const),但可以通过 const 参数、const 返回值等方式实现只读语义。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>

class Counter {
public:
Counter(int v) : value(v) {}

// const 成员函数
int get() const {
return value;
}

// 非 const 成员函数
void increment() {
++value;
}

private:
int value;
};

int main() {
// 非 const 对象
Counter a(10);

// const 对象
const Counter b(20);

// const 成员函数:两者都可以调用
std::cout << a.get() << std::endl;
std::cout << b.get() << std::endl;

// 正确写法(编译通过),非 const 成员函数,只能由非 const 对象调用
a.increment();

// 错误写法(编译失败),非 const 成员函数,不能被 const 对象调用
// b.increment();

return 0;
}

  • 成员函数末尾 const 的本质
1
int get() const;
  • 等价于:
1
int get(const Counter* this);
  • 而非 const 成员函数是:
1
void increment();
  • 等价于:
1
void increment(Counter* this);
  • this 指针是否为 const,决定了函数能否被 const 对象调用