c++中const是怎么使用的?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了c++中const是怎么使用的?相关的知识,希望对你有一定的参考价值。

看下代码,为什么这样比较规范呢?
什么情况下使用它比较好,我想养成一个良好的编程习惯

用来修饰变量或函数(包括成员函数)及其参数:
(1)const修饰基本数据类型
(2)const应用到函数中
(3)const在类中的用法
(4)const修饰类对象,定义常量对象
一、const修饰基本数据类型
1.const修饰一般常量及数组
const int a=10; 等价的书写方式: int const a=10;
const int arr[3]=1,2,3; int const arr[3]=1,2,3;

对于类似这些基本数据类型,修饰符const可以用在类型说明符前,也可以用在类型说明符后,其结果是一样的。在使用这些常量的时候,只要不改变这些常量的值便好。
2.const修饰指针变量*及引用变量&
介绍本部分内容之前,先说说指针和引用的一些基本知识。
指针(pointer)是用来指向实际内存地址的变量,一般来说,指针是整型,而且一般的大家会接受十六进制的输出格式。
引用(reference)是其相应变量的别名,用于向函数提供直接访问参数(而不是参数的副本)的途径,与指针相比,引用是一种受限制的指针类型,或者说是指针的一个子集,而从其功能上来看,似乎可以说引用是指针功能的一种高层实现。
关于运算符&和*:
在C++里,沿袭C中的语法,有两个一元运算符用于指针操作:&和*。按照本来的定义,&应当是取址符,*是指针符,也就是说, &用于返回变量的实际地址,*用于返回地址所指向的变量,他们应当互为逆运算。实际的情况也是如此。
在定义变量的引用的时候,&只是个定义引用的标志,不代表取地址。
***************************const修饰指针(*):*********************
const int* a = & [1] //非常量数据的常量指针 指针常量
int const *a = & [2] //非常量数据的常量指针 a is a pointer to the constant char variable

int* const a = & [3] //常量数据的非常量指针指针常量 常量指针 a is a constant pointer to the (non-constant) char variable

const int* const a = & [4] //常量数据的常量指针
可以参考《Effective c++》Item21上的做法,
如果const位于星号*的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量;
如果const位于星号的右侧,const就是修饰指针本身,即指针本身是常量。
因此,[1]和[2]的情况相同,都是指针所指向的内容为常量,这种情况下不允许对内容进行更改操作,如不能*a = 3 ;
[3]为指针本身是常量,而指针所指向的内容不是常量,这种情况下不能对指针本身进行更改操作,如a++是错误的;
[4]为指针本身和指向的内容均为常量。
****************const修饰引用(&):***************************
int const &a=x;
const int &a=x;
int &const a=x;//这种方式定义是C、C++编译器未定义,虽然不会报错,但是该句效果和int &a一样。
这两种定义方式是等价的,此时的引用a不能被更新。如:a++ 这是错误的。
二、const应用到函数中
1.作为参数的const修饰符
2.作为函数返回值的const修饰符
其实,不论是参数还是返回值,道理都是一样的,参数传入时候和函数返回的时候,初始化const变量
1 修饰参数的const,如 void fun0(const A* a ); void fun1(const A& a);
调用函数的时候,用相应的变量初始化const常量,则在函数体中,按照const所修饰的部分进行常量化,如形参为const A* a,
则不能对传递进来的指针的内容 进行改变,保护了原指针所指向的内容;如形参为const A& a,则不能对传递进来的引用对象进行改变,
保护了原对象的属性。
[注意]:参数const通常用于参数为指针或引用的情况;
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修饰返回值为对象本身(非引用和指针)的情况多用于二目操作符重载函数并产生新对象的时候。
类中的成员函数:A fun4()const; 其意义上是不能修改所在类的的任何变量。
三、类中定义常量(const的特殊用法)

在类中实现常量的定义大致有这么几种方式实现:
1.使用枚举类型
class test

