zoukankan      html  css  js  c++  java
  • const关键字

    【1】const关键字的作用?

    C++语言在C语言的基础上新增加了几点优化是很耀眼的。const算作其中之一。

    const直接可以取代C语言中的宏 #define。 const 是 constant 的缩写,“恒定不变”的意思。

    被const修饰的东西都受到强制保护,可以预防意外的修改,提高程序健壮性。

    记住一点:const修饰的变量都是“只读的”。

    【2】如何使用const?

    (1)限定符  声明变量  必须初始化

    作为一个限定符,const声明的变量,其在声明的同时必须初始化。

    示例代码如下:

    1     const int n = 5; // OK 
    2     const int m;  // error! 编译错误!

    (2)限定符  声明变量

    作为一个限定符,const声明的变量,其命运随之改变,即就是:此变量具有常性,只能被读,任何试图对其的修改是非法的。

    示例代码如下:

    1     const int n = 5;
    2     int m = 0;
    3     n = m; // error! 编译错误!
    4     m = n; // OK!

    (3)在另一个文件中引用const变量

    当在另一连接文件中引用const常量时,只需要声明,不可以再次赋值。

    示例代码如下:

    1     extern const int i;       // OK
    2     extern const int j = 10;    // error! 常量不可以被再次赋值。

    (4)便于进行类型检查

    const常量有数据类型,而宏常量没有数据类型。

    编译器可以对前者进行类型安全检查,而对后者只进行字符替换,没有类型安全检查。

    所以,后者在字符替换时可能会产生意料不到的错误(边际效应)。

    示例代码如下:

    1     const int max = 100;    // const常量
    2     
    3     #define max 10;     // 宏定义 (注意分号)

    (5)可以避免不必要的内存分配

    示例代码如下:

     1     #define STRING "abcdefghijklmn\n"
     2 
     3     const char string[] = "abcdefghijklm\n";
     4 
     5     printf(STRING);   // 为STRING分配了第一次内存
     6 
     7     printf(string);   // 为string一次分配了内存,以后不再分配
     8 
     9     printf(STRING);   // 为STRING分配了第二次内存
    10 
    11     printf(string);

    由于const定义常量从汇编的角度看,给出了对应的内存地址,而不是#define一样给出的是立即数。

    所以,const定义的常量在程序运行过程中只有一份拷贝,而#define定义的常量在内存中有若干个拷贝。

    (6)const的常量值一定不可以被修改?

    先看一段代码如下:

    1     const int n = 0;
    2     int* p = (int *)&n;
    3     *p = 100;

    通过强制类型转换,将地址赋给变量,再作修改即可以改变const常量值。

    (7)分清 数值常量 和 指针常量

    示例代码如下:

    1     int n = 10;
    2     const int cn = 20; // cn是常量,cn的值只读
    3     const int * cip = &cn;  // 指针cip所指内容是常量
    4     int * const icp = &n;    // 指针icp是常量,指针值不可改变,所指内容可改变
    5     const int * const cipc = &cn;  // 指针cipc是常量,所指内容也是常量
    6     cip = &n;   // OK
    7     *icp = 100;   // OK

    (8)const修饰类的数据成员

    示例代码如下:

    1 class A 
    2 {
    3     const int size; 
    4 };

    理解:const数据成员只在某个对象生存期内是常量,而对于整个类而言却是可变的。

    因为类可以创建多个对象,不同的对象其const数据成员的值可以不同。

    提醒:所以不能在类声明中初始化const数据成员,因为类的对象未被创建时,编译器不知道const数据成员的值是多少。

    注意:const数据成员的初始化只能在类的构造函数的初始化表中进行。

    要想建立在整个类中都恒定的常量,应该用类中的枚举常量来实现。

    示例代码如下:

     1 // 试图用const在类中实现常量是不可取的
     2 class A 
     3 {
     4     const int size = 100;    // error!
     5 
     6     int array[size];         // error! 未知的size!
     7 };
     8 
     9 // 正确的方式是利用枚举
    10 class A 
    11 {
    12     enum {size1 = 100, size2 = 200}; 
    13 
    14     int array1[size1];  // OK
    15 
    16     int array2[size2];  // OK
    17 
    18 };

    枚举常量不会占用对象的存储空间,他们在编译时被全部求值。

    但是,枚举常量的隐含数据类型是整数,其最大值有限,且不能表示浮点数。

    (9)const修饰指针

    示例代码如下:

    1 const int * a;   // [1]
    2 
    3 int const * a;   // [2]  
    4 
    5 int * const a;   // [3]
    6  
    7 const int * const a;   // [4]

    如果:const位于星号的左侧,则const就是用来修饰指针所指向的变量,即指针指向内容为常量;

    如果:const位于星号的右侧,const就是修饰指针本身,即指针变量是常量。

    如果:星号左右两侧均有const,那么指针的“两个值”同时被限定。即指针变量的值不可修改,同时指针指向的内容值不可修改。

    因此,可以分析为:

    [1] 和 [2]的情况相同,都是指针所指向的内容为常量(const放在变量声明符的位置无关),这种情况下不允许对内容进行更改操作,如不能*a = 具体数值;

    [3] 为指针本身是常量,而指针所指向的内容不是常量,这种情况下不能对指针本身进行更改操作,如a++是错误的;

    [4] 为指针本身和指向的内容均为常量。

    (10)const的初始化

    <1> 非指针const常量初始化的情况。代码如下:

    1 // 假使A是自定义类型
    2 A a;
    3 const A b = a;

    <2> 指针const常量初始化的情况。代码如下:

    1 A* pa = new A();
    2 const A * pb = pa;

    <3> 引用const常量初始化的情况。代码如下:

    1 A f;
    2 const A & e = f;  // 作为常对象,e对象只能访问声明为const的常函数,而不能访问一般的成员函数。

    (11)const一些强大的功能在于它对函数的修饰

    在一个函数声明中,const 可以修饰函数的返回值,或某个参数;对于成员函数,还可以修饰整个函数。

    有如下几种情况,以下会逐个说明用法:

    1 A & operator = (const A & a);  // 修饰函数参数(典型的用法)
    2  
    3 void fun0 (const A * a );     // 修饰函数的参数
    4 
    5 void fun1() const;    // 修饰类成员函数 
    6 
    7 const A fun2( );       // 修饰函数返回值

    <1> 修饰参数的const。示例代码:

    1 void fun0(const A* a ); 
    2 
    3 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修饰返回值。示例代码如下:

    1 const A fun2(); 
    2 
    3 const A* fun3(); 

    这样声明返回值后,const按照“修饰原则”进行修饰,起到相应的保护作用。

    示例代码如下:

    const Rational operator*(const Rational& lhs, const Rational& rhs) 
    { 
        return Rational(lhs.numerator() * rhs.numerator(), lhs.denominator() * rhs.denominator()); 
    } 

    返回值用const修饰可以防止这样的操作发生:

    1 Rational a, b; 
    2 
    3 Radional c; 
    4  
    5 (a * b) = c; 

    一般用const修饰返回值为对象本身(非引用和指针)的情况多用于二目操作符重载函数并产生新对象的时候。

    【3】使用const注意事项

    (1)一般情况下,函数的返回值为某个对象时,如果将其声明为const时,多用于操作符的重载。

    通常,不建议用const修饰函数的返回值类型为某个对象或对某个对象引用的情况。

    原因如下:如果返回值为某个对象const(const test A = A 实例) 或 某个对象的引用为const(const A & test = A实例),

    则返回值具有const属性,返回实例只能访问类A中的公有(保护)数据成员和const成员函数,并且不允许对其进行赋值操作,这在一般情况下很少用到。

    (2)如果给采用“指针传递”方式的函数返回值加const修饰,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给加const 修饰的同类型指针。

    示例代码如下:

    1 const char * GetString(void);  

    如下语句将出现编译错误:

    1 char *str = GetString();   //error!!!

    正确的用法是:

    1 const char *str = GetString();  //OK

    (3)函数返回值采用“引用传递”的场合不多,这种方式一般只出现在类的赋值函数中,目的是为了实现链式表达。

    示例代码如下:

     1 class A 
     2 {
     3 
     4 A &operate = (const A &other);  // 赋值函数 
     5 
     6 };
     7 
     8 A a, b, c;              // a, b, c为A的对象 
     9 
    10 a = b = c;              // 正常  
    11 
    12 (a = b) = c;            // 不正常,但是合法

    若赋值函数的返回值加const修饰,那么该返回值的内容不允许修改,上例中a = b = c依然正确。(a = b) = c就不正确了。

    【4】const修饰类成员函数作用

    const修饰类成员函数一般放在函数体后。形如:void fun() const; 

    任何不会修改数据成员的函数都应该声明为const类型。

    如果在编写const成员函数时,不慎修改了数据成员,或者调用了其他非const成员函数,编译器将报错,这大大提高了程序的健壮性。

    示例代码如下:

     1 class Stack 
     2 {  
     3 public: 
     4     void Push(int elem);
     5     int Pop(void); 
     6     int GetCount(void)  const;   // const 成员函数 
     7 
     8 private: 
     9     int m_num; 
    10     int m_data[100]; 
    11 }; 
    12 
    13 int Stack::GetCount() const 
    14 { 
    15     ++m_num;  // error! 编译错误,企图修改数据成员m_num
    16     Pop();    // error! 编译错误,企图调用非const函数
    17     return m_num;
    18 }

    【5】const使用建议有哪些?

    (1)要大胆的使用const,这将给你带来无尽的益处,但前提是你必须搞清楚原委。

    (2)要避免最一般的赋值操作错误,如将const变量赋值,具体可见思考题。

    (3)在参数中使用const应该使用引用或指针,而不是一般的对象实例,原因同上。

    (4)const在成员函数中的三种用法(参数、返回值、函数)要很好的使用。

    (5)不要轻易的将函数的返回值类型定为const。

    (6)除了重载操作符外一般不要将返回值类型定为对某个对象的const引用。

    思考1:以下的这种赋值方法正确吗?

    1 const A * cp = new A(); 
    2 
    3 A* pe = cp; 

    这种方法不正确。因为声明指针的目的是为了对其指向的内容进行改变,而声明的指针e指向的是一个常量,所以不正确;

    思考2:以下的这种赋值方法正确吗?

    1 A * const pc = new A(); 
    2  
    3 A* pb = pc; 

    这种方法正确,因为声明指针所指向的内容可变。

    思考3:这样定义赋值操作符重载函数可以吗? 

    const A & operator = (const A& a); 

    这种做法不正确。

    在const A::operator=(const A& a)中,参数列表中的const的用法正确。而当这样连续赋值的时侯,问题就出现了:

    1 A a, b, c;
    2 
    3 (a = b) = c;

    因为a.operator=(b) 的返回值是对a的const引用,不能再将c赋值给const常量。

    看到const 关键字,C++程序员首先想到的可能是const 常量。这可不是良好的条件反射。如果只知道用const 定义常量,那么相当于把火药仅用于制作鞭炮。

    const 更大的魅力是它可以修饰函数的参数、返回值,甚至函数体。

     

    作者:kaizen
    声明:本文版权归作者和博客园共有,欢迎转载。但未经作者同意必须保留此声明,且在文章明显位置给出本文链接,否则保留追究法律责任的权利。
    签名:顺序 选择 循环
  • 相关阅读:
    QuickContactBadge
    第一周——15选1
    UVA 10036 Divisibility
    POJ 3984 迷宫问题
    POJ 3258 River Hopscotch
    CodeForces 230A Dragons
    HDU 4450 Draw Something
    POJ 2485(PRIME算法)
    HDU 1213
    CodeForces 16E
  • 原文地址:https://www.cnblogs.com/Braveliu/p/2839817.html
Copyright © 2011-2022 走看看