第3课 进化后的 const分析
1. C语言中的const变量(const修饰变量)
(1)const修饰的变量是只读的,使得变量具有只读属性,但本质还是变量
(2)const定义的并不是真正的常量,它只是告诉编译器该变量不能出现在赋值符号的左边。
(3)const修饰的局部变量在栈上分配空间,全局变量在只读存储区分配空间
(4)const只在编译期有用,在运行期无用
【编程实验】C/C++中的const 3-1.cpp
#include <stdio.h> int main() { const int c = 0; //C语言中会为变量c分配内存 int* p = (int*)&c; //C++中遇到&才为c分配内存 printf("Begin...\n"); *p = 5; //内存中的值己被改为5. printf("c = %d\n", c);//C语言会输出内存中的5. //C++中会从符号表(而不是内存)中取值,所以为0 printf("End...\n"); return 0; }
运行结果:
2. C++中的const变量(const修饰变量)
(1)C++在C语言的基础上对const进行了优先处理,当碰见const声明时在符号表中放入常量。
(2)编译过程中若发现使用常量,则直接以符号表中的值替换。
(3)C++编译过程中若发现对const常量使用了extern或&操作符,则会给对应的常量分配存储空间。
(4)注意:C++编译器虽然可能为const常量分配空间,但不会使用其存储空间中的值。
3. 对比C/C++中的const
|
C语言 |
C++ |
本质 |
只读变量 |
常量 |
分配内存 |
会分配 |
可能分配存储空间
|
4. C++中的const与宏的区别(const常量类似于宏定义)
C++中的const |
宏 |
|
定义 |
const int c = 5; |
#define c 5 |
处理方式 |
由编译器处理,编译器会进行类型检查和作用域检查 |
由预处理器处理,只是简单的文本替换 |
【编程实验】const常量与宏定义 3-2.cpp
#include <stdio.h> void f() { //宏由预编译处理,其后面的宏起作用 #define a 3 const int b = 4;//作用域仅限于f函数 } void g() { printf("a = %d\n", a); //合法,只要是宏定义之后都可以使用 //printf("b = %d\n", b); //非法b的作用域仅限于f函数 } int main() { const int A = 1; const int B = 2; int array[A + B] = {0};//C++中合法,因为它认为A、B都是常量。 //而C语言的const本质还是变量,数组大小只能是常量 int i = 0; for(i=0;i<(A + B); i++) { printf("array[%d] = %d\n", i, array[i]); } f(); g(); return 0; }
运行结果:
5. 小结
(1)与C语言不同,C++中的const不是只读变量
(2)C++中的const是一个真正意义上的常量
(3)C++编译器可能会为const常量分配空间
(4)C++完全兼容C语言中的const常量的语法特性
补充:
6.const指针(const修饰指针)
(1)几种情况
①const int* p; //p可变,p指向的内容不可变
②int const* p; //p可变,p指向的内容不可变
③int* const p; //p不可变,p指向的内容可变
④const int* const p; //p不可变,p指向的内容不可变
(2)口诀:左数右指
①当const出现在*号的左边时,指针指向的数据为常量
②当const出现在*号的右边时,指针本身为常量
【实例分析】常量与指针
#include <stdio.h> int main() { int i = 0; const int* p1 = &i; int const* p2 = &i; int* const p3 = &i; const int* const p4 = &i; *p1 = 1; // compile error p1 = NULL; // ok *p2 = 2; // compile error p2 = NULL; // ok *p3 = 3; // ok p3 = NULL; // compile error *p4 = 4; // compile error p4 = NULL; // compile error return 0; }
7. const对象(const修饰对象)
(1)const关键字能够修饰对象,使之能为只读对象,即成员变量不允许被改变。
(2)只读对象是编译阶段的概念,在运行时无效。
8. const成员函数
(1)const成员函数中不能直接改写成员变量的值。
(2)const对象只能调用const成员函数。如拷贝构造函数里只能调用const成员函数
(3)const成员函数只能调用const成员函数
(4)const成员函数的定义:
Type ClassName::func(Type p) const{}; //声明与定义都必须带const关键字
【编程实验】类的const函数初探
#include <stdio.h> class Test { private: int mi; public: Test(int i); Test(const Test& t);//t是const对象(的引用),函数内只能调用const成员函数 int getMI() const; //const成员函数 void print() //非const成员函数 { printf("mi = %d\n", mi); } void show() const { //print(); //错误,const成员函数不能调用非const的成员函数 printf("mi = %d\n", mi); } }; Test::Test(int i) { mi = i; } Test::Test(const Test& t) { } int Test::getMI() const { //mi = 2; //const成员函数不能改变成员变量的值! return mi; } int main() { const Test t(1); //const对象 t.getMI(); //正确,const对象只能调用const成员函数 t.show(); //正确,const对象只能调用const成员函数 //t.print(); //错误,const对象不能调用非const成员函数 return 0; }
9. const函数重载
函数重载
函数重载的定义是:在相同的作用域中,如果函数具有相同名字而仅仅是形参表不同,此时成为函数重载。注意函数重载不能基于不同的返回值类型进行重载。
注意函数重载中的“形参表”不同,是指本质不同,不要被一些表象迷惑。main函数不能被重载。
下面三组定义本质是相同的,不是重载:
1)int sum (int &a); 和 int sum (int &);
2) int sum (int a) 和 int sum (const int a);
3)typedef int DD;
int sum(int a); 和 int sum (DD a);
其中第二个例子对于非引用传参,形参是否const是等价的。但是当使用引用传参时,有无const是不同的。使用指针传参时,指向const对象的指针和指向非const对象的指针做形参的函数是不同的。
*下面谈论一个比较恶心的问题,基于const的重载。
在类中会有这样一种重载,它是合法的。
Class A {
int function ();
int function () const;
};
可以看到在A类中,function函数是发生重载了,而且是合法的。而且在调用时,只用A类的const对象才能调用const版本的function函数,而非const对象可以调用任意一种,通常非const对象调用不是const版本的function函数。
原因是:按照函数重载的定义,函数名相同而形参表有本质不同的函数称为重载。在类中,由于隐含的this形参的存在,const版本的function函数使得作为形参的this指针的类型变为指向const对象的指针,而非const版本的使得作为形参的this指针就是正常版本的指针。此处是发生重载的本质。重载函数在最佳匹配过程中,对于const对象调用的就选取const版本的成员函数,而普通的对象调用就选取非const版本的成员函数。
(注:this指针是一个const指针,地址不能改,但能改变其指向的对象或者变量)
我们从一个例子说起,来看一下String类, 我们为它提供一个下标操作符([ ])以读写指定位置的字符(char)。
只要了解过C++的操作符重载的语法,很快就可以写出下面这个[]操作符重载函数:
char& operator[](int posion) // function_1
{
return data[posion];
};
注意,这里该函数的返回值为一个引用,否则str[0] = 'c'这样的语句就会不合法,因为str[0]将是一个左值。
那么,是否提供这样一个function_1就足够了呢?看下面这段代码:
const String str= "She";
char c = str[0]; // 错误!编译提示:error C2678: 二进制“[” : 没有找到接受“const String”类型的左操作数的运算符(或没有可接受的转换)
很显然,我们必须还要为const String提供一个const版本的opeartor[]。如下:
char& operator[](int posion) const
{
return data[posion];
}
这样,当使用const的String对象使用[]操作符时,便会调用该const的重载版本。
但是,这样就OK了嘛?虽然上面的那段代码没有问题了,但是其中却隐藏了一个陷阱,看如下代码:
const String str = "She";
str[0] = 'T';
上面这段代码可以编译,运行通过,str变为了"The"!而str声明为const的!!
现在,你应该知道了,对于const的该操作符重载函数其返回值也应该是const的,否则就会出现可以通过其修改const对象的漏洞。修改如下:
const char& operator[](int posion) const
{
return data[posion];
}
好了,现在没有问题了!
我们再回过头来看一下,为了给String提供一个[]操作符来读写指定位置的字符,需要提供如下两个函数,以分别对非const String对象和const String对象提供支持:
char& operator[](int posion)
{
return data[posion];
};
const char& operator[](int posion) const
{
return data[posion];
}