目录
- [1. 命名空间](#1. 命名空间)
- [2. 缺省参数(默认参数)](#2. 缺省参数(默认参数))
- [3. 函数重载](#3. 函数重载)
- [4. const 引用](#4. const 引用)
-
- [4.1 权限规则](#4.1 权限规则)
- 内联函数
1. 命名空间
命名空间的使用主要有三种方式:
- 指定命名空间访问 :在成员前显式加上命名空间和作用域解析运算符。
例如:std::rand; - 使用
using声明展开命名空间中的某个成员 :将特定成员引入当前作用域,之后使用该成员时无需再写命名空间。
例如:using std::cout;之后,cout前面就不需要再写std::了。 - 使用
using指令展开命名空间中的全部成员 :将整个命名空间的所有成员引入当前作用域。
例如:using namespace std;
2. 缺省参数(默认参数)
函数的参数可以设置默认值,但默认参数必须从右向左连续设置。调用时,只能从左向右依次省略参数。
- 正确示例:
Func(1, 2, 3)(假设三个参数都有默认值,这里提供了全部实参) - 错误示例:
Func(, 2, 3)(不能跳过第一个参数而只提供后面两个)
3. 函数重载
在同一作用域内,可以定义多个同名函数,但要求它们的形参列表不同 (参数个数不同或参数类型不同)。函数重载与返回值类型无关 。
例如:
cpp
void swap(int* p1);
void swap(double* p1);
4. const 引用
4.1 权限规则
可以定义一个 const 引用来引用一个 const 对象,此时引用也必须是 const 的。
核心结论:在引用(或指针)的传递过程中,对原对象的访问权限可以缩小(即增加 const 限制),但不能放大(即去掉 const 限制)。
-
权限不变 (引用 const 对象,使用 const 引用):
cppconst int x = 0; const int &r = x; // 正确:权限相同 -
权限缩小 (引用非 const 对象,使用 const 引用):
cppint x = 0; const int &r2 = x; // 正确:权限缩小(只读引用可引用可读可写对象) -
指针和引用同样遵循权限规则 :
cppint z = 10; int *p3 = &z; const int *p4 = p3; // 正确:权限缩小(从可修改指针变为只读指针) -
错误示例(试图放大权限) :
cppconst int y = 1; const int *p1 = &y; int *p2 = p1; // 错误:不能将 const int* 赋值给 int*(试图去掉 const,放大权限)
此外,对于临时变量(右值)的引用也需要注意常性。临时表达式(如 x * 10)本身具有常性(右值),因此必须用 const 引用(或右值引用)来绑定。
例如:
cpp
int x = 5;
const int &r4 = x * 10; // 正确:const 引用可以绑定到临时表达式
// int &r5 = x * 10; // 错误:非 const 引用不能绑定到右值
规则总结 :当表达式的结果是临时量(右值)时,它本身具有"常性",因此左侧的引用也必须用 const 修饰(或使用右值引用 &&),以保持权限的一致。
内联函数
在 C++ 中,常规函数调用会涉及一系列开销:调用指令(call)需要跳转到函数的虚拟地址,执行函数体代码,然后返回。如果函数体代码量很小但被频繁调用,这种调用开销在性能上就显得不够理想。
内联函数(inline function) 的设计目的正是为了减少这种小函数频繁调用的开销。编译器在编译时,可能会将内联函数的代码直接"展开"插入到每个调用点,从而省去了函数调用的跳转、参数压栈、栈帧创建等操作。
关键特性:
- 建议性 :
inline关键字只是向编译器提出内联展开的建议,编译器会根据函数复杂度、调用频率等因素自行决定是否真正内联。过于复杂的函数(如包含循环、递归)通常不会被内联。 - 适用场景:代码简短、逻辑简单、且被频繁调用的函数适合声明为内联函数。
- 定义与声明 :内联函数通常定义在头文件 中,并且要求定义与声明不分离(即不能只在头文件中声明,在源文件中定义)。这是因为编译器需要在每个调用点看到函数的完整定义才能进行内联展开。
代码示例:
cpp
inline int Add(int x, int y)
{
int ret = x + y;
return ret;
}
在上面的例子中,如果编译器决定内联 Add 函数,那么在调用 Add(a, b) 的地方,可能会被直接替换为 a + b 的求值操作。# 类和对象
javascript
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class Date
{
public:
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
// 这?只是声明,没有开空间
int _year;
int _month;
int _day;
};
int main()
{
// Date类实例化出对象d1和d2
Date d1;
Date d2;
d1.Init(2024, 3, 31);
d1.Print();
d2.Init(2024, 7, 5);
d2.Print();
return 0;
}
对象大小
在 C++ 中,类的对象(或结构体)在内存中的大小遵循与 C 语言结构体相同的内存对齐规则。理解这些规则对于优化内存布局和避免潜在的性能问题至关重要。
内存对齐规则:
- 起始地址:第一个成员变量存储在相对于对象起始地址偏移量为 0 的位置。
- 成员对齐 :其他每个成员变量需要存储在其对齐数(alignment) 的整数倍地址处。
- 对齐数 = min(编译器默认对齐数,该成员自身大小)。
- 在 Visual Studio 中,默认对齐数通常为 8。
- 整体大小 :整个对象(或结构体)的总大小必须是所有成员中最大对齐数的整数倍。
- 嵌套结构体/类 :如果成员中包含另一个结构体或类(嵌套),则该嵌套对象需要对齐到其自身所有成员中最大对齐数的整数倍地址处。整个外层对象的总大小,则是所有对齐数(包括嵌套对象内部的最大对齐数)中最大值的整数倍。
示例分析 :
以上文 Date 类为例,它包含三个 int 类型成员(_year, _month, _day)。
int类型在 32/64 位系统中通常占 4 字节。int的对齐数 = min(编译器默认对齐数 8, 成员大小 4) = 4。- 因此,每个
int成员都必须存储在 4 的整数倍地址上。 - 按照规则计算后,
Date类对象的总大小为 12 字节 (三个int连续存放,且总大小 12 是最大对齐数 4 的整数倍)。## this 指针
当我们调用同一个类的不同对象的成员函数时(例如 d1.Print() 和 d2.Print()),这些函数使用的是相同的代码,那么编译器如何知道当前操作的是哪个对象的数据呢?答案就是 this 指针。
this 指针的原理 :
每个非静态成员函数在调用时,编译器都会隐式地传递一个指向当前调用对象的指针作为第一个参数,这个指针就是 this。因此,d1.Print() 在底层类似于 Print(&d1),d2.Print() 类似于 Print(&d2)。在成员函数内部,所有对成员变量的访问(例如 _year)实际上都是通过 this 指针进行的,即 this->_year。
关键特性:
- 隐式传递与使用 :
this指针的传递和接收由编译器自动完成,程序员不能在函数调用的实参和函数定义的形参位置显式地写出this。但在成员函数体内部,可以显式地使用this来访问成员或区分局部变量。 - 存储位置 :
this指针本身是一个局部指针变量,通常存储在函数的栈帧中。 - 常量性 :在非
const成员函数中,this的类型是ClassName*;在const成员函数中,this的类型是const ClassName*,这保证了在const成员函数内不能修改对象的数据成员。
代码视角 :
对于 Date 类的 Print 函数,其内部视角可以理解为:
cpp
void Print(Date* this) // 编译器隐式添加的形参
{
cout << this->_year << "/" << this->_month << "/" << this->_day << endl;
}
调用 d1.Print() 时,this 被设置为 &d1,从而正确访问 d1 的成员数据。