原文地址 http://www.cnblogs.com/yc_sunniwell/archive/2010/07/14/1777416.html
c const
C/C中const关键字详解为什么使用const采用符号常量写出的代码更容易维护指针常常是边读边移动而不是边写边移动许多函数参数是只读不写的。const最常见用途是作为数组的界和switch分情况标号(也可以用枚举符代替)分类如下
常变量 const 类型说明符 变量名
常引用 const 类型说明符 类名 const 对象名
常成员函数 类名::fun(形参) const
常数组 类型说明符 const 数组名[大小]
常指针 const 类型说明符* 指针名 类型说明符* const 指针名
首先提示的是在常变量const 类型说明符 变量名、常引用const 类型说明符 、常对象类名 const 对象名、 常数组类型说明符 const 数组名[大小] const” 与 “类型说明符”或“类名”其实类名是一种自定义的类型说明符 的位置可以互换。如
const int a5; 与 int const a5; 等同
类名 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 对象的指针。
用法3const修饰函数传入参数 将函数传入参数声明为const以指明使用这种参数仅仅是为了效率的原因而不是想让调用函数能够修改对象的值。同理将指针参数声明为const函数将不修改由这个参数所指的对象。 通常修饰指针参数和引用参数void Fun( const A *in); //修饰指针型传入参数void Fun(const A //修饰引用型传入参数
用法4修饰函数返回值 可以阻止用户修改返回值。返回值也要相应的付给一个常量或常指针。
用法5const修饰成员函数(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
*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
#include
void func(const int *pi)
{
//这里相当于重新构建了一个指针指向相同的内存区域。当然就可以通过该指针修改内存中的值了。
int* pp (int*)pi;
*pp 100;
}
int main()
{
using namespace std;
int* p new int(10);
cout <<"*p " <<*p < func(p); cout <<"*p " <<*p < delete p; return 0; } (二)常量与引用 常量与引用的关系稍微简单一点。因为引用就是另一个变量的别名它本身就是一个常量。也就是说不能再让一个引用成为另外一个变量的别名, 那么他们只剩下代表的内存区域是否可变。即 int i 10; // 正确表示不能通过该引用去修改对应的内存的内容。 const int i; // 错误不能这样写。 int i; 由此可见如果我们不希望函数的调用者改变参数的值。最可靠的方法应该是使用引用。下面的操作会存在编译错误 void func(const int不能通过i去改变它所代表的内存区域。 i 100; } int main() { int i 10; func(i); return 0; } 这里已经明白了常量与指针以及常量与引用的关系。但是有必要深入的说明以下。在系统加载程序的时候系统会将内存分为4个区域堆区 栈区全局区静态和代码区。从这里可以看出对于常量来说系统没有划定专门的区域来保护其中的数据不能被更改。也就是说使用常量的方式对数据进行保护是通过编译器作语法限制来实现的。我们仍然可以绕过编译器的限制去修改被定义为“常量”的内存区域。看下面的代码 const int i 10; // 这里i已经被定义为常量但是我们仍然可以通过另外的方式去修改它的值。 // 这说明把i定义为常量实际上是防止通过i去修改所代表的内存。 int *pi (int*) (三)常量函数 常量函数是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.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 "< // 这里我们根据this指针重新定义了一个指向同一块内存地址的指针。 // 通过这个新定义的指针我们仍然可以修改对象的状态。 Fred* pFred (Fred*)this; pFred->intValue 50; cout <<"Fred::inspect() called. intValue "< } 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 #include using namespace std; class Fred{ public: void func() const; void func(); }; void Fred::func() const { cout <<"const function is called."< } void Fred::func() { cout <<"non-const function is called."< } void UserCode(Fred fred.func(); cout <<"cFred is const object, and the result of cFred.func() is:" < 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 {size1100, size2 200 }; int array1[size1]; int array2[size2]; } 枚举常量不会占用对象的存储空间他们在编译时被全部求值。但是枚举常量的隐含数据类型是整数其最大值有限且不能表示浮点数。 3. const修饰指针的情况见下式 int b 500; const int* a 那么恭喜你你已经迈出了可喜的一步。不知道也没关系我们可以参考《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 f; // 这样作e只能访问声明为const的函数而不能访问一般的成员函数 [思考1] 以下的这种赋值方法正确吗 const A* cnew A(); A* e c; [思考2] 以下的这种赋值方法正确吗 A* const c new A(); A* b c; 5. 另外const 的一些强大的功能在于它在函数声明中的应用。在一个函数声明中const可以修饰函数的返回值或某个参数对于成员函数还可以修饰是整个函数。有如下几种情况以下会逐渐的说明用法A(const A void fun0(const A* a ); void fun1( ) const; // fun1( ) 为类成员函数 const A fun2( ); 1) 修饰参数的const如 void fun0(const A* a ); void fun1(const A 调用函数的时候用相应的变量初始化const常量则在函数体中按照const所修饰的部分进行常量化如形参为const A*a则不能对传递进来的指针的内容进行改变保护了原指针所指向的内容如形参为const A则不能对传递进来的引用对象进行改变保护了原对象的属性。 [注意]参数const通常用于参数为指针或引用的情况且只能修饰输入参数;若输入参数采用“值传递”方式由于函数将自动产生临时变量用于复制该参数该参数本就不需要保护所以不用const修饰。 [总结]对于非内部数据类型的输入参数因该将“值传递”的方式改为“const引用传递”目的是为了提高效率。例如将void Func(A a)改为void Func(const A 不要将“值传递”的方式改为“const引用传递”。否则既达不到提高效率的目的又降低了函数的可理解性。例如void Func(int x)不应该改为void Func(const int 如const A fun2( ); const A* fun3( ); 这样声明了返回值后const按照"修饰原则"进行修饰起到相应的保护作用。const Rational operator*(const Rational } 返回值用const修饰可以防止允许这样的操作发生:Rational a,b; Radional c; (a*b) c; 一般用const修饰返回值为对象本身非引用和指针的情况多用于二目操作符重载函数并产生新对象的时候。 [总结] 1. 一般情况下函数的返回值为某个对象时如果将其声明为const时多用于操作符的重载。通常不建议用const修饰函数的返回值类型为某个对象或对某个对象引用的情况。原因如下如果返回值为某个对象为constconst A test A实例或某个对象的引用为constconst A A实例则返回值具有const属性则返回实例只能访问类A中的公有保护数据成员和const成员函数并且不允许对其进行赋值操作这在一般情况下很少用到。 2. 如果给采用“指针传递”方式的函数返回值加const修饰那么函数返回值即指针的内容不能被修改该返回值只能被赋给加const 修饰的同类型指针。如 const char * GetString(void); 如下语句将出现编译错误 char *strGetString(); 正确的用法是 const char *strGetString(); 3. 函数返回值采用“引用传递”的场合不多这种方式一般只出现在类的赙值函数中目的是为了实现链式表达。如 class A {… A (const A //负值函数 }A a,b,c; //a,b,c为A的对象 … abc; //正常 (ab)c; //不正常但是合法 若负值函数的返回值加const修饰那么该返回值的内容不允许修改上例中abc依然正确。(ab)c就不正确了。[思考3] 这样定义赋值操作符重载函数可以吗 const A(const 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参数列表中的const的用法正确而当这样连续赋值的时侯问题就出现了 A a,b,c: (ab)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 也就是说在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只做简单的宏替换并不提供类型检查. 转载于:https://www.cnblogs.com/wangkangluo1/archive/2011/07/09/2101848.html