enum SIZE1 = 10, SIZE2 = 20; // 枚举常量
int array1[SIZE1];
int array2[SIZE2];
;
2.使用const
不能在类声明中初始化const数据成员。以下用法是错误的,因为类的对象未被创建时,编译器不知道SIZE的值是什么。
class test

const int SIZE = 100; // 错误,企图在类声明中初始化const数据成员
int array[SIZE]; // 错误,未知的SIZE
;
正确的使用const实现方法为:const数据成员的初始化只能在类构造函数的初始化表中进行。
注意:对const成员变量的初始化,不能在变量声明的地方,必须在类的构造函数的初始化列表中完成,即使是在构造函数内部赋值也是不行的。
具体原因请参见 【初始化列表和赋值的区别】
3.使用static const
通过结合静态变量来实现:

#include<iostream.h>
class Year

private:
int y;
public:
static int const Inity;
public:
Year()

y=Inity;

;
int const Year::Inity=1997;//静态变量的赋值方法,注意必须放在类外定义
void main()

cout<<Year.Inity<<endl;//注意调用方式,这里是用类名调用的。


到这里就把在类中定义常量的方法都陈列出来了。
四、const定义常量对象,以及常量对象的用法
class test

public:
test():x(1)

y=2;

~test()

void set(int yy)

y=yy;

int getx() const

return x;

//protected:
const int x;
int y;
;
void main()

const test t;
t.set(33);//error
t.getx();

常量对象只能调用常量函数,别的成员函数都不能调用。
五、使用const的一些建议
<1>要大胆的使用const,这将给你带来无尽的益处,但前提是你必须搞清楚原委;
<2> 要避免最一般的赋值操作错误,如将const变量赋值,具体可见思考题;
<3> 在参数中使用const应该使用引用或指针,而不是一般的对象实例,原因同上;
<4> const在成员函数中的三种用法(参数、返回值、函数)要很好的使用;
<5>不要轻易的将函数的返回值类型定为const;
<6>除了重载操作符外一般不要将返回值类型定为对某个对象的const引用;
参考技术A 你好,首先你应当明白引用时C++区别于C的一点。但是在Lippleman的<深度探索C++的对象模型>中曾经指出,引用的实现说到底还是采用指针实现的,但是它比指针有自己的优势。然后再Lippleman的<C++primer>中他指出现代C++程序偏向于使用引用,而不是指针,但是并不是说指针不再使用。
C++中的const关键字是指出对象时一个常量,是不允许变动它在内存当中的值的。但是还有很多点是需要注意的。
1.指向const对象的指针 const double *ptr表明ptr指向一个double类型的对象,你可以赋值给ptr一个常量或者不是常量。比如:
double temp = 3.0;const double *ptr = &temp; //此时你不能通过ptr来更改temp的值,也就是说下面的语句是错误的:*ptr = 4.0;但是temp本身并不是const变量,下面的语句就是对的:temp = 4.0; //注意const对象一定要在对象的初始化时赋值.就是说下面的语句是错误的:
double temp = 3.0;const double* ptr; ptr = &temp; //error

2.const指针 ,和一般的const对象一样,说明指针本身是const,是不允许指向其他的对象的;比如下面的语句就是错的:
double temp = 3.0;double* const ptr = &temp; double err = 4.0;ptr = &err; //注意const对象的初始化时一定需要赋值.
但是你可以使用ptr来更改temp的值,就是说下面的语句是对的:*ptr = 10.0;

对于你上面的例子中,首先你应该知道C++的标识符的命名本身就应该包含足够的信息,而不是随便的a,b,等没有用的名字。函数print()就是说该函数是用来打印的,并不会更改对象的内容,所以你将函数的形参设为const,是为了当你在函数的实现中一不小心更改了对象的内容,编译器会及时的报错,从而能避免以后不需要的麻烦。
在标准的C中,函数形参的const说明是会被编译器直接忽略的就是说你写成const,但是你还是可以在函数的实现中更改对象的内容,但是你的const说明符告诉函数的使用者该专递给函数一个const对象,但是编译器并不负责对你的更改操作给出error提示的.在C++中,确实会给出提示的。
所以当你的函数并不会改变对象的内容时,将其定义为const对象回事很好的编程习惯的。追问

