const关键字在C语言中就有了,并不是C++新引入的。C++只是扩展了const的含义和用法。所以,这里我们分成C语言中的const和C++语言中的const两个部分来介绍。注意到,一般来说,C++语言中可以兼容C语言的用法,若是有例外,会特意标明。
一. C语言中的const
简言之,const关键字是一个修饰字,表明所修饰的变量是只读的,即只读变量(readonly)。注意区分只读变量与常量,它们是不同的。
通过下面这段代码能看出区别:
#define MAX (100) //MAX是通过预编译宏定义的常量
const int Max=100; // Max是只读变量
int a[MAX]; // 正确
int b[Max]; // 编译报错,因为静态数组的长度必须是常量
C语言中引入const关键字的目的:
我觉得目的很简单,就是为了适应只读这个概念。这在后面的用法中能体会出来。而一些网上的参考资料中,在说C语言中的const时,就将其与#define预编译宏做比较,认为用const能“节省空间,避免不必要的内存分配,同时提高效率”。我认为这种说法是不准确的。首先,就C语言中的const来说,并不能真正替换#define宏常量,这从上面的代码中就能看到。即使在两者都可以使用的上下文环境中,例如以下代码:
1 #difine M 3 //宏常量
2 const int N=5;//编译至此,并不分配内存给N
3 int i = N; //N的首次使用,编译至此,静态数据区分配内存存放N,栈数据区分配内存存放i,并完成赋值(初始化)
4 int I = M; //预编译期间已替换成立即数,只需在栈数据区分配内存存放i,并完成赋值(初始化)
5 int j = N; //N的再次使用,编译至此,只需在栈数据区分配内存给j并初始化,因为N在静态数据区已存在了
6 int J = M; //预编译期间替换成立即数(与第4句的立即数只是值相同,实质是两个不相干的数),其他同第4句
参考资料中认为,语句4和6在预编译期间进行宏替换需要分配内存,是没道理的,宏替换的本质是程序代码的替换,还没有进入编译环节,不存在内存分配一说。而认为语句5没有内存分配,也是不准确的。因为N确实不用再分配内存,但j作为栈变量,在定义的时候必然是要分配内存的。所以,从上面这6行代码来看:一、认为const节省空间,是指const定义的变量在静态数据区只有一份拷贝,而不像宏常量在预编译后散布在代码各处。这其实并不是真正的节省空间,更偏向于一种一致性的含义。二、认为避免了不必要的内存分配,就是无稽之谈了。const变量确实只要分配内存一次,但是宏常量在预编译后,就是立即数而已,根本不存在内存分配。三、认为能提高效率,这就更奇怪了。从编译效率来看,宏常量多了一步预编译,似乎效率低,但是考虑到真实程序中,即使你一个宏常量都不用,还是避免不了预编译这一步,因为还有其他宏呢,#include宏你不会不用吧。从运行效率来看,操作数是立即数,总归比操作数来自一个内存地址要快。所以所谓提高效率是不可能的了。
C语言中const关键字用法:
1.修饰一般变量
一般常量是指简单类型的只读变量。这种只读变量在定义时,修饰符const 可以用在类型说明符前,也可以用在类型说明符后。例如:
int const i=2; 或const int i=2;
2.修饰数组
定义或说明一个只读数组可采用如下格式:
int const a[5]={1, 2, 3, 4, 5};或
const int a[5]={1, 2, 3, 4, 5};
3.修饰指针
const int *p; // p 可变,p 指向的对象不可变
int const *p; // p 可变,p 指向的对象不可变
int *const p; // p 不可变,p 指向的对象可变
const int *const p; //指针p 和p 指向的对象都不可变
这里给出一个网上广为流传的记忆和理解的方法:
先忽略类型名(编译器解析的时候也是忽略类型名),我们看const 离哪个近,离谁近就修饰谁。
const int *p; //const 修饰*p,p 是指针,*p 是指针指向的对象,不可变
int const *p; //const修饰*p,p 是指针,*p 是指针指向的对象,不可变
int *const p; //const修饰p,p 不可变,p 指向的对象可变
const int *const p; //前一个const 修饰*p,后一个const 修饰p,指针p 和p 指向的对象都不可变
4.修饰函数的参数
const 修饰符也可以修饰函数的参数,当不希望这个参数值被函数体内意外改变时使用。例如:
void Fun(const int i); // 其实无意义,修饰指针才有意义
告诉编译器i 在函数体中的值不能改变。
5.修饰函数的返回值
const修饰函数返回值其实用的并不是很多,它的含义和const修饰普通变量以及指针的含义基本相同。
a.const int fun1() //这个其实无意义,因为参数返回本身就是赋值。
b.const int * fun2() //调用时 const int *pValue = fun2();
//我们可以把fun2()看作成一个变量,即指针内容不可变。
c.int* const fun3() //调用时 int * const pValue = fun2();
//我们可以把fun2()看作成一个变量,即指针本身不可变。
二. C++语言对const的扩展
首先,C++将const修饰的“只读变量”提升到了常量的高度。在C++中,定义常量已经被推荐放弃宏常量而用const了。也就是说,上面举例用的代码可以编译通过了:
#define MAX (100) //MAX是通过预编译宏定义的常量
const int Max=100; // Max在C++中是常量了
int a[MAX]; // 正确
int b[Max]; // 在C++中正确
既然已经是常量了,那么,C++编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率提高。
其次,因为C++中引入了引用类型,需要定义const和引用类型的搭配:
int i;
const int& ri = i; //对基本数据类型的const引用,表明使用引用时,不可修改应用目标
A f;
const A& rf = f; //对类或者结构类型的const引用,表明使用引用时,只能访问声明为const的成员
引用类型常用在函数入参中,const引用在函数入参中也扮演重要角色:
void function(const Class& Var); //引用参数在函数内不可以改变
void function(const int& Var); //引用参数在函数内为常量不可变
这样的一个const引用传递和最普通的函数按值传递的效果是一模一样的,他禁止对引用的对象的一切修改,唯一不同的是按值传递会先建立一个类对象的副本, 然后传递过去,而它直接传递地址,所以这种传递比按值传递更有效.
最后,也是最重要的,是在类中扩展const的使用。
1.const修饰成员变量
const修饰类的成员函数,表示成员常量,不能被修改,同时它只能在初始化列表中赋值。
class A
{
…
const int nValue; //成员常量不能被修改
…
A(int x): nValue(x) { } ; //只能在初始化列表中赋值
}
另外一种初始化常量成员的方法,是static和const并用。然后在类声明的外部初始化。
class A
{
public: A() {}
private: static const int i; //注意必须是静态的!
};
...
const int A::i=3;
2.const修饰成员函数
const修饰类的成员函数,则该成员函数不能修改类中任何成员,不能访问类中任何非const函数。一般写在函数的最后来修饰。
class A
{
…
void function()const; //常成员函数, 它不改变对象的成员变量.
//也不能调用类中任何非const成员函数。
}
任何不会修改数据成员的函数都应该声明为const类型。如果在编写const成员函数时,不慎修改了数据成员,或者调用了其他非const成员函数,编译器将报错,这大大提高了程序的健壮性。
另外,因为对于const类对象/指针/引用,只能调用类的const成员函数,因此,const修饰成员函数的最重要作用就是限制对于const对象的使用。
3.const修饰类对象/对象指针/对象引用
·const修饰类对象表示该对象为常量对象,其中的任何成员都不能被修改。对于对象指针和对象引用也是一样。
·const修饰的对象,该对象的任何非const成员函数都不能被调用,因为任何非const成员函数会有修改成员变量的企图。
例如:
class AAA
{
void func1();
void func2() const;
}
const AAA aObj;
aObj.func1(); ×
aObj.func2(); 正确
const AAA* aObj = new AAA();
aObj-> func1(); ×
aObj-> func2(); 正确
一. C语言中的const
简言之,const关键字是一个修饰字,表明所修饰的变量是只读的,即只读变量(readonly)。注意区分只读变量与常量,它们是不同的。
通过下面这段代码能看出区别:
#define MAX (100) //MAX是通过预编译宏定义的常量
const int Max=100; // Max是只读变量
int a[MAX]; // 正确
int b[Max]; // 编译报错,因为静态数组的长度必须是常量
C语言中引入const关键字的目的:
我觉得目的很简单,就是为了适应只读这个概念。这在后面的用法中能体会出来。而一些网上的参考资料中,在说C语言中的const时,就将其与#define预编译宏做比较,认为用const能“节省空间,避免不必要的内存分配,同时提高效率”。我认为这种说法是不准确的。首先,就C语言中的const来说,并不能真正替换#define宏常量,这从上面的代码中就能看到。即使在两者都可以使用的上下文环境中,例如以下代码:
1 #difine M 3 //宏常量
2 const int N=5;//编译至此,并不分配内存给N
3 int i = N; //N的首次使用,编译至此,静态数据区分配内存存放N,栈数据区分配内存存放i,并完成赋值(初始化)
4 int I = M; //预编译期间已替换成立即数,只需在栈数据区分配内存存放i,并完成赋值(初始化)
5 int j = N; //N的再次使用,编译至此,只需在栈数据区分配内存给j并初始化,因为N在静态数据区已存在了
6 int J = M; //预编译期间替换成立即数(与第4句的立即数只是值相同,实质是两个不相干的数),其他同第4句
参考资料中认为,语句4和6在预编译期间进行宏替换需要分配内存,是没道理的,宏替换的本质是程序代码的替换,还没有进入编译环节,不存在内存分配一说。而认为语句5没有内存分配,也是不准确的。因为N确实不用再分配内存,但j作为栈变量,在定义的时候必然是要分配内存的。所以,从上面这6行代码来看:一、认为const节省空间,是指const定义的变量在静态数据区只有一份拷贝,而不像宏常量在预编译后散布在代码各处。这其实并不是真正的节省空间,更偏向于一种一致性的含义。二、认为避免了不必要的内存分配,就是无稽之谈了。const变量确实只要分配内存一次,但是宏常量在预编译后,就是立即数而已,根本不存在内存分配。三、认为能提高效率,这就更奇怪了。从编译效率来看,宏常量多了一步预编译,似乎效率低,但是考虑到真实程序中,即使你一个宏常量都不用,还是避免不了预编译这一步,因为还有其他宏呢,#include宏你不会不用吧。从运行效率来看,操作数是立即数,总归比操作数来自一个内存地址要快。所以所谓提高效率是不可能的了。
C语言中const关键字用法:
1.修饰一般变量
一般常量是指简单类型的只读变量。这种只读变量在定义时,修饰符const 可以用在类型说明符前,也可以用在类型说明符后。例如:
int const i=2; 或const int i=2;
2.修饰数组
定义或说明一个只读数组可采用如下格式:
int const a[5]={1, 2, 3, 4, 5};或
const int a[5]={1, 2, 3, 4, 5};
3.修饰指针
const int *p; // p 可变,p 指向的对象不可变
int const *p; // p 可变,p 指向的对象不可变
int *const p; // p 不可变,p 指向的对象可变
const int *const p; //指针p 和p 指向的对象都不可变
这里给出一个网上广为流传的记忆和理解的方法:
先忽略类型名(编译器解析的时候也是忽略类型名),我们看const 离哪个近,离谁近就修饰谁。
const int *p; //const 修饰*p,p 是指针,*p 是指针指向的对象,不可变
int const *p; //const修饰*p,p 是指针,*p 是指针指向的对象,不可变
int *const p; //const修饰p,p 不可变,p 指向的对象可变
const int *const p; //前一个const 修饰*p,后一个const 修饰p,指针p 和p 指向的对象都不可变
4.修饰函数的参数
const 修饰符也可以修饰函数的参数,当不希望这个参数值被函数体内意外改变时使用。例如:
void Fun(const int i); // 其实无意义,修饰指针才有意义
告诉编译器i 在函数体中的值不能改变。
5.修饰函数的返回值
const修饰函数返回值其实用的并不是很多,它的含义和const修饰普通变量以及指针的含义基本相同。
a.const int fun1() //这个其实无意义,因为参数返回本身就是赋值。
b.const int * fun2() //调用时 const int *pValue = fun2();
//我们可以把fun2()看作成一个变量,即指针内容不可变。
c.int* const fun3() //调用时 int * const pValue = fun2();
//我们可以把fun2()看作成一个变量,即指针本身不可变。
二. C++语言对const的扩展
首先,C++将const修饰的“只读变量”提升到了常量的高度。在C++中,定义常量已经被推荐放弃宏常量而用const了。也就是说,上面举例用的代码可以编译通过了:
#define MAX (100) //MAX是通过预编译宏定义的常量
const int Max=100; // Max在C++中是常量了
int a[MAX]; // 正确
int b[Max]; // 在C++中正确
既然已经是常量了,那么,C++编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率提高。
其次,因为C++中引入了引用类型,需要定义const和引用类型的搭配:
int i;
const int& ri = i; //对基本数据类型的const引用,表明使用引用时,不可修改应用目标
A f;
const A& rf = f; //对类或者结构类型的const引用,表明使用引用时,只能访问声明为const的成员
引用类型常用在函数入参中,const引用在函数入参中也扮演重要角色:
void function(const Class& Var); //引用参数在函数内不可以改变
void function(const int& Var); //引用参数在函数内为常量不可变
这样的一个const引用传递和最普通的函数按值传递的效果是一模一样的,他禁止对引用的对象的一切修改,唯一不同的是按值传递会先建立一个类对象的副本, 然后传递过去,而它直接传递地址,所以这种传递比按值传递更有效.
最后,也是最重要的,是在类中扩展const的使用。
1.const修饰成员变量
const修饰类的成员函数,表示成员常量,不能被修改,同时它只能在初始化列表中赋值。
class A
{
…
const int nValue; //成员常量不能被修改
…
A(int x): nValue(x) { } ; //只能在初始化列表中赋值
}
另外一种初始化常量成员的方法,是static和const并用。然后在类声明的外部初始化。
class A
{
public: A() {}
private: static const int i; //注意必须是静态的!
};
...
const int A::i=3;
2.const修饰成员函数
const修饰类的成员函数,则该成员函数不能修改类中任何成员,不能访问类中任何非const函数。一般写在函数的最后来修饰。
class A
{
…
void function()const; //常成员函数, 它不改变对象的成员变量.
//也不能调用类中任何非const成员函数。
}
任何不会修改数据成员的函数都应该声明为const类型。如果在编写const成员函数时,不慎修改了数据成员,或者调用了其他非const成员函数,编译器将报错,这大大提高了程序的健壮性。
另外,因为对于const类对象/指针/引用,只能调用类的const成员函数,因此,const修饰成员函数的最重要作用就是限制对于const对象的使用。
3.const修饰类对象/对象指针/对象引用
·const修饰类对象表示该对象为常量对象,其中的任何成员都不能被修改。对于对象指针和对象引用也是一样。
·const修饰的对象,该对象的任何非const成员函数都不能被调用,因为任何非const成员函数会有修改成员变量的企图。
例如:
class AAA
{
void func1();
void func2() const;
}
const AAA aObj;
aObj.func1(); ×
aObj.func2(); 正确
const AAA* aObj = new AAA();
aObj-> func1(); ×
aObj-> func2(); 正确