Const类对象和成员函数
通过 Const符号常量,您了解到基本数据类型(int,double,char等)可以通过const关键字构造为const,并且所有const变量必须在创建时初始化。
对于const基本数据类型,可以通过复制,直接或统一初始化来完成初始化:
const int value1 = 5; // 复制初始化
const int value2(7); //直接初始化
const int value3 { 9 }; // 统一初始化 (C++11)
Const类
类似地,也可以使用const关键字使实例化的类对象成为const。初始化是通过类构造函数完成的:
const Date date1; //用默认构造函数初始化
const Date date2(2020, 10, 16); //用带参构造函数初始化
const Date date3 { 2020, 10, 16 }; //用带参构造函数初始化 (C++11)
一旦通过构造函数初始化了const类对象,就不允许任何修改对象成员变量的尝试,因为它会违反对象的常量性。这包括直接更改成员变量(如果它们是公共的),或调用成员函数来设置成员变量的值。考虑以下类:
class Something
{
public:
int m_value;
Something(): m_value(0) { }
void setValue(int value) { m_value = value; }
int getValue() { return m_value ; }
};
int main()
{
const Something something; // 调用默认构造函数
something.m_value = 5; // 编译器报错: 违反const
something.setValue(5); // 编译器报错: 违反const
return 0;
}
涉及变量的上述两行都是非法的,因为它们要么通过直接尝试更改成员变量或者通过调用尝试更改成员变量的成员函数来违反某些常量性。
就像普通变量一样,您通常希望在需要确保它们在创建后不被修改时,并且使您的类对象为const。
Const成员函数
现在,考虑以下代码行:
std::cout << something.getValue();
也许令人惊讶的是,这也会导致编译错误,即使getValue()没有做任何改变成员变量的事情!事实证明,const类对象只能显式调用const成员函数,并且getValue()尚未被标记为const成员函数。
const成员函数是一个成员函数,保证它不会修改对象或调用任何非const成员函数(它们可能会修改对象)。
为了使getValue()成为一个const成员函数,我们只需将const关键字附加到函数原型,在参数列表之后,但在函数体之前:
class Something
{
public:
int m_value;
Something() { m_value= 0; }
void resetValue() { m_value = 0; }
void setValue(int value) { m_value = value; }
int getValue() const { return m_value; } //注意:在参数列表之后添加const关键字,但在函数体之前
};
现在getValue()已经成为一个const成员函数,这意味着我们可以在任何const对象上调用它。
对于在类定义之外定义的成员函数,必须在类定义中的函数原型和函数定义上使用const关键字:
class Something
{
public:
int m_value;
Something() { m_value= 0; }
void resetValue() { m_value = 0; }
void setValue(int value) { m_value = value; }
int getValue() const; // 注意:在这里添加const关键字
};
int Something::getValue() const // 还有这里哦
{
return m_value;
}
此外,任何尝试更改成员变量或调用非const成员函数的const成员函数都将导致编译器错误发生。例如:
class Something
{
public:
int m_value ;
void resetValue() const { m_value = 0; } // 编译器报错,:const函数无法更改成员变量。
};
在此示例中,resetValue()已标记为const成员函数,但它尝试更改m_value。这将导致编译器错误。
注意,构造函数不能标记为const。这是因为构造函数需要能够初始化它们的成员变量,而const构造函数将无法这样做。因此,该语言(C++)不允许const构造函数。值得注意的是,const对象不需要初始化其成员变量(也就是说,const类对象调用构造函数来初始化所有成员变量,部分变量或者没有成员变量也是合法的)!
规则:任何成员函数都不能修改类对象const状态的成员
Const引用
虽然实例化const类对象是创建const对象的一种方法,但更常见的方法是通过const引用将对象传递给函数。
按值传递类参数会导致创建类的副本(这很慢) - 大多数情况下,我们不需要副本,对原始参数的引用工作正常,并且性能更高,因为它避免了不必要的复制。我们通常创建引用const以确保函数不会无意中更改参数,并允许函数使用其他值(例如文字),它们可以作为const引用传递,但不能作为非const引用传递。
你能弄清楚以下代码有什么问题吗?
#include <iostream>
class Date
{
private:
int m_year;
int m_month;
int m_day;
public:
Date(int year, int month, int day)
{
setDate(year, month, day);
}
void setDate(int year, int month, int day)
{
m_year = year;
m_month = month;
m_day = day;
}
int getYear() { return m_year; }
int getMonth() { return m_month; }
int getDay() { return m_day; }
};
//注意:我们在这里通过const引用传递日期,以避免复制日期
void printDate(const Date &date)
{
std::cout << date.getYear() << "/" << date.getMonth() << "/" << date.getDay() << '\n';
}
int main()
{
Date date(2016, 10, 16);
printDate(date);
return 0;
}
答案是在printDate函数内部,date被视为const对象。使用那个const日期,我们调用函数getYear(),getMonth()和getDay(),它们都是非const的。由于我们不能在const对象上调用非const成员函数,这将导致编译错误。
修复很简单:make getYear(),getMonth()和getDay()const:
class Date
{
private:
int m_year;
int m_month;
int m_day;
public:
Date(int year, int month, int day)
{
setDate(year, month, day);
}
// setDate() cannot be const, modifies member variables
void setDate(int year, int month, int day)
{
m_year = year;
m_month = month;
m_day = day;
}
// 以下的get函数都可以成为const
int getYear() const { return m_year; }
int getMonth() const { return m_month; }
int getDay() const { return m_day; }
};
现在在函数printDate()中,const日期将能够成功调用getYear(),getMonth()和getDay()。
重载const和非const函数
最后,尽管不经常这样做,但是可以以这样的方式重载函数以具有相同函数的const和非const版本:
#include <string>
class Something
{
private:
std::string m_value;
public:
Something(const std::string &value="") { m_value= value; }
const std::string& getValue() const { return m_value; } // getValue() 为一个const对象
std::string& getValue() { return m_value; } // getValue() 为一个非const对象
};
将在任何const对象上调用函数的const版本,并且将在任何非const对象上调用非const版本:
int main()
{
Something something;
something.getValue() = "Hi"; // 调用非const getValue();
const Something something2;
something2.getValue(); //调用const getValue();
return 0;
}
当返回值需要在const中不同时,通常会使用const和非const版本重载函数。在上面的例子中,getValue()的非const版本只能用于非const对象,但更灵活,因为我们可以用它来读写m_value(我们通过分配字符串“Hi”来做) )。
getValue()的const版本将使用const或非const对象,但返回一个const引用,以确保我们不能修改const对象的数据。
这是因为函数的常量被认为是函数声明的一部分,所以const和非const函数只有const的含义才被认为是不同的。
Summary
因为通过const引用传递对象很常见,所以您的类应该是友好的const友好的。这意味着使任何成员函数不会修改类对象const的状态!