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

大纲

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 放在类型前或后含义都相同),只是写法不同

结构体

结构体的概念

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++ 中结构体与类的区别
特性struct(结构体)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
未命名参数(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 可以看成是一种更严格的内联函数。

未命名参数

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

    • 函数声明时,可以只有形参类型,没有形参名称。
    • 比如: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 错误。