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 * p | const 在 * 左边(定值) | 是 | 否 | 常量指针(指针可变,指向的数据不可改) |
char const * p | const 在 * 左边(定值) | 是 | 否 | 常量指针(指针可变,指向的数据不可改) |
char * const p | const 在 * 右边(定向) | 否 | 是 | 指针常量(指针不可变,指向的数据可改) |
const char * const p | 左定值 + 右定向 → 两者都受限 | 否 | 否 | 指向常量的指针常量(指针和指向的数据都不可变) |
char const * const p | 左定值 + 右定向 → 两者都受限 | 否 | 否 | 指向常量的指针常量(指针和指向的数据都不可变) |
(1)
const char *- 含义:
- 指向
const char的指针(常量指针)
- 指向
- 特性:
- 指针:可以改
- 指向的数据:不能改
- 示例:
1
2
3
4
5
6const 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
6const 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
6const char str[] = "Hello";
const char other[] = "World";
char * const p = str;
p = other; // 错误写法:指针本身不能变
*p = 'A'; // 正确写法:可以修改指针所指向的内容
- 含义:
(4)
const char * const p- 含义:
- 指向
const char的const指针(指向常量的指针常量) - 也就是:指针本身不能变,指向的数据也不能变
- 指向
- 特性:
- 指针:不能改
- 指向的数据:不能改
- 示例:
1
2
3
4
5
6
7const 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 char的const指针(指向常量的指针常量) - 也就是:指针本身不能变,指向的数据也不能变
- 指向
- 特性:
- 指针:不能改
- 指向的数据:不能改
- 示例:
1
2
3
4
5
6
7const 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 | struct Student { |
- 在 C 语言中,关键点是:
- 结构体里只能包含成员变量,不能有函数。
- 结构体定义出来后,声明变量时通常需要写
struct关键字。
1 | struct Student s1; // 声明变量时必须带 struct 关键字 |
- 除非使用
typedef关键字进行简化
1 | typedef struct Student { |
C++ 的结构体
在 C++ 中,结构体进行了大幅增强,功能上与类(class)几乎完全等价,只是默认的访问控制权限不同而已。C++ 中的结构体支持面向对象特性,可以包含以下内容:
- 成员变量
- 成员函数
- 构造函数 / 析构函数
- 访问控制符(
public、protected、private) - 继承、多态、虚函数
- 模板
- 基础语法
1 |
|
- C++ 中结构体与类的区别
| 特性 | struct(结构体) | class(类) |
|---|---|---|
| 默认成员访问权限 | public | private |
| 默认继承方式 | 公有继承(public) | 私有继承(private) |
| 其他特性 | 几乎完全相同 | 几乎完全相同 |
C 与 C++ 结构体的区别
| 对比项 | C 结构体 | C++ 结构体 |
|---|---|---|
| 功能定位 | 仅数据集合(面向过程) | 完整对象(面向对象) |
| 成员 | 只能有变量,不能有函数 | 可以有变量、函数、构造函数、析构函数、继承、虚函数、模板等 |
| 访问控制符 | 不支持 | 支持 public / protected / private |
| 继承 | 不支持 | 支持继承 |
| 多态 | 不支持 | 支持(虚函数) |
| 默认访问权限 | 全部 public | 默认 public |
typedef 简化 | 定义后不可以直接使用,需要 typedef 才能省略 struct 关键字 | 定义后可以直接使用,无需 typedef 就可以省略 struct 关键字 |
| 用途 | 数据封装 | 既能封装数据,也能封装行为 |
结构体的使用
本节将演示 C++ 中如何使用结果体,不再涉及 C 语言的结构体。
结构体作为值形参
1 |
|
程序运行输出的结果如下:
1 | id = 1, name = Peter |
结构体作为引用形参
1 |
|
程序运行输出的结果如下:
1 | id = 1, name = Peter |
结果体作为指针形参
1 |
|
程序运行输出的结果如下:
1 | id = 1, name = Peter |
函数特性
| 函数特性 | C++ 标准 | 说明 |
|---|---|---|
| 内联函数(Inline Function) | C++ 98 | inline 用于提示编译器将函数展开;类内定义的成员函数默认 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
3inline 返回类型 函数名(参数列表) {
函数体
}内联函数的使用示例:
1
2
3
4
5
6
7inline 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
3inline int square(int x) {
return x - x;
}
- 例如 Getter/Setter、数学小函数:
- (2) 类的成员函数
- 在类内定义的成员函数,默认都是内联函数:
1
2
3
4
5
6class Point {
int x, y;
public:
int getX() { return x; } // 默认 inline
int getY() { return y; } // 默认 inline
};
- 在类内定义的成员函数,默认都是内联函数:
- (3) 模板函数
- 模板函数和或静态成员函数通常在头文件中定义,结合
inline可以避免重复定义的链接错误:1
2
3
4template <typename T>
inline T max(T a, T b) {
return (a > b) ? a : b;
}
- 模板函数和或静态成员函数通常在头文件中定义,结合
- (1) 小型、简单的函数
内联函数的优缺点:
优点 缺点 减少函数调用开销 可能导致代码膨胀,增大可执行文件 适合小函数、频繁调用 大函数不适合使用 inline,会导致代码膨胀便于在头文件中定义函数 不是强制内联,编译器可能忽略
C++ 11 的增强
在 C++ 11 中,增加了 inline 和 constexpr、模板、类静态成员函数的结合用法,核心作用是减少函数调用开销、便于在头文件中定义小函数。值得一提的是,constexpr 可以看成是一种更严格的内联函数。
未命名参数
未命名参数(也称为省略参数名)的概述:
- 函数声明时,可以只有形参类型,没有形参名称。
- 比如:
int func(int a, int);。 - 第一个参数
int a有名称,可以在函数体中使用。 - 第二个参数只有类型
int,没有名称,所以在函数体中无法访问这个参数。 - 未命名参数不等于默认参数,因此在调用上面的函数时,必须传入两个参数。
未命名参数的常见用途:
- 回调或接口函数中,暂时不使用某个参数:
1
2
3int 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
4template <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可以结合使用,让函数返回类型延迟到函数体或参数类型确定之后。 - 这对模板和泛型编程非常有用。
- 后置返回类型和
- (1) 适合函数声明和函数定义分离
后置返回类型的注意事项:
- 不是必须的,普通函数仍然可以用传统写法。
- 不影响函数调用语法,调用时与普通函数一致。
- 主要在返回类型依赖参数类型或者模板参数的情况下使用。
函数杂合用法
返回 void 的表达式
概述和语法:
- C++ 17 引入了允许
return void_expression;的特性,也称为void return expression或返回 void 的表达式。 - 在 C++ 17 之前,
return expr;中的expr不能是void。 - 从 C++ 17 开始,可以这样写:
1
2
3
4
5
6
7void func1() {
}
void func2() {
return func1(); // func1() 返回 void,可以直接 return
}
- C++ 17 引入了允许
作用和意义:
- 允许直接返回
void表达式,写法更一致、简洁。 - 等价于:
1
2
3
4
5
6
7
8void func1() {
}
void func2() {
func1();
return;
} - 语义上没有额外效果,只是语法糖,方便模板编程或统一返回写法。
- 允许直接返回
注意事项:
- 函数返回类型必须是
void。 - 不能用在有非
void返回值的函数中,比如:1
2
3
4
5
6
7void func1() {
}
int func3() {
return func1(); // 错误写法:因为 func1() 是 void
}
- 函数返回类型必须是
其他常见的函数用法
(1) 没有形参的函数,可以使用以下两种等价写法进行声明:
int func();int func(void);- 两者在 C++ 中的含义相同,均表示 “无参数函数”。
(2) 如果一个函数从未被调用,则它可以只有声明而没有定义,编译和链接都不会报错。但是,如果函数被调用,则必须提供函数定义,否则会产生链接错误。
(3) 普通函数可以被声明多次(通常放在
.h头文件中),但函数定义只能出现一次(通常放在对应的.cpp文件中);多次定义同一个函数将导致链接阶段出现multiple definition错误。
