C++ const在成员函数中的用法总结

首先把这学明白是看了C++ primer中对这里的讲解,学懂这部分一定要理解this指针的用法。

那么这期笔记分几点来讲:

一.根据C++ primer内容讲this指针,然后讲const在成员函数中的概念。

二.理解了概念之后,通过网上的一个博客案例([c/c++: c++函数返回类型什么情况带const - A_zhu - 博客园 (cnblogs.com)](https://www.cnblogs.com/Azhu/p/4352613.html#:~:text=const 成员函数的返回类型是引用时候,需要加const 约束 int fun (),const%3B int %26 fun () const%3B))来巩固一下。

三.巩固完之后呢,通过Effctive C++中的一个例子,详细说明一下,const成员函数以及它的返回类型什么时候返回const ,什么时候返回const&,通过排列组合来说明。

一、this指针

我们借用C++ primer的基础程序来举例说明:

1
2
3
4
5
6
Sales_data total;
total.isbn();
void isbn()
{
return bookId;
}

我们的疑问是,在调用isbn的函数的时候,函数怎么会准确的返回total这个对象的bookId成员呢?

原因是,类里边的成员函数其实是有一个隐形的参数的,他叫做this指针(注意,this指针出现的位置是类的成员函数)this指针是一个常量指针(这个指针的值是不变的,指针值指向的变量是可以变化的)。

那么this指针这个隐形的成员函数参数是做什么的呢?

它的作用就是当一个类对象调用这个类的成员函数的时候,类对象的地址就会赋给成员函数的this指针。

1
2
//伪代码,用于说明调用成员函数的实际执行过程
Sales_data::isbn(&total);

二、const成员函数

1.const类对象与const成员函数

上文提到,成员函数中的this指针是一个常量指针,但是指针指向的类对象他是可以变化的。

那么问题来了?

假设我们定义了一个const的类对象,如下:

1
const Sales_data sd;

现在我们想调用sd的成员函数isbn:

1
sd.isbn();

这么做可以么?

答案是不可以,我们借助this指针的知识仔细分析一下,sd是const变量,无论如何是不允许改变的,

我们调用isbn函数,isbn里边的this指针指向的变量可是可变的啊,也就是说会有一个如下的过程:

1
2
3
Sales_data* const this;
const Sales_data sd;
this=*sd;

合理么?

不合理!

因为你可以试图通过this指针改变一个const对象。这是不合法的。

那可怎么办?总不能我设计一个类,这个类永远不能定义const类对象吧?

这个时候C++有一种方法,引入const成员函数,具体做法:

1
2
3
4
void isbn()const
{
return bookId;
}

在函数体之前加一个const,就叫做const成员函数。

那么这么做有什么用呢?

答案是当有对象想调用这个成员函数时,这个成员函数的this指针是一个指向常变量的常量指针,即:

1
2
const Sales_data sd;
const Sales_data* const this=&sd;

这么做就非常合理了,我调用成员函数的类对象是个const变量,我调用的成员函数里边的this指针是一个指向常变量的常量指针。非常合理!

2.普通类对象和const成员函数

上文说到,const类对象能调用的成员函数必须是const成员函数。

那么,普通类对象可以调用const成员函数么?我们来剖析一下:

1
2
3
4
5
6
Sales_data sd;
void isbn()const
{
return bookId;
}
sd.isbn();

这么做是否可行,完全看成员函数的this指针是否可以接纳类对象的地址:

1
const Sales_data* const this=&sd;

这么做合理么?

合理!也就是说我们通过this指针不可以改变sd这个对象,但是sd对象依旧是可以通过其他的途径改变。

所以说普通类对象是可以调用const成员函数的!

3.const成员函数有哪些作用?

或者说,我们什么时候需要定义const成员函数?

拍脑袋一想,为了能够定义const对象,肯定每一个成员函数都需要定义一个对应的const成员函数啊,啊不对不对。。。const对象是不可以改变的,很多成员函数是会改变类对象的,

重新说:那当一个成员函数没有改变类对象时,我就需要把他定义为const成员函数,这个是硬性要求!因为你不这么做,你定义的const类对象一个成员函数也调用不了!

那么,只有这一个作用么?

其实不是,我们上一点说过,普通类对象也可以调用const成员函数,当我们设计一个成员函数时,如果这个成员函数不会改变类对象,我们最好也把他设计成const成员函数,反正你也不改变类对象,你设计成const编译器就会为你把关,用来提高程序的健壮性!

综上,两点作用:

1.为了const类对象能有成员函数调用(const类对象能调用的成员函数只能是不改变类对象的,所以为了照顾他,应该把类内的所有不会使类对象改变的成员函数都有一个对应的const成员函数版本,理论上是这样0.0)

2.为了提升程序的健壮性,只要这个成员函数不想让类对象改变,我们就要将他设计为const成员函数

4.容易让我们忽视的点

1
2
3
4
5
6
7
8
void isbn()
{
return bookId;
}
void isbn()const
{
return bookId;
}

注意,这两个函数是可以重载的!

有时候我们会定义诸如此类的两个版本,后边那个版本自然是方便const对象去调用咯。

还有一个需要注意的点,以isbn()函数举例子

如果类中只有这一个成员函数:

1
2
3
4
void isbn()const
{
return bookId;
}

执行下列语句:

1
2
Sales_data sd;
sd.isbn();

这么做是合理的,上文已经讲过。

如果类中有两个isbn成员函数:

1
2
3
4
5
6
7
8
void isbn()
{
return bookId;
}
void isbn()const
{
return bookId;
}

执行下列语句:

1
2
Sales_data sd;
sd.isbn();

注意哦,现在调用的成员函数是这个版本:

1
2
3
4
void isbn()
{
return bookId;
}

原因很简单,因为函数重载0.0调用这个版本最合理。

5.浅浅的总结

1.const成员函数存在的意义有两点,1为了const类对象,2为了程序的健壮性

2.同函数名字的成员函数和const成员函数是重载的。

三、简单的实例来说明const成员函数以及返回类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class A {
public:
A():num(2) { }
void setnum() { }
void getnum() const{ }
private:
int num;
};
int main()
{
const A b;
b.getnum();
b.setnum();///////////////////////错误
return 0;
}

我们来看b.setnum();他错在哪里呢?

很简单,b是一个const对象,如果想调用setnum函数:

1
A* const this=&b;

this指针企图改变一个const值。显然不可以!

关于const成员函数的返回类型

1.返回类型为void

没啥可说的

2.返回类型为引用类型

如果是引用类型,必须是const引用!

因为你这个是引用类型,你不加const约束

我们来看个例子就知道了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Test
{
public :
Test(int a):value(a){}
const int & GetValue()const
{
return value;
}
private:
const int value;
};

int main()
{
Test t(3);
const int &a = t.GetValue();
cout<<a<<endl;
return 0;
}

先来明确一个点吧,首先 定义了一个普通的类对象:

1
Test t(3);

然后调用const成员函数,注意这个时候成员函数的this指针:

1
const int* const this=&t;

注意这个时候是有一个类型转换的!value这个成员不在是int类型,而是转换成了 const int,究其原因就是因为this指针指向的是一个常量了!

所以当你要返回成员变量时,你肯定不能返回int&了吧,那样不合法,所以你只能返回const int&

image-20221011215017290

所以,小小的总结一下,既是一个普通的类对象在调用const成员函数时,在成员函数里边,所有类对象的成员都变成了const类型,那么你在返回他们的引用的时候,必然要加const。

3.返回类型为非引用类型

这个随意了:

1
2
const int a=10;
int b=a;//合理

四、Effective C++中的例子:

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
#include<iostream>  
class TextBlock
{
public:
TextBlock(const char* _text) :text(_text) {}
const char& operator[](std::size_t)const;
char& operator[](std::size_t);
private:
std::string text;
};
const char& TextBlock::operator[](std::size_t num)const
{
//std::cout << "sss" << std::endl;
return text[num];
}
char& TextBlock::operator[](std::size_t num)
{
//std::cout << "lll" << std::endl;
return text[num];
}
int main()
{
TextBlock tb("Hello");
std::cout << tb[0] << std::endl;
tb[0] = 'b';
const TextBlock ctb("World");
std::cout << ctb[0] << std::endl;
return 0;
}

首先这个例子是想说明重载的,也就是说tb对象调用的是非const成员函数

ctb对象调用的是const成员函数。

然后我们借用这个例子说明一下成员函数返回值应该注意什么(尤其还是这种重载函数)

1.上文所说的const成员函数如果返回引用必须是const类型

原因我已经讲过,我们县在让他是普通引用看看有啥问题就行:

image-20221011221229340

当然针对const成员函数你要是返回类型为char自然是没问题

image-20221011221323752

2.关于运算符重载成员函数有什么讲究?

先说非const成员函数,其实这个需要具体问题具体分析,我们直接拿上述代码举例子分析就行了。

重载运算符[],你可以返回char么?

当然可以编译通过。

但是你想一下这个操作:

1
tb[0] = 'b';

能合法么?

不能!因为你这个函数返回了一个值!一个右值!

右值怎么可能出现在等号左边?

所以,需要的返回类型是char&

当然针对const成员函数,返回char是可以的

五、总结

本篇文章,先是1.讲述了this指针的来源(成员函数的隐藏参数)

然后2.引出了const成员函数(其this指针指向的对象是常对象,所以成员函数内部关于常对象的成员都是const的)

紧接着,我们3.讲述了const类对象只能调用const成员函数,但是普通类对象一样可以调用const成员函数。

后续说明4.const成员函数存在的意义:为了照顾const变量;为了提高程序的健壮性。

然后我们找了几个例子说明了const成员函数。

同时介绍了5.const成员函数返回值需要注意的事项,最需要注意就是返回引用的时候,因为const成员函数使调用对象变成了const,所以返回的引用必须是const&

接着通过Effctive C++说明了这一点

6.引申了一下重载运算符成员函数返回类型的要求。