目录
一、const在c语言和c++中的区别
在c语言的学习中,我们都接触过const的使用规则,在c语言中,const所修饰的变量称为常变量,而常变量的处理是在编译阶段判断长变量又没有做左值,其他与普通变量处理相同。详情请见const规则详解(c语言版)。
而在c++中,const所修饰的变量称为常量,在编译阶段将用到的常量替换成常量初始化的值。
二、const修饰的变量称为常量,具有以下的特点:
- 一定要初始化(因为在编译阶段要进行替换);
- 不能有修改常量内存块的风险;
- const修饰的全局变量的符号是一个local的属性;
- 用变量来初始化常量,常量退化为常变量;
代码一:(1、2)
const int a = 10;
const int *p = &a;//错误,即该句为*p = &a;
//a在编译阶段会被替换为10,10会被存在寄存器中,对它不能进行取地址
*p = 30;//错误
代码二:(3)
“test.cpp”
extern const int gdata = 10;//存放在.data段,是一个global属性
“main.cpp”
extern const int gdata;//"UND" 声明一个外部符号
int main()
{
const int a = gdata;//引用错误
}
代码四:(4)
int a = 10;
int arr[a] ;//正确 在编译阶段a被替换为数字10
int b = 10;
const int c = b;//a退化为常变量
int arr[c];//错误 常变量不能用来给出数组的下标
那么,在了解到const在c++中的使用规则之后,接下来,进一步了解一下const与指针、引用的结合的使用规则。
三、const与指针、引用的结合
(一)const与指针的结合
如果const位于*的左边,则const就是用来修饰指针所指向的变量,即指针指向为常量;
如果const位于*的右边,const就是修饰指针本身,即指针本身是常量。
const int a = 10;
const int *p = &a;//定义指向常量的指针p,可以改变指针的指向,但是不能通过指针来改变 x中的值
int * const p2= &a;//定义常指针p2,不可改变指针的指向(常指针),但可以通过指针来修改x中的值
const int * const p3=&x;//不可以修改指针的指向,也不能修改通过指针修改变量的值
1、一级指针
无const时的指针用法
int a = 10;
int *p = &a;
int *q = p;
这三句代码就是我们平常用到的指针的简单使用,p存放的是内存单元a的地址,而将q赋值为p,那么q也存放的是a的地址,p,q都会间接访问到内存块a,进而会对其值进行修改。
有const修饰时的使用:有const修饰:杜绝间接访问修改常量内存块的风险
(1)
const int a = 10;
int *p = &a;//错误
int *q = p;//错误
分析:首先由于变量a被const所修饰,其为一个常量。那么,根据const修饰的变量的特点可知,不能出现有修改常量内存块的风险。但是,在这里指针p可间接访问到常量内存块a,那么在第三句中,q也会间接访问到常量内存块,也就是说a存在被修改的风险。我们可以利用const修饰间接访问符,进而杜绝间接访问的可能,修改成以下就正确。
const int a = 10;
const int *p = &a;
const int *q = p;
(2)
int a = 10;
const int *p = &a;
int *q = p;//错误
分析:这里的直接访问为a,间接访问有*p 和*q,p的指向为常量,但是*p由于有const的修饰,以杜绝访问常量内存块修改的风险。然而,仍然可通过*q的指向来间接访问内存块修改,所以第三句代码是错的。可以通过给第三句加上const修饰来杜绝风险。修改如下:
int a = 10;
const int *p = &a;
const int *q = p;
(3)
int a = 10;
int * const p = &a;
int *q = p;
分析:const修饰的是p,那么p为一个常指针,直接访问a的有a,p。这里不存在间接访问。这几行代码是正确的。
(4)
int a = 10;
int *p = &a;
const int *q = p;
分析:这里的直接访问为a,*p和*q是间接访问,因为a是一个普通变量,可以通过*p去访问并修改其值。const修饰的是数据类型int*,因此这里的q指向的是一个常量内存块,存在风险但又有const修饰而被杜绝掉。因此这几行代码也是正确的。
(5)
int a = 10;
int *p = &a;
int const *q = p;
分析:前两行代码是指针的普通使用,在第三行中,q被const修饰,因此q是一个常指针,指向a,它没有被间接访问,不存在风险。这几行代码是正确的。
2.与二级指针的结合
无const修饰的二级指针的使用
int a = 10;
int *p = &a;
int **q = p;
这几句代码是二级指针的普通使用,p指向内存块a,q指向指针p的内存块通过p之间接修改a的值,通过q可间接修改p的指向和a的值。
2.const和二级指针的结合
(1)
const int a = 10;
int *P = &a;//错误
int **q = &p; //错误
分析:const修饰a,则a是常量内存块。那么,*p可以间接访问常量内存块a,因此存在修改的风险;而**q也可以间接访问到常量内存块a。那么,可以利用const来修饰,杜绝这样的风险。修改后的如下:
const int a = 10;
const int *P = &a;
const int **q = &p;
(2)
int a = 10;
const int *p = &a;
int **q = &p;
分析:const修饰类型int*,那么p的指向为常量。**q可间接访问到p的指向也就是常量内存块。因此,第三句存在错误。修改如下:
int a = 10;
const int *p = &a;
const int **q = &p;
(3)
int a = 10;
int *const p = &a;
int **q = &p;
分析:const直接修饰p,那么p是一个常指针,也就是说不能间接来修改p的指向。但是在第三句中,q为二级指针,对q进行一次解引用后会访问到p的内存地址,存在对p的指向进行修改的风险。修改如下:
int a = 10;
int *const p = &a;
int *const *q = &p;
(4)
int a = 10;
int *p = &a;
const int **q = &q;
分析:const修饰类型int**,那么就是说q的指向为常量,q的类型为const int **,而&p的类型为int **,在c++中,是不允许将int**转换为const int **。因为它之间存在修改的常量内存的风险。修改后的正确表示如下:
int a = 10;
int *p = &a;
const int *const *q = &q;
或者
int a = 10;
const int *p = &a;
const int **q = &q;
对于int**不能转换为const int **,我们看这几行代码:
const int a = 10;
int *P;
const int **q = &p;
*p = &a;//错误
**q = 20;
*q存在修改常量内存块的风险。
(5)
int a = 10;
int *p = &a;
int *const *q = &q;
分析:const修饰的是*q,直接访问是p,间接访问是*q,并且*q被const修饰,则不会存在修改的风险。该代码正确。
(6)
int a = 10;
int *p = &a;
int **const q = &q;
分析:const修饰q,直接访问为q,无间接访问。则无修改常量内存块的风险,该代码正确。
(二)const与引用的结合:
1、引用
(1)引用传入
- 表层:引用时内存块的别名
int a = 10;
int &b = a;
- 底层:c++中和指针的处理方式相同,即在用到引用变量的地方,系统会自动进行解引用。
上述代码就相当于:int a = 10;
int *b = &a;
(2)特点
- 引用必须初始化
int a = 10;
int &b ;//错误
int &b = a;//正确
- 引用初始化的变量一定要可以取地址
int a = 10;
int &b = 10;//错误
像10这样的立即数是存放在寄存器当中的,是不能对立即数取地址的;
- 引用是不可改变的
int a = 10;
int c = 20;
int &b = a;
&b = c;//错误
- 引用只能访问引用变量所引用的内存单元
int a = 10;
int c = 20;
int &b = a;
cout << b << endl;//10
cout << &b << endl;//&a
2、const与引用、与引用和指针的结合使用
(1)
int a = 10;
int &b = a;
int &c = 20;//错误
分析:这三行是对引用的一般使用,根据引用的特点是它的初始化的变量必须可以取地址,但20这样的立即数不可以。因此,可以利用const修饰,形成常引用,常引用是可以引用立即数的,此时将立即数存放到临时变量中,常引用来引用临时变量。正确的写法如下:
const int &c = 20;//引用的是临时量
(2)
const int a = 10;
int &b = a;//错误
分析:a是常量内存块,b和a的内存保持一致,存在修改的风险,加const杜绝。修改:const int & b = a;
(3)
int a = 10;
const int &b = a;//const int <== int
判断条件:左操作数的权限 <= 右操作数的权限
除去一种特殊情况即左const int *;对应的右为int *这是是错误的。
(4)
int a = 10;
int *p = &a;
int *&q = p;
分析:q是p的别名,p,q均为一级指针,左右操作权限相等,正确。
(5)
int a = 10;
const int *p = &a;
int *&q = p;//int * <= const int *错误
分析:第三句左操作数的权限大于右操作数的权限,错误。修改:
const int *&q = p;
(6)
int a = 10;
int *const p = &a;
int *&q = p;//左:int *,右:int * const 错误
分析:左操作数的权限大于右操作数的权限,错误。修改:
int *const &q= p;
(7)
int a = 10;
int * p = &a;
const int *&q = p;//左:const int * ;右:int *,特殊情况,错误
分析:隐藏的将int **转换为了const int **,这是错误的,有修改常量内存块的风险。修改:
int a = 10;
int * p = &a;
const int * const &q = p;
或:
int a = 10;
const int * p = &a;
const int * &q = p;
(8)
int a = 10;
int * p = &a;
int *const&q = p;//左:int *const ;右:int *
分析:左操作数的权限小于右操作数的权限,正确。
四、const、引用与形参的结合
形参加上const:
- 防止实参被修改;
- 可以用来引用立即数;
引用做形参和普通变量做形参的区别:
- 引用修改实参的值;
- 引用不能引用立即数,部分实参无法调用;
五、引用、指针与返回值的结合以及接受返回值时的使用
在这里我们应该明确一点,函数不能返回局部变量的地址或引用;
我们从以下三个例子来分别进行研究:
(1)
int getValue()
{
int tmp = 20;
return tmp;//有寄存器eax带回,所带回的值为tmp的值20
}
int main()
{
//const int& a = getValue();//int& a = eax;
//int b = getValue(); //int a = eax;
//int* p = &getValue();// int* p = &eax;错误表达
return 0;
}
(2)
int* getValue()
{
static int tmp = 20;
return tmp;//有寄存器eax带回,所带回的值为tmp的地址
}
int main()
{
int a = *getValue();//int a = *eax;即int a = &tmp;
int* p = getValue();//int *p = eax;
int&b = *getValue();//int &b = *eax;即int &b = tmp;
int*& pr = getValue();//int *&pr = eax;错误;可修改为:int*const& pr = getValue();
return 0;
}
(3)
int& getValue()
{
static int tmp = 20;
return tmp; //由寄存器带回,带回的值为tmp
}
int main()
{
int a = getValue();//int a = tmp;
int&b = getValue();//int &b= tmp;
int* p = &getValue();//int *p = &tmp;
return 0;
}