const的最初动机
const的使用方法
使用const的好处是允许指定一种语义上的约束,即某种对象不能被修改,且由编译器具体实施这种约束。
const声明格式:const 类型名 对象名;
修饰普通变量,时期不能被随意修改
【注意】1.C++中的const为内部连接,即由const定义的常量仅在被定义的文件中才能看见,除非使用extern。一般情况下,编译器不为const分配空间(而extern则强制分配空间)。
2. 当定义一个常量(const)时,必须进行初始化,即赋初值给它,除非已经用extern最清楚说明。
可以使用const修饰数组,表示“常”数组,即数组的值不能被修改。例如
const int data[] = { 1, 2, 3 };//合法使用,定义一个常量数组
struct MyStruct
{
int i, j;
};
const MyStruct mList[] = { {1, 2}, {3, 4} };//定义一个结构体常量数组
data[0] = 10; //错误
mList[1].i = 4;//错误
同时,定义上述数组后,对常数组的一下引用也是错误的。
char cList[data[2]];//错误
float fList[mList[0].i];//错误
错误原因在于,在编译时编译器必须为数组分配固定大小的内存。而用const修饰的数组意味着“不能被改变”的一块存储区,但其值在编译期间不能被使用。
const与指针
const与指针的结合使用由两种情况:1.用const修饰指针,即修饰存储在指针里的地址;2.修饰指针指向的对象。
为了防止使用混乱,采用“就近原则”,即const距离哪个近就修饰哪个:若const距离变量近,则表示的含义为指向常量的指针;若const距离指针近,则表达的含义为指向变量的常指针。
指向常量的指针
定义格式:const 类型名* 指针变量名;
或者类型名 const* 指针变量名;
这两种格式是等价的。
const int data = 10;
const int *p = &a;
*p = 20; //错误
const int max = 20;
p = &max; //正确
表明p是一个指向const int的指针。指针所指向的单元不能修改,但是其本身为指针变量可以指向别的位置。
常指针
定义格式为:类型名 *const 指针名;
int i = 1;
int * const p = &i;
表明p是一个常指针,一个指向int类型的变量i的const指针,p必须用一个初值,它只能指向这个初始对象i,不能“被改变”而指向其他对象,但对象的值可以被修改。
同时,不仅可以使用一个常指针指向一个变量,也可以把非const对象变成const对象。例如:
int a = 10;
int *const p = &a; //可以用const指针指向一个非const对象
const int *const q = &a;//可以把const对象地址赋值给const对象指针
const与函数
函数与const的结合使用由两种方式:1.参数为const类型;2.返回值为const类型。
const类型的参数
定义格式:返回值类型 函数名称(const 类型 参数名,···)
例如:
void f(const int i){
i++;//错误
}
当c++在函数的参数使用引用时,需要特别注意对常量引用的使用。例如:
void f1(int&) {}
void f2(const int&) {}
int main() {
f1(1);//错误,在f1()中,可以修改参数内容,而1为常量
f2(1);//正确,在f2()中,参数声明为常量
int n = 10;
const int m = 11;
f1(n);//正确
f1(m);//错误,不能使用普通引用方式引用常量
f2(n);//正确,可以用常量方式引用普通常量
f2(m);//正确
return 0;
}
const类型的返回值
可以用const修饰函数的返回值,即函数返回一个常量值,此常量值即可以赋给常量(对常量初始化),也可以赋给变量。其对返回值为某个类对象时,会显得尤为重要。
常对象的使用:
class MyClass
{
public:
MyClass(int i = 0):data(i){}
void seti(int i) {
data = i;
}
private:
int data;
};
MyClass test1() {
return MyClass();//返回为普通对象
}
const MyClass test2() {
return MyClass();//返回为常对象
}
int main() {
test1() = MyClass(10);//正确,test1()返回一个MyClass对象,并把对象MyClass(10)的值赋给它
test1().seti(20);//正确,调用test1(),得到一个返回对象,并调用此对象的成员函数seti()
test2() = MyClass(10);//错误,常对象不能被修改(赋值)
test2().seti(20);//错误,常对象不能被修改
return 0;
}
const与类
const在类里有两种应用:1.是在类里建立类内局部常量,可用在常量表达式中,而常量表达式在编译期间被求职;2.是const和类成员函数的结合使用。
类内const局部常量
在一个类内使用const修饰的意思就是“在这个对象的寿命期内,这是一个常量。”然而,对这个常量来讲,每个不同的对象可以含有一个不同的值。
在类内建立一个const成员时不能赋初值,只能在构造函数里面对其赋初值,而且要放在构造函数的特殊地方,因为const必须在创建它的地方被初始化,所以在构造函数的主体里,const成员必须已被初始化。例如:
class MyClass
{
public:
MyClass():num(100){}
private:
const int num;
};
常用的一种情况就是,在类内声明一个常量,用这个常量来定义数组的大小,把数组的大小隐藏在类内。
错误示范:
class MyClass
{
public:
MyClass();
private:
const int num = 100;//错误
int data[100];//错误
};
因为在类内进行存储空间分配时,编译器无法知道const的内容是什么,所以不能把它用于编译期间的常量。
解决这种问题的方法有两种:
- 静态常量。为提高效率保证所有的类对象最多只有一份拷贝值,通常需要将常量声明为是静态的。例如:
class MyClass{
static const int num = 100;
int mList[num];
};
- enum(枚举常量)。例如:
class MyClass{
enum{num = 100};
int data[num];
};
常对象与常成员函数
声明一个“复杂”的对象为常量,对于公有数据这点很容易做到,然而对于私有数据,该如何保证每个成员函数的调用不改变呢?需要声明这个成员函数为const类型,等同于告诉编译器此类的一个const对象可以调用这个成员函数,而const对象调用非const成员函数。
const成员函数定义格式:class 类名{返回值类型 成员函数名称(参数列表) const;};
【注意】若在函数的前面加上const,则表明函数返回值为const,为防止混乱,应把const放在函数的后面。在一个const成员函数里,试图改变任何数据成员或调用非const成员函数,编译器都将显示出错误信息。
例如:const成员函数与非const成员函数使用方式的比较。
//student.h
#ifndef STUDENT_H_//防止重复包含头文件student.h
#define STUDENT_H_
class Student{
int No;
char Name[20];
public:
Student();
int GetNo() const;//const成员函数
const char* GetName();//返回值为const的成员函数,不是const成员函数
};
#endif
//student.cpp
#include <string.h>
#include <student.h>
Student::Student(){
No = 1;
strcpy(Name, "wang");
}
int Student::GetNo()const{
return No;
}
const char* Student::GetName(){
return Name;
}
//test.cpp
#include <student.h>
int main(){
Student s1;
s1.GetNo();
s1.GetName();
const Student s2;
s2.GetNo();//正确,常对象调用const成员函数
s2.GetName();//错误,常对象调用了非const成员函数
return 0;
然而,有时候我们需要修改对象的某些数据成员时该怎么办?有两种方法.
1.强制转换。在常成员函数中修改成员变量的值。
class Test{
int i, j;
public:
Test():i(0),j(0){};
void f()const;
};
void Test f()const{
i = 1; //错误,在常成员函数中修改类成员
((Test*)this)->j = 5; //正确
}
int main(){
const Test t;
t.f();//正确
return 0;
}
- 使用mutable,用它指定某个特定的数据成员在常量对象的某个函数里是可以被修改的。在常成员函数中修改有mutable所修改的变量值。
class Test{
int i;
mutable int j;
public:
Test():i(0),j(0){};
void f()const;
};
void Test f()const{
i = 1; //错误,在常成员函数中修改类成员
j = 5; //正确,可以在常成员函数中修改被mutable修饰的类成员
}
int main(){
const Test t;
t.f();//正确
return 0;
}