C++ Const详解

本文详细探讨了C++中的const关键字,包括const限定符的特性,如必须初始化且值不可改变,以及const对象的作用域。此外,还讲解了const引用、指针与const的关系,特别是指针到const和const指针的区别。文章进一步阐述了顶层const和底层const的概念,以及const在函数参数中的应用,尤其是const形参的重复定义问题。同时,介绍了const成员函数在类中的作用,模板类型左值引用参数的推断,以及constexpr和常量表达式的重要性。最后,讨论了const_cast在消除对象的const属性中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

C++ Const详解

一、const限定符

1、const限定对象必须初始化

const int i = 100;   //正确:编译时初始化
const int j;         //错误:j是一个未初始化的常量

2、const限定的值不可以改变

cosnt int i = 100;
i = 5;              //错误:试图向const修饰对象写值

const类型对象能完成非const类型所能参与的大部分操作,主要是限制是
只能在const的对象上执行不改变其内容的操作。

例:

int i = 100;
const int ci = i;       //正确:i的值拷贝给ci
int j = ci;             //正确:ci的值呗拷贝给j

///<原因:拷贝一个对象并不会改变它的内容。

3、cosnt对象仅在文件中有效

如果程序包含多个文件,定义同名全局变量不加const,
由于全局变量是外部链接所以会重复定义。

//a.h
int i = 1024;    

//b.h
int i = 512;    

//c.h
int i = 2048;   

///<编译失败,重复定义

对全局变量加上const限定符,限制变量的作用域为文件内(内部链接)。
所以不会重复定义

//a.h
const int ci = 1024;     //正确

//b.h
const int ci = 512;      //正确

//c.h
const int ci = 2048;     //正确

///<编译正确
  • 如果想只需要定义一次,在多个文件之间共享这个const对象
  • 那么对于cosnt变量不管是声明还是定义都添加extern关键字。
//a.h
extern const int ci = 1024;

//b.h
extern const int ci;      //与a.h中定义的ci是同一个,值为1024

//c.h
extern const int ci;      //与a.h中定义的ci是同一个,值为1024

///<  a.h的常量用extern限定使其被其他文件使用
///<  b.h, c.h的extern指明ci并非文件独有,它的定义在别处出现

二、const的引用

1、对常量的引用

简称“常量引用”是 “对常量的引用”。
严格来说并不存在常量引用。因为引用不是一个对象。

const int ci = 1024;
const int &cri = ci;

2、对常量的引用初始化

引用类型必须与所引用对象的类型一致。但有两个例外:
第一种就是在初始化话常量引用时允许用任意表达式作为初始,只要改表达式的结果能转成引用类型即可。
第二种基类的引用绑定到派生类对象上(本文不解释)

int i = 10;
const int &cri1 = i;          // 允许将const int&绑定到一个普通的int对象上
const int &cri2 = 42;         //正确:绑定一个临时量对象
const int &cri3 = cri1 * 2;   //正确:绑定一个临时量对象
int &ri4 = cri1 * 2;          //错误:ri4是一个普通的非常量引用
double d = 3.14;
const int & cri4 = d;        //正确:绑定一个临时量对象

常量引用之所以可以这样初始化,它绑定的是一个临时量的对象
以最后一个为例,编译器把上述代码变成下面的形式:

double d = 3.14;
const int temp = d;         //由双精度浮点数生成一个临时的整型常量
const int &cri4 = temp;     //让cri4绑定这个临时量

非const的引用可以不可以引用一个const的对象

const int ci = 1;
int &ri = ci;   //错误

const的引用可以引用一个非const的对象

int i = 42;
int &ri = i;            //引用ri绑定对象i
const int &cri = i;     //(合法行为)cri也绑定对象i,但是不允许通过cri修改i的值
ri = 0;                 //ri非常量,i的值修改为0
cri = 0;                //错误:cri是一个常量引用

三、指针和const

1、指向常量的指针(pointer to const–指针常量)

指向常量的指针可以指向常量和非常量对象
普通指针不可以指向常量对象

const double pi = 3.14;
double *ptrd = &pi;           //错误:ptrd是一个普通指针,想要常量对象的地址,只能使用指向常量的指针
const double *cptrd = &pi;    //正确:指向常量
*cptrd = 100;                 //错误:不能给*cptrd赋值

指针的类型必须与其所指对象的类型一致。但有两个例外:
第一种就是允许另一个指向常量的指针指向一个非常量对象
第二种基类的指针指向派生类对象上(本文不解释)

double d = 3.14;    
const int *cptri = d;

2、const指针(const pointer–常量指针)

指针是对象而引用不是,因此指针可以像其他类型的对象一样,允许把指针本身设置常量。
常量指针必须初始化,一但初始化完成,则值不能再改变

int i = 0;
int j = 1;
int *const cptri = &i;      //cptri将一直指向i
*cptri = 100;               //i的值修改为100
cptri = &j;                //错误:cptri的值不能修改

const double pi = 3.14;
const double * const cprtpi = & pi; //cprtpi 是一个指向常量的常量指针
*cprtpi = 2.14;             //错误: cprtpi也是指向常量的

四、顶层const和底层const

定义

顶层cosnt:本身是一个常量。
底层cosnt:指向或引用的对象是一个常量。

int i = 0;                  
int *cosnt p1 = &i;         //不能改p1的值,顶层cosnt
const int ci = 42;          //不能改p2的值,顶层cosnt
const int *p2 = &ci;        //允许改变p2的值,底层cosnt
const int *const p3 = p2;   //靠右边的const是顶层cosnt,靠左变的const是底层cosnt
const int &r = ci;          //声明引用的const都是底层cosnt 

操作

当执行对象拷贝操作时,顶层cosnt不受影响:

i = ci;     //正确:ci顶层const,操作无影响啊
p2 = p3;    //正确:p2和p3指向的对象类型相同,p3顶层cosnt的部分不影响

当执行对象拷贝操作时,关于底层cosnt,
拷入和拷出的对象必须具有相同的底层const资格,
或者两个对象的数据类型必须能够转换。
一般来说非常量可以转换成常量,反之不行

int *p = p3;        //错误:p3包含底层const,而p没有
p2 = p3;            //正确:p2和p3都是底层cosnt
p2 = &i;            //正确:非常量转换常量
int &r = ci;        //错误:普通的int& 不能绑到int常量上
const int &r2 = i;  //正确:cosnt int&可以绑定到普通int上

五、const形参和实参

1、顶层const形参

顶层const作用对象本身

const int ci = 10;   
int i = ci;         //正确

int *const cp = &i;
int *p = cp;        //正确

当实参初始化形参时会忽略掉顶层const。也就是,形参的顶层cosnt被忽略掉了。
所以当形参是const时,传给常量对象或者非常量对象都是可以的

void func(const int i){/**func能够读取i,但是不能向i写值*/}

调用func函数时, 即可以传入const int也可以传入int。

顶层const带来重复定义问题

形参忽略掉了顶层const可能产生异想不到的结果

void func(const int i){/**func能够读取i,但是不能向i写值*/}

void func(int i){/*...*/} //错误:重复定义了func

因为顶层cosnt被忽略掉了,所以上面的代码两个func函数的参数可以完全一样。因此发生了重复定义

2、指针或引用形参与cosnt

我们可以使用非常量初始化一个底层cosnt对象,但是反过来不行;
同时一个普通的引用必须同类型对象初始化

int i = 10;             
const int *cp = &i;     //正确:常量指针可以指向非常量对象
const int &r = i;       //正确:常量引用可以绑定非常量对象
const int &r2 = 10;     //正确:绑定临时量对象
int *p = cp;            //错误:底层cosnt:常量转非常量
int &r3 = r;            //错误:底层cosnt:常量转非常量
int &r4 = 100;          //错误:不能用字面值初始化非常量引用

将上面初始化规则引用到参数传递(初始化形参)上:

void func(int *);
void func(int &);

int i = 0;
const int ci = i;
unsigned int ui = 0;

func(&i);       //正确:调用int*形参
func(&ci);      //错误:不能用const int*初始int*,底层const不能转换成非cosnt
func(i);        //正确:调用int& 形参
func(ci);       //错误:不能吧普通引用绑定到const对象上
func(42);       //错误:不能用字面值初始化非常量引用
func(ui);       //错误:类型不匹配

3、重载和const

顶层cosnt不影响传入函数的对象,一个拥有顶层const的形参无法和另一个没有顶层const的形参区分出来。

Record lookup(Phone);
Record lookup(const Phone);		//重复声明了Record lookup(Phone)

Record lookup(Phone*);
Record lookup(Phone *const);		//重复声明了Record lookup(Phone*)

如果形参是底层const,可以实现重载

Record lookup(Phone& );             //函数作用于Phone的引用
Record lookup(const Phone&);        //新函数:作用于常量引用

Record lookup(Phone*);              //新函数。作用于指向Phone指针
Record lookup(const Phone *);       //新函数,作用于指向常量的指针

六、类和const

1、cosnt成员函数

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

//表示在常量成员函数func里this是一个指向常量的指针
//可以想象为void func(const A* const this);
//也就是说func可以读取它的对象的数据,但是不能写入新值。

通过区分成员函数是否是常量成员函数,可以对其进行重载。

七、模板类型左值引用参数推断类型

当一个函数参数是模板类型参数的一个普通(左值)引用时(即,T&),绑定规则告诉我们,只能传递给它一个左值。

template<typename T> void func1(T&);    //实参必须是一个左值

func1(i);   //i是一个int;模板参数类型T是int。
func1(ci);  //ci是一个const int;模板参数T是cosnt int
func1(10);  //错误:传递给一个&参数的实参必须是一个左值

如果一个函数参数是const T&,则可以传递给它任何值实参:一个对象(cosnt或非cosnt)、一个临时对象或是一个字面常量值。

template<typename T> void func2(const T&);    //可以接受一个右值

func2(i);   //i是一个int;模板参数类型T是int。
func2(ci);  //ci是一个const int;模板参数T是int
func2(10);  //const &参数可以绑定到一个右值,T是int

八、constexpr和常量表达式

1、常量表达式

常量表达式是指不会改变并且编译过程就能的到计算结果的表达式。
子面值, 用常量表达式初始化的const对象

const int max =20;          //max常量表达式
const int limit = max + 1;  //limit常量表达式
int i = 100;                //i不是常量表达式, i值会改变
const int sz = get_size();  //sz不是常量表达式,sz运行时才能得到具体指

2、constexpr变量

在复杂的系统中,很难分辨一个初始值到底是不是常量表达式。

c++11新标准中规定,允许将变量声明为constexpr类型以便有编译器来验证变量的值是否是一个常量表达式。
声明为constexpr的变量一定是一个常量,而且必须用常量表达式初始化。

constexpr int m = 20;       //20是常量表达式
constexpr int l = m + 1;    //m + 1是常量表达式
constexpr int sz = size();  //只有当size()是一个m + 1函数时才是一条正确的表达式

虽然不能用普通函数作为constexpr变量的初始值,
但新标准允许定义一种特殊的constexpr函数。这种函数应该足够简单,简单到编译时就可以计算其结果。
这样就可以用constexpr函数去初始化constexpr变量

3、字面值类型

常量表达式的值需要在编译时就得到计算,因此对声明constexpr时用到的类型必须有所限制。因为这些类型一般比较简单,值也显而易见,容易得到,就把它们称为“字面值”。
算术类型、引用、和指针,字面值常量类是字面值类型。
自定义类、IO类不属于该类型。

4、指针和constexpr

在constexpr声明中如果定义了一个指针,限定符constexpr仅对指针有效,与指针所指对象无关

const int *p = nullptr; //p是一个指向整型常量的指针
constexpr int *q = nullptr; //q是一个指向整数的常量
///> p 指向常量的指针, q常量指针(顶层cosnt)

constexpr把它多定义的对象置为顶层const

与其他常量指针类似, constexpr可以指向常量或非常量

int j = 0;
constexpr int i = 100;      //i的类型是整型常量

constexpr const int *p = &i;    //p是常量指针,指向整型常量o
constexpr int * p1 = &j;        //p1是常量指针,指向整数j

5、constexpr函数

constexpr函数是指能用于常量表达式的函数。
定义constexpr函数与其函数类似,不过要遵循几项约定:函数的返回类型及所有形参的类型都得是字面值类型,而且函数体中必须有且只有一条return语句:

constexpr int size() {return 100;}

constexpr int sz = size();          //正确:sz是一个常量表达式

执行该初始化任务时,编译器把对constexpr函数的调用替换成其结果值。
为了能在编译过程中随时展开,constexpr函数被隐式的指定为内联函数。

我们允许constexpr函数的返回值并非一个常量

constexpr size_t scale(size_t cnt)
{
    return size() * cnt;
}

当scale的实参是常量表达式时,它的返回值也是常量表达式;反之不然:

int arr[scale(10)];         //正确:scale(2)是常量表达式
int i= 2;                   //i不是常量表达式
int a2[scale(i)];           //错误:scale(i)不是常量表达式

九、const_cast

const_cast只能修改运算对象的底层const,const_cast可以去掉const性质。

const char *pc;
char *p = const_cast<char *>(pc);   //正确

const_cast和重载

const string &shorterString(cosnt string &s1, cosnt string &s2)
{
    return s1.szie() <= s2.size() ? s1 : s2;
}

//函数的返回类型都是const string的引用。当我们用两个实参调用这个函数,返回的结果仍然const string的引用。
//下面的shorterString当实参不是常量时,得到的结果是一个普通的引用。

string &shorterString(string &s1, string &s12)
{
    auto &r = shorterString(const_cast<const string&>)(s1), const_cast<const string&>)(s2));
    return const_cast<string&>(r);
}
//const_cast将string &强制转换为const string&
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值