目录
1. const修饰成员函数
将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。
1.1 语法格式
在成员函数的声明中使用const关键字的语法格式为:
返回类型 函数名() const;
在成员函数的定义中使用const关键字的语法格式为:
返回类型 类名::函数名() const {
// 函数体
}
1.2 权限放大缩小
const 修饰的权限放大(扩大)或缩小(缩小)取决于成员函数和成员变量的修饰符。
-
成员函数:
- 如果一个非const成员函数被const修饰符修饰,它的权限会缩小,即它不能修改对象的非mutable成员变量,也不能调用非const成员函数。
- 如果一个const成员函数被const修饰符修饰,它的权限保持不变,即它不能修改对象的任何成员变量,也不能调用非const成员函数。
-
成员变量:
- 如果一个非const成员变量被const修饰符修饰,它的权限会缩小,即它只能被const成员函数访问,而非const成员函数不能修改它。
- 如果一个const成员变量被const修饰符修饰,它的权限保持不变,即它不能修改对象的任何成员变量,也不能调用非const成员函数。
class Example {
private:
int data;
const int constData;
mutable int mutableData;
public:
void nonConstFunc() {
data = 10; // 可以修改非const成员变量
constData = 20; // 错误!不能修改const成员变量
mutableData = 30; // 可以修改mutable成员变量
constFunc(); // 可以调用const成员函数
nonConstMemberFunc(); // 可以调用非const成员函数
}
void constFunc() const {
data = 10; // 错误!不能修改非mutable成员变量
constData = 20; // 错误!不能修改const成员变量
mutableData = 30; // 可以修改mutable成员变量
nonConstMemberFunc(); // 错误!不能调用非const成员函数
}
void nonConstMemberFunc() {
// 省略函数体
}
};
1.3 思考
1. const对象可以调用非const成员函数吗?
2. 非const对象可以调用const成员函数吗?
3. const成员函数内可以调用其它的非const成员函数吗?
4. 非const成员函数内可以调用其它的const成员函数吗?
1.4 解答
1. const对象不能调用非const成员函数。
const对象被视为只读对象,它的成员函数不能修改对象的状态。
因此,只有被声明为常成员函数的成员函数才能在const对象上调用。
#include <iostream>
using namespace std;
class Example
{
public:
void nonConstFunc()
{
cout << "Non-const member function." << endl;
}
void constFunc() const
{
cout << "Const member function." << endl;
}
};
int main()
{
const Example obj;
obj.constFunc(); // 可以调用const成员函数
obj.nonConstFunc(); // 错误!不能调用非const成员函数
return 0;
}
注释掉 “ obj.nonConstFunc(); ” 之后:
2. 非const对象可以调用const成员函数。
const成员函数是一种特殊的成员函数,它承诺不修改对象的状态。
因此,无论是非const对象还是const对象,都可以调用const成员函数。
#include <iostream>
using namespace std;
class Example {
public:
void nonConstFunc()
{
cout << "Non-const member function." << endl;
}
void constFunc() const
{
cout << "Const member function." << endl;
}
};
int main()
{
Example obj;
obj.constFunc(); // 可以调用const成员函数
obj.nonConstFunc(); // 可以调用非const成员函数
return 0;
}
3. const成员函数内不可以调用其他非const成员函数。
const成员函数调用非const成员函数,调用该const成员函数的对象已经被设置为const类型,只可以访问不能修改,在用该const成员函数访问其他非const成员函数可能会修改,因此const成员函数不能调用非const成员函数。
#include <iostream>
using namespace std;
class Example
{
public:
void nonConstFunc()
{
cout << "Non-const member function." << endl;
}
void constFunc() const
{
cout << "Const member function." << endl;
nonConstFunc(); // 可以调用非const成员函数
}
};
int main() {
Example obj;
obj.constFunc();
return 0;
}
4. 非const成员函数内可以调用其他的const成员函数。
非const成员函数可以修改对象的状态,因此它在内部调用const成员函数时可以保证对象的状态不被修改。
#include <iostream>
using namespace std;
class Example {
private:
int data;
public:
Example(int val) : data(val)
{}
void nonConstFunc() {
cout << "Non-const member function." << endl;
constFunc(); // 可以调用const成员函数
}
void constFunc() const {
cout << "Const member function." << endl;
cout << "Data: " << data << endl;
}
};
int main()
{
Example obj(10);
obj.nonConstFunc();
return 0;
}
2. 再谈构造函数
2.1 构造函数体赋值
在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值。
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量 的初始化,构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始 化一次,而构造函数体内可以多次赋值。
2.2 初始化列表
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟 一个放在括号中的初始值或表达式。
class Date
{
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
【注意】
1. 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
2. 类中包含以下成员,必须放在初始化列表位置进行初始化:
- 引用成员变量
- const成员变量
- 自定义类型成员(且该类没有默认构造函数时)
3. 尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量, 一定会先使用初始化列表初始化。
4. 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后 次序无关
class A
{
public:
//A(int a = 0, int b = 1)
A(int a, int b)
:_a(a)
{
cout << "A(int a = 0)" << endl;
}
private:
int _a;
};
class Date
{
public:
// 初始化列表是每个成员变量定义初始化的位置
// 能用初始化列表就建议用初始化列表
Date(int year, int month, int day, int& x)
:_year(year)
,_month(month)
,_day(day)
,_n(1)
,_ref(x)
,_aa(1, 2)
,_p((int*)malloc(sizeof(4) * 10))
{
if (_p == nullptr)
{
perror("malloc fail");
}
for (size_t i = 0; i < 10; i++)
{
_p[i] = 0;
}
}
private:
// 声明
int _year;
int _month;
int _day;
// 必须走初始化
const int _n;
int& _ref;
A _aa;
int* _p;
};
int main()
{
// 对象实例化
int x = 10;
Date d1(2024, 1, 31, x);
//A aa(2, 3);
return 0;
}
2.3 explicit关键字
explicit是一个关键字,用于声明构造函数为显示构造函数。
当一个构造函数被声明为explicit时,它不能被用于隐式转换,只能被用于显式地创建对象。这个关键字通常用于防止意外的隐式类型转换。
class MyInt
{
public:
explicit MyInt(int i)
: value(i)
{}
int getValue() const
{
return value;
}
private:
int value;
};
void foo(MyInt mi)
{
int i = mi.getValue();
// ...
}
int main() {
MyInt mi1 = 42; // 错误,不能进行隐式转换
MyInt mi2(42); // 正确,可以显式创建对象
foo(42); // 错误,不能进行隐式转换
foo(MyInt(42)); // 正确,可以显式创建对象
return 0;
}
3. static成员
static是用于修饰变量、函数和类成员的关键字。它可以有不同的作用,具体取决于它应用的位置。
3.1 静态变量
在函数内部使用static修饰的变量称为静态变量,它的生命周期是整个程序运行期间,而不是仅在函数调用时存在。
静态变量在内存中只有一个副本,且在第一次初始化时赋值,并且在程序结束时销毁。静态变量默认为0,除非显式初始化。
void foo()
{
static int count = 0; // 静态变量
count++;
cout << "Count: " << count << endl;
}
int main() {
foo(); // 输出 Count: 1
foo(); // 输出 Count: 2
foo(); // 输出 Count: 3
return 0;
}
3.2 静态函数
在类内部使用static修饰的函数称为静态函数,它与类的实例无关,可以直接通过类名调用,而不需要创建对象。
静态函数只能访问类的静态成员变量和其他静态函数,不能访问非静态成员变量和非静态函数。
class MyClass
{
public:
static void myStaticFunc()
{
cout << "Static function!" << endl;
}
};
int main()
{
MyClass::myStaticFunc(); // 直接调用静态函数
return 0;
}
静态函数的特点:
- 静态函数属于类本身,而不是类的实例。
- 静态函数可以直接通过类名调用,不需要通过对象来调用。
- 静态函数无法访问非静态成员变量和非静态成员函数,因为它们是与对象相关的。
- 静态函数可以访问静态成员变量和静态成员函数。
- 静态函数在类的范围内具有全局可见性,可以在其他源文件中使用类名来调用静态函数。
3.3 静态成员变量
在类内部使用static修饰的成员变量称为静态成员变量,它是类的所有实例共享的,不属于任何特定对象。
静态成员变量在类内部声明,并在类外部进行定义和初始化。定义时需要指定数据类型,并且通常需要提供一个初始值。
声明静态成员变量:
class MyClass {
public:
static int myStaticVar; // 静态成员变量的声明
};
定义和初始化静态成员变量(在类外部):
int MyClass::myStaticVar = 0; // 静态成员变量的定义和初始化
静态成员变量可以像普通成员变量一样访问,可以通过类名和作用域运算符::
,或者通过对象名和点操作符.
来访问。
MyClass::myStaticVar = 5; // 通过类名访问静态成员变量
MyClass obj;
obj.myStaticVar = 10; // 通过对象名访问静态成员变量
静态成员变量的特点:
- 所有的类对象共享同一个静态成员变量。
- 静态成员变量在程序开始时初始化,并在程序结束时销毁。
- 静态成员变量可以被所有对象访问,但只有一个副本。
- 静态成员变量可以被类的所有成员函数访问,包括静态成员函数和非静态成员函数。