C/C++要点全掌握(三)——const

         

        上接:C/C++要点全掌握(二)

 

11、const辨析

 

        const 是一个“左结合”的修饰符,一般与左侧类型标识符结合声明只读变量(常量);指针修饰符(*)是一个“右结合”修饰符,一般与右侧变量名结合声明指针(在定义指针时可将”*p”看作一个整体,前面类型即为*p内容的数据类型),其优先级高于const与类型修饰符。如果const与类型修饰符(如int)二者直接相连,中间没有指针修饰符(*),那么const和类型修饰符(如int)的前后顺序可以任意。

 

(一)常见常量的两种定义方法及分析

 

       (1)a为整型常量,a不能为左值(即a不能出现在赋值运算符“=”左边),故a的值不会改变:

int  const  a=1;

const  int  a=1;

a=2;//错误

       (2) *p为整型常量,*p不能为左值。p为指向i的指针,不能通过*p修改i的值,但直接给i赋值会改变*p的值,可以改变p的值使其不指向i。

int i=1,j;

int  const  *p=&i; 

const  int  *p=&i;

*p=2;//错误

i=2;//此时*p为2

p=&j;//p指向j

       (3)p为整型指针常量,p不能为左值。p为指向i的指针常量,不能修改p的指向,可通过*p修改i的值。

int  i=1, j;

int*  const  p=&i;

const  pInt  p=&i;//typedef  int*  pInt;

p=&j;//错误

*p=2;//此时i为2

       (4)*p为整型常量,p为整型指针常量,*p和p都不能为左值。不能修改p的指向,也不能通过*p修改i的值。

int i=1, j;
int  const*  const  p=&i;
const  int*  const  p=&i;
p=&j;//错误*p=2;//错误

       (5)常量数组。Arr[i]不能为左值。

const int arr[5]={1,2,3,4,5};

int const arr[5]={1,2,3,4,5};

 arr[0]=11;//错误

 *(arr+1)=22;//错误

 

(二)const使用注意

 

       (1)const必须初始化

const int a=5;

const int j;//编译警告“使用了未初始化的局部变量”

int k=j;//运行出错

       (2) const指针可以接受const和非const的地址,但是非const指针只能接受非const的地址。

const int a=1;

int* p=&a;// 不能用一个非常量指针(p)指向常量(a)

       (3)在前面介绍堆栈时已提到给字符指针初始化的字符串是存在于常量区,故不能利用字符指针对其修改。

char *p="tht";

p[0]='2';//编译时没错,运行时访问冲突

       (4)在另一连接文件中声明引用const常量时不能赋值。

extern const int i; //合法

extern const int j=10;//非法,常量不可以被再次赋值

 

(三)const与函数

 

       (1) const修饰形参

void  func1(const int *p);//指针(C/C++)

void  func2(const int &i);//引用(C++)

void  func3(const A &a);//A为自定义类型

       如果该参数作传出参数用(相对与高级语言的out关键字,显然该参数不能“按值传递”),不论它是什么数据类型,也不论它采用“指针传递”还是“引用传递”,都不能加const修饰,否则该参数将不能作为左值,进而不能通过它将所需结果传出。

       const只能修饰传入参数

       如果传入参数采用“指针传递”(函数func1),那么加const 修饰可以防止意外地改动该指针所指的内容,起到保护作用。如果传入参数采用“引用传递”(函数func2),则const修饰可以保护该参数。如果传入参数采用“值传递”,由于函数将自动产生临时变量用于复制该参数,该参数本来就无需保护,所以不用加const 修饰。

       对于非基本数据类型的参数而言,像“void Func(A a)”这样声明的函数注定效率比较底。因为函数体内将产生A 类型的临时对象用于复制参数a,而临时对象的构造、复制、析构过程都将消耗时间。为了提高效率,可以将函数声明改为“void Func(A &a)”,因为“引用传递”仅借用一下参数的别名而已,不需要产生临时对象。但是函数“void Func(A &a)”存在一个缺点:“引用传递”有可能改变实参,这是我们不期望的。解决这个问题很容易,加const修饰即可(函数func3)。

       对于内部数据类型的参数,不存在构造、析构的过程,而复制也非常快,故不要将“值传递”的方式改为“const 引用传递”。否则既达不到提高效率的目的,又降低了函数的可理解性。

       (2) const修饰返回值

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

