关于C++中const关键字的总结

简而言之

C++中的const关键字是一个很重要的修饰符,也是面试中常见的考点,尤其是在搭配变量、函数、指针等概念之后变得复杂难懂。本文章试图通过更加通俗化的描述解释清楚const,希望对敏而好学的各位起到一点帮助。
简而言之,const关键字的作用就是- 不变

1、与常量的区别

推荐大家看一下这篇文章,扫一眼就知道区别了,在此不再赘述。
傻瓜教学——什么是常量?常量可以分几类?它们分别又是什么?又有什么含义?

2、const的作用

之前提到const的作用是保持“不变”,这里就出现了两个问题:
(1)使什么值保持不变?(可修饰的类型)
(2)怎么保持值不变?(如何修饰)
在使用const关键字时,修饰何种类型和如何修饰该类型是密不可分的。因此下面的叙述以可修饰的类型作为分类标准,并在其中介绍如何修饰该类型。
const可修饰的类型可以是以下几类:变量,指针,函数,类。

2.1、修饰变量

const修饰变量,使之成为常变量,不能再被改变。
例如:

const int a = 1;
a = 2;       // 错误,表达式必须是可修改的左值
int *p = &a; // 错误,"const int *" 类型的值不能用于初始化 "int *" 类型的实体
*p = 2;

在这段代码中,const修饰变量a,a的值不能修改,通过变量本身或者地址访问去修改都不允许。

2.2、修饰指针

虽然指针也是一个变量,但是因为指针特殊,因此在此处讨论时将指针单独列出来。
const修饰指针,可细分为常量指针指针常量
“常量指针”和“指针常量”这两个概念容易弄混淆,我以前也区分不了。现在教大家两个分清楚的诀窍。

  1. “常量”就是const,“指针”就是*,所以按照先后顺序读和写就是对应的概念。例如:
int const * p1; // 常量指针
// const int * p1;	 // const放在最前面,将const后移一位,与原式等价
int * const p2; // 指针常量
  1. “常量指针”和“指针常量”,谁在前谁就不可改变

在“常量指针”中,“常量”在前,因此该类型的值不可以改变。通过解引用* + 指针可以取出这个值,也就是 *指针 这种操作是不允许的。例如:

int a = 1;
int b = 1;
const int *p1 = &a; // 等价于int const *p1 = &a; 前面提到过
*p1 = 2; // 错误,表达式必须是可修改的左值。常量指针不允许修改值
a = 2;   // 正确
p1 = &b; // 正确

在“指针常量”中,“指针”在前,因此指针的值不可以改变。我们知道,指针保存的是一个变量或者对象的地址,指针的值不变也就是这个地址不变。那么地址一旦改变,是不是就意味着换了一个变量或者对象!?例如:

int a = 1;
int b = 1;
int *const p1 = &a;
a = 2;   // 正确
*p1 = 2; // 正确
p1 = &b; // 错误,表达式必须是可修改的左值。指针常量不可以再指向别的变量

2.3、修饰函数

const修饰函数,分为三种情况,根据const关键字放置位置的不同,分为前面,中间和后面。具体什么意思往下看。

2.3.1 前面(修饰返回值)

(1)值传递
如果函数返回值采用“值传递”方式,返回值返回的仅仅是一个副本,所以用const修饰毫无意义。例如:
将 int GetData()改为const int GetData()没有意义。
(2)指针传递
如果函数返回值采用“指针传递”方式,加const修饰之后,函数的返回值可以是
1⃣️常量指针,表示指针所指向的值不能被改变,必须使用带有const关键字修饰的指针接收;
2⃣️指针常量,指针不可以被改变,返回的指针不可以再指向别的变量。
例如:
1⃣️

const int* GetData()
{
    static int a = 2;
    cout << "a = " << a << endl;
    return &a;
}

int main()
{
    int* p = GetData(); //错误,"const int *" 类型的值不能用于初始化 "int *" 类型的实体
    
    system("pause");
    return 0;
}

2⃣️

int* const GetData()
{
    static int a = 2;
    cout << "a = " << a << endl;
    return &a;
}

int main()
{
    int b = 3;
    GetData() = &b;	//错误,表达式必须是可修改的左值。返回值是指针常量,不可以修改指针的指向
    
    system("pause");
    return 0;
}

(3)引用传递
如果函数返回值采用“引用传递”方式,函数的返回值是变量的别名,此时是可以作为左值使用,也就是可以被赋值,例如:

int &GetData()
{
    static int a = 2;
    cout << "a = " << a << endl;
    return a;
}

int main()
{
    GetData() = 5;	// 输出a = 2
    GetData();		// 输出a = 5
    
    system("pause");
    return 0;
}

如果加了const修饰,此时返回值是一个常变量,不能被改变也就不能作左值了。

2.3.2 中间(修饰形参)

(1)值传递
跟上面情况相同,如果函数形参采用“值传递”方式,输入参数仅仅是实参的一个副本,这种方式不会改变实参的原始值,所以用const修饰毫无意义。例如:
将 void SetData(int a)改为void SetData(const int a)没有意义。
(2)指针传递
函数形参采用指针传递,形如 void SetData(const char *),传递的是一个常量指针,前面提到常量指针的作用是使的指针所指向的变量的值不能被改变。这种方式在字符串操作函数中很常见,例如char *strcpy(char *__dst, const char *__src);
(3)引用传递
函数形参采用引用传递的时候,传递的是实参本身,相较于值传递和指针传递,少了拷贝的操作(说句题外话,指针传递的本质也是值传递,它是将地址值复制了一份传递进来),因此提升了效率。加const修饰可以避免实参被改变。

2.3.2 后面(修饰类的成员函数)

这种修饰方法见下面 2.4.2。

2.4、修饰类

先看个例子:

class TestConst
{
private:
    /* data */
public:
    TestConst(/* args */);

    void Print() const;
    void SetData(int data);
    void SetData();

private:
    const int m_a;
    int m_b;
};

TestConst::TestConst(/* args */): m_a(1)   // 正确
{
    m_a = 2;    //错误1,表达式必须是可修改的左值
    m_b = 1;
}

void TestConst::Print() const
{
    m_b++;          // 错误2,表达式必须是可修改的左值。
    				// const函数不可以修改成员变量的值
    SetData(12);    // 错误3,对象含有与成员 函数 "TestConst::SetData" 不兼容的类型限定符。
    				// const函数不可以调用非const成员函数
    SetData();      // 错误4,对象含有与成员 函数 "TestConst::SetData" 不兼容的类型限定符。
    				// const函数不可以调用非const成员函数,即使该非const成员数没有改变成员变量的值
    cout << "m_a = " << m_a << endl;
}

void TestConst::SetData(int data)
{
    m_b = data;
}

void TestConst::SetData()
{
    cout << "m_a = " << m_a << endl;
}
2.4.1、修饰成员变量

const修饰类的成员变量,和修饰普通变量类似,一样是不可以被改变。额外需要注意的是类的const成员变量必须在类的构造函数的参数列表中进行初始化(在构造函数内部属于赋值,不属于初始化,会报编译错误)。见错误1。

2.4.2、修饰成员函数

const修饰成员函数,将const放在“)”与“{”之间(简称后面),表示这个函数是一个“只读函数”,函数不能改变类对象的状态,不能改变对象的成员变量的值。
见错误2、错误3和错误4。
由于const成员函数不被允许改变成员变量的操作,为了避免在const成员函数中调用非const成员函数而导致了改变成员变量的可能性,因此编译器直接不允许const成员函数调用非const成员函数。

2.4.3、修饰类对象
class TestConst
{
private:
    /* data */
public:
    void Print() const
    {
    	cout << "const成员函数" << endl;
    };
    void Print()
    {
    	cout << "非const成员函数" << endl;
    };
    void SetData(int a)
    {
    	m_a = a;
    };
	int m_a;
};

int main()
{
	const TestConst a;
	TestConst b;
	a.SetData(1);	// 错误,const对象不可以调用非const成员函数
	a.Print();
	b.Print();
}

运行结果:
在这里插入图片描述
const修饰的类对象会优先调用const成员函数,非const对象会优先调用非const成员函数。
如果将非const成员函数注释,那运行结果是:
在这里插入图片描述
这也揭示了const的另一个作用,函数重载。

3、const实现函数重载

函数重载的定义是函数名相同,形参列表不同的函数构成函数重载。形参列表不同表现为参数个数不同,参数顺序不同,参数类型不同。
那么const和非const的成员函数是如何构成函数重载的呢?具体的请大家看下面这篇博客
const和非const函数重载
在此直接下结论,const和非const成员函数构成重载并没有违反函数重载的定义。它依然是形参列表不同,更深层的原因是this指针的不同,一个带有const修饰,一个不带const修饰。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值