你好,谢谢你的回答,有点疑惑,既然const定义是要赋值,那么我上面的例子,定义一个对象的时候也有吗

追答

const对象在定义的时候是一定需要初始化的。函数print在开始被调用的时候,主调函数调用print并传递实参用于初始化形参pS.所以上面的例子中还是初始化了const对象啊

参考技术B 在你觉得变量内容不应该被改变的地方用const修饰 这样编译器会提醒你如果发现有些地方可能会造成改变追问

那图片上的代码是用const修饰一个对象吗,修饰一个对象是什么意思

追答

就是这个函数的参数是不能改变了,因为你里面只有打印语句阿,不需要改动变量的值。如果有语句改动值了,说明和设计的意图不符,编译器会用错误消息提醒你。

参考技术C 如果不希望函数调用的时候改变传入形参的值,就用const修饰,即上述的规范格式,否则形参别用const修饰。
可参考如下链接:http://www.cppblog.com/yishanhante/articles/21322.html?opt=admin
参考技术D

首先是const的用处,它可以用来修饰一个变量或者类的成员函数, 修饰变量时表示变量是不可修改的,修饰类的成员函数时,表示调用这个函数,不会修改类的内部成员


指针和 const 指针的功能区别: const指针表示指针指向的对象是不可修改的, 而普通指针可以,

另外,普通的指针可以当做const指针的来用,如下:

假设现在有两个指针 Tdate *p1,  const Tdata *p2

第一个函数定义为 someFunc(T *) , 那么 someFunc(p1)是可以的,someFunc(p2)是不行的,因为const指针不能转换成非const指针

第二个函数定义为someFunc(const T *) 那么someFunc(p1)和someFunc(p2)都是可以的,p1可以当做const T *来用


someFunc2的解释

假设现有对象 Tdata t;

someFunc2(Tdata re),形参是一个对象,由于c/c++是传值调用,所以每次调用这个someFunc2(t)时,都会构造一个新的Tdata形参re,并把t的内容复制到re中去,这样是一种效率的浪费,解决办法就是改成指针someFunc2(Tdata *re) 或者引用 someFunc2(Tdata &re)


不使用普通引用而是是用const引用的好处在于:

    const引用使用要求比较低,  如前面所述, 普通引用和const引用都可以调用这个函数

    const明确告诉使用者,该函数不会修改re的内容,也就是t的内容,而使用非const引用会误导使用者以为该函数要修改t的内容

本回答被提问者和网友采纳

const关键字到底该怎么用

前言

我们都知道使用const关键字限定一个变量为只读,但它是真正意义上的只读吗?实际中又该如何使用const关键字?在解答这些问题之前,我们需要先理解const关键字的基本使用。本文说明C中的const关键字,不包括C++。

基本介绍

const是constant的简写,是不变的意思。但并不是说它修饰常量,而是说它限定一个变量为只读。

修饰普通变量

例如:

const int NUM = 10; //与int const NUM等价
NUM = 9;  //编译错误,不可再次修改

由于使用了const修饰NUM,使得NUM为只读,因此尝试对NUM再次赋值的操作是非法的,编译器将会报错。正因如此,如果需要使用const修饰一个变量,那么它只能在开始声明时就赋值,否则后面就没有机会了(后面会讲到一个特殊情况)。

修饰数组

例如使用const关键字修饰数组,使其元素不允许被改变:

const int arr[] = {0,0,2,3,4}; //与int const arr[]等价
arr[2] = 1; //编译错误

试图修改arr的内容的操作是非法的,编译器将会报错:

error: assignment of read-only location ‘arr[2]’

修饰指针

修饰指针的情况比较多,主要有以下几种情况:
1.const 修饰 *p,指向的对象只读,指针的指向可变:

int a = 9;
int b = 10;
const int *p = &a;//p是一个指向int类型的const值,与int const *p等价
*p = 11;    //编译错误,指向的对象是只读的,不可通过p进行改变
p = &b;     //合法,改变了p的指向

这里为了便于理解,可认为const修饰的是*p,通常使用*对指针进行解引用来访问对象,因而,该对象是只读的。

2.const修饰p,指向的对象可变,指针的指向不可变:

int a = 9;
int b = 10;
int * const p = &a;//p是一个const指针
*p = 11;    //合法,
p = &b;     //编译错误,p是一个const指针,只读,不可变

3.指针不可改变指向,指向的内容也不可变

int a = 9;
int b = 10;
const int * const p = &a;//p既是一个const指针,同时也指向了int类型的const值
*p = 11;    //编译错误,指向的对象是只读的,不可通过p进行改变
p = &b;     //编译错误,p是一个const指针,只读,不可变

看完上面几种情况之后是否会觉得混乱,并且难以记忆呢?我们使用一句话总结:
const放在*的左侧任意位置,限定了该指针指向的对象是只读的;const放在*的右侧,限定了指针本身是只读的,即不可变的。

如果还不是很好理解,我们可以这样来看,去掉类型说明符,查看const修饰的内容,上面三种情况去掉类型说明符int之后,如下:

const *p; //修饰*p,指针指向的对象不可变
const p; //修饰p,指针不可变
const * const p; //第一个修饰了*p,第二个修饰了p,两者都不可变

const右边修饰谁,就说明谁是不可变的。上面的说法仅仅是帮助理解和记忆。借助上面这种理解,就会发现以下几种等价情况:

const int NUM = 10; //与int const NUM等价
int a = 9;
const int *p  = &a;//与int const *p等价
const int arr[] = {0,0,2,3,4}; //与int const arr[]等价

const关键字到底有什么用

前面介绍了这么多内容,是不是都常用呢?const关键字到底有什么用?

修饰函数形参

实际上,为我们可以经常发现const关键字的身影,例如很多库函数的声明:

char *strncpy(char *dest,const char *src,size_t n);//字符串拷贝函数
int  *strncmp(const char *s1,const char *s2,size_t n);//字符串比较函数

通过看strncpy函数的原型可以知道,源字符串src是只读的,不可变的,而dest并没有该限制。我们通过一个小例子继续观察:

//test.c
#include<stdio.h>
void myPrint(const char *str);
void myPrint(const char *str)
{
    str[0] = ‘H‘;
    printf("my print:%s ",str);
}
int main(void)
{
    char str[] = "hello world";
    myPrint(str);
    return 0;
}

在这个例子中,我们不希望myPrint函数修改传入的字符串内容,因此入参使用了const限定符,表明传入的字符串是只读的,因此,如果myPrint函数内部如果尝试对str进行修改,将会报错:

$ gcc -o test test.c
test.c:6:12: error: assignment of read-only location ‘*str’
     str[0] = ‘H‘;

因此,我们自己在编码过程中,如果确定传入的指针参数仅用于访问数据,那么应该将其声明为一个指向const限定类型的指针,避免函数内部对数据进行意外地修改

修饰全局变量

我们知道,使用全局变量是一种不安全的做法,因为程序的任何部分都能够对全局数据进行修改。而如果对全局变量增加const限定符(假设该全局数据不希望被修改),就可以避免被程序其他部分修改。这里有两种使用方式。
第一种,在a文件中定义,其他文件中使用外部声明,例如:
a.h

//a.h
const int ARR[] = {0,1,2,3,4,5,6,7,8,9};  //定义int数组

b.c

//b.c
extern const int ARR[];   //注意,这里不能再对ARR进行赋值
//后面可以使用ARR

第二种,在a文件中定义,并使用static修饰,b文件包含a文件,例如:
a.h

//a.h
static const int ARR[] = {0,1,2,3,4,5,6,7,8,9};  //定义int数组

b.c

//b.c
#include<a.h>
//后面可以使用ARR

注意,这里必须使用static修饰,否则多个文件包含导致编译会出现重复定义的错误。有兴趣的可以尝试一下。

const修饰的变量是真正的只读吗?

使用const修饰之后的变量真的是完全的只读吗?看下面这个例子:

#include <stdio.h>
int main(void)
{
    const int a = 2018;
    int *p = &a;
    *p = 2019;
    printf("%d ",a);
    return 0;
}

运行结果:

2019

可以看到,我们通过另外定义一个指针变量,将被const修饰的a的值改变了。那么我们不禁要问,const到底做了什么呢?它修饰的变量是真正意义上的只读吗?为什么它修饰的变量的值仍然可以改变?

#include<stdio.h>
int main(void)
{
    int a = 2019;
    //const int a = 2019;
    printf("%d ",a);
    return 0;
}

我们分别获取有const修饰和无const修饰的汇编代码。
无const修饰,汇编代码:

.LC0:
        .string "%d "
main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     DWORD PTR [rbp-4], 2019
        mov     eax, DWORD PTR [rbp-4]
        mov     esi, eax
        mov     edi, OFFSET FLAT:.LC0
        mov     eax, 0
        call    printf
        mov     eax, 0
        leave
        ret

有const修饰,汇编代码:

.LC0:
        .string "%dn"
main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     DWORD PTR [rbp-4], 2019
        mov     eax, DWORD PTR [rbp-4]
        mov     esi, eax
        mov     edi, OFFSET FLAT:.LC0
        mov     eax, 0
        call    printf
        mov     eax, 0
        leave
        ret

我们发现,并没有任何差异!当然这一个例子并不能说明所有的问题。但是我们要知道的是,const关键字告诉了编译器,它修饰的变量不能被改变,如果代码中发现有类似改变该变量的操作,那么编译器就会捕捉这个错误。

那么它在实际中的意义之一是什么呢?帮助程序员提前发现问题,避免不该修改的值被意外地修改,但是无法完全保证不被修改!例如我们可以通过对指针进行强转:

#include<stdio.h>
void myPrint(const char *str);
void myPrint(const char *str)
{
    char *b = (char *)str;
    b[0] = ‘H‘;
    printf("my print:%s ",b);

}
int main(void)
{
    char str[] = "hello world";
    myPrint(str);
    return 0;
}

运行结果:

my print:Hello world

也就是说,const关键字是给编译器用的,帮助程序员提早发现可能存在的问题。
但是实际中永远不要写这样的代码!

总结

介绍了这么多,关键点如下:

  • const关键字让编译器帮助我们发现变量不该被修改却被意外修改的错误。
  • const关键字修饰的变量并非真正意义完完全全的只读。
  • 对于不该被修改的入参,应该用const修饰,这是const使用的常见姿势。
  • const修饰的变量只能正常赋值一次。
  • 不要试图将非const数据的地址赋给普通指针。
  • 不要忽略编译器的警告,除非你很清楚在做什么。
  • 虽然可以通过某种不正规途径修改const修饰的变量,但是永远不要这么做

思考

  • 与#define相比,const有什么优点?
  • const关键字到底该什么时候用?

微信公众号【编程珠玑】:专注但不限于分享计算机编程基础,Linux,C语言,C++,算法,数据库等编程相关[原创]技术文章,号内包含大量经典电子书和视频学习资源。欢迎一起交流学习,一起修炼计算机“内功”,知其然,更知其所以然。
































































































































以上是关于c++中const是怎么使用的?的主要内容,如果未能解决你的问题,请参考以下文章

const关键字到底该怎么用

c++重载时const的作用

C++ const 常量指针

c++中Std有啥用

C++中const简介

C++中const关键字的使用总结