4. const 的使用
c语言里const是常变量,而c++里是常量,即立即数。
因此c语言里const修饰的变量并不能作为初始化数组长度的下标。
而c++里const修饰的变量编译时就被当作立即数,可以当作数组初始化下标
c语言里用const修饰的值可以不初始化,只是之后再没有机会对这个const修饰的变量赋值了。因此我们可以总结出c语言里const定义的常变量和一般变量的区别很小,只是常变量定义后不能作为左值存在。但其真身,或者说是编译方式,和普通变量是一样的。
然而c++里的const修饰的量必须初始化,否则编译无法通过,即在编译的时候把使用const修饰的量都替换为其的值,因此就能作为数组下标,因为编译时,编译器看到的下标并非const 定义的量的名字,而是它对应的值的立即数。
如果写了以下的代码在某cpp文件中
Const int a=10;
Int *p=(int *)&a;
*p=30;
Printf(“%d %d”,a,*p);
最终打印的结果将是10和30,程序运行后,p指向a的内存,修改a,a的内存里的数据肯定已经变成30了,但为什么最终打印的a还是10呢,因为编译时已经把printf里那个a换成了10这个立即数了,不会再因为a运行后的任何改变而改变了
刚说了c++里的const修饰的量必须初始化。初始化的值必须是个常量,不能是个普通变量,这样的原因是编译器编译时,替换const常量将不明确,只有立即数或者之前定义过的const常量是明确的具体的数字。
Int b=8;
Const int a=b;
是不对的
const int a=8;
const int b=a;
是可以的
并且在c++中,const修饰的数据,最终生成的符号是local符号,即只有本文件可见的。
如果想要把const修饰的数据供整个工程使用,生成global全局符号,那么其实也有一种方法,就是在定义处前面加extern声明。
举例 const int a=10;是本文件可见
extern const int a=10;本工程可见
6.引用
C++的一种新的类型,通常为数据类型+&;单独&表示去地址
比如简单的引用 int &b=a;就是表示b是a的引用。
引用就是被引用量的别名,如果改变b的值,同样也会改变a的值。
我们来看看引用的底层汇编究竟是什么
int a=10;
00ED439E mov dword ptr [a],0Ah
int &b=a;
00ED43A5 lea eax,[a]
00ED43A8 mov dword ptr [b],eax
可以看到b对a引用,实际上先让eax存放了a的地址,然后再将eax存放的a的地址赋给了b。这不就是个指针做的事情吗。我们再看看指针的汇编是什么样的。
int a=10;
012D439E mov dword ptr [a],0Ah
int *p=&a;
012D43A5 lea eax,[a]
012D43A8 mov dword ptr [p],eax
可以看到指针的汇编和引用的汇编原理是一模一样的。所以说引用其实就是被包装了的指针,不过每次使用引用时都其实都自动做了相当于指针解引用的操作。
那么引用变量到底开辟不开辟内存呢?
我们定义
int a=10;int &b=a; 打印&a 和&b的地址发现是一样的,有人就会说不开辟内存。
其实不是的,刚都说了,引用的本质就是指针,指针是要开辟内存的,32位系统下指针都是4个字节的,可是为什么&a和&b一样呢?因为使用引用b时,已经相当于把b引用所隐藏的指针解过引用了,此时b就是a了,所以怎么取地址都相当于&a,所以地址是一样的,至于引用偷偷开辟的那个4个字节我们是没有观测到的。但它真实存在。
怎么引用数组,指针呢?
引用一纬数组int arr[10]={0};
就相当于对int *arr 数组指针进行引用
我们看整形引用是
Int &b=int a;
b实际上对a取地址,int *b=&a 只不过&和*都掩盖了,然后在b引用名前紧跟了一个&引用类型
如果是int arr[10]呢,即对int *arr取地址
按上面的推,是不是b其实相当于一个数组指针,对arr数组指针取地址
Int (*b)[10]=&arr
然后掩盖&取地址和一个*
Int (b)[10]=arr
然后再紧跟着引用名b前面加上一个&引用类型名
Int (&b)[10]=arr
那么怎么对指针引用呢?用一级指针举例
是不是引用相当于是一个指针的指针,即二级指针
int *p;
int **b= &p;
然后掩盖
Int *b=p;
然后加&
Int * &b=p;
是不是慢慢地感觉规律来了,就好想好写很多了?
7. const 指针 引用结合
(1) const 与指针
一级指针
Int a=10;
Int *p=&a;
其中const修饰第二句可以这样2种修饰
即int const *p 和int * const p
前者修饰*p,意味着不能改变p解引用后的值,即不能通过解引用p去修改p指向的内存的值。
后者修饰的是p,表示p是常量,即可以通过p改变p指向的内存的值,但并不能改变p这个指针本身的值,即p的指向不能改变。
如果int a定义为 const int a;那么这个p只能定义成int const *p;
因为const int a代表不能直接或间接的对a修改,直接就是直接修改a,当然是不行的。间接呢?间接就是把a的地址或者引用泄露出去,让其他高权限的指针通过解引用进行修改。这种显然不符合我们对const的要求,编译器是要杜绝的。
还有一种情况就是这样的。
Int a=10;
Const int *p1=&a;
Int *p2=p1;
a是可以修改的,然后定义了一个const指针p1,它表示不能通过p1修改a的值,这属于权限缩小,当然是没有错误的。
可是第三句int *p2=p1;却是不对的,那是为什么?
首先可以通过权限来判断,第三句相当于把低权限的指针p1让高权限的指针p2使用,这是权限放大,显然有误。
可是有人会疑问,a不是可以修改呢?P2权限大修改a也不影响最终结果啊。
实际上是这样的。编译器在读到Const int *p1=&a这时已经认为p1的指向是个const int类型了,之后再和p1相等指针都应该是const int *类型,如果改成下面这种语序,表达的意思其实和上面一样,但能通过编译
Int a=10;
Int *p2=&a;
Const int *p1=&a;
二级指针和const 中需要注意下面这一种情况,和上面的很像。
Int a=10;
Const Int *p=&a;
Int **pp=&p;
这样是编译无法通过的。和
Int a=10;
Const int *p1=&a;
Int *p2=p1;
比,就是第三句不同,前者是Int **p=&p;后者是Int *p2=p1;
就是本来把p1赋值给另一个一级指针p2,变成了一个二级指针指向p。
为什么会编译不通过呢。上面也说了Const int *p=&a;
这句编译器读完后,就认为p1指向的是个const int 类型了,int **pp指向const int *p就会权限扩大,肯定就会失败。虽然 我们想着**pp修改a,a明明可以修改啊,你可以想成编译器并没有记忆力,先到int a了不会记住a的权限,只会记住Const int *p中p的属性,即使pp和a有合理的关联,但无法通过p这关。也就是常说的间接修改。这也算编译器怕你违反 它认为的规则 的一种预防吧。
(2)Const 与函数重载
之前说过函数重载,对于参数来说,const是否能构成一种新的参数类型呢?
就是
Fx(int a);
Fx(const int a);这俩是否能构成重载呢?
答案是不能的。为什么不能,你可以理解为,如果const没有修饰*指针或&引用,那么其类型其实并没有变化。什么意思呢?
Int * const p;只是修饰了p,并没有修饰*号,即p自己随意怎么固定,反正int *p和int *const p最终表示的是一样的,有人会说明显不一样啊,一个p可以改,一个p不可以改。再次强调我说的是对参数,其表现就是究竟对实参的值有影响,反正最终传进来的都是p,管他p怎么固定呢,只要p的解引用都可以修改,权限是一样的,我就认为他俩一样。
下面这就不能构成重载
Fx(int * a);
Fx(int *const a); 最终fx函数里都可以*a=10,请问有什么不同吗。
但是下面这俩就能构成重载
Fx(int * a);
Fx(const int * a); 最终fx里,前者能*a=10,后者*a=10是非法的,这显然等于是2种参数,自然可以构成重载。
(3) const 与 &引用
由于&紧跟着引用变量名,因此就不会出现 int &const b这种东西
我们只用看看 int const &b
在这之前我们先看看普通引用 即 非const引用 和 各种量的结合
①引用和普通变量 这是可以的√
Int a=10;
Int &b=a;
②引用和常量 这是不行的×
Const int a=10;
Int &b=a;
首先我们可以看到权限都是扩大的,怎么引用。
其次通过原理来看,编译时a就变成了立即数10,语句转化过来就是
Int &b=10;这显然是不对的,引用本质是地址,怎么对10取地址?
③引用和立即数 这是不行的×
Int &b=10;
和②理由一样,引用本质是地址,怎么对10取地址?
然后我们看看const和&结合,也就是所谓的常引用
①常引用和普通变量 这是可以的√
Int a=10;
Int const &b=a;
相当于一个缩小权限的指针指向a,合情合理
②常引用和常量 这是可以的√
Const Int a=10;
Int const &b=a;
相当于一个等权限的指针指向a,合情合理
②常引用和立即数 这是可以的√
Int const &b=10;
这个可能有点让人觉得怪异,不是说b本质是指针吗。指针怎么给10取地址。
的确,这里的引用和指针有些区别。
Const Int *p=&10;显然是不对的,立即数前面怎么加取地址都是错误的。
可是引用却可以,巧就巧在引用不必&10,就不会错误。
并且常引用碰到立即数有特殊的解决方法。
我们来看汇编。
const int &b=10;
00056EAE mov dword ptr [ebp-14h],0Ah
00056EB5 lea eax,[ebp-14h]
00056EB8 mov dword ptr [b],eax
这三句汇编意思是,将立即数10赋值到ebp栈底指针上20字节开始的地方,赋值4个字节。然后把那栈底上20个字节那块区域的内存地址放入eax寄存器,最后把这个地址在赋值给常引用b的底层指针。
相当于什么意思呢?就是开辟了一个临时区域,把立即数存入到了那个临时区域,然后常引用相当于指针,指针指向的是那个临时区域。
(4)const & *与函数
引用 函数返回值
定义函数:
int fx()
{
int a=10;
Return a;
}
Int main()
{
Int &a=fx();
}
这是不行的。为什么,因为当函数返回值小于8个字节的时候,返回值是由寄存器带回的,这里就具体为eax带回的值,带回的是个立即数,这是个普通引用,无法引用立即数的。
Const int &a=fx();这样使用常引用就可以。
指针指向函数返回值
int fx()
{
int a=10;
Return a;
}
Int main()
{
Int *p=&fx();
}
这就相当于int *p=&10;显然是错误,也不会像引用一样会开辟临时区域存储立即数返回值。
Int* fx()
{
Static int a=10;
Return &a;
}
Int main()
{
Int *p=fx();
}
如果如上这样定义呢,即fx里有个数据a,返回a的地址,用寄存器带回,是个立即数,相当于就是 int *const p ,赋值给int *p当然是可以的。
那么如果返回的是指针能被引用吗??如下
Int* fx()
{
Static int a=10;
Return &a;
}
Int main()
{
Int * &b=fx();
}
返回值是个立即数地址,相当于int *const p,
而b的引用相当于个二级指针int **pp,没有任何的const修饰,真面目就是int *pp=&p,想一想就知道不可以了!
怎么修改。
是不是换成常引用就可以了?
Int * const &b=fx();
最后说返回值是引用
例如
Int & fx()
{
Static int a=10;
Return a;
}
Int main()
{
Int a=fx();
}
这就相当于用寄存器返回了指向数据a的地址,然后main中的fx()直接就把寄存器解引用*eax,然后main中用a接受了数据a的内容。