const
主要有以下作用:
- 修饰变量,说明该变量不可改变;
- 修饰指针,这里分为指向常量的指针(指针常量,pointer to const)和自身是常量的指针(常量指针,const pointer)
1
2
3int a = 0, b = 10;
const int *p1 = &a; // 指针常量,指向的值不可以使用*p修改,*p = 10 错误,可以修改指向的地址
int *const p2 = &a; // 常量指针,指针指向的地址不可改变,p2 = &b 错误,可以修改该地址的变量值 - 修饰引用,指向常量的引用,用于形参类型,既避免了拷贝,又避免了函数对值进行修改
- 类内修饰成员函数,说明该成员函数内不能修改成员变量
1 |
|
需要注意的是,普通对象可以调用普通函数也可以调用常函数,前者优先,如果是常量对象的话,只能调用常函数,如果调用普通函数会导致报错。
进一步地1
2
3
4
5
6
7
8
9
10
11// 函数
void function1(const int Var); // 传递过来的参数在函数内不可变
void function2(const char* Var); // 参数指针所指内容为常量
void function3(char* const Var); // 参数指针为常量
void function4(const int& Var); // 引用参数在函数内为常量
// 没有 const reference,因为引用只是对象的别名,引用不是对象,不能用 const 修饰
// 函数返回值
const int function5(); // 返回一个常数
const int* function6(); // 返回一个指向常量的指针变量,使用:const int *p = function6();
int* const function7(); // 返回一个指向变量的常指针,使用:int* const p = function7();
static
主要有以下作用:
- 修饰普通变量: 修改变量的存储区域和生命周期,使变量存储在静态区域,在main函数运行前就分配了空间,如果有初始值就用初始值初始化它,如果没有初始值系统用默认值初始化它。需要注意的是: 在函数内部定义了一个静态变量,生命周期到程序结束,但是这个变量的作用域仅限于声明它的函数内部。
1
2
3
4
5
6
7
8
9
10
11
12void myFunction() {
static int count = 0; // 静态局部变量
count++;
std::cout << "Count: " << count << std::endl;
}
int main() {
myFunction(); // 输出 Count: 1
myFunction(); // 输出 Count: 2
// 这里无法直接访问 count
return 0;
} - 修饰普通函数: 表明函数的作用范围,仅在定义该函数的文件内才能使用,它的作用域被限制在声明它的文件中,即它变成了一个“内部链接”的函数,只能在当前文件内部访问。在多人开发项目时,为了防止与他人命名空间里的函数重名,可以将函数定位为 static。
1
2
3
4
5
6
7
8// File1.cpp
static void myFunction() {
std::cout << "这是一个静态函数" << std::endl;
}
// File2.cpp
void anotherFunction() {
myFunction(); // 错误,无法访问静态函数
} - 修饰成员变量: 修饰成员变量时该变量将被所有该类的对象共享,而不是每个对象拥有一份副本,而且不需要生成对象就可以访问该成员。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16class MyClass {
public:
static int count; // 静态成员变量
};
int MyClass::count = 0; // 静态成员变量的初始化
int main() {
MyClass obj1;
MyClass obj2;
obj1.count = 5;
std::cout << obj2.count << std::endl; // 输出 5
return 0;
} - 修饰成员函数: 修饰成员函数使得不需要生成对象就可以访问该函数,但是在 static 函数内只能访问 static 成员。
this 指针
this
指针是一个特殊的指针,它指向当前对象的地址。在 C++ 中,每个类的非静态成员函数都有一个隐含的 this
指针,它指向调用该成员函数的对象。
当一个类对象调用成员函数时,编译程序先将对象的地址赋给了 this
指针,然后调用该成员函数,每次成员函数存取数据成员时,都其实是在隐式地使用 this
指针。
this
指针是一个常量指针,被隐含地声明为: ClassName *const this
,这意味着不能给 this
指针赋值,而在 const
成员函数里被声明为 const ClassName* const
最后还需要注意的是,this
并不是一个常规变量,而是个右值,所以不能取得 this
的地址(不能 &this
)
左值和右值(引用)
- 左值(lvalue)
- 左值既能够出现在等号左边,也能出现在等号右边
- 左值可以被赋值,可以作为赋值语句的目标
- 左值是可寻址的变量,有持久性
- 具体来说,变量、对象或者通过解引用获得的指针都属于左值
1
2int x = 5; // x 是左值,因为它代表一个内存位置,可以被赋值
int* ptr = &x; // &x 是左值,因为它是变量 x 的地址
- 右值(rvalue)
- 右值是不能被赋值的表达式,它们代表的是一个数值或者临时值,通常在赋值语句的右侧
- 右值可以是一个常数、一个临时的计算结果或者一个表达式的返回值。
- 右值在使用后就失去意义,因此不能被取地址
1
2int y = 10; // 10 是右值,因为它代表一个数值,不能被赋值
int z = x + y; // x + y 是右值,因为它代表一个临时计算结果
C++ Primer:” 当一个对象被用作右值的时候,用的是对象的值(内容);当对象被用作左值时,用的是对象的身份(在内存中的位置)”
左值引用(lvalue reference)和右值引用(rvalue reference)是C++中引入的两种不同类型的引用。
- 左值引用: 左值引用是最常见的引用类型。它们使用
&
符号声明,并且只能绑定到左值(可以取地址的表达式)。 - 右值引用: 右值引用是在C++11中引入的新特性,用 && 符号表示。它们可以绑定到临时值、表达式结果或具有名称的右值。
1
2
3
4
5
6
7int x = 6; // x是左值,6是右值
int &y = x; // 左值引用,y引用x
int &z1 = x * 6; // 错误,x*6是一个右值
int &&z2 = x * 6; // 正确,右值引用
int &&z3 = x; // 错误,x是一个左值可以引用右值的除了右值引用外还有 const 左值引用,例如
const int &z4 = x * 6;
正确,可以将一个const引用绑定到一个右值。例如std::vector
的push_back
函数就使用了 const 左值引用(void push_back (const value_type& val);
) ,这样能让我们使用v.push_back(1)
这样的代码。
std::move
可以实现将左值转换成右值以实现对左值进行右值引用1
2
3int i = 3, j;
j = std::move(2); // 合法,从一个右值移动数据
j = std::move(i); // 合法,从一个左值移动数据,i的值之后是不确定的。
inline 内联函数
主要有以下特征:
- 相当于把内联函数里面的内容写在调用内联函数处;
- 相当于不用执行进入函数的步骤,直接执行函数体;
- 相当于宏,却比宏多了类型检查,真正具有函数特性;
- 编译器一般不内联包含循环、递归、switch 等复杂操作的内联函数;
- 在类声明中定义的函数,除了虚函数的其他函数都会自动隐式地当成内联函数。
1 | // 一般在类内定义的成员函数会隐式成为inline,而在类外定义的成员函数不会(当然是否内联完全取决于编译器) |
虚函数可以是内联函数吗?
Standard C++: Are “inline virtual” member functions ever actually “inlined”?
Stackoverflow: Can virtual functions be inlined [duplicate]
虚函数可以是内联函数,但是当虚函数表现出多态性的时候不能内联,因为内联是在编译期建议编译器内联,而虚函数的多态性在运行期,编译器无法知道运行期调用哪个代码,因此虚函数表现为多态性的时候不可以内联。
下面是虚函数内联使用例子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
class Base {
public:
inline virtual void who() {
std::cout << "I am Base" << std::endl;
}
virtual ~Base() { }
};
class Derived: public Base {
public:
// // 不写 inline 时会隐式内联
inline void who() {
std::cout << "I am Derived" << std::endl;
}
};
int main() {
Base fa;
fa.who(); // 此处的虚函数 who(),是通过类(Base)的具体对象(b)来调用的,编译期间就能确定了,所以它可以是内联的,但最终是否内联取决于编译器。
// 此处的虚函数是通过指针调用的,呈现多态性,需要在运行时期间才能确定,所以不能为内联。
Base *ptr = new Derived();
ptr->who();
// 因为Base有虚析构函数(virtual ~Base() {}),所以 delete 时,会先调用派生类(Derived)析构函数,再调用基类(Base)析构函数,防止内存泄漏。
delete ptr;
ptr = nullptr;
return 0;
}
具体来说,就是
- 当使用类的对象来调用时,则虚函数可以当做是内联的,因为编译器在编译时就确切知道对象是哪个类的;
- 当使用基类指针或引用来调用虚函数时,它都不能是内联函数,因为调用发生在运行时,是动态绑定的。