目录
目录
const基本介绍
const
是 C++ 中的修饰符,用于声明常量或表示不可修改的对象、函数或成员函数。
我们已经了解了const基本用法,我们先进行简单的回顾:
-
声明常量变量:使用
const
关键字来声明常量,一旦声明为常量,其值就不能被修改。例如:const int MAX_VALUE = 100;
-
const修饰指针:
a).
const
修饰指针所指向的值不可被修改:
int x = 5;
const int* ptr = &x;
*ptr = 10; // 错误:试图修改 const 指针所指向的值
int y = 10;
ptr = &y; // 正确:const 指针本身可以被修改,指向不同的地址
b).const
修饰指针本身不可被修改:
int x = 5;
int* const ptr = &x;
ptr = nullptr; // 错误:试图修改 const 指针本身的值
int y = 10;
*ptr = y; // 正确:可以通过 const 指针修改指向的值
const修饰指针具体讲解:C语言:const函数修饰指针_const函数指针-CSDN博客
3.声明常量引用:使用 const
关键字修饰引用,表示引用的值不能被修改。例如:
const int& ref = someVariable;
错误例子如下:
int x = 5;
const int& ref = x;
ref = 10; // 错误:试图修改 const 引用的值
正文
const char &getCharRef(const SomeClass ¶m) const
{
// 返回一个 const char& 类型的常量引用
return someChar;
}
声明一个名为 constCharRef 的函数,该函数接收一个 const 类型的参数,
并返回一个 const char& 类型的常量引用。
同时,这个函数本身也是一个 const 成员函数
示例中 MyClass 是一个类名,
SomeClass 是作为参数传递给函数的某个类型,
someChar 是一个 const 类型的字符变量、字符串字面值或者对象中的成员变量
看以上代码,在三个位置(文中称做前中后)都用const进行了修饰,那这三个点具体意义是什么呢?
前:
该const是修饰返回值类型
const int getVal()
{
// 在这里实现函数的逻辑
someVal = 10; // 修改对象中的成员变量
return someVal;
}
const
关键字用于修饰函数返回值前面的类型,表示函数返回的是一个常量值。这意味着,一旦 getVal()
函数返回一个常量值之后,返回的值就不能被修改,否则编译器会报错。
此处的 const
关键字并没有修饰函数本身。因此,即使在函数内部修改了对象中的成员变量,函数返回的仍然是一个常量值,不能被修改
中:
void Add(const int val, const int constant)
{
int result = val + constant;
cout << "Result: " << result << endl;
}
声明常量参数,该const则是为了保护数据,防止val和constant的值被修改
后:
声明常量成员函数:在类中声明成员函数时,使用 const
关键字修饰函数,表示该函数不会修改类的成员变量。该函数不会修改被隐式访问的对象,只能访问类的常量成员变量或调用其他常量成员函数
class MyClass
{
public:
int getValue() const;
private:
int value;
};
int MyClass::getValue() const
{
return value; // 只能读取成员变量的值
}
const char& operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
函数声明中的 const
关键字修饰的是 this
指针,即表示该成员函数对应的实例对象在函数内部是只读的,不能修改对象的成员变量。因此,即使在函数内部尝试修改 _str
字符串,编译器也不会报错,但这样的操作违反了 const
限定的约定,也会导致代码运行时出现不可预期的问题。
-
在函数中断言
_size
大于pos
,确保访问_str
数组元素时不会越界。 -
约束函数不能修改对象的成员变量,避免对对象状态造成破坏或影响调用者对对象状态的判断。
错误例子:
class MyString
{
public:
MyString(const char *str)
{
_str = new char[std::strlen(str) + 1];
std::strcpy(_str, str);
}
// 错误的 const 成员函数
char &operator[](size_t pos) const
{
return _str[pos]; // 修改了成员变量值
}
private:
char *_str;
};
int main()
{
const MyString str("Hello");
str[0] = 'h'; // 错误:试图修改常量对象的值
return 0;
}
拷贝构造使用const
使用拷贝构造时,最用const进行修饰,以防权限的放大
正确写法:
Date(const Date &d)
{
_year = d.year;
_month = d.month;
_year = d.year;
}
不用const修饰:
class Date
{
public:
Date(int year, int month, int day) : _year(year), _month(month), _day(day) {}
// 错误的拷贝构造函数,没有使用 const 修饰符
Date(Date &d)
{
_year = d._year + 1;
_month = d._month;
_day = d._day;
}
void PrintDate()
{
std::cout << "Year: " << _year << ", Month: " << _month << ", Day: " << _day << std::endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2023, 11, 21);
d1.PrintDate(); // 输出:Year: 2023, Month: 11, Day: 21
Date d2 = d1; // 调用拷贝构造函数
d2.PrintDate(); // 输出:Year: 2024, Month: 11, Day: 21
return 0;
}
出现的错误:
-
逻辑错误:拷贝构造函数中可能会修改成员变量的值,从而导致对象的状态发生改变。如果这不是我们的预期行为,那么就会导致程序逻辑错误。
-
破坏不变式:如果被拷贝对象的成员变量有一些不变式,那么拷贝构造函数的修改可能会破坏这些不变式,从而导致程序出错。
假设有一个
Rectangle
类表示矩形,其中成员变量width
和height
分别表示矩形的宽度和高度。该类定义了一个不变式,即width
和height
都必须大于 0。class Rectangle { public: Rectangle(int width, int height) : _width(width), _height(height) {} // 错误的拷贝构造函数,破坏了不变式 Rectangle(Rectangle& other) { _width = other._width + 1; // 修改宽度值 _height = other._height; } private: int _width; int _height; };
在这个例子中,我们定义了一个错误的拷贝构造函数,其中修改了被拷贝对象的宽度值。这将导致破坏
Rectangle
类的不变式,即宽度必须大于 0。如果我们使用该拷贝构造函数创建一个新的矩形对象,它将具有无效的宽度值,从而导致程序出错 -
不可预知的行为:如果被拷贝对象是一个常量对象,那么在拷贝构造函数中修改其成员变量的值是未定义的行为,可能会导致不可预知的结果。
假设有一个
Person
类表示人员信息,其中成员变量name
表示人的姓名,而该类对象被声明为常量对象。class Person { public: Person(const std::string& name) : _name(name) {} // 错误的拷贝构造函数,常量对象被修改 Person(Person& other) { _name = "Copy of " + other._name; // 修改姓名 } private: std::string _name; };
在这个例子中,我们定义了一个错误的拷贝构造函数,其中修改了被拷贝对象的姓名。如果我们使用该拷贝构造函数创建一个常量对象的副本,这将是未定义的行为,可能会导致不可预知的结果。由于常量对象的成员变量应该是不可修改的,所以在拷贝构造函数中修改成员变量将导致不可预知的状态和行为。