为何使用const?采用符号常量写出的代码更容易维护;指针经常是边读边移动,而不是边写边移动;许多函数参数是只读不写的。const最多见用途是做为数组的界和switch分状况标号(也能够用枚举符代替),分类以下:html
常变量: const 类型说明符 变量名ios
常引用: const 类型说明符 &引用名c++
常对象: 类名 const 对象名数组
常成员函数: 类名::fun(形参) const安全
常数组: 类型说明符 const 数组名[大小] 函数
常指针: const 类型说明符* 指针名 ,类型说明符* const 指针名学习
首先提示的是:在常变量(const 类型说明符 变量名)、常引用(const 类型说明符 &引用名)、常对象(类名 const 对象名)、 常数组(类型说明符 const 数组名[大小]), const” 与 “类型说明符”或“类名”(其实类名是一种自定义的类型说明符) 的位置能够互换。如:this
const int a=5; 与 int const a=5; 等同spa
类名 const 对象名 与 const 类名 对象名 等同指针
用法1:常量
取代了C中的宏定义,声明时必须进行初始化(!c++类中则否则)。const限制了常量的使用方式,并无描述常量应该如何分配。若是编译器知道了某const的全部使用,它甚至能够不为该const分配空间。最简单的常见状况就是常量的值在编译时已知,并且不须要分配存储。―《C++ Program Language》
用const声明的变量虽然增长了分配空间,可是能够保证类型安全。
C标准中,const定义的常量是全局的,C++中视声明位置而定。
用法2:指针和常量
使用指针时涉及到两个对象:该指针自己和被它所指的对象。将一个指针的声明用const“预先固定”将使那个对象而不是使这个指针成为常量。要将指针自己而不是被指对象声明为常量,必须使用声明运算符*const。
因此出如今 * 以前的const是做为基础类型的一部分:
char *const cp; //到char的const指针
char const *pc1; //到const char的指针
const char *pc2; //到const char的指针(后两个声明是等同的)
从右向左读的记忆方式:
cp is a const pointer to char. 故pc不能指向别的字符串,但能够修改其指向的字符串的内容
pc2 is a pointer to const char. 故*pc2的内容不能够改变,但pc2能够指向别的字符串
且注意:容许把非 const 对象的地址赋给指向 const 对象的指针,不容许把一个 const 对象的地址赋给一个普通的、非 const 对象的指针。
用法3:const修饰函数传入参数
将函数传入参数声明为const,以指明使用这种参数仅仅是为了效率的缘由,而不是想让调用函数可以修改对象的值。同理,将指针参数声明为const,函数将不修改由这个参数所指的对象。
一般修饰指针参数和引用参数:
void Fun( const A *in); //修饰指针型传入参数
void Fun(const A &in); //修饰引用型传入参数
用法4:修饰函数返回值
能够阻止用户修改返回值。返回值也要相应的付给一个常量或常指针。
用法5:const修饰成员函数(c++特性)
const对象只能访问const成员函数,而非const对象能够访问任意的成员函数,包括const成员函数;
const对象的成员是不能修改的,而经过指针维护的对象确实能够修改的;
const成员函数不能够修改对象的数据,无论对象是否具备const性质。编译时以是否修改为员数据为依据进行检查。
具体展开来说:
(一). 常量与指针
常量与指针放在一块儿很容易让人迷糊。对于常量指针和指针常量也不是全部的学习C/C++的人都能说清除。例如:
const int *m1 = new int(10);
int* const m2 = new int(20);
在上面的两个表达式中,最容易让人迷惑的是const究竟是修饰指针仍是指针指向的内存区域?其实,只要知道:const只对它左边的东西起做用,惟一的例外就是const自己就是最左边的修饰符,那么它才会对右边的东西起做用。根据这个规则来判断,m1应该是常量指针(即,不能经过m1来修改它所指向的内容。);而m2应该是指针常量(即,不能让m2指向其余的内存模块)。因而可知:
1. 对于常量指针,不能经过该指针来改变所指的内容。即,下面的操做是错误的:
int i = 10;
const int *pi = &i;
*pi = 100;
由于你在试图经过pi改变它所指向的内容。可是,并非说该内存块中的内容不能被修改。咱们仍然能够经过其余方式去修改其中的值。例如:
// 1: 经过i直接修改。
i = 100;
// 2: 使用另一个指针来修改。
int *p = (int*)pi;
*p = 100;
实际上,在将程序载入内存的时候,会有专门的一块内存区域来存放常量。可是,上面的i自己不是常量,是存放在栈或者堆中的。咱们仍然能够修改它的值。而pi不能修改指向的值应该说是编译器的一个限制。
2. 根据上面const的规则,const int *m1 = new int(10);咱们也可写做:
int const *m1 = new int(10);
这是,理由就不须做过多说明了。
3. 在函数参数中指针常量时表示不容许将该指针指向其余内容。
void func_02(int* const p)
{
int *pi = new int(100);
//错误!P是指针常量。不能对它赋值。
p = pi;
}
int main()
{
int* p = new int(10);
func_02(p);
delete p;
return 0;
}
4. 在函数参数中使用常量指针时表示在函数中不能改变指针所指向的内容。
void func(const int *pi)
{
//错误!不能经过pi去改变pi所指向的内容!
*pi = 100;
}
int main()
{
int* p = new int(10);
func(p);
delete p;
return 0;
}
咱们可使用这样的方法来防止函数调用者改变参数的值。可是,这样的限制是有限的,做为参数调用者,咱们也不要试图去改变参数中的值。所以,下面的操做是在语法上是正确的,可是可能破还参数的值:
#include <iostream>
#include <string>
void func(const int *pi)
{
//这里至关于从新构建了一个指针,指向相同的内存区域。固然就能够经过该指针修改内存中的值了。
int* pp = (int*)pi;
*pp = 100;
}
int main()
{
using namespace std;
int* p = new int(10);
cout << "*p = " << *p << endl;
func(p);
cout << "*p = " << *p << endl;
delete p;
return 0;
}
(二):常量与引用
常量与引用的关系稍微简单一点。由于引用就是另外一个变量的别名,它自己就是一个常量。也就是说不能再让一个引用成为另一个变量的别名, 那么他们只剩下表明的内存区域是否可变。即:
int i = 10;
// 正确:表示不能经过该引用去修改对应的内存的内容。
const int& ri = i;
// 错误!不能这样写。
int& const rci = i;
因而可知,若是咱们不但愿函数的调用者改变参数的值。最可靠的方法应该是使用引用。下面的操做会存在编译错误:
void func(const int& i)
{
// 错误!不能经过i去改变它所表明的内存区域。
i = 100;
}
int main()
{
int i = 10;
func(i);
return 0;
}
这里已经明白了常量与指针以及常量与引用的关系。可是,有必要深刻的说明如下。在系统加载程序的时候,系统会将内存分为4个区域:堆区 栈区全局区(静态)和代码区。从这里能够看出,对于常量来讲,系统没有划定专门的区域来保护其中的数据不能被更改。也就是说,使用常量的方式对数据进行保护是经过编译器做语法限制来实现的。咱们仍然能够绕过编译器的限制去修改被定义为“常量”的内存区域。看下面的代码:
const int i = 10;
// 这里i已经被定义为常量,可是咱们仍然能够经过另外的方式去修改它的值。
// 这说明把i定义为常量,其实是防止经过i去修改所表明的内存。
int *pi = (int*) &i;
(三):常量函数
常量函数是C++对常量的一个扩展,它很好的确保了C++中类的封装性。在C++中,为了防止类的数据成员被非法访问,将类的成员函数分红了两类,一类是常量成员函数(也被称为观察着);另外一类是很是量成员函数(也被成为变异者)。在一个函数的签名后面加上关键字const后该函数就成了常量函数。对于常量函数,最关键的不一样是编译器不容许其修改类的数据成员。例如:
class Test
{
public:
void func() const;
private:
int intValue;
};
void Test::func() const
{
intValue = 100;
}
上面的代码中,常量函数func函数内试图去改变数据成员intValue的值,所以将在编译的时候引起异常。
固然,对于很是量的成员函数,咱们能够根据须要读取或修改数据成员的值。可是,这要依赖调用函数的对象是不是常量。一般,若是咱们把一个类定义为常量,咱们的本意是但愿他的状态(数据成员)不会被改变。那么,若是一个常量的对象调用它的很是量函数会产生什么后果呢?看下面的代码:
class Fred{
public:
void inspect() const;
void mutate();
};
void UserCode(Fred& changeable, const Fred& unChangeable)
{
changeable.inspect(); // 正确,很是量对象能够调用常量函数。
changeable.mutate(); // 正确,很是量对象也容许修改调用很是量成员函数修改数据成员。
unChangeable.inspect(); // 正确,常量对象只能调用常理函数。由于不但愿修改对象状态。
unChangeable.mutate(); // 错误!常量对象的状态不能被修改,而很是量函数存在修改对象状态的可能
}
从上面的代码能够看出,因为常量对象的状态不容许被修改,所以,经过常量对象调用很是量函数时将会产生语法错误。实际上,咱们知道每一个成员函数都有一个隐含的指向对象自己的this指针。而常量函数则包含一个this的常量指针。以下:
void inspect(const Fred* this) const;
void mutate(Fred* this);
也就是说对于常量函数,咱们不能经过this指针去修改对象对应的内存块。可是,在上面咱们已经知道,这仅仅是编译器的限制,咱们仍然能够绕过编译器的限制,去改变对象的状态。看下面的代码:
class Fred{
public:
void inspect() const;
private:
int intValue;
};
void Fred::inspect() const
{
cout << "At the beginning. intValue = "<< intValue << endl;
// 这里,咱们根据this指针从新定义了一个指向同一块内存地址的指针。
// 经过这个新定义的指针,咱们仍然能够修改对象的状态。
Fred* pFred = (Fred*)this;
pFred->intValue = 50;
cout << "Fred::inspect() called. intValue = "<< intValue << endl;
}
int main()
{
Fred fred;
fred.inspect();
return 0;
}
上面的代码说明,只要咱们愿意,咱们仍是能够经过常量函数修改对象的状态。同理,对于常量对象,咱们也能够构造另一个指向同一块内存的指针去修改它的状态。这里就不做过多描述了。
另外,也有这样的状况,虽然咱们能够绕过编译器的错误去修改类的数据成员。可是C++也容许咱们在数据成员的定义前面加上mutable,以容许该成员能够在常量函数中被修改。例如:
class Fred{
public:
void inspect() const;
private:
mutable int intValue;
};
void Fred::inspect() const
{
intValue = 100;
}
可是,并非全部的编译器都支持mutable关键字。这个时候咱们上面的歪门邪道就有用了。
关于常量函数,还有一个问题是重载。
#include <iostream>
#include <string>
using namespace std;
class Fred{
public:
void func() const;
void func();
};
void Fred::func() const
{
cout << "const function is called."<< endl;
}
void Fred::func()
{
cout << "non-const function is called."<< endl;
}
void UserCode(Fred& fred, const Fred& cFred)
{
cout << "fred is non-const object, and the result of fred.func() is:" << endl;
fred.func();
cout << "cFred is const object, and the result of cFred.func() is:" << endl;
cFred.func();
}
int main()
{
Fred fred;
UserCode(fred, fred);
return 0;
}
输出结果为:
fred is non-const object, and the result of fred.func() is:
non-const function is called.
cFred is const object, and the result of cFred.func() is:
const function is called.
从上面的输出结果,咱们能够看出。当存在同名同参数和返回值的常量函数和很是量函数时,具体调用哪一个函数是根据调用对象是常量对像仍是很是量对象来决定的。常量对象调用常量成员;很是量对象调用很是量的成员。
总之,咱们须要明白常量函数是为了最大程度的保证对象的安全。经过使用常量函数,咱们能够只容许必要的操做去改变对象的状态,从而防止误操做对对象状态的破坏。可是,就像上面看见的同样,这样的保护实际上是有限的。关键仍是在于咱们开发人员要严格的遵照使用规则。另外须要注意的是常量对象不容许调用很是量的函数。这样的规定虽然很武断,但若是咱们都根据原则去编写或使用类的话这样的规定也就彻底能够理解了。
(四):常量返回值
不少时候,咱们的函数中会返回一个地址或者引用。调用这获得这个返回的地址或者引用后就能够修改所指向或者表明的对象。这个时候若是咱们不但愿这个函数的调用这修改这个返回的内容,就应该返回一个常量。这应该很好理解,你们能够去试试。
+++++++++++++++++++++++++++++++++++++++
c++ 中const
+++++++++++++++++++++++++++++++++++++++
1. const常量,如const int max = 100;
优势:const常量有数据类型,而宏常量没有数据类型。编译器能够对前者进行类型安全检查,而对后者只进行字符替换,没有类型安全检查,而且在字符替换时可能会产生意料不到的错误(边际效应)
2. const 修饰类的数据成员。如:
class A
{
const int size;
…
}
const数据成员只在某个对象生存期内是常量,而对于整个类而言倒是可变的。由于类能够建立多个对象,不一样的对象其const数据成员的值能够不一样。因此不能在类声明中初始化const数据成员,由于类的对象未被建立时,编译器不知道const 数据成员的值是什么。如
class A
{
const int size = 100; //错误
int array[size]; //错误,未知的size
}
const数据成员的初始化只能在类的构造函数的初始化表中进行。要想创建在整个类中都恒定的常量,应该用类中的枚举常量来实现。如
class A
{…
enum {size1=100, size2 = 200 };
int array1[size1];
int array2[size2];
}
枚举常量不会占用对象的存储空间,他们在编译时被所有求值。可是枚举常量的隐含数据类型是整数,其最大值有限,且不能表示浮点数。
3. const修饰指针的状况,见下式:
int b = 500;
const int* a = & [1]
int const *a = & [2]
int* const a = & [3]
const int* const a = & [4]
若是你能区分出上述四种状况,那么,恭喜你,你已经迈出了可喜的一步。不知道,也不要紧,咱们能够参考《Effectivec++》Item21上的作法,若是const位于星号的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量;若是const位于星号的右侧,const就是修饰指针自己,即指针自己是常量。所以,[1]和[2]的状况相同,都是指针所指向的内容为常量(const放在变量声明符的位置无关),这种状况下不容许对内容进行更改操做,如不能*a = 3;[3]为指针自己是常量,而指针所指向的内容不是常量,这种状况下不能对指针自己进行更改操做,如a++是错误的;[4]为指针自己和指向的内容均为常量。
4. const的初始化
先看一下const变量初始化的状况
1) 非指针const常量初始化的状况:A b;
const A a = b;
2) 指针const常量初始化的状况:
A* d = new A();
const A* c = d;
或者:const A* c = new A();
3)引用const常量初始化的状况:
A f;
const A& e = f; // 这样做e只能访问声明为const的函数,而不能访问通常的成员函数;
[思考1]: 如下的这种赋值方法正确吗?
const A* c=new A();
A* e = c;
[思考2]: 如下的这种赋值方法正确吗?
A* const c = new A();
A* b = c;
5. 另外const 的一些强大的功能在于它在函数声明中的应用。在一个函数声明中,const能够修饰函数的返回值,或某个参数;对于成员函数,还能够修饰是整个函数。有以下几种状况,如下会逐渐的说明用法:A&operator=(const A& a);
void fun0(const A* a );
void fun1( ) const; // fun1( ) 为类成员函数
const A fun2( );
1) 修饰参数的const,如 void fun0(const A* a ); void fun1(const A& a);
调用函数的时候,用相应的变量初始化const常量,则在函数体中,按照const所修饰的部分进行常量化,如形参为const A*a,则不能对传递进来的指针的内容进行改变,保护了原指针所指向的内容;如形参为const A&a,则不能对传递进来的引用对象进行改变,保护了原对象的属性。
[注意]:参数const一般用于参数为指针或引用的状况,且只能修饰输入参数;若输入参数采用“值传递”方式,因为函数将自动产生临时变量用于复制该参数,该参数本就不须要保护,因此不用const修饰。
[总结]对于非内部数据类型的输入参数,因该将“值传递”的方式改成“const引用传递”,目的是为了提升效率。例如,将void Func(A a)改成void Func(const A &a)
对于内部数据类型的输入参数,不要将“值传递”的方式改成“const引用传递”。不然既达不到提升效率的目的,又下降了函数的可理解性。例如void Func(int x)不该该改成void Func(const int &x)
2) 修饰返回值的const,如const A fun2( ); const A* fun3( );
这样声明了返回值后,const按照"修饰原则"进行修饰,起到相应的保护做用。const Rational operator*(const Rational& lhs, const Rational& rhs)
{
return Rational(lhs.numerator() * rhs.numerator(),
lhs.denominator() * rhs.denominator());
}
返回值用const修饰能够防止容许这样的操做发生:Rational a,b;
Radional c;
(a*b) = c;
通常用const修饰返回值为对象自己(非引用和指针)的状况多用于二目操做符重载函数并产生新对象的时候。
[总结]
1. 通常状况下,函数的返回值为某个对象时,若是将其声明为const时,多用于操做符的重载。一般,不建议用const修饰函数的返回值类型为某个对象或对某个对象引用的状况。缘由以下:若是返回值为某个对象为const(const A test = A实例)或某个对象的引用为const(const A& test = A实例),则返回值具备const属性,则返回实例只能访问类A中的公有(保护)数据成员和const成员函数,而且不容许对其进行赋值操做,这在通常状况下不多用到。
2. 若是给采用“指针传递”方式的函数返回值加const修饰,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给加const 修饰的同类型指针。如:
const char * GetString(void);
以下语句将出现编译错误:
char *str=GetString();
正确的用法是:
const char *str=GetString();
3. 函数返回值采用“引用传递”的场合很少,这种方式通常只出如今类的赙值函数中,目的是为了实现链式表达。如:
class A
{…
A &operate = (const A &other); //负值函数
}
A a,b,c; //a,b,c为A的对象
…
a=b=c; //正常
(a=b)=c; //不正常,可是合法
若负值函数的返回值加const修饰,那么该返回值的内容不容许修改,上例中a=b=c依然正确。(a=b)=c就不正确了。
[思考3]: 这样定义赋值操做符重载函数能够吗?
const A& operator=(const A& a);
6. 类成员函数中const的使用
通常放在函数体后,形如:void fun() const;
任何不会修改数据成员的函数都因该声明为const类型。若是在编写const成员函数时,不慎修改了数据成员,或者调用了其余非const成员函数,编译器将报错,这大大提升了程序的健壮性。如:
class Stack
{
public:
void Push(int elem);
int Pop(void);
int GetCount(void) const; //const 成员函数
private:
int m_num;
int m_data[100];
};
int Stack::GetCount(void) const
{
++m_num; //编译错误,企图修改数据成员m_num
Pop(); //编译错误,企图调用非const函数
Return m_num;
}
7. 使用const的一些建议
1) 要大胆的使用const,这将给你带来无尽的益处,但前提是你必须搞清楚原委;
2) 要避免最通常的赋值操做错误,如将const变量赋值,具体可见思考题;
3) 在参数中使用const应该使用引用或指针,而不是通常的对象实例,缘由同上;
4) const在成员函数中的三种用法(参数、返回值、函数)要很好的使用;
5) 不要轻易的将函数的返回值类型定为const;
6) 除了重载操做符外通常不要将返回值类型定为对某个对象的const引用;
[思考题答案]
1) 这种方法不正确,由于声明指针的目的是为了对其指向的内容进行改变,而声明的指针e指向的是一个常量,因此不正确;
2) 这种方法正确,由于声明指针所指向的内容可变;
3) 这种作法不正确;
在const A::operator=(const A& a)中,参数列表中的const的用法正确,而当这样连续赋值的时侯,问题就出现了:
A a,b,c:
(a=b)=c;
由于a.operator=(b)的返回值是对a的const引用,不能再将c赋值给const常量。
++++++++++++++++++++++++++++++++++++++++
const 在c和c++中的区别 http://tech.e800.com.cn/articles/2009/722/1248229886744_1.html
++++++++++++++++++++++++++++++++++++++++
1. C++中的const正常状况下是当作编译期的常量,编译器并不为const分配空间,只是在编译的时候将期值保存在名字表中,并在适当的时候折合在代码中.因此,如下代码:
using namespace std;
int main()
{
const int a = 1;
const int b = 2;
int array[ a + b ] = {0};
for (int i = 0; i < sizeof array / sizeof *array; i++)
{
cout << array << endl;
}
}
在能够经过编译,而且正常运行.但稍加修改后,放在C编译器中,便会出现错误:
int main()
{
int i;
const int a = 1;
const int b = 2;
int array[ a + b ] = {0};
for (i = 0; i < sizeof array / sizeof *array; i++)
{
printf("%d",array);
}
}
错误消息:
c:\test1\te.c(8): error C2057: 应输入常数表达式
c:\test1\te.c(8): error C2466: 不能分配常数大小为 0 的数组
出现这种状况的缘由是:在C中,const是一个不能被改变的普通变量,既然是变量,就要占用存储空间,因此编译器不知道编译时的值.并且,数组定义时的下标必须为常量.
2. 在C语言中: const int size; 这个语句是正确的,由于它被C编译器看做一个声明,指明在别的地方分配存储空间.但在C++中这样写是不正确的.C++中const默认是内部链接,若是想在C++中达到以上的效果,必需要用extern关键字.即C++中,const默认使用内部链接.而C中使用外部链接.
(1) 内链接:编译器只对正被编译的文件建立存储空间,别的文件可使用相同的表示符或全局变量.C/C++中内链接使用static关键字指定.
(2) 外链接:全部被编译过的文件建立一片单独存储空间.一旦空间被建立,链接器必须解决对这片存储空间的引用.全局变量和函数使用外部链接.经过extern关键字声明,能够从其余文件访问相应的变量和函数.
/* C++代码 header.h */
const int test = 1;
/* C++代码 test1.cpp */
#include "header.h"
using namespace std;
int main() { cout << "in test1 :" << test << endl; }
/* C++代码 test2.cpp */
#include "header.h"
using namespace std;
void print() { cout << "in test2:" << test << endl;}
以上代码编译链接彻底不会出问题,但若是把header.h改成:
extern const int test = 1;
在链接的时候,便会出现如下错误信息:
test2 error LNK2005: "int const test" (?test@@3HB) 已经在 test1.obj 中定义
由于extern关键字告诉C++编译器test会在其余地方引用,因此,C++编译器就会为test建立存储空间,再也不是简单的存储在名字表里面.因此,当两个文件同时包含header.h的时候,会发生名字上的冲突.
此种状况和C中const含义类似:
/* C代码 header.h */
const int test = 1;
/* C代码 test1.c */
#include "header.h"
int main() { printf("in test1:%d\n",test); }
/* C代码 test2.c */
#include "header.h"
void print() { printf("in test2:%d\n",test); }
错误消息:
test3 fatal error LNK1169: 找到一个或多个多重定义的符号
test3 error LNK2005: _test 已经在 test1.obj 中定义
也就是说:在c++ 中const 对象默认为文件的局部变量。与其余变量不一样,除非特别说明,在全局做用域声明的 const 变量是定义该对象的文件的局部变量。此变量只存在于那个文件中,不能被其余文件访问。经过指定 const 变动为 extern,就能够在整个程序中访问 const 对象:
// file_1.cc
// defines and initializes a const that is accessible to other files
extern const int bufSize = fcn();
// file_2.cc
extern const int bufSize; // uses bufSize from file_1
// uses bufSize defined in file_1
for (int index = 0; index != bufSize; ++index)
// ...
3. C++中,是否为const分配空间要看具体状况.若是加上关键字extern或者取const变量地址,则编译器就要为const分配存储空间.
4. C++中定义常量的时候再也不采用define,由于define只作简单的宏替换,并不提供类型检查.
很是感谢博主的分享http://www.cnblogs.com/yc_sunniwell/archive/2010/07/14/1777416.html