C/C++中const的用法

const是C语言的关键字,经C++进行扩充,变得功能强大,用法复杂。const用于定义一个常变量(只读变量),当const与指针,引用,函数等结合起来使用时,情况会变得复杂的多。下面将从五个方面总结const的用法。

1.const位置

const位置较为灵活,一般来说,除了修饰一个类的成员函数外,const不会出现先一条语句的最后。示例如下:

#include <iostream>
using namespace std;
int main(int argc,char* argv[])
{
    int i=5;
    const int v1=1;
    int const v2=2;
    const int* p1;
    int const * p2;
    //以下三条语句报编译错误,为什么?
    //const * int p3;
    //int* const p3=&v1;
    //int * const p3;
    int * const p3=&i;
    const int* const p4=&v1;
    int const * const p5=&v2;
    const int & r1=v1;
    int const & r2=v2;
    //以下语句报编译错误,为什么?
    //const & int r3;
    //以下语句报警告,并忽略const
    int& const r4=i;

    cout<<*p4<<endl;
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

阅读以上程序,得出如下结论: 
(1)程序输出结果是1,以上程序演示const的位置与它的语义之间的关系,看似复杂, 
实际有规律可循。 
(2)const和数据类型结合在一起时形成所谓的“常类型”,利用常类型可申明或定义 
常变量。const用来修饰类型时,既可以放在类型前面,也可以放在类型后面,如const int i和int const i是合法且等价的。用常类型申明或定义变量时,const只会出现在变量前面。 
(3)const和被修饰的类型之间不能有其他标识符。 
(4)int const p和int const p是不同的申明语句,原因是前者const修饰的是int,后者const修饰的是int*。前者表示指针p指向整型常变量(指针所指单元的内容不允许修改),而指针本身可以指向其他的常变量,即p为指向常量的指针——常量指针。后者表示指针p本身的值不可修改,一旦p指向某个整型变量之后就不能指向其他的变量,即p是个指针常量。 
(5)引用本身可以理解为指针常量,在引用前使用const没有意义。上例中int & const r4=I;中const是多余的。即没有引用常量的说法,只有常引用。常引用指被引用对象是一个常量,不允许通过引用和修改被引用对象的值。 
在很多情况下,为表达同一种语义,可将const放在不同的位置。但在某些情况下,const只能放在特定的位置,考查const配合二重指针的例子,代码如下:

int main(int argc,char* argv[])
{
    //const配合二重指针
    int const **p1;
    int* const * p2;
    int i=5;
    int j=6;
    const int * ptr1=&i;
    int * const ptr2=&j;
    p1=&ptr1;
    p2=&ptr2;
    cout<<**p1<< " "<<**p2<<endl;
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

阅读以上代码得出如下结论: 
(1)程序的运行结果是:5 6 
(2)int const *p1和int const * p2申明的二重指针p1和p2的含义完全不同。P1不 
是指针常量,它所指向的变量的类型是int const (指向整型常量的指针)。P2也不是指针常量,其指向的变量类型是int const(整型指针常量)。若按p1=&ptr1和p2=&ptr2赋值,均产生编译错误。

2.const对象和对象的const成员

const定义一个基本类型的变量是不允许修改该变量的值。const修饰类的对象称为常对象,const修饰的类成员函数称为常函数。考查如下代码:

#include <iostream>
using namespace std;

class A{
    int num;
public:
    A(){num =5;};
    void disp();
    void disp() const;
    void set(int n){num=n;};
};

void A::disp(){
    cout<<"Another version of disp()"<<endl;
}

void A::disp() const{
    cout<<num<<endl;
}
int main(int argc,char* argv[])
{
    A a1;
    a1.set(3);
    a1.disp();
    A const a2;
    a2.disp();
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

程序执行结果是: 
Another version of disp() 

阅读以上程序,得出如下结论: 
(1)常函数的申明与定义分开进行时,两边都要使用const关键字,否则发生编译错误。 
(2)只有类的非静态成员函数可以被申明为常函数,原因是静态成员函数不含this指针,属于类级别的函数。其它类型的函数(如外部函数等)不能被申明为常函数。 
(3)一个类的两个成员函数,如果函数的返回值类型、函数名、函数的参数列表完全相同,一个是常函数,一个是普通函数,那么它们构成重载关系。如上例中的void disp()和void disp() const。这是因为void disp()和void disp() const具有不同的签名。C++把this指针也作为参数评估的一部分,由于上面的函数被定义成class A的成员函数,那么它们最终会被看作void disp(A*)和void disp(const A*),从而构成重载。 
(4)非只读对象(如a1)调用某个函数时,先寻找它的非const函数版本,如果没有找到,再调用它的const函数版本。而常对象(a2),只能调用类中定义的常函数,否则出现编译错误。 
(5)存在const和非const版本的成员函数时,普通对象若想调用const函数,应该通过建立该对象的常引用或指向该对象的常指针。如上面的程序,要用对象a1调用常函数disp(),可以使用如下语句: 
((const A&)a1).disp(); 
或者: 
((const A*)&a1)->disp(); 
(6)非只读对象中,也可以将部分数据成员定义为常量,称为类对象的常量成员。类对象的非静态常量成员必须在构造函数中初始化,且只能借助于初始化列表,因为初始化列表才是初始化,构造函数中通过赋值运算符进行的是赋值,并非初始化。

3.const修饰函数的参数和函数的返回值

在定义函数时常用到const,主要用来修饰参数和返回值。其目的是让编译器为程序员做变量的只读性检查,以使程序更加健壮。考查如下代码:

void disp1(const int &ri){
    cout<<ri<<endl;
}

void disp2(const int i){
    cout<<i<<endl;
}

const int disp3(const int& ri){
    cout<<ri<<endl;
    return ri;
}

int& disp4(int& ri){
    cout<<ri<<endl;
    return ri;
}

const int& disp5(int& ri){
    cout<<ri<<endl;
    return ri;
}

int main(int argc,char* argv[])
{
    int n=5;
    disp1(n);
    disp2(n);
    disp3(n);
    disp4(n)=6;
    disp5(n);//disp5(n)=6;是错误的
    getchar();
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

程序运行结果是: 





阅读以上代码得出如下结论: 
(1)const修饰传递调用的形参申明为常量,没有实用价值。如上例中的void disp2(cons tint i)这样的申明没有意义,因为形参i的改变并不影响实参的值。 
(2)函数的返回值是值类型时,被const修饰没有意义,因为此时返回值是一个非左 
值,本身就不能改变,上例中const int disp3(cons tint& ri)对返回值的const限定是多余的。 
(3)const修饰值类型的形参时,不构成函数重载,如void disp(const int i)与void disp(int i)。但当const修饰非值类型(引用、指针)的形参时构成函数重载,如void disp(const int& i)与void disp(int& i)。

4.常见的对const的误解

(1)误解一:用const修改的变量值一定是不能改变的。当const修饰的局部变量存储在非只读存储器中,通过指针可间接修改。 
(2)误解二:常引用或常指针,只能指向常变量,这是一个极大的误解。常引用或者常指针只能说明不能通过该引用(或者该指针)去修改被引用的对象,至于被引用对象原来是什么性质是无法由常引用(常指针)决定的。

5. 将const类型转化为非const类型

使用C++中cons_cast运算符可去除复合类型中的const或volatile属性。当大量使用const_cast是不明智的,只能说程序存在设计缺陷。使用方法见下例:

void constTest(){
    int i;
    cout<<"please input a integer:";
    cin>>i;
    const int a=i;
    int& r=const_cast<int&>(a);//若写成int& r=a;则发生编译错误
    ++r;
    cout<<a<<endl;
}
int main(int argc,char* argv[])
{
    constTest();
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

程序输入5,输出6。 
阅读以上程序,得出如下结论: 
(1)const_cast运算符的语法形式是const_cast< type_id> (expression)。 
(2)const_cast只能去除目标的const或者volatile属性,不能进行不同类型的转换。如下转换就是错误的: 
cons tint A={1,2,3}; 
char* p=const_cast< char*>(A);//不能由const int[]转换为char* 
(3)一个变量被定义为只读变量(常变量),那么它永远是常变量。cosnt_cast取消的是间接引用时的改写权限,而不能改变变量本身的const属性。 
(4)利用传统的C语言中的强制类型转换也可以将const type*类型转换为type*类型,或者将const type&转换为type&类型。但是使用const_cast会更好一些,因为const_cast转换能力较弱,目的明确,不易出错,而C风格的强制类型转换能力太强,风险较大,故建议不要采用C风格的强制类型转换。


参考文献

[1]陈刚.C++高级进阶教程[M].武汉:武汉大学出版社,2008.

时间: 2024-12-10 07:59:59

C/C++中const的用法的相关文章

C++类中const一些用法

在类中的const基本有三种用法 const int func(); // 返回值是const类型 int func(const int); // 参数为const类型 int func(int )const; // 为const类型的成员函数,只能调用类中const类型的变量: 另外,当类的实例是const类型时,也只能调用类中的const成员函数,且只有类的成员函数才能被修饰为const类型: //Point.h #include <iostream> using namespace st

C++中const关键字用法

为什么使用const?采用符号常量写出的代码更容易维护:指针常常是边读边移动,而不是边写边移动:许多函数参数是只读不写的.const最常见用途是作为数组的界和switch分情况标号(也可以用枚举符代替),分类如下: 常变量:  const 类型说明符 变量名 常引用:  const 类型说明符 &引用名 常对象:  类名 const 对象名 常成员函数:  类名::fun(形参) const 常数组:  类型说明符 const 数组名[大小] 常指针:  const 类型说明符* 指针名 ,类型

C++类中const的用法

C++ 类中的const用法总结: 先看一个例子: class A { public: A(int x) : num(x), b(x) {} void fun(const A& a); //const修饰函数形参 int GetNum(void) const;//const修饰不修改成员变量的函数 void SetNum(int x); A& operator=(const A& other);  //const修改成员函数的返回值和形式参数 const A operator*(c

C++中const的用法

1.const修饰普通变量和指针 (1).const修饰普通变量 其写法有2种:a.const type value;   b.type const value; 这两种写法本质上是一样的.其含义是:const修饰的类型为type的变量value是不可变的. (2).const修饰指针 A.const char * value; B.char * const value; C.char const * value; D.const char* const value; 对于前3种,我们换种方式,

如何牢记C/C++中const的用法?

(下面以 typename 表示C/C++内某一类型 我常常会搞混 const 放在 typename* 的前面和后面的区别,今天特地查看了它们两个各自的含义,总结了一下: const typename* ptr 是指 ptr 是个指向常量的指针( pointer to constant data ),它认定所指向的内容是个常量,因此不能通过 *ptr 来改变它所指向的内容.比如说: 1 const int apple = 0; 2 const int* thirstyBoy = &apple;

ES6中const的用法

const声明一个只读的常量.一旦声明,常量的值就不能改变.且const一旦声明变量,就必须立即初始化,不能留到以后赋值. const的作用域与let命令相同:只在声明所在的块级作用域内有效. const命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用.也与let一样不可重复声明. const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址不得改动. const如果引用的是一个对象,只能保证引用对象的这个指针不变,但对象本身的数据结构是可以改变的.如: co

C与C++中const区别

一.C中的const,功能比较单一,较容易理解. · 作用      : 被修饰的内容不可更改. · 使用场合: 修饰变量,函数参数,返回值等.(c++中应用场合要丰富的多) · 特点      : 是运行时const,因此不能取代#define用于成为数组长度等需要编译时常量的情况.同时因为是运行时const,可以只定义而不初始化,而在运行时初始化.如 const int iConst;. 另外,在c中,const变量默认是外部链接,因此在不同的编译单元中如果有同名const变量,会引发命名冲

C++中const用法总结

1. const修饰普通变量和指针const修饰变量,一般有两种写法:const TYPE value;TYPE const value;这两种写法在本质上是一样的.它的含义是:const修饰的类型为TYPE的变量value是不可变的.对于一个非指针的类型TYPE,无论怎么写,都是一个含义,即value只不可变.例如:const int nValue:         //nValue是constint const nValue:    // nValue是const但是对于指针类型的TYPE,

C++中const 的各种用法

C++中const 关键字的用法 const修饰变量 const 主要用于把一个对象转换成一个常量,例如: const int size = 512; size = 0; // error: assignment of read-only variable 上面的例子中,定义size为常量并初始化为512,变量size仍是一个左值,但是现在这个左值是不可修改的,任何修改size的尝试都会导致编译错误. 因为常量在定以后就不能被修改,因此const对象定义时必须初始化,否则会引起编译错误,例如: