C++ Const详解

C++ Const详解

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

1const int i = 100; //正确:编译时初始化

2const int j; //错误:j是一个未初始化的常量

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

1cosnt int i = 100;

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

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

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

2.对全局变量加上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; 2const int &cri = ci;

2、对常量的引用初始化

引用类型必须与所引用对象的类型一致。但有两个例外:

第一种就是在初始化常量引用时允许用任意表达式作为初始,只要改表达式的结果能转成引用类型即可。

第二种基类的引用绑定到派生类对象上(本文不解释)

int i = 10;

const int &cri1 = i; // 允许将const int&绑定到一个普通的int对象上

const int &cri2 = 42; //正确:绑定一个临时量对象 //编译器会给常量42开辟一片内存,并将引用名作为这片内存的别名

const int &cri3 = cri1 * 2; //正确:绑定一个临时量对象 ;

int &ri4 = cri1 * 2; //错误:ri4是一个普通的非常量引用

 double d = 3.14;

const int & cri4 = d; //正确:绑定一个临时量对象

int &d=15; //error: invalid initialization of non-const reference of type 'int&' from an rvalue of type 'int'

int* p = (int *)&c; //const转非const必须显式

*p = 10; //修改了为15开辟的内存内容

常量引用之所以可以这样初始化,它绑定的是一个临时量的对象

以最后一个为例,编译器把上述代码变成下面的形式:

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; 2const 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; //不能改ci的值,顶层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可能产生异想不到的结果

1void func(const int i){/**func能够读取i,但是不能向i写值*/} 2 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)]; //正确:10是常量表达式,scale(10)是常量表达式。

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&

十、常方法和常对象

1.常方法 其中this指针由Test* const--> const Test*const 

void func() const;

①可以访问对象中的常成员,也可以访问普通成员

②该方法不允许修改任何数据的值 

特别地, 静态数据成员在const函数中可以修改

2.常对象:常对象只能调用该类中的常成员函数常方法

3.常数据成员: 

①只能通过构造函数的初始化表就行初始化,其他方式皆不允许

②可以被const成员函数访问,也可以被该类中普通函数访问(但不允许修改其中的值)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值