const 我们都见过,但是今天,我们会从头开始重新再说const的所有用法。
一、const修饰普通变量
当我们定义一个变量时,前面加上const修饰的话,这个变量将不再能被修改,称之为常变量。例如:
int a=10;
a=20;
const int b=10;
int const c=10;
b=20;
在上述代码中,a称之为变量,b称之为常变量。变量修改没有任何问题,但是常变量有“常”属性,不能被修改,所以下面两行是错误的。
另外,常变量必须初始化:
int a;
a=10;
a=20;
const int b;
b=10;
你觉得,我定义一个常变量,然后后面再对他进行赋值能对嘛,答案是:不能!
const可以代替一些宏:
#define MAX_NUMBER 1e8
const long long MAX_NUMBER=1e8;
为什么写起来宏更方便,我们还要用const,我只能告诉你:const更加安全。
const修饰普通变量很常用,也很简单,除了记得必须初始化外,需要注意的点也不多。
二、const修饰指针变量
好嘛!这第二个就开始说const与指针的事了,这简直就是羊了个羊的难度跨越。const修饰指针跟羊了个羊一样,看起来规则就这,但其实玩起来,那需要注意的就多了,用的时候小心点就ok了。
1.常量指针
(常指针)常量指针实质顾名思义就是一个指针,但是我们不能通过指针去修改指向的变量,所以这个指针就是常量指针。来看一下语法:
int a=10;
const int *p=&a;
//或者你也可以这么写
int const *p=&a;
但无论上边哪种写法,我们都可以发现一个问题,const真正修饰的不是变量p,而是*p,*p是什么,不就是p指向的内存的内容嘛,const修饰了,那我们就不能修改了。但p这个指针的指向可以改变,指向别的变量:
int a=10;
int b=20;
const int p=&a;
p=&b;
这么写是没有任何问题的。但同样的,不管p指向哪块儿内存,都不能修改p指向的内容,只能修改p的指向,p也成了常量指针,它的意义就是防止通过指针修改变量。
2.指针常量
指针常量就是一个常量,这个常量表示一个指针,也就是这个常量的数据内容是一个地址信息。什么意思呢?看代码:
int a=10;
int b=20;
int* const p=&a;
p=&b;
聪明的同学又开始提前思考了,这次const的位置发生改变了,这次改变后,const后面紧跟着的就是指针p,那就是说,p不能改变。p=&b那行代码就会报错,于是就去VS上试验了一番,想法正确。对的,这位聪明的同学,你的想法完全正确,你以实践验证想法的做法也让我欣赏万分。
那这次,既然这次我们修饰的是指针变量p,那么p的初始化能不能省去呢?不能的,这次修饰的是变量本身,而非变量内容,修饰变量本身,本身的值是无法被修改的。但是,对应的,我们可以利用指针来改变指针的指向内容:
int a=10;
int* const pa=&a;
*p=30;
cout<<a<<endl;//a=30;
p就是一个存地址的变量,地址不可以被修改。那p的作用是什么,其实p的作用就是修改p指向的内容。在传参时,为了安全考量,我们的地址不能丢失,但是我们还需要这个地址来修改内容。使用cosnt这样修饰的话,我们就能解决上述问题。这看起来好像引用类型,没错,引用就是C++给大家封装好的的指针常量。这就是引用类型的实质,引用必须初始化,那是因为指针常量就是这样的,引用不能改变引用关系,你看,指针常量不也是吗,不能修改指向内容。
3.“双常”
int a=10;
const int* const p=&a;
const哪都跑,真烦人呐,哈哈,不要烦,你就看const修饰谁就完了,修饰*p,那么*p不能变,修饰p,那么p不能变。那要是都修饰,都不变呗。以后这个p 就是a的内存,*p就是a。意义不太大了,p和*p就是&a和a。
*拓展知识
随时拓展,但也不多,总结下来就没几句话,我的习惯,看完代码好说事。来看看下面哪个是正确的写法? 选吧,A or B or C。
const int a =10;
int *pa=&a;//A
const int*pb=&a;//B
int* const pc=&a;//C
答案是B,为什么?a由const修饰,代表着a不可改,只有B选项符合条件。如果用另外两个指针存储a的地址,我们可以通过指针解引用来改变a的值,const属性就没有意义了,C++是一门严谨的编程语言,语法要求比较严格。
那我int a=10; 能用cosnt int *pa=&a;来存嘛,可以的。来看结论吧:(!!非常重要!!)
总结:指针指向的变量,const属性不能丢失,但可以增加。
三、const修饰引用变量
//左值引用:DataType& _a;
//右值引用:DataType&& a;
//万能引用:const DataType & aa;
int test01() {
int a = 10;
return a;
//返回:数值,右值
}
int& test02() {
static int a = 10;
return a;
//返回:引用,左值
}
int main() {
int _a = 2;//_a是左值
int& a = _a;//(左值引用)引用左值
int&& b = 2;//(右值引用)引用右值
//int&& b2 = _a;//(右值引用)不能引用左值
//int& a2 = 2;//(左值引用)不能引用右值
const int& aa = test01();//(万能引用)引用右值
const int& bb = test02();//(万能引用)引用左值
return 0;
}
我们函数传参时,有时候会传入左值,有时候会传入右值,我们传入的还需要是引用类型的数据,那么万能引用才是合适的形参。const又又又又来了,哈哈,用的时候就在引用类型前面加一个const就行了,没那么难。上述左值右值,引用的方法你了解一下就行,(先点赞收藏一波,嘻嘻^_^)等有需要再来看看就行了呗。
const int num = 10;//num是一个int型的常量
const int &n = num;//正确,n是一个常量引用,并且num本身也是一个常量
n = 20;//错误,引用被const限制了,不能修改所引用对象的值了
int &n2 = num;//错误,试图让一个非常量引用指向一个常量对象
int a=50;
const int &n=a;//正确,可以增加const属性
//所以万能引用的实质就是const属性虽不可去除但可以增加的特性
四、const修饰函数
严格意义上cosnt不能修饰除成员函数外的函数,这里只是修饰函数的构成要素:参数/返回值等
1.修饰函数参数
const修饰参数,在函数内就不可以修改参数的值了,就是修饰上面说的三种情况。
2.修饰函数返回值
修饰返回值干嘛?你问这个问题的时候就知道你没想过返回引用类型的时候是怎么个事。
让我们先做一下准备工作:来看一下引用类型返回值的情况:
int& fun(int& a){
a+=10;
return a;
}
这么看,好像啥也没有,就跟简单的返回值没啥区别,那再看一下这个代码:
int main(){
int a=10;
fun(a)=a+10;
cout<<a;
return 0;
}
这就没怎么见过了吧,我什么时候见过函数调用完后放在等号左面的啊?这不就见了吗,fun返回的就是a的引用,我通过a的引用去修改了a的值。
慢慢琢磨吧,学习的路途,任重而道远。
五、const修饰成员函数
const修饰的成员函数,称为常函数。
第一个问题:const修饰符放在哪里?
我们上面说过const修饰函数的问题,但那修饰的不是参数就是返回值,我们到底怎么修饰整个函数呢?const放到哪里合适呢?
放在函数名前面是修饰返回值,不行。放在参数列表,修饰的是形参,不行。放在函数名和参数列表中间?不行啊,函数名和括号中间除了空格以外,不能加修饰符跟其它任何东西啊。放到大括号内部?那就是函数体的一部分了,也不可以。ok那就只有一个地方了:函数后,大括号之前。
int fun() const{
}
当然了,这种写法只在类(结构体)内生效 。
好了,现在我们知道了如何修饰成员函数。那么接下来我们就要说:
第二个问题:这么修饰有什么作用?
常函数的函数体内,不能有修改类内成员属性的代码。也就是说,常函数的意义在于成员变量被该类的函数修改。比如:有一个函数的功能就是为了判断这个类实例化的对象是不是空的,只有判断功能的函数,不应该修改成员属性,此时加上const,就能有效避免问题了。
关键字:mutable。类内成员声明时使用mutable修饰,常函数就有了该成员的修改权限。
第三个问题:常函数有什么特点?
特点一:常函数不能调用调用非常函数
如果一个常函数调用非常函数,非常函数改变了成员属性,不管怎么说这也是在常函数中修改了成员属性,这不允许。
所以这个特点要切记:类内的常函数不可调用类内的非常函数。(实质是,有更改类内属性的风险)。
实质:常函数调用非常函数,常函数的*this的const常属性被去掉了,这是不允许的。
而非常函数可以调用常函数,非常函数的*this增加了const属性,是允许的,可以看一下上文修饰指针的拓展知识。
特点二:同名同参的常函数与非常函数可以构成重载
函数相同,参数相同,常属性不同,构成重载
实质:this指针不同,即显式参数相同,但隐式参数不同,属于参数列表不一样,发生重载
注意:常对象只能调用常函数,非常对象优先调用非常函数
特点三:常函数的this指针和非常函数的this指针不同
常函数的隐藏this指针为:cosnt className* const this
非常函数的隐藏this指针为:className* const this
看字看不懂?配个代码吧:
class A{
int num;
pulic:
void work(){
//非-常函数
this->num=2;//非常函数更改成员属性
fun();//yes,快去试试调用的是哪个fun()吧
cout<<"work"<<endl;
}
void fun() const{
//常函数
//num=2;//改不了
//work();//error
cout<<"const fun"<<endl;
}
void fun() {
//构成重载关系
cout<<"fun"<<endl;
}
};
六、const修饰对象
const修饰的对象,称为常对象。
声明类变量时(这个变量就是对象),前面加上const 修饰,这个对象就是常对象。
常对象只能调用常函数。但普通对象可以调用常函数和非常函数
实质:传参时,常对象的this的const属性不能被去掉,所以this无法向非常函数传参,就不能调用非常函数。而普通对象调用常函数传this时,增加const属性,合法。
一样的,给个代码(类取上面那段代码)
int main(){
A a;//非常对象,可以调用所有在访问权限内的函数(包括常函数)
const A b;//常对象
a.fun();//非常对象优先调用非常函数
b.fun();//常对象只调用常函数
return 0;
}
感谢大家!cosnt的所有用法基本都在了,可以说是相当全的了。建议收藏,哈哈,当然有更全的也可以在评论区留个言,那什么,我也是需要学习的,与诸君共勉 (^_^)!