const char* getStr();//声明
char* str=getStr();//错误
const char * str=getStr();//正确

 

       如果函数返回值采用普通值传递方式,由于函数会把返回值复制到外部临时的存储单元中,加const 修饰没有任何价值。

       如果返回值不是基本数据类型,将函数“A GetA()”改写为“const A &  GetA()”(引用方式)的确能提高效率。但此时千万千万要小心,一定要搞清楚函数究竟是想返回一个对象的“拷贝”还是仅返回“别名”就可以了,否则程序会出错。函数返回值采用“引用传递”的场合并不多,这种方式一般只出现在类的赋值函数中,目的是为了实现链式表达。

class A
{

  const A & operate = (const A &other); // 赋值函数

};

A a, b, c; // a, b, c 为A 的对象

c=new A();

a = b = c; //正确(正常的链式赋值)

(a = b) = c; //错误(不正常的链式赋值)

      如果将赋值函数的返回值去掉const 修饰,上例中,正常的链式赋值语句“a = b = c”仍然正确,不正常的链式赋值语句“(a = b) = c”也合法。

 

       (3)const修饰成员函数

       任何不会修改数据成员的函数都应该声明为const 类型,表明这个函数不会对这个类对象的数据成员(准确地说是非静态数据成员)作任何改变。如果在编写const 成员函数时,不慎修改了数据成员,或者调用了其它非const 成员函数,编译器将指出错误,这无疑会提高程序的健壮性。声明const 成员函数,const关键字只能放在函数声明的尾部。在设计类的时候,一个原则就是对于不改变数据成员的成员函数都要在后面加const。

class A

{

   private:

   int m_data;

   public:

   int getData() const;// const成员函数,只读不修改数据成员

   void setData(int data);/普通函数

};

        const成员函数使用需注意:

        A. const对象只能访问const成员函数,而非const对象可以访问任意的成员函数,包括const成员函数。

A a1;

const A a2=a1; 

a1.setData(1);//正确

a1.getData();//正确

a2.setData(2);//错误,const对象不能访问非const成员函数

a2.getData();//正确

       B. const成员函数不可以修改对象的数据,不管对象是否具有const性质。它在编译时,以是否修改成员数据为依据,进行检查。

       C. 然而加上mutable修饰符的数据成员,对于任何情况下通过任何手段都可修改,自然此时的const成员函数是可以修改它的。

 

(四)const与类的数据成员

 

       上面介绍了const修饰类的成员函数,这里顺便提下const修饰类的数据成员。const数据成员只在某个对象生存期内是常量,而对于整个类而言却是可变的。因为类可以创建多个对象,不同的对象其const数据成员的值可以不同。所以不能在类声明中初始化const数据成员,因为类的对象未被创建时,编译器不知道const 数据成员的值是什么。

class  A

{

public:

  int m_data;

  const  int  m_data=1; //编译错误,只有静态常量整型数据成员才可以在类中初始化

  ...  
};

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

class  A

{

public:

  int  m_data;

  const  int  m_const; 

  A(int data=1,int initConst=0);

};

//构造函数中初始化表中初始化const数据成员

A::A(int data,int initConst):m_const(initConst)

{

   m_data=data;

}

 

(五)const与函数重载

 

       const(或volatile)修饰参数类型时,函数重载的辨析。

       (1)当参数的类型以const或volatile修饰时,在识别函数声明是否相同时,并不考虑const或volatile修饰符,只看参数变量的类型。

//不构成重载

void fun(int a);//整型变量

void fun(const int a);//整型变量,只是该变量在函数体内是只读的

       (2)以下两个函数声明构成重载,因为参数是不同的类型,前者指向整型的指针,后者是指向常整型的指针,是不同类型的指针。

void fun(int *a);// 整型指针

void fun(const int *a); //常整型指针

       (3)以下两个函数声明不构成重载,两者均是指向整型的指针,只是后者用const修饰了指针,所以不构成重载。

void fun(int *a); // 整型指针

void fun(int *const a); 整型常指针

       (4)这里顺便提下,对于成员函数和加了const修饰的成员函数,显然从语法上无法区分调用哪个,但可以编译运行,测试结果显示程序调用了没加const修饰的成员函数。

#include<iostream>

using namespace std;

class A
{
public:
   void fun() const;
   void fun();
};

void A::fun() const
{
   cout<<"const";
}
void A::fun()
{
   cout<<"no const";
}
void main()
{
   A a;
   a.fun();//输出:no const
}



      下接:C/C++要点全掌握(四)

 

 

 

 

 

 

阅读更多
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/tht2009/article/details/6868613
个人分类: C/C++
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