-
2018-05-29 22:13:18
一 问题
有时候我们在阅读一些源码时,发现有些类的成员函数的形参列表后面有个const,但是使用的时候感觉跟那些没有const的成员函数没有啥区别,举个简单栗子如下,
#include <iostream> using namespace std; class TestClass { public: TestClass() : val(100) {} virtual ~TestClass() {} int getVal_v1() { return val; } int getVal_v2() const { return val; } private: int val; }; int main() { TestClass tc; int a = tc.getVal_v1(); int b = tc.getVal_v2(); cout << "getVal_v1: " << a << endl; cout << "getVal_v2: " << b << endl; return 0; }
编译运行都ok,输出如下,
有点懵,那这个const有什么作用呢?要回答这个问题,需要先回顾一下类的this指针。
二 回顾this指针
继续分析上一节的程序,当我们实例化类TestClass生成了对象tc,然后调用成员函数getVal_v1(),就可以拿到tc的private变量val,看上去一气呵成,但是这里需要思考一个问题:getVal_v1()是怎么拿到val的呢?
这就是靠this指针来完成的,成员函数通过this这个额外的隐式参数来访问调用它的对象。当我们调用成员函数时,会用对象的地址来初始化this指针(对象就是调用成员函数的对象)。这样我们在成员函数里就可以拿到对象的私有变量了。
getVal_v1()函数也可以写成如下形式,(一般为了方便会省去this)int getVal_v1() { return this->val; }
this形参是隐式定义的,不会显式地出现在成员函数形参列表里,但实际是存在的,所以任何自定义为this的参数或变量都是非法的。
因为this指针总是指向这个对象,所以this是一个常量指针,不允许修改this的值。this指针的默认初始化过程等价如下,
TestClass *const this = &tc;
ps:常量指针和指针常量是不同意思,常量指针是说指针的值是const的,指针常量是说指针指向的变量的值是const的,还有常指针常量,看字面就知道是把常量指针和指针常量合在一起,不但指针本身是const的,而且指针指向的变量也是const的。OMG!!!
三 成员函数后的const
下面就把目光转向本文开始讨论的问题:成员函数形参列表后的const有什么作用?
成员函数形参列表后带const,这样的成员函数叫做类的const成员函数,也叫常量成员函数。这个const作用是用来修改隐式this指针的类型,把它变成常指针常量(请阅读上一节的ps部分,如果还看不懂,就请查阅《C++ primer 5th》2.4.2节)。
为何要转变?这里以上一节的代码为例,默认情况下,this指针是常量指针,但是如果tc是个常量对象,那么this的默认初始化就是非法的,C++规定只能使用指向常量的指针来存放常量对象的地址。这里修改下第二节的代码,如下,
#include <iostream> using namespace std; class TestClass { public: TestClass() : val(100) {} virtual ~TestClass() {} int getVal_v1() { return val; } int getVal_v2() const { return val; } private: int val; }; int main() { const TestClass tc; int a = tc.getVal_v1(); int b = tc.getVal_v2(); cout << "getVal_v1: " << a << endl; cout << "getVal_v2: " << b << endl; return 0; }
这里把tc声明为常量对象,此时再编译就出错了。
这里有2个解决办法:- 在成员函数getVal_v1()的形参列表后加上const,编译就ok了,也可以正常运行;
- 不加const,然后在main函数里注释掉getVal_v1()的相关语句,程序也可以正常编译运行,如下,(可以看出常量对象只能调用常量成员函数)
int main() { const TestClass tc; // int a = tc.getVal_v1(); int b = tc.getVal_v2(); // cout << "getVal_v1: " << a << endl; cout << "getVal_v2: " << b << endl; return 0; }
讲到这里,就弄明白这个const的作用了,就是把this指针由默认的常量指针变成常指针常量。有了const后,this指针的初始化过程就等价如下,
const TestClass *const this = &tc;
因为此时this是指向常量的,所以常量成员函数不能改变调用它的对象的内容,也就是不能修改对象的私有变量值。另外,非常量对象可以调用常量成员函数,但也是无法修改对象的内容的。
四 结语
经过上述三节,就搞明白成员函数形参列表后const的作用了,简单概述下,就是把this指针由默认的常量指针变成常指针常量,然后常量对象就可以调用这些常量成员函数了。
这里再贴一个《C++ primer 5th》中关于常量成员函数的note:
常量对象,以及常量对象的引用或指针都只能调用常量成员函数。
本文主要参考《C++ primer 5th》关于常量成员函数的讲述,也算是总结一下自己的理解。
如果有写的不对的地方,希望能留言指正,谢谢阅读。
更多相关内容 -
函数的形参列表
2020-09-07 12:12:29函数在定义的时候,可以定义“形参列表”,用于接收参数!形参列表就是定义一系列的参数,可以是变量或函数指针。形参变量用于接收函数调用的时候,传递过来的数据。 这里我们先讲解形参是变量的知识,后续再讲解...函数在定义的时候,可以定义“形参列表”,用于接收参数!形参列表就是定义一系列的参数,可以是变量或函数指针。形参变量用于接收函数调用的时候,传递过来的数据。
这里我们先讲解形参是变量的知识,后续再讲解形参是函数指针的知识。
根据函数的定义格式如下:
返回数据类型 函数名(形参列表)
{
函数体
}
那么,形参列表是变量的时候,就是如同定义变量一样,如下:
返回数据类型 函数名(数据类型名 变量名)
如果要定义多个参数变量,定义格式如下:
返回数据类型 函数名(数据类型名 变量名1, 数据类型名 变量名2, … 数据类型名 变量名N)
可以看到,形参列表就是定义多个变量的列表,定义情况有:
(1) 如果不定义形参列表,函数形参可以使用void关键字修饰;
(2) 如果只定义1个变量,可以如同定义变量一样,末尾不需要用引号“;”结束;
(3) 如果定义多个变量,在定义不同变量之间是有逗号“,”分隔开;
注意:虽然可以在形参列表中定义多个参数,但是,在编写程序的时候,要考虑函数模块之间的“耦合性”和“扩展性”。不要传递太多的参数。如果项目需求改动,需要修改某个参数,就会导致函数接口的改动,函数接口的改动对程序的改动很大,这种操作应该极力避免。
当学习到结构体之后,我们可以把多个参数封装到一个结构体中,函数的参数只需要传递一个结构体变量就OK了。那么,当有数据需要改动,只需要修改结构体的定义和数据,函数的接口不需要修改,那么,函数模块之间的耦合性就降低,提高程序函数模块之间的扩展性。这就是函数模块扩展性好的一个表现形式,也是定义函数时需要思考的注意点。
举例说明形参列表的定义如下:
void func1(int a); //表示func1函数接收一个int类型的参数
void func2(int a, int b, int c)//表示func2函数接收3个int类型的参数
那么,调用函数的时候,有:
(1) func1(8); 此时,调用func1函数,在参数中填入8这个数值,那么,就把8这个数值传递给形参变量a。等价于:
int a = 8;
所以,就是把8整数值赋给形参变量a;
(2) func2(6, 7, 8); 此时,调用func2函数,在参数列表中填入6, 7, 8这个数值,那么,就会按顺序把数值6赋给形参变量a,把数值7赋给形参变量b,把数值8赋给形参变量c。等价于:
int a = 6;
int b = 7;
int c = 8;
所以,就是按顺序,把调用func2函数时的参数,设置到形参变量。
那么,下面编写程序测试,看看怎么样使用定义函数的“形参列表”,测试代码如下:
程序运行结果如下:
可以看到,调用func1(10); 的时候,把实参10这个数值,传递给func1()函数的形参变量a;所以,在func1()函数中输出形参变量a的值,就是10这个数值。
同理,func2(6, 7, 8); 函数的调用,是把实参6, 7, 8分别赋给func2()函数的形参变量 a, b, c。等效于:
int a = 6;
int b = 7;
int c = 8;
所以,func2()函数的形成变量a, b, c分别得到6, 7, 8这些数值。
韦凯峰 Linux C/C++ 程序设计教程,Linux 系统编程,Openwrt 系统开发,微信:13926572996,QQ:1523520001,博客:www.mylinux.vip
-
C2548 缺少参数 X 的默认参数 默认实参不在形参列表的结尾
2019-10-24 16:25:39默认实参不在形参列表的结尾 C2548 缺少参数 X 的默认参数 原因是在C++的形参列表中,初始化的参数必须排列在不初始化的参数后面,也就是说初始化的参数后面的参数,也必须初始化。 修改如下: 正确解决! ...出现如上图的错误:
C2548 缺少参数 X 的默认参数
原因是在C++的形参列表中,初始化的参数必须排列在不初始化的参数后面,也就是说初始化的参数后面的参数,也必须初始化。
修改如下:
正确解决!
(其实只是非常基础的一个问题,平时的编码习惯还是要保持规范!)
-
关于变长形参列表函数的设计与使用问题
2016-03-15 10:27:08根据C语言的语法,一个函数可以没有形式参数,也可以有一个或多个参数,当然,也可以是类似于 scanf() 和 printf() 一样的带有变长形参列表的函数。显然,采用变长形参列表的函数能够提高程序的灵活性。针对目前 C 语言... -
类和对象 —— 形参列表、explicit、static成员
2022-03-24 19:14:56形参列表、explicit、static成员
目录
一、形参列表
形参列表的作用是给成员变量赋初值,在构造函数的末尾,以一个冒号开始,变量之间以分号隔开,括号中放初始值
但是赋值的话,我们不是可以在构造函数内部对变量 “初始化” 吗,我们甚至还能在声明的时候赋值。下面就需要了解一下,形参列表赋值,和其他方式的赋值有什么区别
1、形参列表、构造函数体内、变量声明时 赋值的区别
(1) 形参列表
形参列表就算不写,编译器也会自动生成一份;有些变量尽管没有在形参列表初始化,编译的时候依然会自动补上
值得注意的是,形参列表只能初始化一次,也就是只能赋值一次
但是下面三种情况或者类型,必须要进行初始化,而且只能在形参列表初始化
(1) 没有默认构造函数的自定义类
(2) 引用
(3) const修饰
(2) 构造函数体内
构造函数体准确说,变量并不是被初始化了,而是被再次赋值了
在进入函数体之前,会先经过形参列表,这里虽然没写,但是编译器会自动生成一份
等到进入函数体内部,只不过是把原本的值给覆盖了
(3) 变量声明
下面这种写法乍一看是在给numbers赋初值,这种说法并不严谨,其实这是C++11引入的新特性,这是在给numbers赋缺省值
2、必须放初始化列表进行初始化的三种情况
(1) 没有默认构造函数的自定义类
上面提到,形参列表即便不写,编译器也会自动生成(如下),但是这个时候会调用默认构造函数
Animal():cat()
如果这个时候没有默认构造函数,那就必须通过形参来初始化了
Animal(Cat cat):_cat(cat)
这个时候就会调用有参构造函数
(2) 引用
引用相当于给一个变量起别名,既然是起别名,那就必须要知道是给谁起别名,这就要求引用在初始化的时候绑定一个变量
但是为什么只能放形参列表呢??
因为一旦进入到函数体内部,那就是赋值了,而不是初始化
(3) const修饰的成员变量
和引用类似,const修饰的变量必须在声明的时候就绑定一个变量或者常量
进入到构造函数的函数体内,实际是赋值,相当于要改变 变量的值
所以只能放在形参列表初始化
二、explicit关键字
假设我们要通过有参构造函数来初始化一个对象,我们多数会通过下面这种方式来实现
对象能否通过等号'='来初始化,答案是可以的!
尽管如此,这种实现方式的可读性较差,我们不希望自己定义的类支持这种写法,所以我们可以在构造函数的前面加上 explicit关键字
三、static成员
static修饰的成员变量,称为静态成员变量;static修饰的成员函数,称为静态成员函数
1、静态成员的特性
(1) static修饰的成员变量在 全局变量区,不属于任何实例,所以static修饰的成员变量必须在类外被初始化,而且不带static
(2) 类静态成员可以使用 类名::静态成员 或者 对象.静态成员 来访问
2、静态成员函数的特性
静态成员没有隐藏的 this指针,无法访问任何非静态成员
在java中的一种解释是,static修饰的成员函数在类被创建的时候就已经存在了,而无static修饰的成员函数只有在被调用的时候才会存在
——》 已经存在的无法调用不存在的,所以静态成员函数 无法调用 非静态成员函数;
——》当无static修饰的成员函数被创建出来的时候,static修饰的成员函数早就存在了,
所以非静态成员函数 可以调用 静态成员函数
-
学习笔记(13):7天速学JAVA基础-成员方法的形参列表详解
2020-09-14 14:40:52立即学习:... 形参列表主要指多个形式参数组成的整体,格式: 数据类型 形参名1,数据类型 形参名2,... 若该方法不需要传入任何数据内容时,形参列表空不写。 -
Android Studio 如何像eclipse一样查看函数的形参列表,返回值,以及提示信息.
2016-06-15 16:20:04先选中,然后按F2Eclipse有一个很好的功能,就是当你代码调用某个Android API时,鼠标移到对应的函数或者方法上,就会自动有一个悬 浮窗提示该函数的说明(所包含的参数含义,该方法功能)。迁移到Android Studio后... -
关于C语言中的形参列表空着与加void的区别
2015-11-12 23:31:21关于C语言中的形参列表空着与加void的区别 来一段代码大家看看,不,两段吧! int func() { printf(“hello world\n”); } int func(void) { printf(“hello world\n”); } 请问这两段代码一不一样呀? 不... -
关于Java中形参与实参的理解
2021-03-18 11:53:06js 函数中形参与实参的关系 函数中形参与实参的关系 对于形参和实参的定义,在 权威指南中有着明确的定义.但是,我们更在意的是它们之间的关系,到底形参会不会影响到实参? 形参到底会不会影响到实参? 对于这个问题的... -
C++类的成员函数的形参列表后面的const
2016-06-16 10:23:00看到(C++ Primer)类的成员函数这里,突然对成员函数形参列表后面的const感到迷惑。 因为书中开始说是修饰隐含形参this的,然后又说是声明该函数是只读的。 大为不解! 翻资料、找人讨论。。。 最终... -
Java 普通方法中形参列表中以接口作为数据类型实现继承和多态
2019-09-18 22:05:22在我的印象中,java的接口是不可以实例化的,也就是说没有构造器,没有属性,只有一堆定义好形参和返回值的方法名。不能在程序中用new去实例化一个对象。所以理所应当的在脑海中深化了一种思维,就是接口不可以当做... -
获得方法形参名称列表 -- 哦也,搞定!!
2021-03-17 20:43:16JAVA获取类的方法的参数名 – 老话题,新方法!!折腾了一天,终于搞定了.测试了nutz所有的类,均读取正常!! 完美读取任何class的变量...终于完成这个一直想做到的功能 – 在Java中,获取方法的形参(参数)的名字.由于这个... -
Python 形参列表中带默认值的形参应放在不带默认值的形参的前面
2019-08-01 18:53:37Python 形参列表中带默认值的形参应放在不带默认值的形参的前面,否则会提示: SyntaxError: non-default argument follows default argument 错误代码: def p(n=None, n2): print(n, n2) p(1) ... -
形参中可变参数列表
2020-04-01 11:55:12形参中可变参数列表 -
C语言中形参列表为指针的三种不同swap函数的通俗理解
2022-01-01 02:42:40C语言中形参列表为指针的三种不同swap函数的通俗理解 给出下面两个swap函数 函数名以my_swap函数代替 1、 void my_swap(int *a ,int *b){//不会交换 int *temp = a; a = b; b = temp; } 2、 void my_swap(int *a... -
python中列表作为形参传输给函数
2020-06-10 23:15:49python中列表作为形参传输给函数 代码如上 可见列表作为形参进行参数传递的时候,原列表的值是改变的。 -
C语言-如何定义和调用函数以及实参、形参、参数列表概念
2020-04-24 16:58:45} long li(long x)//调用函数的定义,第一个long是指返回值(return Y)的变量类型,(long x)是参数列表,x是形参,第二个long是实参的数据类型。 { int Y;//局部变量仅限在函数中使用。 Y = x * x * x; ... -
JAVA中通过反射机制获取方法的修饰符,返回值类型,方法名,形参列表
2020-07-11 23:43:27JAVA中通过反射机制获取方法的修饰符,返回值类型,方法名,形参列表 与Field中没太大区别: 获取修饰符列表 Modifier.toString(method.getModifiers()) 获取方法返回值类型 method.getReturnType().... -
数据结构关于形参列表的一个问题?
2020-07-07 10:20:16我之前在李春葆那本数据结构上有个发现他的形参列表都是都是指针变量的引用*&,如果把引用去了就不能修改L了。这两个地方有什么区别吗?好迷啊 typedef struct LNode { ElemType data; struct LNode* ... -
CC00006.bigdatajava——|Java&类和对象.V06|——|Java.v06|point类.v02|返回值类型|形参列表|方法体|
2022-04-08 08:19:07一、成员方法定义 ### --- 成员方法定义 class 类名{ class Person { void show() { 返回值类型成员方法名(形参列表) { ... -
C函数形参列表与汇编寄存器的对应关系
2018-06-11 22:23:44 -
实参与形参
2020-06-21 12:09:38形参:是声明函数时写的,如果是多个形参,那么需要用,隔开。形参的值不是固定的,形参要与实参实际传入的数据一一相对应。 实参:在调用时,实际传入函数中的值,传入后,在函数中使用形参中获取具体的值。 ... -
python 列表 元组 和 字典 做形参
2018-12-20 20:37:43函数传递列表时 形参接受列表 没什么不同 例如 def greet_users(names): for name in names: msg="hello "+name.title()+"!" print(msg) names=['tom','mark','bobby'] greet_users... -
【C++】函数的形参
2018-06-07 17:02:39因此,指定默认值的形参必须放在形参列表的最右侧,或者说,在带默认值的形参的右边,不允许出现无默认值的形参; int add(int x = 10, int y = 20){...表达式;} add(); add(a); add(a, b); float f1(float a...